This commit is contained in:
M 2020-06-20 10:10:00 +03:00
parent 719842964b
commit 81cee186db
94 changed files with 3786 additions and 3001 deletions

2
.gitignore vendored
View file

@ -88,3 +88,5 @@ android/key.properties
**/tool/.secrets-prod.json
**/lib/.secrets.g.dart
vendor/

6
.gitmodules vendored Normal file
View 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

View 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});
}

View file

@ -1,14 +1,35 @@
import 'dart:convert';
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/src/domain/common/balance.dart';
class BitcoinBalance extends Balance {
BitcoinBalance({@required this.confirmed, @required this.unconfirmed});
const BitcoinBalance({@required this.confirmed, @required this.unconfirmed}) : super();
factory BitcoinBalance.fromJSON(String jsonSource) {
if (jsonSource == null) {
return null;
}
final decoded = json.decode(jsonSource) as Map;
return BitcoinBalance(
confirmed: decoded['confirmed'] as int ?? 0,
unconfirmed: decoded['unconfirmed'] as int ?? 0);
}
final int confirmed;
final int unconfirmed;
int get total => confirmed + unconfirmed;
String get confirmedFormatted => bitcoinAmountToString(amount: confirmed);
String get unconfirmedFormatted => bitcoinAmountToString(amount: unconfirmed);
String get totalFormatted => bitcoinAmountToString(amount: total);
String toJSON() =>
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed});
}

View file

@ -1,61 +1,78 @@
import 'dart:convert';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/transaction_history.dart';
import 'package:cake_wallet/bitcoin/file.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
import 'package:cake_wallet/bitcoin/electrum.dart';
import 'package:cake_wallet/src/domain/common/transaction_history.dart';
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
import 'package:cake_wallet/bitcoin/file.dart';
import 'package:cake_wallet/src/domain/common/transaction_direction.dart';
class BitcoinTransactionHistory extends TransactionHistory {
BitcoinTransactionHistory(
{@required this.eclient,
@required this.path,
@required String password,
@required this.wallet})
: _transactions = BehaviorSubject<List<TransactionInfo>>.seeded([]),
part 'bitcoin_transaction_history.g.dart';
// TODO: Think about another transaction store for bitcoin transaction history..
const _transactionsHistoryFileName = 'transactions.json';
class BitcoinTransactionHistory = BitcoinTransactionHistoryBase
with _$BitcoinTransactionHistory;
abstract class BitcoinTransactionHistoryBase
extends TransactionHistoryBase<BitcoinTransactionInfo> with Store {
BitcoinTransactionHistoryBase(
{this.eclient, String dirPath, @required String password})
: path = '$dirPath/$_transactionsHistoryFileName',
_password = password,
_height = 0;
final BitcoinWallet wallet;
BitcoinWallet wallet;
final ElectrumClient eclient;
final String path;
final String _password;
int _height;
@override
Observable<List<TransactionInfo>> get transactions => _transactions.stream;
List<TransactionInfo> get transactionsAll => _transactions.value;
final BehaviorSubject<List<TransactionInfo>> _transactions;
bool _isUpdating = false;
Future<void> init() async {
// TODO: throw exeption if wallet is null;
final info = await _read();
_height = (info['height'] as int) ?? _height;
_transactions.value = info['transactions'] as List<TransactionInfo>;
// FIXME: remove hardcoded value
transactions = ObservableList.of([
BitcoinTransactionInfo(
id: 'test',
height: 12,
amount: 12,
direction: TransactionDirection.incoming,
date: DateTime.now(),
isPending: false)
]);
}
@override
Future<List<TransactionInfo>> getAll() async => _transactions.value;
@override
Future update() async {
if (_isUpdating) {
return;
await super.update();
_updateHeight();
}
try {
_isUpdating = true;
final newTransasctions = await fetchTransactions();
_transactions.value = _transactions.value + newTransasctions;
_updateHeight();
await save();
_isUpdating = false;
} catch (e) {
_isUpdating = false;
rethrow;
}
@override
Future<List<BitcoinTransactionInfo>> fetchTransactions() async {
final addresses = wallet.addresses;
final histories =
addresses.map((record) => eclient.getHistory(address: record.address));
final _historiesWithDetails = await Future.wait(histories)
.then((histories) => histories
.map((h) => h.where((tx) => (tx['height'] as int) > _height))
.expand((i) => i)
.toList())
.then((histories) => histories.map((tx) => fetchTransactionInfo(
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
final historiesWithDetails = await Future.wait(_historiesWithDetails);
return historiesWithDetails
.map((info) => BitcoinTransactionInfo.fromHexAndHeader(
info['raw'] as String, info['header'] as Map<String, Object>,
addresses: addresses.map((record) => record.address).toList()))
.toList();
}
Future<Map<String, Object>> fetchTransactionInfo(
@ -69,51 +86,20 @@ class BitcoinTransactionHistory extends TransactionHistory {
return {'raw': raw, 'header': header};
}
Future<List<BitcoinTransactionInfo>> fetchTransactions() async {
final addresses = wallet.getAddresses();
final histories =
addresses.map((address) => eclient.getHistory(address: address));
final _historiesWithDetails = await Future.wait(histories)
.then((histories) => histories
.map((h) => h.where((tx) => (tx['height'] as int) > _height))
.expand((i) => i)
.toList())
.then((histories) => histories.map((tx) => fetchTransactionInfo(
hash: tx['tx_hash'] as String, height: tx['height'] as int)));
final historiesWithDetails = await Future.wait(_historiesWithDetails);
return historiesWithDetails
.map((info) => BitcoinTransactionInfo.fromHexAndHeader(
info['raw'] as String, info['header'] as Map<String, Object>,
addresses: addresses))
.toList();
}
Future<void> add(List<BitcoinTransactionInfo> transactions) async {
final txs = await getAll()
..addAll(transactions);
await writeData(
path: path,
password: _password,
data: json
.encode(txs.map((tx) => (tx as BitcoinTransactionInfo).toJson())));
this.transactions.addAll(transactions);
await save();
}
Future<void> addOne(BitcoinTransactionInfo tx) async {
final txs = await getAll()
..add(tx);
await writeData(
path: path,
password: _password,
data: json
.encode(txs.map((tx) => (tx as BitcoinTransactionInfo).toJson())));
transactions.add(tx);
await save();
}
Future<void> save() async => writeData(
path: path,
password: _password,
data: json
.encode({'height': _height, 'transactions': _transactions.value}));
data: json.encode({'height': _height, 'transactions': transactions}));
Future<Map<String, Object>> _read() async {
try {
@ -133,13 +119,13 @@ class BitcoinTransactionHistory extends TransactionHistory {
return {'transactions': transactions, 'height': height};
} catch (_) {
return {'transactions': List<TransactionInfo>(), 'height': 0};
return {'transactions': <TransactionInfo>[], 'height': 0};
}
}
void _updateHeight() {
final int newHeight = _transactions.value
.fold(0, (acc, val) => val.height > acc ? val.height : acc);
final newHeight = transactions.fold(
0, (int acc, val) => val.height > acc ? val.height : acc);
_height = newHeight > _height ? newHeight : _height;
}
}

View file

@ -66,7 +66,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
String amountFormatted() => bitcoinAmountToString(amount: amount);
@override
String fiatAmount() => '';
String fiatAmount() => '\$ 24.5';
Map<String, dynamic> toJson() {
final m = Map<String, dynamic>();

View file

@ -1,251 +1,184 @@
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
import 'package:cake_wallet/src/domain/common/sync_status.dart';
import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';
import 'dart:convert';
import 'package:mobx/mobx.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:flutter/foundation.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
import 'package:bitcoin_flutter/src/payments/index.dart' show PaymentData;
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_history.dart';
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
import 'package:cake_wallet/bitcoin/file.dart';
import 'package:cake_wallet/bitcoin/electrum.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_history.dart';
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
import 'package:cake_wallet/src/domain/common/node.dart';
import 'package:cake_wallet/src/domain/common/pending_transaction.dart';
import 'package:cake_wallet/src/domain/common/transaction_creation_credentials.dart';
import 'package:cake_wallet/src/domain/common/transaction_history.dart';
import 'package:cake_wallet/src/domain/common/wallet.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/core/wallet_base.dart';
class BitcoinWallet extends Wallet {
BitcoinWallet(
{@required this.hdwallet,
@required this.eclient,
@required this.path,
@required String password,
int accountIndex = 0,
this.mnemonic})
: _accountIndex = accountIndex,
_password = password,
_syncStatus = BehaviorSubject<SyncStatus>(),
_onBalanceChange = BehaviorSubject<BitcoinBalance>(),
_onAddressChange = BehaviorSubject<String>(),
_onNameChange = BehaviorSubject<String>();
part 'bitcoin_wallet.g.dart';
@override
Observable<BitcoinBalance> get onBalanceChange => _onBalanceChange.stream;
/* TODO: Save balance to a wallet file.
Load balance from the wallet file in `init` method.
*/
@override
Observable<SyncStatus> get syncStatus => _syncStatus.stream;
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
@override
String get name => path.split('/').last ?? '';
@override
String get address => hdwallet.address;
String get xpub => hdwallet.base58;
final String path;
final bitcoin.HDWallet hdwallet;
final ElectrumClient eclient;
final String mnemonic;
BitcoinTransactionHistory history;
final BehaviorSubject<SyncStatus> _syncStatus;
final BehaviorSubject<BitcoinBalance> _onBalanceChange;
final BehaviorSubject<String> _onAddressChange;
final BehaviorSubject<String> _onNameChange;
BehaviorSubject<Object> _addressUpdatesSubject;
StreamSubscription<Object> _addressUpdatesSubscription;
final String _password;
int _accountIndex;
static Future<BitcoinWallet> load(
{@required String name, @required String password}) async {
final walletDirPath =
await pathForWalletDir(name: name, type: WalletType.bitcoin);
final walletPath = '$walletDirPath/$name';
final walletJSONRaw = await read(path: walletPath, password: password);
final jsoned = json.decode(walletJSONRaw) as Map<String, Object>;
final mnemonic = jsoned['mnemonic'] as String;
abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
static BitcoinWallet fromJSON(
{@required String password,
@required String name,
@required String dirPath,
String jsonSource}) {
final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String;
final accountIndex =
(jsoned['account_index'] == "null" || jsoned['account_index'] == null)
(data['account_index'] == "null" || data['account_index'] == null)
? 0
: int.parse(jsoned['account_index'] as String);
: int.parse(data['account_index'] as String);
final _addresses = data['addresses'] as List;
final addresses = <BitcoinAddressRecord>[];
final balance = BitcoinBalance.fromJSON(data['balance'] as String) ??
BitcoinBalance(confirmed: 0, unconfirmed: 0);
return await build(
_addresses?.forEach((Object el) {
if (el is String) {
addresses.add(BitcoinAddressRecord.fromJSON(el));
}
});
return BitcoinWalletBase.build(
dirPath: dirPath,
mnemonic: mnemonic,
password: password,
name: name,
accountIndex: accountIndex);
accountIndex: accountIndex,
initialAddresses: addresses,
initialBalance: balance);
}
static Future<BitcoinWallet> build(
static BitcoinWallet build(
{@required String mnemonic,
@required String password,
@required String name,
int accountIndex = 0}) async {
final hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
network: bitcoin.bitcoin);
final walletDirPath =
await pathForWalletDir(name: name, type: WalletType.bitcoin);
final walletPath = '$walletDirPath/$name';
final historyPath = '$walletDirPath/transactions.json';
@required String dirPath,
List<BitcoinAddressRecord> initialAddresses,
BitcoinBalance initialBalance,
int accountIndex = 0}) {
final walletPath = '$dirPath/$name';
final eclient = ElectrumClient();
final wallet = BitcoinWallet(
hdwallet: hd,
final history = BitcoinTransactionHistory(
eclient: eclient, dirPath: dirPath, password: password);
return BitcoinWallet._internal(
eclient: eclient,
path: walletPath,
name: name,
mnemonic: mnemonic,
password: password,
accountIndex: accountIndex);
final history = BitcoinTransactionHistory(
eclient: eclient,
path: historyPath,
password: password,
wallet: wallet);
wallet.history = history;
await history.init();
await wallet.updateInfo();
return wallet;
accountIndex: accountIndex,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
transactionHistory: history);
}
List<String> getAddresses() => _accountIndex == 0
? [address]
: List<String>.generate(
_accountIndex, (i) => _getAddress(hd: hdwallet, index: i));
BitcoinWalletBase._internal(
{@required this.eclient,
@required this.path,
@required String password,
@required this.name,
List<BitcoinAddressRecord> initialAddresses,
int accountIndex = 0,
this.transactionHistory,
this.mnemonic,
BitcoinBalance initialBalance}) {
balance = initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0);
hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
network: bitcoin.bitcoin);
addresses = initialAddresses != null
? ObservableList<BitcoinAddressRecord>.of(initialAddresses)
: ObservableList<BitcoinAddressRecord>();
Future<String> newAddress() async {
if (addresses.isEmpty) {
addresses.add(BitcoinAddressRecord(hd.address));
}
address = addresses.first.address;
_password = password;
_accountIndex = accountIndex;
}
@override
final BitcoinTransactionHistory transactionHistory;
final String path;
bitcoin.HDWallet hd;
final ElectrumClient eclient;
final String mnemonic;
int _accountIndex;
String _password;
@override
String name;
@override
@observable
String address;
@override
@observable
BitcoinBalance balance;
@override
final type = WalletType.bitcoin;
ObservableList<BitcoinAddressRecord> addresses;
String get xpub => hd.base58;
Future<void> init() async {
await transactionHistory.init();
}
Future<BitcoinAddressRecord> generateNewAddress({String label}) async {
_accountIndex += 1;
final address = _getAddress(hd: hdwallet, index: _accountIndex);
final address = BitcoinAddressRecord(
_getAddress(hd: hd, index: _accountIndex),
label: label);
addresses.add(address);
await save();
return address;
}
@override
Future close() async {
await _addressUpdatesSubscription?.cancel();
Future<void> updateAddress(String address, {String label}) async {
for (final addr in addresses) {
if (addr.address == address) {
addr.label = label;
await save();
break;
}
@override
Future 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
Future<PendingTransaction> createTransaction(
TransactionCreationCredentials credentials) async {
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
final transactions = history.transactionsAll;
history.transactionsAll.sort((q, w) => q.height.compareTo(w.height));
final prevTx = transactions.first;
txb.setVersion(1);
txb.addInput(prevTx, 0);
txb.addOutput('address', 112);
txb.sign(vin: null, keyPair: null);
final hex = txb.build().toHex();
// broadcast transaction to electrum
return null;
}
Future<void> startSync() async {}
@override
Future<String> getAddress() async => address;
Future<void> connectToNode({@required Node node}) async {}
@override
Future<int> getCurrentHeight() async => 0;
Future<void> createTransaction(Object credentials) async {}
@override
Future<String> getFilename() async => path.split('/').last ?? '';
Future<void> save() async =>
await write(path: path, password: _password, data: toJSON());
@override
Future<String> getFullBalance() async =>
bitcoinAmountToString(amount: _onBalanceChange.value.total);
@override
TransactionHistory getHistory() => history;
@override
Future<Map<String, String>> getKeys() async =>
{'publicKey': hdwallet.pubKey, 'privateKey': hdwallet.privKey};
@override
Future<String> getName() async => path.split('/').last ?? '';
@override
Future<int> getNodeHeight() async => 0;
@override
Future<String> getSeed() async => mnemonic;
@override
WalletType getType() => WalletType.bitcoin;
@override
Future<String> getUnlockedBalance() async =>
bitcoinAmountToString(amount: _onBalanceChange.value.total);
@override
Future<bool> isConnected() async => eclient.isConnected;
@override
Observable<String> get onAddressChange => _onAddressChange.stream;
@override
Observable<String> get onNameChange => _onNameChange.stream;
@override
Future rescan({int restoreHeight = 0}) {
// TODO: implement rescan
return null;
}
@override
Future startSync() async {
_addressUpdatesSubject = eclient.addressUpdate(address: address);
_addressUpdatesSubscription =
_addressUpdatesSubject.listen((obj) => print('new obj: $obj'));
_onBalanceChange.value = await fetchBalance();
getHistory().update();
}
@override
Future updateInfo() async {
_onNameChange.value = await getName();
// _addressUpdatesSubject = eclient.addressUpdate(address: address);
// _addressUpdatesSubscription =
// _addressUpdatesSubject.listen((obj) => print('new obj: $obj'));
_onBalanceChange.value = BitcoinBalance(confirmed: 0, unconfirmed: 0);
print(await getKeys());
}
Future<BitcoinBalance> fetchBalance() async {
final balance = await _fetchBalances();
return BitcoinBalance(
confirmed: balance['confirmed'], unconfirmed: balance['unconfirmed']);
}
Future<void> save() async => await write(
path: path,
password: _password,
obj: {'mnemonic': mnemonic, 'account_index': _accountIndex.toString()});
String toJSON() => json.encode({
'mnemonic': mnemonic,
'account_index': _accountIndex.toString(),
'addresses': addresses.map((addr) => addr.toJSON()).toList(),
'balance': balance?.toJSON()
});
String _getAddress({bitcoin.HDWallet hd, int index}) => bitcoin
.P2PKH(
@ -256,9 +189,8 @@ class BitcoinWallet extends Wallet {
Future<Map<String, int>> _fetchBalances() async {
final balances = await Future.wait(
getAddresses().map((address) => eclient.getBalance(address: address)));
final balance =
balances.fold(Map<String, int>(), (Map<String, int> acc, val) {
addresses.map((record) => eclient.getBalance(address: record.address)));
final balance = balances.fold(<String, int>{}, (Map<String, int> acc, val) {
acc['confirmed'] =
(val['confirmed'] as int ?? 0) + (acc['confirmed'] ?? 0);
acc['unconfirmed'] =

View file

@ -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;
}
}

View 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;
}

View 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;
}
}

View file

@ -7,12 +7,11 @@ import 'package:flutter/foundation.dart';
Future<void> write(
{@required String path,
@required String password,
@required Map<String, String> obj}) async {
final jsoned = json.encode(obj);
@required String data}) async {
final keys = extractKeys(password);
final key = encrypt.Key.fromBase64(keys.first);
final iv = encrypt.IV.fromBase64(keys.last);
final encrypted = await encode(key: key, iv: iv, data: jsoned);
final encrypted = await encode(key: key, iv: iv, data: data);
final f = File(path);
f.writeAsStringSync(encrypted);
}

View 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);
}

View 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 '';
}
}
}

View file

@ -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;
}

View file

@ -1,21 +1,41 @@
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/setup_pin_code_state.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/src/domain/common/secret_store_key.dart';
import 'package:cake_wallet/src/domain/common/encrypt.dart';
part 'auth_service.g.dart';
class AuthService with Store {
AuthService({this.secureStorage, this.sharedPreferences});
class AuthService = AuthServiceBase with _$AuthService;
final FlutterSecureStorage secureStorage;
final SharedPreferences sharedPreferences;
abstract class AuthServiceBase with Store {
@observable
SetupPinCodeState setupPinCodeState;
Future<void> setupPinCode({@required String pin}) async {}
Future<bool> authenticate({@required String pin}) async {
return false;
Future setPassword(String password) async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final encodedPassword = encodedPinCode(pin: password);
await secureStorage.write(key: key, value: encodedPassword);
}
void resetSetupPinCodeState() =>
setupPinCodeState = InitialSetupPinCodeState();
Future<bool> canAuthenticate() async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final walletName = sharedPreferences.getString('current_wallet_name') ?? '';
var password = '';
try {
password = await secureStorage.read(key: key);
} catch (e) {
print(e);
}
return walletName.isNotEmpty && password.isNotEmpty;
}
Future<bool> authenticate(String pin) async {
final key = generateStoreKeyFor(key: SecretStoreKey.pinCodePassword);
final encodedPin = await secureStorage.read(key: key);
final decodedPin = decodedPinCode(pin: encodedPin);
return decodedPin == pin;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View 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();
}
}

View 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;
}
}

View 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);
}

