General changes. CAKE-1.

This commit is contained in:
M 2020-07-06 23:09:03 +03:00
commit f45875ba3a
160 changed files with 5492 additions and 4033 deletions

View file

@ -0,0 +1,2 @@
-
uri: electrum2.hodlister.co:50002

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 150 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 315 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 191 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 295 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View file

@ -182,7 +182,7 @@ Future<void> _openWallet(Map<String, String> args) async =>
bool _isWalletExist(String path) => isWalletExistSync(path: path);
void openWallet({String path, String password, int nettype = 0}) async =>
loadWallet(path: path, password: password);
loadWallet(path: path, password: password, nettype: nettype);
Future<void> openWalletAsync(Map<String, String> args) async =>
compute(_openWallet, args);

View file

@ -36,6 +36,8 @@ PODS:
- Flutter
- path_provider (0.0.1):
- Flutter
- path_provider_linux (0.0.1):
- Flutter
- path_provider_macos (0.0.1):
- Flutter
- share (0.0.1):
@ -65,6 +67,7 @@ DEPENDENCIES:
- local_auth (from `.symlinks/plugins/local_auth/ios`)
- package_info (from `.symlinks/plugins/package_info/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- path_provider_linux (from `.symlinks/plugins/path_provider_linux/ios`)
- path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`)
- share (from `.symlinks/plugins/share/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
@ -100,6 +103,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/package_info/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
path_provider_linux:
:path: ".symlinks/plugins/path_provider_linux/ios"
path_provider_macos:
:path: ".symlinks/plugins/path_provider_macos/ios"
share:
@ -129,6 +134,7 @@ SPEC CHECKSUMS:
MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb
package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62
path_provider: abfe2b5c733d04e238b0d8691db0cfd63a27a93c
path_provider_linux: 4d630dc393e1f20364f3e3b4a2ff41d9674a84e4
path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
share: 0b2c3e82132f5888bccca3351c504d0003b3b410
shared_preferences: af6bfa751691cdc24be3045c43ec037377ada40d
@ -141,4 +147,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: f1916a43bb28badbd408be80e8e4b8652a74e93e
COCOAPODS: 1.9.1
COCOAPODS: 1.9.3

View file

@ -388,7 +388,7 @@
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 3.1.28;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
@ -524,7 +524,7 @@
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 3.1.28;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -555,7 +555,7 @@
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 3.1.28;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_BUNDLE_IDENTIFIER = com.cakewallet.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;

View file

@ -0,0 +1,6 @@
class BitcoinTransactionCredentials {
const BitcoinTransactionCredentials(this.address, this.amount);
final String address;
final double amount;
}

View file

@ -26,26 +26,18 @@ abstract class BitcoinTransactionHistoryBase
_password = password,
_height = 0;
BitcoinWallet wallet;
BitcoinWalletBase wallet;
final ElectrumClient eclient;
final String path;
final String _password;
int _height;
Future<void> init() async {
// TODO: throw exeption if wallet is null;
final info = await _read();
_height = (info['height'] as int) ?? _height;
// FIXME: remove hardcoded value
transactions = ObservableList.of([
BitcoinTransactionInfo(
id: 'test',
height: 12,
amount: 12,
direction: TransactionDirection.incoming,
date: DateTime.now(),
isPending: false)
]);
_height = info['height'] as int ?? _height;
transactions = ObservableList.of(
info['transactions'] as List<BitcoinTransactionInfo> ??
<BitcoinTransactionInfo>[]);
}
@override
@ -119,7 +111,7 @@ abstract class BitcoinTransactionHistoryBase
return {'transactions': transactions, 'height': height};
} catch (_) {
return {'transactions': <TransactionInfo>[], 'height': 0};
return {'transactions': <BitcoinTransactionInfo>[], 'height': 0};
}
}

View file

@ -1,5 +1,11 @@
import 'dart:typed_data';
import 'dart:convert';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_credentials.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet_keys.dart';
import 'package:cake_wallet/src/domain/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/domain/common/sync_status.dart';
import 'package:flutter/cupertino.dart';
import 'package:mobx/mobx.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:flutter/foundation.dart';
@ -13,13 +19,10 @@ import 'package:cake_wallet/bitcoin/electrum.dart';
import 'package:cake_wallet/bitcoin/bitcoin_balance.dart';
import 'package:cake_wallet/src/domain/common/node.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:rxdart/rxdart.dart';
part 'bitcoin_wallet.g.dart';
/* TODO: Save balance to a wallet file.
Load balance from the wallet file in `init` method.
*/
class BitcoinWallet = BitcoinWalletBase with _$BitcoinWallet;
abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
@ -31,7 +34,7 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String;
final accountIndex =
(data['account_index'] == "null" || data['account_index'] == null)
(data['account_index'] == 'null' || data['account_index'] == null)
? 0
: int.parse(data['account_index'] as String);
final _addresses = data['addresses'] as List;
@ -90,18 +93,15 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
this.transactionHistory,
this.mnemonic,
BitcoinBalance initialBalance}) {
type = WalletType.bitcoin;
currency = CryptoCurrency.btc;
balance = initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0);
hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
network: bitcoin.bitcoin);
addresses = initialAddresses != null
? ObservableList<BitcoinAddressRecord>.of(initialAddresses)
: ObservableList<BitcoinAddressRecord>();
if (addresses.isEmpty) {
addresses.add(BitcoinAddressRecord(hd.address));
}
address = addresses.first.address;
syncStatus = NotConnectedSyncStatus();
_password = password;
_accountIndex = accountIndex;
@ -113,8 +113,6 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
bitcoin.HDWallet hd;
final ElectrumClient eclient;
final String mnemonic;
int _accountIndex;
String _password;
@override
String name;
@ -128,13 +126,31 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
BitcoinBalance balance;
@override
final type = WalletType.bitcoin;
@observable
SyncStatus syncStatus;
ObservableList<BitcoinAddressRecord> addresses;
String get xpub => hd.base58;
@override
String get seed => mnemonic;
@override
BitcoinWalletKeys get keys => BitcoinWalletKeys(
wif: hd.wif, privateKey: hd.privKey, publicKey: hd.pubKey);
int _accountIndex;
String _password;
BehaviorSubject<Object> _addressUpdateSubject;
Future<void> init() async {
if (addresses.isEmpty) {
addresses.add(BitcoinAddressRecord(_getAddress(hd: hd, index: 0)));
}
address = addresses.first.address;
transactionHistory.wallet = this;
await transactionHistory.init();
}
@ -160,14 +176,55 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
}
}
@action
@override
Future<void> startSync() async {}
Future<void> startSync() async {
try {
syncStatus = StartingSyncStatus();
await _addressUpdateSubject?.close();
_addressUpdateSubject = eclient.addressUpdate(address: address);
await transactionHistory.update();
await _updateBalance();
syncStatus = SyncedSyncStatus();
} catch (e) {
print(e.toString());
syncStatus = FailedSyncStatus();
}
}
@action
@override
Future<void> connectToNode({@required Node node}) async {
try {
syncStatus = ConnectingSyncStatus();
await eclient.connect(host: 'electrum2.hodlister.co', port: 50002);
syncStatus = ConnectedSyncStatus();
} catch (e) {
print(e.toString);
syncStatus = FailedSyncStatus();
}
}
@override
Future<void> connectToNode({@required Node node}) async {}
Future<void> createTransaction(Object credentials) async {
final transactionCredentials = credentials as BitcoinTransactionCredentials;
@override
Future<void> createTransaction(Object credentials) async {}
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
final keyPair = bitcoin.ECPair.fromWIF(hd.wif);
final transactions = transactionHistory.transactions;
transactions.sort((q, w) => q.height.compareTo(w.height));
final prevTx = transactions.first;
txb.setVersion(1);
txb.addInput(prevTx, 0);
txb.addOutput(transactionCredentials.address,
doubleToBitcoinAmount(transactionCredentials.amount));
txb.sign(vin: 0, keyPair: keyPair);
final encoded = txb.build().toHex();
print('Enoded transaction $encoded');
await eclient.broadcastTransaction(transactionRaw: encoded);
}
@override
Future<void> save() async =>
@ -181,24 +238,27 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
});
String _getAddress({bitcoin.HDWallet hd, int index}) => bitcoin
.P2PKH(
.P2WPKH(
data: PaymentData(
pubkey: Uint8List.fromList(hd.derive(index).pubKey.codeUnits)))
.data
.address;
Future<Map<String, int>> _fetchBalances() async {
Future<BitcoinBalance> _fetchBalances() async {
final balances = await Future.wait(
addresses.map((record) => eclient.getBalance(address: record.address)));
final balance = balances.fold(<String, int>{}, (Map<String, int> acc, val) {
acc['confirmed'] =
(val['confirmed'] as int ?? 0) + (acc['confirmed'] ?? 0);
acc['unconfirmed'] =
(val['unconfirmed'] as int ?? 0) + (acc['unconfirmed'] ?? 0);
return acc;
});
final balance = balances.fold(
BitcoinBalance(confirmed: 0, unconfirmed: 0),
(BitcoinBalance acc, val) => BitcoinBalance(
confirmed: (val['confirmed'] as int ?? 0) + (acc.confirmed ?? 0),
unconfirmed:
(val['unconfirmed'] as int ?? 0) + (acc.unconfirmed ?? 0)));
return balance;
}
Future<void> _updateBalance() async {
balance = await _fetchBalances();
await save();
}
}

View file

@ -0,0 +1,9 @@
import 'package:flutter/foundation.dart';
class BitcoinWalletKeys {
const BitcoinWalletKeys({@required this.wif, @required this.privateKey, @required this.publicKey});
final String wif;
final String privateKey;
final String publicKey;
}

View file

@ -34,6 +34,7 @@ class ElectrumClient {
int _id;
final Map<String, SocketTask> _tasks;
bool _isConnected;
Timer _aliveTimer;
Future<void> connect({@required String host, @required int port}) async {
if (socket != null) {
@ -48,8 +49,7 @@ class ElectrumClient {
socket.listen((List<int> event) {
try {
final Map<String, Object> jsoned =
json.decode(utf8.decode(event)) as Map<String, Object>;
final jsoned = json.decode(utf8.decode(event)) as Map<String, Object>;
final method = jsoned['method'];
if (method is String) {
@ -73,6 +73,13 @@ class ElectrumClient {
});
print('Connected to ${socket.remoteAddress}');
keepAlive();
}
void keepAlive() {
_aliveTimer?.cancel();
// FIXME: Unnamed constant.
_aliveTimer = Timer.periodic(Duration(seconds: 30), (_) async => ping());
}
Future<void> ping() => call(method: 'server.ping');
@ -122,6 +129,16 @@ class ElectrumClient {
return '';
});
Future<String> broadcastTransaction({@required String transactionRaw}) async =>
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
.then((dynamic result) {
if (result is String) {
return result;
}
return '';
});
Future<Map<String, dynamic>> getMerkle(
{@required String hash, @required int height}) async =>
await call(

View file

@ -0,0 +1,82 @@
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
class AddressValidator extends TextValidator {
AddressValidator({@required CryptoCurrency type})
: super(
errorMessage: S.current.error_text_address,
pattern: getPattern(type),
length: getLength(type));
static String getPattern(CryptoCurrency type) {
switch (type) {
case CryptoCurrency.xmr:
return '[0-9a-zA-Z]';
case CryptoCurrency.ada:
return '[0-9a-zA-Z]';
case CryptoCurrency.bch:
return '[0-9a-zA-Z]';
case CryptoCurrency.bnb:
return '[0-9a-zA-Z]';
case CryptoCurrency.btc:
return '[0-9a-zA-Z]';
case CryptoCurrency.dash:
return '[0-9a-zA-Z]';
case CryptoCurrency.eos:
return '[0-9a-zA-Z]';
case CryptoCurrency.eth:
return '[0-9a-zA-Z]';
case CryptoCurrency.ltc:
return '[0-9a-zA-Z]';
case CryptoCurrency.nano:
return '[0-9a-zA-Z_]';
case CryptoCurrency.trx:
return '[0-9a-zA-Z]';
case CryptoCurrency.usdt:
return '[0-9a-zA-Z]';
case CryptoCurrency.xlm:
return '[0-9a-zA-Z]';
case CryptoCurrency.xrp:
return '[0-9a-zA-Z]';
default:
return '[0-9a-zA-Z]';
}
}
static List<int> getLength(CryptoCurrency type) {
switch (type) {
case CryptoCurrency.xmr:
return [95, 106];
case CryptoCurrency.ada:
return [59, 92, 105];
case CryptoCurrency.bch:
return [42];
case CryptoCurrency.bnb:
return [42];
case CryptoCurrency.btc:
return [34, 42];
case CryptoCurrency.dash:
return [34];
case CryptoCurrency.eos:
return [42];
case CryptoCurrency.eth:
return [42];
case CryptoCurrency.ltc:
return [34];
case CryptoCurrency.nano:
return [64, 65];
case CryptoCurrency.trx:
return [34];
case CryptoCurrency.usdt:
return [42];
case CryptoCurrency.xlm:
return [56];
case CryptoCurrency.xrp:
return [34];
default:
return [];
}
}
}

View file

@ -0,0 +1,11 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/validator.dart';
class ContactNameValidator extends TextValidator {
ContactNameValidator()
: super(
errorMessage: S.current.error_text_contact_name,
pattern: '''[^`,'"]''',
minLength: 1,
maxLength: 32);
}

View file

@ -0,0 +1,38 @@
import 'package:hive/hive.dart';
import 'package:cake_wallet/store/contact_list_store.dart';
import 'package:cake_wallet/src/domain/common/contact.dart';
class ContactService {
ContactService(this.contactSource, this.contactListStore) {
_forceUpdateContactListStore();
}
final Box<Contact> contactSource;
final ContactListStore contactListStore;
Future add(Contact contact) async {
await contactSource.add(contact);
contactListStore.contacts.add(contact);
}
Future update(Contact contact) async {
await contact.save();
final index = contactListStore.contacts.indexOf(contact) ?? -1;
if (index >= 0) {
_forceUpdateContactListStore();
} else {
contactListStore.contacts.add(contact);
}
}
Future delete(Contact contact) async {
await contact.delete();
contactListStore.contacts.remove(contact);
}
void _forceUpdateContactListStore() {
contactListStore.contacts.clear();
contactListStore.contacts.addAll(contactSource.values);
}
}

25
lib/core/key_service.dart Normal file
View file

