Merged 4.1.0

This commit is contained in:
M 2021-01-13 19:18:28 +02:00
commit 9a79fcdc23
149 changed files with 6461 additions and 2150 deletions

View file

@ -1,2 +1,2 @@
-
uri: electrumx.cakewallet.com:50002
uri: electrum.cakewallet.com:50002

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 408 B

After

Width:  |  Height:  |  Size: 629 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 440 B

View file

@ -294,14 +294,26 @@ extern "C"
return true;
}
void load_wallet(char *path, char *password, int32_t nettype)
bool load_wallet(char *path, char *password, int32_t nettype)
{
nice(19);
Monero::NetworkType networkType = static_cast<Monero::NetworkType>(nettype);
Monero::Wallet *wallet = Monero::WalletManagerFactory::getWalletManager()->openWallet(std::string(path), std::string(password), networkType);
Monero::WalletManager *walletManager = Monero::WalletManagerFactory::getWalletManager();
Monero::Wallet *wallet = walletManager->openWallet(std::string(path), std::string(password), networkType);
int status;
std::string errorString;
wallet->statusWithErrorString(status, errorString);
change_current_wallet(wallet);
return !(status != Monero::Wallet::Status_Ok || !errorString.empty());
}
char *error_string() {
return strdup(get_current_wallet()->errorString().c_str());
}
bool is_wallet_exist(char *path)
{
return Monero::WalletManagerFactory::getWalletManager()->walletExists(std::string(path));

View file

@ -0,0 +1,8 @@
class WalletOpeningException implements Exception {
WalletOpeningException({this.message});
final String message;
@override
String toString() => message;
}

View file

@ -14,7 +14,9 @@ typedef restore_wallet_from_keys = Int8 Function(Pointer<Utf8>, Pointer<Utf8>,
typedef is_wallet_exist = Int8 Function(Pointer<Utf8>);
typedef load_wallet = Void Function(Pointer<Utf8>, Pointer<Utf8>, Int8);
typedef load_wallet = Int8 Function(Pointer<Utf8>, Pointer<Utf8>, Int8);
typedef error_string = Pointer<Utf8> Function();
typedef get_filename = Pointer<Utf8> Function();

View file

@ -14,7 +14,9 @@ typedef RestoreWalletFromKeys = int Function(Pointer<Utf8>, Pointer<Utf8>,
typedef IsWalletExist = int Function(Pointer<Utf8>);
typedef LoadWallet = void Function(Pointer<Utf8>, Pointer<Utf8>, int);
typedef LoadWallet = int Function(Pointer<Utf8>, Pointer<Utf8>, int);
typedef ErrorString = Pointer<Utf8> Function();
typedef GetFilename = Pointer<Utf8> Function();

View file

@ -1,4 +1,5 @@
import 'dart:ffi';
import 'package:cw_monero/exceptions/wallet_opening_exception.dart';
import 'package:cw_monero/wallet.dart';
import 'package:ffi/ffi.dart';
import 'package:flutter/foundation.dart';
@ -32,6 +33,10 @@ final loadWalletNative = moneroApi
.lookup<NativeFunction<load_wallet>>('load_wallet')
.asFunction<LoadWallet>();
final errorStringNative = moneroApi
.lookup<NativeFunction<error_string>>('error_string')
.asFunction<ErrorString>();
void createWalletSync(
{String path, String password, String language, int nettype = 0}) {
final pathPointer = Utf8.toUtf8(path);
@ -136,10 +141,14 @@ void restoreWalletFromKeysSync(
void loadWallet({String path, String password, int nettype = 0}) {
final pathPointer = Utf8.toUtf8(path);
final passwordPointer = Utf8.toUtf8(password);
loadWalletNative(pathPointer, passwordPointer, nettype);
final loaded = loadWalletNative(pathPointer, passwordPointer, nettype) != 0;
free(pathPointer);
free(passwordPointer);
if (!loaded) {
throw WalletOpeningException(
message: convertUTF8ToString(pointer: errorStringNative()));
}
}
void _createWallet(Map<String, dynamic> args) {

View file

@ -354,7 +354,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 17;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -371,7 +371,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.0.9;
MARKETING_VERSION = 4.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -494,7 +494,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 17;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -511,7 +511,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.0.9;
MARKETING_VERSION = 4.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
@ -528,7 +528,7 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
CURRENT_PROJECT_VERSION = 17;
DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
@ -545,7 +545,7 @@
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.0.9;
MARKETING_VERSION = 4.1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.fotolockr.cakewallet;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";

View file

@ -0,0 +1,25 @@
import 'dart:typed_data';
import 'package:bs58check/bs58check.dart' as bs58check;
import 'package:bitcoin_flutter/src/utils/constants/op.dart';
import 'package:bitcoin_flutter/src/utils/script.dart' as bscript;
import 'package:bitcoin_flutter/src/address.dart';
Uint8List p2shAddressToOutputScript(String address) {
final decodeBase58 = bs58check.decode(address);
final hash = decodeBase58.sublist(1);
return bscript.compile(<dynamic>[OPS['OP_HASH160'], hash, OPS['OP_EQUAL']]);
}
Uint8List addressToOutputScript(String address) {
try {
// FIXME: improve validation for p2sh addresses
if (address.startsWith('3')) {
return p2shAddressToOutputScript(address);
}
return Address.addressToOutputScript(address);
} catch (_) {
return Uint8List(0);
}
}

View file

@ -1,19 +1,25 @@
import 'dart:convert';
import 'package:quiver/core.dart';
class BitcoinAddressRecord {
BitcoinAddressRecord(this.address, {this.label, this.index});
BitcoinAddressRecord(this.address, {this.index});
factory BitcoinAddressRecord.fromJSON(String jsonSource) {
final decoded = json.decode(jsonSource) as Map;
return BitcoinAddressRecord(decoded['address'] as String,
label: decoded['label'] as String, index: decoded['index'] as int);
index: decoded['index'] as int);
}
@override
bool operator ==(Object o) =>
o is BitcoinAddressRecord && address == o.address;
final String address;
int index;
String label;
String toJSON() =>
json.encode({'label': label, 'address': address, 'index': index});
@override
int get hashCode => address.hashCode;
String toJSON() => json.encode({'address': address, 'index': index});
}

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:intl/intl.dart';
import 'package:cake_wallet/entities/crypto_amount_format.dart';
@ -7,10 +9,32 @@ final bitcoinAmountFormat = NumberFormat()
..maximumFractionDigits = bitcoinAmountLength
..minimumFractionDigits = 1;
String bitcoinAmountToString({int amount}) =>
bitcoinAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider));
String bitcoinAmountToString({int amount}) => bitcoinAmountFormat.format(
cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider));
double bitcoinAmountToDouble({int amount}) => cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
double bitcoinAmountToDouble({int amount}) =>
cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider);
int doubleToBitcoinAmount(double amount) =>
(amount * bitcoinAmountDivider).toInt();
int stringDoubleToBitcoinAmount(String amount) {
final splitted = amount.split('');
final dotIndex = amount.indexOf('.');
int result = 0;
for (var i = 0; i < splitted.length; i++) {
try {
if (dotIndex == i) {
continue;
}
final char = splitted[i];
final multiplier = dotIndex < i
? bitcoinAmountDivider ~/ pow(10, (i - dotIndex))
: (bitcoinAmountDivider * pow(10, (dotIndex - i -1))).toInt();
final num = int.parse(char) * multiplier;
result += num;
} catch (_) {}
}
return result;
}

View file

@ -5,7 +5,8 @@ import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/entities/balance.dart';
class BitcoinBalance extends Balance {
const BitcoinBalance({@required this.confirmed, @required this.unconfirmed}) : super();
const BitcoinBalance({@required this.confirmed, @required this.unconfirmed})
: super(confirmed, unconfirmed);
factory BitcoinBalance.fromJSON(String jsonSource) {
if (jsonSource == null) {
@ -22,13 +23,12 @@ class BitcoinBalance extends Balance {
final int confirmed;
final int unconfirmed;
int get total => confirmed + unconfirmed;
@override
String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed);
String get confirmedFormatted => bitcoinAmountToString(amount: confirmed);
String get unconfirmedFormatted => bitcoinAmountToString(amount: unconfirmed);
String get totalFormatted => bitcoinAmountToString(amount: total);
@override
String get formattedAdditionalBalance =>
bitcoinAmountToString(amount: unconfirmed);
String toJSON() =>
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed});

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,5 @@
class BitcoinMnemonicIsIncorrectException implements Exception {
@override
String toString() =>
'Bitcoin mnemonic has incorrect format. Mnemonic should contain 12 words separated by space.';
}

View file

@ -4,6 +4,6 @@ class BitcoinTransactionCredentials {
BitcoinTransactionCredentials(this.address, this.amount, this.priority);
final String address;
final double amount;
final String amount;
TransactionPriority priority;
}

View file

@ -65,7 +65,7 @@ abstract class BitcoinTransactionHistoryBase
return historiesWithDetails.fold<Map<String, BitcoinTransactionInfo>>(
<String, BitcoinTransactionInfo>{}, (acc, tx) {
acc[tx.id] = tx;
acc[tx.id] = acc[tx.id]?.updated(tx) ?? tx;
return acc;
});
}
@ -103,10 +103,6 @@ abstract class BitcoinTransactionHistoryBase
Future<void> save() async {
final data = json.encode({'height': _height, 'transactions': transactions});
print('data');
print(data);
await writeData(path: path, password: _password, data: data);
}
@ -168,7 +164,9 @@ abstract class BitcoinTransactionHistoryBase
});
_height = content['height'] as int;
} catch (_) {}
} catch (e) {
print(e);
}
}
void _updateOrInsert(BitcoinTransactionInfo transaction) {

View file

@ -12,6 +12,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
{@required String id,
@required int height,
@required int amount,
@required int fee,
@required TransactionDirection direction,
@required bool isPending,
@required DateTime date,
@ -19,6 +20,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
this.id = id;
this.height = height;
this.amount = amount;
this.fee = fee;
this.direction = direction;
this.date = date;
this.isPending = isPending;
@ -36,37 +38,42 @@ class BitcoinTransactionInfo extends TransactionInfo {
: DateTime.now();
final confirmations = obj['confirmations'] as int ?? 0;
var direction = TransactionDirection.incoming;
var inputsAmount = 0;
var amount = 0;
var totalOutAmount = 0;
for (dynamic vin in vins) {
final vout = vin['vout'] as int;
final out = vin['tx']['vout'][vout] as Map;
final outAddresses =
(out['scriptPubKey']['addresses'] as List<Object>)?.toSet();
inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString());
if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) {
direction = TransactionDirection.outgoing;
break;
}
}
final amount = vout.fold(0, (int acc, dynamic out) {
for (dynamic out in vout) {
final outAddresses =
out['scriptPubKey']['addresses'] as List<Object> ?? [];
final ntrs = outAddresses.toSet().intersection(addressesSet);
var amount = acc;
final value = stringDoubleToBitcoinAmount((out['value'] as double ?? 0.0).toString());
totalOutAmount += value;
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||
(direction == TransactionDirection.outgoing && ntrs.isEmpty)) {
amount += doubleToBitcoinAmount(out['value'] as double ?? 0.0);
amount += value;
}
}
return amount;
});
final fee = inputsAmount - totalOutAmount;
return BitcoinTransactionInfo(
id: id,
height: height,
isPending: false,
fee: fee,
direction: direction,
amount: amount,
date: date,
@ -101,6 +108,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
id: tx.getId(),
height: height,
isPending: false,
fee: null,
direction: TransactionDirection.incoming,
amount: amount,
date: date,
@ -112,6 +120,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
id: data['id'] as String,
height: data['height'] as int,
amount: data['amount'] as int,
fee: data['fee'] as int,
direction: parseTransactionDirectionFromInt(data['direction'] as int),
date: DateTime.fromMillisecondsSinceEpoch(data['date'] as int),
isPending: data['isPending'] as bool,
@ -124,12 +133,29 @@ class BitcoinTransactionInfo extends TransactionInfo {
String amountFormatted() =>
'${formatAmount(bitcoinAmountToString(amount: amount))} BTC';
@override
String feeFormatted() => fee != null
? '${formatAmount(bitcoinAmountToString(amount: fee))} BTC'
: '';
@override
String fiatAmount() => _fiatAmount ?? '';
@override
void changeFiatAmount(String amount) => _fiatAmount = formatAmount(amount);
BitcoinTransactionInfo updated(BitcoinTransactionInfo info) {
return BitcoinTransactionInfo(
id: id,
height: info.height,
amount: info.amount,
fee: info.fee,
direction: direction ?? info.direction,
date: date ?? info.date,
isPending: isPending ?? info.isPending,
confirmations: info.confirmations);
}
Map<String, dynamic> toJson() {
final m = <String, dynamic>{};
m['id'] = id;
@ -139,6 +165,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
m['date'] = date.millisecondsSinceEpoch;
m['isPending'] = isPending;
m['confirmations'] = confirmations;
m['fee'] = fee;
return m;
}
}

View file

@ -1,7 +1,8 @@
import 'dart:async';
import 'dart:convert';
import 'package:cake_wallet/bitcoin/address_to_output_script.dart';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
import 'package:mobx/mobx.dart';
import 'package:bip39/bip39.dart' as bip39;
import 'package:flutter/foundation.dart';
import 'package:rxdart/rxdart.dart';
import 'package:bitcoin_flutter/bitcoin_flutter.dart' as bitcoin;
@ -42,10 +43,11 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
BitcoinBalance initialBalance})
: balance =
initialBalance ?? BitcoinBalance(confirmed: 0, unconfirmed: 0),
hd = bitcoin.HDWallet.fromSeed(bip39.mnemonicToSeed(mnemonic),
network: bitcoin.bitcoin),
hd = bitcoin.HDWallet.fromSeed(mnemonicToSeedBytes(mnemonic),
network: bitcoin.bitcoin)
.derivePath("m/0'/0"),
addresses = initialAddresses != null
? ObservableList<BitcoinAddressRecord>.of(initialAddresses)
? ObservableList<BitcoinAddressRecord>.of(initialAddresses.toSet())
: ObservableList<BitcoinAddressRecord>(),
syncStatus = NotConnectedSyncStatus(),
_password = password,
@ -58,6 +60,7 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
{@required String password,
@required String name,
@required String dirPath,
@required WalletInfo walletInfo,
String jsonSource}) {
final data = json.decode(jsonSource) as Map;
final mnemonic = data['mnemonic'] as String;
@ -83,7 +86,8 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
name: name,
accountIndex: accountIndex,
initialAddresses: addresses,
initialBalance: balance);
initialBalance: balance,
walletInfo: walletInfo);
}
static BitcoinWallet build(
@ -91,6 +95,7 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
@required String password,
@required String name,
@required String dirPath,
@required WalletInfo walletInfo,
List<BitcoinAddressRecord> initialAddresses,
BitcoinBalance initialBalance,
int accountIndex = 0}) {
@ -107,7 +112,21 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
accountIndex: accountIndex,
initialAddresses: initialAddresses,
initialBalance: initialBalance,
transactionHistory: history);
transactionHistory: history,
walletInfo: walletInfo);
}
static int feeAmountForPriority(TransactionPriority priority) {
switch (priority) {
case TransactionPriority.slow:
return 6000;
case TransactionPriority.regular:
return 22080;
case TransactionPriority.fast:
return 24000;
default:
return 0;
}
}
@override
@ -148,21 +167,31 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
Future<void> init() async {
if (addresses.isEmpty) {
final index = 0;
addresses
.add(BitcoinAddressRecord(_getAddress(index: index), index: index));
if (addresses.isEmpty || addresses.length < 33) {
final addressesCount = 33 - addresses.length;
await generateNewAddresses(addressesCount, startIndex: addresses.length);
}
address = addresses.first.address;
address = addresses[_accountIndex].address;
transactionHistory.wallet = this;
await transactionHistory.init();
}
Future<BitcoinAddressRecord> generateNewAddress({String label}) async {
@action
void nextAddress() {
_accountIndex += 1;
if (_accountIndex >= addresses.length) {
_accountIndex = 0;
}
address = addresses[_accountIndex].address;
}
Future<BitcoinAddressRecord> generateNewAddress() async {
_accountIndex += 1;
final address = BitcoinAddressRecord(_getAddress(index: _accountIndex),
index: _accountIndex, label: label);
index: _accountIndex);
addresses.add(address);
await save();
@ -170,10 +199,24 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
return address;
}
Future<void> updateAddress(String address, {String label}) async {
Future<List<BitcoinAddressRecord>> generateNewAddresses(int count,
{int startIndex = 0}) async {
final list = <BitcoinAddressRecord>[];
for (var i = startIndex; i < count + startIndex; i++) {
final address = BitcoinAddressRecord(_getAddress(index: i), index: i);
list.add(address);
}
addresses.addAll(list);
await save();
return list;
}
Future<void> updateAddress(String address) async {
for (final addr in addresses) {
if (addr.address == address) {
addr.label = label;
await save();
break;
}
@ -185,8 +228,10 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
Future<void> startSync() async {
try {
syncStatus = StartingSyncStatus();
transactionHistory.updateAsync(
onFinished: () => print('transactionHistory update finished!'));
transactionHistory.updateAsync(onFinished: () {
print('transactionHistory update finished!');
transactionHistory.save();
});
_subscribeForUpdates();
await _updateBalance();
syncStatus = SyncedSyncStatus();
@ -219,16 +264,20 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
Object credentials) async {
final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[];
final fee = _feeMultiplier(transactionCredentials.priority);
final fee = feeAmountForPriority(transactionCredentials.priority);
final amount = transactionCredentials.amount != null
? doubleToBitcoinAmount(transactionCredentials.amount)
: balance.total - fee;
? stringDoubleToBitcoinAmount(transactionCredentials.amount)
: balance.confirmed - fee;
final totalAmount = amount + fee;
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
var leftAmount = totalAmount;
final changeAddress = address;
var leftAmount = totalAmount;
var totalInputAmount = 0;
if (totalAmount > balance.confirmed) {
throw BitcoinTransactionWrongBalanceException();
}
final unspent = addresses.map((address) => eclient
.getListUnspentWithAddress(address.address)
.then((unspent) => unspent
@ -238,9 +287,8 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
final utxs = await unptsFutures;
for (final utx in utxs) {
final inAmount = utx.value > totalAmount ? totalAmount : utx.value;
leftAmount = leftAmount - inAmount;
totalInputAmount += inAmount;
leftAmount = leftAmount - utx.value;
totalInputAmount += utx.value;
inputs.add(utx);
if (leftAmount <= 0) {
@ -279,7 +327,8 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
}
});
txb.addOutput(transactionCredentials.address, amount);
txb.addOutput(
addressToOutputScript(transactionCredentials.address), amount);
if (changeValue > 0) {
txb.addOutput(changeAddress, changeValue);
@ -295,7 +344,10 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
return PendingBitcoinTransaction(txb.build(),
eclient: eclient, amount: amount, fee: fee)
..addListener((transaction) => transactionHistory.addOne(transaction));
..addListener((transaction) async {
transactionHistory.addOne(transaction);
await _updateBalance();
});
}
String toJSON() => json.encode({
@ -307,11 +359,13 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
@override
double calculateEstimatedFee(TransactionPriority priority) =>
bitcoinAmountToDouble(amount: _feeMultiplier(priority));
bitcoinAmountToDouble(amount: feeAmountForPriority(priority));
@override
Future<void> save() async =>
await write(path: path, password: _password, data: toJSON());
Future<void> save() async {
await write(path: path, password: _password, data: toJSON());
await transactionHistory.save();
}
bitcoin.ECPair keyPairFor({@required int index}) =>
generateKeyPair(hd: hd, index: index);
@ -321,13 +375,18 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
// FIXME: Unimplemented
}
@override
void close() async {
await eclient.close();
}
void _subscribeForUpdates() {
scriptHashes.forEach((sh) async {
await _scripthashesUpdateSubject[sh]?.close();
_scripthashesUpdateSubject[sh] = eclient.scripthashUpdate(sh);
_scripthashesUpdateSubject[sh].listen((event) async {
transactionHistory.updateAsync();
await _updateBalance();
transactionHistory.updateAsync();
});
});
}
@ -352,17 +411,4 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
String _getAddress({@required int index}) =>
generateAddress(hd: hd, index: index);
int _feeMultiplier(TransactionPriority priority) {
switch (priority) {
case TransactionPriority.slow:
return 6000;
case TransactionPriority.regular:
return 9000;
case TransactionPriority.fast:
return 15000;
default:
return 0;
}
}
}

View file

@ -1,21 +1,23 @@
import 'package:cake_wallet/core/wallet_credentials.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
class BitcoinNewWalletCredentials extends WalletCredentials {
BitcoinNewWalletCredentials({String name}) : super(name: name);
BitcoinNewWalletCredentials({String name, WalletInfo walletInfo})
: super(name: name, walletInfo: walletInfo);
}
class BitcoinRestoreWalletFromSeedCredentials extends WalletCredentials {
BitcoinRestoreWalletFromSeedCredentials(
{String name, String password, this.mnemonic})
: super(name: name, password: password);
{String name, String password, this.mnemonic, WalletInfo walletInfo})
: super(name: name, password: password, walletInfo: walletInfo);
final String mnemonic;
}
class BitcoinRestoreWalletFromWIFCredentials extends WalletCredentials {
BitcoinRestoreWalletFromWIFCredentials(
{String name, String password, this.wif})
: super(name: name, password: password);
{String name, String password, this.wif, WalletInfo walletInfo})
: super(name: name, password: password, walletInfo: walletInfo);
final String wif;
}

View file

@ -1,25 +1,34 @@
import 'dart:io';
import 'package:bip39/bip39.dart' as bip39;
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart';
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic_is_incorrect_exception.dart';
import 'package:cake_wallet/bitcoin/file.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet_creation_credentials.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/core/wallet_service.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/entities/pathForWallet.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:hive/hive.dart';
class BitcoinWalletService extends WalletService<
BitcoinNewWalletCredentials,
BitcoinRestoreWalletFromSeedCredentials,
BitcoinRestoreWalletFromWIFCredentials> {
BitcoinWalletService(this.walletInfoSource);
final Box<WalletInfo> walletInfoSource;
@override
Future<BitcoinWallet> create(BitcoinNewWalletCredentials credentials) async {
final dirPath = await pathForWalletDir(
type: WalletType.bitcoin, name: credentials.name);
final wallet = BitcoinWalletBase.build(
dirPath: dirPath,
mnemonic: bip39.generateMnemonic(),
mnemonic: generateMnemonic(),
password: credentials.password,
name: credentials.name);
name: credentials.name,
walletInfo: credentials.walletInfo);
await wallet.save();
await wallet.init();
@ -37,11 +46,15 @@ class BitcoinWalletService extends WalletService<
await pathForWalletDir(name: name, type: WalletType.bitcoin);
final walletPath = '$walletDirPath/$name';
final walletJSONRaw = await read(path: walletPath, password: password);
final walletInfo = walletInfoSource.values.firstWhere(
(info) => info.id == WalletBase.idFor(name, WalletType.bitcoin),
orElse: () => null);
final wallet = BitcoinWalletBase.fromJSON(
password: password,
name: name,
dirPath: walletDirPath,
jsonSource: walletJSONRaw);
jsonSource: walletJSONRaw,
walletInfo: walletInfo);
await wallet.init();
return wallet;
@ -62,13 +75,18 @@ class BitcoinWalletService extends WalletService<
@override
Future<BitcoinWallet> restoreFromSeed(
BitcoinRestoreWalletFromSeedCredentials credentials) async {
if (!validateMnemonic(credentials.mnemonic)) {
throw BitcoinMnemonicIsIncorrectException();
}
final dirPath = await pathForWalletDir(
type: WalletType.bitcoin, name: credentials.name);
final wallet = BitcoinWalletBase.build(
dirPath: dirPath,
name: credentials.name,
password: credentials.password,
mnemonic: credentials.mnemonic);
mnemonic: credentials.mnemonic,
walletInfo: credentials.walletInfo);
await wallet.save();
await wallet.init();

View file

@ -37,7 +37,8 @@ class ElectrumClient {
ElectrumClient()
: _id = 0,
_isConnected = false,
_tasks = {};
_tasks = {},
unterminatedString = '';
static const connectionTimeout = Duration(seconds: 5);
static const aliveTimerDuration = Duration(seconds: 2);
@ -49,6 +50,7 @@ class ElectrumClient {
final Map<String, SocketTask> _tasks;
bool _isConnected;
Timer _aliveTimer;
String unterminatedString;
Future<void> connectToUri(String uri) async {
final splittedUri = uri.split(':');
@ -73,26 +75,50 @@ class ElectrumClient {
socket.listen((Uint8List event) {
try {
final jsoned =
final response =
json.decode(utf8.decode(event.toList())) as Map<String, Object>;
print(jsoned);
final method = jsoned['method'];
final id = jsoned['id'] as String;
final params = jsoned['result'];
_handleResponse(response);
} on FormatException catch (e) {
final msg = e.message.toLowerCase();
if (method is String) {
_methodHandler(method: method, request: jsoned);
if (e.source is String) {
unterminatedString += e.source as String;
}
if (msg.contains("not a subtype of type")) {
unterminatedString += e.source as String;
return;
}
_finish(id, params);
if (isJSONStringCorrect(unterminatedString)) {
final response =
json.decode(unterminatedString) as Map<String, Object>;
_handleResponse(response);
unterminatedString = '';
}
} on TypeError catch (e) {
if (!e.toString().contains('Map<String, Object>')) {
return;
}
final source = utf8.decode(event.toList());
unterminatedString += source;
if (isJSONStringCorrect(unterminatedString)) {
final response =
json.decode(unterminatedString) as Map<String, Object>;
_handleResponse(response);
unterminatedString = null;
}
} catch (e) {
print(e);
print(e.toString());
}
}, onError: (Object error) {
print(error.toString());
_setIsConnected(false);
}, onDone: () => _setIsConnected(false));
}, onDone: () {
_setIsConnected(false);
});
keepAlive();
}
@ -103,7 +129,7 @@ class ElectrumClient {
Future<void> ping() async {
try {
// await callWithTimeout(method: 'server.ping');
await callWithTimeout(method: 'server.ping');
_setIsConnected(true);
} on RequestFailedTimeoutException catch (_) {
_setIsConnected(false);
@ -209,16 +235,20 @@ class ElectrumClient {
Future<Map<String, Object>> getTransactionExpanded(
{@required String hash}) async {
final originalTx = await getTransactionRaw(hash: hash);
final vins = originalTx['vin'] as List<Object>;
try {
final originalTx = await getTransactionRaw(hash: hash);
final vins = originalTx['vin'] as List<Object>;
for (dynamic vin in vins) {
if (vin is Map<String, Object>) {
vin['tx'] = await getTransactionRaw(hash: vin['txid'] as String);
for (dynamic vin in vins) {
if (vin is Map<String, Object>) {
vin['tx'] = await getTransactionRaw(hash: vin['txid'] as String);
}
}
}
return originalTx;
return originalTx;
} catch (_) {
return {};
}
}
Future<String> broadcastTransaction(
@ -228,7 +258,7 @@ class ElectrumClient {
if (result is String) {
return result;
}
print(result);
return '';
});
@ -256,11 +286,13 @@ class ElectrumClient {
return 0;
});
BehaviorSubject<Object> scripthashUpdate(String scripthash) =>
subscribe<Object>(
id: 'blockchain.scripthash.subscribe:$scripthash',
method: 'blockchain.scripthash.subscribe',
params: [scripthash]);
BehaviorSubject<Object> scripthashUpdate(String scripthash) {
_id += 1;
return subscribe<Object>(
id: 'blockchain.scripthash.subscribe:$scripthash',
method: 'blockchain.scripthash.subscribe',
params: [scripthash]);
}
BehaviorSubject<T> subscribe<T>(
{@required String id,
@ -273,7 +305,8 @@ class ElectrumClient {
return subscription;
}
Future<dynamic> call({String method, List<Object> params = const []}) {
Future<dynamic> call({String method, List<Object> params = const []}) async {
await Future<void>.delayed(Duration(milliseconds: 100));
final completer = Completer<dynamic>();
_id += 1;
final id = _id;
@ -307,6 +340,12 @@ class ElectrumClient {
socket.write(jsonrpc(method: method, id: _id, params: params));
}
Future<void> close() async {
_aliveTimer.cancel();
await socket.close();
onConnectionStatusChange = null;
}
void _regisryTask(int id, Completer completer) => _tasks[id.toString()] =
SocketTask(completer: completer, isSubscription: false);
@ -351,6 +390,29 @@ class ElectrumClient {
_isConnected = isConnected;
}
void _handleResponse(Map<String, Object> response) {
final method = response['method'];
final id = response['id'] as String;
final result = response['result'];
if (method is String) {
_methodHandler(method: method, request: response);
return;
}
_finish(id, result);
}
}
// FIXME: move me
bool isJSONStringCorrect(String source) {
try {
json.decode(source);
return true;
} catch (_) {
return false;
}
}
class RequestFailedTimeoutException implements Exception {

View file

@ -16,6 +16,7 @@ class PendingBitcoinTransaction with PendingTransaction {
final int amount;
final int fee;
@override
String get id => _tx.getId();
@override

View file

@ -205,8 +205,8 @@ class BackupService {
_sharedPreferences.getString(PreferencesKey.currentFiatCurrencyKey),
PreferencesKey.shouldSaveRecipientAddressKey: _sharedPreferences
.getBool(PreferencesKey.shouldSaveRecipientAddressKey),
PreferencesKey.currentDarkTheme:
_sharedPreferences.getBool(PreferencesKey.currentDarkTheme),
PreferencesKey.isDarkThemeLegacy:
_sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy),
PreferencesKey.currentPinLength:
_sharedPreferences.getInt(PreferencesKey.currentPinLength),
PreferencesKey.currentTransactionPriorityKey: _sharedPreferences
@ -219,7 +219,7 @@ class BackupService {
_sharedPreferences.getString(PreferencesKey.currentLanguageCode),
PreferencesKey.displayActionListModeKey:
_sharedPreferences.getInt(PreferencesKey.displayActionListModeKey),
'currentTheme': _sharedPreferences.getInt('current_theme')
PreferencesKey.currentTheme: _sharedPreferences.getInt(PreferencesKey.currentTheme)
// FIX-ME: Unnamed constant.
};

View file

@ -1,4 +1,4 @@
import 'package:bip39/src/wordlists/english.dart' as bitcoin_english;
import 'package:cake_wallet/bitcoin/bitcoin_mnemonic.dart' as bitcoin_electrum;
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/entities/mnemonic_item.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
@ -64,7 +64,7 @@ class SeedValidator extends Validator<MnemonicItem> {
static List<String> getBitcoinWordList(String language) {
assert(language.toLowerCase() == LanguageList.english.toLowerCase());
return bitcoin_english.WORDLIST;
return bitcoin_electrum.englishWordlist;
}
@override

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/balance.dart';
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/core/pending_transaction.dart';
@ -9,7 +10,7 @@ import 'package:cake_wallet/entities/sync_status.dart';
import 'package:cake_wallet/entities/node.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
abstract class WalletBase<BalaceType> {
abstract class WalletBase<BalaceType extends Balance> {
WalletBase(this.walletInfo);
static String idFor(String name, WalletType type) =>
@ -52,4 +53,6 @@ abstract class WalletBase<BalaceType> {
Future<void> save();
Future<void> rescan({int height});
void close();
}

View file

@ -1,7 +1,7 @@
import 'package:cake_wallet/entities/wallet_info.dart';
abstract class WalletCredentials {
WalletCredentials({this.name, this.password, this.height});
WalletCredentials({this.name, this.password, this.height, this.walletInfo});
final String name;
final int height;

View file

@ -19,6 +19,7 @@ import 'package:cake_wallet/src/screens/contact/contact_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/faq/faq_page.dart';
import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_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/pin_code/pin_code_widget.dart';
@ -32,6 +33,7 @@ import 'package:cake_wallet/src/screens/send/send_template_page.dart';
import 'package:cake_wallet/src/screens/settings/change_language.dart';
import 'package:cake_wallet/src/screens/settings/settings.dart';
import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
@ -63,6 +65,8 @@ import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.
import 'package:cake_wallet/view_model/rescan_view_model.dart';
import 'package:cake_wallet/view_model/restore_from_backup_view_model.dart';
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:cake_wallet/view_model/trade_details_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/dashboard_view_model.dart';
@ -340,7 +344,8 @@ Future setup(
(ContactRecord contact, _) =>
ContactViewModel(contactSource, contact: contact));
getIt.registerFactory(() => ContactListViewModel(contactSource));
getIt.registerFactory(
() => ContactListViewModel(contactSource, walletInfoSource));
getIt.registerFactoryParam<ContactListPage, bool, void>(
(bool isEditable, _) => ContactListPage(getIt.get<ContactListViewModel>(),
@ -368,7 +373,8 @@ Future setup(
getIt.get<AppStore>().wallet,
tradesSource,
getIt.get<ExchangeTemplateStore>(),
getIt.get<TradesStore>()));
getIt.get<TradesStore>(),
getIt.get<AppStore>().settingsStore));
getIt.registerFactory(() => ExchangeTradeViewModel(
wallet: getIt.get<AppStore>().wallet,
@ -389,7 +395,7 @@ Future setup(
getIt.registerFactory(() => MoneroWalletService(walletInfoSource));
getIt.registerFactory(() => BitcoinWalletService());
getIt.registerFactory(() => BitcoinWalletService(walletInfoSource));
getIt.registerFactoryParam<WalletService, WalletType, void>(
(WalletType param1, __) {
@ -428,13 +434,28 @@ Future setup(
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) =>
WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
getIt
.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => TransactionDetailsViewModel(
transactionInfo: transactionInfo,
transactionDescriptionBox: transactionDescriptionBox,
settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
transactionInfo,
getIt.get<SettingsStore>().shouldSaveRecipientAddress,
transactionDescriptionBox));
transactionDetailsViewModel:
getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
getIt.registerFactory(() => PreSeedPage());
getIt.registerFactoryParam<NewWalletTypePage,
void Function(BuildContext, WalletType), bool>(
(para1, param2) => NewWalletTypePage(getIt.get<WalletNewVM>(),
onTypeSelected: para1, isNewWallet: param2));
getIt.registerFactoryParam<PreSeedPage, WalletType, void>(
(WalletType type, _) => PreSeedPage(type));
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) =>
TradeDetailsViewModel(tradeForDetails: trade, trades: tradesSource));
getIt.registerFactory(() => BackupService(
getIt.get<FlutterSecureStorage>(),
@ -463,4 +484,7 @@ Future setup(
getIt.registerFactory(
() => RestoreFromBackupPage(getIt.get<RestoreFromBackupViewModel>()));
getIt.registerFactoryParam<TradeDetailsPage, Trade, void>((Trade trade, _) =>
TradeDetailsPage(getIt.get<TradeDetailsViewModel>(param1: trade)));
}

View file

@ -1,3 +1,11 @@
abstract class Balance {
const Balance();
const Balance(this.available, this.additional);
final int available;
final int additional;
String get formattedAvailableBalance;
String get formattedAdditionalBalance;
}

View file

@ -7,15 +7,16 @@ class BalanceDisplayMode extends EnumerableItem<int> with Serializable<int> {
: super(title: title, raw: raw);
static const all = [
BalanceDisplayMode.fullBalance,
BalanceDisplayMode.availableBalance,
BalanceDisplayMode.hiddenBalance
BalanceDisplayMode.hiddenBalance,
BalanceDisplayMode.displayableBalance,
];
static const fullBalance = BalanceDisplayMode(raw: 0, title: 'Full Balance');
static const availableBalance =
BalanceDisplayMode(raw: 1, title: 'Available Balance');
static const hiddenBalance =
BalanceDisplayMode(raw: 2, title: 'Hidden Balance');
static const displayableBalance =
BalanceDisplayMode(raw: 3, title: 'Displayable Balance');
static BalanceDisplayMode deserialize({int raw}) {
switch (raw) {
@ -25,6 +26,8 @@ class BalanceDisplayMode extends EnumerableItem<int> with Serializable<int> {
return availableBalance;
case 2:
return hiddenBalance;
case 3:
return displayableBalance;
default:
return null;
}
@ -39,6 +42,8 @@ class BalanceDisplayMode extends EnumerableItem<int> with Serializable<int> {
return S.current.xmr_available_balance;
case BalanceDisplayMode.hiddenBalance:
return S.current.xmr_hidden;
case BalanceDisplayMode.displayableBalance:
return S.current.displayable;
default:
return '';
}

View file

@ -0,0 +1,9 @@
import 'package:cake_wallet/entities/crypto_currency.dart';
abstract class ContactBase {
String name;
String address;
CryptoCurrency type;
}

View file

@ -3,21 +3,27 @@ import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/contact.dart';
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/entities/record.dart';
import 'package:cake_wallet/entities/contact_base.dart';
part 'contact_record.g.dart';
class ContactRecord = ContactRecordBase with _$ContactRecord;
abstract class ContactRecordBase extends Record<Contact> with Store {
abstract class ContactRecordBase extends Record<Contact>
with Store
implements ContactBase {
ContactRecordBase(Box<Contact> source, Contact original)
: super(source, original);
@override
@observable
String name;
@override
@observable
String address;
@override
@observable
CryptoCurrency type;

View file

@ -1,4 +1,8 @@
import 'dart:io' show Platform;
import 'dart:io' show File, Platform;
import 'package:cake_wallet/core/key_service.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/pathForWallet.dart';
import 'package:cake_wallet/monero/monero_wallet_service.dart';
import 'package:flutter/foundation.dart';
import 'package:hive/hive.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -45,7 +49,7 @@ Future defaultSettingsMigration(
FiatCurrency.usd.toString());
await sharedPreferences.setInt(
PreferencesKey.currentTransactionPriorityKey,
TransactionPriority.standart.raw);
TransactionPriority.standard.raw);
await sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey,
BalanceDisplayMode.availableBalance.raw);
@ -73,6 +77,14 @@ Future defaultSettingsMigration(
sharedPreferences: sharedPreferences, nodes: nodes);
break;
case 5:
await addAddressesForMoneroWallets(walletInfoSource);
break;
case 6:
await updateDisplayModes(sharedPreferences);
break;
default:
break;
}
@ -120,7 +132,7 @@ Future<void> changeMoneroCurrentNodeToDefault(
}
Node getBitcoinDefaultElectrumServer({@required Box<Node> nodes}) {
final uri = 'electrumx.cakewallet.com:50002';
final uri = 'electrum.cakewallet.com:50002';
return nodes.values
.firstWhere((Node node) => node.uri == uri, orElse: () => null) ??
@ -189,3 +201,34 @@ Future<void> addBitcoinElectrumServerList({@required Box<Node> nodes}) async {
final serverList = await loadElectrumServerList();
await nodes.addAll(serverList);
}
Future<void> addAddressesForMoneroWallets(
Box<WalletInfo> walletInfoSource) async {
final moneroWalletsInfo =
walletInfoSource.values.where((info) => info.type == WalletType.monero);
moneroWalletsInfo.forEach((info) async {
try {
final walletPath =
await pathForWallet(name: info.name, type: WalletType.monero);
final addressFilePath = '$walletPath.address.txt';
final addressFile = File(addressFilePath);
if (!addressFile.existsSync()) {
return;
}
final addressText = await addressFile.readAsString();
info.address = addressText;
await info.save();
} catch (e) {
print(e.toString());
}
});
}
Future<void> updateDisplayModes(SharedPreferences sharedPreferences) async {
final currentBalanceDisplayMode =
sharedPreferences.getInt(PreferencesKey.currentBalanceDisplayModeKey);
final balanceDisplayMode = currentBalanceDisplayMode < 2 ? 3 : 2;
await sharedPreferences.setInt(PreferencesKey.currentBalanceDisplayModeKey, balanceDisplayMode);
}

View file

@ -19,5 +19,5 @@ Future<void> loadCurrentWallet() async {
await getIt.get<KeyService>().getWalletPassword(walletName: name);
final _service = getIt.get<WalletService>(param1: type);
final wallet = await _service.openWallet(name, password);
appStore.wallet = wallet;
appStore.changeCurrentWallet(wallet);
}

View file

@ -39,9 +39,9 @@ Future<List<Node>> loadElectrumServerList() async {
Future resetToDefault(Box<Node> nodeSource) async {
final moneroNodes = await loadDefaultNodes();
// final bitcoinElectrumServerList = await loadElectrumServerList();
// final nodes = moneroNodes + bitcoinElectrumServerList;
final bitcoinElectrumServerList = await loadElectrumServerList();
final nodes = moneroNodes + bitcoinElectrumServerList;
await nodeSource.clear();
await nodeSource.addAll(moneroNodes);
await nodeSource.addAll(nodes);
}

View file

@ -9,7 +9,8 @@ class PreferencesKey {
static const shouldSaveRecipientAddressKey = 'save_recipient_address';
static const allowBiometricalAuthenticationKey =
'allow_biometrical_authentication';
static const currentDarkTheme = 'dark_theme';
static const currentTheme = 'current_theme';
static const isDarkThemeLegacy = 'dark_theme';
static const displayActionListModeKey = 'display_list_mode';
static const currentPinLength = 'current_pin_length';
static const currentLanguageCode = 'language_code';

View file

@ -4,7 +4,7 @@ part 'transaction_description.g.dart';
@HiveType(typeId: 2)
class TransactionDescription extends HiveObject {
TransactionDescription({this.id, this.recipientAddress});
TransactionDescription({this.id, this.recipientAddress, this.transactionNote});
static const boxName = 'TransactionDescriptions';
static const boxKey = 'transactionDescriptionsBoxKey';
@ -14,4 +14,9 @@ class TransactionDescription extends HiveObject {
@HiveField(1)
String recipientAddress;
@HiveField(2)
String transactionNote;
String get note => transactionNote ?? '';
}

View file

@ -4,6 +4,7 @@ import 'package:cake_wallet/utils/mobx.dart';
abstract class TransactionInfo extends Object with Keyable {
String id;
int amount;
int fee;
TransactionDirection direction;
bool isPending;
DateTime date;
@ -11,6 +12,7 @@ abstract class TransactionInfo extends Object with Keyable {
int confirmations;
String amountFormatted();
String fiatAmount();
String feeFormatted();
void changeFiatAmount(String amount);
@override

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/entities/enumerable_item.dart';
@ -17,7 +18,23 @@ class TransactionPriority extends EnumerableItem<int> with Serializable<int> {
static const medium = TransactionPriority(title: 'Medium', raw: 2);
static const fast = TransactionPriority(title: 'Fast', raw: 3);
static const fastest = TransactionPriority(title: 'Fastest', raw: 4);
static const standart = slow;
static const standard = slow;
static List<TransactionPriority> forWalletType(WalletType type) {
switch (type) {
case WalletType.monero:
return TransactionPriority.all;
case WalletType.bitcoin:
return [
TransactionPriority.slow,
TransactionPriority.regular,
TransactionPriority.fast
];
default:
return [];
}
}
static TransactionPriority deserialize({int raw}) {
switch (raw) {

View file

@ -0,0 +1,15 @@
import 'package:cake_wallet/entities/contact_base.dart';
import 'package:cake_wallet/entities/crypto_currency.dart';
class WalletContact implements ContactBase {
WalletContact(this.address, this.name, this.type);
@override
String address;
@override
String name;
@override
CryptoCurrency type;
}

View file

@ -7,7 +7,7 @@ part 'wallet_info.g.dart';
@HiveType(typeId: 4)
class WalletInfo extends HiveObject {
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight,
this.timestamp, this.dirPath, this.path);
this.timestamp, this.dirPath, this.path, this.address);
factory WalletInfo.external(
{@required String id,
@ -17,9 +17,10 @@ class WalletInfo extends HiveObject {
@required int restoreHeight,
@required DateTime date,
@required String dirPath,
@required String path}) {
@required String path,
@required String address}) {
return WalletInfo(id, name, type, isRecovery, restoreHeight,
date.millisecondsSinceEpoch ?? 0, dirPath, path);
date.millisecondsSinceEpoch ?? 0, dirPath, path, address);
}
static const boxName = 'WalletInfo';
@ -48,5 +49,8 @@ class WalletInfo extends HiveObject {
@HiveField(7)
String path;
@HiveField(8)
String address;
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp);
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:hive/hive.dart';
part 'wallet_type.g.dart';
@ -48,3 +49,25 @@ String walletTypeToString(WalletType type) {
return '';
}
}
String walletTypeToDisplayName(WalletType type) {
switch (type) {
case WalletType.monero:
return 'Monero';
case WalletType.bitcoin:
return 'Bitcoin (Electrum)';
default:
return '';
}
}
CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
switch (type) {
case WalletType.monero:
return CryptoCurrency.xmr;
case WalletType.bitcoin:
return CryptoCurrency.btc;
default:
return null;
}
}

View file

@ -27,11 +27,14 @@ class XMRTOExchangeProvider extends ExchangeProvider {
static const _orderParameterUriSuffix = '/order_parameter_query';
static const _orderStatusUriSuffix = '/order_status_query/';
static const _orderCreateUriSuffix = '/order_create/';
static const _headers = {
'Content-Type': 'application/json',
'User-Agent': userAgent
};
static Future<bool> _checkIsAvailable() async {
const url = originalApiUri + _orderParameterUriSuffix;
final response =
await get(url, headers: {'Content-Type': 'application/json'});
final response = await get(url, headers: _headers);
return !(response.statusCode == 403);
}
@ -91,9 +94,8 @@ class XMRTOExchangeProvider extends ExchangeProvider {
Future<Trade> createTrade({TradeRequest request}) async {
final _request = request as XMRTOTradeRequest;
final url = originalApiUri + _orderCreateUriSuffix;
final _amount = _request.isBTCRequest
? _request.receiveAmount
: _request.amount;
final _amount =
_request.isBTCRequest ? _request.receiveAmount : _request.amount;
final _amountCurrency = _request.isBTCRequest
? _request.to.toString()
@ -112,8 +114,8 @@ class XMRTOExchangeProvider extends ExchangeProvider {
'amount_currency': _amountCurrency,
'btc_dest_address': _request.address
};
final response = await post(url,
headers: {'Content-Type': 'application/json'}, body: json.encode(body));
final response =
await post(url, headers: _headers, body: json.encode(body));
if (response.statusCode != 201) {
if (response.statusCode == 400) {
@ -141,13 +143,10 @@ class XMRTOExchangeProvider extends ExchangeProvider {
@override
Future<Trade> findTradeById({@required String id}) async {
const headers = {
'Content-Type': 'application/json',
'User-Agent': userAgent
};
final url = originalApiUri + _orderStatusUriSuffix;
final body = {'uuid': id};
final response = await post(url, headers: headers, body: json.encode(body));
final response =
await post(url, headers: _headers, body: json.encode(body));
if (response.statusCode != 200) {
if (response.statusCode == 400) {
@ -210,8 +209,7 @@ class XMRTOExchangeProvider extends ExchangeProvider {
Future<double> _fetchRates() async {
try {
final url = originalApiUri + _orderParameterUriSuffix;
final response =
await get(url, headers: {'Content-Type': 'application/json'});
final response = await get(url, headers: _headers);
final responseJSON = json.decode(response.body) as Map<String, dynamic>;
final price = double.parse(responseJSON['price'] as String);

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,7 @@
import 'package:cake_wallet/core/backup.dart';
import 'package:cake_wallet/src/screens/backup/backup_page.dart';
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hive/hive.dart';
@ -55,11 +57,11 @@ void main() async {
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
final trades =
await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final walletInfoSource = await Hive.openBox<WalletInfo>(WalletInfo.boxName);
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates =
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
@ -70,7 +72,7 @@ void main() async {
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
initialMigrationVersion: 4);
initialMigrationVersion: 5);
runApp(App());
} catch (e) {
runApp(MaterialApp(
@ -78,7 +80,7 @@ void main() async {
home: Scaffold(
body: Container(
margin:
EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
child: Text(
'Error:\n${e.toString()}',
style: TextStyle(fontSize: 22),
@ -86,17 +88,16 @@ void main() async {
}
}
Future<void> initialSetup(
{@required SharedPreferences sharedPreferences,
@required Box<Node> nodes,
@required Box<WalletInfo> walletInfoSource,
@required Box<Contact> contactSource,
@required Box<Trade> tradesSource,
// @required FiatConvertationService fiatConvertationService,
@required Box<Template> templates,
@required Box<ExchangeTemplate> exchangeTemplates,
@required Box<TransactionDescription> transactionDescriptions,
int initialMigrationVersion = 5}) async {
Future<void> initialSetup({@required SharedPreferences sharedPreferences,
@required Box<Node> nodes,
@required Box<WalletInfo> walletInfoSource,
@required Box<Contact> contactSource,
@required Box<Trade> tradesSource,
// @required FiatConvertationService fiatConvertationService,
@required Box<Template> templates,
@required Box<ExchangeTemplate> exchangeTemplates,
@required Box<TransactionDescription> transactionDescriptions,
int initialMigrationVersion = 6}) async {
await defaultSettingsMigration(
version: initialMigrationVersion,
sharedPreferences: sharedPreferences,
@ -124,28 +125,28 @@ class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
final settingsStore = getIt.get<AppStore>().settingsStore;
if (settingsStore.theme == null) {
settingsStore.isDarkTheme = false;
}
final settingsStore = getIt
.get<AppStore>()
.settingsStore;
final statusBarColor = Colors.transparent;
final statusBarBrightness =
settingsStore.isDarkTheme ? Brightness.light : Brightness.dark;
final statusBarIconBrightness =
settingsStore.isDarkTheme ? Brightness.light : Brightness.dark;
final authenticationStore = getIt.get<AuthenticationStore>();
final initialRoute = authenticationStore.state == AuthenticationState.denied
? Routes.disclaimer
: Routes.login;
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: statusBarColor,
statusBarBrightness: statusBarBrightness,
statusBarIconBrightness: statusBarIconBrightness));
return Observer(builder: (BuildContext context) {
final currentTheme = settingsStore.currentTheme;
final statusBarBrightness = currentTheme.type == ThemeType.dark
? Brightness.light
: Brightness.dark;
final statusBarIconBrightness = currentTheme.type == ThemeType.dark
? Brightness.light
: Brightness.dark;
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: statusBarColor,
statusBarBrightness: statusBarBrightness,
statusBarIconBrightness: statusBarIconBrightness));
return Root(
authenticationStore: authenticationStore,
navigatorKey: navigatorKey,

View file

@ -1,20 +1,31 @@
import 'package:cake_wallet/entities/balance.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:flutter/foundation.dart';
import 'package:cake_wallet/monero/monero_amount_format.dart';
class MoneroBalance {
class MoneroBalance extends Balance {
MoneroBalance({@required this.fullBalance, @required this.unlockedBalance})
: formattedFullBalance = moneroAmountToString(amount: fullBalance),
formattedUnlockedBalance =
moneroAmountToString(amount: unlockedBalance);
moneroAmountToString(amount: unlockedBalance),
super(unlockedBalance, fullBalance);
MoneroBalance.fromString(
{@required this.formattedFullBalance,
@required this.formattedUnlockedBalance})
@required this.formattedUnlockedBalance})
: fullBalance = moneroParseAmount(amount: formattedFullBalance),
unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance);
unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance),
super(moneroParseAmount(amount: formattedUnlockedBalance),
moneroParseAmount(amount: formattedFullBalance));
final int fullBalance;
final int unlockedBalance;
final String formattedFullBalance;
final String formattedUnlockedBalance;
}
@override
String get formattedAvailableBalance => formattedUnlockedBalance;
@override
String get formattedAdditionalBalance => formattedFullBalance;
}

View file

@ -43,7 +43,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
balance = MoneroBalance(
fullBalance: monero_wallet.getFullBalance(accountIndex: account.id),
unlockedBalance:
monero_wallet.getUnlockedBalance(accountIndex: account.id));
monero_wallet.getUnlockedBalance(accountIndex: account.id));
subaddressList.update(accountIndex: account.id);
subaddress = subaddressList.subaddresses.first;
address = subaddress.address;
@ -120,6 +120,7 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
}
}
@override
void close() {
_listener?.stop();
_onAccountChangeReaction?.reaction?.dispose();
@ -315,9 +316,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
}
}
Future<void> _askForUpdateTransactionHistory() async {
await transactionHistory.update();
}
Future<void> _askForUpdateTransactionHistory() async =>
await transactionHistory.update();
int _getFullBalance() =>
monero_wallet.getFullBalance(accountIndex: account.id);
@ -326,13 +326,13 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
monero_wallet.getUnlockedBalance(accountIndex: account.id);
Future<void> _afterSyncSave() async {
if (_isSavingAfterSync) {
return;
}
_isSavingAfterSync = true;
try {
if (_isSavingAfterSync) {
return;
}
_isSavingAfterSync = true;
final nowTimestamp = DateTime.now().millisecondsSinceEpoch;
final sum = _lastAutosaveTimestamp + _autoAfterSyncSaveInterval;
@ -350,13 +350,13 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
}
Future<void> _afterNewTransactionSave() async {
if (_isSavingAfterNewTransaction) {
return;
}
_isSavingAfterNewTransaction = true;
try {
if (_isSavingAfterNewTransaction) {
return;
}
_isSavingAfterNewTransaction = true;
await save();
} catch (e) {
print(e.toString());
@ -366,30 +366,38 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance> with Store {
}
void _onNewBlock(int height, int blocksLeft, double ptc) async {
if (walletInfo.isRecovery) {
await _askForUpdateTransactionHistory();
_askForUpdateBalance();
accountList.update();
}
if (blocksLeft < 100) {
await _askForUpdateTransactionHistory();
_askForUpdateBalance();
accountList.update();
syncStatus = SyncedSyncStatus();
await _afterSyncSave();
try {
if (walletInfo.isRecovery) {
await setAsRecovered();
await _askForUpdateTransactionHistory();
_askForUpdateBalance();
accountList.update();
}
} else {
syncStatus = SyncingSyncStatus(blocksLeft, ptc);
if (blocksLeft < 100) {
await _askForUpdateTransactionHistory();
_askForUpdateBalance();
accountList.update();
syncStatus = SyncedSyncStatus();
await _afterSyncSave();
if (walletInfo.isRecovery) {
await setAsRecovered();
}
} else {
syncStatus = SyncingSyncStatus(blocksLeft, ptc);
}
} catch (e) {
print(e.toString());
}
}
void _onNewTransaction() {
_askForUpdateTransactionHistory();
_askForUpdateBalance();
Timer(Duration(seconds: 1), () => _afterNewTransactionSave());
try {
_askForUpdateTransactionHistory();
_askForUpdateBalance();
Timer(Duration(seconds: 1), () => _afterNewTransactionSave());
} catch (e) {
print(e.toString());
}
}
}

View file

@ -3,6 +3,7 @@ import 'package:cake_wallet/core/wallet_base.dart';
import 'package:hive/hive.dart';
import 'package:cw_monero/wallet_manager.dart' as monero_wallet_manager;
import 'package:cw_monero/wallet.dart' as monero_wallet;
import 'package:cw_monero/exceptions/wallet_opening_exception.dart';
import 'package:cake_wallet/monero/monero_wallet.dart';
import 'package:cake_wallet/core/wallet_credentials.dart';
import 'package:cake_wallet/core/wallet_service.dart';
@ -55,6 +56,18 @@ class MoneroWalletService extends WalletService<
final Box<WalletInfo> walletInfoSource;
static Future<void> _removeCache(String name) async {
final path = await pathForWallet(name: name, type: WalletType.monero);
final cacheFile = File(path);
if (cacheFile.existsSync()) {
cacheFile.deleteSync();
}
}
static bool walletFilesExist(String path) =>
!File(path).existsSync() && !File('$path.keys').existsSync();
@override
Future<MoneroWallet> create(MoneroNewWalletCredentials credentials) async {
try {
@ -94,7 +107,7 @@ class MoneroWalletService extends WalletService<
try {
final path = await pathForWallet(name: name, type: WalletType.monero);
if (!File(path).existsSync()) {
if (walletFilesExist(path)) {
await repairOldAndroidWallet(name);
}
@ -108,17 +121,9 @@ class MoneroWalletService extends WalletService<
final isValid = wallet.validate();
if (!isValid) {
// if (wallet.seed?.isNotEmpty ?? false) {
// let restore from seed in this case;
// final seed = wallet.seed;
// final credentials = MoneroRestoreWalletFromSeedCredentials(
// name: name, password: password, mnemonic: seed, height: 2000000)
// ..walletInfo = walletInfo;
// await remove(name);
// return restoreFromSeed(credentials);
// }
throw MoneroWalletLoadingException();
await _removeCache(name);
wallet.close();
return openWallet(name, password);
}
await wallet.init();
@ -126,7 +131,15 @@ class MoneroWalletService extends WalletService<
return wallet;
} catch (e) {
// TODO: Implement Exception for wallet list service.
print('MoneroWalletsManager Error: $e');
if (e.toString().contains('bad_alloc') ||
(e is WalletOpeningException &&
(e.message == 'std::bad_alloc' ||
e.message.contains('bad_alloc')))) {
await _removeCache(name);
return openWallet(name, password);
}
rethrow;
}
}
@ -204,7 +217,7 @@ class MoneroWalletService extends WalletService<
final dir = Directory(oldAndroidWalletDirPath);
if (!dir.existsSync()) {
throw MoneroWalletLoadingException();
return;
}
final newWalletDirPath =
@ -223,7 +236,6 @@ class MoneroWalletService extends WalletService<
});
} catch (e) {
print(e.toString());
throw MoneroWalletLoadingException();
}
}
}

View file

@ -4,7 +4,7 @@ class Palette {
static const Color green = Color.fromRGBO(39, 206, 80, 1.0);
static const Color red = Color.fromRGBO(255, 51, 51, 1.0);
static const Color darkRed = Color.fromRGBO(204, 38, 38, 1.0);
static const Color blueAlice = Color.fromRGBO(231, 240, 253, 1.0);
static const Color blueAlice = Color.fromRGBO(229, 247, 255, 1.0);
static const Color lightBlue = Color.fromRGBO(172, 203, 238, 1.0);
static const Color lavender = Color.fromRGBO(237, 245, 252, 1.0);
static const Color oceanBlue = Color.fromRGBO(30, 52, 78, 1.0);
@ -13,7 +13,6 @@ class Palette {
static const Color blue = Color.fromRGBO(88, 143, 252, 1.0);
static const Color darkLavender = Color.fromRGBO(229, 238, 250, 1.0);
static const Color nightBlue = Color.fromRGBO(46, 57, 96, 1.0);
static const Color moderateOrangeYellow = Color.fromRGBO(245, 134, 82, 1.0);
static const Color moderateOrange = Color.fromRGBO(235, 117, 63, 1.0);
static const Color shineGreen = Color.fromRGBO(76, 189, 87, 1.0);
@ -22,9 +21,8 @@ class Palette {
static const Color royalBlue = Color.fromRGBO(43, 114, 221, 1.0);
static const Color lightRed = Color.fromRGBO(227, 87, 87, 1.0);
static const Color persianRed = Color.fromRGBO(206, 55, 55, 1.0);
// NEW DESIGN
static const Color blueCraiola = Color.fromRGBO(69, 110, 255, 1.0);
static const Color blueGreyCraiola = Color.fromRGBO(106, 177, 207, 1.0);
static const Color darkBlueCraiola = Color.fromRGBO(53, 86, 136, 1.0);
static const Color pinkFlamingo = Color.fromRGBO(240, 60, 243, 1.0);
static const Color redHat = Color.fromRGBO(209, 68, 37, 1.0);
@ -43,26 +41,17 @@ class Palette {
static const Color alizarinRed = Color.fromRGBO(233, 45, 45, 1.0);
static const Color moderateSlateBlue = Color.fromRGBO(129, 93, 251, 1.0);
static const Color brightOrange = Color.fromRGBO(255, 102, 0, 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);
static const Color dullGray = Color.fromRGBO(98, 98, 98, 1.0);
static const Color protectiveBlue = Color.fromRGBO(33, 148, 255, 1.0);
}
class PaletteDark {
//static const Color distantBlue = Color.fromRGBO(70, 85, 133, 1.0); // mainBackgroundColor
static const Color lightDistantBlue = Color.fromRGBO(81, 96, 147, 1.0); // borderCardColor
static const Color gray = Color.fromRGBO(140, 153, 201, 1.0); // walletCardText
//static const Color violetBlue = Color.fromRGBO(51, 63, 104, 1.0); // walletCardAddressField
//static const Color moderateBlue = Color.fromRGBO(63, 77, 122, 1.0); // walletCardSubAddressField
//static const Color darkNightBlue = Color.fromRGBO(33, 43, 73, 1.0); // historyPanel
static const Color pigeonBlue = Color.fromRGBO(91, 112, 146, 1.0); // historyPanelText
static const Color moderateNightBlue = Color.fromRGBO(39, 53, 96, 1.0); // historyPanelButton
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
// NEW DESIGN
static const Color lightDistantBlue = Color.fromRGBO(81, 96, 147, 1.0);
static const Color gray = Color.fromRGBO(140, 153, 201, 1.0);
static const Color pigeonBlue = Color.fromRGBO(91, 112, 146, 1.0);
static const Color moderateNightBlue = Color.fromRGBO(39, 53, 96, 1.0);
static const Color headerNightBlue = Color.fromRGBO(41, 52, 84, 1.0);
static const Color moderatePurpleBlue = Color.fromRGBO(57, 74, 95, 1.0);
static const Color backgroundColor = Color.fromRGBO(25, 35, 60, 1.0);
static const Color nightBlue = Color.fromRGBO(35, 47, 79, 1.0);
static const Color wildNightBlue = Color.fromRGBO(39, 53, 96, 1.0);
@ -94,8 +83,5 @@ class PaletteDark {
static const Color deepVioletBlue = Color.fromRGBO(52, 66, 104, 1.0);
static const Color lightPurpleBlue = Color.fromRGBO(120, 133, 170, 1.0);
static const Color indicatorVioletBlue = Color.fromRGBO(59, 72, 119, 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);
static const Color granite = Color.fromRGBO(48, 51, 60, 1.0);
}

View file

@ -12,12 +12,13 @@ Future<void> startFiatRateUpdate(AppStore appStore, SettingsStore settingsStore,
return;
}
fiatConversionStore.price = await FiatConversionService.fetchPrice(
appStore.wallet.currency, settingsStore.fiatCurrency);
fiatConversionStore.prices[appStore.wallet.currency] =
await FiatConversionService.fetchPrice(
appStore.wallet.currency, settingsStore.fiatCurrency);
_timer = Timer.periodic(
Duration(seconds: 30),
(_) async => fiatConversionStore.price =
(_) async => fiatConversionStore.prices[appStore.wallet.currency] =
await FiatConversionService.fetchPrice(
appStore.wallet.currency, settingsStore.fiatCurrency));
}

View file

@ -10,14 +10,14 @@ ReactionDisposer _onAuthenticationStateChange;
dynamic loginError;
void startAuthenticationStateChange(AuthenticationStore authenticationStore,
@required GlobalKey<NavigatorState> navigatorKey) {
GlobalKey<NavigatorState> navigatorKey) {
_onAuthenticationStateChange ??= autorun((_) async {
final state = authenticationStore.state;
if (state == AuthenticationState.installed) {
try {
await loadCurrentWallet();
} catch(e) {
} catch (e) {
loginError = e;
}
return;

View file

@ -7,12 +7,13 @@ import 'package:cake_wallet/entities/fiat_currency.dart';
ReactionDisposer _onCurrentFiatCurrencyChangeDisposer;
void startCurrentFiatChangeReaction(AppStore appStore, SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
void startCurrentFiatChangeReaction(AppStore appStore,
SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
_onCurrentFiatCurrencyChangeDisposer?.reaction?.dispose();
_onCurrentFiatCurrencyChangeDisposer = reaction(
(_) => settingsStore.fiatCurrency, (FiatCurrency fiatCurrency) async {
(_) => settingsStore.fiatCurrency, (FiatCurrency fiatCurrency) async {
final cryptoCurrency = appStore.wallet.currency;
fiatConversionStore.price = await FiatConversionService.fetchPrice(
cryptoCurrency, fiatCurrency);
fiatConversionStore.prices[appStore.wallet.currency] =
await FiatConversionService.fetchPrice(cryptoCurrency, fiatCurrency);
});
}
}

View file

@ -6,10 +6,9 @@ ReactionDisposer _onCurrentNodeChangeReaction;
void startOnCurrentNodeChangeReaction(AppStore appStore) {
_onCurrentNodeChangeReaction?.reaction?.dispose();
_onCurrentNodeChangeReaction =
reaction((_) => appStore.settingsStore.currentNode, (Node node) async {
appStore.settingsStore.nodes.observe((change) async {
try {
await appStore.wallet.connectToNode(node: node);
await appStore.wallet.connectToNode(node: change.newValue);
} catch (e) {
print(e.toString());
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/balance.dart';
import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/di.dart';
@ -12,13 +13,14 @@ import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
ReactionDisposer _onCurrentWalletChangeReaction;
ReactionDisposer _onCurrentWalletChangeFiatRateUpdateReaction;
void startCurrentWalletChangeReaction(AppStore appStore,
SettingsStore settingsStore, FiatConversionStore fiatConversionStore) {
_onCurrentWalletChangeReaction?.reaction?.dispose();
_onCurrentWalletChangeReaction =
reaction((_) => appStore.wallet, (WalletBase wallet) async {
reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async {
try {
final node = settingsStore.getCurrentNode(wallet.type);
startWalletSyncStatusChangeReaction(wallet);
@ -30,8 +32,25 @@ void startCurrentWalletChangeReaction(AppStore appStore,
PreferencesKey.currentWalletType, serializeToInt(wallet.type));
await wallet.connectToNode(node: node);
fiatConversionStore.price = await FiatConversionService.fetchPrice(
wallet.currency, settingsStore.fiatCurrency);
if (wallet.walletInfo.address?.isEmpty ?? true) {
wallet.walletInfo.address = wallet.address;
if (wallet.walletInfo.isInBox) {
await wallet.walletInfo.save();
}
}
} catch (e) {
print(e.toString());
}
});
_onCurrentWalletChangeFiatRateUpdateReaction =
reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async {
try {
fiatConversionStore.prices[wallet.currency] = 0;
fiatConversionStore.prices[wallet.currency] =
await FiatConversionService.fetchPrice(
wallet.currency, settingsStore.fiatCurrency);
} catch (e) {
print(e.toString());
}

View file

@ -1,10 +1,11 @@
import 'package:cake_wallet/entities/balance.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/entities/sync_status.dart';
ReactionDisposer _onWalletSyncStatusChangeReaction;
void startWalletSyncStatusChangeReaction(WalletBase wallet) {
void startWalletSyncStatusChangeReaction(WalletBase<Balance> wallet) {
_onWalletSyncStatusChangeReaction?.reaction?.dispose();
_onWalletSyncStatusChangeReaction =
reaction((_) => wallet.syncStatus, (SyncStatus status) async {

View file

@ -65,30 +65,22 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.newWalletFromWelcome:
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>(param1:
(PinCodeState<PinCodeWidget> context, dynamic _) async {
try {
context.changeProcessText(S.current.creating_new_wallet);
final newWalletVM =
getIt.get<WalletNewVM>(param1: WalletType.monero);
await newWalletVM.create(
options: 'English'); // FIXME: Unnamed constant
context.hideProgressText();
await Navigator.of(context.context).pushNamed(Routes.preSeed);
} catch (e) {
context.changeProcessText('Error: ${e.toString()}');
}
}),
builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) =>
Navigator.of(context.context)
.pushNamed(Routes.newWalletType)),
fullscreenDialog: true);
case Routes.newWalletType:
return CupertinoPageRoute<void>(
builder: (_) => NewWalletTypePage(
onTypeSelected: (context, type) => Navigator.of(context)
.pushNamed(Routes.newWallet, arguments: type)));
builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) =>
Navigator.of(context)
.pushNamed(Routes.preSeed, arguments: type),
param2: true));
case Routes.newWallet:
final type = WalletType.monero; // settings.arguments as WalletType;
final type = settings.arguments as WalletType;
final walletNewVM = getIt.get<WalletNewVM>(param1: type);
return CupertinoPageRoute<void>(
@ -107,11 +99,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.restoreWalletType:
return CupertinoPageRoute<void>(
builder: (_) => NewWalletTypePage(
onTypeSelected: (context, type) => Navigator.of(context)
.pushNamed(Routes.restoreWalletOptions, arguments: type),
isNewWallet: false,
));
builder: (_) => getIt.get<NewWalletTypePage>(
param1: (BuildContext context, WalletType type) =>
Navigator.of(context)
.pushNamed(Routes.restoreWallet, arguments: type),
param2: false));
case Routes.restoreOptions:
final type = settings.arguments as WalletType;
@ -149,7 +141,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
return CupertinoPageRoute<void>(
builder: (_) => getIt.get<SetupPinCodePage>(
param1: (PinCodeState<PinCodeWidget> context, dynamic _) =>
Navigator.pushNamed(context.context, Routes.restoreWallet)),
Navigator.pushNamed(
context.context, Routes.restoreWalletType)),
fullscreenDialog: true);
case Routes.seed:
@ -159,16 +152,11 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.restoreWallet:
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<WalletRestorePage>(param1: WalletType.monero));
builder: (_) => getIt.get<WalletRestorePage>(
param1: settings.arguments as WalletType));
case Routes.restoreWalletFromSeed:
// final args = settings.arguments as List<dynamic>;
final type = WalletType.monero; //args.first as WalletType;
// final language = type == WalletType.monero
// ? args[1] as String
// : LanguageList.english;
final type = settings.arguments as WalletType;
return CupertinoPageRoute<void>(
builder: (_) => RestoreWalletFromSeedPage(type: type));
@ -294,7 +282,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.tradeDetails:
return MaterialPageRoute<void>(
builder: (_) => TradeDetailsPage(settings.arguments as Trade));
builder: (_) =>
getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
case Routes.restoreWalletFromSeedDetails:
final args = settings.arguments as List;
@ -327,7 +316,9 @@ Route<dynamic> createRoute(RouteSettings settings) {
builder: (_) => getIt.get<LanguageListPage>());
case Routes.preSeed:
return MaterialPageRoute<void>(builder: (_) => getIt.get<PreSeedPage>());
return MaterialPageRoute<void>(
builder: (_) =>
getIt.get<PreSeedPage>(param1: settings.arguments as WalletType));
case Routes.backup:
return CupertinoPageRoute<void>(

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/palette.dart';
@ -9,13 +10,13 @@ enum AppBarStyle { regular, withShadow, transparent }
abstract class BasePage extends StatelessWidget {
BasePage()
: _scaffoldKey = GlobalKey<ScaffoldState>(),
_closeButtonImage = Image.asset('assets/images/close_button.png'),
_closeButtonImageDarkTheme =
Image.asset('assets/images/close_button_dark_theme.png');
: _scaffoldKey = GlobalKey<ScaffoldState>();
final GlobalKey<ScaffoldState> _scaffoldKey;
final Image _closeButtonImage;
final Image _closeButtonImageDarkTheme;
final Image closeButtonImage =
Image.asset('assets/images/close_button.png');
final Image closeButtonImageDarkTheme =
Image.asset('assets/images/close_button_dark_theme.png');
String get title => null;
@ -37,7 +38,7 @@ abstract class BasePage extends StatelessWidget {
Widget Function(BuildContext, Widget) get rootWrapper => null;
bool get isDarkTheme => getIt.get<SettingsStore>().isDarkTheme;
ThemeBase get currentTheme => getIt.get<SettingsStore>().currentTheme;
void onOpenEndDrawer() => _scaffoldKey.currentState.openEndDrawer();
@ -51,8 +52,8 @@ abstract class BasePage extends StatelessWidget {
final _backButton = Icon(Icons.arrow_back_ios,
color: titleColor ?? Theme.of(context).primaryTextTheme.title.color,
size: 16,);
final _closeButton =
isDarkTheme ? _closeButtonImageDarkTheme : _closeButtonImage;
final _closeButton = currentTheme.type == ThemeType.dark
? closeButtonImageDarkTheme : closeButtonImage;
return SizedBox(
height: 37,
@ -88,8 +89,8 @@ abstract class BasePage extends StatelessWidget {
Widget floatingActionButton(BuildContext context) => null;
ObstructingPreferredSizeWidget appBar(BuildContext context) {
final appBarColor =
isDarkTheme ? backgroundDarkColor : backgroundLightColor;
final appBarColor = currentTheme.type == ThemeType.dark
? backgroundDarkColor : backgroundLightColor;
switch (appBarStyle) {
case AppBarStyle.regular:
@ -131,10 +132,12 @@ abstract class BasePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
final _backgroundColor = currentTheme.type == ThemeType.dark
? backgroundDarkColor : backgroundLightColor;
final root = Scaffold(
key: _scaffoldKey,
backgroundColor:
isDarkTheme ? backgroundDarkColor : backgroundLightColor,
backgroundColor: _backgroundColor,
resizeToAvoidBottomPadding: resizeToAvoidBottomPadding,
extendBodyBehindAppBar: extendBodyBehindAppBar,
endDrawer: endDrawer,

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/contact_base.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
@ -61,107 +62,113 @@ class ContactListPage extends BasePage {
padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
child: Observer(
builder: (_) {
return contactListViewModel.contacts.isNotEmpty
? SectionStandardList(
sectionCount: 1,
context: context,
itemCounter: (int sectionIndex) =>
contactListViewModel.contacts.length,
itemBuilder: (_, sectionIndex, index) {
final contact = contactListViewModel.contacts[index];
final image = _getCurrencyImage(contact.type);
final content = GestureDetector(
onTap: () async {
if (!isEditable) {
Navigator.of(context).pop(contact);
return;
}
return SectionStandardList(
context: context,
sectionCount: 2,
sectionTitleBuilder: (_, int sectionIndex) {
var title = 'Contacts';
final isCopied = await showNameAndAddressDialog(
context, contact.name, contact.address);
if (sectionIndex == 0) {
title = 'My wallets';
}
if (isCopied != null && isCopied) {
await Clipboard.setData(
ClipboardData(text: contact.address));
await showBar<void>(
context, S.of(context).copied_to_clipboard);
}
},
child: Container(
color: Colors.transparent,
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,
fontWeight: FontWeight.normal,
color: Theme.of(context)
.primaryTextTheme
.title
.color),
),
)
],
),
),
);
return Container(
padding: EdgeInsets.only(left: 24, bottom: 20),
child: Text(title, style: TextStyle(fontSize: 36)));
},
itemCounter: (int sectionIndex) => sectionIndex == 0
? contactListViewModel.walletContacts.length
: contactListViewModel.contacts.length,
itemBuilder: (_, sectionIndex, index) {
if (sectionIndex == 0) {
final walletInfo = contactListViewModel.walletContacts[index];
return generateRaw(context, walletInfo);
}
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;
final contact = contactListViewModel.contacts[index];
final content = generateRaw(context, contact);
if (isDelete) {
await contactListViewModel
.delete(contact);
}
},
),
]);
},
)
: Center(
child: Text(
S.of(context).placeholder_contacts,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey, fontSize: 14),
),
);
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);
}
},
),
]);
},
);
},
));
}
Widget generateRaw(BuildContext context, ContactBase contact) {
final image = _getCurrencyImage(contact.type);
return 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));
await showBar<void>(context, S.of(context).copied_to_clipboard);
}
},
child: Container(
color: Colors.transparent,
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,
fontWeight: FontWeight.normal,
color: Theme.of(context).primaryTextTheme.title.color),
),
)
],
),
),
);
}
Image _getCurrencyImage(CryptoCurrency currency) {
Image image;
switch (currency) {

View file

@ -1,5 +1,6 @@
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
@ -20,7 +21,8 @@ class DashboardPage extends BasePage {
});
@override
Color get backgroundLightColor => Colors.transparent;
Color get backgroundLightColor => currentTheme.type == ThemeType.bright
? Colors.transparent : Colors.white;
@override
Color get backgroundDarkColor => Colors.transparent;
@ -50,7 +52,8 @@ class DashboardPage extends BasePage {
@override
Widget trailing(BuildContext context) {
final menuButton =
Image.asset('assets/images/menu.png', color: Colors.white);
Image.asset('assets/images/menu.png',
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
return Container(
alignment: Alignment.centerRight,
@ -65,12 +68,6 @@ class DashboardPage extends BasePage {
final DashboardViewModel walletViewModel;
final WalletAddressListViewModel addressListViewModel;
final sendImage = Image.asset('assets/images/upload.png',
height: 22.24, width: 24, color: Colors.white);
final exchangeImage = Image.asset('assets/images/transfer.png',
height: 24.27, width: 22.25, color: Colors.white);
final receiveImage = Image.asset('assets/images/download.png',
height: 22.24, width: 24, color: Colors.white);
final controller = PageController(initialPage: 1);
var pages = <Widget>[];
@ -78,6 +75,15 @@ class DashboardPage extends BasePage {
@override
Widget body(BuildContext context) {
final sendImage = Image.asset('assets/images/upload.png',
height: 22.24, width: 24,
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
final exchangeImage = Image.asset('assets/images/transfer.png',
height: 24.27, width: 22.25,
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
final receiveImage = Image.asset('assets/images/download.png',
height: 22.24, width: 24,
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
_setEffects();
return SafeArea(
@ -100,7 +106,8 @@ class DashboardPage extends BasePage {
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).indicatorColor,
activeDotColor: Colors.white),
activeDotColor: Theme.of(context).accentTextTheme.display1
.backgroundColor),
)),
Container(
padding: EdgeInsets.only(left: 45, right: 45, bottom: 24),

View file

@ -9,44 +9,53 @@ import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
// FIXME: terrible design
class WalletMenu {
WalletMenu(this.context, this.reconnect);
final List<WalletMenuItem> items = [
WalletMenuItem(
title: S.current.reconnect,
image: Image.asset('assets/images/reconnect_menu.png',
height: 16, width: 16)),
WalletMenuItem(
title: S.current.rescan,
image: Image.asset('assets/images/filter_icon.png',
height: 16, width: 16)),
WalletMenuItem(
title: S.current.wallets,
image: Image.asset('assets/images/wallet_menu.png',
height: 16, width: 16)),
WalletMenuItem(
title: S.current.nodes,
image:
Image.asset('assets/images/nodes_menu.png', height: 16, width: 16)),
WalletMenuItem(
title: S.current.show_keys,
image:
Image.asset('assets/images/key_menu.png', height: 16, width: 16)),
WalletMenuItem(
title: S.current.address_book_menu,
image: Image.asset('assets/images/open_book_menu.png',
height: 16, width: 16)),
WalletMenuItem(
title: S.current.settings_title,
image: Image.asset('assets/images/settings_menu.png',
height: 16, width: 16)),
];
WalletMenu(this.context, this.reconnect, this.hasRescan) : items = [] {
items.addAll([
WalletMenuItem(
title: S.current.reconnect,
image: Image.asset('assets/images/reconnect_menu.png',
height: 16, width: 16)),
if (hasRescan)
WalletMenuItem(
title: S.current.rescan,
image: Image.asset('assets/images/filter_icon.png',
height: 16, width: 16)),
WalletMenuItem(
title: S.current.wallets,
image: Image.asset('assets/images/wallet_menu.png',
height: 16, width: 16)),
WalletMenuItem(
title: S.current.nodes,
image: Image.asset('assets/images/nodes_menu.png',
height: 16, width: 16)),
WalletMenuItem(
title: S.current.show_keys,
image:
Image.asset('assets/images/key_menu.png', height: 16, width: 16)),
WalletMenuItem(
title: S.current.address_book_menu,
image: Image.asset('assets/images/open_book_menu.png',
height: 16, width: 16)),
WalletMenuItem(
title: S.current.settings_title,
image: Image.asset('assets/images/settings_menu.png',
height: 16, width: 16)),
]);
}
final List<WalletMenuItem> items;
final BuildContext context;
final Future<void> Function() reconnect;
final bool hasRescan;
void action(int index) {
switch (index) {
var indx = index;
if (index > 0 && !hasRescan) {
indx += 1;
}
switch (indx) {
case 0:
_presentReconnectAlert(context);
break;

View file

@ -39,7 +39,10 @@ class ActionButton extends StatelessWidget {
SizedBox(height: 15),
Text(
title,
style: TextStyle(fontSize: 14, color: Colors.white),
style: TextStyle(
fontSize: 14,
color: Theme.of(context).accentTextTheme.display3
.backgroundColor),
)
],
),

View file

@ -1,62 +1,109 @@
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
class AddressPage extends StatelessWidget {
AddressPage({@required this.addressListViewModel});
AddressPage({@required this.addressListViewModel})
: _cryptoAmountFocus = FocusNode();
final WalletAddressListViewModel addressListViewModel;
final FocusNode _cryptoAmountFocus;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: QRWidget(addressListViewModel: addressListViewModel),
)
return KeyboardActions(
autoScroll: false,
disableScroll: true,
tapOutsideToDismiss: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor:
Theme.of(context).accentTextTheme.body2.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
)
]),
child: Container(
height: 1,
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: QRWidget(
addressListViewModel: addressListViewModel,
amountTextFieldFocusNode: _cryptoAmountFocus,
isAmountFieldShow: !addressListViewModel.hasAccounts),
)),
Observer(builder: (_) {
return addressListViewModel.hasAddressList
? GestureDetector(
onTap: () =>
Navigator.of(context).pushNamed(Routes.receive),
child: Container(
height: 50,
padding: EdgeInsets.only(left: 24, right: 12),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(25)),
border: Border.all(
color:
Theme.of(context).textTheme.subhead.color,
width: 1),
color: Theme.of(context).buttonColor),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Observer(
builder: (_) => Text(
addressListViewModel.hasAccounts
? S
.of(context)
.accounts_subaddresses
: S.of(context).addresses,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor),
)),
Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor,
)
],
),
),
)
: PrimaryButton(
onPressed: () => addressListViewModel.nextAddress(),
text: 'Next address',
color: Theme.of(context).buttonColor,
textColor: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor);
})
],
),
GestureDetector(
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
child: Container(
height: 50,
padding: EdgeInsets.only(left: 24, right: 12),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(25)),
border: Border.all(
color: Theme.of(context).textTheme.subhead.color,
width: 1
),
color: Theme.of(context).buttonColor
),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
S.of(context).accounts_subaddresses,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white
),
),
Icon(
Icons.arrow_forward_ios,
size: 14,
color: Colors.white,
)
],
),
),
)
],
),
);
));
}
}
}

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -11,58 +12,86 @@ class BalancePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Container(
color: Colors.transparent,
padding: EdgeInsets.all(24),
child: GestureDetector(
onTapUp: (_) => dashboardViewModel.balanceViewModel.isReversing = false,
onTapDown: (_) => dashboardViewModel.balanceViewModel.isReversing = true,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Observer(builder: (_) {
return Text(
dashboardViewModel.balanceViewModel.currency.toString(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Observer(builder: (_) {
return Text(
dashboardViewModel.balanceViewModel.currency.toString(),
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.accentTextTheme
.display2
.backgroundColor,
height: 1),
);
}),
SizedBox(height: 10),
Observer(builder: (_) {
return Text(
'${dashboardViewModel.balanceViewModel.availableBalanceLabel} (${dashboardViewModel.balanceViewModel.availableFiatBalance.toString()})',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.accentTextTheme
.display2
.backgroundColor,
height: 1),
);
}),
SizedBox(height: 10),
Observer(builder: (_) {
return AutoSizeText(
dashboardViewModel.balanceViewModel.availableBalance,
style: TextStyle(
fontSize: 40,
fontSize: 54,
fontWeight: FontWeight.bold,
color: Theme.of(context).indicatorColor,
color: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor,
height: 1),
);
}),
Observer(builder: (_) {
return Text(
dashboardViewModel.balanceViewModel.displayMode.toString(),
maxLines: 1,
textAlign: TextAlign.center);
}),
SizedBox(height: 10),
Observer(builder: (_) {
return Text(
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel} (${dashboardViewModel.balanceViewModel.additionalFiatBalance.toString()})',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.accentTextTheme
.display2
.backgroundColor,
height: 1),
);
}),
SizedBox(height: 10),
Observer(builder: (_) {
return AutoSizeText(
dashboardViewModel.balanceViewModel.additionalBalance
.toString(),
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context).indicatorColor,
fontSize: 18,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor,
height: 1),
);
}),
SizedBox(height: 10),
Observer(builder: (_) {
return AutoSizeText(dashboardViewModel.balanceViewModel.cryptoBalance,
style: TextStyle(
fontSize: 54,
fontWeight: FontWeight.bold,
color: Colors.white,
height: 1),
maxLines: 1,
textAlign: TextAlign.center);
}),
SizedBox(height: 10),
Observer(builder: (_) {
return Text(dashboardViewModel.balanceViewModel.fiatBalance,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Theme.of(context).indicatorColor,
height: 1),
textAlign: TextAlign.center);
}),
],
),
)
maxLines: 1,
textAlign: TextAlign.center);
}),
],
),
);
}
}

View file

@ -27,14 +27,15 @@ class HeaderRow extends StatelessWidget {
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.white
color: Theme.of(context).accentTextTheme.display3.backgroundColor
),
),
GestureDetector(
onTap: () {
showPopUp<void>(
context: context,
builder: (context) => FilterWidget(dashboardViewModel: dashboardViewModel)
builder: (context) =>
FilterWidget(dashboardViewModel: dashboardViewModel)
);
},
child: Container(

View file

@ -66,8 +66,10 @@ class MenuWidgetState extends State<MenuWidget> {
@override
Widget build(BuildContext context) {
final walletMenu =
WalletMenu(context, () async => widget.dashboardViewModel.reconnect());
final walletMenu = WalletMenu(
context,
() async => widget.dashboardViewModel.reconnect(),
widget.dashboardViewModel.hasRescan);
final itemCount = walletMenu.items.length;
moneroIcon = Image.asset('assets/images/monero_menu.png',
@ -148,16 +150,19 @@ class MenuWidgetState extends State<MenuWidget> {
),
if (widget.dashboardViewModel.subname !=
null)
Observer(builder: (_) => Text(
widget.dashboardViewModel.subname,
style: TextStyle(
color: Theme.of(context)
.accentTextTheme
.overline
.decorationColor,
fontWeight: FontWeight.w500,
fontSize: 12),
))
Observer(
builder: (_) => Text(
widget.dashboardViewModel
.subname,
style: TextStyle(
color: Theme.of(context)
.accentTextTheme
.overline
.decorationColor,
fontWeight:
FontWeight.w500,
fontSize: 12),
))
],
),
))

View file

@ -25,54 +25,53 @@ class TradeRow extends StatelessWidget {
return InkWell(
onTap: onTap,
child: Container(
height: 52,
padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
color: Colors.transparent,
padding: EdgeInsets.only(left: 24, right: 24),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
_getPoweredImage(provider),
Expanded(
child: Padding(
padding: const EdgeInsets.only(left: 12),
child: Container(
height: 46,
child: Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_getPoweredImage(provider),
SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text('${from.toString()}${to.toString()}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white
)),
formattedAmount != null
? Text(formattedAmount + ' ' + amountCrypto,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white
))
: Container()
]),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(createdAtFormattedDate,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).textTheme
.overline.backgroundColor))
]),
],
),
),
))
]),
Text('${from.toString()}${to.toString()}',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme.
display3.backgroundColor
)),
formattedAmount != null
? Text(formattedAmount + ' ' + amountCrypto,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme.
display3.backgroundColor
))
: Container()
]),
SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(createdAtFormattedDate,
style: TextStyle(
fontSize: 14,
color: Theme.of(context).textTheme
.overline.backgroundColor))
])
],
)
)
],
),
));
}