View file

@ -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;
}

View file

@ -1,10 +1,11 @@
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
abstract class TranasctionHistoryBase<TransactionType> {
TranasctionHistoryBase() : _isUpdating = false;
abstract class TransactionHistoryBase<TransactionType extends TransactionInfo> {
TransactionHistoryBase() : _isUpdating = false;
@observable
List<TransactionType> transactions;
ObservableList<TransactionType> transactions;
bool _isUpdating;
@ -15,7 +16,7 @@ abstract class TranasctionHistoryBase<TransactionType> {
try {
_isUpdating = false;
transactions = await fetchTransactions();
transactions.addAll(await fetchTransactions());
_isUpdating = true;
} catch (e) {
_isUpdating = false;

View file

@ -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_]\$');
}

View file

@ -1,20 +1,24 @@
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/transaction_history.dart';
import 'package:cake_wallet/src/domain/common/node.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
abstract class WalletBase<BalaceType> {
WalletType type;
String get name;
String get filename;
@observable
String address;
@observable
BalaceType balance;
TransactionHistoryBase transactionHistory;
Future<void> connectToNode({@required Node node});
Future<void> startSync();
Future<void> createTransaction(Object credentials);
Future<void> save();
}

View file

@ -1,34 +1,47 @@
import 'package:cake_wallet/core/wallet_creation_state.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/core/wallet_credentials.dart';
import 'package:cake_wallet/core/bitcoin_wallet_list_service.dart';
import 'package:cake_wallet/core/monero_wallet_list_service.dart';
import 'package:cake_wallet/core/wallet_list_service.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
import 'package:cake_wallet/monero/monero_wallet_service.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/src/domain/common/secret_store_key.dart';
import 'package:cake_wallet/src/domain/common/encrypt.dart';
part 'wallet_creation_service.g.dart';
class WalletCreationService = WalletCreationServiceBase
with _$WalletCreationService;
abstract class WalletCreationServiceBase with Store {
@observable
WalletCreationState state;
class WalletCreationService {
WalletCreationService(
{WalletType initialType,
this.appStore,
this.secureStorage,
this.sharedPreferences})
: type = initialType {
if (type != null) {
changeWalletType(type: type);
}
}
WalletType type;
final AppStore appStore;
final FlutterSecureStorage secureStorage;
final SharedPreferences sharedPreferences;
WalletListService _service;
// final WalletService walletService;
// final Box<WalletInfo> walletInfoSource;
WalletService _service;
void changeWalletType({@required WalletType type}) {
this.type = type;
switch (type) {
case WalletType.monero:
_service = MoneroWalletListService();
_service = MoneroWalletService();
break;
case WalletType.bitcoin:
_service = BitcoinWalletListService();
_service = BitcoinWalletService();
break;
default:
break;
@ -36,32 +49,45 @@ abstract class WalletCreationServiceBase with Store {
}
Future<void> create(WalletCredentials credentials) async {
try {
state = WalletCreating();
await _service.create(credentials);
state = WalletCreatedSuccessfully();
} catch (e) {
state = WalletCreationFailure(error: e.toString());
}
final password = generateWalletPassword(type);
credentials.password = password;
await saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service.create(credentials);
appStore.wallet = wallet;
appStore.authenticationStore.allowed();
}
Future<void> restoreFromKeys(WalletCredentials credentials) async {
try {
state = WalletCreating();
await _service.restoreFromKeys(credentials);
state = WalletCreatedSuccessfully();
} catch (e) {
state = WalletCreationFailure(error: e.toString());
}
final password = generateWalletPassword(type);
credentials.password = password;
await saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service.restoreFromKeys(credentials);
appStore.wallet = wallet;
appStore.authenticationStore.allowed();
}
Future<void> restoreFromSeed(WalletCredentials credentials) async {
try {
state = WalletCreating();
await _service.restoreFromSeed(credentials);
state = WalletCreatedSuccessfully();
} catch (e) {
state = WalletCreationFailure(error: e.toString());
}
final password = generateWalletPassword(type);
credentials.password = password;
await saveWalletPassword(password: password, walletName: credentials.name);
final wallet = await _service.restoreFromSeed(credentials);
appStore.wallet = wallet;
appStore.authenticationStore.allowed();
}
Future<String> getWalletPassword({String walletName}) async {
final key = generateStoreKeyFor(
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
final encodedPassword = await secureStorage.read(key: key);
return decodeWalletPassword(password: encodedPassword);
}
Future<void> saveWalletPassword({String walletName, String password}) async {
final key = generateStoreKeyFor(
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
final encodedPassword = encodeWalletPassword(password: password);
await secureStorage.write(key: key, value: encodedPassword);
}
}

View file

@ -1,6 +1,6 @@
abstract class WalletCredentials {
const WalletCredentials({this.name, this.password});
WalletCredentials({this.name, this.password});
final String name;
final String password;
String password;
}

View file

@ -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);
}

View 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
View 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);
});
}

View file

@ -1,7 +1,13 @@
import 'package:cake_wallet/core/app_service.dart';
import 'package:cake_wallet/reactions/bootstrap.dart';
import 'package:cake_wallet/store/authentication_store.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
import 'package:cake_wallet/monero/monero_wallet_service.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:get_it/get_it.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@ -27,7 +33,8 @@ import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
import 'package:cake_wallet/src/stores/send_template/send_template_store.dart';
import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart';
import 'package:cake_wallet/src/screens/root/root.dart';
import 'package:cake_wallet/src/stores/authentication/authentication_store.dart';
//import 'package:cake_wallet/src/stores/authentication/authentication_store.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/src/stores/price/price_store.dart';
import 'package:cake_wallet/src/domain/services/user_service.dart';
@ -47,6 +54,8 @@ import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
setup();
final appDir = await getApplicationDocumentsDirectory();
Hive.init(appDir.path);
Hive.registerAdapter(ContactAdapter());
@ -87,13 +96,13 @@ void main() async {
sharedPreferences: sharedPreferences);
final userService = UserService(
sharedPreferences: sharedPreferences, secureStorage: secureStorage);
final authenticationStore = AuthenticationStore(userService: userService);
// final authenticationStore = AuthenticationStore(userService: userService);
await initialSetup(
sharedPreferences: sharedPreferences,
walletListService: walletListService,
nodes: nodes,
authStore: authenticationStore,
// authStore: authenticationStore,
initialMigrationVersion: 2);
final settingsStore = await SettingsStoreBase.load(
@ -119,8 +128,7 @@ void main() async {
final walletCreationService = WalletCreationService();
final authService = AuthService();
final appStore = AppService(
walletCreationService: walletCreationService, authService: authService);
setReactions(
settingsStore: settingsStore,
@ -128,7 +136,7 @@ void main() async {
syncStore: syncStore,
walletStore: walletStore,
walletService: walletService,
authenticationStore: authenticationStore,
// authenticationStore: authenticationStore,
loginStore: loginStore);
runApp(MultiProvider(providers: [
@ -141,7 +149,7 @@ void main() async {
Provider(create: (_) => walletStore),
Provider(create: (_) => syncStore),
Provider(create: (_) => balanceStore),
Provider(create: (_) => authenticationStore),
// Provider(create: (_) => authenticationStore),
Provider(create: (_) => contacts),
Provider(create: (_) => nodes),
Provider(create: (_) => transactionDescriptions),
@ -149,7 +157,7 @@ void main() async {
Provider(create: (_) => seedLanguageStore),
Provider(create: (_) => sendTemplateStore),
Provider(create: (_) => exchangeTemplateStore),
Provider(create: (_) => appStore),
// Provider(create: (_) => appStore),
Provider(create: (_) => walletCreationService),
Provider(create: (_) => authService)
], child: CakeWalletApp()));
@ -159,7 +167,7 @@ Future<void> initialSetup(
{WalletListService walletListService,
SharedPreferences sharedPreferences,
Box<Node> nodes,
AuthenticationStore authStore,
// AuthenticationStore authStore,
int initialMigrationVersion = 1,
WalletType initialWalletType = WalletType.bitcoin}) async {
await walletListService.changeWalletManger(walletType: initialWalletType);
@ -167,7 +175,12 @@ Future<void> initialSetup(
version: initialMigrationVersion,
sharedPreferences: sharedPreferences,
nodes: nodes);
await authStore.started();
// await authStore.started();
await bootstrap();
// final authenticationStore = getIt.get<AuthenticationStore>();
// FIXME
// authenticationStore.state = AuthenticationState.denied;
monero_wallet.onStartup();
}
@ -241,6 +254,8 @@ class MaterialAppWithTheme extends StatelessWidget {
nodes: nodes,
trades: trades,
transactionDescriptions: transactionDescriptions),
home: Root());
home: Root(
authenticationStore: getIt.get<AuthenticationStore>(),
));
}
}

View 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;
}
}
}

View file