@ -0,0 +1,25 @@
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:cake_wallet/src/domain/common/secret_store_key.dart';
import 'package:cake_wallet/src/domain/common/encrypt.dart';
class KeyService {
KeyService(this._secureStorage);
final FlutterSecureStorage _secureStorage;
Future<String> getWalletPassword({String walletName}) async {
final key = generateStoreKeyFor(
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
final encodedPassword = await _secureStorage.read(key: key);
return decodeWalletPassword(password: encodedPassword);
}
Future<void> saveWalletPassword({String walletName, String password}) async {
final key = generateStoreKeyFor(
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
final encodedPassword = encodeWalletPassword(password: password);
await _secureStorage.write(key: key, value: encodedPassword);
}
}

View file

@ -0,0 +1,13 @@
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
class MoneroLabelValidator extends TextValidator {
MoneroLabelValidator({@required CryptoCurrency type})
: super(
errorMessage: S.current.error_text_account_name,
pattern: '^[a-zA-Z0-9_]{1,15}\$',
minLength: 1,
maxLength: 15);
}

View file

@ -0,0 +1,10 @@
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/generated/i18n.dart';
class NodeAddressValidator extends TextValidator {
NodeAddressValidator()
: super(
errorMessage: S.current.error_text_node_address,
pattern:
'^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\$|^[0-9a-zA-Z.]+\$');
}

View file

@ -0,0 +1,12 @@
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/validator.dart';
class NodePortValidator extends TextValidator {
NodePortValidator()
: super(
errorMessage: S.current.error_text_node_port,
minLength: 1,
maxLength: 5,
pattern: '^[0-9]');
}

View file

@ -12,11 +12,16 @@ abstract class Validator<T> {
class TextValidator extends Validator<String> {
TextValidator(
{this.minLength, this.maxLength, this.pattern, String errorMessage})
{this.minLength,
this.maxLength,
this.pattern,
this.length,
String errorMessage})
: super(errorMessage: errorMessage);
final int minLength;
final int maxLength;
final List<int> length;
String pattern;
@override
@ -25,8 +30,9 @@ class TextValidator extends Validator<String> {
return true;
}
return value.length > minLength &&
(maxLength > 0 ? (value.length <= maxLength) : true) &&
return value.length > (minLength ?? 0) &&
(length?.contains(value.length) ?? true) &&
((maxLength ?? 0) > 0 ? (value.length <= maxLength) : true) &&
(pattern != null ? match(value) : true);
}

View file

@ -1,19 +1,31 @@
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/core/transaction_history.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/domain/common/sync_status.dart';
import 'package:cake_wallet/src/domain/common/node.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
abstract class WalletBase<BalaceType> {
WalletType type;
CryptoCurrency currency;
String get name;
String address;
BalaceType balance;
SyncStatus syncStatus;
String get seed;
Object get keys;
TransactionHistoryBase transactionHistory;
String get id => walletTypeToString(type).toLowerCase() + '_' + name;
Future<void> connectToNode({@required Node node});
Future<void> startSync();

View file

@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/core/generate_wallet_password.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/core/wallet_credentials.dart';
@ -8,14 +9,13 @@ import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
import 'package:cake_wallet/monero/monero_wallet_service.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/src/domain/common/secret_store_key.dart';
import 'package:cake_wallet/src/domain/common/encrypt.dart';
class WalletCreationService {
WalletCreationService(
{WalletType initialType,
this.appStore,
this.secureStorage,
this.keyService,
this.sharedPreferences})
: type = initialType {
if (type != null) {
@ -27,10 +27,7 @@ class WalletCreationService {
final AppStore appStore;
final FlutterSecureStorage secureStorage;
final SharedPreferences sharedPreferences;
// final WalletService walletService;
// final Box<WalletInfo> walletInfoSource;
final KeyService keyService;
WalletService _service;
void changeWalletType({@required WalletType type}) {
@ -51,7 +48,8 @@ class WalletCreationService {
Future<void> create(WalletCredentials credentials) async {
final password = generateWalletPassword(type);
credentials.password = password;
await saveWalletPassword(password: password, walletName: credentials.name);
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
final wallet = await _service.create(credentials);
appStore.wallet = wallet;
appStore.authenticationStore.allowed();
@ -60,7 +58,8 @@ class WalletCreationService {
Future<void> restoreFromKeys(WalletCredentials credentials) async {
final password = generateWalletPassword(type);
credentials.password = password;
await saveWalletPassword(password: password, walletName: credentials.name);
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
final wallet = await _service.restoreFromKeys(credentials);
appStore.wallet = wallet;
appStore.authenticationStore.allowed();
@ -69,25 +68,10 @@ class WalletCreationService {
Future<void> restoreFromSeed(WalletCredentials credentials) async {
final password = generateWalletPassword(type);
credentials.password = password;
await saveWalletPassword(password: password, walletName: credentials.name);
await keyService.saveWalletPassword(
password: password, walletName: credentials.name);
final wallet = await _service.restoreFromSeed(credentials);
appStore.wallet = wallet;
appStore.authenticationStore.allowed();
}
Future<String> getWalletPassword({String walletName}) async {
final key = generateStoreKeyFor(
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
final encodedPassword = await secureStorage.read(key: key);
return decodeWalletPassword(password: encodedPassword);
}
Future<void> saveWalletPassword({String walletName, String password}) async {
final key = generateStoreKeyFor(
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
final encodedPassword = encodeWalletPassword(password: password);
await secureStorage.write(key: key, value: encodedPassword);
}
}

View file

@ -1,19 +1,52 @@
import 'package:cake_wallet/core/contact_service.dart';
import 'package:cake_wallet/src/domain/common/contact.dart';
import 'package:cake_wallet/src/domain/common/node.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart';
import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
import 'package:cake_wallet/src/screens/settings/settings.dart';
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
import 'package:cake_wallet/store/contact_list_store.dart';
import 'package:cake_wallet/store/node_list_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/core/auth_service.dart';
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/monero/monero_wallet.dart';
import 'package:cake_wallet/src/domain/common/wallet_info.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart';
import 'package:cake_wallet/src/screens/receive/receive_page.dart';
import 'package:cake_wallet/src/screens/send/send_page.dart';
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
import 'package:cake_wallet/view_model/address_list/address_edit_or_create_view_model.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_list_page.dart';
import 'package:cake_wallet/store/wallet_list_store.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/auth_view_model.dart';
import 'package:cake_wallet/view_model/dashboard_view_model.dart';
import 'package:cake_wallet/view_model/address_list/address_list_view_model.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
import 'package:cake_wallet/view_model/send_view_model.dart';
import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
import 'package:flutter/foundation.dart';
import 'package:get_it/get_it.dart';
import 'package:http/http.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/core/wallet_creation_service.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
@ -22,24 +55,63 @@ import 'package:cake_wallet/store/authentication_store.dart';
final getIt = GetIt.instance;
ReactionDisposer _onCurrentWalletChangeReaction;
// FIXME: Move me.
void setup() {
getIt.registerSingleton(AuthenticationStore());
getIt.registerSingleton<AppStore>(
AppStore(authenticationStore: getIt.get<AuthenticationStore>()));
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
Stream<BoxEvent> _onNodesSourceChange;
NodeListStore _nodeListStore;
NodeListStore setupNodeListStore(Box<Node> nodeSource) {
if (_nodeListStore != null) {
return _nodeListStore;
}
_nodeListStore = NodeListStore();
_nodeListStore.replaceValues(nodeSource.values);
_onNodesSourceChange = nodeSource.watch();
_onNodesSourceChange
.listen((_) => _nodeListStore.replaceValues(nodeSource.values));
return _nodeListStore;
}
Future setup(
{Box<WalletInfo> walletInfoSource,
Box<Node> nodeSource,
Box<Contact> contactSource}) async {
getIt.registerSingletonAsync<SharedPreferences>(
() => SharedPreferences.getInstance());
final settingsStore = await SettingsStoreBase.load(nodeSource: nodeSource);
getIt.registerSingleton<FlutterSecureStorage>(FlutterSecureStorage());
getIt.registerSingleton(AuthenticationStore());
getIt.registerSingleton<WalletListStore>(WalletListStore());
getIt.registerSingleton(ContactListStore());
getIt.registerSingleton(setupNodeListStore(nodeSource));
getIt.registerSingleton<SettingsStore>(settingsStore);
getIt.registerSingleton<AppStore>(AppStore(
authenticationStore: getIt.get<AuthenticationStore>(),
walletList: getIt.get<WalletListStore>(),
settingsStore: getIt.get<SettingsStore>(),
contactListStore: getIt.get<ContactListStore>(),
nodeListStore: getIt.get<NodeListStore>()));
getIt.registerSingleton<ContactService>(
ContactService(contactSource, getIt.get<AppStore>().contactListStore));
getIt.registerFactory<KeyService>(
() => KeyService(getIt.get<FlutterSecureStorage>()));
getIt.registerFactoryParam<WalletCreationService, WalletType, void>(
(type, _) => WalletCreationService(
initialType: type,
appStore: getIt.get<AppStore>(),
keyService: getIt.get<KeyService>(),
secureStorage: getIt.get<FlutterSecureStorage>(),
sharedPreferences: getIt.get<SharedPreferences>()));
getIt.registerFactoryParam<WalletNewVM, WalletType, void>((type, _) =>
WalletNewVM(getIt.get<WalletCreationService>(param1: type), type: type));
WalletNewVM(
getIt.get<WalletCreationService>(param1: type), walletInfoSource,
type: type));
getIt
.registerFactoryParam<WalletRestorationFromSeedVM, List, void>((args, _) {
@ -48,14 +120,12 @@ void setup() {
final mnemonic = args[2] as String;
return WalletRestorationFromSeedVM(
getIt.get<WalletCreationService>(param1: type),
type: type,
language: language,
seed: mnemonic);
getIt.get<WalletCreationService>(param1: type), walletInfoSource,
type: type, language: language, seed: mnemonic);
});
getIt.registerFactory<AddressListViewModel>(
() => AddressListViewModel(wallet: getIt.get<AppStore>().wallet));
getIt.registerFactory<WalletAddressListViewModel>(
() => WalletAddressListViewModel(wallet: getIt.get<AppStore>().wallet));
getIt.registerFactory(
() => DashboardViewModel(appStore: getIt.get<AppStore>()));
@ -68,38 +138,122 @@ void setup() {
authService: getIt.get<AuthService>(),
sharedPreferences: getIt.get<SharedPreferences>()));
getIt.registerFactory<AuthPage>(() => AuthPage(
authViewModel: getIt.get<AuthViewModel>(),
onAuthenticationFinished: (isAuthenticated, __) {
if (isAuthenticated) {
getIt.get<AuthenticationStore>().allowed();
}
},
closable: false));
getIt.registerFactory<AuthPage>(
() => AuthPage(
authViewModel: getIt.get<AuthViewModel>(),
onAuthenticationFinished: (isAuthenticated, __) {
if (isAuthenticated) {
getIt.get<AuthenticationStore>().allowed();
}
},
closable: false),
instanceName: 'login');
getIt.registerFactory<DashboardPage>(() => DashboardPage(
walletViewModel: getIt.get<DashboardViewModel>(),
));
getIt
.registerFactoryParam<AuthPage, void Function(bool, AuthPageState), void>(
(onAuthFinished, _) => AuthPage(
authViewModel: getIt.get<AuthViewModel>(),
onAuthenticationFinished: onAuthFinished,
closable: false));
getIt.registerFactory<ReceivePage>(() =>
ReceivePage(addressListViewModel: getIt.get<AddressListViewModel>()));
getIt.registerFactory<DashboardPage>(
() => DashboardPage(walletViewModel: getIt.get<DashboardViewModel>()));
getIt.registerFactoryParam<AddressEditOrCreateViewModel, dynamic, void>(
(dynamic item, _) => AddressEditOrCreateViewModel(
getIt.registerFactory<ReceivePage>(() => ReceivePage(
addressListViewModel: getIt.get<WalletAddressListViewModel>()));
getIt.registerFactoryParam<WalletAddressEditOrCreateViewModel, dynamic, void>(
(dynamic item, _) => WalletAddressEditOrCreateViewModel(
wallet: getIt.get<AppStore>().wallet, item: item));
getIt.registerFactoryParam<AddressEditOrCreatePage, dynamic, void>(
(dynamic item, _) => AddressEditOrCreatePage(
addressEditOrCreateViewModel:
getIt.get<AddressEditOrCreateViewModel>(param1: item)));
getIt.get<WalletAddressEditOrCreateViewModel>(param1: item)));
final appStore = getIt.get<AppStore>();
getIt.registerFactory<SendViewModel>(() => SendViewModel(
getIt.get<AppStore>().wallet, getIt.get<AppStore>().settingsStore));
_onCurrentWalletChangeReaction ??=
reaction((_) => appStore.wallet, (WalletBase wallet) async {
print('Wallet name ${wallet.name}');
await getIt
.get<SharedPreferences>()
.setString('current_wallet_name', wallet.name);
getIt.registerFactory(
() => SendPage(sendViewModel: getIt.get<SendViewModel>()));
getIt.registerFactory(() => WalletListViewModel(
walletInfoSource, getIt.get<AppStore>(), getIt.get<KeyService>()));
getIt.registerFactory(() =>
WalletListPage(walletListViewModel: getIt.get<WalletListViewModel>()));
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
if (wallet is MoneroWallet) {
return MoneroAccountListViewModel(wallet);
}
// FIXME: throw exception.
return null;
});
getIt.registerFactory(() => MoneroAccountListPage(
accountListViewModel: getIt.get<MoneroAccountListViewModel>()));
getIt.registerFactory(() {
final wallet = getIt.get<AppStore>().wallet;
if (wallet is MoneroWallet) {
return MoneroAccountEditOrCreateViewModel(wallet.accountList);
}
// FIXME: throw exception.
return null;
});
getIt.registerFactory(() => MoneroAccountEditOrCreatePage(
moneroAccountCreationViewModel:
getIt.get<MoneroAccountEditOrCreateViewModel>()));
getIt.registerFactory(
() => SettingsViewModel(getIt.get<AppStore>().settingsStore));
getIt.registerFactory(() => SettingsPage(getIt.get<SettingsViewModel>()));
getIt
.registerFactory(() => WalletSeedViewModel(getIt.get<AppStore>().wallet));
getIt.registerFactoryParam<WalletSeedPage, VoidCallback, void>(
(VoidCallback callback, _) => WalletSeedPage(
getIt.get<WalletSeedViewModel>(),
onCloseCallback: callback));
getIt
.registerFactory(() => WalletKeysViewModel(getIt.get<AppStore>().wallet));
getIt.registerFactory(() => WalletKeysPage(getIt.get<WalletKeysViewModel>()));
getIt.registerFactoryParam<ContactViewModel, Contact, void>(
(Contact contact, _) => ContactViewModel(
getIt.get<ContactService>(), getIt.get<AppStore>().wallet,
contact: contact));
getIt.registerFactory(() => ContactListViewModel(
getIt.get<AppStore>().contactListStore, getIt.get<ContactService>()));
getIt.registerFactory(
() => ContactListPage(getIt.get<ContactListViewModel>()));
getIt.registerFactoryParam<ContactPage, Contact, void>((Contact contact, _) =>
ContactPage(getIt.get<ContactViewModel>(param1: contact)));
getIt.registerFactory(() => NodeListViewModel(
getIt.get<AppStore>().nodeListStore,
nodeSource,
getIt.get<AppStore>().wallet));
getIt.registerFactory(() => NodeListPage(getIt.get<NodeListViewModel>()));
getIt.registerFactory(() =>
NodeCreateOrEditViewModel(nodeSource, getIt.get<AppStore>().wallet));
getIt.registerFactory(
() => NodeCreateOrEditPage(getIt.get<NodeCreateOrEditViewModel>()));
}

View file

@ -24,6 +24,7 @@ class S implements WidgetsLocalizations {
String get account => "Account";
String get accounts => "Accounts";
String get accounts_subaddresses => "Accounts and subaddresses";
String get addresses => "Addresses";
String get add => "Add";
String get add_new_node => "Add new node";
String get add_new_word => "Add new word";
@ -121,6 +122,8 @@ class S implements WidgetsLocalizations {
String get payment_id => "Payment ID: ";
String get pending => " (pending)";
String get pin_is_incorrect => "PIN is incorrect";
String get placeholder_contacts => "Your contacts will be displayed here";
String get placeholder_transactions => "Your transactions will be displayed here";
String get please_make_selection => "Please make selection below to create or recover your wallet.";
String get please_select => "Please select:";
String get please_try_to_connect_to_another_node => "Please try to connect to another node";
@ -392,6 +395,8 @@ class $de extends S {
@override
String get wallet_list_create_new_wallet => "Neue Wallet erstellen";
@override
String get placeholder_contacts => "Ihre Kontakte werden hier angezeigt";
@override
String get card_address => "Adresse:";
@override
String get seed_language_portuguese => "Portugiesisch";
@ -462,6 +467,8 @@ class $de extends S {
@override
String get settings_display_balance_as => "Kontostand anzeigen als";
@override
String get placeholder_transactions => "Ihre Transaktionen werden hier angezeigt";
@override
String get trade_details_provider => "Anbieter";
@override
String get seed_language_japanese => "Japanisch";
@ -636,6 +643,8 @@ class $de extends S {
@override
String get accounts_subaddresses => "Konten und Unteradressen";
@override
String get addresses => "Addressen";
@override
String get wallet_name => "Walletname";
@override
String get error_text_payment_id => "Die Zahlungs-ID kann nur 16 bis 64 hexadezimale Zeichen enthalten";
@ -1008,6 +1017,8 @@ class $hi extends S {
@override
String get wallet_list_create_new_wallet => "नया बटुआ बनाएँ";
@override
String get placeholder_contacts => "आपके संपर्क यहां प्रदर्शित होंगे";
@override
String get card_address => "पता:";
@override
String get seed_language_portuguese => "पुर्तगाली";
@ -1078,6 +1089,8 @@ class $hi extends S {
@override
String get settings_display_balance_as => "के रूप में संतुलन प्रदर्शित करें";
@override
String get placeholder_transactions => "आपके लेनदेन यहां प्रदर्शित होंगे";
@override
String get trade_details_provider => "प्रदाता";
@override
String get seed_language_japanese => "जापानी";
@ -1252,6 +1265,8 @@ class $hi extends S {
@override
String get accounts_subaddresses => "लेखा और उपदेस";
@override
String get addresses => "पतों";
@override
String get wallet_name => "बटुए का नाम";
@override
String get error_text_payment_id => "पेमेंट आईडी केवल हेक्स में 16 से 64 चार्ट तक हो सकती है";
@ -1624,6 +1639,8 @@ class $ru extends S {
@override
String get wallet_list_create_new_wallet => "Создать новый кошелёк";
@override
String get placeholder_contacts => "Ваши контакты будут отображаться здесь";
@override
String get card_address => "Адрес:";
@override
String get seed_language_portuguese => "Португальский";
@ -1694,6 +1711,8 @@ class $ru extends S {
@override
String get settings_display_balance_as => "Отображать баланс как";
@override
String get placeholder_transactions => "Ваши транзакции будут отображаться здесь";
@override
String get trade_details_provider => "Провайдер";
@override
String get seed_language_japanese => "Японский";
@ -1868,6 +1887,8 @@ class $ru extends S {
@override
String get accounts_subaddresses => "Аккаунты и субадреса";
@override
String get addresses => "Адреса";
@override
String get wallet_name => "Имя кошелька";
@override
String get error_text_payment_id => "Идентификатор платежа может содержать от 16 до 64 символов в hex";
@ -2240,6 +2261,8 @@ class $ko extends S {
@override
String get wallet_list_create_new_wallet => "새 월렛 만들기";
@override
String get placeholder_contacts => "연락처가 여기에 표시됩니다";
@override
String get card_address => "주소:";
@override
String get seed_language_portuguese => "포르투갈 인";
@ -2310,6 +2333,8 @@ class $ko extends S {
@override
String get settings_display_balance_as => "잔액 표시";
@override
String get placeholder_transactions => "거래가 여기에 표시됩니다";
@override
String get trade_details_provider => "공급자";
@override
String get seed_language_japanese => "일본어";
@ -2484,6 +2509,8 @@ class $ko extends S {
@override
String get accounts_subaddresses => "계정 및 하위 주소";
@override
String get addresses => "구애";
@override
String get wallet_name => "지갑 이름";
@override
String get error_text_payment_id => "지불 ID는 16 ~ 64 자의 16 진 문자 만 포함 할 수 있습니다";
@ -2856,6 +2883,8 @@ class $pt extends S {
@override
String get wallet_list_create_new_wallet => "Criar nova carteira";
@override
String get placeholder_contacts => "Seus contatos serão exibidos aqui";
@override
String get card_address => "Endereço:";
@override
String get seed_language_portuguese => "Português";
@ -2926,6 +2955,8 @@ class $pt extends S {
@override
String get settings_display_balance_as => "Saldo a exibir";
@override
String get placeholder_transactions => "Suas transações serão exibidas aqui";
@override
String get trade_details_provider => "Provedor";
@override
String get seed_language_japanese => "Japonês";
@ -3100,6 +3131,8 @@ class $pt extends S {
@override
String get accounts_subaddresses => "Contas e sub-endereços";
@override
String get addresses => "Endereços";
@override
String get wallet_name => "Nome da carteira";
@override
String get error_text_payment_id => "O ID de pagamento pode conter apenas de 16 a 64 caracteres em hexadecimal";
@ -3472,6 +3505,8 @@ class $uk extends S {
@override
String get wallet_list_create_new_wallet => "Створити новий гаманець";
@override
String get placeholder_contacts => "Тут будуть показані ваші контакти";
@override
String get card_address => "Адреса:";
@override
String get seed_language_portuguese => "Португальська";
@ -3542,6 +3577,8 @@ class $uk extends S {
@override
String get settings_display_balance_as => "Відображати баланс як";
@override
String get placeholder_transactions => "Тут відображатимуться ваші транзакції";
@override
String get trade_details_provider => "Провайдер";
@override
String get seed_language_japanese => "Японська";
@ -3716,6 +3753,8 @@ class $uk extends S {
@override
String get accounts_subaddresses => "Акаунти та субадреси";
@override
String get addresses => "Адреси";
@override
String get wallet_name => "Ім'я гаманця";
@override
String get error_text_payment_id => "Ідентифікатор платежу може містити від 16 до 64 символів в hex";
@ -4088,6 +4127,8 @@ class $ja extends S {
@override
String get wallet_list_create_new_wallet => "新しいウォレットを作成";
@override
String get placeholder_contacts => "連絡先はここに表示されます";
@override
String get card_address => "住所:";
@override
String get seed_language_portuguese => "ポルトガル語";
@ -4158,6 +4199,8 @@ class $ja extends S {
@override
String get settings_display_balance_as => "残高を表示";
@override
String get placeholder_transactions => "あなたの取引はここに表示されます";
@override
String get trade_details_provider => "プロバイダー";
@override
String get seed_language_japanese => "日本語";
@ -4332,6 +4375,8 @@ class $ja extends S {
@override
String get accounts_subaddresses => "アカウントとサブアドレス";
@override
String get addresses => "アドレス";
@override
String get wallet_name => "ウォレット名";
@override
String get error_text_payment_id => "支払いIDには、16進数で16〜64文字しか含めることができません";
@ -4708,6 +4753,8 @@ class $pl extends S {
@override
String get wallet_list_create_new_wallet => "Utwórz nowy portfel";
@override
String get placeholder_contacts => "Twoje kontakty zostaną wyświetlone tutaj";
@override
String get card_address => "Adres:";
@override
String get seed_language_portuguese => "Portugalski";
@ -4778,6 +4825,8 @@ class $pl extends S {
@override
String get settings_display_balance_as => "Wyświetl saldo jako";
@override
String get placeholder_transactions => "Twoje transakcje zostaną wyświetlone tutaj";
@override
String get trade_details_provider => "Dostawca";
@override
String get seed_language_japanese => "Japoński";
@ -4952,6 +5001,8 @@ class $pl extends S {
@override
String get accounts_subaddresses => "Konta i podadresy";
@override
String get addresses => "Adresy";
@override
String get wallet_name => "Nazwa portfela";
@override
String get error_text_payment_id => "ID może zawierać od 16 do 64 znaków w formacie szesnastkowym";
@ -5324,6 +5375,8 @@ class $es extends S {
@override
String get wallet_list_create_new_wallet => "Crear nueva billetera";
@override
String get placeholder_contacts => "Tus contactos se mostrarán aquí";
@override
String get card_address => "Dirección:";
@override
String get seed_language_portuguese => "Portugués";
@ -5394,6 +5447,8 @@ class $es extends S {
@override
String get settings_display_balance_as => "Mostrar saldo como";
@override
String get placeholder_transactions => "Sus transacciones se mostrarán aquí";
@override
String get trade_details_provider => "Proveedor";
@override
String get seed_language_japanese => "Japonés";
@ -5568,6 +5623,8 @@ class $es extends S {
@override
String get accounts_subaddresses => "Cuentas y subdirecciones";
@override
String get addresses => "Direcciones";
@override
String get wallet_name => "Nombre de la billetera";
@override
String get error_text_payment_id => "La ID de pago solo puede contener de 16 a 64 caracteres en hexadecimal";
@ -5940,6 +5997,8 @@ class $nl extends S {
@override
String get wallet_list_create_new_wallet => "Maak een nieuwe portemonnee";
@override
String get placeholder_contacts => "Je contacten worden hier weergegeven";
@override
String get card_address => "Adres:";
@override
String get seed_language_portuguese => "Portugees";
@ -6010,6 +6069,8 @@ class $nl extends S {
@override
String get settings_display_balance_as => "Toon saldo als";
@override
String get placeholder_transactions => "Uw transacties worden hier weergegeven";
@override
String get trade_details_provider => "Leverancier";
@override
String get seed_language_japanese => "Japans";
@ -6184,6 +6245,8 @@ class $nl extends S {
@override
String get accounts_subaddresses => "Accounts en subadressen";
@override
String get addresses => "Adressen";
@override
String get wallet_name => "Portemonnee naam";
@override
String get error_text_payment_id => "Betalings-ID kan alleen 16 tot 64 tekens bevatten in hexadecimale volgorde";
@ -6556,6 +6619,8 @@ class $zh extends S {
@override
String get wallet_list_create_new_wallet => "创建新钱包";
@override
String get placeholder_contacts => "您的聯繫人將顯示在這裡";
@override
String get card_address => "地址:";
@override
String get seed_language_portuguese => "葡萄牙語";
@ -6626,6 +6691,8 @@ class $zh extends S {
@override
String get settings_display_balance_as => "将余额显示为";
@override
String get placeholder_transactions => "您的交易將顯示在這裡";
@override
String get trade_details_provider => "提供者";
@override
String get seed_language_japanese => "日本";
@ -6800,6 +6867,8 @@ class $zh extends S {
@override
String get accounts_subaddresses => "帳戶和子地址";
@override
String get addresses => "地址";
@override
String get wallet_name => "钱包名称";
@override
String get error_text_payment_id => "付款ID只能包含16到64个字符十六进制";

View file

@ -54,8 +54,6 @@ import 'package:cake_wallet/src/stores/seed_language/seed_language_store.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
setup();
final appDir = await getApplicationDocumentsDirectory();
Hive.init(appDir.path);
Hive.registerAdapter(ContactAdapter());
@ -87,6 +85,13 @@ void main() async {
final exchangeTemplates =
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
walletInfoSource: walletInfoSource,
contactSource: contacts,
initialMigrationVersion: 3);
final sharedPreferences = await SharedPreferences.getInstance();
final walletService = WalletService();
final walletListService = WalletListService(
@ -96,15 +101,6 @@ void main() async {
sharedPreferences: sharedPreferences);
final userService = UserService(
sharedPreferences: sharedPreferences, secureStorage: secureStorage);
// final authenticationStore = AuthenticationStore(userService: userService);
await initialSetup(
sharedPreferences: sharedPreferences,
walletListService: walletListService,
nodes: nodes,
// authStore: authenticationStore,
initialMigrationVersion: 2);
final settingsStore = await SettingsStoreBase.load(
nodes: nodes,
sharedPreferences: sharedPreferences,
@ -129,7 +125,6 @@ void main() async {
final walletCreationService = WalletCreationService();
final authService = AuthService();
setReactions(
settingsStore: settingsStore,
priceStore: priceStore,
@ -164,23 +159,20 @@ void main() async {
}
Future<void> initialSetup(
{WalletListService walletListService,
SharedPreferences sharedPreferences,
Box<Node> nodes,
// AuthenticationStore authStore,
int initialMigrationVersion = 1,
WalletType initialWalletType = WalletType.bitcoin}) async {
await walletListService.changeWalletManger(walletType: initialWalletType);
{@required SharedPreferences sharedPreferences,
@required Box<Node> nodes,
@required Box<WalletInfo> walletInfoSource,
@required Box<Contact> contactSource,
int initialMigrationVersion = 3}) async {
await defaultSettingsMigration(
version: initialMigrationVersion,
sharedPreferences: sharedPreferences,
nodes: nodes);
// await authStore.started();
await setup(
walletInfoSource: walletInfoSource,
nodeSource: nodes,
contactSource: contactSource);
await bootstrap();
// final authenticationStore = getIt.get<AuthenticationStore>();
// FIXME
// authenticationStore.state = AuthenticationState.denied;
monero_wallet.onStartup();
}
@ -216,8 +208,6 @@ class MaterialAppWithTheme extends StatelessWidget {
final syncStore = Provider.of<SyncStore>(context);
final balanceStore = Provider.of<BalanceStore>(context);
final theme = Provider.of<ThemeChanger>(context);
final statusBarColor =
settingsStore.isDarkTheme ? Colors.black : Colors.white;
final currentLanguage = Provider.of<Language>(context);
final contacts = Provider.of<Box<Contact>>(context);
final nodes = Provider.of<Box<Node>>(context);
@ -225,8 +215,17 @@ class MaterialAppWithTheme extends StatelessWidget {
final transactionDescriptions =
Provider.of<Box<TransactionDescription>>(context);
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(statusBarColor: statusBarColor));
final statusBarColor =
settingsStore.isDarkTheme ? Colors.black : Colors.white;
final statusBarBrightness =
settingsStore.isDarkTheme ? Brightness.light : Brightness.dark;
final statusBarIconBrightness =
settingsStore.isDarkTheme ? Brightness.light : Brightness.dark;
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: statusBarColor,
statusBarBrightness: statusBarBrightness,
statusBarIconBrightness: statusBarIconBrightness));
return MaterialApp(
debugShowCheckedModeBanner: false,
@ -258,4 +257,4 @@ class MaterialAppWithTheme extends StatelessWidget {
authenticationStore: getIt.get<AuthenticationStore>(),
));
}
}
}

View file

@ -0,0 +1,76 @@
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/src/domain/monero/account.dart';
import 'package:cw_monero/account_list.dart' as account_list;
part 'monero_account_list.g.dart';
class MoneroAccountList = MoneroAccountListBase with _$MoneroAccountList;
abstract class MoneroAccountListBase with Store {
MoneroAccountListBase()
: accounts = ObservableList<Account>(),
_isRefreshing = false,
_isUpdating = false {
refresh();
print(account_list.accountSizeNative());
}
@observable
ObservableList<Account> accounts;
bool _isRefreshing;
bool _isUpdating;
Future update() async {
if (_isUpdating) {
return;
}
try {
_isUpdating = true;
refresh();
final accounts = getAll();
if (accounts.isNotEmpty) {
this.accounts.clear();
this.accounts.addAll(accounts);
}
_isUpdating = false;
} catch (e) {
_isUpdating = false;
rethrow;
}
}
List<Account> getAll() => account_list
.getAllAccount()
.map((accountRow) => Account.fromRow(accountRow))
.toList();
Future addAccount({String label}) async {
await account_list.addAccount(label: label);
await update();
}
Future setLabelAccount({int accountIndex, String label}) async {
await account_list.setLabelForAccount(
accountIndex: accountIndex, label: label);
await update();
}
void refresh() {
if (_isRefreshing) {
return;
}
try {
_isRefreshing = true;
account_list.refreshAccounts();
_isRefreshing = false;
} catch (e) {
_isRefreshing = false;
print(e);
rethrow;
}
}
}

View file

@ -1,18 +1,21 @@
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_monero/wallet.dart';
import 'package:cw_monero/wallet.dart' as monero_wallet;
import 'package:cake_wallet/monero/monero_wallet_keys.dart';
import 'package:cake_wallet/monero/monero_balance.dart';
import 'package:cake_wallet/monero/monero_transaction_history.dart';
import 'package:cake_wallet/monero/monero_subaddress_list.dart';
import 'package:cake_wallet/monero/monero_account_list.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/core/transaction_history.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/src/domain/common/sync_status.dart';
import 'package:cake_wallet/src/domain/monero/account.dart';
import 'package:cake_wallet/src/domain/monero/account_list.dart';
import 'package:cake_wallet/src/domain/monero/subaddress.dart';
import 'package:cw_monero/wallet.dart';
import 'package:cake_wallet/src/domain/common/node.dart';
import 'package:cw_monero/wallet.dart' as monero_wallet;
part 'monero_wallet.g.dart';
@ -20,15 +23,26 @@ class MoneroWallet = MoneroWalletBase with _$MoneroWallet;
abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
MoneroWalletBase({String filename, this.isRecovery = false})
: transactionHistory = MoneroTransactionHistory() {
: transactionHistory = MoneroTransactionHistory(),
accountList = MoneroAccountList(),
subaddressList = MoneroSubaddressList() {
_filename = filename;
accountList = AccountList();
subaddressList = MoneroSubaddressList();
balance = MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: 0),
unlockedBalance: monero_wallet.getFullBalance(accountIndex: 0));
currency = CryptoCurrency.xmr;
type = WalletType.monero;
_rct = reaction(
(_) => syncStatus, (SyncStatus status) => print(status.toString()));
_onAccountChangeReaction = reaction((_) => account, (Account account) {
subaddressList.update(accountIndex: account.id);
subaddress = subaddressList.subaddresses.first;
address = subaddress.address;
});
}
ReactionDisposer _rct;
@override
final MoneroTransactionHistory transactionHistory;
@ -44,26 +58,33 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
@override
String get name => _filename.split('/').last;
@override
final type = WalletType.monero;
@override
@observable
String address;
@override
String get seed => monero_wallet.getSeed();
@override
MoneroWalletKeys get keys => MoneroWalletKeys(
privateSpendKey: monero_wallet.getSecretSpendKey(),
privateViewKey: monero_wallet.getSecretViewKey(),
publicSpendKey: monero_wallet.getPublicSpendKey(),
publicViewKey: monero_wallet.getPublicViewKey());
final MoneroSubaddressList subaddressList;
final MoneroAccountList accountList;
bool isRecovery;
MoneroSubaddressList subaddressList;
AccountList accountList;
String _filename;
SyncListner _listener;
ReactionDisposer _onAccountChangeReaction;
Future<void> init() async {
await accountList.update();
account = accountList.getAll().first;
account = accountList.accounts.first;
subaddressList.update(accountIndex: account.id ?? 0);
subaddress = subaddressList.getAll().first;
balance = MoneroBalance(
@ -76,10 +97,13 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
void close() {
_listener?.stop();
_onAccountChangeReaction?.reaction?.dispose();
}
@override
Future<void> connectToNode({@required Node node}) async {
final node = Node(uri: 'xmr-node-uk.cakewallet.com:18081');
try {
syncStatus = ConnectingSyncStatus();
await monero_wallet.setupNode(
@ -147,6 +171,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
_listener?.stop();
_listener = monero_wallet.setListeners(
_onNewBlock, _onNeedToRefresh, _onNewTransaction);
_listener.start();
}
void _askForUpdateBalance() {

View file

@ -0,0 +1,12 @@
class MoneroWalletKeys {
const MoneroWalletKeys(
{this.privateSpendKey,
this.privateViewKey,
this.publicSpendKey,
this.publicViewKey});
final String publicViewKey;
final String privateViewKey;
final String publicSpendKey;
final String privateSpendKey;
}

View file

@ -12,7 +12,9 @@ class Palette {
static const Color blue = Color.fromRGBO(88, 143, 252, 1.0);
static const Color darkLavender = Color.fromRGBO(225, 238, 250, 1.0);
static const Color nightBlue = Color.fromRGBO(46, 57, 96, 1.0);
// FIXME: Rename.
static const Color eee = Color.fromRGBO(236, 239, 245, 1.0);
static const Color xxx = Color.fromRGBO(72, 89, 109, 1);
}
class PaletteDark {
@ -28,4 +30,7 @@ class PaletteDark {
static const Color headerNightBlue = Color.fromRGBO(41, 52, 84, 1.0); // menuHeader
static const Color lightNightBlue = Color.fromRGBO(48, 59, 95, 1.0); // menuList
static const Color moderatePurpleBlue = Color.fromRGBO(57, 74, 95, 1.0); // selectButtonText
// FIXME: Rename.
static const Color eee = Color.fromRGBO(236, 239, 245, 1.0);
static const Color xxx = Color.fromRGBO(72, 89, 109, 1);
}

View file

@ -1,9 +1,13 @@
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/src/domain/common/sync_status.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/di.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet_service.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/monero/monero_wallet_service.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/authentication_store.dart';
@ -11,22 +15,15 @@ import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/src/domain/common/secret_store_key.dart';
import 'package:cake_wallet/src/domain/common/encrypt.dart';
// FIXME: move me
Future<String> getWalletPassword({String walletName}) async {
final secureStorage = getIt.get<FlutterSecureStorage>();
final key = generateStoreKeyFor(
key: SecretStoreKey.moneroWalletPassword, walletName: walletName);
final encodedPassword = await secureStorage.read(key: key);
return decodeWalletPassword(password: encodedPassword);
}
// FIXME: move me
Future<void> loadCurrentWallet() async {
final appStore = getIt.get<AppStore>();
final name = getIt.get<SharedPreferences>().getString('current_wallet_name');
final type = WalletType.monero; // FIXME
final password = await getWalletPassword(walletName: name);
final typeRaw =
getIt.get<SharedPreferences>().getInt('current_wallet_type') ?? 0;
final type = deserializeFromInt(typeRaw);
final password =
await getIt.get<KeyService>().getWalletPassword(walletName: name);
WalletService _service;
switch (type) {
@ -45,6 +42,8 @@ Future<void> loadCurrentWallet() async {
}
ReactionDisposer _initialAuthReaction;
ReactionDisposer _onCurrentWalletChangeReaction;
ReactionDisposer _onWalletSyncStatusChangeReaction;
Future<void> bootstrap() async {
final authenticationStore = getIt.get<AuthenticationStore>();
@ -63,4 +62,24 @@ Future<void> bootstrap() async {
await loadCurrentWallet();
}
});
_onCurrentWalletChangeReaction ??=
reaction((_) => getIt.get<AppStore>().wallet, (WalletBase wallet) async {
print('Wallet name ${wallet.name}');
_onWalletSyncStatusChangeReaction?.reaction?.dispose();
_onWalletSyncStatusChangeReaction = when(
(_) => wallet.syncStatus is ConnectedSyncStatus,
() async => await wallet.startSync());
await getIt
.get<SharedPreferences>()
.setString('current_wallet_name', wallet.name);
await getIt
.get<SharedPreferences>()
.setInt('current_wallet_type', serializeToInt(wallet.type));
await wallet.connectToNode(node: null);
});
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
import 'package:cake_wallet/view_model/wallet_restoration_from_seed_vm.dart';
import 'package:flutter/cupertino.dart';
@ -55,7 +56,7 @@ import 'package:cake_wallet/src/stores/price/price_store.dart';
// MARK: Import screens
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/nodes/new_node_page.dart';
import 'package:cake_wallet/src/screens/nodes/node_create_or_edit_page.dart';
import 'package:cake_wallet/src/screens/nodes/nodes_list_page.dart';
import 'package:cake_wallet/src/screens/receive/receive_page.dart';
import 'package:cake_wallet/src/screens/subaddress/address_edit_or_create_page.dart';
@ -70,10 +71,10 @@ import 'package:cake_wallet/src/screens/send/send_page.dart';
import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
import 'package:cake_wallet/src/screens/seed_language/seed_language_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
import 'package:cake_wallet/src/screens/accounts/account_page.dart';
import 'package:cake_wallet/src/screens/address_book/address_book_page.dart';
import 'package:cake_wallet/src/screens/address_book/contact_page.dart';
import 'package:cake_wallet/src/screens/show_keys/show_keys_page.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_edit_or_create_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_list_page.dart';
import 'package:cake_wallet/src/screens/contact/contact_page.dart';
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart';
import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart';
import 'package:cake_wallet/src/screens/subaddress/subaddress_list_page.dart';
@ -86,7 +87,6 @@ import 'package:cake_wallet/src/screens/faq/faq_page.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
import 'package:cake_wallet/src/screens/auth/create_unlock_page.dart';
import 'package:cake_wallet/src/screens/auth/create_login_page.dart';
import 'package:cake_wallet/src/screens/seed/create_seed_page.dart';
import 'package:cake_wallet/src/screens/dashboard/create_dashboard_page.dart';
import 'package:cake_wallet/src/screens/welcome/create_welcome_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart';
@ -214,10 +214,8 @@ class Router {
case Routes.seed:
return MaterialPageRoute<void>(
builder: (_) => createSeedPage(
settingsStore: settingsStore,
walletService: walletService,
callback: settings.arguments as void Function()));
builder: (_) => getIt.get<WalletSeedPage>(
param1: settings.arguments as VoidCallback));
case Routes.restoreWalletFromSeed:
final args = settings.arguments as List<dynamic>;
@ -228,13 +226,7 @@ class Router {
return CupertinoPageRoute<void>(
builder: (_) =>
ProxyProvider<AuthenticationStore, WalletRestorationStore>(
update: (_, authStore, __) => WalletRestorationStore(
authStore: authStore,
sharedPreferences: sharedPreferences,
walletListService: walletListService),
child: RestoreWalletFromSeedPage(
type: type, language: language)));
RestoreWalletFromSeedPage(type: type, language: language));
case Routes.restoreWalletFromKeys:
final args = settings.arguments as List<dynamic>;
@ -267,23 +259,7 @@ class Router {
case Routes.send:
return CupertinoPageRoute<void>(
fullscreenDialog: true,
builder: (_) => MultiProvider(providers: [
ProxyProvider<SettingsStore, BalanceStore>(
update: (_, settingsStore, __) => BalanceStore(
walletService: walletService,
settingsStore: settingsStore,
priceStore: priceStore),
),
Provider(
create: (_) => SyncStore(walletService: walletService),
),
Provider(
create: (_) => SendStore(
walletService: walletService,
priceStore: priceStore,
transactionDescriptions: transactionDescriptions)),
], child: SendPage()));
fullscreenDialog: true, builder: (_) => getIt.get<SendPage>());
case Routes.sendTemplate:
return CupertinoPageRoute<void>(
@ -330,25 +306,13 @@ class Router {
case Routes.walletList:
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => Provider(
create: (_) => WalletListStore(
walletListService: walletListService,
walletService: walletService),
child: WalletListPage()));
builder: (_) => getIt.get<WalletListPage>());
case Routes.auth:
return null;
// return MaterialPageRoute<void>(
// fullscreenDialog: true,
// builder: (_) => Provider(
// create: (_) => AuthStore(
// sharedPreferences: sharedPreferences,
// userService: userService,
// walletService: walletService),
// child: AuthPage(
// onAuthenticationFinished:
// settings.arguments as OnAuthenticationFinished),
// ));
return MaterialPageRoute<void>(
fullscreenDialog: true,
builder: (_) => getIt.get<AuthPage>(
param1: settings.arguments as OnAuthenticationFinished));
case Routes.unlock:
return MaterialPageRoute<void>(
@ -361,17 +325,12 @@ class Router {
settings.arguments as OnAuthenticationFinished));
case Routes.nodeList:
return CupertinoPageRoute<void>(builder: (context) {
return Provider(
create: (_) => NodeListStore(nodesSource: nodes),
child: NodeListPage());
});
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<NodeListPage>());
case Routes.newNode:
return CupertinoPageRoute<void>(
builder: (_) => Provider<NodeListStore>(
create: (_) => NodeListStore(nodesSource: nodes),
child: NewNodePage()));
builder: (_) => getIt.get<NodeCreateOrEditPage>());
case Routes.login:
return CupertinoPageRoute<void>(builder: (context) {
@ -386,59 +345,25 @@ class Router {
});
case Routes.accountCreation:
return CupertinoPageRoute<String>(builder: (context) {
return Provider(
create: (_) => AccountListStore(walletService: walletService),
child: AccountPage(account: settings.arguments as Account));
});
return CupertinoPageRoute<String>(
builder: (_) => getIt.get<MoneroAccountEditOrCreatePage>());
case Routes.addressBook:
return MaterialPageRoute<void>(builder: (context) {
return MultiProvider(
providers: [
Provider(
create: (_) =>
AccountListStore(walletService: walletService)),
Provider(create: (_) => AddressBookStore(contacts: contacts))
],
child: AddressBookPage(),
);
});
return MaterialPageRoute<void>(
builder: (_) => getIt.get<ContactListPage>());
case Routes.pickerAddressBook:
return MaterialPageRoute<void>(builder: (context) {
return MultiProvider(
providers: [
Provider(
create: (_) =>
AccountListStore(walletService: walletService)),
Provider(create: (_) => AddressBookStore(contacts: contacts))
],
child: AddressBookPage(isEditable: false),
);
});
return MaterialPageRoute<void>(
builder: (_) => getIt.get<ContactListPage>());
case Routes.addressBookAddContact:
return CupertinoPageRoute<void>(builder: (context) {
return MultiProvider(
providers: [
Provider(
create: (_) =>
AccountListStore(walletService: walletService)),
Provider(create: (_) => AddressBookStore(contacts: contacts))
],
child: ContactPage(contact: settings.arguments as Contact),
);
});
return CupertinoPageRoute<void>(
builder: (_) =>
getIt.get<ContactPage>(param1: settings.arguments as Contact));
case Routes.showKeys:
return MaterialPageRoute<void>(
builder: (context) {
return Provider(
create: (_) => WalletKeysStore(walletService: walletService),
child: ShowKeysPage(),
);
},
builder: (_) => getIt.get<WalletKeysPage>(),
fullscreenDialog: true);
case Routes.exchangeTrade:
@ -538,9 +463,7 @@ class Router {
case Routes.settings:
return MaterialPageRoute<void>(
builder: (_) => Provider(
create: (_) => NodeListStore(nodesSource: nodes),
child: SettingsPage()));
builder: (_) => getIt.get<SettingsPage>());
case Routes.rescan:
return MaterialPageRoute<void>(

View file

@ -19,7 +19,7 @@ class Routes {
static const disclaimer = '/disclaimer';
static const readDisclaimer = '/read_disclaimer';
static const seedLanguage = '/seed_language';
static const walletList = '/wallet_list';
static const walletList = '/view_model.wallet_list';
static const auth = '/auth';
static const nodeList = '/node_list';
static const newNode = '/new_node_list';

View file

@ -3,4 +3,7 @@ import 'package:cake_wallet/src/domain/common/crypto_amount_format.dart';
const bitcoinAmountDivider = 100000000;
double bitcoinAmountToDouble({int amount}) =>
cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
int doubleToBitcoinAmount(double amount) =>
(amount * bitcoinAmountDivider).toInt();

View file

@ -22,6 +22,12 @@ class Contact extends HiveObject {
CryptoCurrency get type => CryptoCurrency.deserialize(raw: raw);
@override
bool operator ==(Object o) => o is Contact && o.key == key;
@override
int get hashCode => key.hashCode;
void updateCryptoCurrency({@required CryptoCurrency currency}) =>
raw = currency.raw;
}

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -14,7 +16,6 @@ Future defaultSettingsMigration(
final currentVersion =
sharedPreferences.getInt('current_default_settings_migration_version') ??
0;
if (currentVersion >= version) {
return;
}
@ -28,10 +29,11 @@ Future defaultSettingsMigration(
switch (version) {
case 1:
await sharedPreferences.setString(
'current_fiat_currency', FiatCurrency.usd.toString());
SettingsStoreBase.currentFiatCurrencyKey, FiatCurrency.usd.toString());
await sharedPreferences.setInt(
'current_fee_priority', TransactionPriority.standart.raw);
await sharedPreferences.setInt('current_balance_display_mode',
SettingsStoreBase.currentTransactionPriorityKey, TransactionPriority.standart.raw);
await sharedPreferences.setInt(
SettingsStoreBase.currentBalanceDisplayModeKey,
BalanceDisplayMode.availableBalance.raw);
await sharedPreferences.setBool('save_recipient_address', true);
await resetToDefault(nodes);
@ -44,6 +46,10 @@ Future defaultSettingsMigration(
await replaceDefaultNode(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 3:
await updateNodeTypes(nodes: nodes);
await addBitcoinElectrumServerList(nodes: nodes);
break;
default:
break;
@ -87,9 +93,11 @@ Future<void> changeCurrentNodeToDefault(
final timeZone = DateTime.now().timeZoneOffset.inHours;
String nodeUri = '';
if (timeZone >= 1) { // Eurasia
if (timeZone >= 1) {
// Eurasia
nodeUri = 'xmr-node-eu.cakewallet.com:18081';
} else if (timeZone <= -4) { // America
} else if (timeZone <= -4) {
// America
nodeUri = 'xmr-node-usa-east.cakewallet.com:18081';
}
@ -121,3 +129,17 @@ Future<void> replaceDefaultNode(
await changeCurrentNodeToDefault(
sharedPreferences: sharedPreferences, nodes: nodes);
}
Future<void> updateNodeTypes({@required Box<Node> nodes}) async {
nodes.values.forEach((node) async {
if (node.type == null) {
node.type = WalletType.monero;
await node.save();
}
});
}
Future<void> addBitcoinElectrumServerList({@required Box<Node> nodes}) async {
final serverList = await loadElectrumServerList();
await nodes.addAll(serverList);
}

View file

@ -2,18 +2,22 @@ import 'package:flutter/foundation.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:hive/hive.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/src/domain/common/digest_request.dart';
part 'node.g.dart';
@HiveType(typeId: 1)
class Node extends HiveObject {
Node({@required this.uri, this.login, this.password});
Node({@required this.uri, @required WalletType type, this.login, this.password}) {
this.type = type;
}
Node.fromMap(Map map)
: uri = (map['uri'] ?? '') as String,
login = map['login'] as String,
password = map['password'] as String;
password = map['password'] as String,
typeRaw = map['typeRaw'] as int;
static const boxName = 'Nodes';
@ -26,7 +30,29 @@ class Node extends HiveObject {
@HiveField(2)
String password;
Future<bool> requestNode(String uri, {String login, String password}) async {
@HiveField(3)
int typeRaw;
WalletType get type => deserializeFromInt(typeRaw);
set type(WalletType type) => typeRaw = serializeToInt(type);
Future<bool> requestNode() async {
try {
switch (type) {
case WalletType.monero:
return requestMoneroNode();
case WalletType.bitcoin:
return requestBitcoinElectrumServer();
default:
return false;
}
} catch (_) {
return false;
}
}
Future<bool> requestMoneroNode() async {
Map<String, dynamic> resBody;
if (login != null && password != null) {
@ -38,12 +64,17 @@ class Node extends HiveObject {
final url = Uri.http(uri, '/json_rpc');
final headers = {'Content-type': 'application/json'};
final body =
json.encode({"jsonrpc": "2.0", "id": "0", "method": "get_info"});
json.encode({'jsonrpc': '2.0', 'id': '0', 'method': 'get_info'});
final response =
await http.post(url.toString(), headers: headers, body: body);
resBody = json.decode(response.body) as Map<String, dynamic>;
}
return !(resBody["result"]["offline"] as bool);
return !(resBody['result']['offline'] as bool);
}
Future<bool> requestBitcoinElectrumServer() async {
// FIXME: IMPLEMENT ME
return true;
}
}

View file

@ -2,6 +2,7 @@ import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
import "package:yaml/yaml.dart";
import 'package:cake_wallet/src/domain/common/node.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
Future<List<Node>> loadDefaultNodes() async {
final nodesRaw = await rootBundle.loadString('assets/node_list.yml');
@ -16,15 +17,34 @@ Future<List<Node>> loadDefaultNodes() async {
}).toList();
}
Future<List<Node>> loadElectrumServerList() async {
final serverListRaw =
await rootBundle.loadString('assets/electrum_server_list.yml');
final serverList = loadYaml(serverListRaw) as YamlList;
return serverList.map((dynamic raw) {
if (raw is Map) {
final node = Node.fromMap(raw);
node?.type = WalletType.bitcoin;
return node;
}
return null;
}).toList();
}
Future resetToDefault(Box<Node> nodeSource) async {
final nodes = await loadDefaultNodes();
final enteties = Map<int, Node>();
final moneroNodes = await loadDefaultNodes();
final bitcoinElectrumServerList = await loadElectrumServerList();
final nodes = moneroNodes + bitcoinElectrumServerList;
final entities = <int, Node>{};
await nodeSource.clear();
for (var i = 0; i < nodes.length; i++) {
enteties[i] = nodes[i];
entities[i] = nodes[i];
}
await nodeSource.putAll(enteties);
await nodeSource.putAll(entities);
}

View file

@ -20,15 +20,19 @@ int serializeToInt(WalletType type) {
switch (type) {
case WalletType.monero:
return 0;
case WalletType.bitcoin:
return 1;
default:
return -1;
}
}
WalletType deserializeToInt(int raw) {
WalletType deserializeFromInt(int raw) {
switch (raw) {
case 0:
return WalletType.monero;
case 1:
return WalletType.bitcoin;
default:
return null;
}

View file

@ -23,7 +23,7 @@ void setReactions(
@required SyncStore syncStore,
@required WalletStore walletStore,
@required WalletService walletService,
@required AuthenticationStore authenticationStore,
// @required AuthenticationStore authenticationStore,
@required LoginStore loginStore}) {
connectToNode(settingsStore: settingsStore, walletStore: walletStore);
onSyncStatusChange(

View file

@ -1,109 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/stores/account_list/account_list_store.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/domain/monero/account.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
class AccountPage extends BasePage {
AccountPage({this.account});
final Account account;
@override
String get title => S.current.account;
@override
Widget body(BuildContext context) => AccountForm(account);
}
class AccountForm extends StatefulWidget {
AccountForm(this.account);
final Account account;
@override
AccountFormState createState() => AccountFormState();
}
class AccountFormState extends State<AccountForm> {
final _formKey = GlobalKey<FormState>();
final _textController = TextEditingController();
@override
void initState() {
if (widget.account != null) _textController.text = widget.account.label;
super.initState();
}
@override
void dispose() {
_textController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final accountListStore = Provider.of<AccountListStore>(context);
_textController.addListener(() {
if (_textController.text.isNotEmpty) {
accountListStore.setDisabledStatus(false);
} else {
accountListStore.setDisabledStatus(true);
}
});
return Form(
key: _formKey,
child: Container(
color: Theme.of(context).backgroundColor,
padding: EdgeInsets.all(24.0),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: BaseTextFormField(
controller: _textController,
hintText: S.of(context).account,
validator: (value) {
accountListStore.validateAccountName(value);
return accountListStore.errorMessage;
},
)
)),
Observer(
builder: (_) => LoadingPrimaryButton(
onPressed: () async {
if (!_formKey.currentState.validate()) {
return;
}
if (widget.account != null) {
await accountListStore.renameAccount(
index: widget.account.id,
label: _textController.text);
} else {
await accountListStore.addAccount(
label: _textController.text);
}
Navigator.of(context).pop(_textController.text);
},
text:
widget.account != null ? S.of(context).rename : S.of(context).add,
color: Colors.green,
textColor: Colors.white,
isLoading: accountListStore.isAccountCreating,
isDisabled: accountListStore.isDisabledStatus,
))
],
),
),
);
}
}

View file

@ -1,293 +0,0 @@
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/stores/address_book/address_book_store.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
class AddressBookPage extends BasePage {
AddressBookPage({this.isEditable = true});
final bool isEditable;
@override
String get title => S.current.address_book;
@override
Widget trailing(BuildContext context) {
if (!isEditable) {
return null;
}
final addressBookStore = Provider.of<AddressBookStore>(context);
return Container(
width: 32.0,
height: 32.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).accentTextTheme.title.backgroundColor
),
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Icon(Icons.add,
color: Theme.of(context).primaryTextTheme.title.color,
size: 22.0),
ButtonTheme(
minWidth: 32.0,
height: 32.0,
child: FlatButton(
shape: CircleBorder(),
onPressed: () async {
await Navigator.of(context)
.pushNamed(Routes.addressBookAddContact);
await addressBookStore.updateContactList();
},
child: Offstage()),
)
],
));
}
@override
Widget body(BuildContext context) {
final addressBookStore = Provider.of<AddressBookStore>(context);
return Container(
color: Theme.of(context).backgroundColor,
padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
child: Observer(
builder: (_) => ListView.separated(
separatorBuilder: (_, __) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
),
itemCount: addressBookStore.contactList == null
? 0
: addressBookStore.contactList.length,
itemBuilder: (BuildContext context, int index) {
final contact = addressBookStore.contactList[index];
final image = _getCurrencyImage(contact.type);
final isDrawTop = index == 0 ? true : false;
final isDrawBottom = index == addressBookStore.contactList.length - 1 ? true : false;
final content = GestureDetector(
onTap: () async {
if (!isEditable) {
Navigator.of(context).pop(contact);
return;
}
final isCopied = await showNameAndAddressDialog(
context, contact.name, contact.address);
if (isCopied != null && isCopied) {
await Clipboard.setData(
ClipboardData(text: contact.address));
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
S.of(context).copied_to_clipboard,
style: TextStyle(
color: Colors.white
),
),
backgroundColor: Colors.green,
duration: Duration(milliseconds: 1500),
),
);
}
},
child: Column(
children: <Widget>[
isDrawTop
? Container(
width: double.infinity,
height: 1,
color: Theme.of(context).dividerColor,
)
: Offstage(),
Container(
width: double.infinity,
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: Padding(
padding: const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image != null
? image
: Offstage(),
Padding(
padding: image != null
? EdgeInsets.only(left: 12)
: EdgeInsets.only(left: 0),
child: Text(
contact.name,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryTextTheme.title.color
),
),
)
],
)
),
),
isDrawBottom
? Container(
width: double.infinity,
height: 1,
color: Theme.of(context).dividerColor,
)
: Offstage(),
],
),
);
return !isEditable
? content
: Slidable(
key: Key('${contact.key}'),
actionPane: SlidableDrawerActionPane(),
child: content,
secondaryActions: <Widget>[
IconSlideAction(
caption: S.of(context).edit,
color: Colors.blue,
icon: Icons.edit,
onTap: () async {
await Navigator.of(context).pushNamed(
Routes.addressBookAddContact,
arguments: contact);
await addressBookStore.updateContactList();
},
),
IconSlideAction(
caption: S.of(context).delete,
color: Colors.red,
icon: CupertinoIcons.delete,
onTap: () async {
await showAlertDialog(context)
.then((isDelete) async {
if (isDelete != null && isDelete) {
await addressBookStore.delete(
contact: contact);
await addressBookStore.updateContactList();
}
});
},
),
],
dismissal: SlidableDismissal(
child: SlidableDrawerDismissal(),
onDismissed: (actionType) async {
await addressBookStore.delete(contact: contact);
await addressBookStore.updateContactList();
},
onWillDismiss: (actionType) async {
return await showAlertDialog(context);
},
),
);
}),
));
}
Image _getCurrencyImage(CryptoCurrency currency) {
Image image;
switch (currency) {
case CryptoCurrency.xmr:
image = Image.asset('assets/images/monero.png', height: 24, width: 24);
break;
case CryptoCurrency.ada:
image = Image.asset('assets/images/ada.png', height: 24, width: 24);
break;
case CryptoCurrency.bch:
image = Image.asset('assets/images/bch.png', height: 24, width: 24);
break;
case CryptoCurrency.bnb:
image = Image.asset('assets/images/bnb.png', height: 24, width: 24);
break;
case CryptoCurrency.btc:
image = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
break;
case CryptoCurrency.dash:
image = Image.asset('assets/images/dash.png', height: 24, width: 24);
break;
case CryptoCurrency.eos:
image = Image.asset('assets/images/eos.png', height: 24, width: 24);
break;
case CryptoCurrency.eth:
image = Image.asset('assets/images/eth.png', height: 24, width: 24);
break;
case CryptoCurrency.ltc:
image = Image.asset('assets/images/litecoin.png', height: 24, width: 24);
break;
case CryptoCurrency.nano:
image = Image.asset('assets/images/nano.png', height: 24, width: 24);
break;
case CryptoCurrency.trx:
image = Image.asset('assets/images/trx.png', height: 24, width: 24);
break;
case CryptoCurrency.usdt:
image = Image.asset('assets/images/usdt.png', height: 24, width: 24);
break;
case CryptoCurrency.xlm:
image = Image.asset('assets/images/xlm.png', height: 24, width: 24);
break;
case CryptoCurrency.xrp:
image = Image.asset('assets/images/xrp.png', height: 24, width: 24);
break;
default:
image = null;
}
return image;
}
Future<bool> showAlertDialog(BuildContext context) async {
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).address_remove_contact,
alertContent: S.of(context).address_remove_content,
leftButtonText: S.of(context).remove,
rightButtonText: S.of(context).cancel,
actionLeftButton: () => Navigator.of(context).pop(true),
actionRightButton: () => Navigator.of(context).pop(false)
);
});
}
Future<bool> showNameAndAddressDialog(
BuildContext context, String name, String address) async {
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: name,
alertContent: address,
leftButtonText: S.of(context).copy,
rightButtonText: S.of(context).cancel,
actionLeftButton: () => Navigator.of(context).pop(true),
actionRightButton: () => Navigator.of(context).pop(false)
);
});
}
}

View file

@ -1,234 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/domain/common/contact.dart';
import 'package:cake_wallet/src/stores/address_book/address_book_store.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
class ContactPage extends BasePage {
ContactPage({this.contact});
final Contact contact;
@override
String get title => S.current.contact;
@override
Widget body(BuildContext context) => ContactForm(contact);
}
class ContactForm extends StatefulWidget {
ContactForm(this.contact);
final Contact contact;
@override
State<ContactForm> createState() => ContactFormState();
}
class ContactFormState extends State<ContactForm> {
final _formKey = GlobalKey<FormState>();
final _contactNameController = TextEditingController();
final _currencyTypeController = TextEditingController();
final _addressController = TextEditingController();
final currencies = CryptoCurrency.all;
CryptoCurrency _selectectCrypto;
@override
void initState() {
super.initState();
if (widget.contact != null) {
_selectectCrypto = widget.contact.type;
_contactNameController.text = widget.contact.name;
_currencyTypeController.text = _selectectCrypto.toString();
_addressController.text = widget.contact.address;
WidgetsBinding.instance.addPostFrameCallback(afterLayout);
}
}
void afterLayout(dynamic _) {
final addressBookStore = Provider.of<AddressBookStore>(context);
addressBookStore.setDisabledStatus(false);
}
@override
void dispose() {
_contactNameController.dispose();
_currencyTypeController.dispose();
_addressController.dispose();
super.dispose();
}
void onHandleControllers(AddressBookStore addressBookStore) {
if (_contactNameController.text.isNotEmpty &&
_addressController.text.isNotEmpty &&
_currencyTypeController.text.isNotEmpty) {
addressBookStore.setDisabledStatus(false);
} else {
addressBookStore.setDisabledStatus(true);
}
}
@override
Widget build(BuildContext context) {
final addressBookStore = Provider.of<AddressBookStore>(context);
final downArrow = Image.asset(
'assets/images/arrow_bottom_purple_icon.png',
color: Theme.of(context).dividerColor,
height: 8);
_contactNameController.addListener(() {onHandleControllers(addressBookStore);});
_currencyTypeController.addListener(() {onHandleControllers(addressBookStore);});
_addressController.addListener(() {onHandleControllers(addressBookStore);});
return Container(
color: Theme.of(context).backgroundColor,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
BaseTextFormField(
controller: _contactNameController,
hintText: S.of(context).contact_name,
validator: (value) {
addressBookStore.validateContactName(value);
return addressBookStore.errorMessage;
}
),
Padding(
padding: EdgeInsets.only(top: 20),
child: Container(
child: InkWell(
onTap: () => _presentPicker(context),
child: IgnorePointer(
child: BaseTextFormField(
controller: _currencyTypeController,
hintText: S.of(context).settings_currency,
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
downArrow
],
),
)
),
),
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: AddressTextField(
controller: _addressController,
options: [AddressTextFieldOption.qrCode],
validator: (value) {
addressBookStore.validateAddress(value,
cryptoCurrency: _selectectCrypto);
return addressBookStore.errorMessage;
},
),
)
],
),
),
bottomSectionPadding: EdgeInsets.only(
left: 24,
right: 24,
bottom: 24
),
bottomSection: Row(
children: <Widget>[
Expanded(
child: PrimaryButton(
onPressed: () {
setState(() {
_selectectCrypto = null;
_contactNameController.text = '';
_currencyTypeController.text = '';
_addressController.text = '';
});
},
text: S.of(context).reset,
color: Colors.red,
textColor: Colors.white
),
),
SizedBox(width: 20),
Expanded(
child: Observer(
builder: (_) => PrimaryButton(
onPressed: () async {
if (!_formKey.currentState.validate()) {
return;
}
try {
if (widget.contact == null) {
final newContact = Contact(
name: _contactNameController.text,
address: _addressController.text,
type: _selectectCrypto);
await addressBookStore.add(contact: newContact);
} else {
widget.contact.name = _contactNameController.text;
widget.contact.address = _addressController.text;
widget.contact
.updateCryptoCurrency(currency: _selectectCrypto);
await addressBookStore.update(
contact: widget.contact);
}
Navigator.pop(context);
} catch (e) {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.current.contact,
alertContent: e.toString(),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()
);
});
}
},
text: S.of(context).save,
color: Colors.green,
textColor: Colors.white,
isDisabled: addressBookStore.isDisabledStatus,
)
)
)
],
)),
);
}
void _presentPicker(BuildContext context) {
showDialog<void>(
builder: (_) => CurrencyPicker(
selectedAtIndex: currencies.indexOf(_selectectCrypto),
items: currencies,
title: S.of(context).please_select,
onItemSelected: (CryptoCurrency item) {
_selectectCrypto = item;
_currencyTypeController.text = _selectectCrypto.toString();
}
),
context: context);
}
}

View file

@ -125,7 +125,7 @@ abstract class BasePage extends StatelessWidget {
_isDarkTheme ? backgroundDarkColor : backgroundLightColor,
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
appBar: appBar(context),
body: SafeArea(child: body(context)),
body: body(context), //SafeArea(child: ),
floatingActionButton: floatingActionButton(context));
return rootWrapper?.call(context, root) ?? root;

View file

@ -0,0 +1,280 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/view_model/contact_list/contact_list_view_model.dart';
class ContactListPage extends BasePage {
ContactListPage(this.contactListViewModel, {this.isEditable = true});
final ContactListViewModel contactListViewModel;
final bool isEditable;
@override
String get title => S.current.address_book;
@override
Widget trailing(BuildContext context) {
if (!isEditable) {
return null;
}
return Container(
width: 32.0,
height: 32.0,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).accentTextTheme.title.backgroundColor),
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Icon(Icons.add,
color: Theme.of(context).primaryTextTheme.title.color,
size: 22.0),
ButtonTheme(
minWidth: 32.0,
height: 32.0,
child: FlatButton(
shape: CircleBorder(),
onPressed: () async {
await Navigator.of(context)
.pushNamed(Routes.addressBookAddContact);
},
child: Offstage()),
)
],
));
}
@override
Widget body(BuildContext context) {
return Container(
color: Theme.of(context).backgroundColor,
padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
child: Observer(
builder: (_) {
return contactListViewModel.contacts.isNotEmpty
? ListView.separated(
separatorBuilder: (_, __) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context)
.accentTextTheme
.title
.backgroundColor,
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
),
itemCount: contactListViewModel.contacts.length,
itemBuilder: (BuildContext context, int index) {
final contact = contactListViewModel.contacts[index];
final image = _getCurrencyImage(contact.type);
final content = GestureDetector(
onTap: () async {
if (!isEditable) {
Navigator.of(context).pop(contact);
return;
}
final isCopied = await showNameAndAddressDialog(
context, contact.name, contact.address);
if (isCopied != null && isCopied) {
await Clipboard.setData(
ClipboardData(text: contact.address));
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
S.of(context).copied_to_clipboard,
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.green,
duration: Duration(milliseconds: 1500),
),
);
}
},
child: Column(
children: <Widget>[
Container(
width: double.infinity,
color: Theme.of(context)
.accentTextTheme
.title
.backgroundColor,
child: Padding(
padding: const EdgeInsets.only(
left: 24, top: 16, bottom: 16, right: 24),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment:
CrossAxisAlignment.center,
children: <Widget>[
image ?? Offstage(),
Padding(
padding: image != null
? EdgeInsets.only(left: 12)
: EdgeInsets.only(left: 0),
child: Text(
contact.name,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
)
],
)),
),
],
),
);
return !isEditable
? content
: Slidable(
key: Key('${contact.key}'),
actionPane: SlidableDrawerActionPane(),
child: content,
secondaryActions: <Widget>[
IconSlideAction(
caption: S.of(context).edit,
color: Colors.blue,
icon: Icons.edit,
onTap: () async => await Navigator.of(context)
.pushNamed(Routes.addressBookAddContact,
arguments: contact),
),
IconSlideAction(
caption: S.of(context).delete,
color: Colors.red,
icon: CupertinoIcons.delete,
onTap: () async {
final isDelete =
await showAlertDialog(context) ?? false;
if (isDelete) {
await contactListViewModel
.delete(contact);
}
},
),
],
dismissal: SlidableDismissal(
child: SlidableDrawerDismissal(),
onDismissed: (actionType) async =>
await contactListViewModel.delete(contact),
onWillDismiss: (actionType) async =>
showAlertDialog(context),
),
);
})
: Center(
child: Text(
S.of(context).placeholder_contacts,
textAlign: TextAlign.center,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color
.withOpacity(0.5),
fontSize: 14),
),
);
},
));
}
Image _getCurrencyImage(CryptoCurrency currency) {
Image image;
switch (currency) {
case CryptoCurrency.xmr:
image = Image.asset('assets/images/monero.png', height: 24, width: 24);
break;
case CryptoCurrency.ada:
image = Image.asset('assets/images/ada.png', height: 24, width: 24);
break;
case CryptoCurrency.bch:
image = Image.asset('assets/images/bch.png', height: 24, width: 24);
break;
case CryptoCurrency.bnb:
image = Image.asset('assets/images/bnb.png', height: 24, width: 24);
break;
case CryptoCurrency.btc:
image = Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
break;
case CryptoCurrency.dash:
image = Image.asset('assets/images/dash.png', height: 24, width: 24);
break;
case CryptoCurrency.eos:
image = Image.asset('assets/images/eos.png', height: 24, width: 24);
break;
case CryptoCurrency.eth:
image = Image.asset('assets/images/eth.png', height: 24, width: 24);
break;
case CryptoCurrency.ltc:
image =
Image.asset('assets/images/litecoin.png', height: 24, width: 24);
break;
case CryptoCurrency.nano:
image = Image.asset('assets/images/nano.png', height: 24, width: 24);
break;
case CryptoCurrency.trx:
image = Image.asset('assets/images/trx.png', height: 24, width: 24);
break;
case CryptoCurrency.usdt:
image = Image.asset('assets/images/usdt.png', height: 24, width: 24);
break;
case CryptoCurrency.xlm:
image = Image.asset('assets/images/xlm.png', height: 24, width: 24);
break;
case CryptoCurrency.xrp:
image = Image.asset('assets/images/xrp.png', height: 24, width: 24);
break;
default:
image = null;
}
return image;
}
Future<bool> showAlertDialog(BuildContext context) async {
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).address_remove_contact,
alertContent: S.of(context).address_remove_content,
leftButtonText: S.of(context).remove,
rightButtonText: S.of(context).cancel,
actionLeftButton: () => Navigator.of(context).pop(true),
actionRightButton: () => Navigator.of(context).pop(false));
});
}
Future<bool> showNameAndAddressDialog(
BuildContext context, String name, String address) async {
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: name,
alertContent: address,
leftButtonText: S.of(context).copy,
rightButtonText: S.of(context).cancel,
actionLeftButton: () => Navigator.of(context).pop(true),
actionRightButton: () => Navigator.of(context).pop(false));
});
}
}

View file

@ -0,0 +1,161 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/contact_name_validator.dart';
import 'package:cake_wallet/view_model/contact_list/contact_view_model.dart';
import 'package:cake_wallet/view_model/contact_list/contact_view_model_state.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_one_action.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart';
class ContactPage extends BasePage {
ContactPage(this.contactViewModel)
: _formKey = GlobalKey<FormState>(),
_nameController = TextEditingController(),
_addressController = TextEditingController(),
_currencyTypeController = TextEditingController() {
_nameController.text = contactViewModel.name;
_addressController.text = contactViewModel.address;
_nameController
.addListener(() => contactViewModel.name = _nameController.text);
_addressController
.addListener(() => contactViewModel.address = _addressController.text);
autorun((_) =>
_currencyTypeController.text = contactViewModel.currency.toString());
}
@override
String get title => S.current.contact;
final ContactViewModel contactViewModel;
final GlobalKey<FormState> _formKey;
final TextEditingController _nameController;
final TextEditingController _currencyTypeController;
final TextEditingController _addressController;
@override
Widget body(BuildContext context) {
final downArrow = Image.asset('assets/images/arrow_bottom_purple_icon.png',
color: Theme.of(context).dividerColor, height: 8);
reaction((_) => contactViewModel.state, (ContactViewModelState state) {
if (state is ContactCreationFailure) {
_onContactSavingFailure(context, state.error);
}
if (state is ContactSavingSuccessfully) {
_onContactSavedSuccessfully(context);
}
});
return Container(
color: Theme.of(context).backgroundColor,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.all(24),
content: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
BaseTextFormField(
controller: _nameController,
hintText: S.of(context).contact_name,
validator: ContactNameValidator()),
Padding(
padding: EdgeInsets.only(top: 20),
child: Container(
child: InkWell(
onTap: () => _presentCurrencyPicker(context),
child: IgnorePointer(
child: BaseTextFormField(
controller: _currencyTypeController,
hintText: S.of(context).settings_currency,
suffixIcon: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[downArrow],
),
)),
),
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: Observer(
builder: (_) => AddressTextField(
controller: _addressController,
options: [AddressTextFieldOption.qrCode],
validator: AddressValidator(
type: contactViewModel.currency),
)),
)
],
),
),
bottomSectionPadding:
EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Row(
children: <Widget>[
Expanded(
child: PrimaryButton(
onPressed: () => contactViewModel.reset(),
text: S.of(context).reset,
color: Colors.red,
textColor: Colors.white),
),
SizedBox(width: 20),
Expanded(
child: Observer(
builder: (_) => PrimaryButton(
onPressed: () async {
if (!_formKey.currentState.validate()) {
return;
}
await contactViewModel.save();
},
text: S.of(context).save,
color: Colors.green,
textColor: Colors.white,
isDisabled: !contactViewModel.isReady)))
],
)),
);
}
void _presentCurrencyPicker(BuildContext context) {
showDialog<void>(
builder: (_) => CurrencyPicker(
selectedAtIndex:
contactViewModel.currencies.indexOf(contactViewModel.currency),
items: contactViewModel.currencies,
title: S.of(context).please_select,
onItemSelected: (CryptoCurrency item) =>
contactViewModel.currency = item),
context: context);
}
void _onContactSavingFailure(BuildContext context, String error) {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.current.contact,
alertContent: error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
void _onContactSavedSuccessfully(BuildContext context) =>
Navigator.of(context).pop();
}

View file

@ -1,78 +1,150 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/view_model/dashboard_view_model.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/wallet_card.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/trade_history_panel.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart';
class DashboardPage extends StatelessWidget {
class DashboardPage extends BasePage {
DashboardPage({@required this.walletViewModel});
final DashboardViewModel walletViewModel;
final _bodyKey = GlobalKey();
@override
Color get backgroundLightColor => Colors.transparent;
@override
Widget build(BuildContext context) =>
DashboardPageBody(key: _bodyKey, walletViewModel: walletViewModel);
}
class DashboardPageBody extends StatefulWidget {
DashboardPageBody({Key key, @required this.walletViewModel})
: super(key: key);
final DashboardViewModel walletViewModel;
Color get backgroundDarkColor => Colors.transparent;
@override
DashboardPageBodyState createState() => DashboardPageBodyState();
}
class DashboardPageBodyState extends State<DashboardPageBody> {
@override
Widget build(BuildContext context) {
final menuButton = Image.asset(
'assets/images/header.png',
color: Theme.of(context).primaryTextTheme.title.color,
);
return SafeArea(
child: Scaffold(
body: Container(
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => Container(
decoration: BoxDecoration(
gradient: LinearGradient(colors: [
Theme.of(context).scaffoldBackgroundColor,
Theme.of(context).primaryColor
], begin: Alignment.centerLeft, end: Alignment.centerRight)),
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.only(top: 10, right: 10),
alignment: Alignment.centerRight,
child: SizedBox(
height: 44,
width: 44,
child: ButtonTheme(
minWidth: double.minPositive,
child: FlatButton(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
padding: EdgeInsets.all(0),
onPressed: () async {
await showDialog<void>(
builder: (_) => MenuWidget(), context: context);
},
child: menuButton),
),
),
),
Padding(
padding: EdgeInsets.only(left: 20, top: 20),
child: WalletCard(walletVM: widget.walletViewModel)),
SizedBox(height: 28),
Expanded(child: TradeHistoryPanel(dashboardViewModel: widget.walletViewModel))
],
),
),
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
child: scaffold);
@override
Widget trailing(BuildContext context) {
final menuButton = Image.asset('assets/images/header.png',
color: Theme.of(context).primaryTextTheme.title.color);
return Container(
alignment: Alignment.centerRight,
child: SizedBox(
width: 24,
child: FlatButton(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
padding: EdgeInsets.all(0),
onPressed: () async {
await showDialog<void>(
builder: (_) => MenuWidget(
name: walletViewModel.name,
subname: walletViewModel.subname,
type: walletViewModel.type),
context: context);
},
child: menuButton),
),
);
}
final DashboardViewModel walletViewModel;
final sendImage = Image.asset('assets/images/send.png');
final exchangeImage = Image.asset('assets/images/exchange.png');
final buyImage = Image.asset('assets/images/coins.png');
@override
Widget body(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
final transactionListMinHeight =
constraints.heightConstraints().maxHeight - 345 - 32;
return SingleChildScrollView(
child: Column(children: [
Container(
height: 345,
child: Column(children: [
Padding(
padding: EdgeInsets.only(left: 24, top: 10),
child: WalletCard(walletVM: walletViewModel)),
Container(
padding: EdgeInsets.only(left: 44, right: 44, top: 32),
child: Row(
children: <Widget>[
Flexible(
child: actionButton(
context: context,
image: sendImage,
title: S.of(context).send,
route: Routes.send)),
Flexible(
child: actionButton(
context: context,
image: exchangeImage,
title: S.of(context).exchange,
route: Routes.exchange)),
],
),
)
])),
SizedBox(height: 32),
ConstrainedBox(
constraints: BoxConstraints(minHeight: transactionListMinHeight),
child: TradeHistoryPanel(dashboardViewModel: walletViewModel)),
// Column(children: [
// Text('1'),
// Text('2')
// ])
]));
});
}
}
Widget actionButton(
{BuildContext context,
@required Image image,
@required String title,
@required String route}) {
return Container(
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () {
if (route.isNotEmpty) {
Navigator.of(context, rootNavigator: true).pushNamed(route);
}
},
child: Container(
height: 48,
width: 48,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Theme.of(context).primaryTextTheme.subhead.color,
shape: BoxShape.circle),
child: image,
),
),
Padding(
padding: EdgeInsets.only(top: 12),
child: Text(
title,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color.fromRGBO(140, 153, 201,
0.8) // Theme.of(context).primaryTextTheme.caption.color
),
),
)
],
),
);
}

View file

@ -16,10 +16,8 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
final buyImage = Image.asset('assets/images/coins.png');
@override
Widget build(BuildContext context, double shrinkOffset, bool overlapsContent) {
// final actionListStore = Provider.of<ActionListStore>(context);
final historyPanelWidth = MediaQuery.of(context).size.width;
Widget build(
BuildContext context, double shrinkOffset, bool overlapsContent) {
final _themeChanger = Provider.of<ThemeChanger>(context);
Image filterButton;
@ -29,74 +27,39 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
filterButton = Image.asset('assets/images/filter_light_button.png');
}
double buttonsOpacity = 1 - shrinkOffset / (maxExtent - minExtent);
double buttonsHeight = maxExtent - minExtent - shrinkOffset;
buttonsOpacity = buttonsOpacity >= 0 ? buttonsOpacity : 0;
buttonsHeight = buttonsHeight >= 0 ? buttonsHeight : 0;
return Stack(
fit: StackFit.expand,
overflow: Overflow.visible,
children: <Widget>[
Opacity(
opacity: buttonsOpacity,
child: Container(
height: buttonsHeight,
padding: EdgeInsets.only(left: 44, right: 44),
child: Row(
children: <Widget>[
Flexible(
child: actionButton(
context: context,
image: sendImage,
title: S.of(context).send,
route: Routes.send
)
),
Flexible(
child: actionButton(
context: context,
image: exchangeImage,
title: S.of(context).exchange,
route: Routes.exchange
)
),
],
),
),
),
Positioned(
top: buttonsHeight,
left: 0,
child: ClipRRect(
borderRadius: BorderRadius.only(topLeft: Radius.circular(20), topRight: Radius.circular(20)),
child: Container(
width: historyPanelWidth,
height: 66,
padding: EdgeInsets.only(top: 20, left: 20, right: 20, bottom: 10),
color: Theme.of(context).backgroundColor,
child: Stack(
alignment: Alignment.center,
children: <Widget>[
Text(
S.of(context).transactions,
style: TextStyle(
fontSize: 20,
color: Theme.of(context).primaryTextTheme.title.color
),
),
Positioned(
right: 0,
child: PopupMenuButton<int>(
itemBuilder: (context) => [
PopupMenuItem(
enabled: false,
value: -1,
child: Text(S.of(context).transactions,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.caption.color))),
return ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24), topRight: Radius.circular(24)),
child: Container(
color: Colors.red,
// height: 75,
padding: EdgeInsets.only(top: 26, left: 20, right: 20, bottom: 10),
// color: Theme.of(context).backgroundColor,
child: Stack(
children: <Widget>[
Center(
child: Text(
S.of(context).transactions,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.title.color),
)),
Positioned(
right: 0,
height: 36,
child: PopupMenuButton<int>(
itemBuilder: (context) => [
PopupMenuItem(
enabled: false,
value: -1,
child: Text(S.of(context).transactions,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.caption
.color))),
// PopupMenuItem(
// value: 0,
// child: Observer(
@ -135,27 +98,28 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
// .toggleOutgoing(),
// )
// ]))),
PopupMenuItem(
value: 2,
child:
Text(S.of(context).transactions_by_date)),
PopupMenuDivider(),
PopupMenuItem(
enabled: false,
value: -1,
child: Text(S.of(context).trades,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.caption.color))),
PopupMenuItem(
value: 3,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text('XMR.TO'),
PopupMenuItem(
value: 2,
child: Text(S.of(context).transactions_by_date)),
PopupMenuDivider(),
PopupMenuItem(
enabled: false,
value: -1,
child: Text(S.of(context).trades,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.caption
.color))),
PopupMenuItem(
value: 3,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text('XMR.TO'),
// Checkbox(
// value: actionListStore
// .tradeFilterStore
@ -167,16 +131,15 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
// ExchangeProviderDescription
// .xmrto),
// )
]))),
PopupMenuItem(
value: 4,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text('Change.NOW'),
]))),
PopupMenuItem(
value: 4,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text('Change.NOW'),
// Checkbox(
// value: actionListStore
// .tradeFilterStore
@ -188,16 +151,15 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
// ExchangeProviderDescription
// .changeNow),
// )
]))),
PopupMenuItem(
value: 5,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Text('MorphToken'),
]))),
PopupMenuItem(
value: 5,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text('MorphToken'),
// Checkbox(
// value: actionListStore
// .tradeFilterStore
@ -209,42 +171,37 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
// ExchangeProviderDescription
// .morphToken),
// )
])))
],
child: filterButton,
onSelected: (item) async {
if (item == 2) {
final List<DateTime> picked =
await date_rage_picker.showDatePicker(
context: context,
initialFirstDate: DateTime.now()
.subtract(Duration(days: 1)),
initialLastDate: (DateTime.now()),
firstDate: DateTime(2015),
lastDate: DateTime.now()
.add(Duration(days: 1)));
])))
],
child: filterButton,
onSelected: (item) async {
if (item == 2) {
final List<DateTime> picked =
await date_rage_picker.showDatePicker(
context: context,
initialFirstDate:
DateTime.now().subtract(Duration(days: 1)),
initialLastDate: (DateTime.now()),
firstDate: DateTime(2015),
lastDate: DateTime.now().add(Duration(days: 1)));
if (picked != null && picked.length == 2) {
if (picked != null && picked.length == 2) {
// actionListStore.transactionFilterStore
// .changeStartDate(picked.first);
// actionListStore.transactionFilterStore
// .changeEndDate(picked.last);
}
}
},
),
)
],
),
),
)
)
],
}
}
},
)),
],
),
),
);
}
@override
double get maxExtent => 174;
double get maxExtent => 164;
@override
double get minExtent => 66;
@ -252,12 +209,11 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
@override
bool shouldRebuild(SliverPersistentHeaderDelegate oldDelegate) => true;
Widget actionButton({
BuildContext context,
@required Image image,
@required String title,
@required String route}) {
Widget actionButton(
{BuildContext context,
@required Image image,
@required String title,
@required String route}) {
return Container(
width: MediaQuery.of(context).size.width,
child: Column(
@ -276,23 +232,24 @@ class ButtonHeader extends SliverPersistentHeaderDelegate {
alignment: Alignment.center,
decoration: BoxDecoration(
color: Theme.of(context).primaryTextTheme.subhead.color,
shape: BoxShape.circle
),
shape: BoxShape.circle),
child: image,
),
),
Padding(
padding: EdgeInsets.only(top: 10),
padding: EdgeInsets.only(top: 12),
child: Text(
title,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).primaryTextTheme.caption.color
),
fontWeight: FontWeight.w600,
color: Color.fromRGBO(140, 153, 201,
0.8) // Theme.of(context).primaryTextTheme.caption.color
),
),
)
],
),
);
}
}
}