View file

@ -23,73 +23,73 @@ class TransactionRow extends StatelessWidget {
return InkWell(
onTap: onTap,
child: Container(
height: 62,
padding: EdgeInsets.fromLTRB(24, 8, 24, 8),
color: Colors.transparent,
padding: EdgeInsets.only(left: 24, right: 24),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).textTheme.overline.decorationColor
),
child: Image.asset(
direction == TransactionDirection.incoming
? 'assets/images/down_arrow.png'
: 'assets/images/up_arrow.png'),
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
height: 36,
width: 36,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).textTheme.overline.decorationColor
),
Expanded(
child: Container(
padding: const EdgeInsets.only(left: 12),
height: 56,
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
(direction == TransactionDirection.incoming
? S.of(context).received
: S.of(context).sent) +
(isPending ? S.of(context).pending : ''),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white)),
Text(formattedAmount,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.white))
]),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(formattedDate,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.textTheme
.overline
.backgroundColor)),
Text(formattedFiatAmount,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.textTheme
.overline
.backgroundColor))
]),
],
),
),
)
]),
child: Image.asset(
direction == TransactionDirection.incoming
? 'assets/images/down_arrow.png'
: 'assets/images/up_arrow.png'),
),
SizedBox(width: 12),
Expanded(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
(direction == TransactionDirection.incoming
? S.of(context).received
: S.of(context).sent) +
(isPending ? S.of(context).pending : ''),
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme.
display3.backgroundColor)),
Text(formattedAmount,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme.
display3.backgroundColor))
]),
SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(formattedDate,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.textTheme
.overline
.backgroundColor)),
Text(formattedFiatAmount,
style: TextStyle(
fontSize: 14,
color: Theme.of(context)
.textTheme
.overline
.backgroundColor))
])
],
)
)
],
),
));
}
}