@ -8,7 +8,7 @@ import 'package:cake_wallet/src/domain/monero/monero_transaction_info.dart';
part 'monero_transaction_history.g.dart';
List<TransactionInfo> _getAllTransactions(dynamic _) =>
List<MoneroTransactionInfo> _getAllTransactions(dynamic _) =>
monero_transaction_history
.getAllTransations()
.map((row) => MoneroTransactionInfo.fromRow(row))
@ -18,9 +18,13 @@ class MoneroTransactionHistory = MoneroTransactionHistoryBase
with _$MoneroTransactionHistory;
abstract class MoneroTransactionHistoryBase
extends TranasctionHistoryBase<TransactionInfo> with Store {
extends TransactionHistoryBase<MoneroTransactionInfo> with Store {
MoneroTransactionHistoryBase() {
transactions = ObservableList<MoneroTransactionInfo>();
}
@override
Future<List<TransactionInfo>> fetchTransactions() async {
Future<List<MoneroTransactionInfo>> fetchTransactions() async {
monero_transaction_history.refreshTransactions();
return _getAllTransactions(null);
}

View file

@ -1,35 +1,36 @@
import 'package:cake_wallet/core/monero_balance.dart';
import 'package:cake_wallet/core/monero_transaction_history.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/monero/monero_balance.dart';
import 'package:cake_wallet/monero/monero_transaction_history.dart';
import 'package:cake_wallet/monero/monero_subaddress_list.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/core/transaction_history.dart';
import 'package:cake_wallet/src/domain/common/sync_status.dart';
import 'package:cake_wallet/src/domain/monero/account.dart';
import 'package:cake_wallet/src/domain/monero/account_list.dart';
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
import 'package:cake_wallet/src/domain/monero/subaddress_list.dart';
import 'package:cw_monero/wallet.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/src/domain/common/node.dart';
import 'package:cw_monero/wallet.dart' as monero_wallet;
import 'wallet_base.dart';
part 'monero_wallet.g.dart';
class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
MoneroWalletBase({String filename, this.isRecovery = false}) {
transactionHistory = MoneroTransactionHistory();
MoneroWalletBase({String filename, this.isRecovery = false})
: transactionHistory = MoneroTransactionHistory() {
_filename = filename;
accountList = AccountList();
subaddressList = SubaddressList();
subaddressList = MoneroSubaddressList();
balance = MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0));
}
MoneroTransactionHistory transactionHistory;
SubaddressList subaddressList;
AccountList accountList;
@override
final MoneroTransactionHistory transactionHistory;
@observable
Account account;
@ -41,30 +42,40 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
SyncStatus syncStatus;
@override
String get name => filename.split('/').last;
String get name => _filename.split('/').last;
@override
String get filename => _filename;
final type = WalletType.monero;
String _filename;
@override
@observable
String address;
bool isRecovery;
SyncListner _listner;
MoneroSubaddressList subaddressList;
void init() {
AccountList accountList;
String _filename;
SyncListner _listener;
Future<void> init() async {
await accountList.update();
account = accountList.getAll().first;
subaddressList.refresh(accountIndex: account.id ?? 0);
subaddressList.update(accountIndex: account.id ?? 0);
subaddress = subaddressList.getAll().first;
balance = MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
unlockedBalance:
monero_wallet.getFullBalance(accountIndex: account.id));
address = subaddress.address;
_setListeners();
}
void close() {
_listner?.stop();
_listener?.stop();
}
@override
@ -133,8 +144,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
Future<bool> isConnected() async => monero_wallet.isConnected();
void _setListeners() {
_listner?.stop();
_listner = monero_wallet.setListeners(
_listener?.stop();
_listener = monero_wallet.setListeners(
_onNewBlock, _onNeedToRefresh, _onNewTransaction);
}

View file

@ -1,21 +1,20 @@
import 'package:cake_wallet/core/monero_wallet.dart';
import 'package:cake_wallet/monero/monero_wallet.dart';
import 'package:cake_wallet/core/wallet_credentials.dart';
import 'package:cake_wallet/core/wallet_list_service.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/src/domain/common/pathForWallet.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cw_monero/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/wallet.dart' as monero_wallet;
class MoneroNewWalletCredentials extends WalletCredentials {
const MoneroNewWalletCredentials(
{String name, String password, this.language})
MoneroNewWalletCredentials({String name, String password, this.language})
: super(name: name, password: password);
final String language;
}
class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
const MoneroRestoreWalletFromSeedCredentials(
MoneroRestoreWalletFromSeedCredentials(
{String name, String password, this.mnemonic, this.height})
: super(name: name, password: password);
@ -24,7 +23,7 @@ class MoneroRestoreWalletFromSeedCredentials extends WalletCredentials {
}
class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
const MoneroRestoreWalletFromKeysCredentials(
MoneroRestoreWalletFromKeysCredentials(
{String name,
String password,
this.language,
@ -41,12 +40,12 @@ class MoneroRestoreWalletFromKeysCredentials extends WalletCredentials {
final int height;
}
class MoneroWalletListService extends WalletListService<
class MoneroWalletService extends WalletService<
MoneroNewWalletCredentials,
MoneroRestoreWalletFromSeedCredentials,
MoneroRestoreWalletFromKeysCredentials> {
@override
Future<void> create(MoneroNewWalletCredentials credentials) async {
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
try {
final path =
await pathForWallet(name: credentials.name, type: WalletType.monero);
@ -56,7 +55,10 @@ class MoneroWalletListService extends WalletListService<
password: credentials.password,
language: credentials.language);
return MoneroWallet(filename: monero_wallet.getFilename())..init();
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
await wallet.init();
return wallet;
} catch (e) {
// TODO: Implement Exception fop wallet list service.
print('MoneroWalletsManager Error: $e');
@ -77,7 +79,7 @@ class MoneroWalletListService extends WalletListService<
}
@override
Future<void> openWallet(String name, String password) async {
Future<MoneroWallet> openWallet(String name, String password) async {
try {
final path = await pathForWallet(name: name, type: WalletType.monero);
monero_wallet_manager.openWallet(path: path, password: password);
@ -86,7 +88,10 @@ class MoneroWalletListService extends WalletListService<
// final walletInfo = walletInfoSource.values
// .firstWhere((info) => info.id == id, orElse: () => null);
return MoneroWallet(filename: monero_wallet.getFilename())..init();
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
await wallet.init();
return wallet;
} catch (e) {
// TODO: Implement Exception fop wallet list service.
print('MoneroWalletsManager Error: $e');
@ -100,7 +105,7 @@ class MoneroWalletListService extends WalletListService<
}
@override
Future<void> restoreFromKeys(
Future<MoneroWallet> restoreFromKeys(
MoneroRestoreWalletFromKeysCredentials credentials) async {
try {
final path =
@ -115,7 +120,10 @@ class MoneroWalletListService extends WalletListService<
viewKey: credentials.viewKey,
spendKey: credentials.spendKey);
return MoneroWallet(filename: monero_wallet.getFilename())..init();
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
await wallet.init();
return wallet;
} catch (e) {
// TODO: Implement Exception fop wallet list service.
print('MoneroWalletsManager Error: $e');
@ -124,7 +132,7 @@ class MoneroWalletListService extends WalletListService<
}
@override
Future<void> restoreFromSeed(
Future<MoneroWallet> restoreFromSeed(
MoneroRestoreWalletFromSeedCredentials credentials) async {
try {
final path =
@ -136,7 +144,10 @@ class MoneroWalletListService extends WalletListService<
seed: credentials.mnemonic,
restoreHeight: credentials.height);
return MoneroWallet(filename: monero_wallet.getFilename())..init();
final wallet = MoneroWallet(filename: monero_wallet.getFilename());
await wallet.init();
return wallet;
} catch (e) {
// TODO: Implement Exception fop wallet list service.
print('MoneroWalletsManager Error: $e');

View file

@ -12,6 +12,7 @@ class Palette {
static const Color blue = Color.fromRGBO(88, 143, 252, 1.0);
static const Color darkLavender = Color.fromRGBO(225, 238, 250, 1.0);
static const Color nightBlue = Color.fromRGBO(46, 57, 96, 1.0);
static const Color eee = Color.fromRGBO(236, 239, 245, 1.0);
}
class PaletteDark {

View 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();
}
});
}

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
@ -6,7 +8,7 @@ import 'package:provider/provider.dart';
import 'package:hive/hive.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'di.dart';
// MARK: Import domains
import 'package:cake_wallet/src/domain/common/contact.dart';
@ -21,7 +23,7 @@ import 'package:cake_wallet/src/domain/common/node.dart';
import 'package:cake_wallet/src/domain/monero/transaction_description.dart';
import 'package:cake_wallet/src/domain/exchange/trade.dart';
import 'package:cake_wallet/src/domain/monero/account.dart';
import 'package:cake_wallet/src/domain/common/mnemotic_item.dart';
import 'package:cake_wallet/src/domain/common/mnemonic_item.dart';
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
@ -56,7 +58,7 @@ import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/nodes/new_node_page.dart';
import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart';
import 'package:cake_wallet/src/screens/receive/receive_page.dart';
import 'package:cake_wallet/src/screens/subaddress/new_subaddress_page.dart';
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart';
import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
@ -126,24 +128,18 @@ class Router {
Navigator.pushNamed(context, Routes.newWalletType))));
case Routes.newWalletType:
return CupertinoPageRoute<void>(builder: (_) => NewWalletTypePage());
return CupertinoPageRoute<void>(
builder: (_) => NewWalletTypePage(
onTypeSelected: (context, type) => Navigator.of(context)
.pushNamed(Routes.newWallet, arguments: type),
));
case Routes.newWallet:
final type = settings.arguments as WalletType;
walletListService.changeWalletManger(walletType: type);
final walletNewVM = getIt.get<WalletNewVM>(param1: type);
return CupertinoPageRoute<void>(
builder:
(_) =>
ProxyProvider<AuthenticationStore, WalletCreationStore>(
update: (_, authStore, __) => WalletCreationStore(
authStore: authStore,
sharedPreferences: sharedPreferences,
walletListService: walletListService),
child: NewWalletPage(
walletsService: walletListService,
walletService: walletService,
sharedPreferences: sharedPreferences)));
builder: (_) => NewWalletPage(walletNewVM));
case Routes.setupPin:
Function(BuildContext, String) callback;
@ -163,6 +159,13 @@ class Router {
callback == null ? null : callback(context, pin))),
fullscreenDialog: true);
case Routes.restoreWalletType:
return CupertinoPageRoute<void>(
builder: (_) => NewWalletTypePage(
onTypeSelected: (context, type) => Navigator.of(context)
.pushNamed(Routes.restoreWalletOptions, arguments: type),
));
case Routes.restoreOptions:
final type = settings.arguments as WalletType;
walletListService.changeWalletManger(walletType: type);
@ -175,7 +178,28 @@ class Router {
walletListService.changeWalletManger(walletType: type);
return CupertinoPageRoute<void>(
builder: (_) => RestoreWalletOptionsPage(type: type));
builder: (_) => RestoreWalletOptionsPage(
type: type,
onRestoreFromSeed: (context) {
final route = type == WalletType.monero
? Routes.seedLanguage
: Routes.restoreWalletFromSeed;
final args = type == WalletType.monero
? [type, Routes.restoreWalletFromSeed]
: [type];
Navigator.of(context).pushNamed(route, arguments: args);
},
onRestoreFromKeys: (context) {
final route = type == WalletType.monero
? Routes.seedLanguage
: Routes.restoreWalletFromKeys;
final args = type == WalletType.monero
? [type, Routes.restoreWalletFromSeed]
: [type];
Navigator.of(context).pushNamed(route, arguments: args);
}));
case Routes.restoreWalletOptionsFromWelcome:
return CupertinoPageRoute<void>(
@ -186,7 +210,7 @@ class Router {
sharedPreferences: sharedPreferences)),
child: SetupPinCodePage(
onPinCodeSetup: (context, _) => Navigator.pushNamed(
context, Routes.restoreWalletOptions))));
context, Routes.restoreWalletType))));
case Routes.seed:
return MaterialPageRoute<void>(
@ -196,8 +220,11 @@ class Router {
callback: settings.arguments as void Function()));
case Routes.restoreWalletFromSeed:
final type = settings.arguments as WalletType;
walletListService.changeWalletManger(walletType: type);
final args = settings.arguments as List<dynamic>;
final type = args.first as WalletType;
final language = type == WalletType.monero
? args[1] as String
: 'English'; // FIXME: Unnamed constant; English default and only one language for bitcoin.
return CupertinoPageRoute<void>(
builder: (_) =>
@ -207,11 +234,15 @@ class Router {
sharedPreferences: sharedPreferences,
walletListService: walletListService),
child: RestoreWalletFromSeedPage(
walletsService: walletListService,
walletService: walletService,
sharedPreferences: sharedPreferences)));
type: type, language: language)));
case Routes.restoreWalletFromKeys:
final args = settings.arguments as List<dynamic>;
final type = args.first as WalletType;
final language = type == WalletType.monero
? args[1] as String
: 'English'; // FIXME: Unnamed constant; English default and only one language for bitcoin.
return CupertinoPageRoute<void>(
builder: (_) =>
ProxyProvider<AuthenticationStore, WalletRestorationStore>(
@ -261,17 +292,11 @@ class Router {
walletService: walletService,
priceStore: priceStore,
transactionDescriptions: transactionDescriptions),
child: SendTemplatePage())
);
child: SendTemplatePage()));
case Routes.receive:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => MultiProvider(providers: [
Provider(
create: (_) =>
SubaddressListStore(walletService: walletService))
], child: ReceivePage()));
fullscreenDialog: true, builder: (_) => getIt.get<ReceivePage>());
case Routes.transactionDetails:
return CupertinoPageRoute<void>(
@ -281,10 +306,8 @@ class Router {
case Routes.newSubaddress:
return CupertinoPageRoute<void>(
builder: (_) => Provider(
create: (_) =>
SubadrressCreationStore(walletService: walletService),
child: NewSubaddressPage()));
builder: (_) =>
getIt.get<AddressEditOrCreatePage>(param1: settings.arguments));
case Routes.disclaimer:
return CupertinoPageRoute<void>(builder: (_) => DisclaimerPage());
@ -294,7 +317,15 @@ class Router {
builder: (_) => DisclaimerPage(isReadOnly: true));
case Routes.seedLanguage:
return CupertinoPageRoute<void>(builder: (_) => SeedLanguage());
final args = settings.arguments as List<dynamic>;
final type = args.first as WalletType;
final redirectRoute = args[1] as String;
return CupertinoPageRoute<void>(builder: (_) {
return SeedLanguage(
onConfirm: (context, lang) => Navigator.of(context)
.popAndPushNamed(redirectRoute, arguments: [type, lang]));
});
case Routes.walletList:
return MaterialPageRoute<void>(
@ -306,17 +337,18 @@ class Router {
child: WalletListPage()));
case Routes.auth:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => Provider(
create: (_) => AuthStore(
sharedPreferences: sharedPreferences,
userService: userService,
walletService: walletService),
child: AuthPage(
onAuthenticationFinished:
settings.arguments as OnAuthenticationFinished),
));
return null;
// return MaterialPageRoute<void>(
// fullscreenDialog: true,
// builder: (_) => Provider(
// create: (_) => AuthStore(
// sharedPreferences: sharedPreferences,
// userService: userService,
// walletService: walletService),
// child: AuthPage(
// onAuthenticationFinished:
// settings.arguments as OnAuthenticationFinished),
// ));
case Routes.unlock:
return MaterialPageRoute<void>(
@ -455,15 +487,13 @@ class Router {
], child: SubaddressListPage()));
case Routes.restoreWalletFromSeedDetails:
final args = settings.arguments as List;
final walletRestorationFromSeedVM =
getIt.get<WalletRestorationFromSeedVM>(param1: args);
return CupertinoPageRoute<void>(
builder: (_) =>
ProxyProvider<AuthenticationStore, WalletRestorationStore>(
update: (_, authStore, __) => WalletRestorationStore(
authStore: authStore,
sharedPreferences: sharedPreferences,
walletListService: walletListService,
seed: settings.arguments as List<MnemoticItem>),
child: RestoreWalletFromSeedDetailsPage()));
builder: (_) => RestoreWalletFromSeedDetailsPage(
walletRestorationFromSeedVM: walletRestorationFromSeedVM));
case Routes.exchange:
return MaterialPageRoute<void>(
@ -487,7 +517,8 @@ class Router {
case Routes.exchangeTemplate:
return MaterialPageRoute<void>(
builder: (_) => Provider(create: (_) {
builder: (_) => Provider(
create: (_) {
final xmrtoprovider = XMRTOExchangeProvider();
return ExchangeStore(
@ -501,8 +532,9 @@ class Router {
MorphTokenExchangeProvider(trades: trades)
],
walletStore: walletStore);
}, child: ExchangeTemplatePage(),)
);
},
child: ExchangeTemplatePage(),
));
case Routes.settings:
return MaterialPageRoute<void>(

View file

@ -46,4 +46,5 @@ class Routes {
static const newWalletType = '/new_wallet_type';
static const sendTemplate = '/send_template';
static const exchangeTemplate = '/exchange_template';
static const restoreWalletType = '/restore_wallet_type';
}

View file

@ -1 +1,3 @@
abstract class Balance {}
abstract class Balance {
const Balance();
}

View 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;
}

View file

@ -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;
}

View file

@ -1,5 +1,4 @@
import 'dart:async';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.manager.dart';
import 'package:cake_wallet/bitcoin/key.dart';
import 'package:cake_wallet/src/domain/common/wallet_info.dart';
import 'package:flutter/foundation.dart';
@ -119,7 +118,7 @@ class WalletListService {
MoneroWalletsManager(walletInfoSource: walletInfoSource);
break;
case WalletType.bitcoin:
walletsManager = BitcoinWalletManager();
// walletsManager = BitcoinWalletManager();
break;
case WalletType.none:
walletsManager = null;

View file

@ -35,10 +35,10 @@ void setReactions(
settingsStore: settingsStore,
priceStore: priceStore);
autorun((_) async {
if (authenticationStore.state == AuthenticationState.allowed) {
await loginStore.loadCurrentWallet();
authenticationStore.state = AuthenticationState.readyToLogin;
}
// if (authenticationStore.state == AuthenticationState.allowed) {
// await loginStore.loadCurrentWallet();
// authenticationStore.state = AuthenticationState.readyToLogin;
// }
});
}

View file

@ -1,10 +1,9 @@
import 'package:mobx/mobx.dart';
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/stores/auth/auth_state.dart';
import 'package:cake_wallet/src/stores/auth/auth_store.dart';
import 'package:cake_wallet/view_model/auth_state.dart';
import 'package:cake_wallet/view_model/auth_view_model.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/src/domain/common/biometric_auth.dart';
@ -12,8 +11,12 @@ import 'package:cake_wallet/src/domain/common/biometric_auth.dart';
typedef OnAuthenticationFinished = void Function(bool, AuthPageState);
class AuthPage extends StatefulWidget {
AuthPage({this.onAuthenticationFinished, this.closable = true});
AuthPage(
{this.onAuthenticationFinished,
this.authViewModel,
this.closable = true});
final AuthViewModel authViewModel;
final OnAuthenticationFinished onAuthenticationFinished;
final bool closable;
@ -26,39 +29,12 @@ class AuthPageState extends State<AuthPage> {
final _pinCodeKey = GlobalKey<PinCodeState>();
final _backArrowImageDarkTheme =
Image.asset('assets/images/back_arrow_dark_theme.png');
void changeProcessText(String text) {
_key.currentState.showSnackBar(
SnackBar(content: Text(text), backgroundColor: Colors.green));
}
void close() => Navigator.of(_key.currentContext).pop();
ReactionDisposer _reaction;
@override
Widget build(BuildContext context) {
final authStore = Provider.of<AuthStore>(context);
final settingsStore = Provider.of<SettingsStore>(context);
if (settingsStore.allowBiometricalAuthentication) {
WidgetsBinding.instance.addPostFrameCallback((_) {
final biometricAuth = BiometricAuth();
biometricAuth.isAuthenticated().then(
(isAuth) {
if (isAuth) {
authStore.biometricAuth();
_key.currentState.showSnackBar(
SnackBar(
content: Text(S.of(context).authenticated),
backgroundColor: Colors.green,
),
);
}
}
);
});
}
reaction((_) => authStore.state, (AuthState state) {
void initState() {
_reaction ??=
reaction((_) => widget.authViewModel.state, (AuthState state) {
if (state is AuthenticatedSuccessfully) {
WidgetsBinding.instance.addPostFrameCallback((_) {
if (widget.onAuthenticationFinished != null) {
@ -119,6 +95,43 @@ class AuthPageState extends State<AuthPage> {
});
}
});
super.initState();
}
@override
void dispose() {
_reaction.reaction.dispose();
super.dispose();
}
void changeProcessText(String text) => _key.currentState.showSnackBar(
SnackBar(content: Text(text), backgroundColor: Colors.green));
void close() => Navigator.of(_key.currentContext).pop();
@override
Widget build(BuildContext context) {
// final authStore = Provider.of<AuthStore>(context);
// final settingsStore = Provider.of<SettingsStore>(context);
// if (settingsStore.allowBiometricalAuthentication) {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// final biometricAuth = BiometricAuth();
// biometricAuth.isAuthenticated().then(
// (isAuth) {
// if (isAuth) {
// authStore.biometricAuth();
// _key.currentState.showSnackBar(
// SnackBar(
// content: Text(S.of(context).authenticated),
// backgroundColor: Colors.green,
// ),
// );
// }
// }
// );
// });
// }
return Scaffold(
key: _key,
@ -143,8 +156,8 @@ class AuthPageState extends State<AuthPage> {
),
resizeToAvoidBottomPadding: false,
body: PinCode(
(pin, _) => authStore.auth(
password: pin.fold('', (ac, val) => ac + '$val')),
(pin, _) => widget.authViewModel
.auth(password: pin.fold('', (ac, val) => ac + '$val')),
false,
_pinCodeKey));
}

View file

@ -14,15 +14,16 @@ Widget createLoginPage(
@required WalletService walletService,
@required WalletListService walletListService,
@required AuthenticationStore authenticationStore}) =>
Provider(
create: (_) => AuthStore(
sharedPreferences: sharedPreferences,
userService: userService,
walletService: walletService),
child: AuthPage(
onAuthenticationFinished: (isAuthenticated, state) {
if (isAuthenticated) {
authenticationStore.loggedIn();
}
},
closable: false));
null;
// Provider(
// create: (_) => AuthStore(
// sharedPreferences: sharedPreferences,
// userService: userService,
// walletService: walletService),
// child: AuthPage(
// onAuthenticationFinished: (isAuthenticated, state) {
// if (isAuthenticated) {
// authenticationStore.loggedIn();
// }
// },
// closable: false));

View file

@ -11,13 +11,14 @@ Widget createUnlockPage(
@required UserService userService,
@required WalletService walletService,
@required Function(bool, AuthPageState) onAuthenticationFinished}) =>
WillPopScope(
onWillPop: () async => false,
child: Provider(
create: (_) => AuthStore(
sharedPreferences: sharedPreferences,
userService: userService,
walletService: walletService),
child: AuthPage(
onAuthenticationFinished: onAuthenticationFinished,
closable: false)));
null;
// WillPopScope(
// onWillPop: () async => false,
// child: Provider(
// create: (_) => AuthStore(
// sharedPreferences: sharedPreferences,
// userService: userService,
// walletService: walletService),
// child: AuthPage(
// onAuthenticationFinished: onAuthenticationFinished,
// closable: false)));

View file

@ -10,12 +10,19 @@ enum AppBarStyle { regular, withShadow }
abstract class BasePage extends StatelessWidget {
String get title => null;
bool get isModalBackButton => false;
Color get backgroundLightColor => Colors.white;
Color get backgroundDarkColor => PaletteDark.darkNightBlue;
bool get resizeToAvoidBottomPadding => true;
AppBarStyle get appBarStyle => AppBarStyle.regular;
Widget Function(BuildContext, Widget) get rootWrapper => null;
final _backArrowImage = Image.asset('assets/images/back_arrow.png');
final _backArrowImageDarkTheme =
Image.asset('assets/images/back_arrow_dark_theme.png');
@ -83,9 +90,8 @@ abstract class BasePage extends StatelessWidget {
leading: leading(context),
middle: middle(context),
trailing: trailing(context),
backgroundColor: _isDarkTheme
? backgroundDarkColor
: backgroundLightColor);
backgroundColor:
_isDarkTheme ? backgroundDarkColor : backgroundLightColor);
case AppBarStyle.withShadow:
return NavBar.withShadow(
@ -93,9 +99,8 @@ abstract class BasePage extends StatelessWidget {
leading: leading(context),
middle: middle(context),
trailing: trailing(context),
backgroundColor: _isDarkTheme
? backgroundDarkColor
: backgroundLightColor);
backgroundColor:
_isDarkTheme ? backgroundDarkColor : backgroundLightColor);
default:
return NavBar(
@ -103,9 +108,8 @@ abstract class BasePage extends StatelessWidget {
leading: leading(context),
middle: middle(context),
trailing: trailing(context),
backgroundColor: _isDarkTheme
? backgroundDarkColor
: backgroundLightColor);
backgroundColor:
_isDarkTheme ? backgroundDarkColor : backgroundLightColor);
}
}
@ -116,13 +120,14 @@ abstract class BasePage extends StatelessWidget {
final _themeChanger = Provider.of<ThemeChanger>(context);
final _isDarkTheme = _themeChanger.getTheme() == Themes.darkTheme;
return Scaffold(
backgroundColor: _isDarkTheme
? backgroundDarkColor
: backgroundLightColor,
final root = Scaffold(
backgroundColor:
_isDarkTheme ? backgroundDarkColor : backgroundLightColor,
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
appBar: appBar(context),
body: SafeArea(child: body(context)),
floatingActionButton: floatingActionButton(context));
return rootWrapper?.call(context, root) ?? root;
}
}

View file

@ -1,28 +1,36 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/view_model/dashboard_view_model.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/wallet_card.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/trade_history_panel.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
class DashboardPage extends StatelessWidget {
DashboardPage({@required this.walletViewModel});
final DashboardViewModel walletViewModel;
final _bodyKey = GlobalKey();
@override
Widget build(BuildContext context) => DashboardPageBody(key: _bodyKey);
Widget build(BuildContext context) =>
DashboardPageBody(key: _bodyKey, walletViewModel: walletViewModel);
}
class DashboardPageBody extends StatefulWidget {
DashboardPageBody({Key key}) : super(key: key);
DashboardPageBody({Key key, @required this.walletViewModel})
: super(key: key);
final DashboardViewModel walletViewModel;
@override
DashboardPageBodyState createState() => DashboardPageBodyState();
}
class DashboardPageBodyState extends State<DashboardPageBody> {
@override
Widget build(BuildContext context) {
final menuButton = Image.asset('assets/images/header.png',
final menuButton = Image.asset(
'assets/images/header.png',
color: Theme.of(context).primaryTextTheme.title.color,
);
@ -30,15 +38,10 @@ class DashboardPageBodyState extends State<DashboardPageBody> {
child: Scaffold(
body: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
gradient: LinearGradient(colors: [
Theme.of(context).scaffoldBackgroundColor,
Theme.of(context).primaryColor
],
begin: Alignment.centerLeft,
end: Alignment.centerRight
)
),
], begin: Alignment.centerLeft, end: Alignment.centerRight)),
child: Column(
children: <Widget>[
Container(
@ -55,9 +58,7 @@ class DashboardPageBodyState extends State<DashboardPageBody> {
padding: EdgeInsets.all(0),
onPressed: () async {
await showDialog<void>(
builder: (_) => MenuWidget(),
context: context
);
builder: (_) => MenuWidget(), context: context);
},
child: menuButton),
),
@ -65,12 +66,9 @@ class DashboardPageBodyState extends State<DashboardPageBody> {
),
Padding(
padding: EdgeInsets.only(left: 20, top: 20),
child: WalletCard(),
),
SizedBox(
height: 28,
),
Expanded(child: TradeHistoryPanel())
child: WalletCard(walletVM: widget.walletViewModel)),
SizedBox(height: 28),
Expanded(child: TradeHistoryPanel(dashboardViewModel: widget.walletViewModel))
],
),
),

View file

@ -17,7 +17,7 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
final actionListStore = Provider.of<ActionListStore>(context);
// final actionListStore = Provider.of<ActionListStore>(context);
final historyPanelWidth = MediaQuery.of(context).size.width;
final _themeChanger = Provider.of<ThemeChanger>(context);
@ -97,44 +97,44 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.caption.color))),
PopupMenuItem(
value: 0,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text(S.of(context).incoming),
Checkbox(
value: actionListStore
.transactionFilterStore
.displayIncoming,
onChanged: (value) =>
actionListStore
.transactionFilterStore
.toggleIncoming(),
)
]))),
PopupMenuItem(
value: 1,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text(S.of(context).outgoing),
Checkbox(
value: actionListStore
.transactionFilterStore
.displayOutgoing,
onChanged: (value) =>
actionListStore
.transactionFilterStore
.toggleOutgoing(),
)
]))),
// PopupMenuItem(
// value: 0,
// child: Observer(
// builder: (_) => Row(
// mainAxisAlignment:
// MainAxisAlignment
// .spaceBetween,
// children: [
// Text(S.of(context).incoming),
// Checkbox(
// value: actionListStore
// .transactionFilterStore
// .displayIncoming,
// onChanged: (value) =>
// actionListStore
// .transactionFilterStore
// .toggleIncoming(),
// )
// ]))),
// PopupMenuItem(
// value: 1,
// child: Observer(
// builder: (_) => Row(
// mainAxisAlignment:
// MainAxisAlignment
// .spaceBetween,
// children: [
// Text(S.of(context).outgoing),
// Checkbox(
// value: actionListStore
// .transactionFilterStore
// .displayOutgoing,
// onChanged: (value) =>
// actionListStore
// .transactionFilterStore
// .toggleOutgoing(),
// )
// ]))),
PopupMenuItem(
value: 2,
child:
@ -156,17 +156,17 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
.spaceBetween,
children: [
Text('XMR.TO'),
Checkbox(
value: actionListStore
.tradeFilterStore
.displayXMRTO,
onChanged: (value) =>
actionListStore
.tradeFilterStore
.toggleDisplayExchange(
ExchangeProviderDescription
.xmrto),
)
// Checkbox(
// value: actionListStore
// .tradeFilterStore
// .displayXMRTO,
// onChanged: (value) =>
// actionListStore
// .tradeFilterStore
// .toggleDisplayExchange(
// ExchangeProviderDescription
// .xmrto),
// )
]))),
PopupMenuItem(
value: 4,
@ -177,17 +177,17 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
.spaceBetween,
children: [
Text('Change.NOW'),
Checkbox(
value: actionListStore
.tradeFilterStore
.displayChangeNow,
onChanged: (value) =>
actionListStore
.tradeFilterStore
.toggleDisplayExchange(
ExchangeProviderDescription
.changeNow),
)
// Checkbox(
// value: actionListStore
// .tradeFilterStore
// .displayChangeNow,
// onChanged: (value) =>
// actionListStore
// .tradeFilterStore
// .toggleDisplayExchange(
// ExchangeProviderDescription
// .changeNow),
// )
]))),
PopupMenuItem(
value: 5,
@ -198,17 +198,17 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
.spaceBetween,
children: [
Text('MorphToken'),
Checkbox(
value: actionListStore
.tradeFilterStore
.displayMorphToken,
onChanged: (value) =>
actionListStore
.tradeFilterStore
.toggleDisplayExchange(
ExchangeProviderDescription
.morphToken),
)
// Checkbox(
// value: actionListStore
// .tradeFilterStore
// .displayMorphToken,
// onChanged: (value) =>
// actionListStore
// .tradeFilterStore
// .toggleDisplayExchange(
// ExchangeProviderDescription
// .morphToken),
// )
])))
],
child: filterButton,
@ -225,10 +225,10 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
.add(Duration(days: 1)));
if (picked != null && picked.length == 2) {
actionListStore.transactionFilterStore
.changeStartDate(picked.first);
actionListStore.transactionFilterStore
.changeEndDate(picked.last);
// actionListStore.transactionFilterStore
// .changeStartDate(picked.first);
// actionListStore.transactionFilterStore
// .changeEndDate(picked.last);
}
}
},