View file

@ -1,25 +1,28 @@
import 'dart:async';
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/src/screens/dashboard/wallet_menu.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
import 'package:provider/provider.dart';
class MenuWidget extends StatefulWidget {
MenuWidget({this.type, this.name, this.subname});
final WalletType type;
final String name;
final String subname;
@override
MenuWidgetState createState() => MenuWidgetState();
}
class MenuWidgetState extends State<MenuWidget> {
final moneroIcon = Image.asset('assets/images/monero.png');
final bitcoinIcon = Image.asset('assets/images/bitcoin.png');
final largeScreen = 731;
double menuWidth;
double screenWidth;
double screenHeight;
double opacity;
bool isDraw;
double headerHeight;
double tileHeight;
@ -32,7 +35,6 @@ class MenuWidgetState extends State<MenuWidget> {
screenWidth = 0;
screenHeight = 0;
opacity = 0;
isDraw = false;
headerHeight = 120;
tileHeight = 75;
@ -59,206 +61,205 @@ class MenuWidgetState extends State<MenuWidget> {
fromBottomEdge *= scale;
}
});
Timer(Duration(milliseconds: 350), () =>
setState(() => isDraw = true)
);
}
@override
Widget build(BuildContext context) {
final walletMenu = WalletMenu(context);
final walletStore = Provider.of<WalletStore>(context);
// final walletStore = Provider.of<WalletStore>(context);
final itemCount = walletMenu.items.length;
return GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
color: Colors.transparent,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
return Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 24),
child: Container(
height: 60,
width: 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)),
color: Theme.of(context).hintColor),
)),
SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () => null,
child: Container(
decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)),
child: Row(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: 24),
child: isDraw
? Container(
height: 60,
width: 4,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(2)),
color: Theme.of(context).hintColor //
),
)
: Container(
height: 60,
width: 4,
)
),
SizedBox(width: 12),
Expanded(
child: GestureDetector(
onTap: () => null,
child: Container(
width: double.infinity,
height: double.infinity,
alignment: Alignment.centerRight,
child: AnimatedContainer(
alignment: Alignment.centerLeft,
width: menuWidth,
height: double.infinity,
duration: Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(24), bottomLeft: Radius.circular(24)),
color: Theme.of(context).primaryTextTheme.display1.color.withOpacity(opacity)
),
child: isDraw
? ListView.separated(
itemBuilder: (_, index) {
if (index == 0) {
return Container(
height: headerHeight,
padding: EdgeInsets.only(
left: 24,
top: fromTopEdge,
right: 24,
bottom: fromBottomEdge),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(topLeft: Radius.circular(24)),
color: Theme.of(context).primaryTextTheme.display2.color
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
moneroIcon,
SizedBox(width: 16),
Expanded(
child: Container(
height: 40,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
walletStore.name,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
fontFamily: 'Lato',
fontSize: 20,
fontWeight: FontWeight.bold
),
),
Text(
walletStore.account.label,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
decoration: TextDecoration.none,
fontFamily: 'Lato',
fontSize: 12
),
)
],
),
)
)
],
),
);
}
index -= 1;
final item = walletMenu.items[index];
final image = walletMenu.images[index] ?? Offstage();
return GestureDetector(
onTap: () {
Navigator.of(context).pop();
walletMenu.action(index);
},
child: index == itemCount - 1
? Container(
height: headerHeight,
padding: EdgeInsets.only(
left: 24,
right: 24,
top: fromBottomEdge,
bottom: fromTopEdge),
alignment: Alignment.topLeft,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(bottomLeft: Radius.circular(24)),
color: Theme.of(context).primaryTextTheme.display1.color,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image,
SizedBox(width: 16),
Expanded(
child: Text(
item,
style: TextStyle(
decoration: TextDecoration.none,
color: Theme.of(context).primaryTextTheme.title.color,
fontFamily: 'Lato',
fontSize: 20,
fontWeight: FontWeight.bold
),
)
)
],
),
)
: Container(
height: tileHeight,
padding: EdgeInsets.only(left: 24, right: 24),
color: Theme.of(context).primaryTextTheme.display1.color,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image,
SizedBox(width: 16),
Expanded(
child: Text(
item,
style: TextStyle(
decoration: TextDecoration.none,
color: Theme.of(context).primaryTextTheme.title.color,
fontFamily: 'Lato',
fontSize: 20,
fontWeight: FontWeight.bold
),
)
)
],
),
width: menuWidth,
height: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24),
bottomLeft: Radius.circular(24)),
color: Theme.of(context).primaryTextTheme.display1.color),
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24),
bottomLeft: Radius.circular(24)),
child: ListView.separated(
itemBuilder: (_, index) {
if (index == 0) {
return Container(
height: headerHeight,
padding: EdgeInsets.only(
left: 24,
top: fromTopEdge,
right: 24,
bottom: fromBottomEdge),
decoration: BoxDecoration(
borderRadius:
BorderRadius.only(topLeft: Radius.circular(24)),
color: Theme.of(context)
.primaryTextTheme
.display2
.color),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_iconFor(type: widget.type),
SizedBox(width: 16),
Expanded(
child: Container(
height: 40,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: widget.subname != null
? MainAxisAlignment.spaceBetween
: MainAxisAlignment.center,
children: <Widget>[
Text(
widget.name,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.title
.color,
decoration: TextDecoration.none,
fontFamily: 'Avenir Next',
fontSize: 20,
fontWeight: FontWeight.bold),
),
);
},
separatorBuilder: (_, index) =>
Container(
height: 1,
color: Theme.of(context).dividerColor,
),
itemCount: itemCount + 1)
: Offstage()
if (widget.subname != null)
Text(
widget.subname,
style: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
decoration: TextDecoration.none,
fontFamily: 'Avenir Next',
fontSize: 12),
)
],
),
))
],
),
);
}
index -= 1;
final item = walletMenu.items[index];
final image = walletMenu.images[index] ?? Offstage();
return GestureDetector(
onTap: () {
Navigator.of(context).pop();
walletMenu.action(index);
},
child: index == itemCount - 1
? Container(
height: headerHeight,
padding: EdgeInsets.only(
left: 24,
right: 24,
top: fromBottomEdge,
bottom: fromTopEdge),
alignment: Alignment.topLeft,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24)),
color: Theme.of(context)
.primaryTextTheme
.display1
.color,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image,
SizedBox(width: 16),
Expanded(
child: Text(
item,
style: TextStyle(
decoration: TextDecoration.none,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
fontFamily: 'Avenir Next',
fontSize: 20,
fontWeight: FontWeight.bold),
))
],
),
)
: Container(
height: tileHeight,
padding: EdgeInsets.only(left: 24, right: 24),
color: Theme.of(context)
.primaryTextTheme
.display1
.color,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
image,
SizedBox(width: 16),
Expanded(
child: Text(
item,
style: TextStyle(
decoration: TextDecoration.none,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
fontFamily: 'Avenir Next',
fontSize: 20,
fontWeight: FontWeight.bold),
))
],
),
),
);
},
separatorBuilder: (_, index) => Container(
height: 1,
color: Theme.of(context).dividerColor,
),
),
)
)
],
)
itemCount: itemCount + 1),
),
),
),
),
))
],
);
}
}
Image _iconFor({@required WalletType type}) {
switch (type) {
case WalletType.monero:
return moneroIcon;
case WalletType.bitcoin:
return bitcoinIcon;
default:
return null;
}
}
}