View file

@ -46,7 +46,8 @@ class TransactionsPage extends StatelessWidget {
if (item is TransactionListItem) {
final transaction = item.transaction;
return TransactionRow(
return Observer(
builder: (_) => TransactionRow(
onTap: () => Navigator.of(context).pushNamed(
Routes.transactionDetails,
arguments: transaction),
@ -55,7 +56,7 @@ class TransactionsPage extends StatelessWidget {
.format(transaction.date),
formattedAmount: item.formattedCryptoAmount,
formattedFiatAmount: item.formattedFiatAmount,
isPending: transaction.isPending);
isPending: transaction.isPending));
}
if (item is TradeListItem) {

View file

@ -1,5 +1,6 @@
import 'dart:ui';
import 'package:cake_wallet/entities/sync_status.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
@ -62,10 +63,11 @@ class ExchangePage extends BasePage {
@override
Widget trailing(BuildContext context) => TrailButton(
caption: S.of(context).reset, onPressed: () {
caption: S.of(context).reset,
onPressed: () {
_formKey.currentState.reset();
exchangeViewModel.reset();
});
});
@override
Widget body(BuildContext context) {
@ -95,9 +97,8 @@ class ExchangePage extends BasePage {
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: isDarkTheme
? Color.fromRGBO(48, 51, 60, 1.0)
: Color.fromRGBO(98, 98, 98, 1.0),
keyboardBarColor:
Theme.of(context).accentTextTheme.body2.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
@ -161,6 +162,11 @@ class ExchangePage extends BasePage {
padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
child: Observer(
builder: (_) => ExchangeCard(
hasAllAmount: exchangeViewModel.hasAllAmount,
allAmount: exchangeViewModel.hasAllAmount
? () => exchangeViewModel
.calculateDepositAllAmount()
: null,
amountFocusNode: _depositAmountFocus,
key: depositKey,
title: S.of(context).you_will_send,
@ -178,9 +184,30 @@ class ExchangePage extends BasePage {
isAmountEstimated: false,
hasRefundAddress: true,
currencies: CryptoCurrency.all,
onCurrencySelected: (currency) =>
exchangeViewModel.changeDepositCurrency(
currency: currency),
onCurrencySelected: (currency) {
// FIXME: need to move it into view model
if (currency == CryptoCurrency.xmr &&
exchangeViewModel.wallet.type ==
WalletType.bitcoin) {
showPopUp<void>(
context: context,
builder: (dialogContext) {
return AlertWithOneAction(
alertTitle: S.of(context).error,
alertContent: S
.of(context)
.exchange_incorrect_current_wallet_for_xmr,
buttonText: S.of(context).ok,
buttonAction: () =>
Navigator.of(dialogContext)
.pop());
});
return;
}
exchangeViewModel.changeDepositCurrency(
currency: currency);
},
imageArrow: arrowBottomPurple,
currencyButtonColor: Colors.transparent,
addressButtonsColor:
@ -395,30 +422,35 @@ class ExchangePage extends BasePage {
}),
),
Observer(
builder: (_) => LoadingPrimaryButton(
text: S.of(context).exchange,
onPressed: () {
if (_formKey.currentState.validate()) {
if ((exchangeViewModel.depositCurrency == CryptoCurrency.xmr)
&&(!(exchangeViewModel.status is SyncedSyncStatus))) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).exchange,
alertContent: S.of(context).exchange_sync_alert_content,
buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop());
});
} else {
exchangeViewModel.createTrade();
}
}
},
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
isLoading: exchangeViewModel.tradeState
is TradeIsCreating)),
builder: (_) => LoadingPrimaryButton(
text: S.of(context).exchange,
onPressed: () {
if (_formKey.currentState.validate()) {
if ((exchangeViewModel.depositCurrency ==
CryptoCurrency.xmr) &&
(!(exchangeViewModel.status
is SyncedSyncStatus))) {
showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithOneAction(
alertTitle: S.of(context).exchange,
alertContent: S
.of(context)
.exchange_sync_alert_content,
buttonText: S.of(context).ok,
buttonAction: () =>
Navigator.of(context).pop());
});
} else {
exchangeViewModel.createTrade();
}
}
},
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
isLoading:
exchangeViewModel.tradeState is TradeIsCreating)),
]),
)),
));