View file

@ -1,21 +1,26 @@
import 'package:cake_wallet/src/domain/common/balance_display_mode.dart';
import 'package:cake_wallet/src/stores/action_list/action_list_store.dart';
import 'package:cake_wallet/src/stores/action_list/date_section_item.dart';
import 'package:cake_wallet/src/stores/action_list/trade_list_item.dart';
import 'package:cake_wallet/src/stores/action_list/transaction_list_item.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:intl/intl.dart';
import 'package:provider/provider.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/view_model/dashboard_view_model.dart';
import 'package:cake_wallet/src/domain/common/balance_display_mode.dart';
import 'package:cake_wallet/src/stores/action_list/action_list_store.dart';
import 'package:cake_wallet/src/stores/action_list/date_section_item.dart';
import 'package:cake_wallet/src/stores/action_list/trade_list_item.dart';
import 'package:cake_wallet/src/stores/action_list/transaction_list_item.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'date_section_raw.dart';
import 'trade_row.dart';
import 'transaction_raw.dart';
import 'button_header.dart';
class TradeHistoryPanel extends StatefulWidget {
TradeHistoryPanel({this.dashboardViewModel});
DashboardViewModel dashboardViewModel;
@override
TradeHistoryPanelState createState() => TradeHistoryPanelState();
}
@ -44,9 +49,9 @@ class TradeHistoryPanelState extends State<TradeHistoryPanel> {
@override
Widget build(BuildContext context) {
final actionListStore = Provider.of<ActionListStore>(context);
final settingsStore = Provider.of<SettingsStore>(context);
final transactionDateFormat = DateFormat("HH:mm");
// final actionListStore = Provider.of<ActionListStore>(context);
// final settingsStore = Provider.of<SettingsStore>(context);
final transactionDateFormat = DateFormat('HH:mm');
return Container(
height: MediaQuery.of(context).size.height,
@ -58,7 +63,9 @@ class TradeHistoryPanelState extends State<TradeHistoryPanel> {
duration: Duration(milliseconds: 1000),
curve: Curves.fastOutSlowIn,
child: ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20)),
child: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
@ -69,26 +76,29 @@ class TradeHistoryPanelState extends State<TradeHistoryPanel> {
Observer(
key: _listObserverKey,
builder: (_) {
final items = actionListStore.items == null
? <String>[]
: actionListStore.items;
// final items = actionListStore.items == null
// ? <String>[]
// : actionListStore.items;
final items = widget.dashboardViewModel.transactions;
final itemsCount = items.length + 1;
final symbol = settingsStore.fiatCurrency.toString();
double freeSpaceHeight = MediaQuery.of(context).size.height - 496;
final symbol =
'\$'; // settingsStore.fiatCurrency.toString();
var freeSpaceHeight =
MediaQuery.of(context).size.height - 496;
return SliverList(
key: _listKey,
delegate: SliverChildBuilderDelegate(
(context, index) {
delegate:
SliverChildBuilderDelegate((context, index) {
if (index == itemsCount - 1) {
freeSpaceHeight = freeSpaceHeight >= 0 ? freeSpaceHeight : 0;
freeSpaceHeight = freeSpaceHeight >= 0
? freeSpaceHeight
: 0;
return Container(
height: freeSpaceHeight,
width: MediaQuery.of(context).size.width,
color: Theme.of(context).backgroundColor,
);
color: Theme.of(context).backgroundColor);
}
final item = items[index];
@ -101,23 +111,28 @@ class TradeHistoryPanelState extends State<TradeHistoryPanel> {
if (item is TransactionListItem) {
freeSpaceHeight -= 62;
final transaction = item.transaction;
final savedDisplayMode = settingsStore.balanceDisplayMode;
final formattedAmount =
savedDisplayMode == BalanceDisplayMode.hiddenBalance
final savedDisplayMode =
BalanceDisplayMode.all;
//settingsStore
// .balanceDisplayMode;
final formattedAmount = savedDisplayMode ==
BalanceDisplayMode.hiddenBalance
? '---'
: transaction.amountFormatted();
final formattedFiatAmount =
savedDisplayMode == BalanceDisplayMode.hiddenBalance
savedDisplayMode ==
BalanceDisplayMode.hiddenBalance
? '---'
: transaction.fiatAmount(); // symbol ???
: transaction
.fiatAmount(); // symbol ???
return TransactionRow(
onTap: () => Navigator.of(context).pushNamed(
Routes.transactionDetails,
onTap: () => Navigator.of(context)
.pushNamed(Routes.transactionDetails,
arguments: transaction),
direction: transaction.direction,
formattedDate:
transactionDateFormat.format(transaction.date),
formattedDate: transactionDateFormat
.format(transaction.date),
formattedAmount: formattedAmount,
formattedFiatAmount: formattedFiatAmount,
isPending: transaction.isPending);
@ -126,37 +141,35 @@ class TradeHistoryPanelState extends State<TradeHistoryPanel> {
if (item is TradeListItem) {
freeSpaceHeight -= 62;
final trade = item.trade;
final savedDisplayMode = settingsStore.balanceDisplayMode;
final savedDisplayMode =
BalanceDisplayMode.all;
//settingsStore
// .balanceDisplayMode;
final formattedAmount = trade.amount != null
? savedDisplayMode == BalanceDisplayMode.hiddenBalance
? savedDisplayMode ==
BalanceDisplayMode.hiddenBalance
? '---'
: trade.amountFormatted()
: trade.amount;
return TradeRow(
onTap: () => Navigator.of(context)
.pushNamed(Routes.tradeDetails, arguments: trade),
.pushNamed(Routes.tradeDetails,
arguments: trade),
provider: trade.provider,
from: trade.from,
to: trade.to,
createdAtFormattedDate:
transactionDateFormat.format(trade.createdAt),
transactionDateFormat
.format(trade.createdAt),
formattedAmount: formattedAmount);
}
return Container(
color: Theme.of(context).backgroundColor
);
},
childCount: itemsCount
)
);
color: Theme.of(context).backgroundColor);
}, childCount: itemsCount));
})
],
),
)
),
);
)))); //,
}
}

View file

