mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2024-12-22 11:39:22 +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
|
**/tool/.secrets-prod.json
|
||||||
**/lib/.secrets.g.dart
|
**/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:flutter/foundation.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/balance.dart';
|
import 'package:cake_wallet/src/domain/common/balance.dart';
|
||||||
|
|
||||||
class BitcoinBalance extends Balance {
|
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 confirmed;
|
||||||
final int unconfirmed;
|
final int unconfirmed;
|
||||||
|
|
||||||
int get total => confirmed + unconfirmed;
|
int get total => confirmed + unconfirmed;
|
||||||
|
|
||||||
String get confirmedFormatted => bitcoinAmountToString(amount: confirmed);
|
String get confirmedFormatted => bitcoinAmountToString(amount: confirmed);
|
||||||
|
|
||||||
String get unconfirmedFormatted => bitcoinAmountToString(amount: unconfirmed);
|
String get unconfirmedFormatted => bitcoinAmountToString(amount: unconfirmed);
|
||||||
|
|
||||||
String get totalFormatted => bitcoinAmountToString(amount: total);
|
String get totalFormatted => bitcoinAmountToString(amount: total);
|
||||||
|
|
||||||
|
String toJSON() =>
|
||||||
|
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,61 +1,78 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
|
|
||||||
import 'package:flutter/foundation.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/bitcoin_transaction_info.dart';
|
||||||
import 'package:cake_wallet/bitcoin/electrum.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/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 {
|
part 'bitcoin_transaction_history.g.dart';
|
||||||
BitcoinTransactionHistory(
|
|
||||||
{@required this.eclient,
|
// TODO: Think about another transaction store for bitcoin transaction history..
|
||||||
@required this.path,
|
|
||||||
@required String password,
|
const _transactionsHistoryFileName = 'transactions.json';
|
||||||
@required this.wallet})
|
|
||||||
: _transactions = BehaviorSubject<List<TransactionInfo>>.seeded([]),
|
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,
|
_password = password,
|
||||||
_height = 0;
|
_height = 0;
|
||||||
|
|
||||||
final BitcoinWallet wallet;
|
BitcoinWallet wallet;
|
||||||
final ElectrumClient eclient;
|
final ElectrumClient eclient;
|
||||||
final String path;
|
final String path;
|
||||||
final String _password;
|
final String _password;
|
||||||
int _height;
|
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 {
|
Future<void> init() async {
|
||||||
|
// TODO: throw exeption if wallet is null;
|
||||||
final info = await _read();
|
final info = await _read();
|
||||||
_height = (info['height'] as int) ?? _height;
|
_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
|
@override
|
||||||
Future<List<TransactionInfo>> getAll() async => _transactions.value;
|
Future update() async {
|
||||||
|
await super.update();
|
||||||
|
_updateHeight();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future update() async {
|
Future<List<BitcoinTransactionInfo>> fetchTransactions() async {
|
||||||
if (_isUpdating) {
|
final addresses = wallet.addresses;
|
||||||
return;
|
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 {
|
return historiesWithDetails
|
||||||
_isUpdating = true;
|
.map((info) => BitcoinTransactionInfo.fromHexAndHeader(
|
||||||
final newTransasctions = await fetchTransactions();
|
info['raw'] as String, info['header'] as Map<String, Object>,
|
||||||
_transactions.value = _transactions.value + newTransasctions;
|
addresses: addresses.map((record) => record.address).toList()))
|
||||||
_updateHeight();
|
.toList();
|
||||||
await save();
|
|
||||||
_isUpdating = false;
|
|
||||||
} catch (e) {
|
|
||||||
_isUpdating = false;
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map<String, Object>> fetchTransactionInfo(
|
Future<Map<String, Object>> fetchTransactionInfo(
|
||||||
|
@ -69,51 +86,20 @@ class BitcoinTransactionHistory extends TransactionHistory {
|
||||||
return {'raw': raw, 'header': header};
|
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 {
|
Future<void> add(List<BitcoinTransactionInfo> transactions) async {
|
||||||
final txs = await getAll()
|
this.transactions.addAll(transactions);
|
||||||
..addAll(transactions);
|
await save();
|
||||||
await writeData(
|
|
||||||
path: path,
|
|
||||||
password: _password,
|
|
||||||
data: json
|
|
||||||
.encode(txs.map((tx) => (tx as BitcoinTransactionInfo).toJson())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addOne(BitcoinTransactionInfo tx) async {
|
Future<void> addOne(BitcoinTransactionInfo tx) async {
|
||||||
final txs = await getAll()
|
transactions.add(tx);
|
||||||
..add(tx);
|
await save();
|
||||||
await writeData(
|
|
||||||
path: path,
|
|
||||||
password: _password,
|
|
||||||
data: json
|
|
||||||
.encode(txs.map((tx) => (tx as BitcoinTransactionInfo).toJson())));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> save() async => writeData(
|
Future<void> save() async => writeData(
|
||||||
path: path,
|
path: path,
|
||||||
password: _password,
|
password: _password,
|
||||||
data: json
|
data: json.encode({'height': _height, 'transactions': transactions}));
|
||||||
.encode({'height': _height, 'transactions': _transactions.value}));
|
|
||||||
|
|
||||||
Future<Map<String, Object>> _read() async {
|
Future<Map<String, Object>> _read() async {
|
||||||
try {
|
try {
|
||||||
|
@ -133,13 +119,13 @@ class BitcoinTransactionHistory extends TransactionHistory {
|
||||||
|
|
||||||
return {'transactions': transactions, 'height': height};
|
return {'transactions': transactions, 'height': height};
|
||||||
} catch (_) {
|
} catch (_) {
|
||||||
return {'transactions': List<TransactionInfo>(), 'height': 0};
|
return {'transactions': <TransactionInfo>[], 'height': 0};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _updateHeight() {
|
void _updateHeight() {
|
||||||
final int newHeight = _transactions.value
|
final newHeight = transactions.fold(
|
||||||
.fold(0, (acc, val) => val.height > acc ? val.height : acc);
|
0, (int acc, val) => val.height > acc ? val.height : acc);
|
||||||
_height = newHeight > _height ? newHeight : _height;
|
_height = newHeight > _height ? newHeight : _height;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
|
||||||
String amountFormatted() => bitcoinAmountToString(amount: amount);
|
String amountFormatted() => bitcoinAmountToString(amount: amount);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String fiatAmount() => '';
|
String fiatAmount() => '\$ 24.5';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final m = Map<String, dynamic>();
|
final m = Map<String, dynamic>();
|
||||||
|
|
|
@ -1,251 +1,184 @@
|
||||||
import 'dart:async';
|
|
||||||
import 'dart:convert';
|
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
|
import 'dart:convert';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/sync_status.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:rxdart/rxdart.dart';
|
|
||||||
import 'package:bip39/bip39.dart' as bip39;
|
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/bitcoin_flutter.dart' as bitcoin;
|
||||||
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
|
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/file.dart';
|
||||||
import 'package:cake_wallet/bitcoin/electrum.dart';
|
import 'package:cake_wallet/bitcoin/electrum.dart';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_transaction_history.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
|
|
||||||
import 'package:cake_wallet/src/domain/common/node.dart';
|
import 'package:cake_wallet/src/domain/common/node.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/pending_transaction.dart';
|
import 'package:cake_wallet/core/wallet_base.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';
|
|
||||||
|
|
||||||
class BitcoinWallet extends Wallet {
|
part 'bitcoin_wallet.g.dart';
|
||||||
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>();
|
|
||||||
|
|
||||||
@override
|
/* TODO: Save balance to a wallet file.
|
||||||
Observable<BitcoinBalance> get onBalanceChange => _onBalanceChange.stream;
|
Load balance from the wallet file in `init` method.
|
||||||
|
*/
|
||||||
|
|
||||||
@override
|
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
|
||||||
Observable<SyncStatus> get syncStatus => _syncStatus.stream;
|
|
||||||
|
|
||||||
@override
|
abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
|
||||||
String get name => path.split('/').last ?? '';
|
static BitcoinWallet fromJSON(
|
||||||
@override
|
{@required String password,
|
||||||
String get address => hdwallet.address;
|
@required String name,
|
||||||
String get xpub => hdwallet.base58;
|
@required String dirPath,
|
||||||
|
String jsonSource}) {
|
||||||
final String path;
|
final data = json.decode(jsonSource) as Map;
|
||||||
final bitcoin.HDWallet hdwallet;
|
final mnemonic = data['mnemonic'] as String;
|
||||||
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;
|
|
||||||
final accountIndex =
|
final accountIndex =
|
||||||
(jsoned['account_index'] == "null" || jsoned['account_index'] == null)
|
(data['account_index'] == "null" || data['account_index'] == null)
|
||||||
? 0
|
? 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,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
name: name,
|
name: name,
|
||||||
accountIndex: accountIndex);
|
accountIndex: accountIndex,
|
||||||
|
initialAddresses: addresses,
|
||||||
|
initialBalance: balance);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<BitcoinWallet> build(
|
static BitcoinWallet build(
|
||||||
{@required String mnemonic,
|
{@required String mnemonic,
|
||||||
@required String password,
|
@required String password,
|
||||||
@required String name,
|
@required String name,
|
||||||
int accountIndex = 0}) async {
|
@required String dirPath,
|
||||||
final hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
|
List<BitcoinAddressRecord> initialAddresses,
|
||||||
network: bitcoin.bitcoin);
|
BitcoinBalance initialBalance,
|
||||||
final walletDirPath =
|
int accountIndex = 0}) {
|
||||||
await pathForWalletDir(name: name, type: WalletType.bitcoin);
|
final walletPath = '$dirPath/$name';
|
||||||
final walletPath = '$walletDirPath/$name';
|
|
||||||
final historyPath = '$walletDirPath/transactions.json';
|
|
||||||
final eclient = ElectrumClient();
|
final eclient = ElectrumClient();
|
||||||
final wallet = BitcoinWallet(
|
final history = BitcoinTransactionHistory(
|
||||||
hdwallet: hd,
|
eclient: eclient, dirPath: dirPath, password: password);
|
||||||
|
|
||||||
|
return BitcoinWallet._internal(
|
||||||
eclient: eclient,
|
eclient: eclient,
|
||||||
path: walletPath,
|
path: walletPath,
|
||||||
|
name: name,
|
||||||
mnemonic: mnemonic,
|
mnemonic: mnemonic,
|
||||||
password: password,
|
password: password,
|
||||||
accountIndex: accountIndex);
|
accountIndex: accountIndex,
|
||||||
final history = BitcoinTransactionHistory(
|
initialAddresses: initialAddresses,
|
||||||
eclient: eclient,
|
initialBalance: initialBalance,
|
||||||
path: historyPath,
|
transactionHistory: history);
|
||||||
password: password,
|
|
||||||
wallet: wallet);
|
|
||||||
wallet.history = history;
|
|
||||||
await history.init();
|
|
||||||
await wallet.updateInfo();
|
|
||||||
|
|
||||||
return wallet;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> getAddresses() => _accountIndex == 0
|
BitcoinWalletBase._internal(
|
||||||
? [address]
|
{@required this.eclient,
|
||||||
: List<String>.generate(
|
@required this.path,
|
||||||
_accountIndex, (i) => _getAddress(hd: hdwallet, index: i));
|
@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;
|
_accountIndex += 1;
|
||||||
final address = _getAddress(hd: hdwallet, index: _accountIndex);
|
final address = BitcoinAddressRecord(
|
||||||
|
_getAddress(hd: hd, index: _accountIndex),
|
||||||
|
label: label);
|
||||||
|
addresses.add(address);
|
||||||
|
|
||||||
await save();
|
await save();
|
||||||
|
|
||||||
return address;
|
return address;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
Future<void> updateAddress(String address, {String label}) async {
|
||||||
Future close() async {
|
for (final addr in addresses) {
|
||||||
await _addressUpdatesSubscription?.cancel();
|
if (addr.address == address) {
|
||||||
}
|
addr.label = label;
|
||||||
|
await save();
|
||||||
@override
|
break;
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<PendingTransaction> createTransaction(
|
Future<void> startSync() async {}
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getAddress() async => address;
|
Future<void> connectToNode({@required Node node}) async {}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<int> getCurrentHeight() async => 0;
|
Future<void> createTransaction(Object credentials) async {}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> getFilename() async => path.split('/').last ?? '';
|
Future<void> save() async =>
|
||||||
|
await write(path: path, password: _password, data: toJSON());
|
||||||
|
|
||||||
@override
|
String toJSON() => json.encode({
|
||||||
Future<String> getFullBalance() async =>
|
'mnemonic': mnemonic,
|
||||||
bitcoinAmountToString(amount: _onBalanceChange.value.total);
|
'account_index': _accountIndex.toString(),
|
||||||
|
'addresses': addresses.map((addr) => addr.toJSON()).toList(),
|
||||||
@override
|
'balance': balance?.toJSON()
|
||||||
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 _getAddress({bitcoin.HDWallet hd, int index}) => bitcoin
|
String _getAddress({bitcoin.HDWallet hd, int index}) => bitcoin
|
||||||
.P2PKH(
|
.P2PKH(
|
||||||
|
@ -256,9 +189,8 @@ class BitcoinWallet extends Wallet {
|
||||||
|
|
||||||
Future<Map<String, int>> _fetchBalances() async {
|
Future<Map<String, int>> _fetchBalances() async {
|
||||||
final balances = await Future.wait(
|
final balances = await Future.wait(
|
||||||
getAddresses().map((address) => eclient.getBalance(address: address)));
|
addresses.map((record) => eclient.getBalance(address: record.address)));
|
||||||
final balance =
|
final balance = balances.fold(<String, int>{}, (Map<String, int> acc, val) {
|
||||||
balances.fold(Map<String, int>(), (Map<String, int> acc, val) {
|
|
||||||
acc['confirmed'] =
|
acc['confirmed'] =
|
||||||
(val['confirmed'] as int ?? 0) + (acc['confirmed'] ?? 0);
|
(val['confirmed'] as int ?? 0) + (acc['confirmed'] ?? 0);
|
||||||
acc['unconfirmed'] =
|
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(
|
Future<void> write(
|
||||||
{@required String path,
|
{@required String path,
|
||||||
@required String password,
|
@required String password,
|
||||||
@required Map<String, String> obj}) async {
|
@required String data}) async {
|
||||||
final jsoned = json.encode(obj);
|
|
||||||
final keys = extractKeys(password);
|
final keys = extractKeys(password);
|
||||||
final key = encrypt.Key.fromBase64(keys.first);
|
final key = encrypt.Key.fromBase64(keys.first);
|
||||||
final iv = encrypt.IV.fromBase64(keys.last);
|
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);
|
final f = File(path);
|
||||||
f.writeAsStringSync(encrypted);
|
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:flutter/foundation.dart';
|
||||||
import 'package:mobx/mobx.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 {
|
Future setPassword(String password) async {
|
||||||
@observable
|
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
|
||||||
SetupPinCodeState setupPinCodeState;
|
final encodedPassword = encodedPinCode(pin: password);
|
||||||
|
await secureStorage.write(key: key, value: encodedPassword);
|
||||||
Future<void> setupPinCode({@required String pin}) async {}
|
|
||||||
|
|
||||||
Future<bool> authenticate({@required String pin}) async {
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void resetSetupPinCodeState() =>
|
Future<bool> canAuthenticate() async {
|
||||||
setupPinCodeState = InitialSetupPinCodeState();
|
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:mobx/mobx.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
|
||||||
|
|
||||||
abstract class TranasctionHistoryBase<TransactionType> {
|
abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> {
|
||||||
TranasctionHistoryBase() : _isUpdating = false;
|
TransactionHistoryBase() : _isUpdating = false;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
List<TransactionType> transactions;
|
ObservableList<TransactionType> transactions;
|
||||||
|
|
||||||
bool _isUpdating;
|
bool _isUpdating;
|
||||||
|
|
||||||
|
@ -15,7 +16,7 @@ abstract class TranasctionHistoryBase<TransactionType> {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
_isUpdating = false;
|
_isUpdating = false;
|
||||||
transactions = await fetchTransactions();
|
transactions.addAll(await fetchTransactions());
|
||||||
_isUpdating = true;
|
_isUpdating = true;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_isUpdating = false;
|
_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: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/node.dart';
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
|
|
||||||
abstract class WalletBase<BalaceType> {
|
abstract class WalletBase<BalaceType> {
|
||||||
|
WalletType type;
|
||||||
|
|
||||||
String get name;
|
String get name;
|
||||||
|
|
||||||
String get filename;
|
|
||||||
|
|
||||||
@observable
|
|
||||||
String address;
|
String address;
|
||||||
|
|
||||||
@observable
|
|
||||||
BalaceType balance;
|
BalaceType balance;
|
||||||
|
|
||||||
|
TransactionHistoryBase transactionHistory;
|
||||||
|
|
||||||
Future<void> connectToNode({@required Node node});
|
Future<void> connectToNode({@required Node node});
|
||||||
|
|
||||||
Future<void> startSync();
|
Future<void> startSync();
|
||||||
|
|
||||||
Future<void> createTransaction(Object credentials);
|
Future<void> createTransaction(Object credentials);
|
||||||
|
|
||||||
Future<void> save();
|
Future<void> save();
|
||||||
}
|
}
|
|
@ -1,34 +1,47 @@
|
||||||
import 'package:cake_wallet/core/wallet_creation_state.dart';
|
|
||||||
import 'package:flutter/foundation.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/wallet_credentials.dart';
|
||||||
import 'package:cake_wallet/core/bitcoin_wallet_list_service.dart';
|
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
|
||||||
import 'package:cake_wallet/core/monero_wallet_list_service.dart';
|
import 'package:cake_wallet/monero/monero_wallet_service.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/wallet_type.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 {
|
||||||
|
WalletCreationService(
|
||||||
class WalletCreationService = WalletCreationServiceBase
|
{WalletType initialType,
|
||||||
with _$WalletCreationService;
|
this.appStore,
|
||||||
|
this.secureStorage,
|
||||||
abstract class WalletCreationServiceBase with Store {
|
this.sharedPreferences})
|
||||||
@observable
|
: type = initialType {
|
||||||
WalletCreationState state;
|
if (type != null) {
|
||||||
|
changeWalletType(type: type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
WalletType 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}) {
|
void changeWalletType({@required WalletType type}) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case WalletType.monero:
|
case WalletType.monero:
|
||||||
_service = MoneroWalletListService();
|
_service = MoneroWalletService();
|
||||||
break;
|
break;
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
_service = BitcoinWalletListService();
|
_service = BitcoinWalletService();
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -36,32 +49,45 @@ abstract class WalletCreationServiceBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> create(WalletCredentials credentials) async {
|
Future<void> create(WalletCredentials credentials) async {
|
||||||
try {
|
final password = generateWalletPassword(type);
|
||||||
state = WalletCreating();
|
credentials.password = password;
|
||||||
await _service.create(credentials);
|
await saveWalletPassword(password: password, walletName: credentials.name);
|
||||||
state = WalletCreatedSuccessfully();
|
final wallet = await _service.create(credentials);
|
||||||
} catch (e) {
|
appStore.wallet = wallet;
|
||||||
state = WalletCreationFailure(error: e.toString());
|
appStore.authenticationStore.allowed();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> restoreFromKeys(WalletCredentials credentials) async {
|
Future<void> restoreFromKeys(WalletCredentials credentials) async {
|
||||||
try {
|
final password = generateWalletPassword(type);
|
||||||
state = WalletCreating();
|
credentials.password = password;
|
||||||
await _service.restoreFromKeys(credentials);
|
await saveWalletPassword(password: password, walletName: credentials.name);
|
||||||
state = WalletCreatedSuccessfully();
|
final wallet = await _service.restoreFromKeys(credentials);
|
||||||
} catch (e) {
|
appStore.wallet = wallet;
|
||||||
state = WalletCreationFailure(error: e.toString());
|
appStore.authenticationStore.allowed();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> restoreFromSeed(WalletCredentials credentials) async {
|
Future<void> restoreFromSeed(WalletCredentials credentials) async {
|
||||||
try {
|
final password = generateWalletPassword(type);
|
||||||
state = WalletCreating();
|
credentials.password = password;
|
||||||
await _service.restoreFromSeed(credentials);
|
await saveWalletPassword(password: password, walletName: credentials.name);
|
||||||
state = WalletCreatedSuccessfully();
|
final wallet = await _service.restoreFromSeed(credentials);
|
||||||
} catch (e) {
|
appStore.wallet = wallet;
|
||||||
state = WalletCreationFailure(error: e.toString());
|
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 {
|
abstract class WalletCredentials {
|
||||||
const WalletCredentials({this.name, this.password});
|
WalletCredentials({this.name, this.password});
|
||||||
|
|
||||||
final String name;
|
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/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/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:flutter_localizations/flutter_localizations.dart';
|
||||||
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.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/send_template/send_template_store.dart';
|
||||||
import 'package:cake_wallet/src/stores/exchange_template/exchange_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/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/settings/settings_store.dart';
|
||||||
import 'package:cake_wallet/src/stores/price/price_store.dart';
|
import 'package:cake_wallet/src/stores/price/price_store.dart';
|
||||||
import 'package:cake_wallet/src/domain/services/user_service.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 {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
setup();
|
||||||
|
|
||||||
final appDir = await getApplicationDocumentsDirectory();
|
final appDir = await getApplicationDocumentsDirectory();
|
||||||
Hive.init(appDir.path);
|
Hive.init(appDir.path);
|
||||||
Hive.registerAdapter(ContactAdapter());
|
Hive.registerAdapter(ContactAdapter());
|
||||||
|
@ -87,13 +96,13 @@ void main() async {
|
||||||
sharedPreferences: sharedPreferences);
|
sharedPreferences: sharedPreferences);
|
||||||
final userService = UserService(
|
final userService = UserService(
|
||||||
sharedPreferences: sharedPreferences, secureStorage: secureStorage);
|
sharedPreferences: sharedPreferences, secureStorage: secureStorage);
|
||||||
final authenticationStore = AuthenticationStore(userService: userService);
|
// final authenticationStore = AuthenticationStore(userService: userService);
|
||||||
|
|
||||||
await initialSetup(
|
await initialSetup(
|
||||||
sharedPreferences: sharedPreferences,
|
sharedPreferences: sharedPreferences,
|
||||||
walletListService: walletListService,
|
walletListService: walletListService,
|
||||||
nodes: nodes,
|
nodes: nodes,
|
||||||
authStore: authenticationStore,
|
// authStore: authenticationStore,
|
||||||
initialMigrationVersion: 2);
|
initialMigrationVersion: 2);
|
||||||
|
|
||||||
final settingsStore = await SettingsStoreBase.load(
|
final settingsStore = await SettingsStoreBase.load(
|
||||||
|
@ -119,8 +128,7 @@ void main() async {
|
||||||
|
|
||||||
final walletCreationService = WalletCreationService();
|
final walletCreationService = WalletCreationService();
|
||||||
final authService = AuthService();
|
final authService = AuthService();
|
||||||
final appStore = AppService(
|
|
||||||
walletCreationService: walletCreationService, authService: authService);
|
|
||||||
|
|
||||||
setReactions(
|
setReactions(
|
||||||
settingsStore: settingsStore,
|
settingsStore: settingsStore,
|
||||||
|
@ -128,7 +136,7 @@ void main() async {
|
||||||
syncStore: syncStore,
|
syncStore: syncStore,
|
||||||
walletStore: walletStore,
|
walletStore: walletStore,
|
||||||
walletService: walletService,
|
walletService: walletService,
|
||||||
authenticationStore: authenticationStore,
|
// authenticationStore: authenticationStore,
|
||||||
loginStore: loginStore);
|
loginStore: loginStore);
|
||||||
|
|
||||||
runApp(MultiProvider(providers: [
|
runApp(MultiProvider(providers: [
|
||||||
|
@ -141,7 +149,7 @@ void main() async {
|
||||||
Provider(create: (_) => walletStore),
|
Provider(create: (_) => walletStore),
|
||||||
Provider(create: (_) => syncStore),
|
Provider(create: (_) => syncStore),
|
||||||
Provider(create: (_) => balanceStore),
|
Provider(create: (_) => balanceStore),
|
||||||
Provider(create: (_) => authenticationStore),
|
// Provider(create: (_) => authenticationStore),
|
||||||
Provider(create: (_) => contacts),
|
Provider(create: (_) => contacts),
|
||||||
Provider(create: (_) => nodes),
|
Provider(create: (_) => nodes),
|
||||||
Provider(create: (_) => transactionDescriptions),
|
Provider(create: (_) => transactionDescriptions),
|
||||||
|
@ -149,7 +157,7 @@ void main() async {
|
||||||
Provider(create: (_) => seedLanguageStore),
|
Provider(create: (_) => seedLanguageStore),
|
||||||
Provider(create: (_) => sendTemplateStore),
|
Provider(create: (_) => sendTemplateStore),
|
||||||
Provider(create: (_) => exchangeTemplateStore),
|
Provider(create: (_) => exchangeTemplateStore),
|
||||||
Provider(create: (_) => appStore),
|
// Provider(create: (_) => appStore),
|
||||||
Provider(create: (_) => walletCreationService),
|
Provider(create: (_) => walletCreationService),
|
||||||
Provider(create: (_) => authService)
|
Provider(create: (_) => authService)
|
||||||
], child: CakeWalletApp()));
|
], child: CakeWalletApp()));
|
||||||
|
@ -159,7 +167,7 @@ Future<void> initialSetup(
|
||||||
{WalletListService walletListService,
|
{WalletListService walletListService,
|
||||||
SharedPreferences sharedPreferences,
|
SharedPreferences sharedPreferences,
|
||||||
Box<Node> nodes,
|
Box<Node> nodes,
|
||||||
AuthenticationStore authStore,
|
// AuthenticationStore authStore,
|
||||||
int initialMigrationVersion = 1,
|
int initialMigrationVersion = 1,
|
||||||
WalletType initialWalletType = WalletType.bitcoin}) async {
|
WalletType initialWalletType = WalletType.bitcoin}) async {
|
||||||
await walletListService.changeWalletManger(walletType: initialWalletType);
|
await walletListService.changeWalletManger(walletType: initialWalletType);
|
||||||
|
@ -167,7 +175,12 @@ Future<void> initialSetup(
|
||||||
version: initialMigrationVersion,
|
version: initialMigrationVersion,
|
||||||
sharedPreferences: sharedPreferences,
|
sharedPreferences: sharedPreferences,
|
||||||
nodes: nodes);
|
nodes: nodes);
|
||||||
await authStore.started();
|
// await authStore.started();
|
||||||
|
await bootstrap();
|
||||||
|
// final authenticationStore = getIt.get<AuthenticationStore>();
|
||||||
|
// FIXME
|
||||||
|
// authenticationStore.state = AuthenticationState.denied;
|
||||||
|
|
||||||
monero_wallet.onStartup();
|
monero_wallet.onStartup();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -241,6 +254,8 @@ class MaterialAppWithTheme extends StatelessWidget {
|
||||||
nodes: nodes,
|
nodes: nodes,
|
||||||
trades: trades,
|
trades: trades,
|
||||||
transactionDescriptions: transactionDescriptions),
|
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';
|
part 'monero_transaction_history.g.dart';
|
||||||
|
|
||||||
List<TransactionInfo> _getAllTransactions(dynamic _) =>
|
List<MoneroTransactionInfo> _getAllTransactions(dynamic _) =>
|
||||||
monero_transaction_history
|
monero_transaction_history
|
||||||
.getAllTransations()
|
.getAllTransations()
|
||||||
.map((row) => MoneroTransactionInfo.fromRow(row))
|
.map((row) => MoneroTransactionInfo.fromRow(row))
|
||||||
|
@ -18,9 +18,13 @@ class MoneroTransactionHistory = MoneroTransactionHistoryBase
|
||||||
with _$MoneroTransactionHistory;
|
with _$MoneroTransactionHistory;
|
||||||
|
|
||||||
abstract class MoneroTransactionHistoryBase
|
abstract class MoneroTransactionHistoryBase
|
||||||
extends TranasctionHistoryBase<TransactionInfo> with Store {
|
extends TransactionHistoryBase<MoneroTransactionInfo> with Store {
|
||||||
|
MoneroTransactionHistoryBase() {
|
||||||
|
transactions = ObservableList<MoneroTransactionInfo>();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<TransactionInfo>> fetchTransactions() async {
|
Future<List<MoneroTransactionInfo>> fetchTransactions() async {
|
||||||
monero_transaction_history.refreshTransactions();
|
monero_transaction_history.refreshTransactions();
|
||||||
return _getAllTransactions(null);
|
return _getAllTransactions(null);
|
||||||
}
|
}
|
|
@ -1,35 +1,36 @@
|
||||||
import 'package:cake_wallet/core/monero_balance.dart';
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:cake_wallet/core/monero_transaction_history.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/common/sync_status.dart';
|
||||||
import 'package:cake_wallet/src/domain/monero/account.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/account_list.dart';
|
||||||
import 'package:cake_wallet/src/domain/monero/subaddress.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: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:cake_wallet/src/domain/common/node.dart';
|
||||||
import 'package:cw_monero/wallet.dart' as monero_wallet;
|
import 'package:cw_monero/wallet.dart' as monero_wallet;
|
||||||
import 'wallet_base.dart';
|
|
||||||
|
|
||||||
part 'monero_wallet.g.dart';
|
part 'monero_wallet.g.dart';
|
||||||
|
|
||||||
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
|
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
|
||||||
|
|
||||||
abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||||
MoneroWalletBase({String filename, this.isRecovery = false}) {
|
MoneroWalletBase({String filename, this.isRecovery = false})
|
||||||
transactionHistory = MoneroTransactionHistory();
|
: transactionHistory = MoneroTransactionHistory() {
|
||||||
_filename = filename;
|
_filename = filename;
|
||||||
accountList = AccountList();
|
accountList = AccountList();
|
||||||
subaddressList = SubaddressList();
|
subaddressList = MoneroSubaddressList();
|
||||||
balance = MoneroBalance(
|
balance = MoneroBalance(
|
||||||
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
|
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
|
||||||
unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0));
|
unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
MoneroTransactionHistory transactionHistory;
|
@override
|
||||||
SubaddressList subaddressList;
|
final MoneroTransactionHistory transactionHistory;
|
||||||
AccountList accountList;
|
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
Account account;
|
Account account;
|
||||||
|
@ -41,30 +42,40 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||||
SyncStatus syncStatus;
|
SyncStatus syncStatus;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get name => filename.split('/').last;
|
String get name => _filename.split('/').last;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get filename => _filename;
|
final type = WalletType.monero;
|
||||||
|
|
||||||
String _filename;
|
@override
|
||||||
|
@observable
|
||||||
|
String address;
|
||||||
|
|
||||||
bool isRecovery;
|
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;
|
account = accountList.getAll().first;
|
||||||
subaddressList.refresh(accountIndex: account.id ?? 0);
|
subaddressList.update(accountIndex: account.id ?? 0);
|
||||||
subaddress = subaddressList.getAll().first;
|
subaddress = subaddressList.getAll().first;
|
||||||
balance = MoneroBalance(
|
balance = MoneroBalance(
|
||||||
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
|
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
|
||||||
unlockedBalance:
|
unlockedBalance:
|
||||||
monero_wallet.getFullBalance(accountIndex: account.id));
|
monero_wallet.getFullBalance(accountIndex: account.id));
|
||||||
|
address = subaddress.address;
|
||||||
_setListeners();
|
_setListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
void close() {
|
void close() {
|
||||||
_listner?.stop();
|
_listener?.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -133,8 +144,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
|
||||||
Future<bool> isConnected() async => monero_wallet.isConnected();
|
Future<bool> isConnected() async => monero_wallet.isConnected();
|
||||||
|
|
||||||
void _setListeners() {
|
void _setListeners() {
|
||||||
_listner?.stop();
|
_listener?.stop();
|
||||||
_listner = monero_wallet.setListeners(
|
_listener = monero_wallet.setListeners(
|
||||||
_onNewBlock, _onNeedToRefresh, _onNewTransaction);
|
_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_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/pathForWallet.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/wallet_type.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_manager.dart' as monero_wallet_manager;
|
||||||
import 'package:cw_monero/wallet.dart' as monero_wallet;
|
import 'package:cw_monero/wallet.dart' as monero_wallet;
|
||||||
|
|
||||||
class MoneroNewWalletCredentials extends WalletCredentials {
|
class MoneroNewWalletCredentials extends WalletCredentials {
|
||||||
const MoneroNewWalletCredentials(
|
MoneroNewWalletCredentials({String name, String password, this.language})
|
||||||
{String name, String password, this.language})
|
|
||||||
: super(name: name, password: password);
|
: super(name: name, password: password);
|
||||||
|
|
||||||
final String language;
|
final String language;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
|
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||||
const MoneroRestoreWalletFromSeedCredentials(
|
MoneroRestoreWalletFromSeedCredentials(
|
||||||
{String name, String password, this.mnemonic, this.height})
|
{String name, String password, this.mnemonic, this.height})
|
||||||
: super(name: name, password: password);
|
: super(name: name, password: password);
|
||||||
|
|
||||||
|
@ -24,7 +23,7 @@ class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
|
class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
|
||||||
const MoneroRestoreWalletFromKeysCredentials(
|
MoneroRestoreWalletFromKeysCredentials(
|
||||||
{String name,
|
{String name,
|
||||||
String password,
|
String password,
|
||||||
this.language,
|
this.language,
|
||||||
|
@ -41,12 +40,12 @@ class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
|
||||||
final int height;
|
final int height;
|
||||||
}
|
}
|
||||||
|
|
||||||
class MoneroWalletListService extends WalletListService<
|
class MoneroWalletService extends WalletService<
|
||||||
MoneroNewWalletCredentials,
|
MoneroNewWalletCredentials,
|
||||||
MoneroRestoreWalletFromSeedCredentials,
|
MoneroRestoreWalletFromSeedCredentials,
|
||||||
MoneroRestoreWalletFromKeysCredentials> {
|
MoneroRestoreWalletFromKeysCredentials> {
|
||||||
@override
|
@override
|
||||||
Future<void> create(MoneroNewWalletCredentials credentials) async {
|
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
|
||||||
try {
|
try {
|
||||||
final path =
|
final path =
|
||||||
await pathForWallet(name: credentials.name, type: WalletType.monero);
|
await pathForWallet(name: credentials.name, type: WalletType.monero);
|
||||||
|
@ -56,7 +55,10 @@ class MoneroWalletListService extends WalletListService<
|
||||||
password: credentials.password,
|
password: credentials.password,
|
||||||
language: credentials.language);
|
language: credentials.language);
|
||||||
|
|
||||||
return MoneroWallet(filename: monero_wallet.getFilename())..init();
|
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
|
||||||
|
await wallet.init();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Implement Exception fop wallet list service.
|
// TODO: Implement Exception fop wallet list service.
|
||||||
print('MoneroWalletsManager Error: $e');
|
print('MoneroWalletsManager Error: $e');
|
||||||
|
@ -77,7 +79,7 @@ class MoneroWalletListService extends WalletListService<
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> openWallet(String name, String password) async {
|
Future<MoneroWallet> openWallet(String name, String password) async {
|
||||||
try {
|
try {
|
||||||
final path = await pathForWallet(name: name, type: WalletType.monero);
|
final path = await pathForWallet(name: name, type: WalletType.monero);
|
||||||
monero_wallet_manager.openWallet(path: path, password: password);
|
monero_wallet_manager.openWallet(path: path, password: password);
|
||||||
|
@ -86,7 +88,10 @@ class MoneroWalletListService extends WalletListService<
|
||||||
// final walletInfo = walletInfoSource.values
|
// final walletInfo = walletInfoSource.values
|
||||||
// .firstWhere((info) => info.id == id, orElse: () => null);
|
// .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) {
|
} catch (e) {
|
||||||
// TODO: Implement Exception fop wallet list service.
|
// TODO: Implement Exception fop wallet list service.
|
||||||
print('MoneroWalletsManager Error: $e');
|
print('MoneroWalletsManager Error: $e');
|
||||||
|
@ -100,7 +105,7 @@ class MoneroWalletListService extends WalletListService<
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> restoreFromKeys(
|
Future<MoneroWallet> restoreFromKeys(
|
||||||
MoneroRestoreWalletFromKeysCredentials credentials) async {
|
MoneroRestoreWalletFromKeysCredentials credentials) async {
|
||||||
try {
|
try {
|
||||||
final path =
|
final path =
|
||||||
|
@ -115,7 +120,10 @@ class MoneroWalletListService extends WalletListService<
|
||||||
viewKey: credentials.viewKey,
|
viewKey: credentials.viewKey,
|
||||||
spendKey: credentials.spendKey);
|
spendKey: credentials.spendKey);
|
||||||
|
|
||||||
return MoneroWallet(filename: monero_wallet.getFilename())..init();
|
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
|
||||||
|
await wallet.init();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Implement Exception fop wallet list service.
|
// TODO: Implement Exception fop wallet list service.
|
||||||
print('MoneroWalletsManager Error: $e');
|
print('MoneroWalletsManager Error: $e');
|
||||||
|
@ -124,7 +132,7 @@ class MoneroWalletListService extends WalletListService<
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> restoreFromSeed(
|
Future<MoneroWallet> restoreFromSeed(
|
||||||
MoneroRestoreWalletFromSeedCredentials credentials) async {
|
MoneroRestoreWalletFromSeedCredentials credentials) async {
|
||||||
try {
|
try {
|
||||||
final path =
|
final path =
|
||||||
|
@ -136,7 +144,10 @@ class MoneroWalletListService extends WalletListService<
|
||||||
seed: credentials.mnemonic,
|
seed: credentials.mnemonic,
|
||||||
restoreHeight: credentials.height);
|
restoreHeight: credentials.height);
|
||||||
|
|
||||||
return MoneroWallet(filename: monero_wallet.getFilename())..init();
|
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
|
||||||
|
await wallet.init();
|
||||||
|
|
||||||
|
return wallet;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: Implement Exception fop wallet list service.
|
// TODO: Implement Exception fop wallet list service.
|
||||||
print('MoneroWalletsManager Error: $e');
|
print('MoneroWalletsManager Error: $e');
|
|
@ -12,6 +12,7 @@ class Palette {
|
||||||
static const Color blue = Color.fromRGBO(88, 143, 252, 1.0);
|
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 darkLavender = Color.fromRGBO(225, 238, 250, 1.0);
|
||||||
static const Color nightBlue = Color.fromRGBO(46, 57, 96, 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 {
|
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/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.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:hive/hive.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
import 'di.dart';
|
||||||
// MARK: Import domains
|
// MARK: Import domains
|
||||||
|
|
||||||
import 'package:cake_wallet/src/domain/common/contact.dart';
|
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/monero/transaction_description.dart';
|
||||||
import 'package:cake_wallet/src/domain/exchange/trade.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/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/common/transaction_info.dart';
|
||||||
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
|
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/wallet_type.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/new_node_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/nodes/nodes_list_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/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/wallet_list/wallet_list_page.dart';
|
||||||
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_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';
|
import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
|
||||||
|
@ -126,24 +128,18 @@ class Router {
|
||||||
Navigator.pushNamed(context, Routes.newWalletType))));
|
Navigator.pushNamed(context, Routes.newWalletType))));
|
||||||
|
|
||||||
case 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:
|
case Routes.newWallet:
|
||||||
final type = settings.arguments as WalletType;
|
final type = settings.arguments as WalletType;
|
||||||
walletListService.changeWalletManger(walletType: type);
|
final walletNewVM = getIt.get<WalletNewVM>(param1: type);
|
||||||
|
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder:
|
builder: (_) => NewWalletPage(walletNewVM));
|
||||||
(_) =>
|
|
||||||
ProxyProvider<AuthenticationStore, WalletCreationStore>(
|
|
||||||
update: (_, authStore, __) => WalletCreationStore(
|
|
||||||
authStore: authStore,
|
|
||||||
sharedPreferences: sharedPreferences,
|
|
||||||
walletListService: walletListService),
|
|
||||||
child: NewWalletPage(
|
|
||||||
walletsService: walletListService,
|
|
||||||
walletService: walletService,
|
|
||||||
sharedPreferences: sharedPreferences)));
|
|
||||||
|
|
||||||
case Routes.setupPin:
|
case Routes.setupPin:
|
||||||
Function(BuildContext, String) callback;
|
Function(BuildContext, String) callback;
|
||||||
|
@ -163,6 +159,13 @@ class Router {
|
||||||
callback == null ? null : callback(context, pin))),
|
callback == null ? null : callback(context, pin))),
|
||||||
fullscreenDialog: true);
|
fullscreenDialog: true);
|
||||||
|
|
||||||
|
case Routes.restoreWalletType:
|
||||||
|
return CupertinoPageRoute<void>(
|
||||||
|
builder: (_) => NewWalletTypePage(
|
||||||
|
onTypeSelected: (context, type) => Navigator.of(context)
|
||||||
|
.pushNamed(Routes.restoreWalletOptions, arguments: type),
|
||||||
|
));
|
||||||
|
|
||||||
case Routes.restoreOptions:
|
case Routes.restoreOptions:
|
||||||
final type = settings.arguments as WalletType;
|
final type = settings.arguments as WalletType;
|
||||||
walletListService.changeWalletManger(walletType: type);
|
walletListService.changeWalletManger(walletType: type);
|
||||||
|
@ -175,7 +178,28 @@ class Router {
|
||||||
walletListService.changeWalletManger(walletType: type);
|
walletListService.changeWalletManger(walletType: type);
|
||||||
|
|
||||||
return CupertinoPageRoute<void>(
|
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:
|
case Routes.restoreWalletOptionsFromWelcome:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
|
@ -186,7 +210,7 @@ class Router {
|
||||||
sharedPreferences: sharedPreferences)),
|
sharedPreferences: sharedPreferences)),
|
||||||
child: SetupPinCodePage(
|
child: SetupPinCodePage(
|
||||||
onPinCodeSetup: (context, _) => Navigator.pushNamed(
|
onPinCodeSetup: (context, _) => Navigator.pushNamed(
|
||||||
context, Routes.restoreWalletOptions))));
|
context, Routes.restoreWalletType))));
|
||||||
|
|
||||||
case Routes.seed:
|
case Routes.seed:
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
|
@ -196,8 +220,11 @@ class Router {
|
||||||
callback: settings.arguments as void Function()));
|
callback: settings.arguments as void Function()));
|
||||||
|
|
||||||
case Routes.restoreWalletFromSeed:
|
case Routes.restoreWalletFromSeed:
|
||||||
final type = settings.arguments as WalletType;
|
final args = settings.arguments as List<dynamic>;
|
||||||
walletListService.changeWalletManger(walletType: type);
|
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>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) =>
|
builder: (_) =>
|
||||||
|
@ -207,11 +234,15 @@ class Router {
|
||||||
sharedPreferences: sharedPreferences,
|
sharedPreferences: sharedPreferences,
|
||||||
walletListService: walletListService),
|
walletListService: walletListService),
|
||||||
child: RestoreWalletFromSeedPage(
|
child: RestoreWalletFromSeedPage(
|
||||||
walletsService: walletListService,
|
type: type, language: language)));
|
||||||
walletService: walletService,
|
|
||||||
sharedPreferences: sharedPreferences)));
|
|
||||||
|
|
||||||
case Routes.restoreWalletFromKeys:
|
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>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) =>
|
builder: (_) =>
|
||||||
ProxyProvider<AuthenticationStore, WalletRestorationStore>(
|
ProxyProvider<AuthenticationStore, WalletRestorationStore>(
|
||||||
|
@ -256,22 +287,16 @@ class Router {
|
||||||
|
|
||||||
case Routes.sendTemplate:
|
case Routes.sendTemplate:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) => Provider(
|
builder: (_) => Provider(
|
||||||
create: (_) => SendStore(
|
create: (_) => SendStore(
|
||||||
walletService: walletService,
|
walletService: walletService,
|
||||||
priceStore: priceStore,
|
priceStore: priceStore,
|
||||||
transactionDescriptions: transactionDescriptions),
|
transactionDescriptions: transactionDescriptions),
|
||||||
child: SendTemplatePage())
|
child: SendTemplatePage()));
|
||||||
);
|
|
||||||
|
|
||||||
case Routes.receive:
|
case Routes.receive:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
fullscreenDialog: true,
|
fullscreenDialog: true, builder: (_) => getIt.get<ReceivePage>());
|
||||||
builder: (_) => MultiProvider(providers: [
|
|
||||||
Provider(
|
|
||||||
create: (_) =>
|
|
||||||
SubaddressListStore(walletService: walletService))
|
|
||||||
], child: ReceivePage()));
|
|
||||||
|
|
||||||
case Routes.transactionDetails:
|
case Routes.transactionDetails:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
|
@ -281,10 +306,8 @@ class Router {
|
||||||
|
|
||||||
case Routes.newSubaddress:
|
case Routes.newSubaddress:
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) => Provider(
|
builder: (_) =>
|
||||||
create: (_) =>
|
getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
|
||||||
SubadrressCreationStore(walletService: walletService),
|
|
||||||
child: NewSubaddressPage()));
|
|
||||||
|
|
||||||
case Routes.disclaimer:
|
case Routes.disclaimer:
|
||||||
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage());
|
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage());
|
||||||
|
@ -294,7 +317,15 @@ class Router {
|
||||||
builder: (_) => DisclaimerPage(isReadOnly: true));
|
builder: (_) => DisclaimerPage(isReadOnly: true));
|
||||||
|
|
||||||
case Routes.seedLanguage:
|
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:
|
case Routes.walletList:
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
|
@ -306,17 +337,18 @@ class Router {
|
||||||
child: WalletListPage()));
|
child: WalletListPage()));
|
||||||
|
|
||||||
case Routes.auth:
|
case Routes.auth:
|
||||||
return MaterialPageRoute<void>(
|
return null;
|
||||||
fullscreenDialog: true,
|
// return MaterialPageRoute<void>(
|
||||||
builder: (_) => Provider(
|
// fullscreenDialog: true,
|
||||||
create: (_) => AuthStore(
|
// builder: (_) => Provider(
|
||||||
sharedPreferences: sharedPreferences,
|
// create: (_) => AuthStore(
|
||||||
userService: userService,
|
// sharedPreferences: sharedPreferences,
|
||||||
walletService: walletService),
|
// userService: userService,
|
||||||
child: AuthPage(
|
// walletService: walletService),
|
||||||
onAuthenticationFinished:
|
// child: AuthPage(
|
||||||
settings.arguments as OnAuthenticationFinished),
|
// onAuthenticationFinished:
|
||||||
));
|
// settings.arguments as OnAuthenticationFinished),
|
||||||
|
// ));
|
||||||
|
|
||||||
case Routes.unlock:
|
case Routes.unlock:
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
|
@ -455,15 +487,13 @@ class Router {
|
||||||
], child: SubaddressListPage()));
|
], child: SubaddressListPage()));
|
||||||
|
|
||||||
case Routes.restoreWalletFromSeedDetails:
|
case Routes.restoreWalletFromSeedDetails:
|
||||||
|
final args = settings.arguments as List;
|
||||||
|
final walletRestorationFromSeedVM =
|
||||||
|
getIt.get<WalletRestorationFromSeedVM>(param1: args);
|
||||||
|
|
||||||
return CupertinoPageRoute<void>(
|
return CupertinoPageRoute<void>(
|
||||||
builder: (_) =>
|
builder: (_) => RestoreWalletFromSeedDetailsPage(
|
||||||
ProxyProvider<AuthenticationStore, WalletRestorationStore>(
|
walletRestorationFromSeedVM: walletRestorationFromSeedVM));
|
||||||
update: (_, authStore, __) => WalletRestorationStore(
|
|
||||||
authStore: authStore,
|
|
||||||
sharedPreferences: sharedPreferences,
|
|
||||||
walletListService: walletListService,
|
|
||||||
seed: settings.arguments as List<MnemoticItem>),
|
|
||||||
child: RestoreWalletFromSeedDetailsPage()));
|
|
||||||
|
|
||||||
case Routes.exchange:
|
case Routes.exchange:
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
|
@ -487,22 +517,24 @@ class Router {
|
||||||
|
|
||||||
case Routes.exchangeTemplate:
|
case Routes.exchangeTemplate:
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
builder: (_) => Provider(create: (_) {
|
builder: (_) => Provider(
|
||||||
final xmrtoprovider = XMRTOExchangeProvider();
|
create: (_) {
|
||||||
|
final xmrtoprovider = XMRTOExchangeProvider();
|
||||||
|
|
||||||
return ExchangeStore(
|
return ExchangeStore(
|
||||||
initialProvider: xmrtoprovider,
|
initialProvider: xmrtoprovider,
|
||||||
initialDepositCurrency: CryptoCurrency.xmr,
|
initialDepositCurrency: CryptoCurrency.xmr,
|
||||||
initialReceiveCurrency: CryptoCurrency.btc,
|
initialReceiveCurrency: CryptoCurrency.btc,
|
||||||
trades: trades,
|
trades: trades,
|
||||||
providerList: [
|
providerList: [
|
||||||
xmrtoprovider,
|
xmrtoprovider,
|
||||||
ChangeNowExchangeProvider(),
|
ChangeNowExchangeProvider(),
|
||||||
MorphTokenExchangeProvider(trades: trades)
|
MorphTokenExchangeProvider(trades: trades)
|
||||||
],
|
],
|
||||||
walletStore: walletStore);
|
walletStore: walletStore);
|
||||||
}, child: ExchangeTemplatePage(),)
|
},
|
||||||
);
|
child: ExchangeTemplatePage(),
|
||||||
|
));
|
||||||
|
|
||||||
case Routes.settings:
|
case Routes.settings:
|
||||||
return MaterialPageRoute<void>(
|
return MaterialPageRoute<void>(
|
||||||
|
|
|
@ -46,4 +46,5 @@ class Routes {
|
||||||
static const newWalletType = '/new_wallet_type';
|
static const newWalletType = '/new_wallet_type';
|
||||||
static const sendTemplate = '/send_template';
|
static const sendTemplate = '/send_template';
|
||||||
static const exchangeTemplate = '/exchange_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 'dart:async';
|
||||||
import 'package:cake_wallet/bitcoin/bitcoin_wallet.manager.dart';
|
|
||||||
import 'package:cake_wallet/bitcoin/key.dart';
|
import 'package:cake_wallet/bitcoin/key.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/wallet_info.dart';
|
import 'package:cake_wallet/src/domain/common/wallet_info.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -119,7 +118,7 @@ class WalletListService {
|
||||||
MoneroWalletsManager(walletInfoSource: walletInfoSource);
|
MoneroWalletsManager(walletInfoSource: walletInfoSource);
|
||||||
break;
|
break;
|
||||||
case WalletType.bitcoin:
|
case WalletType.bitcoin:
|
||||||
walletsManager = BitcoinWalletManager();
|
// walletsManager = BitcoinWalletManager();
|
||||||
break;
|
break;
|
||||||
case WalletType.none:
|
case WalletType.none:
|
||||||
walletsManager = null;
|
walletsManager = null;
|
||||||
|
|
|
@ -35,10 +35,10 @@ void setReactions(
|
||||||
settingsStore: settingsStore,
|
settingsStore: settingsStore,
|
||||||
priceStore: priceStore);
|
priceStore: priceStore);
|
||||||
autorun((_) async {
|
autorun((_) async {
|
||||||
if (authenticationStore.state == AuthenticationState.allowed) {
|
// if (authenticationStore.state == AuthenticationState.allowed) {
|
||||||
await loginStore.loadCurrentWallet();
|
// await loginStore.loadCurrentWallet();
|
||||||
authenticationStore.state = AuthenticationState.readyToLogin;
|
// authenticationStore.state = AuthenticationState.readyToLogin;
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
import 'package:mobx/mobx.dart';
|
import 'package:mobx/mobx.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/src/stores/auth/auth_state.dart';
|
import 'package:cake_wallet/view_model/auth_state.dart';
|
||||||
import 'package:cake_wallet/src/stores/auth/auth_store.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/screens/pin_code/pin_code.dart';
|
||||||
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
|
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/biometric_auth.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);
|
typedef OnAuthenticationFinished = void Function(bool, AuthPageState);
|
||||||
|
|
||||||
class AuthPage extends StatefulWidget {
|
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 OnAuthenticationFinished onAuthenticationFinished;
|
||||||
final bool closable;
|
final bool closable;
|
||||||
|
|
||||||
|
@ -25,40 +28,13 @@ class AuthPageState extends State<AuthPage> {
|
||||||
final _key = GlobalKey<ScaffoldState>();
|
final _key = GlobalKey<ScaffoldState>();
|
||||||
final _pinCodeKey = GlobalKey<PinCodeState>();
|
final _pinCodeKey = GlobalKey<PinCodeState>();
|
||||||
final _backArrowImageDarkTheme =
|
final _backArrowImageDarkTheme =
|
||||||
Image.asset('assets/images/back_arrow_dark_theme.png');
|
Image.asset('assets/images/back_arrow_dark_theme.png');
|
||||||
|
ReactionDisposer _reaction;
|
||||||
void changeProcessText(String text) {
|
|
||||||
_key.currentState.showSnackBar(
|
|
||||||
SnackBar(content: Text(text), backgroundColor: Colors.green));
|
|
||||||
}
|
|
||||||
|
|
||||||
void close() => Navigator.of(_key.currentContext).pop();
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
void initState() {
|
||||||
final authStore = Provider.of<AuthStore>(context);
|
_reaction ??=
|
||||||
final settingsStore = Provider.of<SettingsStore>(context);
|
reaction((_) => widget.authViewModel.state, (AuthState state) {
|
||||||
|
|
||||||
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) {
|
|
||||||
if (state is AuthenticatedSuccessfully) {
|
if (state is AuthenticatedSuccessfully) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (widget.onAuthenticationFinished != null) {
|
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(
|
return Scaffold(
|
||||||
key: _key,
|
key: _key,
|
||||||
appBar: CupertinoNavigationBar(
|
appBar: CupertinoNavigationBar(
|
||||||
leading: widget.closable
|
leading: widget.closable
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
height: 37,
|
height: 37,
|
||||||
width: 20,
|
width: 20,
|
||||||
child: ButtonTheme(
|
child: ButtonTheme(
|
||||||
minWidth: double.minPositive,
|
minWidth: double.minPositive,
|
||||||
child: FlatButton(
|
child: FlatButton(
|
||||||
highlightColor: Colors.transparent,
|
highlightColor: Colors.transparent,
|
||||||
splashColor: Colors.transparent,
|
splashColor: Colors.transparent,
|
||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
child: _backArrowImageDarkTheme),
|
child: _backArrowImageDarkTheme),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: Container(),
|
: Container(),
|
||||||
backgroundColor: Theme.of(context).backgroundColor,
|
backgroundColor: Theme.of(context).backgroundColor,
|
||||||
border: null,
|
border: null,
|
||||||
),
|
),
|
||||||
resizeToAvoidBottomPadding: false,
|
resizeToAvoidBottomPadding: false,
|
||||||
body: PinCode(
|
body: PinCode(
|
||||||
(pin, _) => authStore.auth(
|
(pin, _) => widget.authViewModel
|
||||||
password: pin.fold('', (ac, val) => ac + '$val')),
|
.auth(password: pin.fold('', (ac, val) => ac + '$val')),
|
||||||
false,
|
false,
|
||||||
_pinCodeKey));
|
_pinCodeKey));
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,15 +14,16 @@ Widget createLoginPage(
|
||||||
@required WalletService walletService,
|
@required WalletService walletService,
|
||||||
@required WalletListService walletListService,
|
@required WalletListService walletListService,
|
||||||
@required AuthenticationStore authenticationStore}) =>
|
@required AuthenticationStore authenticationStore}) =>
|
||||||
Provider(
|
null;
|
||||||
create: (_) => AuthStore(
|
// Provider(
|
||||||
sharedPreferences: sharedPreferences,
|
// create: (_) => AuthStore(
|
||||||
userService: userService,
|
// sharedPreferences: sharedPreferences,
|
||||||
walletService: walletService),
|
// userService: userService,
|
||||||
child: AuthPage(
|
// walletService: walletService),
|
||||||
onAuthenticationFinished: (isAuthenticated, state) {
|
// child: AuthPage(
|
||||||
if (isAuthenticated) {
|
// onAuthenticationFinished: (isAuthenticated, state) {
|
||||||
authenticationStore.loggedIn();
|
// if (isAuthenticated) {
|
||||||
}
|
// authenticationStore.loggedIn();
|
||||||
},
|
// }
|
||||||
closable: false));
|
// },
|
||||||
|
// closable: false));
|
||||||
|
|
|
@ -11,13 +11,14 @@ Widget createUnlockPage(
|
||||||
@required UserService userService,
|
@required UserService userService,
|
||||||
@required WalletService walletService,
|
@required WalletService walletService,
|
||||||
@required Function(bool, AuthPageState) onAuthenticationFinished}) =>
|
@required Function(bool, AuthPageState) onAuthenticationFinished}) =>
|
||||||
WillPopScope(
|
null;
|
||||||
onWillPop: () async => false,
|
// WillPopScope(
|
||||||
child: Provider(
|
// onWillPop: () async => false,
|
||||||
create: (_) => AuthStore(
|
// child: Provider(
|
||||||
sharedPreferences: sharedPreferences,
|
// create: (_) => AuthStore(
|
||||||
userService: userService,
|
// sharedPreferences: sharedPreferences,
|
||||||
walletService: walletService),
|
// userService: userService,
|
||||||
child: AuthPage(
|
// walletService: walletService),
|
||||||
onAuthenticationFinished: onAuthenticationFinished,
|
// child: AuthPage(
|
||||||
closable: false)));
|
// onAuthenticationFinished: onAuthenticationFinished,
|
||||||
|
// closable: false)));
|
|
@ -10,12 +10,19 @@ enum AppBarStyle { regular, withShadow }
|
||||||
|
|
||||||
abstract class BasePage extends StatelessWidget {
|
abstract class BasePage extends StatelessWidget {
|
||||||
String get title => null;
|
String get title => null;
|
||||||
|
|
||||||
bool get isModalBackButton => false;
|
bool get isModalBackButton => false;
|
||||||
|
|
||||||
Color get backgroundLightColor => Colors.white;
|
Color get backgroundLightColor => Colors.white;
|
||||||
|
|
||||||
Color get backgroundDarkColor => PaletteDark.darkNightBlue;
|
Color get backgroundDarkColor => PaletteDark.darkNightBlue;
|
||||||
|
|
||||||
bool get resizeToAvoidBottomPadding => true;
|
bool get resizeToAvoidBottomPadding => true;
|
||||||
|
|
||||||
AppBarStyle get appBarStyle => AppBarStyle.regular;
|
AppBarStyle get appBarStyle => AppBarStyle.regular;
|
||||||
|
|
||||||
|
Widget Function(BuildContext, Widget) get rootWrapper => null;
|
||||||
|
|
||||||
final _backArrowImage = Image.asset('assets/images/back_arrow.png');
|
final _backArrowImage = Image.asset('assets/images/back_arrow.png');
|
||||||
final _backArrowImageDarkTheme =
|
final _backArrowImageDarkTheme =
|
||||||
Image.asset('assets/images/back_arrow_dark_theme.png');
|
Image.asset('assets/images/back_arrow_dark_theme.png');
|
||||||
|
@ -83,9 +90,8 @@ abstract class BasePage extends StatelessWidget {
|
||||||
leading: leading(context),
|
leading: leading(context),
|
||||||
middle: middle(context),
|
middle: middle(context),
|
||||||
trailing: trailing(context),
|
trailing: trailing(context),
|
||||||
backgroundColor: _isDarkTheme
|
backgroundColor:
|
||||||
? backgroundDarkColor
|
_isDarkTheme ? backgroundDarkColor : backgroundLightColor);
|
||||||
: backgroundLightColor);
|
|
||||||
|
|
||||||
case AppBarStyle.withShadow:
|
case AppBarStyle.withShadow:
|
||||||
return NavBar.withShadow(
|
return NavBar.withShadow(
|
||||||
|
@ -93,9 +99,8 @@ abstract class BasePage extends StatelessWidget {
|
||||||
leading: leading(context),
|
leading: leading(context),
|
||||||
middle: middle(context),
|
middle: middle(context),
|
||||||
trailing: trailing(context),
|
trailing: trailing(context),
|
||||||
backgroundColor: _isDarkTheme
|
backgroundColor:
|
||||||
? backgroundDarkColor
|
_isDarkTheme ? backgroundDarkColor : backgroundLightColor);
|
||||||
: backgroundLightColor);
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return NavBar(
|
return NavBar(
|
||||||
|
@ -103,9 +108,8 @@ abstract class BasePage extends StatelessWidget {
|
||||||
leading: leading(context),
|
leading: leading(context),
|
||||||
middle: middle(context),
|
middle: middle(context),
|
||||||
trailing: trailing(context),
|
trailing: trailing(context),
|
||||||
backgroundColor: _isDarkTheme
|
backgroundColor:
|
||||||
? backgroundDarkColor
|
_isDarkTheme ? backgroundDarkColor : backgroundLightColor);
|
||||||
: backgroundLightColor);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,13 +120,14 @@ abstract class BasePage extends StatelessWidget {
|
||||||
final _themeChanger = Provider.of<ThemeChanger>(context);
|
final _themeChanger = Provider.of<ThemeChanger>(context);
|
||||||
final _isDarkTheme = _themeChanger.getTheme() == Themes.darkTheme;
|
final _isDarkTheme = _themeChanger.getTheme() == Themes.darkTheme;
|
||||||
|
|
||||||
return Scaffold(
|
final root = Scaffold(
|
||||||
backgroundColor: _isDarkTheme
|
backgroundColor:
|
||||||
? backgroundDarkColor
|
_isDarkTheme ? backgroundDarkColor : backgroundLightColor,
|
||||||
: backgroundLightColor,
|
|
||||||
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
|
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
|
||||||
appBar: appBar(context),
|
appBar: appBar(context),
|
||||||
body: SafeArea(child: body(context)),
|
body: SafeArea(child: body(context)),
|
||||||
floatingActionButton: floatingActionButton(context));
|
floatingActionButton: floatingActionButton(context));
|
||||||
|
|
||||||
|
return rootWrapper?.call(context, root) ?? root;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,36 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/cupertino.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/wallet_card.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/trade_history_panel.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/trade_history_panel.dart';
|
||||||
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
|
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
|
||||||
|
|
||||||
class DashboardPage extends StatelessWidget {
|
class DashboardPage extends StatelessWidget {
|
||||||
|
DashboardPage({@required this.walletViewModel});
|
||||||
|
|
||||||
|
final DashboardViewModel walletViewModel;
|
||||||
final _bodyKey = GlobalKey();
|
final _bodyKey = GlobalKey();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => DashboardPageBody(key: _bodyKey);
|
Widget build(BuildContext context) =>
|
||||||
|
DashboardPageBody(key: _bodyKey, walletViewModel: walletViewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
class DashboardPageBody extends StatefulWidget {
|
class DashboardPageBody extends StatefulWidget {
|
||||||
DashboardPageBody({Key key}) : super(key: key);
|
DashboardPageBody({Key key, @required this.walletViewModel})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final DashboardViewModel walletViewModel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DashboardPageBodyState createState() => DashboardPageBodyState();
|
DashboardPageBodyState createState() => DashboardPageBodyState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class DashboardPageBodyState extends State<DashboardPageBody> {
|
class DashboardPageBodyState extends State<DashboardPageBody> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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,
|
color: Theme.of(context).primaryTextTheme.title.color,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -30,15 +38,10 @@ class DashboardPageBodyState extends State<DashboardPageBody> {
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
body: Container(
|
body: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
gradient: LinearGradient(colors: [
|
||||||
colors: [
|
Theme.of(context).scaffoldBackgroundColor,
|
||||||
Theme.of(context).scaffoldBackgroundColor,
|
Theme.of(context).primaryColor
|
||||||
Theme.of(context).primaryColor
|
], begin: Alignment.centerLeft, end: Alignment.centerRight)),
|
||||||
],
|
|
||||||
begin: Alignment.centerLeft,
|
|
||||||
end: Alignment.centerRight
|
|
||||||
)
|
|
||||||
),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
|
@ -55,22 +58,17 @@ class DashboardPageBodyState extends State<DashboardPageBody> {
|
||||||
padding: EdgeInsets.all(0),
|
padding: EdgeInsets.all(0),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await showDialog<void>(
|
await showDialog<void>(
|
||||||
builder: (_) => MenuWidget(),
|
builder: (_) => MenuWidget(), context: context);
|
||||||
context: context
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
child: menuButton),
|
child: menuButton),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(left: 20, top: 20),
|
padding: EdgeInsets.only(left: 20, top: 20),
|
||||||
child: WalletCard(),
|
child: WalletCard(walletVM: widget.walletViewModel)),
|
||||||
),
|
SizedBox(height: 28),
|
||||||
SizedBox(
|
Expanded(child: TradeHistoryPanel(dashboardViewModel: widget.walletViewModel))
|
||||||
height: 28,
|
|
||||||
),
|
|
||||||
Expanded(child: TradeHistoryPanel())
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -17,7 +17,7 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
|
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 historyPanelWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
final _themeChanger = Provider.of<ThemeChanger>(context);
|
final _themeChanger = Provider.of<ThemeChanger>(context);
|
||||||
|
@ -97,44 +97,44 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Theme.of(context).primaryTextTheme.caption.color))),
|
color: Theme.of(context).primaryTextTheme.caption.color))),
|
||||||
PopupMenuItem(
|
// PopupMenuItem(
|
||||||
value: 0,
|
// value: 0,
|
||||||
child: Observer(
|
// child: Observer(
|
||||||
builder: (_) => Row(
|
// builder: (_) => Row(
|
||||||
mainAxisAlignment:
|
// mainAxisAlignment:
|
||||||
MainAxisAlignment
|
// MainAxisAlignment
|
||||||
.spaceBetween,
|
// .spaceBetween,
|
||||||
children: [
|
// children: [
|
||||||
Text(S.of(context).incoming),
|
// Text(S.of(context).incoming),
|
||||||
Checkbox(
|
// Checkbox(
|
||||||
value: actionListStore
|
// value: actionListStore
|
||||||
.transactionFilterStore
|
// .transactionFilterStore
|
||||||
.displayIncoming,
|
// .displayIncoming,
|
||||||
onChanged: (value) =>
|
// onChanged: (value) =>
|
||||||
actionListStore
|
// actionListStore
|
||||||
.transactionFilterStore
|
// .transactionFilterStore
|
||||||
.toggleIncoming(),
|
// .toggleIncoming(),
|
||||||
)
|
// )
|
||||||
]))),
|
// ]))),
|
||||||
PopupMenuItem(
|
// PopupMenuItem(
|
||||||
value: 1,
|
// value: 1,
|
||||||
child: Observer(
|
// child: Observer(
|
||||||
builder: (_) => Row(
|
// builder: (_) => Row(
|
||||||
mainAxisAlignment:
|
// mainAxisAlignment:
|
||||||
MainAxisAlignment
|
// MainAxisAlignment
|
||||||
.spaceBetween,
|
// .spaceBetween,
|
||||||
children: [
|
// children: [
|
||||||
Text(S.of(context).outgoing),
|
// Text(S.of(context).outgoing),
|
||||||
Checkbox(
|
// Checkbox(
|
||||||
value: actionListStore
|
// value: actionListStore
|
||||||
.transactionFilterStore
|
// .transactionFilterStore
|
||||||
.displayOutgoing,
|
// .displayOutgoing,
|
||||||
onChanged: (value) =>
|
// onChanged: (value) =>
|
||||||
actionListStore
|
// actionListStore
|
||||||
.transactionFilterStore
|
// .transactionFilterStore
|
||||||
.toggleOutgoing(),
|
// .toggleOutgoing(),
|
||||||
)
|
// )
|
||||||
]))),
|
// ]))),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 2,
|
value: 2,
|
||||||
child:
|
child:
|
||||||
|
@ -156,17 +156,17 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
||||||
.spaceBetween,
|
.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text('XMR.TO'),
|
Text('XMR.TO'),
|
||||||
Checkbox(
|
// Checkbox(
|
||||||
value: actionListStore
|
// value: actionListStore
|
||||||
.tradeFilterStore
|
// .tradeFilterStore
|
||||||
.displayXMRTO,
|
// .displayXMRTO,
|
||||||
onChanged: (value) =>
|
// onChanged: (value) =>
|
||||||
actionListStore
|
// actionListStore
|
||||||
.tradeFilterStore
|
// .tradeFilterStore
|
||||||
.toggleDisplayExchange(
|
// .toggleDisplayExchange(
|
||||||
ExchangeProviderDescription
|
// ExchangeProviderDescription
|
||||||
.xmrto),
|
// .xmrto),
|
||||||
)
|
// )
|
||||||
]))),
|
]))),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 4,
|
value: 4,
|
||||||
|
@ -177,17 +177,17 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
||||||
.spaceBetween,
|
.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text('Change.NOW'),
|
Text('Change.NOW'),
|
||||||
Checkbox(
|
// Checkbox(
|
||||||
value: actionListStore
|
// value: actionListStore
|
||||||
.tradeFilterStore
|
// .tradeFilterStore
|
||||||
.displayChangeNow,
|
// .displayChangeNow,
|
||||||
onChanged: (value) =>
|
// onChanged: (value) =>
|
||||||
actionListStore
|
// actionListStore
|
||||||
.tradeFilterStore
|
// .tradeFilterStore
|
||||||
.toggleDisplayExchange(
|
// .toggleDisplayExchange(
|
||||||
ExchangeProviderDescription
|
// ExchangeProviderDescription
|
||||||
.changeNow),
|
// .changeNow),
|
||||||
)
|
// )
|
||||||
]))),
|
]))),
|
||||||
PopupMenuItem(
|
PopupMenuItem(
|
||||||
value: 5,
|
value: 5,
|
||||||
|
@ -198,17 +198,17 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
||||||
.spaceBetween,
|
.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Text('MorphToken'),
|
Text('MorphToken'),
|
||||||
Checkbox(
|
// Checkbox(
|
||||||
value: actionListStore
|
// value: actionListStore
|
||||||
.tradeFilterStore
|
// .tradeFilterStore
|
||||||
.displayMorphToken,
|
// .displayMorphToken,
|
||||||
onChanged: (value) =>
|
// onChanged: (value) =>
|
||||||
actionListStore
|
// actionListStore
|
||||||
.tradeFilterStore
|
// .tradeFilterStore
|
||||||
.toggleDisplayExchange(
|
// .toggleDisplayExchange(
|
||||||
ExchangeProviderDescription
|
// ExchangeProviderDescription
|
||||||
.morphToken),
|
// .morphToken),
|
||||||
)
|
// )
|
||||||
])))
|
])))
|
||||||
],
|
],
|
||||||
child: filterButton,
|
child: filterButton,
|
||||||
|
@ -225,10 +225,10 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
|
||||||
.add(Duration(days: 1)));
|
.add(Duration(days: 1)));
|
||||||
|
|
||||||
if (picked != null && picked.length == 2) {
|
if (picked != null && picked.length == 2) {
|
||||||
actionListStore.transactionFilterStore
|
// actionListStore.transactionFilterStore
|
||||||
.changeStartDate(picked.first);
|
// .changeStartDate(picked.first);
|
||||||
actionListStore.transactionFilterStore
|
// actionListStore.transactionFilterStore
|
||||||
.changeEndDate(picked.last);
|
// .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/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:cake_wallet/routes.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 'date_section_raw.dart';
|
||||||
import 'trade_row.dart';
|
import 'trade_row.dart';
|
||||||
import 'transaction_raw.dart';
|
import 'transaction_raw.dart';
|
||||||
import 'button_header.dart';
|
import 'button_header.dart';
|
||||||
|
|
||||||
class TradeHistoryPanel extends StatefulWidget {
|
class TradeHistoryPanel extends StatefulWidget {
|
||||||
|
TradeHistoryPanel({this.dashboardViewModel});
|
||||||
|
|
||||||
|
DashboardViewModel dashboardViewModel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
TradeHistoryPanelState createState() => TradeHistoryPanelState();
|
TradeHistoryPanelState createState() => TradeHistoryPanelState();
|
||||||
}
|
}
|
||||||
|
@ -44,119 +49,127 @@ class TradeHistoryPanelState extends State<TradeHistoryPanel> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final actionListStore = Provider.of<ActionListStore>(context);
|
// final actionListStore = Provider.of<ActionListStore>(context);
|
||||||
final settingsStore = Provider.of<SettingsStore>(context);
|
// final settingsStore = Provider.of<SettingsStore>(context);
|
||||||
final transactionDateFormat = DateFormat("HH:mm");
|
final transactionDateFormat = DateFormat('HH:mm');
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
height: MediaQuery.of(context).size.height,
|
height: MediaQuery.of(context).size.height,
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
alignment: Alignment.bottomCenter,
|
|
||||||
child: AnimatedContainer(
|
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
height: panelHeight,
|
alignment: Alignment.bottomCenter,
|
||||||
duration: Duration(milliseconds: 1000),
|
child: AnimatedContainer(
|
||||||
curve: Curves.fastOutSlowIn,
|
width: MediaQuery.of(context).size.width,
|
||||||
child: ClipRRect(
|
height: panelHeight,
|
||||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
|
duration: Duration(milliseconds: 1000),
|
||||||
child: CustomScrollView(
|
curve: Curves.fastOutSlowIn,
|
||||||
slivers: <Widget>[
|
child: ClipRRect(
|
||||||
SliverPersistentHeader(
|
borderRadius: BorderRadius.only(
|
||||||
delegate: ButtonHeader(),
|
topLeft: Radius.circular(20),
|
||||||
pinned: true,
|
topRight: Radius.circular(20)),
|
||||||
floating: false,
|
child: CustomScrollView(
|
||||||
),
|
slivers: <Widget>[
|
||||||
Observer(
|
SliverPersistentHeader(
|
||||||
key: _listObserverKey,
|
delegate: ButtonHeader(),
|
||||||
builder: (_) {
|
pinned: true,
|
||||||
final items = actionListStore.items == null
|
floating: false,
|
||||||
? <String>[]
|
),
|
||||||
: actionListStore.items;
|
Observer(
|
||||||
final itemsCount = items.length + 1;
|
key: _listObserverKey,
|
||||||
final symbol = settingsStore.fiatCurrency.toString();
|
builder: (_) {
|
||||||
double freeSpaceHeight = MediaQuery.of(context).size.height - 496;
|
// 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(
|
return SliverList(
|
||||||
key: _listKey,
|
key: _listKey,
|
||||||
delegate: SliverChildBuilderDelegate(
|
delegate:
|
||||||
(context, index) {
|
SliverChildBuilderDelegate((context, index) {
|
||||||
|
if (index == itemsCount - 1) {
|
||||||
|
freeSpaceHeight = freeSpaceHeight >= 0
|
||||||
|
? freeSpaceHeight
|
||||||
|
: 0;
|
||||||
|
|
||||||
if (index == itemsCount - 1) {
|
return Container(
|
||||||
freeSpaceHeight = freeSpaceHeight >= 0 ? freeSpaceHeight : 0;
|
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(
|
return Container(
|
||||||
height: freeSpaceHeight,
|
color: Theme.of(context).backgroundColor);
|
||||||
width: MediaQuery.of(context).size.width,
|
}, childCount: itemsCount));
|
||||||
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
|
|
||||||
)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,21 +1,27 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:cake_wallet/palette.dart';
|
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:flutter/services.dart';
|
||||||
import 'package:provider/provider.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/balance/balance_store.dart';
|
||||||
import 'package:cake_wallet/src/stores/settings/settings_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/sync/sync_store.dart';
|
||||||
import 'package:cake_wallet/src/stores/wallet/wallet_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/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/sync_status.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/src/screens/receive/widgets/qr_image.dart';
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
import 'package:cake_wallet/view_model/dashboard_view_model.dart';
|
||||||
|
|
||||||
class WalletCard extends StatefulWidget {
|
class WalletCard extends StatefulWidget {
|
||||||
|
WalletCard({this.walletVM});
|
||||||
|
|
||||||
|
DashboardViewModel walletVM;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletCardState createState() => WalletCardState();
|
WalletCardState createState() => WalletCardState();
|
||||||
}
|
}
|
||||||
|
@ -50,14 +56,12 @@ class WalletCardState extends State<WalletCard> {
|
||||||
cardWidth = screenWidth;
|
cardWidth = screenWidth;
|
||||||
opacity = 1;
|
opacity = 1;
|
||||||
});
|
});
|
||||||
Timer(Duration(milliseconds: 500), () =>
|
Timer(Duration(milliseconds: 500), () => setState(() => isDraw = true));
|
||||||
setState(() => isDraw = true)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final List<Color> colorsSync = [
|
final colorsSync = [
|
||||||
Theme.of(context).cardTheme.color,
|
Theme.of(context).cardTheme.color,
|
||||||
Theme.of(context).hoverColor
|
Theme.of(context).hoverColor
|
||||||
];
|
];
|
||||||
|
@ -67,77 +71,67 @@ class WalletCardState extends State<WalletCard> {
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
child: AnimatedContainer(
|
child: AnimatedContainer(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
width: cardWidth,
|
width: cardWidth,
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
duration: Duration(milliseconds: 500),
|
duration: Duration(milliseconds: 500),
|
||||||
curve: Curves.fastOutSlowIn,
|
curve: Curves.fastOutSlowIn,
|
||||||
padding: EdgeInsets.only(
|
padding: EdgeInsets.only(top: 1, left: 1, bottom: 1),
|
||||||
top: 1,
|
decoration: BoxDecoration(
|
||||||
left: 1,
|
borderRadius: BorderRadius.only(
|
||||||
bottom: 1
|
topLeft: Radius.circular(10),
|
||||||
),
|
bottomLeft: Radius.circular(10)),
|
||||||
decoration: BoxDecoration(
|
color: Theme.of(context).focusColor,
|
||||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
|
boxShadow: [
|
||||||
color: Theme.of(context).focusColor,
|
BoxShadow(
|
||||||
boxShadow: [
|
color: PaletteDark.darkNightBlue.withOpacity(0.5),
|
||||||
BoxShadow(
|
blurRadius: 8,
|
||||||
color: PaletteDark.darkNightBlue.withOpacity(0.5),
|
offset: Offset(5, 5))
|
||||||
blurRadius: 8,
|
]),
|
||||||
offset: Offset(5, 5))
|
child: ClipRRect(
|
||||||
]
|
borderRadius: BorderRadius.only(
|
||||||
),
|
topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
|
||||||
child: ClipRRect(
|
child: Container(
|
||||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
|
width: cardWidth,
|
||||||
child: Container(
|
height: cardHeight,
|
||||||
width: cardWidth,
|
color: Theme.of(context).cardColor,
|
||||||
height: cardHeight,
|
child: InkWell(
|
||||||
color: Theme.of(context).cardColor,
|
onTap: () => setState(() => isFrontSide = !isFrontSide),
|
||||||
child: InkWell(
|
child: isFrontSide
|
||||||
onTap: () => setState(() => isFrontSide = !isFrontSide),
|
? frontSide(colorsSync)
|
||||||
child: isFrontSide
|
: backSide(colorsSync)),
|
||||||
? frontSide(colorsSync)
|
|
||||||
: backSide(colorsSync)
|
|
||||||
),
|
),
|
||||||
),
|
)),
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget frontSide(List<Color> colorsSync) {
|
Widget frontSide(List<Color> colorsSync) {
|
||||||
final syncStore = Provider.of<SyncStore>(context);
|
// final syncStore = Provider.of<SyncStore>(context);
|
||||||
final walletStore = Provider.of<WalletStore>(context);
|
// final walletStore = Provider.of<WalletStore>(context);
|
||||||
final settingsStore = Provider.of<SettingsStore>(context);
|
final settingsStore = Provider.of<SettingsStore>(context);
|
||||||
final balanceStore = Provider.of<BalanceStore>(context);
|
// final balanceStore = Provider.of<BalanceStore>(context);
|
||||||
final triangleButton = Image.asset('assets/images/triangle.png',
|
final triangleButton = Image.asset(
|
||||||
|
'assets/images/triangle.png',
|
||||||
color: Theme.of(context).primaryTextTheme.title.color,
|
color: Theme.of(context).primaryTextTheme.title.color,
|
||||||
);
|
);
|
||||||
|
|
||||||
return Observer(
|
return Observer(
|
||||||
key: _syncingObserverKey,
|
key: _syncingObserverKey,
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
final status = syncStore.status;
|
final status = widget.walletVM.status;
|
||||||
final statusText = status.title();
|
final statusText = status.title();
|
||||||
final progress = syncStore.status.progress();
|
final progress = status.progress();
|
||||||
final indicatorWidth = progress * cardWidth;
|
final indicatorWidth = progress * cardWidth;
|
||||||
|
final shortAddress = widget.walletVM.address
|
||||||
String shortAddress = walletStore.subaddress.address;
|
.replaceRange(4, widget.walletVM.address.length - 4, '...');
|
||||||
shortAddress = shortAddress.replaceRange(4, shortAddress.length - 4, '...');
|
|
||||||
|
|
||||||
var descriptionText = '';
|
var descriptionText = '';
|
||||||
|
|
||||||
if (status is SyncingSyncStatus) {
|
if (status is SyncingSyncStatus) {
|
||||||
descriptionText = S
|
descriptionText = S.of(context).Blocks_remaining(status.toString());
|
||||||
.of(context)
|
|
||||||
.Blocks_remaining(
|
|
||||||
syncStore.status.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status is FailedSyncStatus) {
|
if (status is FailedSyncStatus) {
|
||||||
descriptionText = S
|
descriptionText = S.of(context).please_try_to_connect_to_another_node;
|
||||||
.of(context)
|
|
||||||
.please_try_to_connect_to_another_node;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
|
@ -149,183 +143,206 @@ class WalletCardState extends State<WalletCard> {
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
width: indicatorWidth,
|
width: indicatorWidth,
|
||||||
decoration: BoxDecoration(
|
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(
|
gradient: LinearGradient(
|
||||||
colors: colorsSync,
|
colors: colorsSync,
|
||||||
begin: Alignment.topCenter,
|
begin: Alignment.topCenter,
|
||||||
end: Alignment.bottomCenter
|
end: Alignment.bottomCenter)),
|
||||||
)
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
progress != 1
|
progress != 1
|
||||||
? Positioned(
|
? Positioned(
|
||||||
left: indicatorWidth,
|
left: indicatorWidth,
|
||||||
top: 0,
|
top: 0,
|
||||||
bottom: 0,
|
bottom: 0,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: 1,
|
width: 1,
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
color: Theme.of(context).focusColor,
|
color: Theme.of(context).focusColor,
|
||||||
)
|
))
|
||||||
)
|
: Offstage(),
|
||||||
: Offstage(),
|
isDraw
|
||||||
isDraw ? Positioned(
|
? Positioned(
|
||||||
left: 20,
|
left: 20,
|
||||||
right: 20,
|
right: 20,
|
||||||
top: 30,
|
top: 30,
|
||||||
bottom: 30,
|
bottom: 30,
|
||||||
child: Container(
|
child: Container(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: <Widget>[
|
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Column(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
InkWell(
|
Column(
|
||||||
onTap: () {},
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
child: Row(
|
children: <Widget>[
|
||||||
children: <Widget>[
|
InkWell(
|
||||||
Text(
|
onTap: () {},
|
||||||
walletStore.name,
|
child: Row(
|
||||||
style: TextStyle(
|
children: <Widget>[
|
||||||
fontSize: 20,
|
Text(
|
||||||
fontWeight: FontWeight.bold,
|
widget.walletVM.name,
|
||||||
color: Theme.of(context).primaryTextTheme.title.color
|
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(
|
Container(
|
||||||
height: 5,
|
width: 98,
|
||||||
),
|
height: 32,
|
||||||
Text(
|
alignment: Alignment.center,
|
||||||
walletStore.account.label,
|
decoration: BoxDecoration(
|
||||||
style: TextStyle(
|
color: Theme.of(context)
|
||||||
fontSize: 12,
|
.accentTextTheme
|
||||||
color: Theme.of(context).primaryTextTheme.caption.color
|
.subtitle
|
||||||
|
.backgroundColor,
|
||||||
|
borderRadius: BorderRadius.all(
|
||||||
|
Radius.circular(16))),
|
||||||
|
child: Text(
|
||||||
|
shortAddress,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 12,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.primaryTextTheme
|
||||||
|
.caption
|
||||||
|
.color),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Container(
|
status is SyncedSyncStatus
|
||||||
width: 98,
|
? Observer(
|
||||||
height: 32,
|
key: _balanceObserverKey,
|
||||||
alignment: Alignment.center,
|
builder: (_) {
|
||||||
decoration: BoxDecoration(
|
final balanceDisplayMode =
|
||||||
color: Theme.of(context).accentTextTheme.subtitle.backgroundColor,
|
BalanceDisplayMode.fullBalance;
|
||||||
borderRadius: BorderRadius.all(Radius.circular(16))
|
// settingsStore.balanceDisplayMode;
|
||||||
),
|
final symbol =
|
||||||
child: Text(
|
settingsStore.fiatCurrency.toString();
|
||||||
shortAddress,
|
var balance = '---';
|
||||||
style: TextStyle(
|
var fiatBalance = '---';
|
||||||
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 = '---';
|
|
||||||
|
|
||||||
if (balanceDisplayMode ==
|
if (balanceDisplayMode ==
|
||||||
BalanceDisplayMode.availableBalance) {
|
BalanceDisplayMode.availableBalance) {
|
||||||
balance =
|
balance = widget.walletVM.balance
|
||||||
balanceStore.unlockedBalance ??
|
.unlockedBalance ??
|
||||||
'0.0';
|
'0.0';
|
||||||
fiatBalance =
|
fiatBalance = '\$ 123.43';
|
||||||
'$symbol ${balanceStore.fiatUnlockedBalance}';
|
// '$symbol ${balanceStore.fiatUnlockedBalance}';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (balanceDisplayMode ==
|
if (balanceDisplayMode ==
|
||||||
BalanceDisplayMode.fullBalance) {
|
BalanceDisplayMode.fullBalance) {
|
||||||
balance =
|
balance = widget.walletVM.balance
|
||||||
balanceStore.fullBalance ?? '0.0';
|
.totalBalance ??
|
||||||
fiatBalance =
|
'0.0';
|
||||||
'$symbol ${balanceStore.fiatFullBalance}';
|
fiatBalance = '\$ 123.43';
|
||||||
}
|
// '$symbol ${balanceStore.fiatFullBalance}';
|
||||||
|
}
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment:
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
CrossAxisAlignment.end,
|
||||||
children: <Widget>[
|
mainAxisAlignment:
|
||||||
Column(
|
MainAxisAlignment.spaceBetween,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
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>[
|
children: <Widget>[
|
||||||
Text(
|
Column(
|
||||||
balanceDisplayMode.toString(),
|
crossAxisAlignment:
|
||||||
style: TextStyle(
|
CrossAxisAlignment.start,
|
||||||
fontSize: 12,
|
children: <Widget>[
|
||||||
color: Theme.of(context).primaryTextTheme.caption.color
|
Text(
|
||||||
),
|
statusText,
|
||||||
),
|
style: TextStyle(
|
||||||
SizedBox(height: 5),
|
fontSize: 12,
|
||||||
Text(
|
color: Theme.of(context)
|
||||||
balance,
|
.primaryTextTheme
|
||||||
style: TextStyle(
|
.caption
|
||||||
fontSize: 28,
|
.color),
|
||||||
color: Theme.of(context).primaryTextTheme.title.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) {
|
Widget backSide(List<Color> colorsSync) {
|
||||||
final walletStore = Provider.of<WalletStore>(context);
|
final rightArrow = Image.asset(
|
||||||
final rightArrow = Image.asset('assets/images/right_arrow.png',
|
'assets/images/right_arrow.png',
|
||||||
color: Theme.of(context).primaryTextTheme.title.color,
|
color: Theme.of(context).primaryTextTheme.title.color,
|
||||||
);
|
);
|
||||||
double messageBoxHeight = 0;
|
var messageBoxHeight = 0.0;
|
||||||
double messageBoxWidth = cardWidth - 10;
|
var messageBoxWidth = cardWidth - 10;
|
||||||
|
|
||||||
return Observer(
|
return Observer(
|
||||||
key: _addressObserverKey,
|
key: _addressObserverKey,
|
||||||
builder: (_) {
|
builder: (_) {
|
||||||
return Container(
|
return Container(
|
||||||
width: cardWidth,
|
width: cardWidth,
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
alignment: Alignment.topCenter,
|
alignment: Alignment.topCenter,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.topRight,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Container(
|
Container(
|
||||||
width: cardWidth,
|
width: cardWidth,
|
||||||
height: cardHeight,
|
height: cardHeight,
|
||||||
padding: EdgeInsets.only(left: 20, right: 20, top: 30, bottom: 30),
|
padding:
|
||||||
decoration: BoxDecoration(
|
EdgeInsets.only(left: 20, right: 20, top: 30, bottom: 30),
|
||||||
borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
|
decoration: BoxDecoration(
|
||||||
gradient: LinearGradient(
|
borderRadius: BorderRadius.only(
|
||||||
colors: colorsSync,
|
topLeft: Radius.circular(10),
|
||||||
begin: Alignment.topCenter,
|
bottomLeft: Radius.circular(10)),
|
||||||
end: Alignment.bottomCenter
|
gradient: LinearGradient(
|
||||||
)
|
colors: colorsSync,
|
||||||
),
|
begin: Alignment.topCenter,
|
||||||
child: Column(
|
end: Alignment.bottomCenter)),
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
child: Column(
|
||||||
children: <Widget>[
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
Row(
|
children: <Widget>[
|
||||||
children: <Widget>[
|
Row(
|
||||||
Expanded(
|
children: <Widget>[
|
||||||
child: Container(
|
Expanded(
|
||||||
|
child: Container(
|
||||||
height: 90,
|
height: 90,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
S.current.card_address,
|
S.current.card_address,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: Theme.of(context).primaryTextTheme.caption.color
|
color: Theme.of(context)
|
||||||
),
|
.primaryTextTheme
|
||||||
|
.caption
|
||||||
|
.color),
|
||||||
),
|
),
|
||||||
|
SizedBox(height: 10),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
Clipboard.setData(ClipboardData(
|
Clipboard.setData(ClipboardData(
|
||||||
text: walletStore.subaddress.address));
|
text: widget.walletVM.address));
|
||||||
_addressObserverKey.currentState.setState(() {
|
_addressObserverKey.currentState
|
||||||
|
.setState(() {
|
||||||
messageBoxHeight = 20;
|
messageBoxHeight = 20;
|
||||||
messageBoxWidth = cardWidth;
|
messageBoxWidth = cardWidth;
|
||||||
});
|
});
|
||||||
Timer(Duration(milliseconds: 1000), () {
|
Timer(Duration(milliseconds: 1000), () {
|
||||||
try {
|
try {
|
||||||
_addressObserverKey.currentState.setState(() {
|
_addressObserverKey.currentState
|
||||||
|
.setState(() {
|
||||||
messageBoxHeight = 0;
|
messageBoxHeight = 0;
|
||||||
messageBoxWidth = cardWidth - 10;
|
messageBoxWidth = cardWidth - 10;
|
||||||
});
|
});
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
print('${e.toString()}');
|
print('${e.toString()}');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
child: Text(
|
child: Text(
|
||||||
walletStore.subaddress.address,
|
widget.walletVM.address,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 14,
|
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(
|
Container(
|
||||||
onTap: () => Navigator.of(context,
|
height: 44,
|
||||||
rootNavigator: true)
|
padding: EdgeInsets.only(left: 20, right: 20),
|
||||||
.pushNamed(Routes.receive),
|
alignment: Alignment.center,
|
||||||
child: Row(
|
decoration: BoxDecoration(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
borderRadius: BorderRadius.all(Radius.circular(22)),
|
||||||
children: <Widget>[
|
color: Theme.of(context)
|
||||||
Text(
|
.primaryTextTheme
|
||||||
S.of(context).accounts_subaddresses,
|
.overline
|
||||||
style: TextStyle(
|
.color),
|
||||||
fontSize: 14,
|
child: InkWell(
|
||||||
color: Theme.of(context).primaryTextTheme.title.color
|
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: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_mobx/flutter_mobx.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/src/stores/wallet_creation/wallet_creation_store.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
import 'package:cake_wallet/src/stores/wallet_creation/wallet_creation_state.dart';
|
import 'package:cake_wallet/src/widgets/seed_language_selector.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/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/widgets/primary_button.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/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/screens/seed_language/widgets/seed_language_picker.dart';
|
||||||
import 'package:cake_wallet/src/widgets/alert_with_one_action.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 {
|
class NewWalletPage extends BasePage {
|
||||||
NewWalletPage(
|
NewWalletPage(this._walletNewVM);
|
||||||
{@required this.walletsService,
|
|
||||||
@required this.walletService,
|
|
||||||
@required this.sharedPreferences});
|
|
||||||
|
|
||||||
final WalletListService walletsService;
|
final WalletNewVM _walletNewVM;
|
||||||
final WalletService walletService;
|
|
||||||
final SharedPreferences sharedPreferences;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => S.current.new_wallet;
|
String get title => S.current.new_wallet;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) => WalletNameForm();
|
Widget body(BuildContext context) => WalletNameForm(_walletNewVM);
|
||||||
}
|
}
|
||||||
|
|
||||||
class WalletNameForm extends StatefulWidget {
|
class WalletNameForm extends StatefulWidget {
|
||||||
|
WalletNameForm(this._walletNewVM);
|
||||||
|
|
||||||
|
final WalletNewVM _walletNewVM;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_WalletNameFormState createState() => _WalletNameFormState();
|
_WalletNameFormState createState() => _WalletNameFormState(_walletNewVM);
|
||||||
}
|
}
|
||||||
|
|
||||||
class _WalletNameFormState extends State<WalletNameForm> {
|
class _WalletNameFormState extends State<WalletNameForm> {
|
||||||
|
_WalletNameFormState(this._walletNewVM);
|
||||||
|
|
||||||
static const aspectRatioImage = 1.22;
|
static const aspectRatioImage = 1.22;
|
||||||
|
|
||||||
final _formKey = GlobalKey<FormState>();
|
|
||||||
final nameController = TextEditingController();
|
|
||||||
final walletNameImage = Image.asset('assets/images/wallet_name.png');
|
final walletNameImage = Image.asset('assets/images/wallet_name.png');
|
||||||
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
final _languageSelectorKey = GlobalKey<SeedLanguageSelectorState>();
|
||||||
|
ReactionDisposer _stateReaction;
|
||||||
|
final WalletNewVM _walletNewVM;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void initState() {
|
||||||
nameController.dispose();
|
_stateReaction ??=
|
||||||
super.dispose();
|
reaction((_) => _walletNewVM.state, (WalletCreationState state) {
|
||||||
}
|
|
||||||
|
|
||||||
@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) {
|
|
||||||
if (state is WalletCreatedSuccessfully) {
|
if (state is WalletCreatedSuccessfully) {
|
||||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
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(
|
return Container(
|
||||||
padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: ScrollableWithBottomSection(
|
child: ScrollableWithBottomSection(
|
||||||
|
@ -116,94 +89,76 @@ class _WalletNameFormState extends State<WalletNameForm> {
|
||||||
child: Form(
|
child: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
textAlign: TextAlign.center,
|
onChanged: (value) => _walletNewVM.name = value,
|
||||||
style: TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontSize: 20.0,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontSize: 20.0,
|
||||||
color: Theme.of(context).primaryTextTheme.title.color),
|
fontWeight: FontWeight.w600,
|
||||||
controller: nameController,
|
color:
|
||||||
decoration: InputDecoration(
|
Theme.of(context).primaryTextTheme.title.color),
|
||||||
hintStyle: TextStyle(
|
decoration: InputDecoration(
|
||||||
fontSize: 16.0,
|
hintStyle: TextStyle(
|
||||||
color: Theme.of(context)
|
fontSize: 16.0,
|
||||||
.primaryTextTheme
|
color: Theme.of(context)
|
||||||
.caption
|
.primaryTextTheme
|
||||||
.color),
|
.caption
|
||||||
hintText: S.of(context).wallet_name,
|
.color),
|
||||||
focusedBorder: UnderlineInputBorder(
|
hintText: S.of(context).wallet_name,
|
||||||
borderSide: BorderSide(
|
focusedBorder: UnderlineInputBorder(
|
||||||
color: Theme.of(context).dividerColor,
|
borderSide: BorderSide(
|
||||||
width: 1.0)),
|
color: Theme.of(context).dividerColor,
|
||||||
enabledBorder: UnderlineInputBorder(
|
width: 1.0)),
|
||||||
borderSide: BorderSide(
|
enabledBorder: UnderlineInputBorder(
|
||||||
color: Theme.of(context).dividerColor,
|
borderSide: BorderSide(
|
||||||
width: 1.0))),
|
color: Theme.of(context).dividerColor,
|
||||||
validator: (value) {
|
width: 1.0))),
|
||||||
walletCreationStore.validateWalletName(value);
|
validator: WalletNameValidator())),
|
||||||
return walletCreationStore.errorMessage;
|
|
||||||
},
|
|
||||||
)),
|
|
||||||
),
|
),
|
||||||
Padding(
|
if (_walletNewVM.hasLanguageSelector) ...[
|
||||||
padding: EdgeInsets.only(top: 40),
|
Padding(
|
||||||
child: Text(
|
padding: EdgeInsets.only(top: 40),
|
||||||
S.of(context).seed_language_choose,
|
child: Text(
|
||||||
textAlign: TextAlign.center,
|
S.of(context).seed_language_choose,
|
||||||
style: TextStyle(
|
textAlign: TextAlign.center,
|
||||||
fontSize: 16.0,
|
style: TextStyle(
|
||||||
fontWeight: FontWeight.w600,
|
fontSize: 16.0,
|
||||||
color: Theme.of(context).primaryTextTheme.title.color),
|
fontWeight: FontWeight.w600,
|
||||||
|
color: Theme.of(context).primaryTextTheme.title.color),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
Padding(
|
padding: EdgeInsets.only(top: 24),
|
||||||
padding: EdgeInsets.only(top: 24),
|
child: SeedLanguageSelector(
|
||||||
child: Observer(
|
key: _languageSelectorKey,
|
||||||
builder: (_) => SelectButton(
|
initialSelected: defaultSeedLanguage),
|
||||||
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:
|
bottomSectionPadding:
|
||||||
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
bottomSection: Observer(
|
bottomSection: Observer(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return LoadingPrimaryButton(
|
return LoadingPrimaryButton(
|
||||||
onPressed: () => _confirmForm(walletCreationService,
|
onPressed: _confirmForm,
|
||||||
seedLanguageStore.selectedSeedLanguage),
|
|
||||||
text: S.of(context).continue_text,
|
text: S.of(context).continue_text,
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
isLoading: walletCreationStore.state is WalletIsCreating,
|
isLoading: _walletNewVM.state is WalletCreatedSuccessfully,
|
||||||
isDisabled: walletCreationStore.isDisabledStatus,
|
isDisabled: _walletNewVM.name.isEmpty,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _confirmForm(
|
void _confirmForm() {
|
||||||
WalletCreationService walletCreationService, String language) {
|
|
||||||
if (!_formKey.currentState.validate()) {
|
if (!_formKey.currentState.validate()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
WalletCredentials credentials;
|
_walletNewVM.create(
|
||||||
|
options: _walletNewVM.hasLanguageSelector
|
||||||
if (walletCreationService.type == WalletType.monero) {
|
? _languageSelectorKey.currentState.selected
|
||||||
credentials = MoneroNewWalletCredentials(
|
: null);
|
||||||
name: nameController.text, language: language);
|
|
||||||
}
|
|
||||||
|
|
||||||
walletCreationService.create(credentials);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.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';
|
import 'package:cake_wallet/routes.dart';
|
||||||
|
|
||||||
class NewWalletTypePage extends BasePage {
|
class NewWalletTypePage extends BasePage {
|
||||||
|
NewWalletTypePage({this.onTypeSelected});
|
||||||
|
|
||||||
|
final void Function(BuildContext, WalletType) onTypeSelected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => S.current.new_wallet;
|
String get title => S.current.new_wallet;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) => WalletTypeForm();
|
Widget body(BuildContext context) =>
|
||||||
|
WalletTypeForm(onTypeSelected: onTypeSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
class WalletTypeForm extends StatefulWidget {
|
class WalletTypeForm extends StatefulWidget {
|
||||||
|
WalletTypeForm({this.onTypeSelected});
|
||||||
|
|
||||||
|
final void Function(BuildContext, WalletType) onTypeSelected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
WalletTypeFormState createState() => WalletTypeFormState();
|
WalletTypeFormState createState() => WalletTypeFormState();
|
||||||
}
|
}
|
||||||
|
@ -23,35 +33,19 @@ class WalletTypeForm extends StatefulWidget {
|
||||||
class WalletTypeFormState extends State<WalletTypeForm> {
|
class WalletTypeFormState extends State<WalletTypeForm> {
|
||||||
static const aspectRatioImage = 1.22;
|
static const aspectRatioImage = 1.22;
|
||||||
|
|
||||||
final moneroIcon = Image.asset('assets/images/monero.png', height: 24, width: 24);
|
final moneroIcon =
|
||||||
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
|
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');
|
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
|
||||||
|
|
||||||
bool isDisabledButton;
|
WalletType selected;
|
||||||
bool isMoneroSelected;
|
List<WalletType> types;
|
||||||
bool isBitcoinSelected;
|
|
||||||
|
|
||||||
Color moneroBackgroundColor = Colors.transparent;
|
|
||||||
Color moneroTextColor = Colors.transparent;
|
|
||||||
Color bitcoinBackgroundColor = Colors.transparent;
|
|
||||||
Color bitcoinTextColor = Colors.transparent;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
isDisabledButton = true;
|
types = [WalletType.bitcoin, WalletType.monero];
|
||||||
isMoneroSelected = false;
|
|
||||||
isBitcoinSelected = false;
|
|
||||||
|
|
||||||
super.initState();
|
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
|
@override
|
||||||
|
@ -75,71 +69,52 @@ class WalletTypeFormState extends State<WalletTypeForm> {
|
||||||
S.of(context).choose_wallet_currency,
|
S.of(context).choose_wallet_currency,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context).primaryTextTheme.title.color
|
color: Theme.of(context).primaryTextTheme.title.color),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
...types.map((type) => Padding(
|
||||||
padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: SelectButton(
|
child: SelectButton(
|
||||||
image: bitcoinIcon,
|
image: _iconFor(type),
|
||||||
text: 'Bitcoin',
|
text: walletTypeToString(type),
|
||||||
color: bitcoinBackgroundColor,
|
color: _backgroundColorFor(selected == type),
|
||||||
textColor: bitcoinTextColor,
|
textColor: _textColorFor(selected == type),
|
||||||
onTap: () {}),
|
onTap: () => setState(() => selected = type)),
|
||||||
),
|
))
|
||||||
Padding(
|
|
||||||
padding: EdgeInsets.only(top: 20),
|
|
||||||
child: SelectButton(
|
|
||||||
image: moneroIcon,
|
|
||||||
text: 'Monero',
|
|
||||||
color: moneroBackgroundColor,
|
|
||||||
textColor: moneroTextColor,
|
|
||||||
onTap: () => onSelectMoneroButton(context)),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
bottomSectionPadding: EdgeInsets.only(
|
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
left: 24,
|
|
||||||
right: 24,
|
|
||||||
bottom: 24
|
|
||||||
),
|
|
||||||
bottomSection: PrimaryButton(
|
bottomSection: PrimaryButton(
|
||||||
onPressed: () => Navigator.of(context).pushNamed(Routes.newWallet),
|
onPressed: () => widget.onTypeSelected(context, selected),
|
||||||
text: S.of(context).seed_language_next,
|
text: S.of(context).seed_language_next,
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
isDisabled: isDisabledButton,
|
isDisabled: selected == null,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onSelectMoneroButton(BuildContext context) {
|
// FIXME: Move color selection inside ui element; add isSelected to buttons.
|
||||||
isMoneroSelected = true;
|
|
||||||
isBitcoinSelected = false;
|
|
||||||
isDisabledButton = false;
|
|
||||||
|
|
||||||
moneroBackgroundColor = Theme.of(context).accentTextTheme.title.decorationColor;
|
Color _backgroundColorFor(bool isSelected) => isSelected
|
||||||
moneroTextColor = Theme.of(context).primaryTextTheme.title.backgroundColor;
|
? Theme.of(context).accentTextTheme.title.decorationColor
|
||||||
bitcoinBackgroundColor = Theme.of(context).accentTextTheme.title.backgroundColor;
|
: Theme.of(context).accentTextTheme.title.backgroundColor;
|
||||||
bitcoinTextColor = Theme.of(context).primaryTextTheme.title.color;
|
|
||||||
|
|
||||||
setState(() {});
|
Color _textColorFor(bool isSelected) => isSelected
|
||||||
}
|
? Theme.of(context).primaryTextTheme.title.backgroundColor
|
||||||
|
: Theme.of(context).primaryTextTheme.title.color;
|
||||||
|
|
||||||
void onSelectBitcoinButton(BuildContext context) {
|
Image _iconFor(WalletType type) {
|
||||||
isMoneroSelected = false;
|
switch (type) {
|
||||||
isBitcoinSelected = true;
|
case WalletType.monero:
|
||||||
isDisabledButton = false;
|
return moneroIcon;
|
||||||
|
case WalletType.bitcoin:
|
||||||
moneroBackgroundColor = Theme.of(context).accentTextTheme.title.backgroundColor;
|
return bitcoinIcon;
|
||||||
moneroTextColor = Theme.of(context).primaryTextTheme.title.color;
|
default:
|
||||||
bitcoinBackgroundColor = moneroBackgroundColor = Theme.of(context).accentTextTheme.title.decorationColor;
|
return null;
|
||||||
bitcoinTextColor = Theme.of(context).primaryTextTheme.title.backgroundColor;
|
}
|
||||||
|
|
||||||
setState(() {});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -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/material.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/services.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/src/widgets/base_text_form_field.dart';
|
||||||
import 'package:cake_wallet/themes.dart';
|
import 'package:cake_wallet/themes.dart';
|
||||||
import 'package:cake_wallet/theme_changer.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 {
|
class ReceivePage extends BasePage {
|
||||||
@override
|
ReceivePage({this.addressListViewModel})
|
||||||
ReceivePageState createState() => ReceivePageState();
|
: amountController = TextEditingController(),
|
||||||
}
|
_formKey = GlobalKey<FormState>() {
|
||||||
|
amountController.addListener(() => addressListViewModel.amount =
|
||||||
|
_formKey.currentState.validate() ? amountController.text : '');
|
||||||
|
}
|
||||||
|
|
||||||
class ReceivePageState extends State<ReceivePage> {
|
final AddressListViewModel addressListViewModel;
|
||||||
final amountController = TextEditingController();
|
final TextEditingController amountController;
|
||||||
final _formKey = GlobalKey<FormState>();
|
final GlobalKey<FormState> _formKey;
|
||||||
final _backArrowImage = Image.asset('assets/images/back_arrow.png');
|
|
||||||
final _backArrowImageDarkTheme =
|
|
||||||
Image.asset('assets/images/back_arrow_dark_theme.png');
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
Color get backgroundLightColor => Colors.transparent;
|
||||||
amountController.dispose();
|
|
||||||
super.dispose();
|
@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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final walletStore = Provider.of<WalletStore>(context);
|
return super.build(context);
|
||||||
final subaddressListStore = Provider.of<SubaddressListStore>(context);
|
}
|
||||||
final accountListStore = Provider.of<AccountListStore>(context);
|
|
||||||
|
|
||||||
final shareImage = Image.asset('assets/images/share.png',
|
@override
|
||||||
color: Theme.of(context).primaryTextTheme.title.color,
|
Widget body(BuildContext context) {
|
||||||
);
|
|
||||||
final copyImage = Image.asset('assets/images/copy_content.png',
|
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;
|
return SingleChildScrollView(
|
||||||
final notCurrentColor = Theme.of(context).backgroundColor;
|
child: Column(
|
||||||
|
children: <Widget>[
|
||||||
final currentTextColor = Colors.blue;
|
SizedBox(height: 25),
|
||||||
final notCurrentTextColor = Theme.of(context).primaryTextTheme.caption.color;
|
Row(children: <Widget>[
|
||||||
|
Spacer(flex: 4),
|
||||||
final _themeChanger = Provider.of<ThemeChanger>(context);
|
Observer(
|
||||||
Image _backButton;
|
builder: (_) => Flexible(
|
||||||
|
flex: 6,
|
||||||
if (_themeChanger.getTheme() == Themes.darkTheme) {
|
child: Center(
|
||||||
_backButton = _backArrowImageDarkTheme;
|
child: AspectRatio(
|
||||||
} else {
|
aspectRatio: 1.0,
|
||||||
_backButton = _backArrowImage;
|
child: QrImage(
|
||||||
}
|
data: addressListViewModel.uri.toString(),
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
amountController.addListener(() {
|
foregroundColor: Theme.of(context)
|
||||||
if (_formKey.currentState.validate()) {
|
.primaryTextTheme
|
||||||
walletStore.onChangedAmountValue(amountController.text);
|
.display4
|
||||||
} else {
|
.color,
|
||||||
walletStore.onChangedAmountValue('');
|
))))),
|
||||||
}
|
Spacer(flex: 4)
|
||||||
});
|
]),
|
||||||
|
Padding(
|
||||||
return Scaffold(
|
padding: EdgeInsets.fromLTRB(24, 40, 24, 0),
|
||||||
resizeToAvoidBottomPadding: false,
|
child: Row(
|
||||||
body: Container(
|
children: <Widget>[
|
||||||
height: MediaQuery.of(context).size.height,
|
Expanded(
|
||||||
width: MediaQuery.of(context).size.width,
|
child: Form(
|
||||||
padding: EdgeInsets.only(top: 24),
|
key: _formKey,
|
||||||
decoration: BoxDecoration(
|
child: BaseTextFormField(
|
||||||
gradient: LinearGradient(
|
controller: amountController,
|
||||||
colors: [
|
keyboardType:
|
||||||
Theme.of(context).scaffoldBackgroundColor,
|
TextInputType.numberWithOptions(decimal: true),
|
||||||
Theme.of(context).primaryColor
|
inputFormatters: [
|
||||||
],
|
BlacklistingTextInputFormatter(
|
||||||
begin: Alignment.centerLeft,
|
RegExp('[\\-|\\ |\\,]'))
|
||||||
end: Alignment.centerRight
|
],
|
||||||
)
|
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(
|
Padding(
|
||||||
children: <Widget>[
|
padding: EdgeInsets.only(left: 24, right: 24, bottom: 40, top: 40),
|
||||||
Container(
|
child: Builder(
|
||||||
padding: EdgeInsets.only(
|
builder: (context) => Observer(
|
||||||
top: 10,
|
builder: (context) => GestureDetector(
|
||||||
bottom: 20,
|
onTap: () {
|
||||||
left: 5,
|
Clipboard.setData(ClipboardData(
|
||||||
right: 10
|
text: addressListViewModel.address.address));
|
||||||
),
|
Scaffold.of(context).showSnackBar(SnackBar(
|
||||||
child: Row(
|
content: Text(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
S.of(context).copied_to_clipboard,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
style: TextStyle(color: Colors.white),
|
||||||
children: <Widget>[
|
),
|
||||||
SizedBox(
|
backgroundColor: Colors.green,
|
||||||
height: 44,
|
duration: Duration(milliseconds: 500),
|
||||||
width: 44,
|
));
|
||||||
child: ButtonTheme(
|
},
|
||||||
minWidth: double.minPositive,
|
child: Container(
|
||||||
child: FlatButton(
|
height: 48,
|
||||||
highlightColor: Colors.transparent,
|
padding: EdgeInsets.only(left: 24, right: 24),
|
||||||
splashColor: Colors.transparent,
|
decoration: BoxDecoration(
|
||||||
padding: EdgeInsets.all(0),
|
borderRadius:
|
||||||
onPressed: () => Navigator.of(context).pop(),
|
BorderRadius.all(Radius.circular(24)),
|
||||||
child: _backButton),
|
color: Theme.of(context)
|
||||||
),
|
.primaryTextTheme
|
||||||
),
|
.overline
|
||||||
Text(
|
.color),
|
||||||
S.of(context).receive,
|
child: Row(
|
||||||
style: TextStyle(
|
mainAxisSize: MainAxisSize.max,
|
||||||
fontSize: 18.0,
|
children: <Widget>[
|
||||||
fontWeight: FontWeight.bold,
|
Expanded(
|
||||||
color: Theme.of(context).primaryTextTheme.title.color),
|
child: Text(
|
||||||
),
|
addressListViewModel.address.address,
|
||||||
SizedBox(
|
maxLines: 1,
|
||||||
height: 44.0,
|
overflow: TextOverflow.ellipsis,
|
||||||
width: 44.0,
|
style: TextStyle(
|
||||||
child: ButtonTheme(
|
fontSize: 18,
|
||||||
minWidth: double.minPositive,
|
fontWeight: FontWeight.w600,
|
||||||
child: FlatButton(
|
color: Theme.of(context)
|
||||||
highlightColor: Colors.transparent,
|
.primaryTextTheme
|
||||||
splashColor: Colors.transparent,
|
.title
|
||||||
padding: EdgeInsets.all(0),
|
.color),
|
||||||
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: 12),
|
||||||
),
|
child: copyImage,
|
||||||
),
|
)
|
||||||
Observer(
|
],
|
||||||
builder: (_) => ListView.separated(
|
),
|
||||||
separatorBuilder: (context, index) => Divider(
|
),
|
||||||
height: 1,
|
))),
|
||||||
color: Theme.of(context).dividerColor,
|
),
|
||||||
),
|
Observer(
|
||||||
shrinkWrap: true,
|
builder: (_) => ListView.separated(
|
||||||
physics: NeverScrollableScrollPhysics(),
|
separatorBuilder: (context, _) =>
|
||||||
itemCount: subaddressListStore.subaddresses.length + 2,
|
Divider(height: 1, color: Theme.of(context).dividerColor),
|
||||||
itemBuilder: (context, index) {
|
shrinkWrap: true,
|
||||||
|
physics: NeverScrollableScrollPhysics(),
|
||||||
|
itemCount: addressListViewModel.items.length,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final item = addressListViewModel.items[index];
|
||||||
|
Widget cell = Container();
|
||||||
|
|
||||||
if (index == 0) {
|
if (item is AccountListHeader) {
|
||||||
return ClipRRect(
|
cell = HeaderTile(
|
||||||
borderRadius: BorderRadius.only(
|
onTap: () async {
|
||||||
topLeft: Radius.circular(24),
|
await showDialog<void>(
|
||||||
topRight: Radius.circular(24)
|
context: context,
|
||||||
),
|
builder: (BuildContext context) {
|
||||||
child: HeaderTile(
|
// return AccountListPage(
|
||||||
onTap: () async {
|
// accountListStore:
|
||||||
await showDialog<void>(
|
// accountListStore);
|
||||||
context: context,
|
});
|
||||||
builder: (BuildContext context) {
|
},
|
||||||
return AccountListPage(accountListStore: accountListStore);
|
title: addressListViewModel.accountLabel,
|
||||||
}
|
icon: Icon(
|
||||||
);
|
Icons.arrow_forward_ios,
|
||||||
},
|
size: 14,
|
||||||
title: walletStore.account.label,
|
color:
|
||||||
icon: Icon(
|
Theme.of(context).primaryTextTheme.title.color,
|
||||||
Icons.arrow_forward_ios,
|
));
|
||||||
size: 14,
|
}
|
||||||
color: Theme.of(context).primaryTextTheme.title.color,
|
|
||||||
)
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index == 1) {
|
if (item is AddressListHeader) {
|
||||||
return HeaderTile(
|
cell = HeaderTile(
|
||||||
onTap: () => Navigator.of(context)
|
onTap: () => Navigator.of(context)
|
||||||
.pushNamed(Routes.newSubaddress),
|
.pushNamed(Routes.newSubaddress),
|
||||||
title: S.of(context).subaddresses,
|
title: S.of(context).subaddresses,
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.add,
|
Icons.add,
|
||||||
size: 20,
|
size: 20,
|
||||||
color: Theme.of(context).primaryTextTheme.title.color,
|
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(
|
return index != 0
|
||||||
builder: (_) {
|
? cell
|
||||||
final subaddress = subaddressListStore.subaddresses[index];
|
: ClipRRect(
|
||||||
final isCurrent =
|
borderRadius: BorderRadius.only(
|
||||||
walletStore.subaddress.address == subaddress.address;
|
topLeft: Radius.circular(24),
|
||||||
|
topRight: Radius.circular(24)),
|
||||||
final label = subaddress.label.isNotEmpty
|
child: cell,
|
||||||
? 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),
|
|
||||||
)
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
],
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
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:mobx/mobx.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_store.dart';
|
import 'package:cake_wallet/core/validator.dart';
|
||||||
import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_state.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/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/widgets/blockchain_height_widget.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/scollable_with_bottom_section.dart';
|
||||||
import 'package:cake_wallet/src/widgets/primary_button.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/src/widgets/alert_with_one_action.dart';
|
||||||
|
import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart';
|
||||||
|
|
||||||
class RestoreWalletFromSeedDetailsPage extends BasePage {
|
class RestoreWalletFromSeedDetailsPage extends BasePage {
|
||||||
|
RestoreWalletFromSeedDetailsPage(
|
||||||
|
{@required this.walletRestorationFromSeedVM});
|
||||||
|
|
||||||
|
final WalletRestorationFromSeedVM walletRestorationFromSeedVM;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => S.current.restore_wallet_restore_description;
|
String get title => S.current.restore_wallet_restore_description;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) => RestoreFromSeedDetailsForm();
|
Widget body(BuildContext context) => RestoreFromSeedDetailsForm(
|
||||||
|
walletRestorationFromSeedVM: walletRestorationFromSeedVM);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RestoreFromSeedDetailsForm extends StatefulWidget {
|
class RestoreFromSeedDetailsForm extends StatefulWidget {
|
||||||
|
RestoreFromSeedDetailsForm({@required this.walletRestorationFromSeedVM});
|
||||||
|
|
||||||
|
final WalletRestorationFromSeedVM walletRestorationFromSeedVM;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_RestoreFromSeedDetailsFormState createState() =>
|
_RestoreFromSeedDetailsFormState createState() =>
|
||||||
_RestoreFromSeedDetailsFormState();
|
_RestoreFromSeedDetailsFormState();
|
||||||
|
@ -31,31 +40,17 @@ class _RestoreFromSeedDetailsFormState
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
final _blockchainHeightKey = GlobalKey<BlockchainHeightState>();
|
final _blockchainHeightKey = GlobalKey<BlockchainHeightState>();
|
||||||
final _nameController = TextEditingController();
|
final _nameController = TextEditingController();
|
||||||
|
ReactionDisposer _stateReaction;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void initState() {
|
||||||
_nameController.dispose();
|
_stateReaction = reaction((_) => widget.walletRestorationFromSeedVM.state,
|
||||||
super.dispose();
|
(WalletCreationState state) {
|
||||||
}
|
if (state is WalletCreatedSuccessfully) {
|
||||||
|
|
||||||
@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) {
|
|
||||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state is WalletRestorationFailure) {
|
if (state is WalletCreationFailure) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
showDialog<void>(
|
showDialog<void>(
|
||||||
context: context,
|
context: context,
|
||||||
|
@ -64,73 +59,87 @@ class _RestoreFromSeedDetailsFormState
|
||||||
alertTitle: S.current.restore_title_from_seed,
|
alertTitle: S.current.restore_title_from_seed,
|
||||||
alertContent: state.error,
|
alertContent: state.error,
|
||||||
buttonText: S.of(context).ok,
|
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(
|
return Container(
|
||||||
padding: EdgeInsets.only(left: 24, right: 24),
|
padding: EdgeInsets.only(left: 24, right: 24),
|
||||||
child: ScrollableWithBottomSection(
|
child: ScrollableWithBottomSection(
|
||||||
contentPadding: EdgeInsets.only(bottom: 24.0),
|
contentPadding: EdgeInsets.only(bottom: 24.0),
|
||||||
content: Form(
|
content: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
child: Column(
|
child: Column(children: <Widget>[
|
||||||
|
Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Row(
|
Flexible(
|
||||||
children: <Widget>[
|
child: Container(
|
||||||
Flexible(
|
padding: EdgeInsets.only(top: 20.0),
|
||||||
child: Container(
|
child: TextFormField(
|
||||||
padding: EdgeInsets.only(top: 20.0),
|
style: TextStyle(
|
||||||
child: TextFormField(
|
fontSize: 16.0,
|
||||||
style: TextStyle(
|
color: Theme.of(context).primaryTextTheme.title.color),
|
||||||
fontSize: 16.0,
|
controller: _nameController,
|
||||||
color: Theme.of(context).primaryTextTheme.title.color
|
decoration: InputDecoration(
|
||||||
),
|
hintStyle: TextStyle(
|
||||||
controller: _nameController,
|
color: Theme.of(context)
|
||||||
decoration: InputDecoration(
|
.primaryTextTheme
|
||||||
hintStyle: TextStyle(
|
.caption
|
||||||
color: Theme.of(context).primaryTextTheme.caption.color,
|
.color,
|
||||||
fontSize: 16
|
fontSize: 16),
|
||||||
),
|
hintText: S.of(context).restore_wallet_name,
|
||||||
hintText: S.of(context).restore_wallet_name,
|
focusedBorder: UnderlineInputBorder(
|
||||||
focusedBorder: UnderlineInputBorder(
|
borderSide: BorderSide(
|
||||||
borderSide: BorderSide(
|
color: Theme.of(context).dividerColor,
|
||||||
color: Theme.of(context).dividerColor,
|
width: 1.0)),
|
||||||
width: 1.0)),
|
enabledBorder: UnderlineInputBorder(
|
||||||
enabledBorder: UnderlineInputBorder(
|
borderSide: BorderSide(
|
||||||
borderSide: BorderSide(
|
color: Theme.of(context).dividerColor,
|
||||||
color: Theme.of(context).dividerColor,
|
width: 1.0))),
|
||||||
width: 1.0))),
|
validator: WalletNameValidator(),
|
||||||
validator: (value) {
|
),
|
||||||
walletRestorationStore
|
))
|
||||||
.validateWalletName(value);
|
],
|
||||||
return walletRestorationStore.errorMessage;
|
),
|
||||||
},
|
if (widget.walletRestorationFromSeedVM.hasRestorationHeight)
|
||||||
),
|
BlockchainHeightWidget(
|
||||||
))
|
key: _blockchainHeightKey,
|
||||||
],
|
onHeightChange: (height) {
|
||||||
),
|
widget.walletRestorationFromSeedVM.height = height;
|
||||||
BlockchainHeightWidget(key: _blockchainHeightKey),
|
print(height);
|
||||||
]),
|
}),
|
||||||
|
]),
|
||||||
),
|
),
|
||||||
bottomSectionPadding: EdgeInsets.only(bottom: 24),
|
bottomSectionPadding: EdgeInsets.only(bottom: 24),
|
||||||
bottomSection: Observer(builder: (_) {
|
bottomSection: Observer(builder: (_) {
|
||||||
return LoadingPrimaryButton(
|
return LoadingPrimaryButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
if (_formKey.currentState.validate()) {
|
if (_formKey.currentState.validate()) {
|
||||||
walletRestorationStore.restoreFromSeed(
|
widget.walletRestorationFromSeedVM.create();
|
||||||
name: _nameController.text,
|
|
||||||
restoreHeight: _blockchainHeightKey.currentState.height);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isLoading: walletRestorationStore.state is WalletIsRestoring,
|
isLoading:
|
||||||
|
widget.walletRestorationFromSeedVM.state is WalletCreating,
|
||||||
text: S.of(context).restore_recover,
|
text: S.of(context).restore_recover,
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
textColor: Colors.white,
|
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/material.dart';
|
||||||
import 'package:flutter/cupertino.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:cake_wallet/routes.dart';
|
import 'package:cake_wallet/routes.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.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/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/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/palette.dart';
|
||||||
|
import 'package:cake_wallet/core/mnemonic_length.dart';
|
||||||
|
|
||||||
class RestoreWalletFromSeedPage extends BasePage {
|
class RestoreWalletFromSeedPage extends BasePage {
|
||||||
RestoreWalletFromSeedPage(
|
RestoreWalletFromSeedPage({@required this.type, @required this.language});
|
||||||
{@required this.walletsService,
|
|
||||||
@required this.walletService,
|
|
||||||
@required this.sharedPreferences});
|
|
||||||
|
|
||||||
final WalletListService walletsService;
|
final WalletType type;
|
||||||
final WalletService walletService;
|
final String language;
|
||||||
final SharedPreferences sharedPreferences;
|
|
||||||
final formKey = GlobalKey<_RestoreFromSeedFormState>();
|
final formKey = GlobalKey<_RestoreFromSeedFormState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -34,11 +26,14 @@ class RestoreWalletFromSeedPage extends BasePage {
|
||||||
Color get backgroundDarkColor => PaletteDark.lightNightBlue;
|
Color get backgroundDarkColor => PaletteDark.lightNightBlue;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) => RestoreFromSeedForm(key: formKey);
|
Widget body(BuildContext context) =>
|
||||||
|
RestoreFromSeedForm(key: formKey, type: type, language: language);
|
||||||
}
|
}
|
||||||
|
|
||||||
class RestoreFromSeedForm extends StatefulWidget {
|
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
|
@override
|
||||||
_RestoreFromSeedFormState createState() => _RestoreFromSeedFormState();
|
_RestoreFromSeedFormState createState() => _RestoreFromSeedFormState();
|
||||||
|
@ -46,13 +41,11 @@ class RestoreFromSeedForm extends StatefulWidget {
|
||||||
|
|
||||||
class _RestoreFromSeedFormState extends State<RestoreFromSeedForm> {
|
class _RestoreFromSeedFormState extends State<RestoreFromSeedForm> {
|
||||||
final _seedKey = GlobalKey<SeedWidgetState>();
|
final _seedKey = GlobalKey<SeedWidgetState>();
|
||||||
void clear() => _seedKey.currentState.clear();
|
|
||||||
|
String mnemonic() => _seedKey.currentState.items.map((e) => e.text).join(' ');
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final walletRestorationStore = Provider.of<WalletRestorationStore>(context);
|
|
||||||
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
|
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () =>
|
onTap: () =>
|
||||||
SystemChannels.textInput.invokeMethod<void>('TextInput.hide'),
|
SystemChannels.textInput.invokeMethod<void>('TextInput.hide'),
|
||||||
|
@ -60,11 +53,13 @@ class _RestoreFromSeedFormState extends State<RestoreFromSeedForm> {
|
||||||
color: Theme.of(context).backgroundColor,
|
color: Theme.of(context).backgroundColor,
|
||||||
child: SeedWidget(
|
child: SeedWidget(
|
||||||
key: _seedKey,
|
key: _seedKey,
|
||||||
onMnemoticChange: (seed) => walletRestorationStore.setSeed(seed),
|
maxLength: mnemonicLength(widget.type),
|
||||||
|
onMnemonicChange: (seed) => null,
|
||||||
onFinish: () => Navigator.of(context).pushNamed(
|
onFinish: () => Navigator.of(context).pushNamed(
|
||||||
Routes.restoreWalletFromSeedDetails,
|
Routes.restoreWalletFromSeedDetails,
|
||||||
arguments: _seedKey.currentState.items),
|
arguments: [widget.type, widget.language, mnemonic()]),
|
||||||
seedLanguage: seedLanguageStore.selectedSeedLanguage,
|
validator:
|
||||||
|
SeedValidator(type: widget.type, language: widget.language),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
import 'package:flutter/material.dart';
|
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/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/restore/widgets/restore_button.dart';
|
||||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||||
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
|
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
class RestoreWalletOptionsPage extends BasePage {
|
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 WalletType type;
|
||||||
|
final Function(BuildContext context) onRestoreFromSeed;
|
||||||
|
final Function(BuildContext context) onRestoreFromKeys;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get title => S.current.restore_seed_keys_restore;
|
String get title => S.current.restore_seed_keys_restore;
|
||||||
|
@ -23,8 +25,6 @@ class RestoreWalletOptionsPage extends BasePage {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) {
|
Widget body(BuildContext context) {
|
||||||
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
|
|
||||||
|
|
||||||
return Container(
|
return Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
|
@ -33,28 +33,56 @@ class RestoreWalletOptionsPage extends BasePage {
|
||||||
child: Column(
|
child: Column(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
RestoreButton(
|
RestoreButton(
|
||||||
onPressed: () {
|
onPressed: () => onRestoreFromSeed(context),
|
||||||
seedLanguageStore
|
|
||||||
.setCurrentRoute(Routes.restoreWalletFromSeed);
|
|
||||||
Navigator.pushNamed(context, Routes.seedLanguage);
|
|
||||||
},
|
|
||||||
image: imageSeed,
|
image: imageSeed,
|
||||||
title: S.of(context).restore_title_from_seed,
|
title: S.of(context).restore_title_from_seed,
|
||||||
description: S.of(context).restore_description_from_seed),
|
description: _fromSeedDescription(context)),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: RestoreButton(
|
child: RestoreButton(
|
||||||
onPressed: () {
|
onPressed: () => onRestoreFromKeys(context),
|
||||||
seedLanguageStore
|
|
||||||
.setCurrentRoute(Routes.restoreWalletFromKeys);
|
|
||||||
Navigator.pushNamed(context, Routes.seedLanguage);
|
|
||||||
},
|
|
||||||
image: imageKeys,
|
image: imageKeys,
|
||||||
title: S.of(context).restore_title_from_keys,
|
title: _fromKeyTitle(context),
|
||||||
description: S.of(context).restore_description_from_keys),
|
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/material.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:cake_wallet/routes.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/price/price_store.dart';
|
||||||
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
|
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
|
||||||
import 'package:cake_wallet/src/stores/wallet/wallet_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';
|
import 'package:cake_wallet/src/screens/welcome/create_welcome_page.dart';
|
||||||
|
|
||||||
class Root extends StatefulWidget {
|
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
|
@override
|
||||||
RootState createState() => RootState();
|
RootState createState() => RootState();
|
||||||
|
@ -30,7 +38,6 @@ class Root extends StatefulWidget {
|
||||||
class RootState extends State<Root> with WidgetsBindingObserver {
|
class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
bool _isInactive;
|
bool _isInactive;
|
||||||
bool _postFrameCallback;
|
bool _postFrameCallback;
|
||||||
AuthenticationStore _authenticationStore;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
|
@ -48,12 +55,12 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!_isInactive &&
|
// if (!_isInactive &&
|
||||||
_authenticationStore.state ==
|
// widget.authenticationStore.state ==
|
||||||
AuthenticationState.authenticated ||
|
// AuthenticationState.authenticated ||
|
||||||
_authenticationStore.state == AuthenticationState.active) {
|
// widget.authenticationStore.state == AuthenticationState.active) {
|
||||||
setState(() => _isInactive = true);
|
// setState(() => _isInactive = true);
|
||||||
}
|
// }
|
||||||
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -63,18 +70,18 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_authenticationStore = Provider.of<AuthenticationStore>(context);
|
// _authenticationStore = Provider.of<AuthenticationStore>(context);
|
||||||
final sharedPreferences = Provider.of<SharedPreferences>(context);
|
// final sharedPreferences = Provider.of<SharedPreferences>(context);
|
||||||
final walletListService = Provider.of<WalletListService>(context);
|
// final walletListService = Provider.of<WalletListService>(context);
|
||||||
final walletService = Provider.of<WalletService>(context);
|
// final walletService = Provider.of<WalletService>(context);
|
||||||
final userService = Provider.of<UserService>(context);
|
// final userService = Provider.of<UserService>(context);
|
||||||
final priceStore = Provider.of<PriceStore>(context);
|
// final priceStore = Provider.of<PriceStore>(context);
|
||||||
final authenticationStore = Provider.of<AuthenticationStore>(context);
|
// final authenticationStore = Provider.of<AuthenticationStore>(context);
|
||||||
final trades = Provider.of<Box<Trade>>(context);
|
// final trades = Provider.of<Box<Trade>>(context);
|
||||||
final transactionDescriptions =
|
// final transactionDescriptions =
|
||||||
Provider.of<Box<TransactionDescription>>(context);
|
// Provider.of<Box<TransactionDescription>>(context);
|
||||||
final walletStore = Provider.of<WalletStore>(context);
|
// final walletStore = Provider.of<WalletStore>(context);
|
||||||
final settingsStore = Provider.of<SettingsStore>(context);
|
// final settingsStore = Provider.of<SettingsStore>(context);
|
||||||
|
|
||||||
if (_isInactive && !_postFrameCallback) {
|
if (_isInactive && !_postFrameCallback) {
|
||||||
_postFrameCallback = true;
|
_postFrameCallback = true;
|
||||||
|
@ -96,38 +103,51 @@ class RootState extends State<Root> with WidgetsBindingObserver {
|
||||||
}
|
}
|
||||||
|
|
||||||
return Observer(builder: (_) {
|
return Observer(builder: (_) {
|
||||||
final state = _authenticationStore.state;
|
final state = widget.authenticationStore.state;
|
||||||
|
print(state);
|
||||||
if (state == AuthenticationState.denied) {
|
if (state == AuthenticationState.denied) {
|
||||||
return createWelcomePage();
|
return createWelcomePage();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == AuthenticationState.readyToLogin) {
|
if (state == AuthenticationState.installed) {
|
||||||
return createLoginPage(
|
return getIt.get<AuthPage>();
|
||||||
sharedPreferences: sharedPreferences,
|
|
||||||
userService: userService,
|
|
||||||
walletService: walletService,
|
|
||||||
walletListService: walletListService,
|
|
||||||
authenticationStore: authenticationStore);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == AuthenticationState.authenticated ||
|
if (state == AuthenticationState.allowed) {
|
||||||
state == AuthenticationState.restored) {
|
return getIt.get<DashboardPage>();
|
||||||
return createDashboardPage(
|
|
||||||
walletService: walletService,
|
|
||||||
priceStore: priceStore,
|
|
||||||
trades: trades,
|
|
||||||
transactionDescriptions: transactionDescriptions,
|
|
||||||
walletStore: walletStore,
|
|
||||||
settingsStore: settingsStore);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (state == AuthenticationState.created) {
|
// if (state == AuthenticationState.denied) {
|
||||||
return createSeedPage(
|
// return createWelcomePage();
|
||||||
settingsStore: settingsStore,
|
// }
|
||||||
walletService: walletService,
|
|
||||||
callback: () =>
|
// if (state == AuthenticationState.readyToLogin) {
|
||||||
_authenticationStore.state = AuthenticationState.authenticated);
|
// 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);
|
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:provider/provider.dart';
|
||||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||||
import 'package:flutter/material.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';
|
import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart';
|
||||||
|
|
||||||
class SeedLanguage extends BasePage {
|
class SeedLanguage extends BasePage {
|
||||||
|
SeedLanguage({this.onConfirm});
|
||||||
|
|
||||||
|
final Function(BuildContext, String) onConfirm;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget body(BuildContext context) => SeedLanguageForm();
|
Widget body(BuildContext context) => SeedLanguageForm(onConfirm: onConfirm);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SeedLanguageForm extends StatefulWidget {
|
class SeedLanguageForm extends StatefulWidget {
|
||||||
|
SeedLanguageForm({this.onConfirm});
|
||||||
|
|
||||||
|
final Function(BuildContext, String) onConfirm;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SeedLanguageFormState createState() => SeedLanguageFormState();
|
SeedLanguageFormState createState() => SeedLanguageFormState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class SeedLanguageFormState extends State<SeedLanguageForm> {
|
class SeedLanguageFormState extends State<SeedLanguageForm> {
|
||||||
static const aspectRatioImage = 1.22;
|
static const aspectRatioImage = 1.22;
|
||||||
|
|
||||||
final walletNameImage = Image.asset('assets/images/wallet_name.png');
|
final walletNameImage = Image.asset('assets/images/wallet_name.png');
|
||||||
|
final _languageSelectorKey = GlobalKey<SeedLanguageSelectorState>();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
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(
|
return Container(
|
||||||
padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: ScrollableWithBottomSection(
|
child: ScrollableWithBottomSection(
|
||||||
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
content: Column(
|
content:
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
|
||||||
children: [
|
Padding(
|
||||||
Padding(
|
padding: EdgeInsets.only(left: 12, right: 12),
|
||||||
padding: EdgeInsets.only(left: 12, right: 12),
|
child: AspectRatio(
|
||||||
child: AspectRatio(
|
aspectRatio: aspectRatioImage,
|
||||||
aspectRatio: aspectRatioImage,
|
child: FittedBox(child: walletNameImage, fit: BoxFit.fill)),
|
||||||
child: FittedBox(child: walletNameImage, fit: BoxFit.fill)),
|
),
|
||||||
),
|
Padding(
|
||||||
Padding(padding: EdgeInsets.only(top: 40),
|
padding: EdgeInsets.only(top: 40),
|
||||||
child: Text(
|
child: Text(
|
||||||
S.of(context).seed_language_choose,
|
S.of(context).seed_language_choose,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0,
|
fontSize: 16.0,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
color: Theme.of(context).primaryTextTheme.title.color
|
color: Theme.of(context).primaryTextTheme.title.color),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Padding(
|
||||||
Padding(padding: EdgeInsets.only(top: 24),
|
padding: EdgeInsets.only(top: 24),
|
||||||
child: Observer(
|
child: SeedLanguageSelector(
|
||||||
builder: (_) => SelectButton(
|
key: _languageSelectorKey,
|
||||||
image: null,
|
initialSelected: defaultSeedLanguage),
|
||||||
text: seedLocales[seedLanguages.indexOf(seedLanguageStore.selectedSeedLanguage)],
|
)
|
||||||
color: Theme.of(context).accentTextTheme.title.backgroundColor,
|
]),
|
||||||
textColor: Theme.of(context).primaryTextTheme.title.color,
|
bottomSectionPadding:
|
||||||
onTap: () async => await showDialog(
|
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
context: context,
|
|
||||||
builder: (BuildContext context) => SeedLanguagePicker()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
)
|
|
||||||
]),
|
|
||||||
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
|
||||||
bottomSection: Observer(
|
bottomSection: Observer(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
return PrimaryButton(
|
return PrimaryButton(
|
||||||
onPressed: () =>
|
onPressed: () => widget
|
||||||
Navigator.of(context).popAndPushNamed(seedLanguageStore.currentRoute),
|
.onConfirm(context, _languageSelectorKey.currentState.selected),
|
||||||
text: S.of(context).seed_language_next,
|
text: S.of(context).seed_language_next,
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
textColor: Colors.white);
|
textColor: Colors.white);
|
||||||
|
|
|
@ -17,7 +17,7 @@ List<Image> flagImages = [
|
||||||
Image.asset('assets/images/spain.png'),
|
Image.asset('assets/images/spain.png'),
|
||||||
];
|
];
|
||||||
|
|
||||||
List<String> languageCodes = [
|
const List<String> languageCodes = [
|
||||||
'Eng',
|
'Eng',
|
||||||
'Chi',
|
'Chi',
|
||||||
'Ned',
|
'Ned',
|
||||||
|
@ -28,19 +28,39 @@ List<String> languageCodes = [
|
||||||
'Esp',
|
'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 {
|
class SeedLanguagePicker extends StatefulWidget {
|
||||||
|
SeedLanguagePicker({Key key, this.selected = defaultSeedLanguage})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final String selected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SeedLanguagePickerState createState() => SeedLanguagePickerState();
|
SeedLanguagePickerState createState() =>
|
||||||
|
SeedLanguagePickerState(selected: selected);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
||||||
|
SeedLanguagePickerState({this.selected});
|
||||||
|
|
||||||
|
String selected;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
|
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => Navigator.of(context).pop(),
|
onTap: () => Navigator.of(context).pop(),
|
||||||
child: Container(
|
child: Container(
|
||||||
|
@ -48,7 +68,8 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
||||||
child: BackdropFilter(
|
child: BackdropFilter(
|
||||||
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
|
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)),
|
decoration: BoxDecoration(
|
||||||
|
color: PaletteDark.darkNightBlue.withOpacity(0.75)),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
@ -62,8 +83,7 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
decoration: TextDecoration.none,
|
decoration: TextDecoration.none,
|
||||||
color: Colors.white
|
color: Colors.white),
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -74,9 +94,8 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
||||||
height: 300,
|
height: 300,
|
||||||
width: 300,
|
width: 300,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.all(Radius.circular(14)),
|
borderRadius: BorderRadius.all(Radius.circular(14)),
|
||||||
color: Theme.of(context).dividerColor
|
color: Theme.of(context).dividerColor),
|
||||||
),
|
|
||||||
child: GridView.count(
|
child: GridView.count(
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
crossAxisCount: 3,
|
crossAxisCount: 3,
|
||||||
|
@ -85,71 +104,64 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
||||||
crossAxisSpacing: 1,
|
crossAxisSpacing: 1,
|
||||||
mainAxisSpacing: 1,
|
mainAxisSpacing: 1,
|
||||||
children: List.generate(9, (index) {
|
children: List.generate(9, (index) {
|
||||||
|
|
||||||
if (index == 8) {
|
if (index == 8) {
|
||||||
|
|
||||||
return gridTile(
|
return gridTile(
|
||||||
isCurrent: false,
|
isCurrent: false,
|
||||||
place: Places.bottomRight,
|
place: Places.bottomRight,
|
||||||
image: null,
|
image: null,
|
||||||
text: '',
|
text: '',
|
||||||
onTap: null);
|
onTap: null);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
final code = languageCodes[index];
|
final code = languageCodes[index];
|
||||||
final flag = flagImages[index];
|
final flag = flagImages[index];
|
||||||
final isCurrent = index == seedLanguages.indexOf(seedLanguageStore.selectedSeedLanguage);
|
final isCurrent =
|
||||||
|
index == seedLanguages.indexOf(selected);
|
||||||
|
|
||||||
if (index == 0) {
|
if (index == 0) {
|
||||||
return gridTile(
|
return gridTile(
|
||||||
isCurrent: isCurrent,
|
isCurrent: isCurrent,
|
||||||
place: Places.topLeft,
|
place: Places.topLeft,
|
||||||
image: flag,
|
image: flag,
|
||||||
text: code,
|
text: code,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
|
selected = seedLanguages[index];
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop(selected);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index == 2) {
|
if (index == 2) {
|
||||||
return gridTile(
|
return gridTile(
|
||||||
isCurrent: isCurrent,
|
isCurrent: isCurrent,
|
||||||
place: Places.topRight,
|
place: Places.topRight,
|
||||||
image: flag,
|
image: flag,
|
||||||
text: code,
|
text: code,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
|
selected = seedLanguages[index];
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop(selected);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index == 6) {
|
if (index == 6) {
|
||||||
return gridTile(
|
return gridTile(
|
||||||
isCurrent: isCurrent,
|
isCurrent: isCurrent,
|
||||||
place: Places.bottomLeft,
|
place: Places.bottomLeft,
|
||||||
image: flag,
|
image: flag,
|
||||||
text: code,
|
text: code,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
|
selected = seedLanguages[index];
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop(selected);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return gridTile(
|
return gridTile(
|
||||||
isCurrent: isCurrent,
|
isCurrent: isCurrent,
|
||||||
place: Places.inside,
|
place: Places.inside,
|
||||||
image: flag,
|
image: flag,
|
||||||
text: code,
|
text: code,
|
||||||
onTap: () {
|
onTap: () {
|
||||||
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
|
selected = seedLanguages[index];
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop(selected);
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
|
@ -165,13 +177,12 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget gridTile({
|
Widget gridTile(
|
||||||
@required bool isCurrent,
|
{@required bool isCurrent,
|
||||||
@required Places place,
|
@required Places place,
|
||||||
@required Image image,
|
@required Image image,
|
||||||
@required String text,
|
@required String text,
|
||||||
@required VoidCallback onTap}) {
|
@required VoidCallback onTap}) {
|
||||||
|
|
||||||
BorderRadius borderRadius;
|
BorderRadius borderRadius;
|
||||||
final color = isCurrent
|
final color = isCurrent
|
||||||
? Theme.of(context).accentTextTheme.subtitle.decorationColor
|
? Theme.of(context).accentTextTheme.subtitle.decorationColor
|
||||||
|
@ -199,40 +210,33 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
|
||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: EdgeInsets.all(10),
|
padding: EdgeInsets.all(10),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(borderRadius: borderRadius, color: color),
|
||||||
borderRadius: borderRadius,
|
child: Center(
|
||||||
color: color
|
child: Row(
|
||||||
),
|
mainAxisSize: MainAxisSize.min,
|
||||||
child: Center(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
child: Row(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
children: <Widget>[
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
image != null ? image : Offstage(),
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
Padding(
|
||||||
children: <Widget>[
|
padding: image != null
|
||||||
image != null
|
? EdgeInsets.only(left: 10)
|
||||||
? image
|
: EdgeInsets.only(left: 0),
|
||||||
: Offstage(),
|
child: Text(
|
||||||
Padding(
|
text,
|
||||||
padding: image != null
|
style: TextStyle(
|
||||||
? EdgeInsets.only(left: 10)
|
fontSize: 18,
|
||||||
: EdgeInsets.only(left: 0),
|
fontWeight: FontWeight.bold,
|
||||||
child: Text(
|
decoration: TextDecoration.none,
|
||||||
text,
|
color: textColor),
|
||||||
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(
|
child: Observer(
|
||||||
builder: (_) => ListView.separated(
|
builder: (_) => ListView.separated(
|
||||||
separatorBuilder: (_, __) => Divider(
|
separatorBuilder: (_, __) => Divider(
|
||||||
color: Theme.of(context).dividerTheme.color,
|
color: Theme.of(context).dividerTheme.color, height: 1.0),
|
||||||
height: 1.0,
|
|
||||||
),
|
|
||||||
itemCount: subaddressListStore.subaddresses == null
|
itemCount: subaddressListStore.subaddresses == null
|
||||||
? 0
|
? 0
|
||||||
: subaddressListStore.subaddresses.length,
|
: subaddressListStore.subaddresses.length,
|
||||||
|
@ -42,9 +40,7 @@ class SubaddressListPage extends BasePage {
|
||||||
final subaddress = subaddressListStore.subaddresses[index];
|
final subaddress = subaddressListStore.subaddresses[index];
|
||||||
final isCurrent =
|
final isCurrent =
|
||||||
walletStore.subaddress.address == subaddress.address;
|
walletStore.subaddress.address == subaddress.address;
|
||||||
final label = subaddress.label != null
|
final label = subaddress.label ?? subaddress.address;
|
||||||
? subaddress.label
|
|
||||||
: subaddress.address;
|
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => Navigator.of(context).pop(subaddress),
|
onTap: () => Navigator.of(context).pop(subaddress),
|
||||||
|
|
|
@ -1,99 +1,99 @@
|
||||||
import 'dart:async';
|
//import 'dart:async';
|
||||||
import 'package:flutter/foundation.dart';
|
//import 'package:flutter/foundation.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
//import 'package:shared_preferences/shared_preferences.dart';
|
||||||
import 'package:mobx/mobx.dart';
|
//import 'package:mobx/mobx.dart';
|
||||||
import 'package:cake_wallet/src/domain/services/user_service.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/domain/services/wallet_service.dart';
|
||||||
import 'package:cake_wallet/src/stores/auth/auth_state.dart';
|
//import 'package:cake_wallet/view_model/auth_state.dart';
|
||||||
import 'package:cake_wallet/generated/i18n.dart';
|
//import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
//
|
||||||
part 'auth_store.g.dart';
|
//part 'auth_store.g.dart';
|
||||||
|
//
|
||||||
class AuthStore = AuthStoreBase with _$AuthStore;
|
//class AuthStore = AuthStoreBase with _$AuthStore;
|
||||||
|
//
|
||||||
abstract class AuthStoreBase with Store {
|
//abstract class AuthStoreBase with Store {
|
||||||
AuthStoreBase(
|
// AuthStoreBase(
|
||||||
{@required this.userService,
|
// {@required this.userService,
|
||||||
@required this.walletService,
|
// @required this.walletService,
|
||||||
@required this.sharedPreferences}) {
|
// @required this.sharedPreferences}) {
|
||||||
state = AuthenticationStateInitial();
|
// state = AuthenticationStateInitial();
|
||||||
_failureCounter = 0;
|
// _failureCounter = 0;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
static const maxFailedLogins = 3;
|
// static const maxFailedLogins = 3;
|
||||||
static const banTimeout = 180; // 3 mins
|
// static const banTimeout = 180; // 3 mins
|
||||||
final banTimeoutKey = S.current.auth_store_ban_timeout;
|
// final banTimeoutKey = S.current.auth_store_ban_timeout;
|
||||||
|
//
|
||||||
final UserService userService;
|
// final UserService userService;
|
||||||
final WalletService walletService;
|
// final WalletService walletService;
|
||||||
|
//
|
||||||
final SharedPreferences sharedPreferences;
|
// final SharedPreferences sharedPreferences;
|
||||||
|
//
|
||||||
@observable
|
// @observable
|
||||||
AuthState state;
|
// AuthState state;
|
||||||
|
//
|
||||||
@observable
|
// @observable
|
||||||
int _failureCounter;
|
// int _failureCounter;
|
||||||
|
//
|
||||||
@action
|
// @action
|
||||||
Future auth({String password}) async {
|
// Future auth({String password}) async {
|
||||||
state = AuthenticationStateInitial();
|
// state = AuthenticationStateInitial();
|
||||||
final _banDuration = banDuration();
|
// final _banDuration = banDuration();
|
||||||
|
//
|
||||||
if (_banDuration != null) {
|
// if (_banDuration != null) {
|
||||||
state = AuthenticationBanned(
|
// state = AuthenticationBanned(
|
||||||
error: S.current.auth_store_banned_for + '${_banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
// error: S.current.auth_store_banned_for + '${_banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
state = AuthenticationInProgress();
|
// state = AuthenticationInProgress();
|
||||||
final isAuth = await userService.authenticate(password);
|
// final isAuth = await userService.authenticate(password);
|
||||||
|
//
|
||||||
if (isAuth) {
|
// if (isAuth) {
|
||||||
state = AuthenticatedSuccessfully();
|
// state = AuthenticatedSuccessfully();
|
||||||
_failureCounter = 0;
|
// _failureCounter = 0;
|
||||||
} else {
|
// } else {
|
||||||
_failureCounter += 1;
|
// _failureCounter += 1;
|
||||||
|
//
|
||||||
if (_failureCounter >= maxFailedLogins) {
|
// if (_failureCounter >= maxFailedLogins) {
|
||||||
final banDuration = await ban();
|
// final banDuration = await ban();
|
||||||
state = AuthenticationBanned(
|
// state = AuthenticationBanned(
|
||||||
error: S.current.auth_store_banned_for + '${banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
// error: S.current.auth_store_banned_for + '${banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
state = AuthenticationFailure(error: S.current.auth_store_incorrect_password);
|
// state = AuthenticationFailure(error: S.current.auth_store_incorrect_password);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Duration banDuration() {
|
// Duration banDuration() {
|
||||||
final unbanTimestamp = sharedPreferences.getInt(banTimeoutKey);
|
// final unbanTimestamp = sharedPreferences.getInt(banTimeoutKey);
|
||||||
|
//
|
||||||
if (unbanTimestamp == null) {
|
// if (unbanTimestamp == null) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
|
// final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
|
||||||
final now = DateTime.now();
|
// final now = DateTime.now();
|
||||||
|
//
|
||||||
if (now.isAfter(unbanTime)) {
|
// if (now.isAfter(unbanTime)) {
|
||||||
return null;
|
// return null;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
|
// return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
Future<Duration> ban() async {
|
// Future<Duration> ban() async {
|
||||||
final multiplier = _failureCounter - maxFailedLogins + 1;
|
// final multiplier = _failureCounter - maxFailedLogins + 1;
|
||||||
final timeout = (multiplier * banTimeout) * 1000;
|
// final timeout = (multiplier * banTimeout) * 1000;
|
||||||
final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
|
// final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
|
||||||
await sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
|
// await sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
|
||||||
|
//
|
||||||
return Duration(milliseconds: timeout);
|
// return Duration(milliseconds: timeout);
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
@action
|
// @action
|
||||||
void biometricAuth() {
|
// void biometricAuth() {
|
||||||
state = AuthenticatedSuccessfully();
|
// state = AuthenticatedSuccessfully();
|
||||||
}
|
// }
|
||||||
}
|
//}
|
||||||
|
|
|
@ -30,8 +30,8 @@ abstract class AuthenticationStoreBase with Store {
|
||||||
@observable
|
@observable
|
||||||
AuthenticationState state;
|
AuthenticationState state;
|
||||||
|
|
||||||
@observable
|
// @observable
|
||||||
String errorMessage;
|
// String errorMessage;
|
||||||
|
|
||||||
Future started() async {
|
Future started() async {
|
||||||
final canAuth = await userService.canAuthenticate();
|
final canAuth = await userService.canAuthenticate();
|
||||||
|
|
|
@ -2,7 +2,7 @@ import 'package:mobx/mobx.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:shared_preferences/shared_preferences.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/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/wallet_restoration/wallet_restoration_state.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/domain/common/crypto_currency.dart';
|
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
|
||||||
|
@ -37,7 +37,7 @@ abstract class WalleRestorationStoreBase with Store {
|
||||||
bool isValid;
|
bool isValid;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
List<MnemoticItem> seed;
|
List<MnemonicItem> seed;
|
||||||
|
|
||||||
@observable
|
@observable
|
||||||
bool disabledState;
|
bool disabledState;
|
||||||
|
@ -79,35 +79,35 @@ abstract class WalleRestorationStoreBase with Store {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
void setSeed(List<MnemoticItem> seed) {
|
void setSeed(List<MnemonicItem> seed) {
|
||||||
this.seed = seed;
|
this.seed = seed;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
// @action
|
||||||
void validateSeed(List<MnemoticItem> seed) {
|
// void validateSeed(List<MnemonicItem> seed) {
|
||||||
final _seed = seed != null ? seed : this.seed;
|
// final _seed = seed != null ? seed : this.seed;
|
||||||
bool isValid = _seed != null ? _seed.length == 25 : false;
|
// bool isValid = _seed != null ? _seed.length == 25 : false;
|
||||||
|
//
|
||||||
if (!isValid) {
|
// if (!isValid) {
|
||||||
errorMessage = S.current.wallet_restoration_store_incorrect_seed_length;
|
// errorMessage = S.current.wallet_restoration_store_incorrect_seed_length;
|
||||||
this.isValid = isValid;
|
// this.isValid = isValid;
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
for (final item in _seed) {
|
// for (final item in _seed) {
|
||||||
if (!item.isCorrect()) {
|
// if (!item.isCorrect()) {
|
||||||
isValid = false;
|
// isValid = false;
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
if (isValid) {
|
// if (isValid) {
|
||||||
errorMessage = null;
|
// errorMessage = null;
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
this.isValid = isValid;
|
// this.isValid = isValid;
|
||||||
return;
|
// return;
|
||||||
}
|
// }
|
||||||
|
|
||||||
String _seedText() {
|
String _seedText() {
|
||||||
return seed.fold('', (acc, item) => acc + ' ' + item.toString());
|
return seed.fold('', (acc, item) => acc + ' ' + item.toString());
|
||||||
|
|
|
@ -2,24 +2,24 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class BaseTextFormField extends StatelessWidget {
|
class BaseTextFormField extends StatelessWidget {
|
||||||
BaseTextFormField({
|
BaseTextFormField(
|
||||||
this.controller,
|
{this.controller,
|
||||||
this.keyboardType = TextInputType.text,
|
this.keyboardType = TextInputType.text,
|
||||||
this.textInputAction = TextInputAction.done,
|
this.textInputAction = TextInputAction.done,
|
||||||
this.textAlign = TextAlign.start,
|
this.textAlign = TextAlign.start,
|
||||||
this.autovalidate = false,
|
this.autovalidate = false,
|
||||||
this.hintText = '',
|
this.hintText = '',
|
||||||
this.maxLines = 1,
|
this.maxLines = 1,
|
||||||
this.inputFormatters,
|
this.inputFormatters,
|
||||||
this.textColor,
|
this.textColor,
|
||||||
this.hintColor,
|
this.hintColor,
|
||||||
this.borderColor,
|
this.borderColor,
|
||||||
this.prefix,
|
this.prefix,
|
||||||
this.suffix,
|
this.suffix,
|
||||||
this.suffixIcon,
|
this.suffixIcon,
|
||||||
this.enabled = true,
|
this.enabled = true,
|
||||||
this.validator
|
this.validator,
|
||||||
});
|
this.placeholderTextStyle});
|
||||||
|
|
||||||
final TextEditingController controller;
|
final TextEditingController controller;
|
||||||
final TextInputType keyboardType;
|
final TextInputType keyboardType;
|
||||||
|
@ -37,6 +37,7 @@ class BaseTextFormField extends StatelessWidget {
|
||||||
final Widget suffixIcon;
|
final Widget suffixIcon;
|
||||||
final bool enabled;
|
final bool enabled;
|
||||||
final FormFieldValidator<String> validator;
|
final FormFieldValidator<String> validator;
|
||||||
|
final TextStyle placeholderTextStyle;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -50,31 +51,26 @@ class BaseTextFormField extends StatelessWidget {
|
||||||
inputFormatters: inputFormatters,
|
inputFormatters: inputFormatters,
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0,
|
fontSize: 16.0,
|
||||||
color: textColor ?? Theme.of(context).primaryTextTheme.title.color
|
color: textColor ?? Theme.of(context).primaryTextTheme.title.color),
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
prefix: prefix,
|
prefix: prefix,
|
||||||
suffix: suffix,
|
suffix: suffix,
|
||||||
suffixIcon: suffixIcon,
|
suffixIcon: suffixIcon,
|
||||||
hintStyle: TextStyle(
|
hintStyle: placeholderTextStyle ??
|
||||||
color: hintColor ?? Theme.of(context).primaryTextTheme.caption.color,
|
TextStyle(
|
||||||
fontSize: 16
|
color: hintColor ??
|
||||||
),
|
Theme.of(context).primaryTextTheme.caption.color,
|
||||||
hintText: hintText,
|
fontSize: 16),
|
||||||
focusedBorder: UnderlineInputBorder(
|
hintText: hintText,
|
||||||
borderSide: BorderSide(
|
focusedBorder: UnderlineInputBorder(
|
||||||
color: borderColor ?? Theme.of(context).dividerColor,
|
borderSide: BorderSide(
|
||||||
width: 1.0
|
color: borderColor ?? Theme.of(context).dividerColor,
|
||||||
)
|
width: 1.0)),
|
||||||
),
|
enabledBorder: UnderlineInputBorder(
|
||||||
enabledBorder: UnderlineInputBorder(
|
borderSide: BorderSide(
|
||||||
borderSide: BorderSide(
|
color: borderColor ?? Theme.of(context).dividerColor,
|
||||||
color: borderColor ?? Theme.of(context).dividerColor,
|
width: 1.0))),
|
||||||
width: 1.0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
),
|
|
||||||
validator: validator,
|
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';
|
import 'package:cake_wallet/src/domain/monero/get_height_by_date.dart';
|
||||||
|
|
||||||
class BlockchainHeightWidget extends StatefulWidget {
|
class BlockchainHeightWidget extends StatefulWidget {
|
||||||
BlockchainHeightWidget({GlobalKey key}) : super(key: key);
|
BlockchainHeightWidget({GlobalKey key, this.onHeightChange})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
final Function(int) onHeightChange;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() => BlockchainHeightState();
|
State<StatefulWidget> createState() => BlockchainHeightState();
|
||||||
|
@ -13,15 +16,23 @@ class BlockchainHeightWidget extends StatefulWidget {
|
||||||
class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||||
final dateController = TextEditingController();
|
final dateController = TextEditingController();
|
||||||
final restoreHeightController = TextEditingController();
|
final restoreHeightController = TextEditingController();
|
||||||
|
|
||||||
int get height => _height;
|
int get height => _height;
|
||||||
int _height = 0;
|
int _height = 0;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
restoreHeightController.addListener(() => _height =
|
restoreHeightController.addListener(() {
|
||||||
restoreHeightController.text != null
|
try {
|
||||||
|
_changeHeight(restoreHeightController.text != null &&
|
||||||
|
restoreHeightController.text.isNotEmpty
|
||||||
? int.parse(restoreHeightController.text)
|
? int.parse(restoreHeightController.text)
|
||||||
: 0);
|
: 0);
|
||||||
|
} catch (_) {
|
||||||
|
_changeHeight(0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,21 +49,18 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0,
|
fontSize: 16.0,
|
||||||
color: Theme.of(context).primaryTextTheme.title.color
|
color: Theme.of(context).primaryTextTheme.title.color),
|
||||||
),
|
|
||||||
controller: restoreHeightController,
|
controller: restoreHeightController,
|
||||||
keyboardType: TextInputType.numberWithOptions(
|
keyboardType: TextInputType.numberWithOptions(
|
||||||
signed: false, decimal: false),
|
signed: false, decimal: false),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
color: Theme.of(context).primaryTextTheme.caption.color,
|
color: Theme.of(context).primaryTextTheme.caption.color,
|
||||||
fontSize: 16
|
fontSize: 16),
|
||||||
),
|
|
||||||
hintText: S.of(context).widgets_restore_from_blockheight,
|
hintText: S.of(context).widgets_restore_from_blockheight,
|
||||||
focusedBorder: UnderlineInputBorder(
|
focusedBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor, width: 1.0)),
|
||||||
width: 1.0)),
|
|
||||||
enabledBorder: UnderlineInputBorder(
|
enabledBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).dividerColor,
|
color: Theme.of(context).dividerColor,
|
||||||
|
@ -81,13 +89,14 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||||
child: TextFormField(
|
child: TextFormField(
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 16.0,
|
fontSize: 16.0,
|
||||||
color: Theme.of(context).primaryTextTheme.title.color
|
color: Theme.of(context).primaryTextTheme.title.color),
|
||||||
),
|
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintStyle: TextStyle(
|
hintStyle: TextStyle(
|
||||||
color: Theme.of(context).primaryTextTheme.caption.color,
|
color: Theme.of(context)
|
||||||
fontSize: 16
|
.primaryTextTheme
|
||||||
),
|
.caption
|
||||||
|
.color,
|
||||||
|
fontSize: 16),
|
||||||
hintText: S.of(context).widgets_restore_from_date,
|
hintText: S.of(context).widgets_restore_from_date,
|
||||||
focusedBorder: UnderlineInputBorder(
|
focusedBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
|
@ -113,7 +122,7 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||||
|
|
||||||
Future _selectDate(BuildContext context) async {
|
Future _selectDate(BuildContext context) async {
|
||||||
final now = DateTime.now();
|
final now = DateTime.now();
|
||||||
final DateTime date = await showDatePicker(
|
final date = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialDate: now.subtract(Duration(days: 1)),
|
initialDate: now.subtract(Duration(days: 1)),
|
||||||
firstDate: DateTime(2014, DateTime.april),
|
firstDate: DateTime(2014, DateTime.april),
|
||||||
|
@ -125,8 +134,13 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
|
||||||
setState(() {
|
setState(() {
|
||||||
dateController.text = DateFormat('yyyy-MM-dd').format(date);
|
dateController.text = DateFormat('yyyy-MM-dd').format(date);
|
||||||
restoreHeightController.text = '$height';
|
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/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:cake_wallet/palette.dart';
|
import 'package:cake_wallet/palette.dart';
|
||||||
import 'package:cake_wallet/src/domain/monero/mnemonics/english.dart';
|
import 'package:cake_wallet/core/seed_validator.dart';
|
||||||
import 'package:cake_wallet/src/domain/monero/mnemonics/chinese_simplified.dart';
|
import 'package:cake_wallet/src/widgets/primary_button.dart';
|
||||||
import 'package:cake_wallet/src/domain/monero/mnemonics/dutch.dart';
|
import 'package:cake_wallet/src/domain/common/mnemonic_item.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/generated/i18n.dart';
|
import 'package:cake_wallet/generated/i18n.dart';
|
||||||
|
|
||||||
class SeedWidget extends StatefulWidget {
|
class SeedWidget extends StatefulWidget {
|
||||||
SeedWidget({Key key, this.onMnemoticChange, this.onFinish, this.seedLanguage}) : super(key: key) {
|
SeedWidget(
|
||||||
switch (seedLanguage) {
|
{Key key,
|
||||||
case 'English':
|
this.maxLength,
|
||||||
words = EnglishMnemonics.words;
|
this.onMnemonicChange,
|
||||||
break;
|
this.onFinish,
|
||||||
case 'Chinese (simplified)':
|
this.validator})
|
||||||
words = ChineseSimplifiedMnemonics.words;
|
: super(key: key);
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final Function(List<MnemoticItem>) onMnemoticChange;
|
final int maxLength;
|
||||||
|
final Function(List<MnemonicItem>) onMnemonicChange;
|
||||||
final Function() onFinish;
|
final Function() onFinish;
|
||||||
final String seedLanguage;
|
final SeedValidator validator;
|
||||||
List<String> words;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SeedWidgetState createState() => SeedWidgetState();
|
SeedWidgetState createState() => SeedWidgetState(maxLength: maxLength);
|
||||||
}
|
}
|
||||||
|
|
||||||
class SeedWidgetState extends State<SeedWidget> {
|
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 _seedController = TextEditingController();
|
||||||
final _seedTextFieldKey = GlobalKey();
|
final _seedTextFieldKey = GlobalKey();
|
||||||
MnemoticItem selectedItem;
|
MnemonicItem selectedItem;
|
||||||
bool isValid;
|
bool isValid;
|
||||||
String errorMessage;
|
String errorMessage;
|
||||||
|
|
||||||
List<MnemoticItem> currentMnemotics;
|
List<MnemonicItem> currentMnemonics;
|
||||||
bool isCurrentMnemoticValid;
|
bool isCurrentMnemonicValid;
|
||||||
String _errorMessage;
|
String _errorMessage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
isValid = false;
|
isValid = false;
|
||||||
isCurrentMnemoticValid = false;
|
isCurrentMnemonicValid = false;
|
||||||
_seedController
|
_seedController
|
||||||
.addListener(() => changeCurrentMnemotic(_seedController.text));
|
.addListener(() => changeCurrentMnemonic(_seedController.text));
|
||||||
}
|
}
|
||||||
|
|
||||||
void addMnemotic(String text) {
|
void addMnemonic(String text) {
|
||||||
setState(() => items.add(MnemoticItem(
|
setState(() => items.add(MnemonicItem(text: text.trim().toLowerCase())));
|
||||||
text: text.trim().toLowerCase(), dic: widget.words)));
|
|
||||||
_seedController.text = '';
|
_seedController.text = '';
|
||||||
|
|
||||||
if (widget.onMnemoticChange != null) {
|
if (widget.onMnemonicChange != null) {
|
||||||
widget.onMnemoticChange(items);
|
widget.onMnemonicChange(items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void mnemoticFromText(String text) {
|
void mnemonicFromText(String text) {
|
||||||
final splitted = text.split(' ');
|
final splitted = text.split(' ');
|
||||||
|
|
||||||
if (splitted.length >= 2) {
|
if (splitted.length >= 2) {
|
||||||
|
@ -98,18 +68,18 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectedItem != null) {
|
if (selectedItem != null) {
|
||||||
editTextOfSelectedMnemotic(text);
|
editTextOfSelectedMnemonic(text);
|
||||||
} else {
|
} else {
|
||||||
addMnemotic(text);
|
addMnemonic(text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void selectMnemotic(MnemoticItem item) {
|
void selectMnemonic(MnemonicItem item) {
|
||||||
setState(() {
|
setState(() {
|
||||||
selectedItem = item;
|
selectedItem = item;
|
||||||
currentMnemotics = [item];
|
currentMnemonics = [item];
|
||||||
|
|
||||||
_seedController
|
_seedController
|
||||||
..text = item.text
|
..text = item.text
|
||||||
|
@ -117,23 +87,23 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void onMnemoticTap(MnemoticItem item) {
|
void onMnemonicTap(MnemonicItem item) {
|
||||||
if (selectedItem == item) {
|
if (selectedItem == item) {
|
||||||
setState(() => selectedItem = null);
|
setState(() => selectedItem = null);
|
||||||
_seedController.text = '';
|
_seedController.text = '';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
selectMnemotic(item);
|
selectMnemonic(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void editTextOfSelectedMnemotic(String text) {
|
void editTextOfSelectedMnemonic(String text) {
|
||||||
setState(() => selectedItem.changeText(text));
|
setState(() => selectedItem.changeText(text));
|
||||||
selectedItem = null;
|
selectedItem = null;
|
||||||
_seedController.text = '';
|
_seedController.text = '';
|
||||||
|
|
||||||
if (widget.onMnemoticChange != null) {
|
if (widget.onMnemonicChange != null) {
|
||||||
widget.onMnemoticChange(items);
|
widget.onMnemonicChange(items);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,83 +113,77 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||||
selectedItem = null;
|
selectedItem = null;
|
||||||
_seedController.text = '';
|
_seedController.text = '';
|
||||||
|
|
||||||
if (widget.onMnemoticChange != null) {
|
if (widget.onMnemonicChange != null) {
|
||||||
widget.onMnemoticChange(items);
|
widget.onMnemonicChange(items);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void invalidate() {
|
void invalidate() => setState(() => isValid = false);
|
||||||
setState(() => isValid = false);
|
|
||||||
}
|
|
||||||
|
|
||||||
void validated() {
|
void validated() => setState(() => isValid = true);
|
||||||
setState(() => isValid = true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void setErrorMessage(String errorMessage) {
|
void setErrorMessage(String errorMessage) =>
|
||||||
setState(() => this.errorMessage = errorMessage);
|
setState(() => this.errorMessage = errorMessage);
|
||||||
}
|
|
||||||
|
|
||||||
void replaceText(String text) {
|
void replaceText(String text) {
|
||||||
setState(() => items = []);
|
setState(() => items = []);
|
||||||
mnemoticFromText(text);
|
mnemonicFromText(text);
|
||||||
}
|
}
|
||||||
|
|
||||||
void changeCurrentMnemotic(String text) {
|
void changeCurrentMnemonic(String text) {
|
||||||
setState(() {
|
setState(() {
|
||||||
final trimmedText = text.trim();
|
final trimmedText = text.trim();
|
||||||
final splitted = trimmedText.split(' ');
|
final splitted = trimmedText.split(' ');
|
||||||
_errorMessage = null;
|
_errorMessage = null;
|
||||||
|
|
||||||
if (text == null) {
|
if (text == null) {
|
||||||
currentMnemotics = [];
|
currentMnemonics = [];
|
||||||
isCurrentMnemoticValid = false;
|
isCurrentMnemonicValid = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentMnemotics = splitted
|
currentMnemonics =
|
||||||
.map((text) => MnemoticItem(text: text, dic: widget.words))
|
splitted.map((text) => MnemonicItem(text: text)).toList();
|
||||||
.toList();
|
|
||||||
|
|
||||||
bool isValid = true;
|
var isValid = true;
|
||||||
|
|
||||||
for (final word in currentMnemotics) {
|
for (final word in currentMnemonics) {
|
||||||
isValid = word.isCorrect();
|
isValid = widget.validator.isValid(word);
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isCurrentMnemoticValid = isValid;
|
isCurrentMnemonicValid = isValid;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void saveCurrentMnemoticToItems() {
|
void saveCurrentMnemonicToItems() {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (selectedItem != null) {
|
if (selectedItem != null) {
|
||||||
selectedItem.changeText(currentMnemotics.first.text.trim());
|
selectedItem.changeText(currentMnemonics.first.text.trim());
|
||||||
selectedItem = null;
|
selectedItem = null;
|
||||||
} else {
|
} else {
|
||||||
items.addAll(currentMnemotics);
|
items.addAll(currentMnemonics);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentMnemotics = [];
|
currentMnemonics = [];
|
||||||
_seedController.text = '';
|
_seedController.text = '';
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void showErrorIfExist() {
|
void showErrorIfExist() {
|
||||||
setState(() => _errorMessage =
|
setState(() => _errorMessage =
|
||||||
!isCurrentMnemoticValid ? S.current.incorrect_seed : null);
|
!isCurrentMnemonicValid ? S.current.incorrect_seed : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isSeedValid() {
|
bool isSeedValid() {
|
||||||
bool isValid;
|
bool isValid;
|
||||||
|
|
||||||
for (final item in items) {
|
for (final item in items) {
|
||||||
isValid = item.isCorrect();
|
isValid = widget.validator.isValid(item);
|
||||||
|
|
||||||
if (!isValid) {
|
if (!isValid) {
|
||||||
break;
|
break;
|
||||||
|
@ -234,192 +198,207 @@ class SeedWidgetState extends State<SeedWidget> {
|
||||||
return Container(
|
return Container(
|
||||||
child: Column(children: [
|
child: Column(children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
fit: FlexFit.tight,
|
fit: FlexFit.tight,
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: Container(
|
child: Container(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
padding: EdgeInsets.all(24),
|
padding: EdgeInsets.all(24),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.only(
|
borderRadius: BorderRadius.only(
|
||||||
bottomLeft: Radius.circular(24),
|
bottomLeft: Radius.circular(24),
|
||||||
bottomRight: 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
|
Padding(
|
||||||
),
|
padding: EdgeInsets.only(top: 5),
|
||||||
child: SingleChildScrollView(
|
child: Wrap(
|
||||||
child: Column(
|
children: items.map((item) {
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
final isValid = widget.validator.isValid(item);
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
final isSelected = selectedItem == item;
|
||||||
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;
|
|
||||||
|
|
||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: () => onMnemoticTap(item),
|
onTap: () => onMnemonicTap(item),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: isValid ? Colors.transparent : Palette.red),
|
color: isValid
|
||||||
margin: EdgeInsets.only(right: 7, bottom: 8),
|
? Colors.transparent
|
||||||
child: Text(
|
: Palette.red),
|
||||||
item.toString(),
|
margin: EdgeInsets.only(right: 7, bottom: 8),
|
||||||
style: TextStyle(
|
child: Text(
|
||||||
color: isValid
|
item.toString(),
|
||||||
? Theme.of(context).primaryTextTheme.title.color
|
style: TextStyle(
|
||||||
: Theme.of(context).primaryTextTheme.caption.color,
|
color: isValid
|
||||||
fontSize: 16,
|
? Theme.of(context)
|
||||||
fontWeight:
|
.primaryTextTheme
|
||||||
isSelected ? FontWeight.w900 : FontWeight.w400,
|
.title
|
||||||
decoration: isSelected
|
.color
|
||||||
? TextDecoration.underline
|
: Theme.of(context)
|
||||||
: TextDecoration.none),
|
.primaryTextTheme
|
||||||
)),
|
.caption
|
||||||
);
|
.color,
|
||||||
}).toList(),)
|
fontSize: 16,
|
||||||
)
|
fontWeight: isSelected
|
||||||
],
|
? FontWeight.w900
|
||||||
),
|
: FontWeight.w400,
|
||||||
|
decoration: isSelected
|
||||||
|
? TextDecoration.underline
|
||||||
|
: TextDecoration.none),
|
||||||
|
)),
|
||||||
|
);
|
||||||
|
}).toList(),
|
||||||
|
))
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
fit: FlexFit.tight,
|
fit: FlexFit.tight,
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: Padding(
|
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(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Text(
|
Text(
|
||||||
S.of(context).restore_new_seed,
|
S.of(context).restore_new_seed,
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
fontSize: 18,
|
fontSize: 18,
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: Theme.of(context).primaryTextTheme.title.color
|
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
|
|
||||||
),
|
),
|
||||||
controller: _seedController,
|
Padding(
|
||||||
textInputAction: TextInputAction.done,
|
padding: EdgeInsets.only(top: 24),
|
||||||
decoration: InputDecoration(
|
child: TextFormField(
|
||||||
suffixIcon: GestureDetector(
|
key: _seedTextFieldKey,
|
||||||
behavior: HitTestBehavior.opaque,
|
onFieldSubmitted: (text) => isCurrentMnemonicValid
|
||||||
child: ConstrainedBox(
|
? saveCurrentMnemonicToItems()
|
||||||
constraints: BoxConstraints(maxWidth: 145),
|
: null,
|
||||||
child: Row(
|
style: TextStyle(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
fontSize: 16.0,
|
||||||
children: <Widget>[
|
color:
|
||||||
Text(
|
Theme.of(context).primaryTextTheme.title.color),
|
||||||
'${items.length}/${SeedWidgetState.maxLength}',
|
controller: _seedController,
|
||||||
style: TextStyle(
|
textInputAction: TextInputAction.done,
|
||||||
color: Theme.of(context).primaryTextTheme.caption.color,
|
decoration: InputDecoration(
|
||||||
fontSize: 14)),
|
suffixIcon: GestureDetector(
|
||||||
SizedBox(width: 10),
|
behavior: HitTestBehavior.opaque,
|
||||||
InkWell(
|
child: ConstrainedBox(
|
||||||
onTap: () async =>
|
constraints: BoxConstraints(maxWidth: 145),
|
||||||
Clipboard.getData('text/plain').then(
|
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) =>
|
(clipboard) =>
|
||||||
replaceText(clipboard.text)),
|
replaceText(clipboard.text)),
|
||||||
child: Container(
|
child: Container(
|
||||||
height: 35,
|
height: 35,
|
||||||
padding: EdgeInsets.all(7),
|
padding: EdgeInsets.all(7),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color:
|
color: Theme.of(context)
|
||||||
Theme.of(context).accentTextTheme.title.backgroundColor,
|
.accentTextTheme
|
||||||
borderRadius:
|
.title
|
||||||
BorderRadius.circular(10.0)),
|
.backgroundColor,
|
||||||
child: Text(
|
borderRadius:
|
||||||
S.of(context).paste,
|
BorderRadius.circular(10.0)),
|
||||||
style: TextStyle(
|
child: Text(
|
||||||
color: Theme.of(context).primaryTextTheme.title.color
|
S.of(context).paste,
|
||||||
),
|
style: TextStyle(
|
||||||
)),
|
color: Theme.of(context)
|
||||||
)
|
.primaryTextTheme
|
||||||
],
|
.title
|
||||||
|
.color),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
hintStyle: TextStyle(
|
||||||
),
|
color: Theme.of(context)
|
||||||
hintStyle:
|
.primaryTextTheme
|
||||||
TextStyle(
|
.caption
|
||||||
color: Theme.of(context).primaryTextTheme.caption.color,
|
.color,
|
||||||
fontSize: 16
|
fontSize: 16),
|
||||||
),
|
hintText:
|
||||||
hintText: S.of(context).restore_from_seed_placeholder,
|
S.of(context).restore_from_seed_placeholder,
|
||||||
errorText: _errorMessage,
|
errorText: _errorMessage,
|
||||||
focusedBorder: UnderlineInputBorder(
|
focusedBorder: UnderlineInputBorder(
|
||||||
borderSide: BorderSide(
|
borderSide: BorderSide(
|
||||||
color: Theme.of(context).dividerColor, width: 1.0)),
|
color: Theme.of(context).dividerColor,
|
||||||
enabledBorder: UnderlineInputBorder(
|
width: 1.0)),
|
||||||
borderSide: BorderSide(
|
enabledBorder: UnderlineInputBorder(
|
||||||
color: Theme.of(context).dividerColor,
|
borderSide: BorderSide(
|
||||||
width: 1.0))),
|
color: Theme.of(context).dividerColor,
|
||||||
enableInteractiveSelection: false,
|
width: 1.0))),
|
||||||
),
|
enableInteractiveSelection: false,
|
||||||
)
|
),
|
||||||
]),
|
)
|
||||||
)
|
]),
|
||||||
),
|
)),
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(right: 8),
|
padding: EdgeInsets.only(right: 8),
|
||||||
child: PrimaryButton(
|
child: PrimaryButton(
|
||||||
onPressed: clear,
|
onPressed: clear,
|
||||||
text: S.of(context).clear,
|
text: S.of(context).clear,
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
isDisabled: items.isEmpty,
|
isDisabled: items.isEmpty,
|
||||||
),
|
),
|
||||||
)
|
)),
|
||||||
),
|
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: EdgeInsets.only(left: 8),
|
padding: EdgeInsets.only(left: 8),
|
||||||
child: (selectedItem == null && items.length == maxLength)
|
child: (selectedItem == null && items.length == maxLength)
|
||||||
? PrimaryButton(
|
? PrimaryButton(
|
||||||
text: S.of(context).restore_next,
|
text: S.of(context).restore_next,
|
||||||
isDisabled: !isSeedValid(),
|
isDisabled: !isSeedValid(),
|
||||||
onPressed: () => widget.onFinish != null
|
onPressed: () => widget.onFinish != null
|
||||||
? widget.onFinish()
|
? widget.onFinish()
|
||||||
: null,
|
: null,
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
textColor: Colors.white)
|
textColor: Colors.white)
|
||||||
: PrimaryButton(
|
: PrimaryButton(
|
||||||
text: selectedItem != null
|
text: selectedItem != null
|
||||||
? S.of(context).save
|
? S.of(context).save
|
||||||
: S.of(context).add_new_word,
|
: S.of(context).add_new_word,
|
||||||
onPressed: () => isCurrentMnemoticValid
|
onPressed: () => isCurrentMnemonicValid
|
||||||
? saveCurrentMnemoticToItems()
|
? saveCurrentMnemonicToItems()
|
||||||
: null,
|
: null,
|
||||||
onDisabledPressed: () => showErrorIfExist(),
|
onDisabledPressed: () => showErrorIfExist(),
|
||||||
isDisabled: !isCurrentMnemoticValid,
|
isDisabled: !isCurrentMnemonicValid,
|
||||||
color: Colors.green,
|
color: Colors.green,
|
||||||
textColor: Colors.white),
|
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(
|
display4: TextStyle(
|
||||||
color: Palette.oceanBlue // QR code
|
color: Palette.oceanBlue // QR code
|
||||||
)
|
),
|
||||||
|
// headline1: TextStyle(color: Palette.nightBlue)
|
||||||
),
|
),
|
||||||
dividerColor: Palette.periwinkle,
|
dividerColor: Palette.eee,
|
||||||
accentTextTheme: TextTheme(
|
accentTextTheme: TextTheme(
|
||||||
title: TextStyle(
|
title: TextStyle(
|
||||||
color: Palette.darkLavender, // top panel
|
color: Palette.darkLavender, // top panel
|
||||||
|
@ -112,7 +113,8 @@ class Themes {
|
||||||
),
|
),
|
||||||
display4: TextStyle(
|
display4: TextStyle(
|
||||||
color: PaletteDark.gray // QR code
|
color: PaletteDark.gray // QR code
|
||||||
)
|
),
|
||||||
|
// headline5: TextStyle(color: PaletteDark.gray)
|
||||||
),
|
),
|
||||||
dividerColor: PaletteDark.distantBlue,
|
dividerColor: PaletteDark.distantBlue,
|
||||||
accentTextTheme: TextTheme(
|
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"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.1.19"
|
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:
|
glob:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -58,6 +58,7 @@ dependencies:
|
||||||
password: ^1.0.0
|
password: ^1.0.0
|
||||||
basic_utils: ^1.0.8
|
basic_utils: ^1.0.8
|
||||||
bitcoin_flutter: ^2.0.0
|
bitcoin_flutter: ^2.0.0
|
||||||
|
get_it: ^4.0.2
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
Loading…
Reference in a new issue