View file

@ -27,7 +27,9 @@ class ExchangeCard extends StatefulWidget {
this.borderColor = Colors.transparent,
this.currencyValueValidator,
this.addressTextFieldValidator,
this.amountFocusNode})
this.amountFocusNode,
this.hasAllAmount = false,
this.allAmount})
: super(key: key);
final List<CryptoCurrency> currencies;
@ -47,6 +49,8 @@ class ExchangeCard extends StatefulWidget {
final FormFieldValidator<String> currencyValueValidator;
final FormFieldValidator<String> addressTextFieldValidator;
final FocusNode amountFocusNode;
final bool hasAllAmount;
Function allAmount;
@override
ExchangeCardState createState() => ExchangeCardState();
@ -166,8 +170,8 @@ class ExchangeCardState extends State<ExchangeCard> {
fontSize: 16,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.textTheme
.subhead
.accentTextTheme
.display4
.decorationColor),
validator: _isAmountEditable
? widget.currencyValueValidator
@ -197,50 +201,88 @@ class ExchangeCardState extends State<ExchangeCard> {
]),
),
),
)
),
if (widget.hasAllAmount)
Positioned(
top: 5,
right: 55,
child: Container(
height: 32,
width: 32,
margin: EdgeInsets.only(left: 14, top: 4, bottom: 10),
decoration: BoxDecoration(
color: Theme.of(context)
.primaryTextTheme
.display1
.color,
borderRadius: BorderRadius.all(Radius.circular(6))),
child: InkWell(
onTap: () => widget.allAmount?.call(),
child: Center(
child: Text(S.of(context).all,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.display1
.decorationColor)),
),
),
))
],
)),
Padding(
padding: EdgeInsets.only(top: 5),
child: Row(mainAxisAlignment: MainAxisAlignment.start, children: <
Widget>[
_min != null
? Text(
S.of(context).min_value(_min, _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context)
.textTheme
.subhead
.decorationColor),
)
: Offstage(),
_min != null ? SizedBox(width: 10) : Offstage(),
_max != null
? Text(
S.of(context).max_value(_max, _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context)
.textTheme
.subhead
.decorationColor))
: Offstage(),
]),
child: Container(
height: 15,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_min != null
? Text(
S
.of(context)
.min_value(_min, _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context)
.accentTextTheme
.display4
.decorationColor),
)
: Offstage(),
_min != null ? SizedBox(width: 10) : Offstage(),
_max != null
? Text(
S
.of(context)
.max_value(_max, _selectedCurrency.toString()),
style: TextStyle(
fontSize: 10,
height: 1.2,
color: Theme.of(context)
.accentTextTheme
.display4
.decorationColor))
: Offstage(),
])),
),
!_isAddressEditable && widget.hasRefundAddress
? Padding(
padding: EdgeInsets.only(top: 20),
child: Text(
S.of(context).refund_address,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color:
Theme.of(context).textTheme.subhead.decorationColor),
))
padding: EdgeInsets.only(top: 20),
child: Text(
S.of(context).refund_address,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.accentTextTheme
.display4
.decorationColor),
))
: Offstage(),
_isAddressEditable
? Padding(
@ -248,7 +290,8 @@ class ExchangeCardState extends State<ExchangeCard> {
child: AddressTextField(
controller: addressController,
placeholder: widget.hasRefundAddress
? S.of(context).refund_address : null,
? S.of(context).refund_address
: null,
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
@ -262,8 +305,10 @@ class ExchangeCardState extends State<ExchangeCard> {
hintStyle: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color:
Theme.of(context).textTheme.subhead.decorationColor),
color: Theme.of(context)
.accentTextTheme
.display4
.decorationColor),
buttonColor: widget.addressButtonsColor,
validator: widget.addressTextFieldValidator,
),
@ -275,8 +320,8 @@ class ExchangeCardState extends State<ExchangeCard> {
onTap: () {
Clipboard.setData(
ClipboardData(text: addressController.text));
showBar<void>(context,
S.of(context).copied_to_clipboard);
showBar<void>(
context, S.of(context).copied_to_clipboard);
},
child: Row(
mainAxisSize: MainAxisSize.max,

View file

@ -6,8 +6,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
@ -172,36 +170,14 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
),
itemBuilder: (context, index) {
final item = widget.exchangeTradeViewModel.items[index];
String value;
final value = item.data ?? fetchingLabel;
final content = Observer(builder: (_) {
switch (index) {
case 0:
value =
'${widget.exchangeTradeViewModel.trade.id ?? fetchingLabel}';
break;
case 1:
value =
'${widget.exchangeTradeViewModel.trade.amount ?? fetchingLabel}';
break;
case 2:
value =
'${widget.exchangeTradeViewModel.trade.state ?? fetchingLabel}';
break;
case 3:
value = widget.exchangeTradeViewModel.trade
.inputAddress ??
fetchingLabel;
break;
}
return StandartListRow(
title: item.title,
value: value,
valueFontSize: 14,
image: item.isCopied ? copyImage : null,
);
});
final content = StandartListRow(
title: item.title,
value: value,
valueFontSize: 14,
image: item.isCopied ? copyImage : null,
);
return item.isCopied
? Builder(
@ -227,16 +203,15 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
final sendingState =
widget.exchangeTradeViewModel.sendViewModel.state;
return trade.from == CryptoCurrency.xmr && !(sendingState is TransactionCommitted)
return widget.exchangeTradeViewModel.isSendable &&
!(sendingState is TransactionCommitted)
? LoadingPrimaryButton(
isDisabled: trade.inputAddress == null ||
trade.inputAddress.isEmpty,
isLoading: sendingState is IsExecutingState,
onPressed: () =>
widget.exchangeTradeViewModel.confirmSending(),
text: trade.provider == ExchangeProviderDescription.xmrto
? S.of(context).confirm
: S.of(context).send_xmr,
text: S.of(context).confirm,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white)
: Offstage();
@ -306,7 +281,11 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
padding: EdgeInsets.only(
top: 220, left: 24, right: 24),
child: Text(
S.of(context).send_success,
S.of(context).send_success(widget
.exchangeTradeViewModel
.wallet
.currency
.toString()),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
@ -379,7 +358,16 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
});
});
},
actionLeftButton: () => Navigator.of(context).pop());
actionLeftButton: () => Navigator.of(context).pop(),
feeFiatAmount: widget.exchangeTradeViewModel.sendViewModel
.pendingTransaction.feeFormatted,
fiatAmountValue: widget.exchangeTradeViewModel.sendViewModel
.pendingTransactionFeeFiatAmount +
' ' +
widget.exchangeTradeViewModel.sendViewModel.fiat.title,
recipientTitle: S.of(context).recipient_address,
recipientAddress:
widget.exchangeTradeViewModel.sendViewModel.address);
});
});
}