View file

@ -1,3 +1,6 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/theme_changer.dart';
import 'package:cake_wallet/themes.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -15,11 +18,12 @@ import 'date_section_raw.dart';
import 'trade_row.dart';
import 'transaction_raw.dart';
import 'button_header.dart';
import 'package:date_range_picker/date_range_picker.dart' as date_rage_picker;
class TradeHistoryPanel extends StatefulWidget {
TradeHistoryPanel({this.dashboardViewModel});
DashboardViewModel dashboardViewModel;
final DashboardViewModel dashboardViewModel;
@override
TradeHistoryPanelState createState() => TradeHistoryPanelState();
@ -49,127 +53,270 @@ class TradeHistoryPanelState extends State<TradeHistoryPanel> {
@override
Widget build(BuildContext context) {
// final actionListStore = Provider.of<ActionListStore>(context);
// final settingsStore = Provider.of<SettingsStore>(context);
// AnimatedContainer(
// width: MediaQuery.of(context).size.width,
// height: panelHeight,
// duration: Duration(milliseconds: 1000),
// curve: Curves.fastOutSlowIn,
// child: )
final transactionDateFormat = DateFormat('HH:mm');
final _themeChanger = Provider.of<ThemeChanger>(context);
final filterButton = Image.asset(
_themeChanger.getTheme() == Themes.darkTheme
? 'assets/images/filter_button.png'
: 'assets/images/filter_light_button.png',
height: 36);
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
alignment: Alignment.bottomCenter,
child: AnimatedContainer(
width: MediaQuery.of(context).size.width,
height: panelHeight,
duration: Duration(milliseconds: 1000),
curve: Curves.fastOutSlowIn,
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(20)),
child: CustomScrollView(
slivers: <Widget>[
SliverPersistentHeader(
delegate: ButtonHeader(),
pinned: true,
floating: false,
),
Observer(
key: _listObserverKey,
builder: (_) {
// final items = actionListStore.items == null
// ? <String>[]
// : actionListStore.items;
final items = widget.dashboardViewModel.transactions;
final itemsCount = items.length + 1;
final symbol =
'\$'; // settingsStore.fiatCurrency.toString();
var freeSpaceHeight =
MediaQuery.of(context).size.height - 496;
return ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20), topRight: Radius.circular(20)),
child: Container(
color: Colors.white,
child: Column(children: [
Container(
padding:
EdgeInsets.only(top: 32, left: 20, right: 20, bottom: 20),
color: Theme.of(context).backgroundColor,
child: Stack(
children: <Widget>[
SizedBox(height: 37), // Force stack height
Center(
child: Text(S.of(context).transactions,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.primaryTextTheme
.title
.color))),
Positioned(
right: 0,
child: PopupMenuButton<int>(
itemBuilder: (context) => [
PopupMenuItem(
enabled: false,
value: -1,
child: Text(S.of(context).transactions,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.caption
.color))),
// PopupMenuItem(
// value: 0,
// child: Observer(
// builder: (_) => Row(
// mainAxisAlignment:
// MainAxisAlignment
// .spaceBetween,
// children: [
// Text(S.of(context).incoming),
// Checkbox(
// value: actionListStore
// .transactionFilterStore
// .displayIncoming,
// onChanged: (value) =>
// actionListStore
// .transactionFilterStore
// .toggleIncoming(),
// )
// ]))),
// PopupMenuItem(
// value: 1,
// child: Observer(
// builder: (_) => Row(
// mainAxisAlignment:
// MainAxisAlignment
// .spaceBetween,
// children: [
// Text(S.of(context).outgoing),
// Checkbox(
// value: actionListStore
// .transactionFilterStore
// .displayOutgoing,
// onChanged: (value) =>
// actionListStore
// .transactionFilterStore
// .toggleOutgoing(),
// )
// ]))),
PopupMenuItem(
value: 2,
child:
Text(S.of(context).transactions_by_date)),
PopupMenuDivider(),
PopupMenuItem(
enabled: false,
value: -1,
child: Text(S.of(context).trades,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.caption
.color))),
PopupMenuItem(
value: 3,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text('XMR.TO'),
// Checkbox(
// value: actionListStore
// .tradeFilterStore
// .displayXMRTO,
// onChanged: (value) =>
// actionListStore
// .tradeFilterStore
// .toggleDisplayExchange(
// ExchangeProviderDescription
// .xmrto),
// )
]))),
PopupMenuItem(
value: 4,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text('Change.NOW'),
// Checkbox(
// value: actionListStore
// .tradeFilterStore
// .displayChangeNow,
// onChanged: (value) =>
// actionListStore
// .tradeFilterStore
// .toggleDisplayExchange(
// ExchangeProviderDescription
// .changeNow),
// )
]))),
PopupMenuItem(
value: 5,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text('MorphToken'),
// Checkbox(
// value: actionListStore
// .tradeFilterStore
// .displayMorphToken,
// onChanged: (value) =>
// actionListStore
// .tradeFilterStore
// .toggleDisplayExchange(
// ExchangeProviderDescription
// .morphToken),
// )
])))
],
child: filterButton,
onSelected: (item) async {
if (item == 2) {
final picked =
await date_rage_picker.showDatePicker(
context: context,
initialFirstDate: DateTime.now()
.subtract(Duration(days: 1)),
initialLastDate: (DateTime.now()),
firstDate: DateTime(2015),
lastDate: DateTime.now()
.add(Duration(days: 1)));
return SliverList(
key: _listKey,
delegate:
SliverChildBuilderDelegate((context, index) {
if (index == itemsCount - 1) {
freeSpaceHeight = freeSpaceHeight >= 0
? freeSpaceHeight
: 0;
return Container(
height: freeSpaceHeight,
width: MediaQuery.of(context).size.width,
color: Theme.of(context).backgroundColor);
}
final item = items[index];
if (item is DateSectionItem) {
freeSpaceHeight -= 38;
return DateSectionRaw(date: item.date);
}
if (item is TransactionListItem) {
freeSpaceHeight -= 62;
final transaction = item.transaction;
final savedDisplayMode =
BalanceDisplayMode.all;
//settingsStore
// .balanceDisplayMode;
final formattedAmount = savedDisplayMode ==
BalanceDisplayMode.hiddenBalance
? '---'
: transaction.amountFormatted();
final formattedFiatAmount =
savedDisplayMode ==
BalanceDisplayMode.hiddenBalance
? '---'
: transaction
.fiatAmount(); // symbol ???
return TransactionRow(
onTap: () => Navigator.of(context)
.pushNamed(Routes.transactionDetails,
arguments: transaction),
direction: transaction.direction,
formattedDate: transactionDateFormat
.format(transaction.date),
formattedAmount: formattedAmount,
formattedFiatAmount: formattedFiatAmount,
isPending: transaction.isPending);
}
if (item is TradeListItem) {
freeSpaceHeight -= 62;
final trade = item.trade;
final savedDisplayMode =
BalanceDisplayMode.all;
//settingsStore
// .balanceDisplayMode;
final formattedAmount = trade.amount != null
? savedDisplayMode ==
BalanceDisplayMode.hiddenBalance
? '---'
: trade.amountFormatted()
: trade.amount;
return TradeRow(
onTap: () => Navigator.of(context)
.pushNamed(Routes.tradeDetails,
arguments: trade),
provider: trade.provider,
from: trade.from,
to: trade.to,
createdAtFormattedDate:
transactionDateFormat
.format(trade.createdAt),
formattedAmount: formattedAmount);
}
return Container(
color: Theme.of(context).backgroundColor);
}, childCount: itemsCount));
})
if (picked != null && picked.length == 2) {
// actionListStore.transactionFilterStore
// .changeStartDate(picked.first);
// actionListStore.transactionFilterStore
// .changeEndDate(picked.last);
}
}
},
)),
],
)))); //,
),
),
widget.dashboardViewModel.transactions?.isNotEmpty ?? false
? ListView.separated(
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
itemCount: widget.dashboardViewModel.transactions.length,
itemBuilder: (_, index) {
final item =
widget.dashboardViewModel.transactions[index];
if (item is DateSectionItem) {
return DateSectionRaw(date: item.date);
}
if (item is TransactionListItem) {
final transaction = item.transaction;
final savedDisplayMode = BalanceDisplayMode.all;
//settingsStore
// .balanceDisplayMode;
final formattedAmount = savedDisplayMode ==
BalanceDisplayMode.hiddenBalance
? '---'
: transaction.amountFormatted();
final formattedFiatAmount = savedDisplayMode ==
BalanceDisplayMode.hiddenBalance
? '---'
: transaction.fiatAmount(); // symbol ???
return TransactionRow(
onTap: () => Navigator.of(context).pushNamed(
Routes.transactionDetails,
arguments: transaction),
direction: transaction.direction,
formattedDate: transactionDateFormat
.format(transaction.date),
formattedAmount: formattedAmount,
formattedFiatAmount: formattedFiatAmount,
isPending: transaction.isPending);
}
if (item is TradeListItem) {
final trade = item.trade;
final savedDisplayMode = BalanceDisplayMode.all;
//settingsStore
// .balanceDisplayMode;
final formattedAmount = trade.amount != null
? savedDisplayMode ==
BalanceDisplayMode.hiddenBalance
? '---'
: trade.amountFormatted()
: trade.amount;
return TradeRow(
onTap: () => Navigator.of(context).pushNamed(
Routes.tradeDetails,
arguments: trade),
provider: trade.provider,
from: trade.from,
to: trade.to,
createdAtFormattedDate:
transactionDateFormat.format(trade.createdAt),
formattedAmount: formattedAmount);
}
return Container(
color: Theme.of(context).backgroundColor,
height: 1);
},
separatorBuilder: (_, __) =>
Container(height: 14, color: Colors.white),
)
: Padding(
padding: EdgeInsets.all(20),
child: Text('Your transactions will be displayed here!',
style: TextStyle(color: Colors.grey)))
]))); //,
}
}