@ -1,21 +1,27 @@
import 'dart:async';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/domain/common/balance_display_mode.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/src/domain/common/balance_display_mode.dart';
import 'package:cake_wallet/src/stores/balance/balance_store.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/src/stores/sync/sync_store.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/common/sync_status.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/view_model/dashboard_view_model.dart';
class WalletCard extends StatefulWidget {
WalletCard({this.walletVM});
DashboardViewModel walletVM;
@override
WalletCardState createState() => WalletCardState();
}
@ -50,14 +56,12 @@ class WalletCardState extends State<WalletCard> {
cardWidth = screenWidth;
opacity = 1;
});
Timer(Duration(milliseconds: 500), () =>
setState(() => isDraw = true)
);
Timer(Duration(milliseconds: 500), () => setState(() => isDraw = true));
}
@override
Widget build(BuildContext context) {
final List<Color> colorsSync = [
final colorsSync = [
Theme.of(context).cardTheme.color,
Theme.of(context).hoverColor
];
@ -72,23 +76,21 @@ class WalletCardState extends State<WalletCard> {
height: cardHeight,
duration: Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
padding: EdgeInsets.only(
top: 1,
left: 1,
bottom: 1
),
padding: EdgeInsets.only(top: 1, left: 1, bottom: 1),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10)),
color: Theme.of(context).focusColor,
boxShadow: [
BoxShadow(
color: PaletteDark.darkNightBlue.withOpacity(0.5),
blurRadius: 8,
offset: Offset(5, 5))
]
),
]),
child: ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
child: Container(
width: cardWidth,
height: cardHeight,
@ -97,47 +99,39 @@ class WalletCardState extends State<WalletCard> {
onTap: () => setState(() => isFrontSide = !isFrontSide),
child: isFrontSide
? frontSide(colorsSync)
: backSide(colorsSync)
),
),
)
: backSide(colorsSync)),
),
)),
);
}
Widget frontSide(List<Color> colorsSync) {
final syncStore = Provider.of<SyncStore>(context);
final walletStore = Provider.of<WalletStore>(context);
// final syncStore = Provider.of<SyncStore>(context);
// final walletStore = Provider.of<WalletStore>(context);
final settingsStore = Provider.of<SettingsStore>(context);
final balanceStore = Provider.of<BalanceStore>(context);
final triangleButton = Image.asset('assets/images/triangle.png',
// final balanceStore = Provider.of<BalanceStore>(context);
final triangleButton = Image.asset(
'assets/images/triangle.png',
color: Theme.of(context).primaryTextTheme.title.color,
);
return Observer(
key: _syncingObserverKey,
builder: (_) {
final status = syncStore.status;
final status = widget.walletVM.status;
final statusText = status.title();
final progress = syncStore.status.progress();
final progress = status.progress();
final indicatorWidth = progress * cardWidth;
String shortAddress = walletStore.subaddress.address;
shortAddress = shortAddress.replaceRange(4, shortAddress.length - 4, '...');
final shortAddress = widget.walletVM.address
.replaceRange(4, widget.walletVM.address.length - 4, '...');
var descriptionText = '';
if (status is SyncingSyncStatus) {
descriptionText = S
.of(context)
.Blocks_remaining(
syncStore.status.toString());
descriptionText = S.of(context).Blocks_remaining(status.toString());
}
if (status is FailedSyncStatus) {
descriptionText = S
.of(context)
.please_try_to_connect_to_another_node;
descriptionText = S.of(context).please_try_to_connect_to_another_node;
}
return Container(
@ -149,13 +143,13 @@ class WalletCardState extends State<WalletCard> {
height: cardHeight,
width: indicatorWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10)),
gradient: LinearGradient(
colors: colorsSync,
begin: Alignment.topCenter,
end: Alignment.bottomCenter
)
),
end: Alignment.bottomCenter)),
),
progress != 1
? Positioned(
@ -166,10 +160,10 @@ class WalletCardState extends State<WalletCard> {
width: 1,
height: cardHeight,
color: Theme.of(context).focusColor,
)
)
))
: Offstage(),
isDraw ? Positioned(
isDraw
? Positioned(
left: 20,
right: 20,
top: 30,
@ -190,27 +184,30 @@ class WalletCardState extends State<WalletCard> {
child: Row(
children: <Widget>[
Text(
walletStore.name,
widget.walletVM.name,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.title.color
),
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
SizedBox(width: 10),
triangleButton
],
),
),
SizedBox(
height: 5,
),
SizedBox(height: 5),
if (widget.walletVM.subname?.isNotEmpty ?? false)
Text(
walletStore.account.label,
widget.walletVM.subname,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).primaryTextTheme.caption.color
),
color: Theme.of(context)
.primaryTextTheme
.caption
.color),
)
],
),
@ -219,15 +216,20 @@ class WalletCardState extends State<WalletCard> {
height: 32,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Theme.of(context).accentTextTheme.subtitle.backgroundColor,
borderRadius: BorderRadius.all(Radius.circular(16))
),
color: Theme.of(context)
.accentTextTheme
.subtitle
.backgroundColor,
borderRadius: BorderRadius.all(
Radius.circular(16))),
child: Text(
shortAddress,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).primaryTextTheme.caption.color
),
color: Theme.of(context)
.primaryTextTheme
.caption
.color),
),
)
],
@ -236,51 +238,60 @@ class WalletCardState extends State<WalletCard> {
? Observer(
key: _balanceObserverKey,
builder: (_) {
final balanceDisplayMode = settingsStore.balanceDisplayMode;
final symbol = settingsStore
.fiatCurrency
.toString();
final balanceDisplayMode =
BalanceDisplayMode.fullBalance;
// settingsStore.balanceDisplayMode;
final symbol =
settingsStore.fiatCurrency.toString();
var balance = '---';
var fiatBalance = '---';
if (balanceDisplayMode ==
BalanceDisplayMode.availableBalance) {
balance =
balanceStore.unlockedBalance ??
balance = widget.walletVM.balance
.unlockedBalance ??
'0.0';
fiatBalance =
'$symbol ${balanceStore.fiatUnlockedBalance}';
fiatBalance = '\$ 123.43';
// '$symbol ${balanceStore.fiatUnlockedBalance}';
}
if (balanceDisplayMode ==
BalanceDisplayMode.fullBalance) {
balance =
balanceStore.fullBalance ?? '0.0';
fiatBalance =
'$symbol ${balanceStore.fiatFullBalance}';
balance = widget.walletVM.balance
.totalBalance ??
'0.0';
fiatBalance = '\$ 123.43';
// '$symbol ${balanceStore.fiatFullBalance}';
}
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment:
CrossAxisAlignment.end,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
balanceDisplayMode.toString(),
style: TextStyle(
fontSize: 12,
color: Theme.of(context).primaryTextTheme.caption.color
),
color: Theme.of(context)
.primaryTextTheme
.caption
.color),
),
SizedBox(height: 5),
Text(
balance,
style: TextStyle(
fontSize: 28,
color: Theme.of(context).primaryTextTheme.title.color
),
color: Theme.of(context)
.primaryTextTheme
.title
.color),
)
],
),
@ -288,34 +299,41 @@ class WalletCardState extends State<WalletCard> {
fiatBalance,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryTextTheme.title.color
),
color: Theme.of(context)
.primaryTextTheme
.title
.color),
)
],
);
}
)
})
: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
statusText,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).primaryTextTheme.caption.color
),
color: Theme.of(context)
.primaryTextTheme
.caption
.color),
),
SizedBox(height: 5),
Text(
descriptionText,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryTextTheme.title.color
),
color: Theme.of(context)
.primaryTextTheme
.title
.color),
)
],
)
@ -323,8 +341,7 @@ class WalletCardState extends State<WalletCard> {
)
],
),
)
)
))
: Offstage()
],
),
@ -334,12 +351,12 @@ class WalletCardState extends State<WalletCard> {
}
Widget backSide(List<Color> colorsSync) {
final walletStore = Provider.of<WalletStore>(context);
final rightArrow = Image.asset('assets/images/right_arrow.png',
final rightArrow = Image.asset(
'assets/images/right_arrow.png',
color: Theme.of(context).primaryTextTheme.title.color,
);
double messageBoxHeight = 0;
double messageBoxWidth = cardWidth - 10;
var messageBoxHeight = 0.0;
var messageBoxWidth = cardWidth - 10;
return Observer(
key: _addressObserverKey,
@ -354,15 +371,16 @@ class WalletCardState extends State<WalletCard> {
Container(
width: cardWidth,
height: cardHeight,
padding: EdgeInsets.only(left: 20, right: 20, top: 30, bottom: 30),
padding:
EdgeInsets.only(left: 20, right: 20, top: 30, bottom: 30),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10)),
gradient: LinearGradient(
colors: colorsSync,
begin: Alignment.topCenter,
end: Alignment.bottomCenter
)
),
end: Alignment.bottomCenter)),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
@ -372,27 +390,31 @@ class WalletCardState extends State<WalletCard> {
child: Container(
height: 90,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S.current.card_address,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).primaryTextTheme.caption.color
),
color: Theme.of(context)
.primaryTextTheme
.caption
.color),
),
SizedBox(height: 10),
GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(
text: walletStore.subaddress.address));
_addressObserverKey.currentState.setState(() {
text: widget.walletVM.address));
_addressObserverKey.currentState
.setState(() {
messageBoxHeight = 20;
messageBoxWidth = cardWidth;
});
Timer(Duration(milliseconds: 1000), () {
try {
_addressObserverKey.currentState.setState(() {
_addressObserverKey.currentState
.setState(() {
messageBoxHeight = 0;
messageBoxWidth = cardWidth - 10;
});
@ -402,26 +424,29 @@ class WalletCardState extends State<WalletCard> {
});
},
child: Text(
walletStore.subaddress.address,
widget.walletVM.address,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryTextTheme.title.color
),
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
)
],
),
)
),
)),
SizedBox(width: 10),
Container(
width: 90,
height: 90,
child: QrImage(
data: walletStore.subaddress.address,
data: widget.walletVM.address,
backgroundColor: Colors.transparent,
foregroundColor: Theme.of(context).primaryTextTheme.caption.color
),
foregroundColor: Theme.of(context)
.primaryTextTheme
.caption
.color),
)
],
),
@ -431,11 +456,13 @@ class WalletCardState extends State<WalletCard> {
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(22)),
color: Theme.of(context).primaryTextTheme.overline.color
),
color: Theme.of(context)
.primaryTextTheme
.overline
.color),
child: InkWell(
onTap: () => Navigator.of(context,
rootNavigator: true)
onTap: () =>
Navigator.of(context, rootNavigator: true)
.pushNamed(Routes.receive),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -444,8 +471,10 @@ class WalletCardState extends State<WalletCard> {
S.of(context).accounts_subaddresses,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryTextTheme.title.color
),
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
rightArrow
],
@ -462,21 +491,17 @@ class WalletCardState extends State<WalletCard> {
duration: Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(10)),
color: Colors.green
),
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
),
style: TextStyle(fontSize: 10, color: Colors.white),
),
)
],
),
);
}
);
});
}
}

View file

@ -1,85 +1,54 @@
import 'package:cake_wallet/core/monero_wallet_list_service.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/core/wallet_credentials.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:mobx/mobx.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/stores/wallet_creation/wallet_creation_store.dart';
import 'package:cake_wallet/src/stores/wallet_creation/wallet_creation_state.dart';
import 'package:cake_wallet/src/domain/services/wallet_list_service.dart';
import 'package:cake_wallet/src/domain/services/wallet_service.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/src/widgets/seed_language_selector.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/view_model/wallet_creation_state.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
class NewWalletPage extends BasePage {
NewWalletPage(
{@required this.walletsService,
@required this.walletService,
@required this.sharedPreferences});
NewWalletPage(this._walletNewVM);
final WalletListService walletsService;
final WalletService walletService;
final SharedPreferences sharedPreferences;
final WalletNewVM _walletNewVM;
@override
String get title => S.current.new_wallet;
@override
Widget body(BuildContext context) => WalletNameForm();
Widget body(BuildContext context) => WalletNameForm(_walletNewVM);
}
class WalletNameForm extends StatefulWidget {
WalletNameForm(this._walletNewVM);
final WalletNewVM _walletNewVM;
@override
_WalletNameFormState createState() => _WalletNameFormState();
_WalletNameFormState createState() => _WalletNameFormState(_walletNewVM);
}
class _WalletNameFormState extends State<WalletNameForm> {
_WalletNameFormState(this._walletNewVM);
static const aspectRatioImage = 1.22;
final _formKey = GlobalKey<FormState>();
final nameController = TextEditingController();
final walletNameImage = Image.asset('assets/images/wallet_name.png');
final _formKey = GlobalKey<FormState>();
final _languageSelectorKey = GlobalKey<SeedLanguageSelectorState>();
ReactionDisposer _stateReaction;
final WalletNewVM _walletNewVM;
@override
void dispose() {
nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final walletCreationStore = Provider.of<WalletCreationStore>(context);
final walletCreationService = Provider.of<WalletCreationService>(context);
// FIXME: Does seed language store is really needed ???
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
final seedLocales = [
S.current.seed_language_english,
S.current.seed_language_chinese,
S.current.seed_language_dutch,
S.current.seed_language_german,
S.current.seed_language_japanese,
S.current.seed_language_portuguese,
S.current.seed_language_russian,
S.current.seed_language_spanish
];
nameController.addListener(() =>
walletCreationStore.setDisabledStatus(!nameController.text.isNotEmpty));
reaction((_) => walletCreationStore.state, (WalletCreationState state) {
void initState() {
_stateReaction ??=
reaction((_) => _walletNewVM.state, (WalletCreationState state) {
if (state is WalletCreatedSuccessfully) {
Navigator.of(context).popUntil((route) => route.isFirst);
}
@ -98,7 +67,11 @@ class _WalletNameFormState extends State<WalletNameForm> {
});
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 24),
child: ScrollableWithBottomSection(
@ -116,12 +89,13 @@ class _WalletNameFormState extends State<WalletNameForm> {
child: Form(
key: _formKey,
child: TextFormField(
onChanged: (value) => _walletNewVM.name = value,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20.0,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.title.color),
controller: nameController,
color:
Theme.of(context).primaryTextTheme.title.color),
decoration: InputDecoration(
hintStyle: TextStyle(
fontSize: 16.0,
@ -138,12 +112,9 @@ class _WalletNameFormState extends State<WalletNameForm> {
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
validator: (value) {
walletCreationStore.validateWalletName(value);
return walletCreationStore.errorMessage;
},
)),
validator: WalletNameValidator())),
),
if (_walletNewVM.hasLanguageSelector) ...[
Padding(
padding: EdgeInsets.only(top: 40),
child: Text(
@ -157,53 +128,37 @@ class _WalletNameFormState extends State<WalletNameForm> {
),
Padding(
padding: EdgeInsets.only(top: 24),
child: Observer(
builder: (_) => SelectButton(
image: null,
text: seedLocales[seedLanguages
.indexOf(seedLanguageStore.selectedSeedLanguage)],
color: Theme.of(context)
.accentTextTheme
.title
.backgroundColor,
textColor: Theme.of(context).primaryTextTheme.title.color,
onTap: () async => await showDialog(
context: context,
builder: (BuildContext context) =>
SeedLanguagePicker()))),
child: SeedLanguageSelector(
key: _languageSelectorKey,
initialSelected: defaultSeedLanguage),
)
]
]),
bottomSectionPadding:
EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Observer(
builder: (context) {
return LoadingPrimaryButton(
onPressed: () => _confirmForm(walletCreationService,
seedLanguageStore.selectedSeedLanguage),
onPressed: _confirmForm,
text: S.of(context).continue_text,
color: Colors.green,
textColor: Colors.white,
isLoading: walletCreationStore.state is WalletIsCreating,
isDisabled: walletCreationStore.isDisabledStatus,
isLoading: _walletNewVM.state is WalletCreatedSuccessfully,
isDisabled: _walletNewVM.name.isEmpty,
);
},
)),
);
}
void _confirmForm(
WalletCreationService walletCreationService, String language) {
void _confirmForm() {
if (!_formKey.currentState.validate()) {
return;
}
WalletCredentials credentials;
if (walletCreationService.type == WalletType.monero) {
credentials = MoneroNewWalletCredentials(
name: nameController.text, language: language);
}
walletCreationService.create(credentials);
_walletNewVM.create(
options: _walletNewVM.hasLanguageSelector
? _languageSelectorKey.currentState.selected
: null);
}
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -8,14 +9,23 @@ import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
import 'package:cake_wallet/routes.dart';
class NewWalletTypePage extends BasePage {
NewWalletTypePage({this.onTypeSelected});
final void Function(BuildContext, WalletType) onTypeSelected;
@override
String get title => S.current.new_wallet;
@override
Widget body(BuildContext context) => WalletTypeForm();
Widget body(BuildContext context) =>
WalletTypeForm(onTypeSelected: onTypeSelected);
}
class WalletTypeForm extends StatefulWidget {
WalletTypeForm({this.onTypeSelected});
final void Function(BuildContext, WalletType) onTypeSelected;
@override
WalletTypeFormState createState() => WalletTypeFormState();
}
@ -23,35 +33,19 @@ class WalletTypeForm extends StatefulWidget {
class WalletTypeFormState extends State<WalletTypeForm> {
static const aspectRatioImage = 1.22;
final moneroIcon = Image.asset('assets/images/monero.png', height: 24, width: 24);
final bitcoinIcon = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final moneroIcon =
Image.asset('assets/images/monero.png', height: 24, width: 24);
final bitcoinIcon =
Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
bool isDisabledButton;
bool isMoneroSelected;
bool isBitcoinSelected;
Color moneroBackgroundColor = Colors.transparent;
Color moneroTextColor = Colors.transparent;
Color bitcoinBackgroundColor = Colors.transparent;
Color bitcoinTextColor = Colors.transparent;
WalletType selected;
List<WalletType> types;
@override
void initState() {
isDisabledButton = true;
isMoneroSelected = false;
isBitcoinSelected = false;
types = [WalletType.bitcoin, WalletType.monero];
super.initState();
WidgetsBinding.instance.addPostFrameCallback(afterLayout);
}
void afterLayout(dynamic _) {
moneroBackgroundColor = Theme.of(context).accentTextTheme.title.backgroundColor;
moneroTextColor = Theme.of(context).primaryTextTheme.title.color;
bitcoinBackgroundColor = Theme.of(context).accentTextTheme.title.backgroundColor;
bitcoinTextColor = Theme.of(context).primaryTextTheme.title.color;
setState(() {});
}
@override
@ -77,69 +71,50 @@ class WalletTypeFormState extends State<WalletTypeForm> {
style: TextStyle(
fontSize: 16,
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),
child: SelectButton(
image: bitcoinIcon,
text: 'Bitcoin',
color: bitcoinBackgroundColor,
textColor: bitcoinTextColor,
onTap: () {}),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: SelectButton(
image: moneroIcon,
text: 'Monero',
color: moneroBackgroundColor,
textColor: moneroTextColor,
onTap: () => onSelectMoneroButton(context)),
)
image: _iconFor(type),
text: walletTypeToString(type),
color: _backgroundColorFor(selected == type),
textColor: _textColorFor(selected == type),
onTap: () => setState(() => selected = type)),
))
],
),
bottomSectionPadding: EdgeInsets.only(
left: 24,
right: 24,
bottom: 24
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: PrimaryButton(
onPressed: () => Navigator.of(context).pushNamed(Routes.newWallet),
onPressed: () => widget.onTypeSelected(context, selected),
text: S.of(context).seed_language_next,
color: Colors.green,
textColor: Colors.white,
isDisabled: isDisabledButton,
isDisabled: selected == null,
),
),
);
}
void onSelectMoneroButton(BuildContext context) {
isMoneroSelected = true;
isBitcoinSelected = false;
isDisabledButton = false;
// FIXME: Move color selection inside ui element; add isSelected to buttons.
moneroBackgroundColor = Theme.of(context).accentTextTheme.title.decorationColor;
moneroTextColor = Theme.of(context).primaryTextTheme.title.backgroundColor;
bitcoinBackgroundColor = Theme.of(context).accentTextTheme.title.backgroundColor;
bitcoinTextColor = Theme.of(context).primaryTextTheme.title.color;
Color _backgroundColorFor(bool isSelected) => isSelected
? Theme.of(context).accentTextTheme.title.decorationColor
: Theme.of(context).accentTextTheme.title.backgroundColor;
setState(() {});
}
Color _textColorFor(bool isSelected) => isSelected
? Theme.of(context).primaryTextTheme.title.backgroundColor
: Theme.of(context).primaryTextTheme.title.color;
void onSelectBitcoinButton(BuildContext context) {
isMoneroSelected = false;
isBitcoinSelected = true;
isDisabledButton = false;
moneroBackgroundColor = Theme.of(context).accentTextTheme.title.backgroundColor;
moneroTextColor = Theme.of(context).primaryTextTheme.title.color;
bitcoinBackgroundColor = moneroBackgroundColor = Theme.of(context).accentTextTheme.title.decorationColor;
bitcoinTextColor = Theme.of(context).primaryTextTheme.title.backgroundColor;
setState(() {});
Image _iconFor(WalletType type) {
switch (type) {
case WalletType.monero:
return moneroIcon;
case WalletType.bitcoin:
return bitcoinIcon;
default:
return null;
}
}
}

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
@ -16,158 +18,105 @@ import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/themes.dart';
import 'package:cake_wallet/theme_changer.dart';
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
import 'package:cake_wallet/view_model/address_list/account_list_header.dart';
import 'package:cake_wallet/view_model/address_list/address_list_header.dart';
import 'package:cake_wallet/view_model/address_list/address_list_item.dart';
import 'package:cake_wallet/view_model/address_list/address_list_view_model.dart';
class ReceivePage extends StatefulWidget {
@override
ReceivePageState createState() => ReceivePageState();
class ReceivePage extends BasePage {
ReceivePage({this.addressListViewModel})
: amountController = TextEditingController(),
_formKey = GlobalKey<FormState>() {
amountController.addListener(() => addressListViewModel.amount =
_formKey.currentState.validate() ? amountController.text : '');
}
class ReceivePageState extends State<ReceivePage> {
final amountController = TextEditingController();
final _formKey = GlobalKey<FormState>();
final _backArrowImage = Image.asset('assets/images/back_arrow.png');
final _backArrowImageDarkTheme =
Image.asset('assets/images/back_arrow_dark_theme.png');
final AddressListViewModel addressListViewModel;
final TextEditingController amountController;
final GlobalKey<FormState> _formKey;
@override
void dispose() {
amountController.dispose();
super.dispose();
}
Color get backgroundLightColor => Colors.transparent;
@override
Widget build(BuildContext context) {
final walletStore = Provider.of<WalletStore>(context);
final subaddressListStore = Provider.of<SubaddressListStore>(context);
final accountListStore = Provider.of<AccountListStore>(context);
Color get backgroundDarkColor => Colors.transparent;
final shareImage = Image.asset('assets/images/share.png',
color: Theme.of(context).primaryTextTheme.title.color,
);
final copyImage = Image.asset('assets/images/copy_content.png',
color: Theme.of(context).primaryTextTheme.title.color,
);
final currentColor = Theme.of(context).accentTextTheme.subtitle.decorationColor;
final notCurrentColor = Theme.of(context).backgroundColor;
final currentTextColor = Colors.blue;
final notCurrentTextColor = Theme.of(context).primaryTextTheme.caption.color;
final _themeChanger = Provider.of<ThemeChanger>(context);
Image _backButton;
if (_themeChanger.getTheme() == Themes.darkTheme) {
_backButton = _backArrowImageDarkTheme;
} else {
_backButton = _backArrowImage;
}
amountController.addListener(() {
if (_formKey.currentState.validate()) {
walletStore.onChangedAmountValue(amountController.text);
} else {
walletStore.onChangedAmountValue('');
}
});
return Scaffold(
resizeToAvoidBottomPadding: false,
body: Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.only(top: 24),
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
gradient: LinearGradient(colors: [
Theme.of(context).scaffoldBackgroundColor,
Theme.of(context).primaryColor
],
begin: Alignment.centerLeft,
end: Alignment.centerRight
)
),
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(
top: 10,
bottom: 20,
left: 5,
right: 10
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 44,
width: 44,
child: ButtonTheme(
minWidth: double.minPositive,
child: FlatButton(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
padding: EdgeInsets.all(0),
onPressed: () => Navigator.of(context).pop(),
child: _backButton),
),
),
Text(
], 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),
),
SizedBox(
height: 44.0,
width: 44.0,
);
@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, walletStore.subaddress.address, 'text/plain'),
onPressed: () => Share.text(S.current.share_address,
addressListViewModel.address.address, 'text/plain'),
child: shareImage),
),
)
],
),
),
Expanded(
child: SingleChildScrollView(
);
}
@override
Widget build(BuildContext context) {
return super.build(context);
}
@override
Widget body(BuildContext context) {
final copyImage = Image.asset('assets/images/copy_content.png',
color: Theme.of(context).primaryTextTheme.title.color);
return SingleChildScrollView(
child: Column(
children: <Widget>[
Observer(builder: (_) {
return Row(
children: <Widget>[
Spacer(
flex: 1,
),
Flexible(
flex: 2,
SizedBox(height: 25),
Row(children: <Widget>[
Spacer(flex: 4),
Observer(
builder: (_) => Flexible(
flex: 6,
child: Center(
child: AspectRatio(
aspectRatio: 1.0,
child: QrImage(
data: walletStore.subaddress.address +
walletStore.amountValue,
data: addressListViewModel.uri.toString(),
backgroundColor: Colors.transparent,
foregroundColor: Theme.of(context).primaryTextTheme.display4.color,
),
),
)),
Spacer(
flex: 1,
)
],
);
}),
foregroundColor: Theme.of(context)
.primaryTextTheme
.display4
.color,
))))),
Spacer(flex: 4)
]),
Padding(
padding: EdgeInsets.all(24),
padding: EdgeInsets.fromLTRB(24, 40, 24, 0),
child: Row(
children: <Widget>[
Expanded(
@ -175,33 +124,39 @@ class ReceivePageState extends State<ReceivePage> {
key: _formKey,
child: BaseTextFormField(
controller: amountController,
keyboardType: TextInputType.numberWithOptions(decimal: true),
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;
},
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))))
],
),
),
Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
padding: EdgeInsets.only(left: 24, right: 24, bottom: 40, top: 40),
child: Builder(
builder: (context) => Observer(
builder: (context) => GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(
text: walletStore.subaddress.address));
text: addressListViewModel.address.address));
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
S.of(context).copied_to_clipboard,
@ -215,22 +170,27 @@ class ReceivePageState extends State<ReceivePage> {
height: 48,
padding: EdgeInsets.only(left: 24, right: 24),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(24)),
color: Theme.of(context).primaryTextTheme.overline.color
),
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,
addressListViewModel.address.address,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 14,
fontSize: 18,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.title.color
),
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
),
Padding(
@ -240,123 +200,74 @@ class ReceivePageState extends State<ReceivePage> {
],
),
),
)
)
),
))),
),
Observer(
builder: (_) => ListView.separated(
separatorBuilder: (context, index) => Divider(
height: 1,
color: Theme.of(context).dividerColor,
),
separatorBuilder: (context, _) =>
Divider(height: 1, color: Theme.of(context).dividerColor),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: subaddressListStore.subaddresses.length + 2,
itemCount: addressListViewModel.items.length,
itemBuilder: (context, index) {
final item = addressListViewModel.items[index];
Widget cell = Container();
if (index == 0) {
return ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24)
),
child: HeaderTile(
if (item is AccountListHeader) {
cell = HeaderTile(
onTap: () async {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AccountListPage(accountListStore: accountListStore);
}
);
// return AccountListPage(
// accountListStore:
// accountListStore);
});
},
title: walletStore.account.label,
title: addressListViewModel.accountLabel,
icon: Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context).primaryTextTheme.title.color,
)
),
);
color:
Theme.of(context).primaryTextTheme.title.color,
));
}
if (index == 1) {
return HeaderTile(
if (item is AddressListHeader) {
cell = HeaderTile(
onTap: () => Navigator.of(context)
.pushNamed(Routes.newSubaddress),
title: S.of(context).subaddresses,
icon: Icon(
Icons.add,
size: 20,
color: Theme.of(context).primaryTextTheme.title.color,
)
);
color:
Theme.of(context).primaryTextTheme.title.color,
));
}
index -= 2;
return Observer(
builder: (_) {
final subaddress = subaddressListStore.subaddresses[index];
final isCurrent =
walletStore.subaddress.address == subaddress.address;
final label = subaddress.label.isNotEmpty
? subaddress.label
: subaddress.address;
final content = InkWell(
onTap: () => walletStore.setSubaddress(subaddress),
child: Container(
color: isCurrent ? currentColor : notCurrentColor,
padding: EdgeInsets.only(
left: 24,
right: 24,
top: 28,
bottom: 28
),
child: Text(
label,
style: TextStyle(
fontSize: subaddress.label.isNotEmpty
? 18 : 10,
fontWeight: FontWeight.bold,
color: isCurrent
? currentTextColor
: notCurrentTextColor,
),
),
),
);
return isCurrent
? content
: Slidable(
key: Key(subaddress.address),
actionPane: SlidableDrawerActionPane(),
child: content,
secondaryActions: <Widget>[
IconSlideAction(
caption: S.of(context).edit,
color: Theme.of(context).primaryTextTheme.overline.color,
icon: Icons.edit,
onTap: () => Navigator.of(context)
.pushNamed(Routes.newSubaddress, arguments: subaddress),
)
]
);
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 index != 0
? cell
: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24)),
child: cell,
);
}
)
),
})),
],
),
)
)
],
)
),
);
}
}