View file

@ -1,6 +1,5 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -22,17 +21,24 @@ class NewWalletPage extends BasePage {
final WalletNewVM _walletNewVM;
final walletNameImage = Image.asset('assets/images/wallet_name.png');
final walletNameLightImage =
Image.asset('assets/images/wallet_name_light.png');
@override
String get title => S.current.new_wallet;
@override
Widget body(BuildContext context) => WalletNameForm(_walletNewVM);
Widget body(BuildContext context) => WalletNameForm(_walletNewVM,
currentTheme.type == ThemeType.dark
? walletNameImage : walletNameLightImage);
}
class WalletNameForm extends StatefulWidget {
WalletNameForm(this._walletNewVM);
WalletNameForm(this._walletNewVM, this.walletImage);
final WalletNewVM _walletNewVM;
final Image walletImage;
@override
_WalletNameFormState createState() => _WalletNameFormState(_walletNewVM);
@ -43,9 +49,6 @@ class _WalletNameFormState extends State<WalletNameForm> {
static const aspectRatioImage = 1.22;
final walletNameImage = Image.asset('assets/images/wallet_name.png');
final walletNameLightImage =
Image.asset('assets/images/wallet_name_light.png');
final _formKey = GlobalKey<FormState>();
final _languageSelectorKey = GlobalKey<SeedLanguageSelectorState>();
ReactionDisposer _stateReaction;
@ -78,10 +81,6 @@ class _WalletNameFormState extends State<WalletNameForm> {
@override
Widget build(BuildContext context) {
final walletImage = getIt.get<SettingsStore>().isDarkTheme
? walletNameImage
: walletNameLightImage;
return Container(
padding: EdgeInsets.only(top: 24),
child: ScrollableWithBottomSection(
@ -92,7 +91,7 @@ class _WalletNameFormState extends State<WalletNameForm> {
padding: EdgeInsets.only(left: 12, right: 12),
child: AspectRatio(
aspectRatio: aspectRatioImage,
child: FittedBox(child: walletImage, fit: BoxFit.fill)),
child: FittedBox(child: widget.walletImage, fit: BoxFit.fill)),
),
Padding(
padding: EdgeInsets.only(top: 24),

View file

@ -1,6 +1,13 @@
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/wallet_new_vm.dart';
import 'package:flushbar/flushbar.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -10,25 +17,36 @@ import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/screens/new_wallet/widgets/select_button.dart';
class NewWalletTypePage extends BasePage {
NewWalletTypePage({this.onTypeSelected, this.isNewWallet = true});
NewWalletTypePage(this.walletNewVM, {this.onTypeSelected, this.isNewWallet});
final void Function(BuildContext, WalletType) onTypeSelected;
final bool isNewWallet;
final WalletNewVM walletNewVM;
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
final walletTypeLightImage =
Image.asset('assets/images/wallet_type_light.png');
@override
String get title => isNewWallet
? S.current.new_wallet
: S.current.wallet_list_restore_wallet;
String get title =>
isNewWallet ? S.current.new_wallet : S.current.wallet_list_restore_wallet;
@override
Widget body(BuildContext context) =>
WalletTypeForm(onTypeSelected: onTypeSelected);
Widget body(BuildContext context) => WalletTypeForm(walletNewVM, isNewWallet,
onTypeSelected: onTypeSelected,
walletImage: currentTheme.type == ThemeType.dark
? walletTypeImage
: walletTypeLightImage);
}
class WalletTypeForm extends StatefulWidget {
WalletTypeForm({this.onTypeSelected});
WalletTypeForm(this.walletNewVM, this.isNewWallet,
{this.onTypeSelected, this.walletImage});
final void Function(BuildContext, WalletType) onTypeSelected;
final WalletNewVM walletNewVM;
final bool isNewWallet;
final Image walletImage;
@override
WalletTypeFormState createState() => WalletTypeFormState();
@ -42,10 +60,12 @@ class WalletTypeFormState extends State<WalletTypeForm> {
final bitcoinIcon =
Image.asset('assets/images/bitcoin.png', height: 24, width: 24);
final walletTypeImage = Image.asset('assets/images/wallet_type.png');
final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png');
final walletTypeLightImage =
Image.asset('assets/images/wallet_type_light.png');
WalletType selected;
List<WalletType> types;
Flushbar<void> _progressBar;
@override
void initState() {
@ -55,11 +75,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
@override
Widget build(BuildContext context) {
final walletImage = getIt.get<SettingsStore>().isDarkTheme
? walletTypeImage : walletTypeLightImage;
return Container(
padding: EdgeInsets.only(top: 24),
padding: EdgeInsets.only(top: 24, bottom: 24),
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
content: Column(
@ -69,7 +86,8 @@ class WalletTypeFormState extends State<WalletTypeForm> {
padding: EdgeInsets.only(left: 12, right: 12),
child: AspectRatio(
aspectRatio: aspectRatioImage,
child: FittedBox(child: walletImage, fit: BoxFit.fill)),
child:
FittedBox(child: widget.walletImage, fit: BoxFit.fill)),
),
Padding(
padding: EdgeInsets.only(top: 48),
@ -86,7 +104,7 @@ class WalletTypeFormState extends State<WalletTypeForm> {
padding: EdgeInsets.only(top: 24),
child: SelectButton(
image: _iconFor(type),
text: walletTypeToString(type),
text: walletTypeToDisplayName(type),
isSelected: selected == type,
onTap: () => setState(() => selected = type)),
))
@ -94,9 +112,9 @@ class WalletTypeFormState extends State<WalletTypeForm> {
),
bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: PrimaryButton(
onPressed: () => widget.onTypeSelected(context, selected),
onPressed: () => onTypeSelected(),
text: S.of(context).seed_language_next,
color: Colors.green,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
isDisabled: selected == null,
),
@ -114,4 +132,35 @@ class WalletTypeFormState extends State<WalletTypeForm> {
return null;
}
}
Future<void> onTypeSelected() async {
if (!widget.isNewWallet) {
widget.onTypeSelected(context, selected);
return;
}
try {
_changeProcessText(S.of(context).creating_new_wallet);
widget.walletNewVM.type = selected;
await widget.walletNewVM
.create(options: 'English'); // FIXME: Unnamed constant
await _progressBar?.dismiss();
final state = widget.walletNewVM.state;
if (state is ExecutedSuccessfullyState) {
widget.onTypeSelected(context, selected);
}
if (state is FailureState) {
_changeProcessText(
S.of(context).creating_new_wallet_error(state.error));
}
} catch (e) {
_changeProcessText(S.of(context).creating_new_wallet_error(e.toString()));
}
}
void _changeProcessText(String text) {
_progressBar = createBar<void>(text, duration: null)..show(context);
}
}

View file

@ -16,7 +16,7 @@ class SelectButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final color = isSelected
? Theme.of(context).accentTextTheme.subtitle.decorationColor
? Colors.green
: Theme.of(context).accentTextTheme.caption.color;
final textColor = isSelected
? Theme.of(context).accentTextTheme.headline.decorationColor

View file

@ -65,98 +65,90 @@ class NodeListPage extends BasePage {
padding: EdgeInsets.only(top: 10),
child: Observer(
builder: (BuildContext context) {
return nodeListViewModel.nodes.isNotEmpty
? SectionStandardList(
sectionCount: 2,
context: context,
itemCounter: (int sectionIndex) {
if (sectionIndex == 0) {
return 1;
}
return SectionStandardList(
sectionCount: 2,
context: context,
itemCounter: (int sectionIndex) {
if (sectionIndex == 0) {
return 1;
}
return nodeListViewModel.nodes.length;
},
itemBuilder: (_, sectionIndex, index) {
if (sectionIndex == 0) {
return NodeHeaderListRow(
title: S.of(context).add_new_node,
onTap: (_) async => await Navigator.of(context)
.pushNamed(Routes.newNode));
}
return nodeListViewModel.nodes.length;
},
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 node = nodeListViewModel.nodes[index];
final isSelected = node.keyIndex ==
nodeListViewModel.settingsStore.currentNode.keyIndex;
final nodeListRow = NodeListRow(
title: node.uri,
isSelected: isSelected,
isAlive: node.requestNode(),
onTap: (_) async {
if (isSelected) {
return;
final node = nodeListViewModel.nodes[index];
final isSelected =
node.keyIndex == nodeListViewModel.currentNode?.keyIndex;
final nodeListRow = NodeListRow(
title: node.uri,
isSelected: isSelected,
isAlive: node.requestNode(),
onTap: (_) async {
if (isSelected) {
return;
}
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle:
S.of(context).change_current_node_title,
alertContent:
S.of(context).change_current_node(node.uri),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () =>
Navigator.of(context).pop(),
actionRightButton: () async {
await nodeListViewModel.setAsCurrent(node);
Navigator.of(context).pop();
});
});
});
final dismissibleRow = Slidable(
key: Key('${node.keyIndex}'),
actionPane: SlidableDrawerActionPane(),
child: nodeListRow,
secondaryActions: <Widget>[
IconSlideAction(
caption: S.of(context).delete,
color: Colors.red,
icon: CupertinoIcons.delete,
onTap: () async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context).remove_node,
alertContent:
S.of(context).remove_node_message,
rightButtonText: S.of(context).remove,
leftButtonText: S.of(context).cancel,
actionRightButton: () =>
Navigator.pop(context, true),
actionLeftButton: () =>
Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
await nodeListViewModel.delete(node);
}
},
),
]);
await showPopUp<void>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle: S.of(context)
.change_current_node_title,
alertContent: S
.of(context)
.change_current_node(node.uri),
leftButtonText: S.of(context).cancel,
rightButtonText: S.of(context).change,
actionLeftButton: () =>
Navigator.of(context).pop(),
actionRightButton: () async {
await nodeListViewModel
.setAsCurrent(node);
Navigator.of(context).pop();
});
});
});
final dismissibleRow = Slidable(
key: Key('${node.keyIndex}'),
actionPane: SlidableDrawerActionPane(),
child: nodeListRow,
secondaryActions: <Widget>[
IconSlideAction(
caption: S.of(context).delete,
color: Colors.red,
icon: CupertinoIcons.delete,
onTap: () async {
final confirmed = await showPopUp<bool>(
context: context,
builder: (BuildContext context) {
return AlertWithTwoActions(
alertTitle:
S.of(context).remove_node,
alertContent: S
.of(context)
.remove_node_message,
rightButtonText:
S.of(context).remove,
leftButtonText:
S.of(context).cancel,
actionRightButton: () =>
Navigator.pop(context, true),
actionLeftButton: () =>
Navigator.pop(context, false));
}) ??
false;
if (confirmed) {
await nodeListViewModel.delete(node);
}
},
),
]);
return isSelected ? nodeListRow : dismissibleRow;
})
: Container();
return isSelected ? nodeListRow : dismissibleRow;
});
},
),
);

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
@ -27,16 +28,47 @@ class ReceivePage extends BasePage {
String get title => S.current.receive;
@override
Color get backgroundLightColor => Colors.transparent;
Color get backgroundLightColor => currentTheme.type == ThemeType.bright
? Colors.transparent : Colors.white;
@override
Color get backgroundDarkColor => Colors.transparent;
@override
Color get titleColor => Colors.white;
final FocusNode _cryptoAmountFocus;
@override
Widget leading(BuildContext context) {
final _backButton = Icon(Icons.arrow_back_ios,
color: Theme.of(context).accentTextTheme.display3.backgroundColor,
size: 16,);
return SizedBox(
height: 37,
width: 37,
child: ButtonTheme(
minWidth: double.minPositive,
child: FlatButton(
highlightColor: Colors.transparent,
splashColor: Colors.transparent,
padding: EdgeInsets.all(0),
onPressed: () => onClose(context),
child: _backButton),
),
);
}
@override
Widget middle(BuildContext context) {
return Text(
title,
style: TextStyle(
fontSize: 18.0,
fontWeight: FontWeight.bold,
fontFamily: 'Lato',
color: Theme.of(context).accentTextTheme.display3.backgroundColor),
);
}
@override
Widget Function(BuildContext, Widget) get rootWrapper =>
(BuildContext context, Widget scaffold) => Container(
@ -51,7 +83,8 @@ class ReceivePage extends BasePage {
@override
Widget trailing(BuildContext context) {
final shareImage =
Image.asset('assets/images/share.png', color: Colors.white);
Image.asset('assets/images/share.png',
color: Theme.of(context).accentTextTheme.display3.backgroundColor);
return SizedBox(
height: 20.0,
@ -74,9 +107,8 @@ class ReceivePage extends BasePage {
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: isDarkTheme
? Color.fromRGBO(48, 51, 60, 1.0)
: Color.fromRGBO(98, 98, 98, 1.0),
keyboardBarColor: Theme.of(context).accentTextTheme.body2
.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(

View file

@ -47,14 +47,14 @@ class QRWidget extends StatelessWidget {
child: QrImage(
data: addressListViewModel.uri.toString(),
backgroundColor: Colors.transparent,
foregroundColor: Colors.white,
//Theme.of(context).textTheme.headline.color,
foregroundColor: Theme.of(context).accentTextTheme.
display3.backgroundColor,
))))),
Spacer(flex: 3)
]),
isAmountFieldShow
? Padding(
padding: EdgeInsets.only(top: 60),
padding: EdgeInsets.only(top: 40),
child: Row(
children: <Widget>[
Expanded(
@ -67,11 +67,12 @@ class QRWidget extends StatelessWidget {
decimal: true),
inputFormatters: [
BlacklistingTextInputFormatter(
RegExp('[\\-|\\ |\\,]'))
RegExp('[\\-|\\ ]'))
],
textAlign: TextAlign.center,
hintText: S.of(context).receive_amount,
textColor: Colors.white,
textColor: Theme.of(context).accentTextTheme.
display3.backgroundColor,
borderColor: Theme.of(context)
.textTheme
.headline
@ -108,9 +109,10 @@ class QRWidget extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
fontSize: 15,
fontWeight: FontWeight.w500,
color: Colors.white),
color: Theme.of(context).accentTextTheme.
display3.backgroundColor),
),
),
Padding(

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/view_model/wallet_restore_view_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@ -7,12 +9,20 @@ import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/blockchain_height_widget.dart';
class WalletRestoreFromSeedForm extends StatefulWidget {
WalletRestoreFromSeedForm({Key key, this.blockHeightFocusNode,
this.onHeightOrDateEntered})
WalletRestoreFromSeedForm(
{Key key,
@required this.displayLanguageSelector,
@required this.displayBlockHeightSelector,
@required this.type,
this.blockHeightFocusNode,
this.onHeightOrDateEntered})
: super(key: key);
final WalletType type;
final bool displayLanguageSelector;
final bool displayBlockHeightSelector;
final FocusNode blockHeightFocusNode;
final Function (bool) onHeightOrDateEntered;
final Function(bool) onHeightOrDateEntered;
@override
WalletRestoreFromSeedFormState createState() =>
@ -41,32 +51,35 @@ class WalletRestoreFromSeedFormState extends State<WalletRestoreFromSeedForm> {
return Container(
padding: EdgeInsets.only(left: 25, right: 25),
child: Column(children: [
SeedWidget(key: seedWidgetStateKey, language: language),
GestureDetector(
onTap: () async {
final selected = await showPopUp<String>(
context: context,
builder: (BuildContext context) =>
SeedLanguagePicker(selected: language));
SeedWidget(
key: seedWidgetStateKey, language: language, type: widget.type),
if (widget.displayLanguageSelector)
GestureDetector(
onTap: () async {
final selected = await showPopUp<String>(
context: context,
builder: (BuildContext context) =>
SeedLanguagePicker(selected: language));
if (selected == null || selected.isEmpty) {
return;
}
if (selected == null || selected.isEmpty) {
return;
}
_changeLanguage(selected);
},
child: Container(
color: Colors.transparent,
padding: EdgeInsets.only(top: 20.0),
child: IgnorePointer(
child: BaseTextFormField(
controller: languageController,
enableInteractiveSelection: false,
readOnly: true)))),
BlockchainHeightWidget(
focusNode: widget.blockHeightFocusNode,
key: blockchainHeightKey,
onHeightOrDateEntered: widget.onHeightOrDateEntered)
_changeLanguage(selected);
},
child: Container(
color: Colors.transparent,
padding: EdgeInsets.only(top: 20.0),
child: IgnorePointer(
child: BaseTextFormField(
controller: languageController,
enableInteractiveSelection: false,
readOnly: true)))),
if (widget.displayBlockHeightSelector)
BlockchainHeightWidget(
focusNode: widget.blockHeightFocusNode,
key: blockchainHeightKey,
onHeightOrDateEntered: widget.onHeightOrDateEntered)
]));
}

View file

@ -25,16 +25,30 @@ class WalletRestorePage extends BasePage {
_pages = [],
_blockHeightFocusNode = FocusNode(),
_controller = PageController(initialPage: 0) {
_pages.addAll([
WalletRestoreFromSeedForm(
key: walletRestoreFromSeedFormKey,
blockHeightFocusNode: _blockHeightFocusNode,
onHeightOrDateEntered: (value)
=> walletRestoreViewModel.isButtonEnabled = value),
WalletRestoreFromKeysFrom(key: walletRestoreFromKeysFormKey,
onHeightOrDateEntered: (value)
=> walletRestoreViewModel.isButtonEnabled = value)
]);
walletRestoreViewModel.availableModes.forEach((mode) {
switch (mode) {
case WalletRestoreMode.seed:
_pages.add(WalletRestoreFromSeedForm(
displayBlockHeightSelector:
walletRestoreViewModel.hasBlockchainHeightLanguageSelector,
displayLanguageSelector:
walletRestoreViewModel.hasSeedLanguageSelector,
type: walletRestoreViewModel.type,
key: walletRestoreFromSeedFormKey,
blockHeightFocusNode: _blockHeightFocusNode,
onHeightOrDateEntered: (value) =>
walletRestoreViewModel.isButtonEnabled = value));
break;
case WalletRestoreMode.keys:
_pages.add(WalletRestoreFromKeysFrom(
key: walletRestoreFromKeysFormKey,
onHeightOrDateEntered: (value) =>
walletRestoreViewModel.isButtonEnabled = value));
break;
default:
break;
}
});
}
@override
@ -76,20 +90,19 @@ class WalletRestorePage extends BasePage {
}
});
reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode)
{
walletRestoreViewModel.isButtonEnabled = false;
reaction((_) => walletRestoreViewModel.mode, (WalletRestoreMode mode) {
walletRestoreViewModel.isButtonEnabled = false;
walletRestoreFromSeedFormKey.currentState.blockchainHeightKey
.currentState.restoreHeightController.text = '';
walletRestoreFromSeedFormKey.currentState.blockchainHeightKey
.currentState.dateController.text = '';
walletRestoreFromSeedFormKey.currentState.blockchainHeightKey.currentState
.restoreHeightController.text = '';
walletRestoreFromSeedFormKey.currentState.blockchainHeightKey.currentState
.dateController.text = '';
walletRestoreFromKeysFormKey.currentState.blockchainHeightKey
.currentState.restoreHeightController.text = '';
walletRestoreFromKeysFormKey.currentState.blockchainHeightKey
.currentState.dateController.text = '';
});
walletRestoreFromKeysFormKey.currentState.blockchainHeightKey.currentState
.restoreHeightController.text = '';
walletRestoreFromKeysFormKey.currentState.blockchainHeightKey.currentState
.dateController.text = '';
});
return Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Expanded(
@ -100,40 +113,37 @@ class WalletRestorePage extends BasePage {
},
controller: _controller,
itemCount: _pages.length,
itemBuilder: (_, index) => SingleChildScrollView(child: _pages[index]))),
Padding(
padding: EdgeInsets.only(top: 10),
child: SmoothPageIndicator(
controller: _controller,
count: _pages.length,
effect: ColorTransitionEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).hintColor.withOpacity(0.5),
activeDotColor: Theme.of(context).hintColor),
)),
itemBuilder: (_, index) =>
SingleChildScrollView(child: _pages[index]))),
if (_pages.length > 1)
Padding(
padding: EdgeInsets.only(top: 10),
child: SmoothPageIndicator(
controller: _controller,
count: _pages.length,
effect: ColorTransitionEffect(
spacing: 6.0,
radius: 6.0,
dotWidth: 6.0,
dotHeight: 6.0,
dotColor: Theme.of(context).hintColor.withOpacity(0.5),
activeDotColor: Theme.of(context).hintColor),
)),
Padding(
padding: EdgeInsets.only(top: 20, bottom: 40, left: 25, right: 25),
child: Observer(
builder: (context) {
return LoadingPrimaryButton(
onPressed: () =>
walletRestoreViewModel.create(options: _credentials()),
text: S.of(context).restore_recover,
color: Theme
.of(context)
.accentTextTheme
.subtitle
.decorationColor,
textColor: Theme
.of(context)
.accentTextTheme
.headline
.decorationColor,
isLoading: walletRestoreViewModel.state is IsExecutingState,
isDisabled: !walletRestoreViewModel.isButtonEnabled,);
onPressed: () =>
walletRestoreViewModel.create(options: _credentials()),
text: S.of(context).restore_recover,
color:
Theme.of(context).accentTextTheme.subtitle.decorationColor,
textColor:
Theme.of(context).accentTextTheme.headline.decorationColor,
isLoading: walletRestoreViewModel.state is IsExecutingState,
isDisabled: !walletRestoreViewModel.isButtonEnabled,
);
},
))
]);
@ -145,8 +155,11 @@ class WalletRestorePage extends BasePage {
if (walletRestoreViewModel.mode == WalletRestoreMode.seed) {
credentials['seed'] = walletRestoreFromSeedFormKey
.currentState.seedWidgetStateKey.currentState.text;
credentials['height'] = walletRestoreFromSeedFormKey
.currentState.blockchainHeightKey.currentState.height;
if (walletRestoreViewModel.hasBlockchainHeightLanguageSelector) {
credentials['height'] = walletRestoreFromSeedFormKey
.currentState.blockchainHeightKey.currentState.height;
}
} else {
credentials['address'] =
walletRestoreFromKeysFormKey.currentState.addressController.text;

View file

@ -1,6 +1,6 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -8,8 +8,17 @@ import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
class PreSeedPage extends BasePage {
static final imageLight = Image.asset('assets/images/pre_seed_light.png');
static final imageDark = Image.asset('assets/images/pre_seed_dark.png');
PreSeedPage(this.type)
: imageLight = Image.asset('assets/images/pre_seed_light.png'),
imageDark = Image.asset('assets/images/pre_seed_dark.png'),
wordsCount = type == WalletType.monero
? 25
: 12; // FIXME: Stupid fast implementation
final Image imageDark;
final Image imageLight;
final WalletType type;
final int wordsCount;
@override
Widget leading(BuildContext context) => null;
@ -19,8 +28,7 @@ class PreSeedPage extends BasePage {
@override
Widget body(BuildContext context) {
final image =
getIt.get<SettingsStore>().isDarkTheme ? imageDark : imageLight;
final image = currentTheme.type == ThemeType.dark ? imageDark : imageLight;
return WillPopScope(
onWillPop: () async => false,
@ -41,7 +49,7 @@ class PreSeedPage extends BasePage {
Padding(
padding: EdgeInsets.only(top: 70, left: 16, right: 16),
child: Text(
S.of(context).pre_seed_description,
S.of(context).pre_seed_description(wordsCount.toString()),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,

View file

@ -1,7 +1,6 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/cupertino.dart';
@ -17,8 +16,8 @@ import 'package:cake_wallet/view_model/wallet_seed_view_model.dart';
class WalletSeedPage extends BasePage {
WalletSeedPage(this.walletSeedViewModel, {@required this.isNewWalletCreated});
static final imageLight = Image.asset('assets/images/crypto_lock_light.png');
static final imageDark = Image.asset('assets/images/crypto_lock.png');
final imageLight = Image.asset('assets/images/crypto_lock_light.png');
final imageDark = Image.asset('assets/images/crypto_lock.png');
@override
String get title => S.current.seed_title;
@ -83,8 +82,7 @@ class WalletSeedPage extends BasePage {
@override
Widget body(BuildContext context) {
final image =
getIt.get<SettingsStore>().isDarkTheme ? imageDark : imageLight;
final image = currentTheme.type == ThemeType.dark ? imageDark : imageLight;
return WillPopScope(onWillPop: () async => false, child: Container(
padding: EdgeInsets.all(24),

View file

@ -1,6 +1,5 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/src/widgets/seed_language_selector.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
@ -15,17 +14,26 @@ class SeedLanguage extends BasePage {
final Function(BuildContext, String) onConfirm;
final walletNameImage = Image.asset('assets/images/wallet_name.png');
final walletNameLightImage =
Image.asset('assets/images/wallet_name_light.png');
@override
String get title => S.current.wallet_list_restore_wallet;
@override
Widget body(BuildContext context) => SeedLanguageForm(onConfirm: onConfirm);
Widget body(BuildContext context) =>
SeedLanguageForm(
onConfirm: onConfirm,
walletImage: currentTheme.type == ThemeType.dark
? walletNameImage : walletNameLightImage);
}
class SeedLanguageForm extends StatefulWidget {
SeedLanguageForm({this.onConfirm});
SeedLanguageForm({this.onConfirm, this.walletImage});
final Function(BuildContext, String) onConfirm;
final Image walletImage;
@override
SeedLanguageFormState createState() => SeedLanguageFormState();
@ -33,18 +41,10 @@ class SeedLanguageForm extends StatefulWidget {
class SeedLanguageFormState extends State<SeedLanguageForm> {
static const aspectRatioImage = 1.22;
final walletNameImage = Image.asset('assets/images/wallet_name.png');
final walletNameLightImage =
Image.asset('assets/images/wallet_name_light.png');
final _languageSelectorKey = GlobalKey<SeedLanguageSelectorState>();
@override
Widget build(BuildContext context) {
final walletImage = getIt.get<SettingsStore>().isDarkTheme
? walletNameImage
: walletNameLightImage;
return Container(
padding: EdgeInsets.only(top: 24),
child: ScrollableWithBottomSection(
@ -55,7 +55,8 @@ class SeedLanguageFormState extends State<SeedLanguageForm> {
padding: EdgeInsets.only(left: 12, right: 12),
child: AspectRatio(
aspectRatio: aspectRatioImage,
child: FittedBox(child: walletImage, fit: BoxFit.fill)),
child: FittedBox(child: widget.walletImage,
fit: BoxFit.fill)),
),
Padding(
padding: EdgeInsets.only(top: 40),

View file

@ -31,6 +31,7 @@ class SendPage extends BasePage {
: _addressController = TextEditingController(),
_cryptoAmountController = TextEditingController(),
_fiatAmountController = TextEditingController(),
_noteController = TextEditingController(),
_formKey = GlobalKey<FormState>(),
_cryptoAmountFocus = FocusNode(),
_fiatAmountFocus = FocusNode(),
@ -46,6 +47,7 @@ class SendPage extends BasePage {
final TextEditingController _addressController;
final TextEditingController _cryptoAmountController;
final TextEditingController _fiatAmountController;
final TextEditingController _noteController;
final GlobalKey<FormState> _formKey;
final FocusNode _cryptoAmountFocus;
final FocusNode _fiatAmountFocus;
@ -83,9 +85,8 @@ class SendPage extends BasePage {
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: isDarkTheme
? Color.fromRGBO(48, 51, 60, 1.0)
: Color.fromRGBO(98, 98, 98, 1.0),
keyboardBarColor: Theme.of(context).accentTextTheme.body2
.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
@ -133,7 +134,8 @@ class SendPage extends BasePage {
if (uri != null) {
address = uri.path;
amount = uri.queryParameters['tx_amount'];
amount = uri.queryParameters['tx_amount'] ??
uri.queryParameters['amount'];
} else {
address = uri.toString();
}
@ -304,6 +306,30 @@ class SendPage extends BasePage {
fontWeight: FontWeight.w500,
fontSize: 14),
)),
Padding(
padding: EdgeInsets.only(top: 20),
child: BaseTextFormField(
controller: _noteController,
keyboardType: TextInputType.multiline,
maxLines: null,
borderColor: Theme.of(context)
.primaryTextTheme
.headline
.color,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
hintText: S.of(context).note_optional,
placeholderTextStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.headline
.decorationColor),
),
),
Observer(
builder: (_) => GestureDetector(
onTap: () =>
@ -313,6 +339,7 @@ class SendPage extends BasePage {
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S
@ -326,23 +353,50 @@ class SendPage extends BasePage {
color: Colors.white)),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
sendViewModel
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
sendViewModel
.estimatedFee
.toString() +
' ' +
sendViewModel
' ' +
sendViewModel
.currency.title,
style: TextStyle(
fontSize: 12,
fontWeight:
style: TextStyle(
fontSize: 12,
fontWeight:
FontWeight.w600,
//color: Theme.of(context).primaryTextTheme.display2.color,
color:
//color: Theme.of(context).primaryTextTheme.display2.color,
color:
Colors.white)),
Padding(
padding:
EdgeInsets.only(top: 5),
child: Text(
sendViewModel
.estimatedFeeFiatAmount
+ ' ' +
sendViewModel
.fiat.title,
style: TextStyle(
fontSize: 12,
fontWeight:
FontWeight.w600,
color: Theme
.of(context)
.primaryTextTheme
.headline
.decorationColor))
),
],
),
Padding(
padding: EdgeInsets.only(
top: 2,
left: 5),
child: Icon(
Icons.arrow_forward_ios,
@ -497,14 +551,8 @@ class SendPage extends BasePage {
}
},
text: S.of(context).send,
color: Theme.of(context)
.accentTextTheme
.subtitle
.decorationColor,
textColor: Theme.of(context)
.accentTextTheme
.headline
.decorationColor,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
isLoading: sendViewModel.state is IsExecutingState ||
sendViewModel.state is TransactionCommitting,
isDisabled:
@ -540,6 +588,14 @@ class SendPage extends BasePage {
}
});
_noteController.addListener(() {
final note = _noteController.text ?? '';
if (note != sendViewModel.note) {
sendViewModel.note = note;
}
});
reaction((_) => sendViewModel.sendAll, (bool all) {
if (all) {
_cryptoAmountController.text = S.current.all;
@ -577,6 +633,12 @@ class SendPage extends BasePage {
}
});
reaction((_) => sendViewModel.note, (String note) {
if (note != _noteController.text) {
_noteController.text = note;
}
});
reaction((_) => sendViewModel.state, (ExecutionState state) {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -602,8 +664,14 @@ class SendPage extends BasePage {
amount: S.of(context).send_amount,
amountValue:
sendViewModel.pendingTransaction.amountFormatted,
fiatAmountValue: sendViewModel.pendingTransactionFiatAmount
+ ' ' + sendViewModel.fiat.title,
fee: S.of(context).send_fee,
feeValue: sendViewModel.pendingTransaction.feeFormatted,
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmount
+ ' ' + sendViewModel.fiat.title,
recipientTitle: S.of(context).recipient_address,
recipientAddress: sendViewModel.address,
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
@ -620,94 +688,17 @@ class SendPage extends BasePage {
}
if (state is TransactionCommitted) {
return Stack(
children: <Widget>[
Container(
color: Theme.of(context).backgroundColor,
child: Center(
child: Image.asset(
'assets/images/birthday_cake.png'),
),
),
Center(
child: Padding(
padding: EdgeInsets.only(
top: 220, left: 24, right: 24),
child: Text(
S.of(context).send_success,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
decoration: TextDecoration.none,
),
),
),
),
Positioned(
left: 24,
right: 24,
bottom: 24,
child: PrimaryButton(
onPressed: () =>
Navigator.of(context).pop(),
text: S.of(context).send_got_it,
color: Theme.of(context)
.accentTextTheme
.body2
.color,
textColor: Colors.white))
],
);
return AlertWithOneAction(
alertTitle: '',
alertContent: S.of(context).send_success(
sendViewModel.currency
.toString()),
buttonText: S.of(context).ok,
buttonAction: () =>
Navigator.of(context).pop());
}
if (state is TransactionCommitting) {
return Stack(
children: <Widget>[
Container(
color: Theme.of(context).backgroundColor,
child: Center(
child: Image.asset(
'assets/images/birthday_cake.png'),
),
),
BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 3.0, sigmaY: 3.0),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.backgroundColor
.withOpacity(0.25)),
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 220),
child: Text(
S.of(context).send_sending,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
decoration: TextDecoration.none,
),
),
),
),
),
)
],
);
}
return Container();
return Offstage();
});
});
},
@ -748,8 +739,9 @@ class SendPage extends BasePage {
}
Future<void> _setTransactionPriority(BuildContext context) async {
final items = TransactionPriority.all;
final items = TransactionPriority.forWalletType(sendViewModel.walletType);
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
final isShowScrollThumb = items.length > 3;
await showPopUp<void>(
builder: (_) => Picker(
@ -759,7 +751,6 @@ class SendPage extends BasePage {
mainAxisAlignment: MainAxisAlignment.center,
onItemSelected: (TransactionPriority priority) =>
sendViewModel.setTransactionPriority(priority),
isAlwaysShowScrollThumb: true,
),
context: context);
}

View file

@ -49,9 +49,8 @@ class SendTemplatePage extends BasePage {
return KeyboardActions(
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: isDarkTheme
? Color.fromRGBO(48, 51, 60, 1.0)
: Color.fromRGBO(98, 98, 98, 1.0),
keyboardBarColor: Theme.of(context).accentTextTheme.body2
.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
@ -113,7 +112,8 @@ class SendTemplatePage extends BasePage {
if (uri != null) {
address = uri.path;
amount = uri.queryParameters['tx_amount'];
amount = uri.queryParameters['tx_amount'] ??
uri.queryParameters['amount'];
} else {
address = uri.toString();
}
@ -246,9 +246,8 @@ class SendTemplatePage extends BasePage {
}
},
text: S.of(context).save,
color: Theme.of(context).accentTextTheme.subtitle.decorationColor,
textColor:
Theme.of(context).accentTextTheme.headline.decorationColor,
color: Colors.green,
textColor: Colors.white,
),
),
));

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/palette.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/src/widgets/base_alert_dialog.dart';
@ -6,8 +7,12 @@ class ConfirmSendingAlert extends BaseAlertDialog {
@required this.alertTitle,
@required this.amount,
@required this.amountValue,
@required this.fiatAmountValue,
@required this.fee,
@required this.feeValue,
@required this.feeFiatAmount,
@required this.recipientTitle,
@required this.recipientAddress,
@required this.leftButtonText,
@required this.rightButtonText,
@required this.actionLeftButton,
@ -18,8 +23,12 @@ class ConfirmSendingAlert extends BaseAlertDialog {
final String alertTitle;
final String amount;
final String amountValue;
final String fiatAmountValue;
final String fee;
final String feeValue;
final String feeFiatAmount;
final String recipientTitle;
final String recipientAddress;
final String leftButtonText;
final String rightButtonText;
final VoidCallback actionLeftButton;
@ -29,74 +38,145 @@ class ConfirmSendingAlert extends BaseAlertDialog {
@override
String get titleText => alertTitle;
@override
bool get isDividerExists => true;
@override
String get leftActionButtonText => leftButtonText;
@override
String get rightActionButtonText => rightButtonText;
@override
VoidCallback get actionLeft => actionLeftButton;
@override
VoidCallback get actionRight => actionRightButton;
@override
bool get barrierDismissible => alertBarrierDismissible;
@override
Widget content(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
amount,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
Text(
amountValue,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
amountValue,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
Text(
fiatAmountValue,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
],
)
],
),
Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
fee,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
Padding(
padding: EdgeInsets.only(top: 16),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
fee,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
),
Text(
feeValue,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
feeValue,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
Text(
feeFiatAmount,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
],
)
],
)
),
Padding(
padding: EdgeInsets.fromLTRB(0, 16, 0, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'$recipientTitle:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.normal,
fontFamily: 'Lato',
color: Theme.of(context).primaryTextTheme.title.color,
decoration: TextDecoration.none,
),
),
)
],
Padding(
padding: EdgeInsets.only(top: 8),
child: Text(
recipientAddress,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
fontFamily: 'Lato',
color: PaletteDark.pigeonBlue,
decoration: TextDecoration.none,
),
)
)
],
),
)
],
);