View file

@ -24,27 +24,19 @@ class TransactionRow extends StatelessWidget {
return InkWell(
onTap: onTap,
child: Container(
height: 60,
decoration: BoxDecoration(
color: Theme.of(context).backgroundColor,
border: Border.all(
width: 1,
color: Theme.of(context).backgroundColor
),
),
padding: EdgeInsets.only(top: 5, bottom: 5, left: 20, right: 20),
height: 41,
color: Theme.of(context).backgroundColor,
padding: EdgeInsets.only(left: 20, right: 20),
child: Row(children: <Widget>[
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).primaryTextTheme.display3.color
),
child: Image.asset(
direction == TransactionDirection.incoming
? 'assets/images/down_arrow.png'
: 'assets/images/up_arrow.png'),
shape: BoxShape.circle,
color: Theme.of(context).primaryTextTheme.display3.color),
child: Image.asset(direction == TransactionDirection.incoming
? 'assets/images/down_arrow.png'
: 'assets/images/up_arrow.png'),
),
Expanded(
child: Padding(
@ -62,28 +54,43 @@ class TransactionRow extends StatelessWidget {
(isPending ? S.of(context).pending : ''),
style: TextStyle(
fontSize: 16,
color: Theme.of(context).primaryTextTheme.title.color
)),
Text(direction == TransactionDirection.incoming
? formattedAmount
: '- ' + formattedAmount,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.title
.color)),
Text(
direction == TransactionDirection.incoming
? formattedAmount
: '- ' + formattedAmount,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).primaryTextTheme.title.color
))
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.title
.color))
]),
SizedBox(height: 5,),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(formattedDate,
style: TextStyle(
fontSize: 14, color: Theme.of(context).primaryTextTheme.headline.color)),
Text(direction == TransactionDirection.incoming
? formattedFiatAmount
: '- ' + formattedFiatAmount,
fontSize: 14,
color: Theme.of(context)
.primaryTextTheme
.headline
.color)),
Text(
direction == TransactionDirection.incoming
? formattedFiatAmount
: '- ' + formattedFiatAmount,
style: TextStyle(
fontSize: 14, color: Theme.of(context).primaryTextTheme.headline.color))
fontSize: 14,
color: Theme.of(context)
.primaryTextTheme
.headline
.color))
]),
],
),

View file

@ -1,16 +1,12 @@
import 'dart:async';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/src/domain/common/balance_display_mode.dart';
import 'package:cake_wallet/src/stores/balance/balance_store.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/src/stores/sync/sync_store.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/common/sync_status.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
@ -20,7 +16,7 @@ import 'package:cake_wallet/view_model/dashboard_view_model.dart';
class WalletCard extends StatefulWidget {
WalletCard({this.walletVM});
DashboardViewModel walletVM;
final DashboardViewModel walletVM;
@override
WalletCardState createState() => WalletCardState();
@ -70,24 +66,20 @@ class WalletCardState extends State<WalletCard> {
width: double.infinity,
height: cardHeight,
alignment: Alignment.centerRight,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(14), bottomLeft: Radius.circular(14))),
child: AnimatedContainer(
alignment: Alignment.centerLeft,
width: cardWidth,
height: cardHeight,
duration: Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn,
padding: EdgeInsets.only(top: 1, left: 1, bottom: 1),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10)),
color: Theme.of(context).focusColor,
boxShadow: [
BoxShadow(
color: PaletteDark.darkNightBlue.withOpacity(0.5),
blurRadius: 8,
offset: Offset(5, 5))
]),
topLeft: Radius.circular(14),
bottomLeft: Radius.circular(14)),
color: Theme.of(context).focusColor),
child: ClipRRect(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10), bottomLeft: Radius.circular(10)),
@ -95,21 +87,18 @@ class WalletCardState extends State<WalletCard> {
width: cardWidth,
height: cardHeight,
color: Theme.of(context).cardColor,
child: InkWell(
onTap: () => setState(() => isFrontSide = !isFrontSide),
child: isFrontSide
? frontSide(colorsSync)
: backSide(colorsSync)),
child: isFrontSide
? frontSide(colorsSync)
: InkWell(
onTap: () => setState(() => isFrontSide = true),
child: backSide(colorsSync)),
),
)),
);
}
Widget frontSide(List<Color> colorsSync) {
// final syncStore = Provider.of<SyncStore>(context);
// final walletStore = Provider.of<WalletStore>(context);
final settingsStore = Provider.of<SettingsStore>(context);
// final balanceStore = Provider.of<BalanceStore>(context);
final triangleButton = Image.asset(
'assets/images/triangle.png',
color: Theme.of(context).primaryTextTheme.title.color,
@ -121,9 +110,9 @@ class WalletCardState extends State<WalletCard> {
final status = widget.walletVM.status;
final statusText = status.title();
final progress = status.progress();
final indicatorWidth = progress * cardWidth;
final shortAddress = widget.walletVM.address
.replaceRange(4, widget.walletVM.address.length - 4, '...');
final indicatorOffset = progress * cardWidth;
final indicatorWidth =
progress <= 1 ? cardWidth - indicatorOffset : 0.0;
var descriptionText = '';
if (status is SyncingSyncStatus) {
@ -137,37 +126,26 @@ class WalletCardState extends State<WalletCard> {
return Container(
width: cardWidth,
height: cardHeight,
color: Colors.white,
child: Stack(
children: <Widget>[
Container(
height: cardHeight,
width: indicatorWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
bottomLeft: Radius.circular(10)),
gradient: LinearGradient(
colors: colorsSync,
begin: Alignment.topCenter,
end: Alignment.bottomCenter)),
),
progress != 1
progress <= 1
? Positioned(
left: indicatorWidth,
left: indicatorOffset,
top: 0,
bottom: 0,
child: Container(
width: 1,
width: indicatorWidth,
height: cardHeight,
color: Theme.of(context).focusColor,
color: Color.fromRGBO(227, 238, 249, 1),
))
: Offstage(),
isDraw
? Positioned(
left: 20,
right: 20,
top: 30,
bottom: 30,
left: 24,
right: 24,
top: 32,
bottom: 24,
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
@ -180,7 +158,8 @@ class WalletCardState extends State<WalletCard> {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
InkWell(
onTap: () {},
onTap: () => Navigator.of(context)
.pushNamed(Routes.walletList),
child: Row(
children: <Widget>[
Text(
@ -199,11 +178,13 @@ class WalletCardState extends State<WalletCard> {
),
),
SizedBox(height: 5),
if (widget.walletVM.subname?.isNotEmpty ?? false)
if (widget.walletVM.subname?.isNotEmpty ??
false)
Text(
widget.walletVM.subname,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.primaryTextTheme
.caption
@ -211,26 +192,34 @@ class WalletCardState extends State<WalletCard> {
)
],
),
Container(
width: 98,
height: 32,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Theme.of(context)
.accentTextTheme
.subtitle
.backgroundColor,
borderRadius: BorderRadius.all(
Radius.circular(16))),
child: Text(
shortAddress,
style: TextStyle(
fontSize: 12,
color: Theme.of(context)
.primaryTextTheme
.caption
.color),
),
InkWell(
onTap: () =>
setState(() => isFrontSide = false),
child: Container(
width: 98,
height: 32,
alignment: Alignment.center,
decoration: BoxDecoration(
color: Theme.of(context)
.accentTextTheme
.subtitle
.backgroundColor,
border: Border.all(
color: Color.fromRGBO(
219, 231, 237, 1)),
// FIXME
borderRadius: BorderRadius.all(
Radius.circular(16))),
child: Text(
'Receive',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
)),
)
],
),
@ -239,7 +228,7 @@ class WalletCardState extends State<WalletCard> {
key: _balanceObserverKey,
builder: (_) {
final balanceDisplayMode =
BalanceDisplayMode.fullBalance;
BalanceDisplayMode.availableBalance;
// settingsStore.balanceDisplayMode;
final symbol =
settingsStore.fiatCurrency.toString();
@ -251,7 +240,7 @@ class WalletCardState extends State<WalletCard> {
balance = widget.walletVM.balance
.unlockedBalance ??
'0.0';
fiatBalance = '\$ 123.43';
fiatBalance = '\$ 0.00';
// '$symbol ${balanceStore.fiatUnlockedBalance}';
}
@ -260,7 +249,7 @@ class WalletCardState extends State<WalletCard> {
balance = widget.walletVM.balance
.totalBalance ??
'0.0';
fiatBalance = '\$ 123.43';
fiatBalance = '\$ 0.00';
// '$symbol ${balanceStore.fiatFullBalance}';
}
@ -271,6 +260,8 @@ class WalletCardState extends State<WalletCard> {
MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
@ -284,25 +275,33 @@ class WalletCardState extends State<WalletCard> {
.color),
),
SizedBox(height: 5),
Text(
balance,
style: TextStyle(
fontSize: 28,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
)
Container(
height: 36,
child: Text(
balance,
style: TextStyle(
fontSize: 32,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
fontWeight:
FontWeight.bold),
))
],
),
Text(
fiatBalance,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
fontWeight: FontWeight.w600,
// FIXME
// color: Theme.of(context)
// .primaryTextTheme
// .title
// .color,
color: Color.fromRGBO(
72, 89, 109, 1)),
)
],
);
@ -320,6 +319,7 @@ class WalletCardState extends State<WalletCard> {
statusText,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.primaryTextTheme
.caption
@ -330,6 +330,7 @@ class WalletCardState extends State<WalletCard> {
descriptionText,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.primaryTextTheme
.title
@ -351,10 +352,8 @@ class WalletCardState extends State<WalletCard> {
}
Widget backSide(List<Color> colorsSync) {
final rightArrow = Image.asset(
'assets/images/right_arrow.png',
color: Theme.of(context).primaryTextTheme.title.color,
);
final rightArrow = Image.asset('assets/images/right_arrow.png',
color: Theme.of(context).primaryTextTheme.title.color);
var messageBoxHeight = 0.0;
var messageBoxWidth = cardWidth - 10;
@ -372,7 +371,7 @@ class WalletCardState extends State<WalletCard> {
width: cardWidth,
height: cardHeight,
padding:
EdgeInsets.only(left: 20, right: 20, top: 30, bottom: 30),
EdgeInsets.only(left: 24, right: 24, top: 32, bottom: 32),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
@ -385,10 +384,10 @@ class WalletCardState extends State<WalletCard> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Container(
height: 90,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
@ -426,7 +425,8 @@ class WalletCardState extends State<WalletCard> {
child: Text(
widget.walletVM.address,
style: TextStyle(
fontSize: 14,
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.primaryTextTheme
.title
@ -468,9 +468,10 @@ class WalletCardState extends State<WalletCard> {
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
S.of(context).accounts_subaddresses,
S.of(context).addresses,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.primaryTextTheme
.title

View file

@ -10,6 +10,7 @@ import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/stores/exchange_template/exchange_template_store.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart';
import 'package:cake_wallet/src/screens/exchange/widgets/base_exchange_widget.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
class ExchangePage extends BasePage {
@override
@ -32,20 +33,9 @@ class ExchangePage extends BasePage {
Widget trailing(BuildContext context) {
final exchangeStore = Provider.of<ExchangeStore>(context);
return ButtonTheme(
minWidth: double.minPositive,
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
child: FlatButton(
padding: EdgeInsets.all(0),
child: Text(
S.of(context).clear,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
fontWeight: FontWeight.w500,
fontSize: 14),
),
onPressed: () => exchangeStore.reset()),
return TrailButton(
caption: S.of(context).reset,
onPressed: () => exchangeStore.reset()
);
}

View file

@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/monero_account_label_validator.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_state.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_edit_or_create_view_model.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
class MoneroAccountEditOrCreatePage extends BasePage {
MoneroAccountEditOrCreatePage({@required this.moneroAccountCreationViewModel})
: _formKey = GlobalKey<FormState>(),
_textController = TextEditingController() {
_textController.addListener(
() => moneroAccountCreationViewModel.label = _textController.text);
_textController.text = moneroAccountCreationViewModel.label;
}
final MoneroAccountEditOrCreateViewModel moneroAccountCreationViewModel;
@override
String get title => S.current.account;
final GlobalKey<FormState> _formKey;
final TextEditingController _textController;
@override
Widget body(BuildContext context) =>
Form(
key: _formKey,
child: Container(
color: Theme.of(context).backgroundColor,
padding: EdgeInsets.all(24.0),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: BaseTextFormField(
controller: _textController,
hintText: S.of(context).account,
validator: MoneroLabelValidator(),
))),
Observer(
builder: (_) =>
LoadingPrimaryButton(
onPressed: () async {
if (!_formKey.currentState.validate()) {
return;
}
await moneroAccountCreationViewModel.save();
Navigator.of(context).pop(_textController.text);
},
text: moneroAccountCreationViewModel.isEdit
? S.of(context).rename
: S.of(context).add,
color: Colors.green,
textColor: Colors.white,
isLoading: moneroAccountCreationViewModel.state
is AccountIsCreating,
isDisabled:
moneroAccountCreationViewModel.label?.isEmpty ?? true,
))
],
),
),
);
}

View file

@ -1,33 +1,30 @@
import 'dart:ui';
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/stores/account_list/account_list_store.dart';
import 'package:cake_wallet/src/screens/accounts/widgets/account_tile.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
import 'package:cake_wallet/view_model/monero_account_list/monero_account_list_view_model.dart';
import 'package:cake_wallet/src/screens/monero_accounts/widgets/account_tile.dart';
class AccountListPage extends StatefulWidget {
AccountListPage({@required this.accountListStore});
class MoneroAccountListPage extends StatefulWidget {
MoneroAccountListPage({@required this.accountListViewModel});
final AccountListStore accountListStore;
final MoneroAccountListViewModel accountListViewModel;
@override
AccountListPageForm createState() => AccountListPageForm(accountListStore);
MoneroAccountListPageForm createState() =>
MoneroAccountListPageForm(accountListViewModel);
}
class AccountListPageForm extends State<AccountListPage> {
AccountListPageForm(this.accountListStore);
class MoneroAccountListPageForm extends State<MoneroAccountListPage> {
MoneroAccountListPageForm(this.accountListViewModel);
final AccountListStore accountListStore;
final MoneroAccountListViewModel accountListViewModel;
@override
Widget build(BuildContext context) {
final walletStore = Provider.of<WalletStore>(context);
return GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
@ -35,7 +32,8 @@ class AccountListPageForm extends State<AccountListPage> {
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 3.0, sigmaY: 3.0),
child: Container(
decoration: BoxDecoration(color: PaletteDark.darkNightBlue.withOpacity(0.75)),
decoration: BoxDecoration(
color: PaletteDark.darkNightBlue.withOpacity(0.75)),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
@ -46,11 +44,10 @@ class AccountListPageForm extends State<AccountListPage> {
S.of(context).choose_account,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
color: Colors.white
),
fontSize: 18,
fontWeight: FontWeight.bold,
decoration: TextDecoration.none,
color: Colors.white),
),
),
Padding(
@ -61,52 +58,42 @@ class AccountListPageForm extends State<AccountListPage> {
borderRadius: BorderRadius.all(Radius.circular(14)),
child: Container(
height: 296,
color: Theme.of(context).accentTextTheme.title.backgroundColor,
color: Theme.of(context)
.accentTextTheme
.title
.backgroundColor,
child: Column(
children: <Widget>[
Expanded(
child: Observer(
builder: (_) {
final accounts = accountListStore.accounts;
Expanded(child: Observer(builder: (_) {
final accounts =
widget.accountListViewModel.accounts;
return ListView.separated(
separatorBuilder: (context, index) => Divider(
color: Theme.of(context).dividerColor,
height: 1,
),
itemCount: accounts == null ? 0 : accounts.length,
itemBuilder: (context, index) {
final account = accounts[index];
return ListView.separated(
separatorBuilder: (context, index) => Divider(
color: Theme.of(context).dividerColor,
height: 1),
itemCount: accounts.length ?? 0,
itemBuilder: (context, index) {
final account = accounts[index];
return Observer(
builder: (_) {
final isCurrent = walletStore.account.id == account.id;
return AccountTile(
isCurrent: isCurrent,
accountName: account.label,
onTap: () {
if (isCurrent) {
return;
}
walletStore.setAccount(account);
Navigator.of(context).pop();
}
);
return AccountTile(
isCurrent: account.isSelected,
accountName: account.label,
onTap: () {
if (account.isSelected) {
return;
}
);
},
);
}
)
),
widget.accountListViewModel
.select(account);
Navigator.of(context).pop();
});
},
);
})),
GestureDetector(
onTap: () async {
await Navigator.of(context)
.pushNamed(Routes.accountCreation);
accountListStore.updateAccountList();
},
onTap: () async => await Navigator.of(context)
.pushNamed(Routes.accountCreation),
child: Container(
height: 62,
color: Colors.white,

View file

@ -1,241 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/stores/node_list/node_list_store.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
class NewNodePage extends BasePage {
@override
String get title => S.current.node_new;
@override
Widget body(BuildContext context) => NewNodePageForm();
}
class NewNodePageForm extends StatefulWidget {
@override
NewNodeFormState createState() => NewNodeFormState();
}
class NewNodeFormState extends State<NewNodePageForm> {
final _formKey = GlobalKey<FormState>();
final _nodeAddressController = TextEditingController();
final _nodePortController = TextEditingController();
final _loginController = TextEditingController();
final _passwordController = TextEditingController();
@override
void dispose() {
_nodeAddressController.dispose();
_nodePortController.dispose();
_loginController.dispose();
_passwordController.dispose();
super.dispose();
}
void onHandleControllers(NodeListStore nodeListStore) {
if (_nodeAddressController.text.isNotEmpty &&
_nodePortController.text.isNotEmpty) {
nodeListStore.setDisabledState(false);
} else {
nodeListStore.setDisabledState(true);
}
}
@override
Widget build(BuildContext context) {
final nodeList = Provider.of<NodeListStore>(context);
_nodeAddressController.addListener(() {onHandleControllers(nodeList);});
_nodePortController.addListener(() {onHandleControllers(nodeList);});
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0),
content: Form(
key: _formKey,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.title.color
),
decoration: InputDecoration(
hintStyle:
TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
fontSize: 16
),
hintText: S.of(context).node_address,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _nodeAddressController,
validator: (value) {
nodeList.validateNodeAddress(value);
return nodeList.errorMessage;
},
),
)
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.title.color
),
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: false),
decoration: InputDecoration(
hintStyle:
TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
fontSize: 16
),
hintText: S.of(context).node_port,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _nodePortController,
validator: (value) {
nodeList.validateNodePort(value);
return nodeList.errorMessage;
},
),
)
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.title.color
),
decoration: InputDecoration(
hintStyle:
TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
fontSize: 16
),
hintText: S.of(context).login,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _loginController,
validator: (value) => null,
),
)
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.title.color
),
decoration: InputDecoration(
hintStyle:
TextStyle(
color: Theme.of(context).primaryTextTheme.caption.color,
fontSize: 16
),
hintText: S.of(context).password,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _passwordController,
validator: (value) => null,
),
)
],
)
],
)
),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: PrimaryButton(
onPressed: () {
_nodeAddressController.text = '';
_nodePortController.text = '';
_loginController.text = '';
_passwordController.text = '';
},
text: S.of(context).reset,
color: Colors.red,
textColor: Colors.white),
)),
Flexible(
child: Container(
padding: EdgeInsets.only(left: 8.0),
child: PrimaryButton(
onPressed: () async {
if (!_formKey.currentState.validate()) {
return;
}
await nodeList.addNode(
address: _nodeAddressController.text,
port: _nodePortController.text,
login: _loginController.text,
password: _passwordController.text);
Navigator.of(context).pop();
},
text: S.of(context).save,
color: Colors.green,
textColor: Colors.white,
isDisabled: nodeList.disabledState,
),
)),
],
)
),
)
);
}
}