View 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())
]);
}
}

View file

@ -1,26 +1,35 @@
import 'package:mobx/mobx.dart';
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_store.dart';
import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_state.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/view_model/wallet_creation_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart';
class RestoreWalletFromSeedDetailsPage extends BasePage {
RestoreWalletFromSeedDetailsPage(
{@required this.walletRestorationFromSeedVM});
final WalletRestorationFromSeedVM walletRestorationFromSeedVM;
@override
String get title => S.current.restore_wallet_restore_description;
@override
Widget body(BuildContext context) => RestoreFromSeedDetailsForm();
Widget body(BuildContext context) => RestoreFromSeedDetailsForm(
walletRestorationFromSeedVM: walletRestorationFromSeedVM);
}
class RestoreFromSeedDetailsForm extends StatefulWidget {
RestoreFromSeedDetailsForm({@required this.walletRestorationFromSeedVM});
final WalletRestorationFromSeedVM walletRestorationFromSeedVM;
@override
_RestoreFromSeedDetailsFormState createState() =>
_RestoreFromSeedDetailsFormState();
@ -31,31 +40,17 @@ class _RestoreFromSeedDetailsFormState
final _formKey = GlobalKey<FormState>();
final _blockchainHeightKey = GlobalKey<BlockchainHeightState>();
final _nameController = TextEditingController();
ReactionDisposer _stateReaction;
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final walletRestorationStore = Provider.of<WalletRestorationStore>(context);
_nameController.addListener(() {
if (_nameController.text.isNotEmpty) {
walletRestorationStore.setDisabledState(false);
} else {
walletRestorationStore.setDisabledState(true);
}
});
reaction((_) => walletRestorationStore.state, (WalletRestorationState state) {
if (state is WalletRestoredSuccessfully) {
void initState() {
_stateReaction = reaction((_) => widget.walletRestorationFromSeedVM.state,
(WalletCreationState state) {
if (state is WalletCreatedSuccessfully) {
Navigator.of(context).popUntil((route) => route.isFirst);
}
if (state is WalletRestorationFailure) {
if (state is WalletCreationFailure) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog<void>(
context: context,
@ -64,21 +59,33 @@ class _RestoreFromSeedDetailsFormState
alertTitle: S.current.restore_title_from_seed,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()
);
buttonAction: () => Navigator.of(context).pop());
});
});
}
});
_nameController.addListener(
() => widget.walletRestorationFromSeedVM.name = _nameController.text);
super.initState();
}
@override
void dispose() {
_nameController.dispose();
_stateReaction.reaction.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0),
content: Form(
key: _formKey,
child: Column(
children: <Widget>[
child: Column(children: <Widget>[
Row(
children: <Widget>[
Flexible(
@ -87,14 +94,15 @@ class _RestoreFromSeedDetailsFormState
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.title.color
),
color: Theme.of(context).primaryTextTheme.title.color),
controller: _nameController,
decoration: InputDecoration(
hintStyle: TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
fontSize: 16
),
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16),
hintText: S.of(context).restore_wallet_name,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
@ -104,16 +112,18 @@ class _RestoreFromSeedDetailsFormState
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
validator: (value) {
walletRestorationStore
.validateWalletName(value);
return walletRestorationStore.errorMessage;
},
validator: WalletNameValidator(),
),
))
],
),
BlockchainHeightWidget(key: _blockchainHeightKey),
if (widget.walletRestorationFromSeedVM.hasRestorationHeight)
BlockchainHeightWidget(
key: _blockchainHeightKey,
onHeightChange: (height) {
widget.walletRestorationFromSeedVM.height = height;
print(height);
}),
]),
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
@ -121,16 +131,15 @@ class _RestoreFromSeedDetailsFormState
return LoadingPrimaryButton(
onPressed: () {
if (_formKey.currentState.validate()) {
walletRestorationStore.restoreFromSeed(
name: _nameController.text,
restoreHeight: _blockchainHeightKey.currentState.height);
widget.walletRestorationFromSeedVM.create();
}
},
isLoading: walletRestorationStore.state is WalletIsRestoring,
isLoading:
widget.walletRestorationFromSeedVM.state is WalletCreating,
text: S.of(context).restore_recover,
color: Colors.green,
textColor: Colors.white,
isDisabled: walletRestorationStore.disabledState,
isDisabled: _nameController.text.isNotEmpty,
);
}),
),

View file