View file

@ -43,7 +43,6 @@ class SettingsPage extends BasePage {
return SettingsPickerCell<dynamic>(
title: item.title,
selectedItem: item.selectedItem(),
isAlwaysShowScrollThumb: item.isAlwaysShowScrollThumb,
items: item.items,
onItemSelected: (dynamic value) => item.onItemSelected(value),
);

View file

@ -9,8 +9,7 @@ class SettingsPickerCell<ItemType> extends StandardListRow {
{@required String title,
this.selectedItem,
this.items,
this.onItemSelected,
this.isAlwaysShowScrollThumb})
this.onItemSelected})
: super(
title: title,
isSelected: false,
@ -24,7 +23,6 @@ class SettingsPickerCell<ItemType> extends StandardListRow {
selectedAtIndex: selectedAtIndex,
title: S.current.please_select,
mainAxisAlignment: MainAxisAlignment.center,
isAlwaysShowScrollThumb: isAlwaysShowScrollThumb,
onItemSelected: (ItemType item) =>
onItemSelected?.call(item)));
});
@ -32,7 +30,6 @@ class SettingsPickerCell<ItemType> extends StandardListRow {
final ItemType selectedItem;
final List<ItemType> items;
final void Function(ItemType item) onItemSelected;
final bool isAlwaysShowScrollThumb;
@override
Widget buildTrailing(BuildContext context) {

View file

@ -1,82 +1,43 @@
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/trade_details_view_model.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/exchange/trade.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
class TradeDetailsPage extends BasePage {
TradeDetailsPage(this.trade) : _items = [] {
final dateFormat = DateFormatter.withCurrentLocal();
_items.addAll([
StandartListItem(title: S.current.trade_details_id, value: trade.id),
StandartListItem(
title: S.current.trade_details_state,
value: trade.state != null
? trade.state.toString()
: S.current.trade_details_fetching)
]);
if (trade.provider != null) {
_items.add(StandartListItem(
title: S.current.trade_details_provider,
value: trade.provider.toString()));
}
if (trade.createdAt != null) {
_items.add(StandartListItem(
title: S.current.trade_details_created_at,
value: dateFormat.format(trade.createdAt).toString()));
}
if (trade.from != null && trade.to != null) {
_items.add(StandartListItem(
title: S.current.trade_details_pair,
value: '${trade.from.toString()}${trade.to.toString()}'));
}
}
TradeDetailsPage(this.tradeDetailsViewModel);
@override
String get title => S.current.trade_details_title;
final Trade trade;
final List<StandartListItem> _items;
final TradeDetailsViewModel tradeDetailsViewModel;
@override
Widget body(BuildContext context) {
return Container(child: Observer(builder: (_) {
return ListView.separated(
separatorBuilder: (_, __) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).backgroundColor,
child: Container(
height: 1,
color: Theme.of(context)
.primaryTextTheme
.title
.backgroundColor)),
itemCount: _items.length,
itemBuilder: (BuildContext context, int index) {
final item = _items[index];
final isDrawBottom = index == _items.length - 1 ? true : false;
return Observer(builder: (_) {
return SectionStandardList(
sectionCount: 1,
itemCounter: (int _) => tradeDetailsViewModel.items.length,
itemBuilder: (_, __, index) {
final item = tradeDetailsViewModel.items[index];
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: '${item.value}'));
showBar<void>(context, S.of(context).copied_to_clipboard);
showBar<void>(context, S
.of(context)
.copied_to_clipboard);
},
child: StandartListRow(
title: '${item.title}',
value: '${item.value}',
isDrawBottom: isDrawBottom,
title: '${item.title}',
value: '${item.value}'
));
});
}));
});
}
}
}