View file

@ -0,0 +1,249 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/node_address_validator.dart';
import 'package:cake_wallet/core/node_port_validator.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
class NodeCreateOrEditPage extends BasePage {
NodeCreateOrEditPage(this.nodeCreateOrEditViewModel)
: _formKey = GlobalKey<FormState>(),
_addressController = TextEditingController(),
_portController = TextEditingController(),
_loginController = TextEditingController(),
_passwordController = TextEditingController() {
reaction((_) => nodeCreateOrEditViewModel.address, (String address) {
if (address != _addressController.text) {
_addressController.text = address;
}
});
reaction((_) => nodeCreateOrEditViewModel.port, (String port) {
if (port != _portController.text) {
_portController.text = port;
}
});
if (nodeCreateOrEditViewModel.hasAuthCredentials) {
reaction((_) => nodeCreateOrEditViewModel.login, (String login) {
if (login != _loginController.text) {
_loginController.text = login;
}
});
reaction((_) => nodeCreateOrEditViewModel.password, (String password) {
if (password != _passwordController.text) {
_passwordController.text = password;
}
});
}
_addressController.addListener(
() => nodeCreateOrEditViewModel.address = _addressController.text);
_portController.addListener(
() => nodeCreateOrEditViewModel.port = _portController.text);
_loginController.addListener(
() => nodeCreateOrEditViewModel.login = _loginController.text);
_passwordController.addListener(
() => nodeCreateOrEditViewModel.password = _passwordController.text);
}
final GlobalKey<FormState> _formKey;
final TextEditingController _addressController;
final TextEditingController _portController;
final TextEditingController _loginController;
final TextEditingController _passwordController;
@override
String get title => S.current.node_new;
final NodeCreateOrEditViewModel nodeCreateOrEditViewModel;
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(left: 24, right: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24.0),
content: Form(
key: _formKey,
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Expanded(
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
decoration: InputDecoration(
hintStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16),
hintText: S.of(context).node_address,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _addressController,
validator: NodeAddressValidator(),
),
)
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: false),
decoration: InputDecoration(
hintStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16),
hintText: S.of(context).node_port,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _portController,
validator: NodePortValidator(),
),
)
],
),
SizedBox(height: 10.0),
if (nodeCreateOrEditViewModel.hasAuthCredentials) ...[
Row(
children: <Widget>[
Expanded(
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
decoration: InputDecoration(
hintStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16),
hintText: S.of(context).login,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _loginController,
validator: (value) => null,
),
)
],
),
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
decoration: InputDecoration(
hintStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.caption
.color,
fontSize: 16),
hintText: S.of(context).password,
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
controller: _passwordController,
validator: (value) => null,
),
)
],
)
]
],
)),
bottomSectionPadding: EdgeInsets.only(bottom: 24),
bottomSection: Observer(
builder: (_) => Row(
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: PrimaryButton(
onPressed: () => nodeCreateOrEditViewModel.reset(),
text: S.of(context).reset,
color: Colors.red,
textColor: Colors.white),
)),
Flexible(
child: Container(
padding: EdgeInsets.only(left: 8.0),
child: PrimaryButton(
onPressed: () async {
if (!_formKey.currentState.validate()) {
return;
}
await nodeCreateOrEditViewModel.save();
Navigator.of(context).pop();
},
text: S.of(context).save,
color: Colors.green,
textColor: Colors.white,
isDisabled: !nodeCreateOrEditViewModel.isReady,
),
)),
],
)),
));
}
}

View file

@ -1,35 +1,30 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_indicator.dart';
import 'package:cake_wallet/src/stores/node_list/node_list_store.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_list_row.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
class NodeListPage extends BasePage {
NodeListPage();
NodeListPage(this.nodeListViewModel);
@override
String get title => S.current.nodes;
final NodeListViewModel nodeListViewModel;
@override
Widget trailing(context) {
final nodeList = Provider.of<NodeListStore>(context);
final settings = Provider.of<SettingsStore>(context);
return Container(
height: 32,
width: 72,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16)),
color: Theme.of(context).accentTextTheme.title.backgroundColor
),
borderRadius: BorderRadius.all(Radius.circular(16)),
color: Theme.of(context).accentTextTheme.title.backgroundColor),
child: ButtonTheme(
minWidth: double.minPositive,
child: FlatButton(
@ -38,24 +33,24 @@ class NodeListPage extends BasePage {
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).node_reset_settings_title,
alertContent: S.of(context).nodes_list_reset_to_default_message,
leftButtonText: S.of(context).reset,
rightButtonText: S.of(context).cancel,
actionLeftButton: () async {
Navigator.of(context).pop();
await nodeList.reset();
await settings.setCurrentNodeToDefault();
},
actionRightButton: () => Navigator.of(context).pop()
);
alertTitle: S.of(context).node_reset_settings_title,
alertContent:
S.of(context).nodes_list_reset_to_default_message,
leftButtonText: S.of(context).reset,
rightButtonText: S.of(context).cancel,
actionLeftButton: () async {
Navigator.of(context).pop();
await nodeListViewModel.reset();
},
actionRightButton: () => Navigator.of(context).pop());
});
},
child: Text(
S.of(context).reset,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10.0,
fontSize: 14.0,
fontWeight: FontWeight.w600,
color: Colors.blue),
)),
),
@ -63,157 +58,77 @@ class NodeListPage extends BasePage {
}
@override
Widget body(context) => NodeListPageBody();
}
class NodeListPageBody extends StatefulWidget {
@override
NodeListPageBodyState createState() => NodeListPageBodyState();
}
class NodeListPageBodyState extends State<NodeListPageBody> {
@override
Widget build(BuildContext context) {
final nodeList = Provider.of<NodeListStore>(context);
final settings = Provider.of<SettingsStore>(context);
final trashImage = Image.asset('assets/images/trash.png', height: 32, width: 32, color: Colors.white);
final currentColor = Theme.of(context).accentTextTheme.subtitle.decorationColor;
final notCurrentColor = Theme.of(context).accentTextTheme.title.backgroundColor;
final currentTextColor = Colors.blue;
final notCurrentTextColor = Theme.of(context).primaryTextTheme.title.color;
Widget body(context) {
return Container(
height: double.infinity,
padding: EdgeInsets.only(top: 12),
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
NodeListRow(
title: S.of(context).add_new_node,
trailing: Icon(Icons.add,
color: Theme.of(context).primaryTextTheme.title.color,
size: 24.0),
color: Theme.of(context).accentTextTheme.title.backgroundColor,
textColor: Theme.of(context).primaryTextTheme.title.color,
onTap: () async =>
await Navigator.of(context).pushNamed(Routes.newNode),
isDrawTop: true,
isDrawBottom: true),
Expanded(
child: Padding(
padding: EdgeInsets.only(top: 32),
child: Observer(
builder: (_) => ListView.separated(
separatorBuilder: (_, __) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
),
itemCount: nodeList.nodes.length,
itemBuilder: (BuildContext context, int index) {
final node = nodeList.nodes[index];
padding: EdgeInsets.only(top: 10),
child: Observer(
builder: (_) => SectionStandardList(
sectionCount: 2,
context: context,
itemBuilder: (_, sectionIndex, index) {
if (sectionIndex == 0) {
return NodeHeaderListRow(
title: S.of(context).add_new_node,
onTap: (_) async =>
await Navigator.of(context).pushNamed(Routes.newNode));
}
final isDrawTop = index == 0 ? true : false;
final isDrawBottom = index == nodeList.nodes.length - 1 ? true : false;
final node = nodeListViewModel.nodes[index];
final isSelected = index == 1; // FIXME: hardcoded value.
final nodeListRow = NodeListRow(
title: node.uri,
isSelected: isSelected,
isAlive: node.requestNode(),
onTap: (_) {});
return Observer(
builder: (_) {
final isCurrent = settings.node == null
? false
: node.key == settings.node.key;
final dismissibleRow = Dismissible(
key: Key('${node.key}'),
confirmDismiss: (direction) async {
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).remove_node,
alertContent: S.of(context).remove_node_message,
leftButtonText: S.of(context).remove,
rightButtonText: S.of(context).cancel,
actionLeftButton: () =>
Navigator.pop(context, true),
actionRightButton: () =>
Navigator.pop(context, false));
});
},
onDismissed: (direction) async =>
nodeListViewModel.delete(node),
direction: DismissDirection.endToStart,
background: Container(
padding: EdgeInsets.only(right: 10.0),
alignment: AlignmentDirectional.centerEnd,
color: Palette.red,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
const Icon(
CupertinoIcons.delete,
color: Colors.white,
),
Text(
S.of(context).delete,
style: TextStyle(color: Colors.white),
)
],
)),
child: nodeListRow);
final content = NodeListRow(
title: node.uri,
trailing: FutureBuilder(
future: nodeList.isNodeOnline(node),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
return NodeIndicator(
color: snapshot.data as bool
? Palette.green
: Palette.red);
default:
return NodeIndicator();
}
}),
color: isCurrent ? currentColor : notCurrentColor,
textColor: isCurrent ? currentTextColor : notCurrentTextColor,
onTap: () async {
if (!isCurrent) {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.current.nodes,
alertContent: S.of(context)
.change_current_node(node.uri),
leftButtonText: S.of(context).change,
rightButtonText: S.of(context).cancel,
actionLeftButton: () async {
Navigator.of(context).pop();
await settings.setCurrentNode(
node: node);
},
actionRightButton: () => Navigator.of(context).pop()
);
});
}
},
isDrawTop: isDrawTop,
isDrawBottom: isDrawBottom);
return isSelected ? nodeListRow : dismissibleRow;
},
itemCounter: (int sectionIndex) {
if (sectionIndex == 0) {
return 1;
}
return isCurrent
? content
: Dismissible(
key: Key('${node.key}'),
confirmDismiss: (direction) async {
return await showDialog(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).remove_node,
alertContent: S.of(context).remove_node_message,
leftButtonText: S.of(context).remove,
rightButtonText: S.of(context).cancel,
actionLeftButton: () =>
Navigator.pop(context, true),
actionRightButton: () =>
Navigator.pop(context, false)
);
});
},
onDismissed: (direction) async =>
await nodeList.remove(node: node),
direction: DismissDirection.endToStart,
background: Container(
padding: EdgeInsets.only(right: 10.0, top: 2),
alignment: AlignmentDirectional.centerEnd,
color: Palette.red,
child: Column(
children: <Widget>[
trashImage,
Text(
S.of(context).delete,
style: TextStyle(color: Colors.white),
)
],
)),
child: content);
},
);
})
),
)
)
],
return nodeListViewModel.nodes.length;
}),
),
);
}

View file

@ -2,16 +2,17 @@ import 'package:flutter/material.dart';
import 'package:cake_wallet/palette.dart';
class NodeIndicator extends StatelessWidget {
NodeIndicator({this.color = Palette.red});
NodeIndicator({this.isLive = false});
final Color color;
final bool isLive;
@override
Widget build(BuildContext context) {
return Container(
width: 8.0,
height: 8.0,
decoration: BoxDecoration(shape: BoxShape.circle, color: color),
decoration: BoxDecoration(
shape: BoxShape.circle, color: isLive ? Palette.green : Palette.red),
);
}
}

View file

@ -1,62 +1,43 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/nodes/widgets/node_indicator.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class NodeListRow extends StatelessWidget {
NodeListRow({
@required this.title,
@required this.trailing,
@required this.color,
@required this.textColor,
@required this.onTap,
@required this.isDrawTop,
@required this.isDrawBottom});
class NodeListRow extends StandardListRow {
NodeListRow(
{@required String title,
@required void Function(BuildContext context) onTap,
@required bool isSelected,
@required this.isAlive})
: super(title: title, onTap: onTap, isSelected: isSelected);
final String title;
final Widget trailing;
final Color color;
final Color textColor;
final VoidCallback onTap;
final bool isDrawTop;
final bool isDrawBottom;
final Future<bool> isAlive;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
isDrawTop
? Container(
width: double.infinity,
height: 1,
color: Theme.of(context).dividerColor,
)
: Offstage(),
Container(
width: double.infinity,
height: 56,
color: color,
child: ListTile(
contentPadding: EdgeInsets.only(
left: 24,
right: 24,
),
title: Text(
title,
style: TextStyle(
fontSize: 14,
color: textColor
),
textAlign: TextAlign.left),
trailing: trailing,
onTap: onTap,
)
),
isDrawBottom
? Container(
width: double.infinity,
height: 1,
color: Theme.of(context).dividerColor,
)
: Offstage(),
],
);
Widget buildTrailing(BuildContext context) {
return FutureBuilder(
future: isAlive,
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.done:
return NodeIndicator(isLive: snapshot.data as bool);
default:
return NodeIndicator();
}
});
}
}
class NodeHeaderListRow extends StandardListRow {
NodeHeaderListRow({@required String title, @required void Function(BuildContext context) onTap})
: super(title: title, onTap: onTap, isSelected: false);
@override
Widget buildTrailing(BuildContext context) {
return Icon(Icons.add,
color: Theme.of(context).primaryTextTheme.title.color, size: 24.0);
}
}

View file

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/common/biometric_auth.dart';
abstract class PinCodeWidget extends StatefulWidget {
PinCodeWidget({Key key, this.onPinCodeEntered, this.hasLengthSwitcher})
@ -29,6 +30,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
static const sixPinLength = 6;
static const fourPinLength = 4;
final _gridViewKey = GlobalKey();
final _key = GlobalKey<ScaffoldState>();
int pinLength = defaultPinLength;
List<int> pin = List<int>.filled(defaultPinLength, null);
@ -83,9 +85,12 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
}
@override
Widget build(BuildContext context) => Scaffold(body: body(context));
Widget build(BuildContext context) =>
Scaffold(key: _key, body: body(context));
Widget body(BuildContext context) {
final settingsStore = Provider.of<SettingsStore>(context);
final deleteIconImage = Image.asset(
'assets/images/delete_icon.png',
color: Theme.of(context).primaryTextTheme.title.color,
@ -95,8 +100,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
color: Theme.of(context).primaryTextTheme.title.color,
);
return SafeArea(
child: Container(
return Container(
color: Theme.of(context).backgroundColor,
padding: EdgeInsets.only(left: 40.0, right: 40.0, bottom: 40.0),
child: Column(children: <Widget>[
@ -105,8 +109,7 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.title.color
)),
color: Theme.of(context).primaryTextTheme.title.color)),
Spacer(flex: 3),
Container(
width: 180,
@ -138,7 +141,9 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
},
child: Text(
_changePinLengthText(),
style: TextStyle(fontSize: 14.0, color: Theme.of(context).primaryTextTheme.caption.color),
style: TextStyle(
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.caption.color),
))
],
Spacer(flex: 1),
@ -161,10 +166,38 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
margin: EdgeInsets.only(
left: marginLeft, right: marginRight),
child: FlatButton(
onPressed: () {},
onPressed: (widget.hasLengthSwitcher ||
!settingsStore
.allowBiometricalAuthentication)
? null
: () {
// FIXME
// if (authStore != null) {
// WidgetsBinding.instance.addPostFrameCallback((_) {
// final biometricAuth = BiometricAuth();
// biometricAuth.isAuthenticated().then(
// (isAuth) {
// if (isAuth) {
// authStore.biometricAuth();
// _key.currentState.showSnackBar(
// SnackBar(
// content: Text(S.of(context).authenticated),
// backgroundColor: Colors.green,
// ),
// );
// }
// }
// );
// });
// }
},
color: Theme.of(context).backgroundColor,
shape: CircleBorder(),
child: faceImage,
child: (widget.hasLengthSwitcher ||
!settingsStore
.allowBiometricalAuthentication)
? Offstage()
: faceImage,
),
);
} else if (index == 10) {
@ -195,14 +228,17 @@ class PinCodeState<T extends PinCodeWidget> extends State<T> {
style: TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.title.color)),
color: Theme.of(context)
.primaryTextTheme
.title
.color)),
),
);
}),
)
: null))
]),
));
);
}
void _push(int num) {

View file

@ -1,29 +1,22 @@
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:provider/provider.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/stores/subaddress_list/subaddress_list_store.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
import 'package:cake_wallet/src/screens/accounts/account_list_page.dart';
import 'package:cake_wallet/src/stores/account_list/account_list_store.dart';
import 'package:cake_wallet/src/screens/monero_accounts/monero_account_list_page.dart';
import 'package:cake_wallet/src/screens/receive/widgets/header_tile.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/themes.dart';
import 'package:cake_wallet/theme_changer.dart';
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cake_wallet/src/screens/receive/widgets/address_cell.dart';
import 'package:cake_wallet/view_model/address_list/account_list_header.dart';
import 'package:cake_wallet/view_model/address_list/address_list_header.dart';
import 'package:cake_wallet/view_model/address_list/address_list_item.dart';
import 'package:cake_wallet/view_model/address_list/address_list_view_model.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_account_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_header.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
class ReceivePage extends BasePage {
ReceivePage({this.addressListViewModel})
@ -33,7 +26,7 @@ class ReceivePage extends BasePage {
_formKey.currentState.validate() ? amountController.text : '');
}
final AddressListViewModel addressListViewModel;
final WalletAddressListViewModel addressListViewModel;
final TextEditingController amountController;
final GlobalKey<FormState> _formKey;
@ -83,11 +76,6 @@ class ReceivePage extends BasePage {
);
}
@override
Widget build(BuildContext context) {
return super.build(context);
}
@override
Widget body(BuildContext context) {
final copyImage = Image.asset('assets/images/copy_content.png',
@ -186,7 +174,7 @@ class ReceivePage extends BasePage {
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.title
@ -213,17 +201,11 @@ class ReceivePage extends BasePage {
final item = addressListViewModel.items[index];
Widget cell = Container();
if (item is AccountListHeader) {
if (item is WalletAccountListHeader) {
cell = HeaderTile(
onTap: () async {
await showDialog<void>(
context: context,
builder: (BuildContext context) {
// return AccountListPage(
// accountListStore:
// accountListStore);
});
},
onTap: () async => await showDialog<void>(
context: context,
builder: (_) => getIt.get<MoneroAccountListPage>()),
title: addressListViewModel.accountLabel,
icon: Icon(
Icons.arrow_forward_ios,
@ -233,11 +215,11 @@ class ReceivePage extends BasePage {
));
}
if (item is AddressListHeader) {
if (item is WalletAddressListHeader) {
cell = HeaderTile(
onTap: () => Navigator.of(context)
.pushNamed(Routes.newSubaddress),
title: S.of(context).subaddresses,
title: S.of(context).addresses,
icon: Icon(
Icons.add,
size: 20,
@ -246,15 +228,15 @@ class ReceivePage extends BasePage {
));
}
if (item is AddressListItem) {
if (item is WalletAddressListItem) {
cell = Observer(
builder: (_) => AddressCell.fromItem(item,
isCurrent: item.address ==
addressListViewModel.address.address,
onTap: (_) =>
addressListViewModel.address = item,
onEdit: () => Navigator.of(context)
.pushNamed(Routes.newSubaddress, arguments: item)));
onTap: (_) => addressListViewModel.address = item,
onEdit: () => Navigator.of(context).pushNamed(
Routes.newSubaddress,
arguments: item)));
}
return index != 0

View file

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/address_list/address_list_item.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_item.dart';
class AddressCell extends StatelessWidget {
factory AddressCell.fromItem(AddressListItem item,
factory AddressCell.fromItem(WalletAddressListItem item,
{@required bool isCurrent,
Function(String) onTap,
Function() onEdit}) =>
@ -47,7 +47,7 @@ class AddressCell extends StatelessWidget {
name ?? address,
style: TextStyle(
fontSize: name?.isNotEmpty ?? false ? 18 : 10,
fontWeight: FontWeight.bold,
fontWeight: FontWeight.w600,
color: isCurrent ? currentTextColor : notCurrentTextColor,
),
),

View file

@ -32,7 +32,7 @@ class HeaderTile extends StatelessWidget {
title,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
fontWeight: FontWeight.w500,
color: Theme.of(context).primaryTextTheme.title.color
),
),

View file

@ -20,7 +20,6 @@ import 'package:cake_wallet/src/domain/services/wallet_service.dart';
import 'package:cake_wallet/src/domain/exchange/trade.dart';
import 'package:cake_wallet/src/domain/monero/transaction_description.dart';
import 'package:cake_wallet/src/screens/auth/create_login_page.dart';
import 'package:cake_wallet/src/screens/seed/create_seed_page.dart';
import 'package:cake_wallet/src/screens/dashboard/create_dashboard_page.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/welcome/create_welcome_page.dart';
@ -104,13 +103,13 @@ class RootState extends State<Root> with WidgetsBindingObserver {
return Observer(builder: (_) {
final state = widget.authenticationStore.state;
print(state);
if (state == AuthenticationState.denied) {
return createWelcomePage();
}
if (state == AuthenticationState.installed) {
return getIt.get<AuthPage>();
return getIt.get<AuthPage>(instanceName: 'login');
}
if (state == AuthenticationState.allowed) {

View file

@ -1,14 +0,0 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:cake_wallet/src/domain/services/wallet_service.dart';
import 'package:cake_wallet/src/screens/seed/seed_page.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/src/stores/wallet_seed/wallet_seed_store.dart';
Widget createSeedPage(
{@required SettingsStore settingsStore,
@required WalletService walletService,
@required void Function() callback}) =>
Provider(
create: (_) => WalletSeedStore(walletService: walletService),
child: SeedPage(onCloseCallback: callback));

View file

@ -1,161 +0,0 @@
import 'package:provider/provider.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/stores/wallet_seed/wallet_seed_store.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
class SeedPage extends BasePage {
SeedPage({this.onCloseCallback});
static final image = Image.asset('assets/images/crypto_lock.png');
@override
String get title => S.current.seed_title;
final VoidCallback onCloseCallback;
@override
void onClose(BuildContext context) =>
onCloseCallback != null ? onCloseCallback() : Navigator.of(context).pop();
@override
Widget leading(BuildContext context) {
return onCloseCallback != null ? Offstage() : super.leading(context);
}
@override
Widget trailing(BuildContext context) {
return onCloseCallback != null
? GestureDetector(
onTap: () => onClose(context),
child: Container(
width: 70,
height: 32,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16)),
color: Theme.of(context).accentTextTheme.title.color
),
child: Text(
S.of(context).seed_language_next,
style: TextStyle(
fontSize: 10,
fontWeight: FontWeight.w600,
color: Colors.blue
),
),
),
)
: Offstage();
}
@override
Widget body(BuildContext context) {
final walletSeedStore = Provider.of<WalletSeedStore>(context);
String _seed;
return Container(
width: double.infinity,
height: double.infinity,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(left: 40, right: 40, bottom: 20),
content: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 33),
child: image,
),
Padding(
padding: EdgeInsets.only(top: 33),
child: Observer(
builder: (_) {
_seed = walletSeedStore.seed;
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
walletSeedStore.name,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.title.color
),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: Text(
_seed,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryTextTheme.caption.color
),
),
)
],
);
}
),
)
],
),
bottomSectionPadding: EdgeInsets.only(
left: 24,
right: 24,
bottom: 52
),
bottomSection: Container(
child: Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: PrimaryButton(
onPressed: () => Share.text(
S.of(context).seed_share,
_seed,
'text/plain'),
text: S.of(context).save,
color: Colors.green,
textColor: Colors.white),
)
),
Flexible(
child: Container(
padding: EdgeInsets.only(left: 8.0),
child: Builder(
builder: (context) => PrimaryButton(
onPressed: () {
Clipboard.setData(
ClipboardData(text: _seed));
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(S
.of(context)
.copied_to_clipboard),
backgroundColor: Colors.green,
duration: Duration(milliseconds: 1500),
),
);
},
text: S.of(context).copy,
color: Colors.blue,
textColor: Colors.white)
),
)
)
],
),
),
),
);
}
}

View file