@ -1,27 +1,19 @@
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/services/wallet_list_service.dart';
import 'package:cake_wallet/src/domain/services/wallet_service.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_store.dart';
import 'package:cake_wallet/src/widgets/seed_widget.dart';
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/core/seed_validator.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/core/mnemonic_length.dart';
class RestoreWalletFromSeedPage extends BasePage {
RestoreWalletFromSeedPage(
{@required this.walletsService,
@required this.walletService,
@required this.sharedPreferences});
RestoreWalletFromSeedPage({@required this.type, @required this.language});
final WalletListService walletsService;
final WalletService walletService;
final SharedPreferences sharedPreferences;
final WalletType type;
final String language;
final formKey = GlobalKey<_RestoreFromSeedFormState>();
@override
@ -34,11 +26,14 @@ class RestoreWalletFromSeedPage extends BasePage {
Color get backgroundDarkColor => PaletteDark.lightNightBlue;
@override
Widget body(BuildContext context) => RestoreFromSeedForm(key: formKey);
Widget body(BuildContext context) =>
RestoreFromSeedForm(key: formKey, type: type, language: language);
}
class RestoreFromSeedForm extends StatefulWidget {
RestoreFromSeedForm({Key key}) : super(key: key);
RestoreFromSeedForm({Key key, this.type, this.language}) : super(key: key);
final WalletType type;
final String language;
@override
_RestoreFromSeedFormState createState() => _RestoreFromSeedFormState();
@ -46,13 +41,11 @@ class RestoreFromSeedForm extends StatefulWidget {
class _RestoreFromSeedFormState extends State<RestoreFromSeedForm> {
final _seedKey = GlobalKey<SeedWidgetState>();
void clear() => _seedKey.currentState.clear();
String mnemonic() => _seedKey.currentState.items.map((e) => e.text).join(' ');
@override
Widget build(BuildContext context) {
final walletRestorationStore = Provider.of<WalletRestorationStore>(context);
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
return GestureDetector(
onTap: () =>
SystemChannels.textInput.invokeMethod<void>('TextInput.hide'),
@ -60,11 +53,13 @@ class _RestoreFromSeedFormState extends State<RestoreFromSeedForm> {
color: Theme.of(context).backgroundColor,
child: SeedWidget(
key: _seedKey,
onMnemoticChange: (seed) => walletRestorationStore.setSeed(seed),
maxLength: mnemonicLength(widget.type),
onMnemonicChange: (seed) => null,
onFinish: () => Navigator.of(context).pushNamed(
Routes.restoreWalletFromSeedDetails,
arguments: _seedKey.currentState.items),
seedLanguage: seedLanguageStore.selectedSeedLanguage,
arguments: [widget.type, widget.language, mnemonic()]),
validator:
SeedValidator(type: widget.type, language: widget.language),
),
),
);

View file

@ -1,19 +1,21 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/restore/widgets/restore_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
import 'package:provider/provider.dart';
class RestoreWalletOptionsPage extends BasePage {
RestoreWalletOptionsPage({@required this.type});
RestoreWalletOptionsPage(
{@required this.type,
@required this.onRestoreFromSeed,
@required this.onRestoreFromKeys});
static const _aspectRatioImage = 2.086;
final WalletType type;
final Function(BuildContext context) onRestoreFromSeed;
final Function(BuildContext context) onRestoreFromKeys;
@override
String get title => S.current.restore_seed_keys_restore;
@ -23,8 +25,6 @@ class RestoreWalletOptionsPage extends BasePage {
@override
Widget body(BuildContext context) {
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
return Container(
width: double.infinity,
height: double.infinity,
@ -33,28 +33,56 @@ class RestoreWalletOptionsPage extends BasePage {
child: Column(
children: <Widget>[
RestoreButton(
onPressed: () {
seedLanguageStore
.setCurrentRoute(Routes.restoreWalletFromSeed);
Navigator.pushNamed(context, Routes.seedLanguage);
},
onPressed: () => onRestoreFromSeed(context),
image: imageSeed,
title: S.of(context).restore_title_from_seed,
description: S.of(context).restore_description_from_seed),
description: _fromSeedDescription(context)),
Padding(
padding: EdgeInsets.only(top: 24),
child: RestoreButton(
onPressed: () {
seedLanguageStore
.setCurrentRoute(Routes.restoreWalletFromKeys);
Navigator.pushNamed(context, Routes.seedLanguage);
},
onPressed: () => onRestoreFromKeys(context),
image: imageKeys,
title: S.of(context).restore_title_from_keys,
description: S.of(context).restore_description_from_keys),
title: _fromKeyTitle(context),
description: _fromKeyDescription(context)),
)
],
),
));
}
String _fromSeedDescription(BuildContext context) {
switch (type) {
case WalletType.monero:
return S.of(context).restore_description_from_seed;
case WalletType.bitcoin:
// TODO: Add transaction for bitcoin description.
return 'Restore your wallet from 12 word combination code';
default:
return '';
}
}
String _fromKeyDescription(BuildContext context) {
switch (type) {
case WalletType.monero:
return S.of(context).restore_description_from_keys;
case WalletType.bitcoin:
// TODO: Add transaction for bitcoin description.
return 'Restore your wallet from generated WIF string from your private keys';
default:
return '';
}
}
String _fromKeyTitle(BuildContext context) {
switch (type) {
case WalletType.monero:
return S.of(context).restore_title_from_keys;
case WalletType.bitcoin:
// TODO: Add transaction for bitcoin description.
return 'Restore from WIF';
default:
return '';
}
}
}

View file

@ -1,10 +1,15 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:hive/hive.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/stores/authentication/authentication_store.dart';
import 'package:cake_wallet/store/authentication_store.dart';
//import 'package:cake_wallet/src/stores/authentication/authentication_store.dart';
import 'package:cake_wallet/src/stores/price/price_store.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
@ -21,7 +26,10 @@ import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/welcome/create_welcome_page.dart';
class Root extends StatefulWidget {
Root({Key key}) : super(key: key);
Root({Key key, this.authenticationStore, this.appStore}) : super(key: key);
final AuthenticationStore authenticationStore;
final AppStore appStore;
@override
RootState createState() => RootState();
@ -30,7 +38,6 @@ class Root extends StatefulWidget {
class RootState extends State<Root> with WidgetsBindingObserver {
bool _isInactive;
bool _postFrameCallback;
AuthenticationStore _authenticationStore;
@override
void initState() {
@ -48,12 +55,12 @@ class RootState extends State<Root> with WidgetsBindingObserver {
return;
}
if (!_isInactive &&
_authenticationStore.state ==
AuthenticationState.authenticated ||
_authenticationStore.state == AuthenticationState.active) {
setState(() => _isInactive = true);
}
// if (!_isInactive &&
// widget.authenticationStore.state ==
// AuthenticationState.authenticated ||
// widget.authenticationStore.state == AuthenticationState.active) {
// setState(() => _isInactive = true);
// }
break;
default:
@ -63,18 +70,18 @@ class RootState extends State<Root> with WidgetsBindingObserver {
@override
Widget build(BuildContext context) {
_authenticationStore = Provider.of<AuthenticationStore>(context);
final sharedPreferences = Provider.of<SharedPreferences>(context);
final walletListService = Provider.of<WalletListService>(context);
final walletService = Provider.of<WalletService>(context);
final userService = Provider.of<UserService>(context);
final priceStore = Provider.of<PriceStore>(context);
final authenticationStore = Provider.of<AuthenticationStore>(context);
final trades = Provider.of<Box<Trade>>(context);
final transactionDescriptions =
Provider.of<Box<TransactionDescription>>(context);
final walletStore = Provider.of<WalletStore>(context);
final settingsStore = Provider.of<SettingsStore>(context);
// _authenticationStore = Provider.of<AuthenticationStore>(context);
// final sharedPreferences = Provider.of<SharedPreferences>(context);
// final walletListService = Provider.of<WalletListService>(context);
// final walletService = Provider.of<WalletService>(context);
// final userService = Provider.of<UserService>(context);
// final priceStore = Provider.of<PriceStore>(context);
// final authenticationStore = Provider.of<AuthenticationStore>(context);
// final trades = Provider.of<Box<Trade>>(context);
// final transactionDescriptions =
// Provider.of<Box<TransactionDescription>>(context);
// final walletStore = Provider.of<WalletStore>(context);
// final settingsStore = Provider.of<SettingsStore>(context);
if (_isInactive && !_postFrameCallback) {
_postFrameCallback = true;
@ -96,38 +103,51 @@ class RootState extends State<Root> with WidgetsBindingObserver {
}
return Observer(builder: (_) {
final state = _authenticationStore.state;
final state = widget.authenticationStore.state;
print(state);
if (state == AuthenticationState.denied) {
return createWelcomePage();
}
if (state == AuthenticationState.readyToLogin) {
return createLoginPage(
sharedPreferences: sharedPreferences,
userService: userService,
walletService: walletService,
walletListService: walletListService,
authenticationStore: authenticationStore);
if (state == AuthenticationState.installed) {
return getIt.get<AuthPage>();
}
if (state == AuthenticationState.authenticated ||
state == AuthenticationState.restored) {
return createDashboardPage(
walletService: walletService,
priceStore: priceStore,
trades: trades,
transactionDescriptions: transactionDescriptions,
walletStore: walletStore,
settingsStore: settingsStore);
if (state == AuthenticationState.allowed) {
return getIt.get<DashboardPage>();
}
if (state == AuthenticationState.created) {
return createSeedPage(
settingsStore: settingsStore,
walletService: walletService,
callback: () =>
_authenticationStore.state = AuthenticationState.authenticated);
}
// if (state == AuthenticationState.denied) {
// return createWelcomePage();
// }
// if (state == AuthenticationState.readyToLogin) {
// return createLoginPage(
// sharedPreferences: sharedPreferences,
// userService: userService,
// walletService: walletService,
// walletListService: walletListService,
// authenticationStore: authenticationStore);
// }
// if (state == AuthenticationState.authenticated ||
// state == AuthenticationState.restored) {
// return createDashboardPage(
// walletService: walletService,
// priceStore: priceStore,
// trades: trades,
// transactionDescriptions: transactionDescriptions,
// walletStore: walletStore,
// settingsStore: settingsStore);
// }
// if (state == AuthenticationState.created) {
// return createSeedPage(
// settingsStore: settingsStore,
// walletService: walletService,
// callback: () =>
// _authenticationStore.state = AuthenticationState.authenticated);
// }
return Container(color: Colors.white);
});

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/src/widgets/seed_language_selector.dart';
import 'package:provider/provider.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
@ -11,79 +12,68 @@ import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
import 'package:cake_wallet/src/screens/seed_language/widgets/seed_language_picker.dart';
class SeedLanguage extends BasePage {
SeedLanguage({this.onConfirm});
final Function(BuildContext, String) onConfirm;
@override
Widget body(BuildContext context) => SeedLanguageForm();
Widget body(BuildContext context) => SeedLanguageForm(onConfirm: onConfirm);
}
class SeedLanguageForm extends StatefulWidget {
SeedLanguageForm({this.onConfirm});
final Function(BuildContext, String) onConfirm;
@override
SeedLanguageFormState createState() => SeedLanguageFormState();
}
class SeedLanguageFormState extends State<SeedLanguageForm> {
static const aspectRatioImage = 1.22;
final walletNameImage = Image.asset('assets/images/wallet_name.png');
final _languageSelectorKey = GlobalKey<SeedLanguageSelectorState>();
@override
Widget build(BuildContext context) {
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
final List<String> seedLocales = [
S.current.seed_language_english,
S.current.seed_language_chinese,
S.current.seed_language_dutch,
S.current.seed_language_german,
S.current.seed_language_japanese,
S.current.seed_language_portuguese,
S.current.seed_language_russian,
S.current.seed_language_spanish
];
return Container(
padding: EdgeInsets.only(top: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
content: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
content:
Column(crossAxisAlignment: CrossAxisAlignment.center, children: [
Padding(
padding: EdgeInsets.only(left: 12, right: 12),
child: AspectRatio(
aspectRatio: aspectRatioImage,
child: FittedBox(child: walletNameImage, fit: BoxFit.fill)),
),
Padding(padding: EdgeInsets.only(top: 40),
Padding(
padding: EdgeInsets.only(top: 40),
child: Text(
S.of(context).seed_language_choose,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.title.color
color: Theme.of(context).primaryTextTheme.title.color),
),
),
),
Padding(padding: EdgeInsets.only(top: 24),
child: Observer(
builder: (_) => SelectButton(
image: null,
text: seedLocales[seedLanguages.indexOf(seedLanguageStore.selectedSeedLanguage)],
color: Theme.of(context).accentTextTheme.title.backgroundColor,
textColor: Theme.of(context).primaryTextTheme.title.color,
onTap: () async => await showDialog(
context: context,
builder: (BuildContext context) => SeedLanguagePicker()
)
)
),
Padding(
padding: EdgeInsets.only(top: 24),
child: SeedLanguageSelector(
key: _languageSelectorKey,
initialSelected: defaultSeedLanguage),
)
]),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSectionPadding:
EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Observer(
builder: (context) {
return PrimaryButton(
onPressed: () =>
Navigator.of(context).popAndPushNamed(seedLanguageStore.currentRoute),
onPressed: () => widget
.onConfirm(context, _languageSelectorKey.currentState.selected),
text: S.of(context).seed_language_next,
color: Colors.green,
textColor: Colors.white);

View file

@ -17,7 +17,7 @@ List<Image> flagImages = [
Image.asset('assets/images/spain.png'),
];
List<String> languageCodes = [
const List<String> languageCodes = [
'Eng',
'Chi',
'Ned',
@ -28,19 +28,39 @@ List<String> languageCodes = [
'Esp',
];
const defaultSeedLanguage = 'English';
const List<String> seedLanguages = [
defaultSeedLanguage,
'Chinese (simplified)',
'Dutch',
'German',
'Japanese',
'Portuguese',
'Russian',
'Spanish'
];
enum Places { topLeft, topRight, bottomLeft, bottomRight, inside }
class SeedLanguagePicker extends StatefulWidget {
SeedLanguagePicker({Key key, this.selected = defaultSeedLanguage})
: super(key: key);
final String selected;
@override
SeedLanguagePickerState createState() => SeedLanguagePickerState();
SeedLanguagePickerState createState() =>
SeedLanguagePickerState(selected: selected);
}
class SeedLanguagePickerState extends State<SeedLanguagePicker> {
SeedLanguagePickerState({this.selected});
String selected;
@override
Widget build(BuildContext context) {
final seedLanguageStore = Provider.of<SeedLanguageStore>(context);
return GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
@ -48,7 +68,8 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
child: Container(
decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)),
decoration: BoxDecoration(
color: PaletteDark.darkNightBlue.withOpacity(0.75)),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
@ -62,8 +83,7 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
fontSize: 18,
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
color: Colors.white
),
color: Colors.white),
),
),
Padding(
@ -75,8 +95,7 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
width: 300,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(14)),
color: Theme.of(context).dividerColor
),
color: Theme.of(context).dividerColor),
child: GridView.count(
shrinkWrap: true,
crossAxisCount: 3,
@ -85,21 +104,18 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
crossAxisSpacing: 1,
mainAxisSpacing: 1,
children: List.generate(9, (index) {
if (index == 8) {
return gridTile(
isCurrent: false,
place: Places.bottomRight,
image: null,
text: '',
onTap: null);
} else {
final code = languageCodes[index];
final flag = flagImages[index];
final isCurrent = index == seedLanguages.indexOf(seedLanguageStore.selectedSeedLanguage);
final isCurrent =
index == seedLanguages.indexOf(selected);
if (index == 0) {
return gridTile(
@ -108,10 +124,9 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
image: flag,
text: code,
onTap: () {
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
Navigator.of(context).pop();
}
);
selected = seedLanguages[index];
Navigator.of(context).pop(selected);
});
}
if (index == 2) {
@ -121,10 +136,9 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
image: flag,
text: code,
onTap: () {
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
Navigator.of(context).pop();
}
);
selected = seedLanguages[index];
Navigator.of(context).pop(selected);
});
}
if (index == 6) {
@ -134,10 +148,9 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
image: flag,
text: code,
onTap: () {
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
Navigator.of(context).pop();
}
);
selected = seedLanguages[index];
Navigator.of(context).pop(selected);
});
}
return gridTile(
@ -146,10 +159,9 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
image: flag,
text: code,
onTap: () {
seedLanguageStore.setSelectedSeedLanguage(seedLanguages[index]);
Navigator.of(context).pop();
}
);
selected = seedLanguages[index];
Navigator.of(context).pop(selected);
});
}
}),
),
@ -165,13 +177,12 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
);
}
Widget gridTile({
@required bool isCurrent,
Widget gridTile(
{@required bool isCurrent,
@required Places place,
@required Image image,
@required String text,
@required VoidCallback onTap}) {
BorderRadius borderRadius;
final color = isCurrent
? Theme.of(context).accentTextTheme.subtitle.decorationColor
@ -202,19 +213,14 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
onTap: onTap,
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
borderRadius: borderRadius,
color: color
),
decoration: BoxDecoration(borderRadius: borderRadius, color: color),
child: Center(
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image != null
? image
: Offstage(),
image != null ? image : Offstage(),
Padding(
padding: image != null
? EdgeInsets.only(left: 10)
@ -225,14 +231,12 @@ class SeedLanguagePickerState extends State<SeedLanguagePicker> {
fontSize: 18,
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
color: textColor
),
color: textColor),
),
)
],
),
),
)
);
));
}
}

View 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,
),
)
],
),
));
}
}

View file

@ -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,
),
)
],
),
)
);
}
}

View file

@ -32,9 +32,7 @@ class SubaddressListPage extends BasePage {
child: Observer(
builder: (_) => ListView.separated(
separatorBuilder: (_, __) => Divider(
color: Theme.of(context).dividerTheme.color,
height: 1.0,
),
color: Theme.of(context).dividerTheme.color, height: 1.0),
itemCount: subaddressListStore.subaddresses == null
? 0
: subaddressListStore.subaddresses.length,
@ -42,9 +40,7 @@ class SubaddressListPage extends BasePage {
final subaddress = subaddressListStore.subaddresses[index];
final isCurrent =
walletStore.subaddress.address == subaddress.address;
final label = subaddress.label != null
? subaddress.label
: subaddress.address;
final label = subaddress.label ?? subaddress.address;
return InkWell(
onTap: () => Navigator.of(context).pop(subaddress),

View file

@ -1,99 +1,99 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/src/domain/services/user_service.dart';
import 'package:cake_wallet/src/domain/services/wallet_service.dart';
import 'package:cake_wallet/src/stores/auth/auth_state.dart';
import 'package:cake_wallet/generated/i18n.dart';
part 'auth_store.g.dart';
class AuthStore = AuthStoreBase with _$AuthStore;
abstract class AuthStoreBase with Store {
AuthStoreBase(
{@required this.userService,
@required this.walletService,
@required this.sharedPreferences}) {
state = AuthenticationStateInitial();
_failureCounter = 0;
}
static const maxFailedLogins = 3;
static const banTimeout = 180; // 3 mins
final banTimeoutKey = S.current.auth_store_ban_timeout;
final UserService userService;
final WalletService walletService;
final SharedPreferences sharedPreferences;
@observable
AuthState state;
@observable
int _failureCounter;
@action
Future auth({String password}) async {
state = AuthenticationStateInitial();
final _banDuration = banDuration();
if (_banDuration != null) {
state = AuthenticationBanned(
error: S.current.auth_store_banned_for + '${_banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
return;
}
state = AuthenticationInProgress();
final isAuth = await userService.authenticate(password);
if (isAuth) {
state = AuthenticatedSuccessfully();
_failureCounter = 0;
} else {
_failureCounter += 1;
if (_failureCounter >= maxFailedLogins) {
final banDuration = await ban();
state = AuthenticationBanned(
error: S.current.auth_store_banned_for + '${banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
return;
}
state = AuthenticationFailure(error: S.current.auth_store_incorrect_password);
}
}
Duration banDuration() {
final unbanTimestamp = sharedPreferences.getInt(banTimeoutKey);
if (unbanTimestamp == null) {
return null;
}
final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
final now = DateTime.now();
if (now.isAfter(unbanTime)) {
return null;
}
return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
}
Future<Duration> ban() async {
final multiplier = _failureCounter - maxFailedLogins + 1;
final timeout = (multiplier * banTimeout) * 1000;
final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
await sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
return Duration(milliseconds: timeout);
}
@action
void biometricAuth() {
state = AuthenticatedSuccessfully();
}
}
//import 'dart:async';
//import 'package:flutter/foundation.dart';
//import 'package:shared_preferences/shared_preferences.dart';
//import 'package:mobx/mobx.dart';
//import 'package:cake_wallet/src/domain/services/user_service.dart';
//import 'package:cake_wallet/src/domain/services/wallet_service.dart';
//import 'package:cake_wallet/view_model/auth_state.dart';
//import 'package:cake_wallet/generated/i18n.dart';
//
//part 'auth_store.g.dart';
//
//class AuthStore = AuthStoreBase with _$AuthStore;
//
//abstract class AuthStoreBase with Store {
// AuthStoreBase(
// {@required this.userService,
// @required this.walletService,
// @required this.sharedPreferences}) {
// state = AuthenticationStateInitial();
// _failureCounter = 0;
// }
//
// static const maxFailedLogins = 3;
// static const banTimeout = 180; // 3 mins
// final banTimeoutKey = S.current.auth_store_ban_timeout;
//
// final UserService userService;
// final WalletService walletService;
//
// final SharedPreferences sharedPreferences;
//
// @observable
// AuthState state;
//
// @observable
// int _failureCounter;
//
// @action
// Future auth({String password}) async {
// state = AuthenticationStateInitial();
// final _banDuration = banDuration();
//
// if (_banDuration != null) {
// state = AuthenticationBanned(
// error: S.current.auth_store_banned_for + '${_banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
// return;
// }
//
// state = AuthenticationInProgress();
// final isAuth = await userService.authenticate(password);
//
// if (isAuth) {
// state = AuthenticatedSuccessfully();
// _failureCounter = 0;
// } else {
// _failureCounter += 1;
//
// if (_failureCounter >= maxFailedLogins) {
// final banDuration = await ban();
// state = AuthenticationBanned(
// error: S.current.auth_store_banned_for + '${banDuration.inMinutes}' + S.current.auth_store_banned_minutes);
// return;
// }
//
// state = AuthenticationFailure(error: S.current.auth_store_incorrect_password);
// }
// }
//
// Duration banDuration() {
// final unbanTimestamp = sharedPreferences.getInt(banTimeoutKey);
//
// if (unbanTimestamp == null) {
// return null;
// }
//
// final unbanTime = DateTime.fromMillisecondsSinceEpoch(unbanTimestamp);
// final now = DateTime.now();
//
// if (now.isAfter(unbanTime)) {
// return null;
// }
//
// return Duration(milliseconds: unbanTimestamp - now.millisecondsSinceEpoch);
// }
//
// Future<Duration> ban() async {
// final multiplier = _failureCounter - maxFailedLogins + 1;
// final timeout = (multiplier * banTimeout) * 1000;
// final unbanTimestamp = DateTime.now().millisecondsSinceEpoch + timeout;
// await sharedPreferences.setInt(banTimeoutKey, unbanTimestamp);
//
// return Duration(milliseconds: timeout);
// }
//
// @action
// void biometricAuth() {
// state = AuthenticatedSuccessfully();
// }
//}

View file

@ -30,8 +30,8 @@ abstract class AuthenticationStoreBase with Store {
@observable
AuthenticationState state;
@observable
String errorMessage;
// @observable
// String errorMessage;
Future started() async {
final canAuth = await userService.canAuthenticate();

View file

@ -2,7 +2,7 @@ import 'package:mobx/mobx.dart';
import 'package:flutter/foundation.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/src/domain/services/wallet_list_service.dart';
import 'package:cake_wallet/src/domain/common/mnemotic_item.dart';
import 'package:cake_wallet/src/domain/common/mnemonic_item.dart';
import 'package:cake_wallet/src/stores/wallet_restoration/wallet_restoration_state.dart';
import 'package:cake_wallet/src/stores/authentication/authentication_store.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
@ -37,7 +37,7 @@ abstract class WalleRestorationStoreBase with Store {
bool isValid;
@observable
List<MnemoticItem> seed;
List<MnemonicItem> seed;
@observable
bool disabledState;
@ -79,35 +79,35 @@ abstract class WalleRestorationStoreBase with Store {
}
@action
void setSeed(List<MnemoticItem> seed) {
void setSeed(List<MnemonicItem> seed) {
this.seed = seed;
}
@action
void validateSeed(List<MnemoticItem> seed) {
final _seed = seed != null ? seed : this.seed;
bool isValid = _seed != null ? _seed.length == 25 : false;
if (!isValid) {
errorMessage = S.current.wallet_restoration_store_incorrect_seed_length;
this.isValid = isValid;
return;
}
for (final item in _seed) {
if (!item.isCorrect()) {
isValid = false;
break;
}
}
if (isValid) {
errorMessage = null;
}
this.isValid = isValid;
return;
}
// @action
// void validateSeed(List<MnemonicItem> seed) {
// final _seed = seed != null ? seed : this.seed;
// bool isValid = _seed != null ? _seed.length == 25 : false;
//
// if (!isValid) {
// errorMessage = S.current.wallet_restoration_store_incorrect_seed_length;
// this.isValid = isValid;
// return;
// }
//
// for (final item in _seed) {
// if (!item.isCorrect()) {
// isValid = false;
// break;
// }
// }
//
// if (isValid) {
// errorMessage = null;
// }
//
// this.isValid = isValid;
// return;
// }
String _seedText() {
return seed.fold('', (acc, item) => acc + ' ' + item.toString());

View file

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class BaseTextFormField extends StatelessWidget {
BaseTextFormField({
this.controller,
BaseTextFormField(
{this.controller,
this.keyboardType = TextInputType.text,
this.textInputAction = TextInputAction.done,
this.textAlign = TextAlign.start,
@ -18,8 +18,8 @@ class BaseTextFormField extends StatelessWidget {
this.suffix,
this.suffixIcon,
this.enabled = true,
this.validator
});
this.validator,
this.placeholderTextStyle});
final TextEditingController controller;
final TextInputType keyboardType;
@ -37,6 +37,7 @@ class BaseTextFormField extends StatelessWidget {
final Widget suffixIcon;
final bool enabled;
final FormFieldValidator<String> validator;
final TextStyle placeholderTextStyle;
@override
Widget build(BuildContext context) {
@ -51,30 +52,25 @@ class BaseTextFormField extends StatelessWidget {
enabled: enabled,
style: TextStyle(
fontSize: 16.0,
color: textColor ?? Theme.of(context).primaryTextTheme.title.color
),
color: textColor ?? Theme.of(context).primaryTextTheme.title.color),
decoration: InputDecoration(
prefix: prefix,
suffix: suffix,
suffixIcon: suffixIcon,
hintStyle: TextStyle(
color: hintColor ?? Theme.of(context).primaryTextTheme.caption.color,
fontSize: 16
),
hintStyle: placeholderTextStyle ??
TextStyle(
color: hintColor ??
Theme.of(context).primaryTextTheme.caption.color,
fontSize: 16),
hintText: hintText,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: borderColor ?? Theme.of(context).dividerColor,
width: 1.0
)
),
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: borderColor ?? Theme.of(context).dividerColor,
width: 1.0
)
)
),
width: 1.0))),
validator: validator,
);
}

View file

@ -4,7 +4,10 @@ import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/monero/get_height_by_date.dart';
class BlockchainHeightWidget extends StatefulWidget {
BlockchainHeightWidget({GlobalKey key}) : super(key: key);
BlockchainHeightWidget({GlobalKey key, this.onHeightChange})
: super(key: key);
final Function(int) onHeightChange;
@override
State<StatefulWidget> createState() => BlockchainHeightState();
@ -13,15 +16,23 @@ class BlockchainHeightWidget extends StatefulWidget {
class BlockchainHeightState extends State<BlockchainHeightWidget> {
final dateController = TextEditingController();
final restoreHeightController = TextEditingController();
int get height => _height;
int _height = 0;
@override
void initState() {
restoreHeightController.addListener(() => _height =
restoreHeightController.text != null
restoreHeightController.addListener(() {
try {
_changeHeight(restoreHeightController.text != null &&
restoreHeightController.text.isNotEmpty
? int.parse(restoreHeightController.text)
: 0);
} catch (_) {
_changeHeight(0);
}
});
super.initState();
}
@ -38,21 +49,18 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.title.color
),
color: Theme.of(context).primaryTextTheme.title.color),
controller: restoreHeightController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: false),
decoration: InputDecoration(
hintStyle: TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
fontSize: 16
),
fontSize: 16),
hintText: S.of(context).widgets_restore_from_blockheight,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
color: Theme.of(context).dividerColor, width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
@ -81,13 +89,14 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.title.color
),
color: Theme.of(context).primaryTextTheme.title.color),
decoration: InputDecoration(
hintStyle: TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
fontSize: 16
),
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16),
hintText: S.of(context).widgets_restore_from_date,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
@ -113,7 +122,7 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
Future _selectDate(BuildContext context) async {
final now = DateTime.now();
final DateTime date = await showDatePicker(
final date = await showDatePicker(
context: context,
initialDate: now.subtract(Duration(days: 1)),
firstDate: DateTime(2014, DateTime.april),
@ -125,8 +134,13 @@ class BlockchainHeightState extends State<BlockchainHeightWidget> {
setState(() {
dateController.text = DateFormat('yyyy-MM-dd').format(date);
restoreHeightController.text = '$height';
_height = height;
_changeHeight(height);
});
}
}
void _changeHeight(int height) {
_height = height;
widget.onHeightChange?.call(height);
}
}

View file

@ -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);
});
}
}