View file

@ -1,6 +1,6 @@
class StandartListItem {
StandartListItem({this.title, this.value});
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
final String title;
final String value;
class StandartListItem extends TransactionDetailsListItem {
StandartListItem({String title, String value})
: super(title: title, value: value);
}

View file

@ -0,0 +1,8 @@
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
class TextFieldListItem extends TransactionDetailsListItem {
TextFieldListItem({String title, String value, this.onSubmitted})
: super(title: title, value: value);
final Function(String value) onSubmitted;
}

View file

@ -0,0 +1,6 @@
abstract class TransactionDetailsListItem {
TransactionDetailsListItem({this.title, this.value});
final String title;
final String value;
}

View file

@ -1,104 +1,32 @@
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/widgets/textfield_list_row.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:flutter/material.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/monero/monero_transaction_info.dart';
import 'package:cake_wallet/entities/transaction_info.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/utils/date_formatter.dart';
import 'package:hive/hive.dart';
class TransactionDetailsPage extends BasePage {
TransactionDetailsPage(this.transactionInfo, bool showRecipientAddress, Box<TransactionDescription> transactionDescriptionBox)
: _items = [] {
final dateFormat = DateFormatter.withCurrentLocal();
final tx = transactionInfo;
if (tx is MoneroTransactionInfo) {
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(
title: S.current.transaction_details_amount,
value: tx.amountFormatted()),
StandartListItem(
title: S.current.send_fee,
value: tx.feeFormatted())
];
if (showRecipientAddress) {
final recipientAddress = transactionDescriptionBox.values.firstWhere((val) => val.id == transactionInfo.id, orElse: () => null)?.recipientAddress;
if (recipientAddress?.isNotEmpty ?? false) {
items.add(StandartListItem(
title: S.current.transaction_details_recipient_address,
value: recipientAddress));
}
}
if (tx.key?.isNotEmpty ?? null) {
// FIXME: add translation
items.add(StandartListItem(title: 'Transaction Key', value: tx.key));
}
_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: 'Confirmations', value: tx.confirmations?.toString()),
StandartListItem(
title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted())
];
_items.addAll(items);
}
}
TransactionDetailsPage({this.transactionDetailsViewModel});
@override
String get title => S.current.transaction_details_title;
final TransactionInfo transactionInfo;
final List<StandartListItem> _items;
final TransactionDetailsViewModel transactionDetailsViewModel;
@override
Widget body(BuildContext context) {
return Container(
child: ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).backgroundColor,
child: Container(
height: 1,
color:
Theme.of(context).primaryTextTheme.title.backgroundColor,
),
),
itemCount: _items.length,
itemBuilder: (context, index) {
final item = _items[index];
final isDrawBottom = index == _items.length - 1 ? true : false;
return SectionStandardList(
sectionCount: 1,
itemCounter: (int _) => transactionDetailsViewModel.items.length,
itemBuilder: (_, __, index) {
final item = transactionDetailsViewModel.items[index];
if (item is StandartListItem) {
return GestureDetector(
onTap: () {
Clipboard.setData(ClipboardData(text: item.value));
@ -107,10 +35,19 @@ class TransactionDetailsPage extends BasePage {
},
child: StandartListRow(
title: '${item.title}:',
value: item.value,
isDrawBottom: isDrawBottom),
value: item.value),
);
}),
);
}
if (item is TextFieldListItem) {
return TextFieldListRow(
title: item.title,
value: item.value,
onSubmitted: item.onSubmitted,
);
}
return null;
});
}
}