@ -0,0 +1,154 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'package:esys_flutter_share/esys_flutter_share.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/themes.dart';
import 'package:cake_wallet/theme_changer.dart';
import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
class WalletSeedPage extends BasePage {
WalletSeedPage(this.walletSeedViewModel, {this.onCloseCallback});
static final imageLight = Image.asset('assets/images/crypto_lock_light.png');
static final imageDark = Image.asset('assets/images/crypto_lock.png');
@override
String get title => S.current.seed_title;
final VoidCallback onCloseCallback;
final WalletSeedViewModel walletSeedViewModel;
@override
void onClose(BuildContext context) =>
onCloseCallback != null ? onCloseCallback() : Navigator.of(context).pop();
@override
Widget leading(BuildContext context) =>
onCloseCallback != null ? Offstage() : super.leading(context);
@override
Widget trailing(BuildContext context) {
return onCloseCallback != null
? GestureDetector(
onTap: () => onClose(context),
child: Container(
width: 100,
height: 42,
alignment: Alignment.center,
margin: EdgeInsets.only(left: 10),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16)),
color: Theme.of(context).accentTextTheme.title.color),
child: Text(
S.of(context).seed_language_next,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Colors.blue),
),
),
)
: Offstage();
}
@override
Widget body(BuildContext context) {
final _themeChanger = Provider.of<ThemeChanger>(context);
final image =
_themeChanger.getTheme() == Themes.darkTheme ? imageDark : imageLight;
return Container(
padding: EdgeInsets.all(24),
child: Column(
children: <Widget>[
Flexible(
flex: 2,
child: AspectRatio(
aspectRatio: 1,
child: FittedBox(child: image, fit: BoxFit.fill))),
Flexible(
flex: 3,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 33),
child: Observer(builder: (_) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text(
walletSeedViewModel.name,
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
Padding(
padding: EdgeInsets.only(top: 20),
child: Text(
walletSeedViewModel.seed,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.primaryTextTheme
.caption
.color),
),
)
],
);
}),
),
Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Flexible(
child: Container(
padding: EdgeInsets.only(right: 8.0),
child: PrimaryButton(
onPressed: () => Share.text(
S.of(context).seed_share,
walletSeedViewModel.seed,
'text/plain'),
text: S.of(context).save,
color: Colors.green,
textColor: Colors.white),
)),
Flexible(
child: Container(
padding: EdgeInsets.only(left: 8.0),
child: Builder(
builder: (context) => PrimaryButton(
onPressed: () {
Clipboard.setData(ClipboardData(
text: walletSeedViewModel.seed));
Scaffold.of(context).showSnackBar(
SnackBar(
content: Text(
S.of(context).copied_to_clipboard),
backgroundColor: Colors.green,
duration: Duration(milliseconds: 1500),
),
);
},
text: S.of(context).copy,
color: Colors.blue,
textColor: Colors.white)),
))
],
)
],
))
],
));
}
}

View file

@ -1,5 +1,8 @@
import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/view_model/send_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -14,7 +17,8 @@ import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/src/stores/balance/balance_store.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
import 'package:cake_wallet/src/stores/send/send_store.dart';
import 'package:cake_wallet/src/stores/send/sending_state.dart';
//import 'package:cake_wallet/src/stores/send/sending_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/domain/common/crypto_currency.dart';
import 'package:cake_wallet/src/domain/common/calculate_estimated_fee.dart';
@ -29,8 +33,13 @@ import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart'
import 'package:cake_wallet/src/screens/send/widgets/sending_alert.dart';
import 'package:cake_wallet/src/widgets/template_tile.dart';
import 'package:cake_wallet/src/stores/send_template/send_template_store.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
class SendPage extends BasePage {
SendPage({@required this.sendViewModel});
final SendViewModel sendViewModel;
@override
String get title => S.current.send_title;
@ -45,35 +54,20 @@ class SendPage extends BasePage {
@override
Widget trailing(context) {
final sendStore = Provider.of<SendStore>(context);
// final sendStore = Provider.of<SendStore>(context);
return Container(
height: 32,
width: 82,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(16)),
color: Theme.of(context).accentTextTheme.title.color
),
child: ButtonTheme(
minWidth: double.minPositive,
child: FlatButton(
onPressed: () => sendStore.clear(),
child: Text(
S.of(context).clear,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 10.0,
color: Colors.blue),
)),
),
);
return TrailButton(caption: S.of(context).clear, onPressed: () => null);
}
@override
Widget body(BuildContext context) => SendForm();
Widget body(BuildContext context) => SendForm(sendViewModel: sendViewModel);
}
class SendForm extends StatefulWidget {
SendForm({this.sendViewModel});
final SendViewModel sendViewModel;
@override
State<StatefulWidget> createState() => SendFormState();
}
@ -92,7 +86,7 @@ class SendFormState extends State<SendForm> {
@override
void initState() {
_focusNode.addListener(() {
if (!_focusNode.hasFocus &&_addressController.text.isNotEmpty) {
if (!_focusNode.hasFocus && _addressController.text.isNotEmpty) {
getOpenaliasRecord(context);
}
});
@ -102,7 +96,8 @@ class SendFormState extends State<SendForm> {
Future<void> getOpenaliasRecord(BuildContext context) async {
final sendStore = Provider.of<SendStore>(context);
final isOpenalias = await sendStore.isOpenaliasRecord(_addressController.text);
final isOpenalias =
await sendStore.isOpenaliasRecord(_addressController.text);
if (isOpenalias) {
_addressController.text = sendStore.recordAddress;
@ -111,24 +106,24 @@ class SendFormState extends State<SendForm> {
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).openalias_alert_title,
alertContent: S.of(context).openalias_alert_content(sendStore.recordName),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()
);
alertTitle: S.of(context).openalias_alert_title,
alertContent:
S.of(context).openalias_alert_content(sendStore.recordName),
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
}
}
@override
Widget build(BuildContext context) {
final settingsStore = Provider.of<SettingsStore>(context);
final sendStore = Provider.of<SendStore>(context);
sendStore.settingsStore = settingsStore;
final balanceStore = Provider.of<BalanceStore>(context);
final walletStore = Provider.of<WalletStore>(context);
final syncStore = Provider.of<SyncStore>(context);
final sendTemplateStore = Provider.of<SendTemplateStore>(context);
// final settingsStore = Provider.of<SettingsStore>(context);
// final sendStore = Provider.of<SendStore>(context);
// sendStore.settingsStore = settingsStore;
// final balanceStore = Provider.of<BalanceStore>(context);
// final walletStore = Provider.of<WalletStore>(context);
// final syncStore = Provider.of<SyncStore>(context);
// final sendTemplateStore = Provider.of<SendTemplateStore>(context);
_setEffects(context);
@ -166,110 +161,116 @@ class SendFormState extends State<SendForm> {
AddressTextFieldOption.addressBook
],
buttonColor: Theme.of(context).accentTextTheme.title.color,
validator: (value) {
sendStore.validateAddress(value,
cryptoCurrency: CryptoCurrency.xmr);
return sendStore.errorMessage;
},
validator: widget.sendViewModel.addressValidator,
),
Observer(
builder: (_) {
return Padding(
padding: const EdgeInsets.only(top: 20),
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.title.color
),
controller: _cryptoAmountController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
inputFormatters: [
BlacklistingTextInputFormatter(
RegExp('[\\-|\\ |\\,]'))
],
decoration: InputDecoration(
prefixIcon: Padding(
padding: EdgeInsets.only(top: 12),
child: Text('XMR:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.title.color,
)),
),
suffixIcon: Padding(
padding: EdgeInsets.only(
bottom: 5
),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width/2,
alignment: Alignment.centerLeft,
child: Text(
' / ' + balanceStore.unlockedBalance,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).primaryTextTheme.caption.color
)
Observer(builder: (_) {
return Padding(
padding: const EdgeInsets.only(top: 20),
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
controller: _cryptoAmountController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
inputFormatters: [
BlacklistingTextInputFormatter(
RegExp('[\\-|\\ |\\,]'))
],
decoration: InputDecoration(
prefixIcon: Padding(
padding: EdgeInsets.only(top: 12),
child: Text('XMR:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
)),
),
suffixIcon: Padding(
padding: EdgeInsets.only(bottom: 5),
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: <Widget>[
Container(
width:
MediaQuery.of(context).size.width / 2,
alignment: Alignment.centerLeft,
child: Text(
' / ' + widget.sendViewModel.balance,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
color: Theme.of(context)
.primaryTextTheme
.caption
.color)),
),
Container(
height: 32,
width: 32,
margin: EdgeInsets.only(
left: 12, bottom: 7, top: 4),
decoration: BoxDecoration(
color: Theme.of(context)
.accentTextTheme
.title
.color,
borderRadius: BorderRadius.all(
Radius.circular(6))),
child: InkWell(
onTap: () => null,
// widget.sendViewModel,
child: Center(
child: Text(S.of(context).all,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.caption
.color)),
),
),
Container(
height: 32,
width: 32,
margin: EdgeInsets.only(left: 12, bottom: 7, top: 4),
decoration: BoxDecoration(
color: Theme.of(context).accentTextTheme.title.color,
borderRadius: BorderRadius.all(Radius.circular(6))
),
child: InkWell(
onTap: () => sendStore.setSendAll(),
child: Center(
child: Text(S.of(context).all,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 9,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.caption.color
)
),
),
),
)
],
),
)
],
),
hintStyle: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.title.color),
hintText: '0.0000',
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
validator: (value) {
sendStore.validateXMR(
value, balanceStore.unlockedBalance);
return sendStore.errorMessage;
}),
);
}
),
),
hintStyle: TextStyle(
fontSize: 16.0,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
hintText: '0.0000',
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0)),
enabledBorder: UnderlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).dividerColor,
width: 1.0))),
validator: widget.sendViewModel.amountValidator),
);
}),
Padding(
padding: const EdgeInsets.only(top: 20),
child: TextFormField(
style: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.title.color),
color:
Theme.of(context).primaryTextTheme.title.color),
controller: _fiatAmountController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
@ -281,16 +282,22 @@ class SendFormState extends State<SendForm> {
prefixIcon: Padding(
padding: EdgeInsets.only(top: 12),
child: Text(
'${settingsStore.fiatCurrency.toString()}:',
'${widget.sendViewModel.fiat.toString()}:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Theme.of(context).primaryTextTheme.title.color,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
)),
),
hintStyle: TextStyle(
fontSize: 16.0,
color: Theme.of(context).primaryTextTheme.caption.color),
color: Theme.of(context)
.primaryTextTheme
.caption
.color),
hintText: '0.00',
focusedBorder: UnderlineInputBorder(
borderSide: BorderSide(
@ -310,14 +317,20 @@ class SendFormState extends State<SendForm> {
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.title.color,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
)),
Text(
'${calculateEstimatedFee(priority: settingsStore.transactionPriority)} XMR',
'${widget.sendViewModel.estimatedFee} ${widget.sendViewModel.currency.toString()}',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.title.color,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
))
],
),
@ -325,143 +338,140 @@ class SendFormState extends State<SendForm> {
]),
),
),
Padding(
padding: EdgeInsets.only(
top: 32,
left: 24,
bottom: 24
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Text(
S.of(context).send_templates,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.caption.color
),
)
],
),
),
Container(
height: 40,
width: double.infinity,
padding: EdgeInsets.only(left: 24),
child: Observer(
builder: (_) {
final itemCount = sendTemplateStore.templates.length + 1;
return ListView.builder(
scrollDirection: Axis.horizontal,
itemCount: itemCount,
itemBuilder: (context, index) {
if (index == 0) {
return GestureDetector(
onTap: () => Navigator.of(context)
.pushNamed(Routes.sendTemplate),
child: Container(
padding: EdgeInsets.only(right: 10),
child: DottedBorder(
borderType: BorderType.RRect,
dashPattern: [8, 4],
color: Theme.of(context).accentTextTheme.title.backgroundColor,
strokeWidth: 2,
radius: Radius.circular(20),
child: Container(
height: 40,
width: 75,
padding: EdgeInsets.only(left: 10, right: 10),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(20)),
color: Colors.transparent,
),
child: Text(
S.of(context).send_new,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w600,
color: Theme.of(context).primaryTextTheme.caption.color
),
),
)
),
),
);
}
index -= 1;
final template = sendTemplateStore.templates[index];
return TemplateTile(
to: template.name,
amount: template.amount,
from: template.cryptoCurrency,
onTap: () {
_addressController.text = template.address;
_cryptoAmountController.text = template.amount;
getOpenaliasRecord(context);
}
);
}
);
}
),
)
// Padding(
// padding: EdgeInsets.only(top: 32, left: 24, bottom: 24),
// child: Row(
// mainAxisAlignment: MainAxisAlignment.start,
// children: <Widget>[
// Text(
// S.of(context).send_templates,
// style: TextStyle(
// fontSize: 18,
// fontWeight: FontWeight.w600,
// color:
// Theme.of(context).primaryTextTheme.caption.color),
// )
// ],
// ),
// ),
// Container(
// height: 40,
// width: double.infinity,
// padding: EdgeInsets.only(left: 24),
// child: Observer(builder: (_) {
// final itemCount = sendTemplateStore.templates.length + 1;
//
// return ListView.builder(
// scrollDirection: Axis.horizontal,
// itemCount: itemCount,
// itemBuilder: (context, index) {
// if (index == 0) {
// return GestureDetector(
// onTap: () => Navigator.of(context)
// .pushNamed(Routes.sendTemplate),
// child: Container(
// padding: EdgeInsets.only(right: 10),
// child: DottedBorder(
// borderType: BorderType.RRect,
// dashPattern: [8, 4],
// color: Theme.of(context)
// .accentTextTheme
// .title
// .backgroundColor,
// strokeWidth: 2,
// radius: Radius.circular(20),
// child: Container(
// height: 40,
// width: 75,
// padding: EdgeInsets.only(left: 10, right: 10),
// alignment: Alignment.center,
// decoration: BoxDecoration(
// borderRadius:
// BorderRadius.all(Radius.circular(20)),
// color: Colors.transparent,
// ),
// child: Text(
// S.of(context).send_new,
// style: TextStyle(
// fontSize: 14,
// fontWeight: FontWeight.w600,
// color: Theme.of(context)
// .primaryTextTheme
// .caption
// .color),
// ),
// )),
// ),
// );
// }
//
// index -= 1;
//
// final template = sendTemplateStore.templates[index];
//
// return TemplateTile(
// to: template.name,
// amount: template.amount,
// from: template.cryptoCurrency,
// onTap: () {
// _addressController.text = template.address;
// _cryptoAmountController.text = template.amount;
// getOpenaliasRecord(context);
// });
// });
// }),
// )
],
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: Observer(builder: (_) {
return LoadingPrimaryButton(
onPressed: syncStore.status is SyncedSyncStatus
? () async {
// Hack. Don't ask me.
FocusScope.of(context).requestFocus(FocusNode());
if (_formKey.currentState.validate()) {
await showDialog<void>(
context: context,
builder: (dialogContext) {
return AlertWithTwoActions(
alertTitle: S.of(context).send_creating_transaction,
alertContent: S.of(context).confirm_sending,
leftButtonText: S.of(context).send,
rightButtonText: S.of(context).cancel,
actionLeftButton: () async {
await Navigator.of(dialogContext)
.popAndPushNamed(Routes.auth,
arguments: (bool
isAuthenticatedSuccessfully,
AuthPageState auth) {
if (!isAuthenticatedSuccessfully) {
return;
}
Navigator.of(auth.context).pop();
sendStore.createTransaction(
address: _addressController.text,
paymentId: '');
});
},
actionRightButton: () =>
Navigator.of(context).pop()
);
});
}
}
: null,
onPressed: () => null,
// syncStore.status is SyncedSyncStatus
// ? () async {
// // Hack. Don't ask me.
// FocusScope.of(context).requestFocus(FocusNode());
//
// if (_formKey.currentState.validate()) {
// await showDialog<void>(
// context: context,
// builder: (dialogContext) {
// return AlertWithTwoActions(
// alertTitle:
// S.of(context).send_creating_transaction,
// alertContent: S.of(context).confirm_sending,
// leftButtonText: S.of(context).send,
// rightButtonText: S.of(context).cancel,
// actionLeftButton: () async {
// await Navigator.of(dialogContext)
// .popAndPushNamed(Routes.auth, arguments:
// (bool isAuthenticatedSuccessfully,
// AuthPageState auth) {
// if (!isAuthenticatedSuccessfully) {
// return;
// }
//
// Navigator.of(auth.context).pop();
//
// sendStore.createTransaction(
// address: _addressController.text,
// paymentId: '');
// });
// },
// actionRightButton: () =>
// Navigator.of(context).pop());
// });
// }
// }
// : null,
text: S.of(context).send,
color: Colors.blue,
textColor: Colors.white,
isLoading: sendStore.state is CreatingTransaction ||
sendStore.state is TransactionCommiting,
isDisabled: !(syncStore.status is SyncedSyncStatus),
);
isLoading: widget.sendViewModel.state is TransactionIsCreating ||
widget.sendViewModel.state is TransactionCommitting,
isDisabled:
false // FIXME !(syncStore.status is SyncedSyncStatus),
);
}),
),
);
@ -472,93 +482,88 @@ class SendFormState extends State<SendForm> {
return;
}
final sendStore = Provider.of<SendStore>(context);
// reaction((_) => widget.sendViewModel.fiatAmount, (String amount) {
// if (amount != _fiatAmountController.text) {
// _fiatAmountController.text = amount;
// }
// });
//
// reaction((_) => widget.sendViewModel.cryptoAmount, (String amount) {
// if (amount != _cryptoAmountController.text) {
// _cryptoAmountController.text = amount;
// }
// });
//
// reaction((_) => widget.sendViewModel.address, (String address) {
// if (address != _addressController.text) {
// _addressController.text = address;
// }
// });
//
// _addressController.addListener(() {
// final address = _addressController.text;
//
// if (widget.sendViewModel.address != address) {
// widget.sendViewModel.changeAddress(address);
// }
// });
reaction((_) => sendStore.fiatAmount, (String amount) {
if (amount != _fiatAmountController.text) {
_fiatAmountController.text = amount;
}
});
// _fiatAmountController.addListener(() {
// final fiatAmount = _fiatAmountController.text;
//
// if (sendStore.fiatAmount != fiatAmount) {
// sendStore.changeFiatAmount(fiatAmount);
// }
// });
reaction((_) => sendStore.cryptoAmount, (String amount) {
if (amount != _cryptoAmountController.text) {
_cryptoAmountController.text = amount;
}
});
// _cryptoAmountController.addListener(() {
// final cryptoAmount = _cryptoAmountController.text;
//
// if (sendStore.cryptoAmount != cryptoAmount) {
// sendStore.changeCryptoAmount(cryptoAmount);
// }
// });
reaction((_) => sendStore.address, (String address) {
if (address != _addressController.text) {
_addressController.text = address;
}
});
_addressController.addListener(() {
final address = _addressController.text;
if (sendStore.address != address) {
sendStore.changeAddress(address);
}
});
_fiatAmountController.addListener(() {
final fiatAmount = _fiatAmountController.text;
if (sendStore.fiatAmount != fiatAmount) {
sendStore.changeFiatAmount(fiatAmount);
}
});
_cryptoAmountController.addListener(() {
final cryptoAmount = _cryptoAmountController.text;
if (sendStore.cryptoAmount != cryptoAmount) {
sendStore.changeCryptoAmount(cryptoAmount);
}
});
reaction((_) => sendStore.state, (SendingState state) {
reaction((_) => widget.sendViewModel.state, (SendViewModelState state) {
if (state is SendingFailed) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()
);
alertTitle: S.of(context).error,
alertContent: state.error,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
});
}
if (state is TransactionCreatedSuccessfully) {
WidgetsBinding.instance.addPostFrameCallback((_) {
showDialog<void>(
context: context,
builder: (BuildContext context) {
return ConfirmSendingAlert(
alertTitle: S.of(context).confirm_sending,
amount: S.of(context).send_amount,
amountValue: sendStore.pendingTransaction.amount,
fee: S.of(context).send_fee,
feeValue: sendStore.pendingTransaction.fee,
leftButtonText: S.of(context).ok,
rightButtonText: S.of(context).cancel,
actionLeftButton: () {
Navigator.of(context).pop();
sendStore.commitTransaction();
showDialog<void>(
context: context,
builder: (BuildContext context) {
return SendingAlert(sendStore: sendStore);
}
);
},
actionRightButton: () => Navigator.of(context).pop()
);
});
});
// WidgetsBinding.instance.addPostFrameCallback((_) {
// showDialog<void>(
// context: context,
// builder: (BuildContext context) {
// return ConfirmSendingAlert(
// alertTitle: S.of(context).confirm_sending,
// amount: S.of(context).send_amount,
// amountValue: sendStore.pendingTransaction.amount,
// fee: S.of(context).send_fee,
// feeValue: sendStore.pendingTransaction.fee,
// leftButtonText: S.of(context).ok,
// rightButtonText: S.of(context).cancel,
// actionLeftButton: () {
// Navigator.of(context).pop();
// sendStore.commitTransaction();
// showDialog<void>(
// context: context,
// builder: (BuildContext context) {
// return SendingAlert(sendStore: sendStore);
// });
// },
// actionRightButton: () => Navigator.of(context).pop());
// });
// });
}
if (state is TransactionCommitted) {

View file

@ -55,7 +55,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
@ -64,7 +64,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
)
@ -79,7 +79,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
@ -88,7 +88,7 @@ class ConfirmSendingAlert extends BaseAlertDialog {
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Colors.white,
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
)

View file

@ -1,438 +1,72 @@
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/common/balance_display_mode.dart';
import 'package:cake_wallet/src/domain/common/fiat_currency.dart';
import 'package:cake_wallet/src/domain/common/transaction_priority.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/src/stores/action_list/action_list_display_mode.dart';
import 'package:cake_wallet/view_model/settings/settings_view_model.dart';
import 'package:cake_wallet/view_model/settings/link_list_item.dart';
import 'package:cake_wallet/view_model/settings/picker_list_item.dart';
import 'package:cake_wallet/view_model/settings/regular_list_item.dart';
import 'package:cake_wallet/view_model/settings/switcher_list_item.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_link_provider_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_cell_with_arrow.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_picker_cell.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switcher_cell.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/settings/attributes.dart';
import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart';
import 'package:cake_wallet/src/screens/settings/items/settings_item.dart';
import 'package:cake_wallet/src/screens/settings/items/item_headers.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
// Settings widgets
import 'package:cake_wallet/src/screens/settings/widgets/settings_arrow_list_row.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_header_list_row.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_link_list_row.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_switch_list_row.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_text_list_row.dart';
import 'package:cake_wallet/src/screens/settings/widgets/settings_raw_widget_list_row.dart';
class SettingsPage extends BasePage {
SettingsPage(this.settingsViewModel);
final SettingsViewModel settingsViewModel;
@override
String get title => S.current.settings_title;
@override
Widget body(BuildContext context) {
return SettingsForm();
}
}
class SettingsForm extends StatefulWidget {
@override
SettingsFormState createState() => SettingsFormState();
}
class SettingsFormState extends State<SettingsForm> {
final _telegramImage = Image.asset('assets/images/Telegram.png');
final _twitterImage = Image.asset('assets/images/Twitter.png');
final _changeNowImage = Image.asset('assets/images/change_now.png');
final _xmrBtcImage = Image.asset('assets/images/xmr_btc.png');
final _morphImage = Image.asset('assets/images/morph_icon.png');
final _emailUrl = 'mailto:support@cakewallet.com';
final _telegramUrl = 'https:t.me/cakewallet_bot';
final _twitterUrl = 'https:twitter.com/CakewalletXMR';
final _changeNowUrl = 'mailto:support@changenow.io';
final _xmrToUrl = 'mailto:support@xmr.to';
final _morphUrl = 'mailto:support@morphtoken.com';
final _items = List<SettingsItem>();
void _launchUrl(String url) async {
if (await canLaunch(url)) await launch(url);
}
void _setSettingsList() {
final settingsStore = Provider.of<SettingsStore>(context);
settingsStore.setItemHeaders();
_items.addAll([
SettingsItem(
onTaped: () => _setBalance(context),
title: ItemHeaders.displayBalanceAs,
widget: Observer(
builder: (_) => Text(
settingsStore.balanceDisplayMode.toString(),
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.caption.color),
)),
attribute: Attributes.widget),
SettingsItem(
onTaped: () => _setCurrency(context),
title: ItemHeaders.currency,
widget: Observer(
builder: (_) => Text(
settingsStore.fiatCurrency.toString(),
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.caption.color),
)),
attribute: Attributes.widget),
SettingsItem(
onTaped: () => _setTransactionPriority(context),
title: ItemHeaders.feePriority,
widget: Observer(
builder: (_) => Text(
settingsStore.transactionPriority.toString(),
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.caption.color),
)),
attribute: Attributes.widget),
SettingsItem(
title: ItemHeaders.saveRecipientAddress,
attribute: Attributes.switcher),
SettingsItem(title: '', attribute: Attributes.header),
SettingsItem(
onTaped: () {
Navigator.of(context).pushNamed(Routes.auth,
arguments: (bool isAuthenticatedSuccessfully,
AuthPageState auth) =>
isAuthenticatedSuccessfully
? Navigator.of(context).popAndPushNamed(Routes.setupPin,
arguments:
(BuildContext setupPinContext, String _) =>
Navigator.of(context).pop())
: null);
},
title: ItemHeaders.changePIN,
attribute: Attributes.arrow),
SettingsItem(
onTaped: () => Navigator.pushNamed(context, Routes.changeLanguage),
title: ItemHeaders.changeLanguage,
attribute: Attributes.arrow),
SettingsItem(
title: ItemHeaders.allowBiometricalAuthentication,
attribute: Attributes.switcher),
SettingsItem(title: ItemHeaders.darkMode, attribute: Attributes.switcher),
SettingsItem(
widgetBuilder: (context) {
return PopupMenuButton<ActionListDisplayMode>(
itemBuilder: (context) => [
PopupMenuItem(
value: ActionListDisplayMode.transactions,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(S
.of(context)
.settings_transactions,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color
),
),
Checkbox(
value: settingsStore
.actionlistDisplayMode
.contains(ActionListDisplayMode
.transactions),
onChanged: (value) => settingsStore
.toggleTransactionsDisplay(),
)
]))),
PopupMenuItem(
value: ActionListDisplayMode.trades,
child: Observer(
builder: (_) => Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
children: [
Text(
S.of(context).settings_trades,
style: TextStyle(
color: Theme.of(context).primaryTextTheme.title.color
),
),
Checkbox(
value: settingsStore
.actionlistDisplayMode
.contains(
ActionListDisplayMode.trades),
onChanged: (value) => settingsStore
.toggleTradesDisplay(),
)
])))
],
child: Container(
height: 56,
padding: EdgeInsets.only(left: 24, right: 24),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(S.of(context).settings_display_on_dashboard_list,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryTextTheme.title.color)),
Observer(builder: (_) {
var title = '';
if (settingsStore.actionlistDisplayMode.length ==
ActionListDisplayMode.values.length) {
title = S.of(context).settings_all;
}
if (title.isEmpty &&
settingsStore.actionlistDisplayMode
.contains(ActionListDisplayMode.trades)) {
title = S.of(context).settings_only_trades;
}
if (title.isEmpty &&
settingsStore.actionlistDisplayMode.contains(
ActionListDisplayMode.transactions)) {
title = S.of(context).settings_only_transactions;
}
if (title.isEmpty) {
title = S.of(context).settings_none;
}
return Text(title,
style: TextStyle(
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.caption.color));
})
]),
));
},
attribute: Attributes.rawWidget),
SettingsItem(title: '', attribute: Attributes.header),
SettingsItem(
onTaped: () => _launchUrl(_emailUrl),
title: 'Email',
link: 'support@cakewallet.com',
image: null,
attribute: Attributes.link),
SettingsItem(
onTaped: () => _launchUrl(_telegramUrl),
title: 'Telegram',
link: 'Cake_Wallet',
image: _telegramImage,
attribute: Attributes.link),
SettingsItem(
onTaped: () => _launchUrl(_twitterUrl),
title: 'Twitter',
link: '@CakeWalletXMR',
image: _twitterImage,
attribute: Attributes.link),
SettingsItem(
onTaped: () => _launchUrl(_changeNowUrl),
title: 'ChangeNow',
link: 'support@changenow.io',
image: _changeNowImage,
attribute: Attributes.link),
SettingsItem(
onTaped: () => _launchUrl(_morphUrl),
title: 'Morph',
link: 'support@morphtoken.com',
image: _morphImage,
attribute: Attributes.link),
SettingsItem(
onTaped: () => _launchUrl(_xmrToUrl),
title: 'XMR.to',
link: 'support@xmr.to',
image: _xmrBtcImage,
attribute: Attributes.link),
SettingsItem(
onTaped: () {
Navigator.push(
context,
CupertinoPageRoute<void>(
builder: (BuildContext context) => DisclaimerPage()));
},
title: ItemHeaders.termsAndConditions,
attribute: Attributes.arrow),
SettingsItem(
onTaped: () => Navigator.pushNamed(context, Routes.faq),
title: ItemHeaders.faq,
attribute: Attributes.arrow)
]);
setState(() {});
}
void _afterLayout(dynamic _) => _setSettingsList();
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback(_afterLayout);
}
Widget _getWidget(SettingsItem item) {
switch (item.attribute) {
case Attributes.arrow:
return SettingsArrowListRow(
onTaped: item.onTaped,
title: item.title,
);
case Attributes.header:
return SettingsHeaderListRow(
title: item.title,
);
case Attributes.link:
return SettingsLinktListRow(
onTaped: item.onTaped,
title: item.title,
link: item.link,
image: item.image,
);
case Attributes.switcher:
return SettingsSwitchListRow(
title: item.title,
);
case Attributes.widget:
return SettingsTextListRow(
onTaped: item.onTaped,
title: item.title,
widget: item.widget,
);
case Attributes.rawWidget:
return SettingRawWidgetListRow(widgetBuilder: item.widgetBuilder);
default:
return Offstage();
}
}
@override
Widget build(BuildContext context) {
final settingsStore = Provider.of<SettingsStore>(context);
settingsStore.setItemHeaders();
final shortDivider = Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
);
final longDivider = Container(
height: 1,
color: Theme.of(context).dividerColor,
);
return Container(
padding: EdgeInsets.only(top: 12),
child: SingleChildScrollView(
child: Column(
children: <Widget>[
longDivider,
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: _items.length,
itemBuilder: (context, index) {
final item = _items[index];
Widget divider;
if (item.attribute == Attributes.header || item == _items.last) {
divider = longDivider;
} else if (_items[index + 1].attribute == Attributes.header){
divider = longDivider;
} else {
divider = shortDivider;
}
return Column(
children: <Widget>[
_getWidget(item),
divider
],
);
}),
Padding(
padding: EdgeInsets.only(top: 12),
child: ListTile(
title: Center(
child: Text(
settingsStore.itemHeaders[ItemHeaders.version],
style: TextStyle(
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.caption.color)
),
),
),
)
],
)),
);
}
Future<void> _setBalance(BuildContext context) async {
final settingsStore = Provider.of<SettingsStore>(context);
final items = BalanceDisplayMode.all;
final selectedItem = items.indexOf(settingsStore.balanceDisplayMode);
await showDialog<void>(
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedItem,
title: S.of(context).please_select,
mainAxisAlignment: MainAxisAlignment.center,
onItemSelected: (BalanceDisplayMode mode) async =>
await settingsStore.setCurrentBalanceDisplayMode(
balanceDisplayMode: mode)),
context: context);
}
Future<void> _setCurrency(BuildContext context) async {
final settingsStore = Provider.of<SettingsStore>(context);
final items = FiatCurrency.all;
final selectedItem = items.indexOf(settingsStore.fiatCurrency);
await showDialog<void>(
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedItem,
title: S.of(context).please_select,
mainAxisAlignment: MainAxisAlignment.center,
onItemSelected: (FiatCurrency currency) async =>
await settingsStore.setCurrentFiatCurrency(currency: currency)),
context: context);
}
Future<void> _setTransactionPriority(BuildContext context) async {
final settingsStore = Provider.of<SettingsStore>(context);
final items = TransactionPriority.all;
final selectedItem = items.indexOf(settingsStore.transactionPriority);
await showDialog<void>(
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedItem,
title: S.of(context).please_select,
mainAxisAlignment: MainAxisAlignment.center,
onItemSelected: (TransactionPriority priority) async =>
await settingsStore.setCurrentTransactionPriority(
priority: priority)),
context: context);
return SectionStandardList(
sectionCount: settingsViewModel.sections.length,
itemCounter: (int sectionIndex) {
if (sectionIndex < settingsViewModel.sections.length) {
return settingsViewModel.sections[sectionIndex].length;
}
return 0;
},
itemBuilder: (_, sectionIndex, itemIndex) {
final item = settingsViewModel.sections[sectionIndex][itemIndex];
if (item is PickerListItem) {
return Observer(builder: (_) {
return SettingsPickerCell<dynamic>(
title: item.title,
selectedItem: item.selectedItem(),
items: item.items);
});
}
if (item is SwitcherListItem) {
return Observer(builder: (_) {
return SettingsSwitcherCell(
title: item.title,
value: item.value(),
onValueChange: item.onValueChange);
});
}
if (item is RegularListItem) {
return SettingsCellWithArrow(title: item.title);
}
if (item is LinkListItem) {
return SettingsLinkProviderCell(
title: item.title,
icon: item.icon,
link: item.link,
linkTitle: item.linkTitle);
}
return Container();
});
}
}