View file

@ -1,94 +1,64 @@
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/domain/monero/mnemonics/english.dart';
import 'package:cake_wallet/src/domain/monero/mnemonics/chinese_simplified.dart';
import 'package:cake_wallet/src/domain/monero/mnemonics/dutch.dart';
import 'package:cake_wallet/src/domain/monero/mnemonics/german.dart';
import 'package:cake_wallet/src/domain/monero/mnemonics/japanese.dart';
import 'package:cake_wallet/src/domain/monero/mnemonics/portuguese.dart';
import 'package:cake_wallet/src/domain/monero/mnemonics/russian.dart';
import 'package:cake_wallet/src/domain/monero/mnemonics/spanish.dart';
import 'package:cake_wallet/src/domain/common/mnemotic_item.dart';
import 'package:cake_wallet/core/seed_validator.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/domain/common/mnemonic_item.dart';
import 'package:cake_wallet/generated/i18n.dart';
class SeedWidget extends StatefulWidget {
SeedWidget({Key key, this.onMnemoticChange, this.onFinish, this.seedLanguage}) : super(key: key) {
switch (seedLanguage) {
case 'English':
words = EnglishMnemonics.words;
break;
case 'Chinese (simplified)':
words = ChineseSimplifiedMnemonics.words;
break;
case 'Dutch':
words = DutchMnemonics.words;
break;
case 'German':
words = GermanMnemonics.words;
break;
case 'Japanese':
words = JapaneseMnemonics.words;
break;
case 'Portuguese':
words = PortugueseMnemonics.words;
break;
case 'Russian':
words = RussianMnemonics.words;
break;
case 'Spanish':
words = SpanishMnemonics.words;
break;
default:
words = EnglishMnemonics.words;
}
}
SeedWidget(
{Key key,
this.maxLength,
this.onMnemonicChange,
this.onFinish,
this.validator})
: super(key: key);
final Function(List<MnemoticItem>) onMnemoticChange;
final int maxLength;
final Function(List<MnemonicItem>) onMnemonicChange;
final Function() onFinish;
final String seedLanguage;
List<String> words;
final SeedValidator validator;
@override
SeedWidgetState createState() => SeedWidgetState();
SeedWidgetState createState() => SeedWidgetState(maxLength: maxLength);
}
class SeedWidgetState extends State<SeedWidget> {
static const maxLength = 25;
SeedWidgetState({this.maxLength});
List<MnemoticItem> items = <MnemoticItem>[];
List<MnemonicItem> items = <MnemonicItem>[];
final int maxLength;
final _seedController = TextEditingController();
final _seedTextFieldKey = GlobalKey();
MnemoticItem selectedItem;
MnemonicItem selectedItem;
bool isValid;
String errorMessage;
List<MnemoticItem> currentMnemotics;
bool isCurrentMnemoticValid;
List<MnemonicItem> currentMnemonics;
bool isCurrentMnemonicValid;
String _errorMessage;
@override
void initState() {
super.initState();
isValid = false;
isCurrentMnemoticValid = false;
isCurrentMnemonicValid = false;
_seedController
.addListener(() => changeCurrentMnemotic(_seedController.text));
.addListener(() => changeCurrentMnemonic(_seedController.text));
}
void addMnemotic(String text) {
setState(() => items.add(MnemoticItem(
text: text.trim().toLowerCase(), dic: widget.words)));
void addMnemonic(String text) {
setState(() => items.add(MnemonicItem(text: text.trim().toLowerCase())));
_seedController.text = '';
if (widget.onMnemoticChange != null) {
widget.onMnemoticChange(items);
if (widget.onMnemonicChange != null) {
widget.onMnemonicChange(items);
}
}
void mnemoticFromText(String text) {
void mnemonicFromText(String text) {
final splitted = text.split(' ');
if (splitted.length >= 2) {
@ -98,18 +68,18 @@ class SeedWidgetState extends State<SeedWidget> {
}
if (selectedItem != null) {
editTextOfSelectedMnemotic(text);
editTextOfSelectedMnemonic(text);
} else {
addMnemotic(text);
addMnemonic(text);
}
}
}
}
void selectMnemotic(MnemoticItem item) {
void selectMnemonic(MnemonicItem item) {
setState(() {
selectedItem = item;
currentMnemotics = [item];
currentMnemonics = [item];
_seedController
..text = item.text
@ -117,23 +87,23 @@ class SeedWidgetState extends State<SeedWidget> {
});
}
void onMnemoticTap(MnemoticItem item) {
void onMnemonicTap(MnemonicItem item) {
if (selectedItem == item) {
setState(() => selectedItem = null);
_seedController.text = '';
return;
}
selectMnemotic(item);
selectMnemonic(item);
}
void editTextOfSelectedMnemotic(String text) {
void editTextOfSelectedMnemonic(String text) {
setState(() => selectedItem.changeText(text));
selectedItem = null;
_seedController.text = '';
if (widget.onMnemoticChange != null) {
widget.onMnemoticChange(items);
if (widget.onMnemonicChange != null) {
widget.onMnemonicChange(items);
}
}
@ -143,83 +113,77 @@ class SeedWidgetState extends State<SeedWidget> {
selectedItem = null;
_seedController.text = '';
if (widget.onMnemoticChange != null) {
widget.onMnemoticChange(items);
if (widget.onMnemonicChange != null) {
widget.onMnemonicChange(items);
}
});
}
void invalidate() {
setState(() => isValid = false);
}
void invalidate() => setState(() => isValid = false);
void validated() {
setState(() => isValid = true);
}
void validated() => setState(() => isValid = true);
void setErrorMessage(String errorMessage) {
void setErrorMessage(String errorMessage) =>
setState(() => this.errorMessage = errorMessage);
}
void replaceText(String text) {
setState(() => items = []);
mnemoticFromText(text);
mnemonicFromText(text);
}
void changeCurrentMnemotic(String text) {
void changeCurrentMnemonic(String text) {
setState(() {
final trimmedText = text.trim();
final splitted = trimmedText.split(' ');
_errorMessage = null;
if (text == null) {
currentMnemotics = [];
isCurrentMnemoticValid = false;
currentMnemonics = [];
isCurrentMnemonicValid = false;
return;
}
currentMnemotics = splitted
.map((text) => MnemoticItem(text: text, dic: widget.words))
.toList();
currentMnemonics =
splitted.map((text) => MnemonicItem(text: text)).toList();
bool isValid = true;
var isValid = true;
for (final word in currentMnemotics) {
isValid = word.isCorrect();
for (final word in currentMnemonics) {
isValid = widget.validator.isValid(word);
if (!isValid) {
break;
}
}
isCurrentMnemoticValid = isValid;
isCurrentMnemonicValid = isValid;
});
}
void saveCurrentMnemoticToItems() {
void saveCurrentMnemonicToItems() {
setState(() {
if (selectedItem != null) {
selectedItem.changeText(currentMnemotics.first.text.trim());
selectedItem.changeText(currentMnemonics.first.text.trim());
selectedItem = null;
} else {
items.addAll(currentMnemotics);
items.addAll(currentMnemonics);
}
currentMnemotics = [];
currentMnemonics = [];
_seedController.text = '';
});
}
void showErrorIfExist() {
setState(() => _errorMessage =
!isCurrentMnemoticValid ? S.current.incorrect_seed : null);
!isCurrentMnemonicValid ? S.current.incorrect_seed : null);
}
bool isSeedValid() {
bool isValid;
for (final item in items) {
isValid = item.isCorrect();
isValid = widget.validator.isValid(item);
if (!isValid) {
break;
@ -243,10 +207,8 @@ class SeedWidgetState extends State<SeedWidget> {
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)
),
color: Theme.of(context).accentTextTheme.title.backgroundColor
),
bottomRight: Radius.circular(24)),
color: Theme.of(context).accentTextTheme.title.backgroundColor),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
@ -256,38 +218,47 @@ class SeedWidgetState extends State<SeedWidget> {
S.of(context).restore_active_seed,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryTextTheme.caption.color
),
color:
Theme.of(context).primaryTextTheme.caption.color),
),
Padding(
padding: EdgeInsets.only(top: 5),
child: Wrap(
children: items.map((item) {
final isValid = item.isCorrect();
final isValid = widget.validator.isValid(item);
final isSelected = selectedItem == item;
return InkWell(
onTap: () => onMnemoticTap(item),
onTap: () => onMnemonicTap(item),
child: Container(
decoration: BoxDecoration(
color: isValid ? Colors.transparent : Palette.red),
color: isValid
? Colors.transparent
: Palette.red),
margin: EdgeInsets.only(right: 7, bottom: 8),
child: Text(
item.toString(),
style: TextStyle(
color: isValid
? Theme.of(context).primaryTextTheme.title.color
: Theme.of(context).primaryTextTheme.caption.color,
? Theme.of(context)
.primaryTextTheme
.title
.color
: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16,
fontWeight:
isSelected ? FontWeight.w900 : FontWeight.w400,
fontWeight: isSelected
? FontWeight.w900
: FontWeight.w400,
decoration: isSelected
? TextDecoration.underline
: TextDecoration.none),
)),
);
}).toList(),)
)
}).toList(),
))
],
),
),
@ -297,7 +268,8 @@ class SeedWidgetState extends State<SeedWidget> {
fit: FlexFit.tight,
flex: 2,
child: Padding(
padding: EdgeInsets.only(left: 24, top: 48, right: 24, bottom: 24),
padding:
EdgeInsets.only(left: 24, top: 48, right: 24, bottom: 24),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
@ -307,20 +279,20 @@ class SeedWidgetState extends State<SeedWidget> {
style: TextStyle(
fontSize: 18,
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()
onFieldSubmitted: (text) => isCurrentMnemonicValid
? saveCurrentMnemonicToItems()
: null,
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.title.color
),
color:
Theme.of(context).primaryTextTheme.title.color),
controller: _seedController,
textInputAction: TextInputAction.done,
decoration: InputDecoration(
@ -331,10 +303,12 @@ class SeedWidgetState extends State<SeedWidget> {
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text(
'${items.length}/${SeedWidgetState.maxLength}',
Text('${items.length}/$maxLength',
style: TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 14)),
SizedBox(width: 10),
InkWell(
@ -346,31 +320,38 @@ class SeedWidgetState extends State<SeedWidget> {
height: 35,
padding: EdgeInsets.all(7),
decoration: BoxDecoration(
color:
Theme.of(context).accentTextTheme.title.backgroundColor,
color: Theme.of(context)
.accentTextTheme
.title
.backgroundColor,
borderRadius:
BorderRadius.circular(10.0)),
child: Text(
S.of(context).paste,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color
),
color: Theme.of(context)
.primaryTextTheme
.title
.color),
)),
)
],
),
),
),
hintStyle:
TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
fontSize: 16
),
hintText: S.of(context).restore_from_seed_placeholder,
hintStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16),
hintText:
S.of(context).restore_from_seed_placeholder,
errorText: _errorMessage,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor, width: 1.0)),
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
@ -379,8 +360,7 @@ class SeedWidgetState extends State<SeedWidget> {
),
)
]),
)
),
)),
Padding(
padding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
child: Row(
@ -395,8 +375,7 @@ class SeedWidgetState extends State<SeedWidget> {
textColor: Colors.white,
isDisabled: items.isEmpty,
),
)
),
)),
Flexible(
child: Padding(
padding: EdgeInsets.only(left: 8),
@ -413,11 +392,11 @@ class SeedWidgetState extends State<SeedWidget> {
text: selectedItem != null
? S.of(context).save
: S.of(context).add_new_word,
onPressed: () => isCurrentMnemoticValid
? saveCurrentMnemoticToItems()
onPressed: () => isCurrentMnemonicValid
? saveCurrentMnemonicToItems()
: null,
onDisabledPressed: () => showErrorIfExist(),
isDisabled: !isCurrentMnemoticValid,
isDisabled: !isCurrentMnemonicValid,
color: Colors.green,
textColor: Colors.white),
),

16
lib/store/app_store.dart Normal file
View 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;
}

View 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;
}

View 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;
}

View file

@ -44,9 +44,10 @@ class Themes {
),
display4: TextStyle(
color: Palette.oceanBlue // QR code
)
),
dividerColor: Palette.periwinkle,
// headline1: TextStyle(color: Palette.nightBlue)
),
dividerColor: Palette.eee,
accentTextTheme: TextTheme(
title: TextStyle(
color: Palette.darkLavender, // top panel
@ -112,7 +113,8 @@ class Themes {
),
display4: TextStyle(
color: PaletteDark.gray // QR code
)
),
// headline5: TextStyle(color: PaletteDark.gray)
),
dividerColor: PaletteDark.distantBlue,
accentTextTheme: TextTheme(

3
lib/utils/list_item.dart Normal file
View file

@ -0,0 +1,3 @@
abstract class ListItem {
const ListItem();
}

View file

@ -0,0 +1,5 @@
class ListSection<Item> {
const ListSection({this.items});
final List<Item> items;
}

View file

@ -0,0 +1,3 @@
import 'package:cake_wallet/utils/list_item.dart';
class AccountListHeader extends ListItem {}

View file

@ -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();
}
}
}

View file

@ -0,0 +1,3 @@
import 'package:cake_wallet/utils/list_item.dart';
class AddressListHeader extends ListItem {}

View 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;
}

View 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());
}
}

View 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;
}

View 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();
}

View 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');
}
}

View 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;
}

View 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();
}

View 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);
}

View 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);
}

View file

@ -371,6 +371,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.19"
get_it:
dependency: "direct main"
description:
name: get_it
url: "https://pub.dartlang.org"
source: hosted
version: "4.0.2"
glob:
dependency: transitive
description:

View file

@ -58,6 +58,7 @@ dependencies:
password: ^1.0.0
basic_utils: ^1.0.8
bitcoin_flutter: ^2.0.0
get_it: ^4.0.2
dev_dependencies:
flutter_test: