TMP 4

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