View file

@ -1,33 +0,0 @@
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
class SettingsArrowListRow extends StatelessWidget {
SettingsArrowListRow({@required this.onTaped, this.title});
final VoidCallback onTaped;
final String title;
@override
Widget build(BuildContext context) {
final settingsStore = Provider.of<SettingsStore>(context);
final _cakeArrowImage = Image.asset('assets/images/select_arrow.png',
color: Theme.of(context).primaryTextTheme.caption.color);
return Container(
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: ListTile(
contentPadding: EdgeInsets.only(left: 24.0, right: 24.0),
title: Observer(
builder: (_) => Text(
settingsStore.itemHeaders[title],
style: TextStyle(
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.title.color),
)),
trailing: _cakeArrowImage,
onTap: onTaped),
);
}
}

View file

@ -0,0 +1,12 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
class SettingsCellWithArrow extends StandardListRow {
SettingsCellWithArrow({@required String title})
: super(title: title, isSelected: false);
@override
Widget buildTrailing(BuildContext context) =>
Image.asset('assets/images/select_arrow.png',
color: Theme.of(context).primaryTextTheme.caption.color);
}

View file

@ -1,43 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:provider/provider.dart';
class SettingsHeaderListRow extends StatelessWidget {
SettingsHeaderListRow({this.title});
final String title;
@override
Widget build(BuildContext context) {
final settingsStore = Provider.of<SettingsStore>(context);
return Column(
children: <Widget>[
SizedBox(
height: 20.0,
),
Container(
padding: EdgeInsets.only(left: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Observer(
builder: (_) => Text(
title.isNotEmpty
? settingsStore.itemHeaders[title]
: '',
style: TextStyle(
fontSize: 15.0,
color: Theme.of(context).primaryTextTheme.caption.color),
))
],
),
),
SizedBox(
height: 14.0,
),
],
);
}
}

View file

@ -1,42 +0,0 @@
import 'package:flutter/material.dart';
class SettingsLinktListRow extends StatelessWidget {
SettingsLinktListRow(
{@required this.onTaped, this.title, this.link, this.image});
final VoidCallback onTaped;
final String title;
final String link;
final Image image;
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: ListTile(
contentPadding: EdgeInsets.only(left: 24.0, right: 24.0),
title: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
image != null ? image : Offstage(),
Container(
padding: image != null ? EdgeInsets.only(left: 10) : null,
child: Text(
title,
style: TextStyle(
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.title.color),
),
)
],
),
trailing: Text(
link,
style: TextStyle(fontSize: 14.0, color: Colors.blue),
),
onTap: onTaped,
),
);
}
}

View file

@ -0,0 +1,24 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
class SettingsLinkProviderCell extends StandardListRow {
SettingsLinkProviderCell(
{@required String title,
@required this.icon,
@required this.link,
@required this.linkTitle})
: super(title: title, isSelected: false);
final String icon;
final String link;
final String linkTitle;
@override
Widget buildLeading(BuildContext context) =>
icon != null ? Image.asset(icon) : null;
@override
Widget buildTrailing(BuildContext context) => Text(linkTitle,
style: TextStyle(
fontSize: 14.0, fontWeight: FontWeight.w500, color: Colors.blue));
}

View file

@ -0,0 +1,38 @@
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/picker.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/generated/i18n.dart';
class SettingsPickerCell<ItemType> extends StandardListRow {
SettingsPickerCell({@required String title, this.selectedItem, this.items})
: super(
title: title,
isSelected: false,
onTap: (BuildContext context) async {
final selectedAtIndex = items.indexOf(selectedItem);
await showDialog<void>(
context: context,
builder: (_) => Picker(
items: items,
selectedAtIndex: selectedAtIndex,
title: S.current.please_select,
mainAxisAlignment: MainAxisAlignment.center,
onItemSelected: (Object _) {}));
});
final ItemType selectedItem;
final List<ItemType> items;
@override
Widget buildTrailing(BuildContext context) {
return Text(
selectedItem.toString(),
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 14.0,
fontWeight: FontWeight.w500,
color: Theme.of(context).primaryTextTheme.caption.color),
);
}
}

View file

@ -0,0 +1,9 @@
import 'package:flutter/cupertino.dart';
class SettingsPickerRaw extends StatelessWidget {
@override
Widget build(BuildContext context) {
// TODO: implement build
throw UnimplementedError();
}
}

View file

@ -1,15 +0,0 @@
import 'package:flutter/material.dart';
class SettingRawWidgetListRow extends StatelessWidget {
SettingRawWidgetListRow({@required this.widgetBuilder});
final WidgetBuilder widgetBuilder;
@override
Widget build(BuildContext context) {
return Container(
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: widgetBuilder(context) ?? Container(),
);
}
}

View file

@ -1,76 +0,0 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/theme_changer.dart';
import 'package:cake_wallet/themes.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/src/widgets/standart_switch.dart';
class SettingsSwitchListRow extends StatelessWidget {
SettingsSwitchListRow({@required this.title});
final String title;
Widget _getSwitch(BuildContext context) {
final settingsStore = Provider.of<SettingsStore>(context);
final _themeChanger = Provider.of<ThemeChanger>(context);
if (settingsStore.itemHeaders[title] ==
S.of(context).settings_save_recipient_address) {
return Observer(
builder: (_) => StandartSwitch(
value: settingsStore.shouldSaveRecipientAddress,
onTaped: () {
final _currentValue = !settingsStore.shouldSaveRecipientAddress;
settingsStore.setSaveRecipientAddress(
shouldSaveRecipientAddress: _currentValue);
}));
}
if (settingsStore.itemHeaders[title] ==
S.of(context).settings_allow_biometrical_authentication) {
return Observer(
builder: (_) => StandartSwitch(
value: settingsStore.allowBiometricalAuthentication,
onTaped: () {
final _currentValue =
!settingsStore.allowBiometricalAuthentication;
settingsStore.setAllowBiometricalAuthentication(
allowBiometricalAuthentication: _currentValue);
}));
}
if (settingsStore.itemHeaders[title] == S.of(context).settings_dark_mode) {
return Observer(
builder: (_) => StandartSwitch(
value: settingsStore.isDarkTheme,
onTaped: () {
final _currentValue = !settingsStore.isDarkTheme;
settingsStore.saveDarkTheme(isDarkTheme: _currentValue);
_themeChanger.setTheme(
_currentValue ? Themes.darkTheme : Themes.lightTheme);
}));
}
return null;
}
@override
Widget build(BuildContext context) {
final settingsStore = Provider.of<SettingsStore>(context);
return Container(
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: ListTile(
contentPadding: EdgeInsets.only(left: 24.0, right: 24.0),
title: Observer(
builder: (_) => Text(settingsStore.itemHeaders[title],
style: TextStyle(
fontSize: 14,
color: Theme.of(context).primaryTextTheme.title.color)),
),
trailing: _getSwitch(context)),
);
}
}

View file

@ -0,0 +1,16 @@
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/src/widgets/standart_switch.dart';
class SettingsSwitcherCell extends StandardListRow {
SettingsSwitcherCell(
{@required String title, @required this.value, this.onValueChange})
: super(title: title, isSelected: false);
final bool value;
final void Function(bool value) onValueChange;
@override
Widget buildTrailing(BuildContext context) =>
StandartSwitch(value: value, onTaped: () => onValueChange(!value));
}

View file

@ -1,42 +0,0 @@
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
class SettingsTextListRow extends StatelessWidget {
SettingsTextListRow({@required this.onTaped, this.title, this.widget});
final VoidCallback onTaped;
final String title;
final Widget widget;
@override
Widget build(BuildContext context) {
final settingsStore = Provider.of<SettingsStore>(context);
return Container(
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: ListTile(
contentPadding: EdgeInsets.only(left: 24.0, right: 24.0),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child: Observer(
builder: (_) => Text(
settingsStore.itemHeaders[title],
style: TextStyle(
fontSize: 14.0,
color: Theme.of(context).primaryTextTheme.title.color),
)),
),
Flexible(
child: widget
)
],
),
onTap: onTaped,
),
);
}
}

View file

@ -1,80 +0,0 @@
import 'package:provider/provider.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_keys_store.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
class ShowKeysPage extends BasePage {
@override
String get title => S.current.wallet_keys;
@override
Widget body(BuildContext context) {
final walletKeysStore = Provider.of<WalletKeysStore>(context);
return Container(
padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
child: Observer(
builder: (_) {
final keysMap = {
S.of(context).view_key_public: walletKeysStore.publicViewKey,
S.of(context).spend_key_private: walletKeysStore.privateSpendKey
};
if (walletKeysStore.privateViewKey.isNotEmpty) {
keysMap[S.of(context).view_key_private] =
walletKeysStore.privateViewKey;
}
if (walletKeysStore.publicSpendKey.isNotEmpty) {
keysMap[S.of(context).spend_key_public] =
walletKeysStore.publicSpendKey;
}
return ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
),
itemCount: keysMap.length,
itemBuilder: (BuildContext context, int index) {
final key = keysMap.keys.elementAt(index);
final value = keysMap.values.elementAt(index);
final isDrawTop = index == 0 ? true : false;
final isDrawBottom = index == keysMap.length - 1 ? true : false;
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: value));
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
S.of(context).copied_key_to_clipboard(key),
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.green,
duration: Duration(seconds: 1),
));
},
child: StandartListRow(
title: key + ':',
value: value,
isDrawTop: isDrawTop,
isDrawBottom: isDrawBottom,
),
);
});
},
));
}
}

View file

@ -3,8 +3,8 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/address_list/address_edit_or_create_view_model.dart';
import 'package:cake_wallet/core/AddressLabelValidator.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
import 'package:cake_wallet/core/address_label_validator.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
@ -21,7 +21,7 @@ class AddressEditOrCreatePage extends BasePage {
print(addressEditOrCreateViewModel.label);
}
final AddressEditOrCreateViewModel addressEditOrCreateViewModel;
final WalletAddressEditOrCreateViewModel addressEditOrCreateViewModel;
final GlobalKey<FormState> _formKey;
final TextEditingController _labelController;

View file

@ -1,52 +1,21 @@
import 'package:cake_wallet/src/domain/monero/monero_transaction_info.dart';
import 'package:provider/provider.dart';
import 'package:intl/intl.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
import 'package:cake_wallet/src/domain/monero/monero_transaction_info.dart';
import 'package:cake_wallet/src/domain/common/transaction_info.dart';
import 'package:cake_wallet/src/stores/settings/settings_store.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/palette.dart';
class TransactionDetailsPage extends BasePage {
TransactionDetailsPage({this.transactionInfo});
final TransactionInfo transactionInfo;
@override
String get title => S.current.transaction_details_title;
@override
Widget body(BuildContext context) {
final settingsStore = Provider.of<SettingsStore>(context);
return TransactionDetailsForm(
transactionInfo: transactionInfo, settingsStore: settingsStore);
}
}
class TransactionDetailsForm extends StatefulWidget {
TransactionDetailsForm(
{@required this.transactionInfo, @required this.settingsStore});
final TransactionInfo transactionInfo;
final SettingsStore settingsStore;
@override
TransactionDetailsFormState createState() => TransactionDetailsFormState();
}
class TransactionDetailsFormState extends State<TransactionDetailsForm> {
final _items = List<StandartListItem>();
@override
void initState() {
final _dateFormat = widget.settingsStore.getCurrentDateFormat(
formatUSA: "yyyy.MM.dd, HH:mm", formatDefault: "dd.MM.yyyy, HH:mm");
final tx = widget.transactionInfo;
TransactionDetailsPage({this.transactionInfo}) : _items = [] {
// FIXME
// final _dateFormat = widget.settingsStore.getCurrentDateFormat(
// formatUSA: "yyyy.MM.dd, HH:mm", formatDefault: "dd.MM.yyyy, HH:mm");
final dateFormat = DateFormat('dd.MM.yyyy, HH:mm');
final tx = transactionInfo;
if (tx is MoneroTransactionInfo) {
final items = [
@ -54,7 +23,31 @@ class TransactionDetailsFormState extends State<TransactionDetailsForm> {
title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(
title: S.current.transaction_details_date,
value: _dateFormat.format(tx.date)),
value: dateFormat.format(tx.date)),
StandartListItem(
title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted())
];
// FIXME
// if (widget.settingsStore.shouldSaveRecipientAddress &&
// tx.recipientAddress != null) {
// items.add(StandartListItem(
// title: S.current.transaction_details_recipient_address,
// value: tx.recipientAddress));
// }
_items.addAll(items);
}
if (tx is BitcoinTransactionInfo) {
final items = [
StandartListItem(
title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date)),
StandartListItem(
title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(
@ -62,33 +55,31 @@ class TransactionDetailsFormState extends State<TransactionDetailsForm> {
value: tx.amountFormatted())
];
if (widget.settingsStore.shouldSaveRecipientAddress &&
tx.recipientAddress != null) {
items.add(StandartListItem(
title: S.current.transaction_details_recipient_address,
value: tx.recipientAddress));
}
_items.addAll(items);
}
super.initState();
}
@override
Widget build(BuildContext context) {
String get title => S.current.transaction_details_title;
final TransactionInfo transactionInfo;
final List<StandartListItem> _items;
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 20, bottom: 20),
child: ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
),
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).accentTextTheme.title.backgroundColor,
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
),
itemCount: _items.length,
itemBuilder: (context, index) {
final item = _items[index];
@ -108,8 +99,7 @@ class TransactionDetailsFormState extends State<TransactionDetailsForm> {
),
);
},
child:
StandartListRow(
child: StandartListRow(
title: '${item.title}:',
value: item.value,
isDrawTop: isDrawTop,

View file

@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
class WalletKeysPage extends BasePage {
WalletKeysPage(this.walletKeysViewModel);
@override
String get title => S.current.wallet_keys;
final WalletKeysViewModel walletKeysViewModel;
@override
Widget body(BuildContext context) {
return Container(
padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
child: Observer(
builder: (_) {
return ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context)
.accentTextTheme
.title
.backgroundColor,
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
),
itemCount: walletKeysViewModel.items.length,
itemBuilder: (BuildContext context, int index) {
final item = walletKeysViewModel.items[index];
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: item.value));
Scaffold.of(context).showSnackBar(SnackBar(
content: Text(
S.of(context).copied_key_to_clipboard(item.title),
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
backgroundColor: Colors.green,
duration: Duration(seconds: 1),
));
},
child: StandartListRow(
title: item.title + ':',
value: item.value,
),
);
});
},
));
}
}

View file

@ -1,177 +1,188 @@
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:provider/provider.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/domain/common/wallet_type.dart';
import 'package:cake_wallet/view_model/wallet_list/wallet_list_view_model.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/stores/wallet_list/wallet_list_store.dart';
import 'package:cake_wallet/src/stores/wallet/wallet_store.dart';
import 'package:cake_wallet/src/screens/wallet_list/wallet_menu.dart';
import 'package:cake_wallet/src/screens/wallet_list/widgets/wallet_tile.dart';
class WalletListPage extends BasePage {
WalletListPage({this.walletListViewModel});
final WalletListViewModel walletListViewModel;
@override
Widget body(BuildContext context) => WalletListBody();
Widget body(BuildContext context) =>
WalletListBody(walletListViewModel: walletListViewModel);
}
class WalletListBody extends StatefulWidget {
WalletListBody({this.walletListViewModel});
final WalletListViewModel walletListViewModel;
@override
WalletListBodyState createState() => WalletListBodyState();
}
class WalletListBodyState extends State<WalletListBody> {
final moneroIcon = Image.asset('assets/images/monero.png', height: 24, width: 24);
WalletListStore _walletListStore;
ScrollController scrollController = ScrollController();
final moneroIcon =
Image.asset('assets/images/monero.png', height: 24, width: 24);
final bitcoinIcon =
Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final scrollController = ScrollController();
@override
Widget build(BuildContext context) {
final walletStore = Provider.of<WalletStore>(context);
_walletListStore = Provider.of<WalletListStore>(context);
final newWalletImage = Image.asset('assets/images/new_wallet.png',
height: 12,
width: 12,
color: Palette.oceanBlue);
height: 12, width: 12, color: Palette.oceanBlue);
final restoreWalletImage = Image.asset('assets/images/restore_wallet.png',
height: 12,
width: 12,
color: Theme.of(context).primaryTextTheme.title.color);
return SafeArea(
child: Container(
padding: EdgeInsets.only(top: 16),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 20),
content: Container(
child: Observer(
builder: (_) => ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (_, index) => Divider(
color: Theme.of(context).backgroundColor,
height: 16),
itemCount: _walletListStore.wallets.length,
itemBuilder: (__, index) {
final wallet = _walletListStore.wallets[index];
final screenWidth = MediaQuery.of(context).size.width;
child: Container(
padding: EdgeInsets.only(top: 16),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 20),
content: Container(
child: Observer(
builder: (_) => ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
separatorBuilder: (_, index) => Divider(
color: Theme.of(context).backgroundColor, height: 16),
itemCount: widget.walletListViewModel.wallets.length,
itemBuilder: (__, index) {
final wallet = widget.walletListViewModel.wallets[index];
final screenWidth = MediaQuery.of(context).size.width;
// String shortAddress = '';
final isCurrentWallet =
_walletListStore.isCurrentWallet(wallet);
// if (wallet.isCurrent) {
// shortAddress = wallet.address;
// shortAddress = shortAddress.replaceRange(
// 4, shortAddress.length - 4, '...');
// }
String shortAddress = '';
final walletMenu = WalletMenu(context, widget.walletListViewModel);
final items =
walletMenu.generateItemsForWalletMenu(wallet.isCurrent);
final colors = walletMenu
.generateColorsForWalletMenu(wallet.isCurrent);
final images = walletMenu
.generateImagesForWalletMenu(wallet.isCurrent);
if (isCurrentWallet) {
shortAddress = walletStore.subaddress.address;
shortAddress = shortAddress.replaceRange(4, shortAddress.length - 4, '...');
}
return Container(
height: 108,
width: double.infinity,
child: CustomScrollView(
scrollDirection: Axis.horizontal,
controller: scrollController,
slivers: <Widget>[
SliverPersistentHeader(
pinned: false,
floating: true,
delegate: WalletTile(
min: screenWidth - 228,
max: screenWidth,
image: _imageFor(type: wallet.type),
walletName: wallet.name,
walletAddress: '', //shortAddress,
isCurrent: wallet.isCurrent),
),
SliverList(
delegate:
SliverChildBuilderDelegate((context, index) {
final item = items[index];
final color = colors[index];
final image = images[index];
final walletMenu = WalletMenu(context);
final items = walletMenu.generateItemsForWalletMenu(isCurrentWallet);
final colors = walletMenu.generateColorsForWalletMenu(isCurrentWallet);
final images = walletMenu.generateImagesForWalletMenu(isCurrentWallet);
final radius = index == 0 ? 12.0 : 0.0;
return Container(
height: 108,
width: double.infinity,
child: CustomScrollView(
scrollDirection: Axis.horizontal,
controller: scrollController,
slivers: <Widget>[
SliverPersistentHeader(
pinned: false,
floating: true,
delegate: WalletTile(
min: screenWidth - 228,
max: screenWidth,
image: moneroIcon,
walletName: wallet.name,
walletAddress: shortAddress,
isCurrent: isCurrentWallet
return GestureDetector(
onTap: () {
scrollController.animateTo(0.0,
duration: Duration(milliseconds: 500),
curve: Curves.fastOutSlowIn);
walletMenu.action(
walletMenu.listItems.indexOf(item),
wallet,
wallet.isCurrent);
},
child: Container(
height: 108,
width: 108,
color: Theme.of(context).backgroundColor,
child: Container(
padding: EdgeInsets.only(left: 5, right: 5),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(radius),
bottomLeft: Radius.circular(radius)),
color: color),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
image,
SizedBox(height: 5),
Text(
item,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: Colors.white),
)
],
),
),
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate(
(context, index) {
final item = items[index];
final color = colors[index];
final image = images[index];
final radius = index == 0 ? 12.0 : 0.0;
return GestureDetector(
onTap: () {
scrollController.animateTo(0.0, duration: Duration(milliseconds: 500), curve: Curves.fastOutSlowIn);
walletMenu.action(
walletMenu.listItems.indexOf(item), wallet, isCurrentWallet);
},
child: Container(
height: 108,
width: 108,
color: Theme.of(context).backgroundColor,
child: Container(
padding: EdgeInsets.only(left: 5, right: 5),
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(radius),
bottomLeft: Radius.circular(radius)
),
color: color
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
image,
SizedBox(height: 5),
Text(
item,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
color: Colors.white
),
)
],
),
),
),
),
);
},
childCount: items.length
)
)
],
),
);
}),
),
);
}, childCount: items.length))
],
),
);
}),
),
bottomSection: Column(children: <Widget>[
PrimaryImageButton(
onPressed: () => Navigator.of(context).pushNamed(Routes.newWalletType),
image: newWalletImage,
text: S.of(context).wallet_list_create_new_wallet,
color: Colors.white,
textColor: Palette.oceanBlue,
borderColor: Palette.oceanBlue,
),
SizedBox(height: 10.0),
PrimaryImageButton(
onPressed: () =>
Navigator.of(context).pushNamed(Routes.restoreWalletOptions),
),
bottomSection: Column(children: <Widget>[
PrimaryImageButton(
onPressed: () =>
Navigator.of(context).pushNamed(Routes.newWalletType),
image: newWalletImage,
text: S.of(context).wallet_list_create_new_wallet,
color: Colors.white,
textColor: Palette.oceanBlue,
borderColor: Palette.oceanBlue,
),
SizedBox(height: 10.0),
PrimaryImageButton(
onPressed: () => Navigator.of(context)
.pushNamed(Routes.restoreWalletType),
image: restoreWalletImage,
text: S.of(context).wallet_list_restore_wallet,
color: Theme.of(context).primaryTextTheme.overline.color,
textColor: Theme.of(context).primaryTextTheme.title.color)
])),
)
);
])),
));
}
Image _imageFor({WalletType type}) {
switch (type) {
case WalletType.bitcoin:
return bitcoinIcon;
case WalletType.monero:
return moneroIcon;
default:
return null;
}
}
}

Some files were not shown because too many files have changed in this diff Show more