View file

@ -0,0 +1,71 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
class TextFieldListRow extends StatelessWidget {
TextFieldListRow(
{this.title,
this.value,
this.titleFontSize = 14,
this.valueFontSize = 16,
this.onSubmitted}) {
_textController = TextEditingController();
_textController.text = value;
}
final String title;
final String value;
final double titleFontSize;
final double valueFontSize;
final Function(String value) onSubmitted;
TextEditingController _textController;
@override
Widget build(BuildContext context) {
return Container(
width: double.infinity,
color: Theme.of(context).backgroundColor,
child: Padding(
padding:
const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(title,
style: TextStyle(
fontSize: titleFontSize,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme.overline.color),
textAlign: TextAlign.left),
TextField(
controller: _textController,
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.done,
maxLines: null,
textAlign: TextAlign.start,
style: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme.title.color),
decoration: InputDecoration(
isDense: true,
contentPadding: EdgeInsets.only(top: 12, bottom: 0),
hintText: S.of(context).enter_your_note,
hintStyle: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme.overline.color),
border: InputBorder.none
),
onSubmitted: (value) => onSubmitted.call(value),
)
]),
),
);
}
}

View file

@ -1,5 +1,4 @@
import 'package:cake_wallet/src/screens/auth/auth_page.dart';
import 'package:cake_wallet/src/screens/wallet_list/widgets/wallet_menu_alert.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
@ -15,7 +14,6 @@ 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/screens/wallet_list/wallet_menu.dart';
import 'package:flutter_slidable/flutter_slidable.dart';
class WalletListPage extends BasePage {
@ -51,7 +49,7 @@ class WalletListBodyState extends State<WalletListBody> {
final newWalletImage = Image.asset('assets/images/new_wallet.png',
height: 12,
width: 12,
color: Theme.of(context).accentTextTheme.headline.decorationColor);
color: Colors.white);
final restoreWalletImage = Image.asset('assets/images/restore_wallet.png',
height: 12,
width: 12,
@ -165,17 +163,16 @@ class WalletListBodyState extends State<WalletListBody> {
),
bottomSection: Column(children: <Widget>[
PrimaryImageButton(
onPressed: () => _generateNewWallet(),
onPressed: () => Navigator.of(context).pushNamed(Routes.newWalletType),
image: newWalletImage,
text: S.of(context).wallet_list_create_new_wallet,
color: Theme.of(context).accentTextTheme.subtitle.decorationColor,
textColor:
Theme.of(context).accentTextTheme.headline.decorationColor,
color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white,
),
SizedBox(height: 10.0),
PrimaryImageButton(
onPressed: () =>
Navigator.of(context).pushNamed(Routes.restoreWallet),
Navigator.of(context).pushNamed(Routes.restoreWalletType),
image: restoreWalletImage,
text: S.of(context).wallet_list_restore_wallet,
color: Theme.of(context).accentTextTheme.caption.color,

View file

@ -1,5 +1,4 @@
import 'package:cake_wallet/di.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/themes/theme_base.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
@ -23,9 +22,7 @@ class WelcomePage extends BasePage {
@override
Widget body(BuildContext context) {
final welcomeImage = getIt
.get<SettingsStore>()
.isDarkTheme
final welcomeImage = currentTheme.type == ThemeType.dark
? welcomeImageDark : welcomeImageLight;
final newWalletImage = Image.asset('assets/images/new_wallet.png',

View file

@ -2,8 +2,8 @@ import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/entities/contact_record.dart';
import 'package:cake_wallet/entities/qr_scanner.dart';
import 'package:cake_wallet/entities/contact_base.dart';
enum AddressTextFieldOption { paste, qrCode, addressBook }
@ -42,7 +42,7 @@ class AddressTextField extends StatelessWidget {
final Color iconColor;
final TextStyle textStyle;
final TextStyle hintStyle;
FocusNode focusNode;
final FocusNode focusNode;
@override
Widget build(BuildContext context) {
@ -204,7 +204,7 @@ class AddressTextField extends StatelessWidget {
onURIScanned(uri);
}
} catch (e) {
print('Error $e');
print(e.toString());
}
}
@ -212,7 +212,7 @@ class AddressTextField extends StatelessWidget {
final contact = await Navigator.of(context, rootNavigator: true)
.pushNamed(Routes.pickerAddressBook);
if (contact is ContactRecord && contact.address != null) {
if (contact is ContactBase && contact.address != null) {
controller.text = contact.address;
}
}

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