Merged with 4.1.0

This commit is contained in:
Tanner Silva 2021-01-12 19:05:56 -06:00
commit 027e94f524
86 changed files with 2262 additions and 1151 deletions

View file

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

View file

@ -294,14 +294,26 @@ extern "C"
return true; return true;
} }
void load_wallet(char *path, char *password, int32_t nettype) bool load_wallet(char *path, char *password, int32_t nettype)
{ {
nice(19); nice(19);
Monero::NetworkType networkType = static_cast<Monero::NetworkType>(nettype); 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); 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) bool is_wallet_exist(char *path)
{ {
return Monero::WalletManagerFactory::getWalletManager()->walletExists(std::string(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 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(); 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 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(); typedef GetFilename = Pointer<Utf8> Function();

View file

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

View file

@ -354,7 +354,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 7; CURRENT_PROJECT_VERSION = 17;
DEVELOPMENT_TEAM = 32J6BB6VUS; DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -494,7 +494,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 7; CURRENT_PROJECT_VERSION = 17;
DEVELOPMENT_TEAM = 32J6BB6VUS; DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (
@ -528,7 +528,7 @@
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 7; CURRENT_PROJECT_VERSION = 17;
DEVELOPMENT_TEAM = 32J6BB6VUS; DEVELOPMENT_TEAM = 32J6BB6VUS;
ENABLE_BITCODE = NO; ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = ( FRAMEWORK_SEARCH_PATHS = (

View file

@ -1,19 +1,25 @@
import 'dart:convert'; import 'dart:convert';
import 'package:quiver/core.dart';
class BitcoinAddressRecord { class BitcoinAddressRecord {
BitcoinAddressRecord(this.address, {this.label, this.index}); BitcoinAddressRecord(this.address, {this.index});
factory BitcoinAddressRecord.fromJSON(String jsonSource) { factory BitcoinAddressRecord.fromJSON(String jsonSource) {
final decoded = json.decode(jsonSource) as Map; final decoded = json.decode(jsonSource) as Map;
return BitcoinAddressRecord(decoded['address'] as String, 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; final String address;
int index; int index;
String label;
String toJSON() => @override
json.encode({'label': label, 'address': address, 'index': index}); 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:intl/intl.dart';
import 'package:cake_wallet/entities/crypto_amount_format.dart'; import 'package:cake_wallet/entities/crypto_amount_format.dart';
@ -7,10 +9,32 @@ final bitcoinAmountFormat = NumberFormat()
..maximumFractionDigits = bitcoinAmountLength ..maximumFractionDigits = bitcoinAmountLength
..minimumFractionDigits = 1; ..minimumFractionDigits = 1;
String bitcoinAmountToString({int amount}) => String bitcoinAmountToString({int amount}) => bitcoinAmountFormat.format(
bitcoinAmountFormat.format(cryptoAmountToDouble(amount: amount, divider: bitcoinAmountDivider)); 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) => int stringDoubleToBitcoinAmount(String amount) {
(amount * bitcoinAmountDivider).toInt(); 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

@ -1,16 +1,12 @@
import 'dart:convert'; import 'dart:convert';
import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart'; import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/entities/balance.dart'; import 'package:cake_wallet/entities/balance.dart';
class BitcoinBalance extends Balance { class BitcoinBalance extends Balance {
const BitcoinBalance({@required this.confirmed, @required this.unconfirmed}) const BitcoinBalance({@required this.confirmed, @required this.unconfirmed})
: super(const [ : super(confirmed, unconfirmed);
BalanceDisplayMode.availableBalance,
BalanceDisplayMode.fullBalance
]);
factory BitcoinBalance.fromJSON(String jsonSource) { factory BitcoinBalance.fromJSON(String jsonSource) {
if (jsonSource == null) { if (jsonSource == null) {
@ -27,31 +23,12 @@ class BitcoinBalance extends Balance {
final int confirmed; final int confirmed;
final int unconfirmed; final int unconfirmed;
int get total => @override
confirmed + (unconfirmed < 0 ? unconfirmed * -1 : unconfirmed); String get formattedAvailableBalance => bitcoinAmountToString(amount: confirmed);
int get availableBalance => confirmed + (unconfirmed < 0 ? unconfirmed : 0);
String get confirmedFormatted => bitcoinAmountToString(amount: confirmed);
String get unconfirmedFormatted => bitcoinAmountToString(amount: unconfirmed);
String get totalFormatted => bitcoinAmountToString(amount: total);
String get availableBalanceFormatted =>
bitcoinAmountToString(amount: availableBalance);
@override @override
String formattedBalance(BalanceDisplayMode mode) { String get formattedAdditionalBalance =>
switch (mode) { bitcoinAmountToString(amount: unconfirmed);
case BalanceDisplayMode.fullBalance:
return totalFormatted;
case BalanceDisplayMode.availableBalance:
return availableBalanceFormatted;
default:
return null;
}
}
String toJSON() => String toJSON() =>
json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed}); json.encode({'confirmed': confirmed, 'unconfirmed': unconfirmed});

View file

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

View file

@ -47,7 +47,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final out = vin['tx']['vout'][vout] as Map; final out = vin['tx']['vout'][vout] as Map;
final outAddresses = final outAddresses =
(out['scriptPubKey']['addresses'] as List<Object>)?.toSet(); (out['scriptPubKey']['addresses'] as List<Object>)?.toSet();
inputsAmount += doubleToBitcoinAmount(out['value'] as double ?? 0); inputsAmount += stringDoubleToBitcoinAmount((out['value'] as double ?? 0).toString());
if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) { if (outAddresses?.intersection(addressesSet)?.isNotEmpty ?? false) {
direction = TransactionDirection.outgoing; direction = TransactionDirection.outgoing;
@ -58,7 +58,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final outAddresses = final outAddresses =
out['scriptPubKey']['addresses'] as List<Object> ?? []; out['scriptPubKey']['addresses'] as List<Object> ?? [];
final ntrs = outAddresses.toSet().intersection(addressesSet); final ntrs = outAddresses.toSet().intersection(addressesSet);
final value = doubleToBitcoinAmount(out['value'] as double ?? 0.0); final value = stringDoubleToBitcoinAmount((out['value'] as double ?? 0.0).toString());
totalOutAmount += value; totalOutAmount += value;
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) || if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||

View file

@ -47,7 +47,7 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
network: bitcoin.bitcoin) network: bitcoin.bitcoin)
.derivePath("m/0'/0"), .derivePath("m/0'/0"),
addresses = initialAddresses != null addresses = initialAddresses != null
? ObservableList<BitcoinAddressRecord>.of(initialAddresses) ? ObservableList<BitcoinAddressRecord>.of(initialAddresses.toSet())
: ObservableList<BitcoinAddressRecord>(), : ObservableList<BitcoinAddressRecord>(),
syncStatus = NotConnectedSyncStatus(), syncStatus = NotConnectedSyncStatus(),
_password = password, _password = password,
@ -116,6 +116,19 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
walletInfo: walletInfo); 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 @override
final BitcoinTransactionHistory transactionHistory; final BitcoinTransactionHistory transactionHistory;
final String path; final String path;
@ -154,21 +167,31 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject; Map<String, BehaviorSubject<Object>> _scripthashesUpdateSubject;
Future<void> init() async { Future<void> init() async {
if (addresses.isEmpty) { if (addresses.isEmpty || addresses.length < 33) {
final index = 0; final addressesCount = 33 - addresses.length;
addresses await generateNewAddresses(addressesCount, startIndex: addresses.length);
.add(BitcoinAddressRecord(_getAddress(index: index), index: index));
} }
address = addresses.first.address; address = addresses[_accountIndex].address;
transactionHistory.wallet = this; transactionHistory.wallet = this;
await transactionHistory.init(); 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; _accountIndex += 1;
final address = BitcoinAddressRecord(_getAddress(index: _accountIndex), final address = BitcoinAddressRecord(_getAddress(index: _accountIndex),
index: _accountIndex, label: label); index: _accountIndex);
addresses.add(address); addresses.add(address);
await save(); await save();
@ -176,13 +199,12 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
return address; return address;
} }
Future<List<BitcoinAddressRecord>> generateNewAddresses(int count) async { Future<List<BitcoinAddressRecord>> generateNewAddresses(int count,
{int startIndex = 0}) async {
final list = <BitcoinAddressRecord>[]; final list = <BitcoinAddressRecord>[];
for (var i = 0; i < count; i++) { for (var i = startIndex; i < count + startIndex; i++) {
_accountIndex += 1; final address = BitcoinAddressRecord(_getAddress(index: i), index: i);
final address = BitcoinAddressRecord(_getAddress(index: _accountIndex),
index: _accountIndex, label: null);
list.add(address); list.add(address);
} }
@ -192,10 +214,9 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
return list; return list;
} }
Future<void> updateAddress(String address, {String label}) async { Future<void> updateAddress(String address) async {
for (final addr in addresses) { for (final addr in addresses) {
if (addr.address == address) { if (addr.address == address) {
addr.label = label;
await save(); await save();
break; break;
} }
@ -243,16 +264,20 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
Object credentials) async { Object credentials) async {
final transactionCredentials = credentials as BitcoinTransactionCredentials; final transactionCredentials = credentials as BitcoinTransactionCredentials;
final inputs = <BitcoinUnspent>[]; final inputs = <BitcoinUnspent>[];
final fee = _feeMultiplier(transactionCredentials.priority); final fee = feeAmountForPriority(transactionCredentials.priority);
final amount = transactionCredentials.amount != null final amount = transactionCredentials.amount != null
? doubleToBitcoinAmount(transactionCredentials.amount) ? stringDoubleToBitcoinAmount(transactionCredentials.amount)
: balance.total - fee; : balance.confirmed - fee;
final totalAmount = amount + fee; final totalAmount = amount + fee;
final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin); final txb = bitcoin.TransactionBuilder(network: bitcoin.bitcoin);
var leftAmount = totalAmount;
final changeAddress = address; final changeAddress = address;
var leftAmount = totalAmount;
var totalInputAmount = 0; var totalInputAmount = 0;
if (totalAmount > balance.confirmed) {
throw BitcoinTransactionWrongBalanceException();
}
final unspent = addresses.map((address) => eclient final unspent = addresses.map((address) => eclient
.getListUnspentWithAddress(address.address) .getListUnspentWithAddress(address.address)
.then((unspent) => unspent .then((unspent) => unspent
@ -334,7 +359,7 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
@override @override
double calculateEstimatedFee(TransactionPriority priority) => double calculateEstimatedFee(TransactionPriority priority) =>
bitcoinAmountToDouble(amount: _feeMultiplier(priority)); bitcoinAmountToDouble(amount: feeAmountForPriority(priority));
@override @override
Future<void> save() async { Future<void> save() async {
@ -386,17 +411,4 @@ abstract class BitcoinWalletBase extends WalletBase<BitcoinBalance> with Store {
String _getAddress({@required int index}) => String _getAddress({@required int index}) =>
generateAddress(hd: hd, index: index); generateAddress(hd: hd, index: index);
int _feeMultiplier(TransactionPriority priority) {
switch (priority) {
case TransactionPriority.slow:
return 6000;
case TransactionPriority.regular:
return 22080;
case TransactionPriority.fast:
return 24000;
default:
return 0;
}
}
} }

View file

@ -89,7 +89,6 @@ class BitcoinWalletService extends WalletService<
walletInfo: credentials.walletInfo); walletInfo: credentials.walletInfo);
await wallet.save(); await wallet.save();
await wallet.init(); await wallet.init();
await wallet.generateNewAddresses(32);
return wallet; return wallet;
} }

View file

@ -23,8 +23,7 @@ String jsonrpcparams(List<Object> params) {
String jsonrpc( String jsonrpc(
{String method, List<Object> params, int id, double version = 2.0}) => {String method, List<Object> params, int id, double version = 2.0}) =>
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json '{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
.encode(params)}}\n';
class SocketTask { class SocketTask {
SocketTask({this.completer, this.isSubscription, this.subject}); SocketTask({this.completer, this.isSubscription, this.subject});
@ -38,7 +37,8 @@ class ElectrumClient {
ElectrumClient() ElectrumClient()
: _id = 0, : _id = 0,
_isConnected = false, _isConnected = false,
_tasks = {}; _tasks = {},
unterminatedString = '';
static const connectionTimeout = Duration(seconds: 5); static const connectionTimeout = Duration(seconds: 5);
static const aliveTimerDuration = Duration(seconds: 2); static const aliveTimerDuration = Duration(seconds: 2);
@ -75,24 +75,43 @@ class ElectrumClient {
socket.listen((Uint8List event) { socket.listen((Uint8List event) {
try { try {
_handleResponse(utf8.decode(event.toList())); final response =
json.decode(utf8.decode(event.toList())) as Map<String, Object>;
_handleResponse(response);
} on FormatException catch (e) { } on FormatException catch (e) {
final msg = e.message.toLowerCase(); final msg = e.message.toLowerCase();
if (msg == 'Unterminated string'.toLowerCase()) { if (e.source is String) {
unterminatedString = e.source as String;
}
if (msg == 'Unexpected character'.toLowerCase()) {
unterminatedString += e.source as String; unterminatedString += e.source as String;
} }
if (msg.contains("not a subtype of type")) {
unterminatedString += e.source as String;
return;
}
if (isJSONStringCorrect(unterminatedString)) { if (isJSONStringCorrect(unterminatedString)) {
_handleResponse(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; unterminatedString = null;
} }
} catch (e) { } catch (e) {
print(e); print(e.toString());
} }
}, onError: (Object error) { }, onError: (Object error) {
print(error.toString()); print(error.toString());
@ -239,7 +258,7 @@ class ElectrumClient {
if (result is String) { if (result is String) {
return result; return result;
} }
print(result);
return ''; return '';
}); });
@ -275,7 +294,8 @@ class ElectrumClient {
params: [scripthash]); params: [scripthash]);
} }
BehaviorSubject<T> subscribe<T>({@required String id, BehaviorSubject<T> subscribe<T>(
{@required String id,
@required String method, @required String method,
List<Object> params = const []}) { List<Object> params = const []}) {
final subscription = BehaviorSubject<T>(); final subscription = BehaviorSubject<T>();
@ -296,7 +316,8 @@ class ElectrumClient {
return completer.future; return completer.future;
} }
Future<dynamic> callWithTimeout({String method, Future<dynamic> callWithTimeout(
{String method,
List<Object> params = const [], List<Object> params = const [],
int timeout = 2000}) async { int timeout = 2000}) async {
final completer = Completer<dynamic>(); final completer = Completer<dynamic>();
@ -325,8 +346,7 @@ class ElectrumClient {
onConnectionStatusChange = null; onConnectionStatusChange = null;
} }
void _regisryTask(int id, Completer completer) => void _regisryTask(int id, Completer completer) => _tasks[id.toString()] =
_tasks[id.toString()] =
SocketTask(completer: completer, isSubscription: false); SocketTask(completer: completer, isSubscription: false);
void _regisrySubscription(String id, BehaviorSubject subject) => void _regisrySubscription(String id, BehaviorSubject subject) =>
@ -371,22 +391,20 @@ class ElectrumClient {
_isConnected = isConnected; _isConnected = isConnected;
} }
void _handleResponse(String response) { void _handleResponse(Map<String, Object> response) {
print('Response: $response'); final method = response['method'];
final jsoned = json.decode(response) as Map<String, Object>; final id = response['id'] as String;
// print(jsoned); final result = response['result'];
final method = jsoned['method'];
final id = jsoned['id'] as String;
final result = jsoned['result'];
if (method is String) { if (method is String) {
_methodHandler(method: method, request: jsoned); _methodHandler(method: method, request: response);
return; return;
} }
_finish(id, result); _finish(id, result);
} }
} }
// FIXME: move me // FIXME: move me
bool isJSONStringCorrect(String source) { bool isJSONStringCorrect(String source) {
try { try {

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/balance.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:cake_wallet/entities/wallet_info.dart'; import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/core/pending_transaction.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/node.dart';
import 'package:cake_wallet/entities/wallet_type.dart'; import 'package:cake_wallet/entities/wallet_type.dart';
abstract class WalletBase<BalaceType> { abstract class WalletBase<BalaceType extends Balance> {
WalletBase(this.walletInfo); WalletBase(this.walletInfo);
static String idFor(String name, WalletType type) => static String idFor(String name, WalletType type) =>

View file

@ -28,6 +28,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/change_language.dart';
import 'package:cake_wallet/src/screens/settings/settings.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/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/transaction_details/transaction_details_page.dart';
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
@ -55,6 +56,8 @@ import 'package:cake_wallet/view_model/node_list/node_list_view_model.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
import 'package:cake_wallet/view_model/rescan_view_model.dart'; import 'package:cake_wallet/view_model/rescan_view_model.dart';
import 'package:cake_wallet/view_model/setup_pin_code_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/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/auth_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
@ -327,7 +330,8 @@ Future setup(
(ContactRecord contact, _) => (ContactRecord contact, _) =>
ContactViewModel(contactSource, contact: contact)); ContactViewModel(contactSource, contact: contact));
getIt.registerFactory(() => ContactListViewModel(contactSource)); getIt.registerFactory(
() => ContactListViewModel(contactSource, walletInfoSource));
getIt.registerFactoryParam<ContactListPage, bool, void>( getIt.registerFactoryParam<ContactListPage, bool, void>(
(bool isEditable, _) => ContactListPage(getIt.get<ContactListViewModel>(), (bool isEditable, _) => ContactListPage(getIt.get<ContactListViewModel>(),
@ -355,7 +359,8 @@ Future setup(
getIt.get<AppStore>().wallet, getIt.get<AppStore>().wallet,
tradesSource, tradesSource,
getIt.get<ExchangeTemplateStore>(), getIt.get<ExchangeTemplateStore>(),
getIt.get<TradesStore>())); getIt.get<TradesStore>(),
getIt.get<AppStore>().settingsStore));
getIt.registerFactory(() => ExchangeTradeViewModel( getIt.registerFactory(() => ExchangeTradeViewModel(
wallet: getIt.get<AppStore>().wallet, wallet: getIt.get<AppStore>().wallet,
@ -415,11 +420,17 @@ Future setup(
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) => getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) =>
WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: 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>( getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => TransactionDetailsPage( (TransactionInfo transactionInfo, _) => TransactionDetailsPage(
transactionInfo, transactionDetailsViewModel:
getIt.get<SettingsStore>().shouldSaveRecipientAddress, getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
transactionDescriptionBox));
getIt.registerFactoryParam<NewWalletTypePage, getIt.registerFactoryParam<NewWalletTypePage,
void Function(BuildContext, WalletType), bool>( void Function(BuildContext, WalletType), bool>(
@ -428,4 +439,10 @@ Future setup(
getIt.registerFactoryParam<PreSeedPage, WalletType, void>( getIt.registerFactoryParam<PreSeedPage, WalletType, void>(
(WalletType type, _) => PreSeedPage(type)); (WalletType type, _) => PreSeedPage(type));
getIt.registerFactoryParam<TradeDetailsViewModel, Trade, void>((trade, _) =>
TradeDetailsViewModel(tradeForDetails: trade, trades: tradesSource));
getIt.registerFactoryParam<TradeDetailsPage, Trade, void>((Trade trade, _) =>
TradeDetailsPage(getIt.get<TradeDetailsViewModel>(param1: trade)));
} }

View file

@ -1,9 +1,11 @@
import 'package:cake_wallet/entities/balance_display_mode.dart';
abstract class Balance { abstract class Balance {
const Balance(this.availableModes); const Balance(this.available, this.additional);
final List<BalanceDisplayMode> availableModes; final int available;
String formattedBalance(BalanceDisplayMode mode); 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); : super(title: title, raw: raw);
static const all = [ static const all = [
BalanceDisplayMode.fullBalance, BalanceDisplayMode.hiddenBalance,
BalanceDisplayMode.availableBalance, BalanceDisplayMode.displayableBalance,
BalanceDisplayMode.hiddenBalance
]; ];
static const fullBalance = BalanceDisplayMode(raw: 0, title: 'Full Balance'); static const fullBalance = BalanceDisplayMode(raw: 0, title: 'Full Balance');
static const availableBalance = static const availableBalance =
BalanceDisplayMode(raw: 1, title: 'Available Balance'); BalanceDisplayMode(raw: 1, title: 'Available Balance');
static const hiddenBalance = static const hiddenBalance =
BalanceDisplayMode(raw: 2, title: 'Hidden Balance'); BalanceDisplayMode(raw: 2, title: 'Hidden Balance');
static const displayableBalance =
BalanceDisplayMode(raw: 3, title: 'Displayable Balance');
static BalanceDisplayMode deserialize({int raw}) { static BalanceDisplayMode deserialize({int raw}) {
switch (raw) { switch (raw) {
@ -25,6 +26,8 @@ class BalanceDisplayMode extends EnumerableItem<int> with Serializable<int> {
return availableBalance; return availableBalance;
case 2: case 2:
return hiddenBalance; return hiddenBalance;
case 3:
return displayableBalance;
default: default:
return null; return null;
} }
@ -39,6 +42,8 @@ class BalanceDisplayMode extends EnumerableItem<int> with Serializable<int> {
return S.current.xmr_available_balance; return S.current.xmr_available_balance;
case BalanceDisplayMode.hiddenBalance: case BalanceDisplayMode.hiddenBalance:
return S.current.xmr_hidden; return S.current.xmr_hidden;
case BalanceDisplayMode.displayableBalance:
return S.current.displayable;
default: default:
return ''; 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/contact.dart';
import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/entities/record.dart'; import 'package:cake_wallet/entities/record.dart';
import 'package:cake_wallet/entities/contact_base.dart';
part 'contact_record.g.dart'; part 'contact_record.g.dart';
class ContactRecord = ContactRecordBase with _$ContactRecord; 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) ContactRecordBase(Box<Contact> source, Contact original)
: super(source, original); : super(source, original);
@override
@observable @observable
String name; String name;
@override
@observable @observable
String address; String address;
@override
@observable @observable
CryptoCurrency type; 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:flutter/foundation.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
@ -73,6 +77,14 @@ Future defaultSettingsMigration(
sharedPreferences: sharedPreferences, nodes: nodes); sharedPreferences: sharedPreferences, nodes: nodes);
break; break;
case 5:
await addAddressesForMoneroWallets(walletInfoSource);
break;
case 6:
await updateDisplayModes(sharedPreferences);
break;
default: default:
break; break;
} }
@ -120,7 +132,7 @@ Future<void> changeMoneroCurrentNodeToDefault(
} }
Node getBitcoinDefaultElectrumServer({@required Box<Node> nodes}) { Node getBitcoinDefaultElectrumServer({@required Box<Node> nodes}) {
final uri = 'electrumx.cakewallet.com:50002'; final uri = 'electrum.cakewallet.com:50002';
return nodes.values return nodes.values
.firstWhere((Node node) => node.uri == uri, orElse: () => null) ?? .firstWhere((Node node) => node.uri == uri, orElse: () => null) ??
@ -189,3 +201,34 @@ Future<void> addBitcoinElectrumServerList({@required Box<Node> nodes}) async {
final serverList = await loadElectrumServerList(); final serverList = await loadElectrumServerList();
await nodes.addAll(serverList); 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

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

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) @HiveType(typeId: 4)
class WalletInfo extends HiveObject { class WalletInfo extends HiveObject {
WalletInfo(this.id, this.name, this.type, this.isRecovery, this.restoreHeight, 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( factory WalletInfo.external(
{@required String id, {@required String id,
@ -17,9 +17,10 @@ class WalletInfo extends HiveObject {
@required int restoreHeight, @required int restoreHeight,
@required DateTime date, @required DateTime date,
@required String dirPath, @required String dirPath,
@required String path}) { @required String path,
@required String address}) {
return WalletInfo(id, name, type, isRecovery, restoreHeight, return WalletInfo(id, name, type, isRecovery, restoreHeight,
date.millisecondsSinceEpoch ?? 0, dirPath, path); date.millisecondsSinceEpoch ?? 0, dirPath, path, address);
} }
static const boxName = 'WalletInfo'; static const boxName = 'WalletInfo';
@ -48,5 +49,8 @@ class WalletInfo extends HiveObject {
@HiveField(7) @HiveField(7)
String path; String path;
@HiveField(8)
String address;
DateTime get date => DateTime.fromMillisecondsSinceEpoch(timestamp); 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'; import 'package:hive/hive.dart';
part 'wallet_type.g.dart'; part 'wallet_type.g.dart';
@ -59,3 +60,14 @@ String walletTypeToDisplayName(WalletType type) {
return ''; return '';
} }
} }
CryptoCurrency walletTypeToCryptoCurrency(WalletType type) {
switch (type) {
case WalletType.monero:
return CryptoCurrency.xmr;
case WalletType.bitcoin:
return CryptoCurrency.btc;
default:
return null;
}
}

File diff suppressed because it is too large Load diff

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/bitcoin/bitcoin_address_record.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -69,7 +70,7 @@ void main() async {
templates: templates, templates: templates,
exchangeTemplates: exchangeTemplates, exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions, transactionDescriptions: transactionDescriptions,
initialMigrationVersion: 4); initialMigrationVersion: 5);
runApp(App()); runApp(App());
} catch (e) { } catch (e) {
runApp(MaterialApp( runApp(MaterialApp(
@ -84,8 +85,8 @@ void main() async {
))))); )))));
} }
} }
Future<void> initialSetup(
{@required SharedPreferences sharedPreferences, Future<void> initialSetup({@required SharedPreferences sharedPreferences,
@required Box<Node> nodes, @required Box<Node> nodes,
@required Box<WalletInfo> walletInfoSource, @required Box<WalletInfo> walletInfoSource,
@required Box<Contact> contactSource, @required Box<Contact> contactSource,
@ -94,7 +95,7 @@ Future<void> initialSetup(
@required Box<Template> templates, @required Box<Template> templates,
@required Box<ExchangeTemplate> exchangeTemplates, @required Box<ExchangeTemplate> exchangeTemplates,
@required Box<TransactionDescription> transactionDescriptions, @required Box<TransactionDescription> transactionDescriptions,
int initialMigrationVersion = 5}) async { int initialMigrationVersion = 6}) async {
await defaultSettingsMigration( await defaultSettingsMigration(
version: initialMigrationVersion, version: initialMigrationVersion,
sharedPreferences: sharedPreferences, sharedPreferences: sharedPreferences,
@ -122,7 +123,9 @@ class App extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final settingsStore = getIt.get<AppStore>().settingsStore; final settingsStore = getIt
.get<AppStore>()
.settingsStore;
final statusBarColor = Colors.transparent; final statusBarColor = Colors.transparent;
final authenticationStore = getIt.get<AuthenticationStore>(); final authenticationStore = getIt.get<AuthenticationStore>();
final initialRoute = authenticationStore.state == AuthenticationState.denied final initialRoute = authenticationStore.state == AuthenticationState.denied

View file

@ -8,20 +8,15 @@ class MoneroBalance extends Balance {
: formattedFullBalance = moneroAmountToString(amount: fullBalance), : formattedFullBalance = moneroAmountToString(amount: fullBalance),
formattedUnlockedBalance = formattedUnlockedBalance =
moneroAmountToString(amount: unlockedBalance), moneroAmountToString(amount: unlockedBalance),
super(const [ super(unlockedBalance, fullBalance);
BalanceDisplayMode.availableBalance,
BalanceDisplayMode.fullBalance
]);
MoneroBalance.fromString( MoneroBalance.fromString(
{@required this.formattedFullBalance, {@required this.formattedFullBalance,
@required this.formattedUnlockedBalance}) @required this.formattedUnlockedBalance})
: fullBalance = moneroParseAmount(amount: formattedFullBalance), : fullBalance = moneroParseAmount(amount: formattedFullBalance),
unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance), unlockedBalance = moneroParseAmount(amount: formattedUnlockedBalance),
super(const [ super(moneroParseAmount(amount: formattedUnlockedBalance),
BalanceDisplayMode.availableBalance, moneroParseAmount(amount: formattedFullBalance));
BalanceDisplayMode.fullBalance
]);
final int fullBalance; final int fullBalance;
final int unlockedBalance; final int unlockedBalance;
@ -29,14 +24,8 @@ class MoneroBalance extends Balance {
final String formattedUnlockedBalance; final String formattedUnlockedBalance;
@override @override
String formattedBalance(BalanceDisplayMode mode) { String get formattedAvailableBalance => formattedUnlockedBalance;
switch (mode) {
case BalanceDisplayMode.fullBalance: @override
return formattedFullBalance; String get formattedAdditionalBalance => formattedFullBalance;
case BalanceDisplayMode.availableBalance:
return formattedUnlockedBalance;
default:
return null;
}
}
} }

View file

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

View file

@ -10,7 +10,7 @@ ReactionDisposer _onAuthenticationStateChange;
dynamic loginError; dynamic loginError;
void startAuthenticationStateChange(AuthenticationStore authenticationStore, void startAuthenticationStateChange(AuthenticationStore authenticationStore,
@required GlobalKey<NavigatorState> navigatorKey) { GlobalKey<NavigatorState> navigatorKey) {
_onAuthenticationStateChange ??= autorun((_) async { _onAuthenticationStateChange ??= autorun((_) async {
final state = authenticationStore.state; final state = authenticationStore.state;

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/balance.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:shared_preferences/shared_preferences.dart'; import 'package:shared_preferences/shared_preferences.dart';
import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/di.dart';
@ -19,7 +20,7 @@ void startCurrentWalletChangeReaction(AppStore appStore,
_onCurrentWalletChangeReaction?.reaction?.dispose(); _onCurrentWalletChangeReaction?.reaction?.dispose();
_onCurrentWalletChangeReaction = _onCurrentWalletChangeReaction =
reaction((_) => appStore.wallet, (WalletBase wallet) async { reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async {
try { try {
final node = settingsStore.getCurrentNode(wallet.type); final node = settingsStore.getCurrentNode(wallet.type);
startWalletSyncStatusChangeReaction(wallet); startWalletSyncStatusChangeReaction(wallet);
@ -30,16 +31,25 @@ void startCurrentWalletChangeReaction(AppStore appStore,
await getIt.get<SharedPreferences>().setInt( await getIt.get<SharedPreferences>().setInt(
PreferencesKey.currentWalletType, serializeToInt(wallet.type)); PreferencesKey.currentWalletType, serializeToInt(wallet.type));
await wallet.connectToNode(node: node); await wallet.connectToNode(node: node);
if (wallet.walletInfo.address?.isEmpty ?? true) {
wallet.walletInfo.address = wallet.address;
if (wallet.walletInfo.isInBox) {
await wallet.walletInfo.save();
}
}
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
} }
}); });
_onCurrentWalletChangeFiatRateUpdateReaction = _onCurrentWalletChangeFiatRateUpdateReaction =
reaction((_) => appStore.wallet, (WalletBase wallet) async { reaction((_) => appStore.wallet, (WalletBase<Balance> wallet) async {
try { try {
fiatConversionStore.prices[wallet.currency] = 0; fiatConversionStore.prices[wallet.currency] = 0;
fiatConversionStore.prices[wallet.currency] = await FiatConversionService.fetchPrice( fiatConversionStore.prices[wallet.currency] =
await FiatConversionService.fetchPrice(
wallet.currency, settingsStore.fiatCurrency); wallet.currency, settingsStore.fiatCurrency);
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());

View file

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

View file

@ -279,7 +279,8 @@ Route<dynamic> createRoute(RouteSettings settings) {
case Routes.tradeDetails: case Routes.tradeDetails:
return MaterialPageRoute<void>( return MaterialPageRoute<void>(
builder: (_) => TradeDetailsPage(settings.arguments as Trade)); builder: (_) =>
getIt.get<TradeDetailsPage>(param1: settings.arguments as Trade));
case Routes.restoreWalletFromSeedDetails: case Routes.restoreWalletFromSeedDetails:
final args = settings.arguments as List; final args = settings.arguments as List;

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_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -61,16 +62,71 @@ class ContactListPage extends BasePage {
padding: EdgeInsets.only(top: 20.0, bottom: 20.0), padding: EdgeInsets.only(top: 20.0, bottom: 20.0),
child: Observer( child: Observer(
builder: (_) { builder: (_) {
return contactListViewModel.contacts.isNotEmpty return SectionStandardList(
? SectionStandardList(
sectionCount: 1,
context: context, context: context,
itemCounter: (int sectionIndex) => sectionCount: 2,
contactListViewModel.contacts.length, sectionTitleBuilder: (_, int sectionIndex) {
var title = 'Contacts';
if (sectionIndex == 0) {
title = 'My wallets';
}
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) { itemBuilder: (_, sectionIndex, index) {
if (sectionIndex == 0) {
final walletInfo = contactListViewModel.walletContacts[index];
return generateRaw(context, walletInfo);
}
final contact = contactListViewModel.contacts[index]; final contact = contactListViewModel.contacts[index];
final content = generateRaw(context, contact);
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); final image = _getCurrencyImage(contact.type);
final content = GestureDetector(
return GestureDetector(
onTap: () async { onTap: () async {
if (!isEditable) { if (!isEditable) {
Navigator.of(context).pop(contact); Navigator.of(context).pop(contact);
@ -81,16 +137,14 @@ class ContactListPage extends BasePage {
context, contact.name, contact.address); context, contact.name, contact.address);
if (isCopied != null && isCopied) { if (isCopied != null && isCopied) {
await Clipboard.setData( await Clipboard.setData(ClipboardData(text: contact.address));
ClipboardData(text: contact.address)); await showBar<void>(context, S.of(context).copied_to_clipboard);
await showBar<void>(
context, S.of(context).copied_to_clipboard);
} }
}, },
child: Container( child: Container(
color: Colors.transparent, color: Colors.transparent,
padding: const EdgeInsets.only( padding:
left: 24, top: 16, bottom: 16, right: 24), const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24),
child: Row( child: Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
@ -106,60 +160,13 @@ class ContactListPage extends BasePage {
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.normal, fontWeight: FontWeight.normal,
color: Theme.of(context) color: Theme.of(context).primaryTextTheme.title.color),
.primaryTextTheme
.title
.color),
), ),
) )
], ],
), ),
), ),
); );
return !isEditable
? content
: Slidable(
key: Key('${contact.key}'),
actionPane: SlidableDrawerActionPane(),
child: content,
secondaryActions: <Widget>[
IconSlideAction(
caption: S.of(context).edit,
color: Colors.blue,
icon: Icons.edit,
onTap: () async =>
await Navigator.of(context).pushNamed(
Routes.addressBookAddContact,
arguments: contact),
),
IconSlideAction(
caption: S.of(context).delete,
color: Colors.red,
icon: CupertinoIcons.delete,
onTap: () async {
final isDelete =
await showAlertDialog(context) ??
false;
if (isDelete) {
await contactListViewModel
.delete(contact);
}
},
),
]);
},
)
: Center(
child: Text(
S.of(context).placeholder_contacts,
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey, fontSize: 14),
),
);
},
));
} }
Image _getCurrencyImage(CryptoCurrency currency) { Image _getCurrencyImage(CryptoCurrency currency) {

View file

@ -1,35 +1,65 @@
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:flutter/material.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.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/src/screens/receive/widgets/qr_widget.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
class AddressPage extends StatelessWidget { class AddressPage extends StatelessWidget {
AddressPage({@required this.addressListViewModel}); AddressPage({@required this.addressListViewModel})
: _cryptoAmountFocus = FocusNode();
final WalletAddressListViewModel addressListViewModel; final WalletAddressListViewModel addressListViewModel;
final FocusNode _cryptoAmountFocus;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( 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), padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Column( child: Column(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
child: Center( child: Center(
child: QRWidget(addressListViewModel: addressListViewModel), child: QRWidget(
addressListViewModel: addressListViewModel,
amountTextFieldFocusNode: _cryptoAmountFocus,
isAmountFieldShow: !addressListViewModel.hasAccounts),
)), )),
GestureDetector( Observer(builder: (_) {
onTap: () => Navigator.of(context).pushNamed(Routes.receive), return addressListViewModel.hasAddressList
? GestureDetector(
onTap: () =>
Navigator.of(context).pushNamed(Routes.receive),
child: Container( child: Container(
height: 50, height: 50,
padding: EdgeInsets.only(left: 24, right: 12), padding: EdgeInsets.only(left: 24, right: 12),
alignment: Alignment.center, alignment: Alignment.center,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(25)), borderRadius:
BorderRadius.all(Radius.circular(25)),
border: Border.all( border: Border.all(
color: Theme.of(context).textTheme.subhead.color, color:
Theme.of(context).textTheme.subhead.color,
width: 1), width: 1),
color: Theme.of(context).buttonColor), color: Theme.of(context).buttonColor),
child: Row( child: Row(
@ -39,7 +69,9 @@ class AddressPage extends StatelessWidget {
Observer( Observer(
builder: (_) => Text( builder: (_) => Text(
addressListViewModel.hasAccounts addressListViewModel.hasAccounts
? S.of(context).accounts_subaddresses ? S
.of(context)
.accounts_subaddresses
: S.of(context).addresses, : S.of(context).addresses,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
@ -61,8 +93,17 @@ class AddressPage extends StatelessWidget {
), ),
), ),
) )
: PrimaryButton(
onPressed: () => addressListViewModel.nextAddress(),
text: 'Next address',
color: Theme.of(context).buttonColor,
textColor: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor);
})
], ],
), ),
); ));
} }
} }

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
@ -10,18 +11,7 @@ class BalancePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return Container(
onTapUp: (_) {
if (dashboardViewModel.balanceViewModel.canReverse) {
dashboardViewModel.balanceViewModel.isReversing = false;
}
},
onTapDown: (_) {
if (dashboardViewModel.balanceViewModel.canReverse) {
dashboardViewModel.balanceViewModel.isReversing = true;
}
},
child: Container(
color: Colors.transparent, color: Colors.transparent,
padding: EdgeInsets.all(24), padding: EdgeInsets.all(24),
child: Column( child: Column(
@ -41,9 +31,10 @@ class BalancePage extends StatelessWidget {
height: 1), height: 1),
); );
}), }),
SizedBox(height: 10),
Observer(builder: (_) { Observer(builder: (_) {
return Text( return Text(
dashboardViewModel.balanceViewModel.displayMode.toString(), '${dashboardViewModel.balanceViewModel.availableBalanceLabel} (${dashboardViewModel.balanceViewModel.availableFiatBalance.toString()})',
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
@ -57,7 +48,7 @@ class BalancePage extends StatelessWidget {
SizedBox(height: 10), SizedBox(height: 10),
Observer(builder: (_) { Observer(builder: (_) {
return AutoSizeText( return AutoSizeText(
dashboardViewModel.balanceViewModel.cryptoBalance, dashboardViewModel.balanceViewModel.availableBalance,
style: TextStyle( style: TextStyle(
fontSize: 54, fontSize: 54,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
@ -71,16 +62,36 @@ class BalancePage extends StatelessWidget {
}), }),
SizedBox(height: 10), SizedBox(height: 10),
Observer(builder: (_) { Observer(builder: (_) {
return Text(dashboardViewModel.balanceViewModel.fiatBalance, 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( style: TextStyle(
fontSize: 18, fontSize: 18,
fontWeight: FontWeight.w500, fontWeight: FontWeight.bold,
color: Theme.of(context).indicatorColor, color: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor,
height: 1), height: 1),
maxLines: 1,
textAlign: TextAlign.center); textAlign: TextAlign.center);
}), }),
], ],
), ),
)); );
} }
} }

View file

@ -1,5 +1,6 @@
import 'dart:ui'; import 'dart:ui';
import 'package:cake_wallet/entities/sync_status.dart'; 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:dotted_border/dotted_border.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -62,7 +63,8 @@ class ExchangePage extends BasePage {
@override @override
Widget trailing(BuildContext context) => TrailButton( Widget trailing(BuildContext context) => TrailButton(
caption: S.of(context).reset, onPressed: () { caption: S.of(context).reset,
onPressed: () {
_formKey.currentState.reset(); _formKey.currentState.reset();
exchangeViewModel.reset(); exchangeViewModel.reset();
}); });
@ -95,8 +97,8 @@ class ExchangePage extends BasePage {
return KeyboardActions( return KeyboardActions(
config: KeyboardActionsConfig( config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS, keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor: Theme.of(context).accentTextTheme.body2 keyboardBarColor:
.backgroundColor, Theme.of(context).accentTextTheme.body2.backgroundColor,
nextFocus: false, nextFocus: false,
actions: [ actions: [
KeyboardActionsItem( KeyboardActionsItem(
@ -160,6 +162,11 @@ class ExchangePage extends BasePage {
padding: EdgeInsets.fromLTRB(24, 100, 24, 32), padding: EdgeInsets.fromLTRB(24, 100, 24, 32),
child: Observer( child: Observer(
builder: (_) => ExchangeCard( builder: (_) => ExchangeCard(
hasAllAmount: exchangeViewModel.hasAllAmount,
allAmount: exchangeViewModel.hasAllAmount
? () => exchangeViewModel
.calculateDepositAllAmount()
: null,
amountFocusNode: _depositAmountFocus, amountFocusNode: _depositAmountFocus,
key: depositKey, key: depositKey,
title: S.of(context).you_will_send, title: S.of(context).you_will_send,
@ -177,9 +184,30 @@ class ExchangePage extends BasePage {
isAmountEstimated: false, isAmountEstimated: false,
hasRefundAddress: true, hasRefundAddress: true,
currencies: CryptoCurrency.all, currencies: CryptoCurrency.all,
onCurrencySelected: (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( exchangeViewModel.changeDepositCurrency(
currency: currency), currency: currency);
},
imageArrow: arrowBottomPurple, imageArrow: arrowBottomPurple,
currencyButtonColor: Colors.transparent, currencyButtonColor: Colors.transparent,
addressButtonsColor: addressButtonsColor:
@ -398,16 +426,21 @@ class ExchangePage extends BasePage {
text: S.of(context).exchange, text: S.of(context).exchange,
onPressed: () { onPressed: () {
if (_formKey.currentState.validate()) { if (_formKey.currentState.validate()) {
if ((exchangeViewModel.depositCurrency == CryptoCurrency.xmr) if ((exchangeViewModel.depositCurrency ==
&&(!(exchangeViewModel.status is SyncedSyncStatus))) { CryptoCurrency.xmr) &&
(!(exchangeViewModel.status
is SyncedSyncStatus))) {
showPopUp<void>( showPopUp<void>(
context: context, context: context,
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertWithOneAction( return AlertWithOneAction(
alertTitle: S.of(context).exchange, alertTitle: S.of(context).exchange,
alertContent: S.of(context).exchange_sync_alert_content, alertContent: S
.of(context)
.exchange_sync_alert_content,
buttonText: S.of(context).ok, buttonText: S.of(context).ok,
buttonAction: () => Navigator.of(context).pop()); buttonAction: () =>
Navigator.of(context).pop());
}); });
} else { } else {
exchangeViewModel.createTrade(); exchangeViewModel.createTrade();
@ -416,8 +449,8 @@ class ExchangePage extends BasePage {
}, },
color: Theme.of(context).accentTextTheme.body2.color, color: Theme.of(context).accentTextTheme.body2.color,
textColor: Colors.white, textColor: Colors.white,
isLoading: exchangeViewModel.tradeState isLoading:
is TradeIsCreating)), exchangeViewModel.tradeState is TradeIsCreating)),
]), ]),
)), )),
)); ));

View file

@ -27,7 +27,9 @@ class ExchangeCard extends StatefulWidget {
this.borderColor = Colors.transparent, this.borderColor = Colors.transparent,
this.currencyValueValidator, this.currencyValueValidator,
this.addressTextFieldValidator, this.addressTextFieldValidator,
this.amountFocusNode}) this.amountFocusNode,
this.hasAllAmount = false,
this.allAmount})
: super(key: key); : super(key: key);
final List<CryptoCurrency> currencies; final List<CryptoCurrency> currencies;
@ -47,6 +49,8 @@ class ExchangeCard extends StatefulWidget {
final FormFieldValidator<String> currencyValueValidator; final FormFieldValidator<String> currencyValueValidator;
final FormFieldValidator<String> addressTextFieldValidator; final FormFieldValidator<String> addressTextFieldValidator;
final FocusNode amountFocusNode; final FocusNode amountFocusNode;
final bool hasAllAmount;
Function allAmount;
@override @override
ExchangeCardState createState() => ExchangeCardState(); ExchangeCardState createState() => ExchangeCardState();
@ -197,16 +201,50 @@ 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(
padding: EdgeInsets.only(top: 5), padding: EdgeInsets.only(top: 5),
child: Row(mainAxisAlignment: MainAxisAlignment.start, children: < child: Container(
Widget>[ height: 15,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
_min != null _min != null
? Text( ? Text(
S.of(context).min_value(_min, _selectedCurrency.toString()), S
.of(context)
.min_value(_min, _selectedCurrency.toString()),
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
height: 1.2, height: 1.2,
@ -219,7 +257,9 @@ class ExchangeCardState extends State<ExchangeCard> {
_min != null ? SizedBox(width: 10) : Offstage(), _min != null ? SizedBox(width: 10) : Offstage(),
_max != null _max != null
? Text( ? Text(
S.of(context).max_value(_max, _selectedCurrency.toString()), S
.of(context)
.max_value(_max, _selectedCurrency.toString()),
style: TextStyle( style: TextStyle(
fontSize: 10, fontSize: 10,
height: 1.2, height: 1.2,
@ -228,7 +268,7 @@ class ExchangeCardState extends State<ExchangeCard> {
.display4 .display4
.decorationColor)) .decorationColor))
: Offstage(), : Offstage(),
]), ])),
), ),
!_isAddressEditable && widget.hasRefundAddress !_isAddressEditable && widget.hasRefundAddress
? Padding( ? Padding(
@ -238,8 +278,7 @@ class ExchangeCardState extends State<ExchangeCard> {
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: color: Theme.of(context)
Theme.of(context)
.accentTextTheme .accentTextTheme
.display4 .display4
.decorationColor), .decorationColor),
@ -251,7 +290,8 @@ class ExchangeCardState extends State<ExchangeCard> {
child: AddressTextField( child: AddressTextField(
controller: addressController, controller: addressController,
placeholder: widget.hasRefundAddress placeholder: widget.hasRefundAddress
? S.of(context).refund_address : null, ? S.of(context).refund_address
: null,
options: [ options: [
AddressTextFieldOption.paste, AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode, AddressTextFieldOption.qrCode,
@ -265,8 +305,7 @@ class ExchangeCardState extends State<ExchangeCard> {
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 16, fontSize: 16,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
color: color: Theme.of(context)
Theme.of(context)
.accentTextTheme .accentTextTheme
.display4 .display4
.decorationColor), .decorationColor),
@ -281,8 +320,8 @@ class ExchangeCardState extends State<ExchangeCard> {
onTap: () { onTap: () {
Clipboard.setData( Clipboard.setData(
ClipboardData(text: addressController.text)); ClipboardData(text: addressController.text));
showBar<void>(context, showBar<void>(
S.of(context).copied_to_clipboard); context, S.of(context).copied_to_clipboard);
}, },
child: Row( child: Row(
mainAxisSize: MainAxisSize.max, mainAxisSize: MainAxisSize.max,

View file

@ -6,8 +6,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/execution_state.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/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart'; import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart'; import 'package:cake_wallet/src/widgets/standart_list_row.dart';
@ -172,36 +170,14 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
), ),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final item = widget.exchangeTradeViewModel.items[index]; final item = widget.exchangeTradeViewModel.items[index];
String value; final value = item.data ?? fetchingLabel;
final content = Observer(builder: (_) { final content = StandartListRow(
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, title: item.title,
value: value, value: value,
valueFontSize: 14, valueFontSize: 14,
image: item.isCopied ? copyImage : null, image: item.isCopied ? copyImage : null,
); );
});
return item.isCopied return item.isCopied
? Builder( ? Builder(
@ -382,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

@ -54,7 +54,7 @@ class QRWidget extends StatelessWidget {
]), ]),
isAmountFieldShow isAmountFieldShow
? Padding( ? Padding(
padding: EdgeInsets.only(top: 60), padding: EdgeInsets.only(top: 40),
child: Row( child: Row(
children: <Widget>[ children: <Widget>[
Expanded( Expanded(
@ -109,7 +109,7 @@ class QRWidget extends StatelessWidget {
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: TextStyle( style: TextStyle(
fontSize: 16, fontSize: 15,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme. color: Theme.of(context).accentTextTheme.
display3.backgroundColor), display3.backgroundColor),

View file

@ -49,7 +49,7 @@ class PreSeedPage extends BasePage {
Padding( Padding(
padding: EdgeInsets.only(top: 70, left: 16, right: 16), padding: EdgeInsets.only(top: 70, left: 16, right: 16),
child: Text( child: Text(
S.of(context).pre_seed_description(wordsCount), S.of(context).pre_seed_description(wordsCount.toString()),
textAlign: TextAlign.center, textAlign: TextAlign.center,
style: TextStyle( style: TextStyle(
fontSize: 14, fontSize: 14,

View file

@ -31,6 +31,7 @@ class SendPage extends BasePage {
: _addressController = TextEditingController(), : _addressController = TextEditingController(),
_cryptoAmountController = TextEditingController(), _cryptoAmountController = TextEditingController(),
_fiatAmountController = TextEditingController(), _fiatAmountController = TextEditingController(),
_noteController = TextEditingController(),
_formKey = GlobalKey<FormState>(), _formKey = GlobalKey<FormState>(),
_cryptoAmountFocus = FocusNode(), _cryptoAmountFocus = FocusNode(),
_fiatAmountFocus = FocusNode(), _fiatAmountFocus = FocusNode(),
@ -46,6 +47,7 @@ class SendPage extends BasePage {
final TextEditingController _addressController; final TextEditingController _addressController;
final TextEditingController _cryptoAmountController; final TextEditingController _cryptoAmountController;
final TextEditingController _fiatAmountController; final TextEditingController _fiatAmountController;
final TextEditingController _noteController;
final GlobalKey<FormState> _formKey; final GlobalKey<FormState> _formKey;
final FocusNode _cryptoAmountFocus; final FocusNode _cryptoAmountFocus;
final FocusNode _fiatAmountFocus; final FocusNode _fiatAmountFocus;
@ -304,6 +306,30 @@ class SendPage extends BasePage {
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 14), 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( Observer(
builder: (_) => GestureDetector( builder: (_) => GestureDetector(
onTap: () => onTap: () =>
@ -313,6 +339,7 @@ class SendPage extends BasePage {
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.spaceBetween, MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Text( Text(
S S
@ -326,7 +353,12 @@ class SendPage extends BasePage {
color: Colors.white)), color: Colors.white)),
Container( Container(
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[ children: <Widget>[
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text( Text(
sendViewModel sendViewModel
.estimatedFee .estimatedFee
@ -341,8 +373,30 @@ class SendPage extends BasePage {
//color: Theme.of(context).primaryTextTheme.display2.color, //color: Theme.of(context).primaryTextTheme.display2.color,
color: color:
Colors.white)), 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(
padding: EdgeInsets.only( padding: EdgeInsets.only(
top: 2,
left: 5), left: 5),
child: Icon( child: Icon(
Icons.arrow_forward_ios, Icons.arrow_forward_ios,
@ -534,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) { reaction((_) => sendViewModel.sendAll, (bool all) {
if (all) { if (all) {
_cryptoAmountController.text = S.current.all; _cryptoAmountController.text = S.current.all;
@ -571,6 +633,12 @@ class SendPage extends BasePage {
} }
}); });
reaction((_) => sendViewModel.note, (String note) {
if (note != _noteController.text) {
_noteController.text = note;
}
});
reaction((_) => sendViewModel.state, (ExecutionState state) { reaction((_) => sendViewModel.state, (ExecutionState state) {
if (state is FailureState) { if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
@ -596,8 +664,14 @@ class SendPage extends BasePage {
amount: S.of(context).send_amount, amount: S.of(context).send_amount,
amountValue: amountValue:
sendViewModel.pendingTransaction.amountFormatted, sendViewModel.pendingTransaction.amountFormatted,
fiatAmountValue: sendViewModel.pendingTransactionFiatAmount
+ ' ' + sendViewModel.fiat.title,
fee: S.of(context).send_fee, fee: S.of(context).send_fee,
feeValue: sendViewModel.pendingTransaction.feeFormatted, feeValue: sendViewModel.pendingTransaction.feeFormatted,
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmount
+ ' ' + sendViewModel.fiat.title,
recipientTitle: S.of(context).recipient_address,
recipientAddress: sendViewModel.address,
rightButtonText: S.of(context).ok, rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel, leftButtonText: S.of(context).cancel,
actionRightButton: () { actionRightButton: () {
@ -614,96 +688,17 @@ class SendPage extends BasePage {
} }
if (state is TransactionCommitted) { if (state is TransactionCommitted) {
return Stack( return AlertWithOneAction(
children: <Widget>[ alertTitle: '',
Container( alertContent: S.of(context).send_success(
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(
sendViewModel.currency sendViewModel.currency
.toString()), .toString()),
textAlign: TextAlign.center, buttonText: S.of(context).ok,
style: TextStyle( buttonAction: () =>
fontSize: 22, Navigator.of(context).pop());
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))
],
);
} }
if (state is TransactionCommitting) { return Offstage();
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();
}); });
}); });
}, },
@ -746,6 +741,7 @@ class SendPage extends BasePage {
Future<void> _setTransactionPriority(BuildContext context) async { Future<void> _setTransactionPriority(BuildContext context) async {
final items = TransactionPriority.forWalletType(sendViewModel.walletType); final items = TransactionPriority.forWalletType(sendViewModel.walletType);
final selectedItem = items.indexOf(sendViewModel.transactionPriority); final selectedItem = items.indexOf(sendViewModel.transactionPriority);
final isShowScrollThumb = items.length > 3;
await showPopUp<void>( await showPopUp<void>(
builder: (_) => Picker( builder: (_) => Picker(
@ -755,7 +751,6 @@ class SendPage extends BasePage {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
onItemSelected: (TransactionPriority priority) => onItemSelected: (TransactionPriority priority) =>
sendViewModel.setTransactionPriority(priority), sendViewModel.setTransactionPriority(priority),
isAlwaysShowScrollThumb: true,
), ),
context: context); context: context);
} }

View file

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

View file

@ -9,8 +9,7 @@ class SettingsPickerCell<ItemType> extends StandardListRow {
{@required String title, {@required String title,
this.selectedItem, this.selectedItem,
this.items, this.items,
this.onItemSelected, this.onItemSelected})
this.isAlwaysShowScrollThumb})
: super( : super(
title: title, title: title,
isSelected: false, isSelected: false,
@ -24,7 +23,6 @@ class SettingsPickerCell<ItemType> extends StandardListRow {
selectedAtIndex: selectedAtIndex, selectedAtIndex: selectedAtIndex,
title: S.current.please_select, title: S.current.please_select,
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
isAlwaysShowScrollThumb: isAlwaysShowScrollThumb,
onItemSelected: (ItemType item) => onItemSelected: (ItemType item) =>
onItemSelected?.call(item))); onItemSelected?.call(item)));
}); });
@ -32,7 +30,6 @@ class SettingsPickerCell<ItemType> extends StandardListRow {
final ItemType selectedItem; final ItemType selectedItem;
final List<ItemType> items; final List<ItemType> items;
final void Function(ItemType item) onItemSelected; final void Function(ItemType item) onItemSelected;
final bool isAlwaysShowScrollThumb;
@override @override
Widget buildTrailing(BuildContext context) { 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/utils/show_bar.dart';
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.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/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/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'; import 'package:cake_wallet/src/widgets/standart_list_row.dart';
class TradeDetailsPage extends BasePage { class TradeDetailsPage extends BasePage {
TradeDetailsPage(this.trade) : _items = [] { TradeDetailsPage(this.tradeDetailsViewModel);
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()}'));
}
}
@override @override
String get title => S.current.trade_details_title; String get title => S.current.trade_details_title;
final Trade trade; final TradeDetailsViewModel tradeDetailsViewModel;
final List<StandartListItem> _items;
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
return Container(child: Observer(builder: (_) { return Observer(builder: (_) {
return ListView.separated( return SectionStandardList(
separatorBuilder: (_, __) => Container( sectionCount: 1,
height: 1, itemCounter: (int _) => tradeDetailsViewModel.items.length,
padding: EdgeInsets.only(left: 24), itemBuilder: (_, __, index) {
color: Theme.of(context).backgroundColor, final item = tradeDetailsViewModel.items[index];
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 GestureDetector( return GestureDetector(
onTap: () { onTap: () {
Clipboard.setData(ClipboardData(text: '${item.value}')); 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( child: StandartListRow(
title: '${item.title}', title: '${item.title}',
value: '${item.value}', value: '${item.value}'
isDrawBottom: isDrawBottom,
)); ));
}); });
})); });
} }
} }

View file

@ -1,6 +1,6 @@
class StandartListItem { import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
StandartListItem({this.title, this.value});
final String title; class StandartListItem extends TransactionDetailsListItem {
final String value; 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,11 +1,11 @@
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/utils/show_bar.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.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/widgets/standart_list_row.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.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/src/screens/base_page.dart';
@ -29,114 +29,26 @@ String blockExplorerName(String inputName) {
} }
class TransactionDetailsPage extends BasePage { class TransactionDetailsPage extends BasePage {
TransactionDetailsPage(this.transactionInfo, bool showRecipientAddress, TransactionDetailsPage({this.transactionDetailsViewModel});
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);
_items.add(StandartListItem(
title: "View in Block Explorer",
value: "https://xmrchain.net/search?value=${tx.id}"));
}
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()),
if (tx.feeFormatted()?.isNotEmpty)
StandartListItem(title: S.current.send_fee, value: tx.feeFormatted())
];
_items.addAll(items);
_items.add(StandartListItem(
title: "View in Block Explorer",
value: "https://www.blockchain.com/btc/tx/${tx.id}"));
}
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));
}
}
}
@override @override
String get title => S.current.transaction_details_title; String get title => S.current.transaction_details_title;
final TransactionInfo transactionInfo; final TransactionDetailsViewModel transactionDetailsViewModel;
final List<StandartListItem> _items;
@override @override
Widget body(BuildContext context) { Widget body(BuildContext context) {
return Container( return SectionStandardList(
child: ListView.separated( sectionCount: 1,
separatorBuilder: (context, index) => Container( itemCounter: (int _) => transactionDetailsViewModel.items.length,
height: 1, itemBuilder: (_, __, index) {
padding: EdgeInsets.only(left: 24), final item = transactionDetailsViewModel.items[index];
color: Theme.of(context).backgroundColor,
child: Container( if (item is StandartListItem) {
height: 1,
color:
Theme.of(context).primaryTextTheme.title.backgroundColor,
),
),
itemCount: _items.length,
itemBuilder: (context, index) {
final item = _items[index];
final isFinalBlockExplorerItem = final isFinalBlockExplorerItem =
index == _items.length - 1 ? true : false; index == transactionDetailsViewModel.items.length - 1
? true
: false;
if (isFinalBlockExplorerItem == false) { if (isFinalBlockExplorerItem == false) {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
@ -144,10 +56,8 @@ class TransactionDetailsPage extends BasePage {
showBar<void>(context, showBar<void>(context,
S.of(context).transaction_details_copied(item.title)); S.of(context).transaction_details_copied(item.title));
}, },
child: StandartListRow( child:
title: '${item.title}:', StandartListRow(title: '${item.title}:', value: item.value),
value: item.value,
isDrawBottom: isFinalBlockExplorerItem),
); );
} else { } else {
return GestureDetector( return GestureDetector(
@ -157,11 +67,20 @@ class TransactionDetailsPage extends BasePage {
child: StandartListRow( child: StandartListRow(
title: '${item.title}:', title: '${item.title}:',
value: value:
"View transaction on ${blockExplorerName(item.value)}", "View transaction on ${blockExplorerName(item.value)}"),
isDrawBottom: isFinalBlockExplorerItem),
);
}
}),
); );
} }
} }
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

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

View file

@ -7,6 +7,7 @@ class BaseAlertDialog extends StatelessWidget {
String get contentText => ''; String get contentText => '';
String get leftActionButtonText => ''; String get leftActionButtonText => '';
String get rightActionButtonText => ''; String get rightActionButtonText => '';
bool get isDividerExists => false;
VoidCallback get actionLeft => () {}; VoidCallback get actionLeft => () {};
VoidCallback get actionRight => () {}; VoidCallback get actionRight => () {};
bool get barrierDismissible => true; bool get barrierDismissible => true;
@ -127,19 +128,28 @@ class BaseAlertDialog extends StatelessWidget {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: <Widget>[
Container( Column(
padding: EdgeInsets.fromLTRB(24, 32, 24, 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[ children: <Widget>[
title(context),
Padding( Padding(
padding: EdgeInsets.only(top: 8), padding: EdgeInsets.fromLTRB(24, 20, 24, 0),
child: title(context),
),
isDividerExists
? Padding(
padding: EdgeInsets.only(top: 16, bottom: 8),
child: Container(
height: 1,
color: Theme.of(context).dividerColor,
),
)
: Offstage(),
Padding(
padding: EdgeInsets.fromLTRB(24, 8, 24, 32),
child: content(context), child: content(context),
) )
], ],
), ),
),
Container( Container(
height: 1, height: 1,
color: Theme.of(context).dividerColor, color: Theme.of(context).dividerColor,

View file

@ -51,7 +51,7 @@ class BaseTextFormField extends StatelessWidget {
final FocusNode focusNode; final FocusNode focusNode;
final bool readOnly; final bool readOnly;
final bool enableInteractiveSelection; final bool enableInteractiveSelection;
String initialValue; final String initialValue;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {

View file

@ -15,7 +15,6 @@ class Picker<Item extends Object> extends StatefulWidget {
this.description, this.description,
@required this.onItemSelected, @required this.onItemSelected,
this.mainAxisAlignment = MainAxisAlignment.start, this.mainAxisAlignment = MainAxisAlignment.start,
this.isAlwaysShowScrollThumb = false
}); });
final int selectedAtIndex; final int selectedAtIndex;
@ -25,7 +24,6 @@ class Picker<Item extends Object> extends StatefulWidget {
final String description; final String description;
final Function(Item) onItemSelected; final Function(Item) onItemSelected;
final MainAxisAlignment mainAxisAlignment; final MainAxisAlignment mainAxisAlignment;
final bool isAlwaysShowScrollThumb;
@override @override
PickerState createState() => PickerState<Item>(items, images, onItemSelected); PickerState createState() => PickerState<Item>(items, images, onItemSelected);
@ -56,6 +54,8 @@ class PickerState<Item> extends State<Picker> {
setState(() {}); setState(() {});
}); });
final isShowScrollThumb = items != null ? items.length > 3 : false;
return AlertBackground( return AlertBackground(
child: Stack( child: Stack(
alignment: Alignment.center, alignment: Alignment.center,
@ -168,7 +168,7 @@ class PickerState<Item> extends State<Picker> {
) )
) )
: Offstage(), : Offstage(),
widget.isAlwaysShowScrollThumb isShowScrollThumb
? CakeScrollbar( ? CakeScrollbar(
backgroundHeight: backgroundHeight, backgroundHeight: backgroundHeight,
thumbHeight: thumbHeight, thumbHeight: thumbHeight,

View file

@ -113,16 +113,19 @@ class SectionStandardList extends StatelessWidget {
{@required this.itemCounter, {@required this.itemCounter,
@required this.itemBuilder, @required this.itemBuilder,
@required this.sectionCount, @required this.sectionCount,
this.sectionTitleBuilder,
this.hasTopSeparator = false, this.hasTopSeparator = false,
BuildContext context}) BuildContext context})
: totalRows = transform(hasTopSeparator, context, sectionCount, : totalRows = transform(hasTopSeparator, context, sectionCount,
itemCounter, itemBuilder); itemCounter, itemBuilder, sectionTitleBuilder);
final int sectionCount; final int sectionCount;
final bool hasTopSeparator; final bool hasTopSeparator;
final int Function(int sectionIndex) itemCounter; final int Function(int sectionIndex) itemCounter;
final Widget Function(BuildContext context, int sectionIndex, int itemIndex) final Widget Function(BuildContext context, int sectionIndex, int itemIndex)
itemBuilder; itemBuilder;
final Widget Function(BuildContext context, int sectionIndex)
sectionTitleBuilder;
final List<Widget> totalRows; final List<Widget> totalRows;
static List<Widget> transform( static List<Widget> transform(
@ -131,7 +134,9 @@ class SectionStandardList extends StatelessWidget {
int sectionCount, int sectionCount,
int Function(int sectionIndex) itemCounter, int Function(int sectionIndex) itemCounter,
Widget Function(BuildContext context, int sectionIndex, int itemIndex) Widget Function(BuildContext context, int sectionIndex, int itemIndex)
itemBuilder) { itemBuilder,
Widget Function(BuildContext context, int sectionIndex)
sectionTitleBuilder) {
final items = <Widget>[]; final items = <Widget>[];
for (var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) { for (var sectionIndex = 0; sectionIndex < sectionCount; sectionIndex++) {
@ -139,6 +144,10 @@ class SectionStandardList extends StatelessWidget {
items.add(StandardListSeparator(padding: EdgeInsets.only(left: 24))); items.add(StandardListSeparator(padding: EdgeInsets.only(left: 24)));
} }
if (sectionTitleBuilder != null) {
items.add(sectionTitleBuilder(context, sectionIndex));
}
final itemCount = itemCounter(sectionIndex); final itemCount = itemCounter(sectionIndex);
for (var itemIndex = 0; itemIndex < itemCount; itemIndex++) { for (var itemIndex = 0; itemIndex < itemCount; itemIndex++) {

View file

@ -7,21 +7,17 @@ class StandartListRow extends StatelessWidget {
this.value, this.value,
this.titleFontSize = 14, this.titleFontSize = 14,
this.valueFontSize = 16, this.valueFontSize = 16,
this.image, this.image});
this.isDrawBottom = false});
final String title; final String title;
final String value; final String value;
final double titleFontSize; final double titleFontSize;
final double valueFontSize; final double valueFontSize;
final Image image; final Image image;
final bool isDrawBottom;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Container(
children: <Widget>[
Container(
width: double.infinity, width: double.infinity,
color: Theme.of(context).backgroundColor, color: Theme.of(context).backgroundColor,
child: Padding( child: Padding(
@ -65,19 +61,6 @@ class StandartListRow extends StatelessWidget {
) )
]), ]),
), ),
),
isDrawBottom
? Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).backgroundColor,
child: Container(
height: 1,
color: Theme.of(context).primaryTextTheme.title.backgroundColor,
),
)
: Offstage(),
],
); );
} }
} }

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/balance.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/store/wallet_list_store.dart'; import 'package:cake_wallet/store/wallet_list_store.dart';
@ -19,7 +20,7 @@ abstract class AppStoreBase with Store {
AuthenticationStore authenticationStore; AuthenticationStore authenticationStore;
@observable @observable
WalletBase wallet; WalletBase<Balance> wallet;
WalletListStore walletList; WalletListStore walletList;

View file

@ -85,6 +85,11 @@ abstract class SettingsStoreBase with Store {
(String languageCode) => sharedPreferences.setString( (String languageCode) => sharedPreferences.setString(
PreferencesKey.currentLanguageCode, languageCode)); PreferencesKey.currentLanguageCode, languageCode));
reaction((_) => balanceDisplayMode,
(BalanceDisplayMode mode) => sharedPreferences.setInt(
PreferencesKey.currentBalanceDisplayModeKey,
mode.serialize()));
this this
.nodes .nodes
.observe((change) => _saveCurrentNode(change.newValue, change.key)); .observe((change) => _saveCurrentNode(change.newValue, change.key));
@ -153,7 +158,7 @@ abstract class SettingsStoreBase with Store {
.getBool(PreferencesKey.allowBiometricalAuthenticationKey) ?? .getBool(PreferencesKey.allowBiometricalAuthenticationKey) ??
false; false;
final legacyTheme = final legacyTheme =
sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) (sharedPreferences.getBool(PreferencesKey.isDarkThemeLegacy) ?? false)
? ThemeType.dark.index ? ThemeType.dark.index
: ThemeType.bright.index; : ThemeType.bright.index;
final savedTheme = ThemeList.deserialize( final savedTheme = ThemeList.deserialize(

View file

@ -1,4 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'package:cake_wallet/entities/wallet_contact.dart';
import 'package:cake_wallet/entities/wallet_info.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/contact_record.dart'; import 'package:cake_wallet/entities/contact_record.dart';
@ -11,15 +14,22 @@ class ContactListViewModel = ContactListViewModelBase
with _$ContactListViewModel; with _$ContactListViewModel;
abstract class ContactListViewModelBase with Store { abstract class ContactListViewModelBase with Store {
ContactListViewModelBase(this.contactSource) ContactListViewModelBase(this.contactSource, this.walletInfoSource)
: contacts = ObservableList<ContactRecord>() { : contacts = ObservableList<ContactRecord>(),
walletContacts = walletInfoSource.values
.where((info) => info.address?.isNotEmpty ?? false)
.map((info) => WalletContact(
info.address, info.name, walletTypeToCryptoCurrency(info.type)))
.toList() {
_subscription = contactSource.bindToListWithTransform( _subscription = contactSource.bindToListWithTransform(
contacts, (Contact contact) => ContactRecord(contactSource, contact), contacts, (Contact contact) => ContactRecord(contactSource, contact),
initialFire: true); initialFire: true);
} }
final Box<Contact> contactSource; final Box<Contact> contactSource;
final Box<WalletInfo> walletInfoSource;
final ObservableList<ContactRecord> contacts; final ObservableList<ContactRecord> contacts;
final List<WalletContact> walletContacts;
StreamSubscription<BoxEvent> _subscription; StreamSubscription<BoxEvent> _subscription;
Future<void> delete(ContactRecord contact) async => contact.original.delete(); Future<void> delete(ContactRecord contact) async => contact.original.delete();

View file

@ -1,10 +1,13 @@
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/entities/balance.dart';
import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/entities/wallet_type.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/monero/monero_wallet.dart'; import 'package:cake_wallet/monero/monero_wallet.dart';
import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount.dart'; import 'package:cake_wallet/entities/calculate_fiat_amount.dart';
import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/view_model/dashboard/wallet_balance.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
@ -18,19 +21,46 @@ abstract class BalanceViewModelBase with Store {
BalanceViewModelBase( BalanceViewModelBase(
{@required this.appStore, {@required this.appStore,
@required this.settingsStore, @required this.settingsStore,
@required this.fiatConvertationStore}) @required this.fiatConvertationStore}) {
: isReversing = false; isReversing = false;
wallet ??= appStore.wallet;
_reaction = reaction((_) => appStore.wallet, _onWalletChange);
final _wallet = wallet;
if (_wallet is MoneroWallet) {
balance = _wallet.balance;
}
if (_wallet is BitcoinWallet) {
balance = _wallet.balance;
}
_onCurrentWalletChangeReaction =
reaction<void>((_) => wallet.balance, (dynamic balance) {
if (balance is Balance) {
this.balance = balance;
}
});
}
final AppStore appStore; final AppStore appStore;
final SettingsStore settingsStore; final SettingsStore settingsStore;
final FiatConversionStore fiatConvertationStore; final FiatConversionStore fiatConvertationStore;
bool get canReverse => bool get canReverse => false;
(appStore.wallet.balance.availableModes as List).length > 1;
@observable @observable
bool isReversing; bool isReversing;
@observable
Balance balance;
@observable
WalletBase<Balance> wallet;
@computed @computed
double get price => fiatConvertationStore.prices[appStore.wallet.currency]; double get price => fiatConvertationStore.prices[appStore.wallet.currency];
@ -45,66 +75,97 @@ abstract class BalanceViewModelBase with Store {
: savedDisplayMode; : savedDisplayMode;
@computed @computed
String get cryptoBalance { String get availableBalanceLabel {
final walletBalance = _walletBalance; if (wallet.type == WalletType.monero) {
var balance = '---'; return S.current.xmr_available_balance;
if (displayMode == BalanceDisplayMode.availableBalance) {
balance = walletBalance.unlockedBalance ?? '0.0';
} }
if (displayMode == BalanceDisplayMode.fullBalance) { return S.current.confirmed;
balance = walletBalance.totalBalance ?? '0.0';
}
return balance;
} }
@computed @computed
String get fiatBalance { String get additionalBalanceLabel {
if (wallet.type == WalletType.monero) {
return S.current.xmr_full_balance;
}
return S.current.unconfirmed;
}
@computed
String get availableBalance {
final walletBalance = _walletBalance;
if (settingsStore.balanceDisplayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
return walletBalance.formattedAvailableBalance;
}
@computed
String get additionalBalance {
final walletBalance = _walletBalance;
if (settingsStore.balanceDisplayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
return walletBalance.formattedAdditionalBalance;
}
@computed
String get availableFiatBalance {
final walletBalance = _walletBalance; final walletBalance = _walletBalance;
final fiatCurrency = settingsStore.fiatCurrency; final fiatCurrency = settingsStore.fiatCurrency;
var balance = '---';
final totalBalance = if (settingsStore.balanceDisplayMode == BalanceDisplayMode.hiddenBalance) {
_getFiatBalance(price: price, cryptoAmount: walletBalance.totalBalance); return '---';
final unlockedBalance = _getFiatBalance(
price: price, cryptoAmount: walletBalance.unlockedBalance);
if (displayMode == BalanceDisplayMode.availableBalance) {
balance = fiatCurrency.toString() + ' ' + unlockedBalance ?? '0.00';
} }
if (displayMode == BalanceDisplayMode.fullBalance) { return fiatCurrency.toString() +
balance = fiatCurrency.toString() + ' ' + totalBalance ?? '0.00'; ' ' +
} _getFiatBalance(
price: price,
return balance; cryptoAmount: walletBalance.formattedAvailableBalance);
} }
@computed @computed
WalletBalance get _walletBalance { String get additionalFiatBalance {
final _wallet = appStore.wallet; final walletBalance = _walletBalance;
final fiatCurrency = settingsStore.fiatCurrency;
if (_wallet is MoneroWallet) { if (settingsStore.balanceDisplayMode == BalanceDisplayMode.hiddenBalance) {
return WalletBalance( return '---';
unlockedBalance: _wallet.balance.formattedUnlockedBalance,
totalBalance: _wallet.balance.formattedFullBalance);
} }
if (_wallet is BitcoinWallet) { return fiatCurrency.toString() +
return WalletBalance( ' ' +
unlockedBalance: _wallet.balance.availableBalanceFormatted, _getFiatBalance(
totalBalance: _wallet.balance.totalFormatted); price: price,
cryptoAmount: walletBalance.formattedAdditionalBalance);
} }
return null; @computed
} Balance get _walletBalance => wallet.balance;
@computed @computed
CryptoCurrency get currency => appStore.wallet.currency; CryptoCurrency get currency => appStore.wallet.currency;
ReactionDisposer _onCurrentWalletChangeReaction;
ReactionDisposer _reaction;
@action
void _onWalletChange(WalletBase<Balance> wallet) {
this.wallet = wallet;
balance = wallet.balance;
_onCurrentWalletChangeReaction?.reaction?.dispose();
_onCurrentWalletChangeReaction = reaction<Balance>(
(_) => wallet.balance, (Balance balance) => this.balance = balance);
}
String _getFiatBalance({double price, String cryptoAmount}) { String _getFiatBalance({double price, String cryptoAmount}) {
if (cryptoAmount == null) { if (cryptoAmount == null) {
return '0.00'; return '0.00';

View file

@ -1,5 +1,6 @@
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart'; import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart'; import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/entities/balance.dart';
import 'package:cake_wallet/entities/transaction_history.dart'; import 'package:cake_wallet/entities/transaction_history.dart';
import 'package:cake_wallet/monero/account.dart'; import 'package:cake_wallet/monero/account.dart';
import 'package:cake_wallet/monero/monero_balance.dart'; import 'package:cake_wallet/monero/monero_balance.dart';
@ -184,7 +185,7 @@ abstract class DashboardViewModelBase with Store {
} }
@observable @observable
WalletBase wallet; WalletBase<Balance> wallet;
bool get hasRescan => wallet.type == WalletType.monero; bool get hasRescan => wallet.type == WalletType.monero;
@ -212,7 +213,7 @@ abstract class DashboardViewModelBase with Store {
} }
@action @action
void _onWalletChange(WalletBase wallet) { void _onWalletChange(WalletBase<Balance> wallet) {
this.wallet = wallet; this.wallet = wallet;
type = wallet.type; type = wallet.type;
name = wallet.name; name = wallet.name;
@ -231,6 +232,8 @@ abstract class DashboardViewModelBase with Store {
_onMoneroTransactionsUpdate(wallet); _onMoneroTransactionsUpdate(wallet);
} else { } else {
subname = null;
transactions.clear(); transactions.clear();
transactions.addAll(wallet.transactionHistory.transactions.values.map( transactions.addAll(wallet.transactionHistory.transactions.values.map(

View file

@ -39,20 +39,11 @@ abstract class ExchangeTradeViewModelBase with Store {
} }
items = ObservableList<ExchangeTradeItem>(); items = ObservableList<ExchangeTradeItem>();
items.addAll([
ExchangeTradeItem( _updateItems();
title: S.current.id, data: '${trade.id}', isCopied: true),
ExchangeTradeItem(
title: S.current.amount, data: '${trade.amount}', isCopied: false),
ExchangeTradeItem(
title: S.current.status, data: '${trade.state}', isCopied: false),
ExchangeTradeItem(
title: S.current.widgets_address + ':',
data: trade.inputAddress,
isCopied: true),
]);
_updateTrade(); _updateTrade();
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade()); _timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
} }
@ -95,8 +86,27 @@ abstract class ExchangeTradeViewModelBase with Store {
} }
trade = updatedTrade; trade = updatedTrade;
_updateItems();
} catch (e) { } catch (e) {
print(e.toString()); print(e.toString());
} }
} }
void _updateItems() {
items?.clear();
items.addAll([
ExchangeTradeItem(
title: S.current.id, data: '${trade.id}', isCopied: true),
ExchangeTradeItem(
title: S.current.amount, data: '${trade.amount}', isCopied: false),
ExchangeTradeItem(
title: S.current.status, data: '${trade.state}', isCopied: false),
ExchangeTradeItem(
title: S.current.widgets_address + ':',
data: trade.inputAddress,
isCopied: true),
]);
}
} }

View file

@ -1,3 +1,5 @@
import 'package:cake_wallet/bitcoin/bitcoin_amount_format.dart';
import 'package:cake_wallet/bitcoin/bitcoin_wallet.dart';
import 'package:cake_wallet/core/wallet_base.dart'; import 'package:cake_wallet/core/wallet_base.dart';
import 'package:cake_wallet/entities/crypto_currency.dart'; import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/entities/sync_status.dart'; import 'package:cake_wallet/entities/sync_status.dart';
@ -7,6 +9,7 @@ import 'package:cake_wallet/exchange/limits.dart';
import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/limits_state.dart'; import 'package:cake_wallet/exchange/limits_state.dart';
import 'package:cake_wallet/store/dashboard/trades_store.dart'; import 'package:cake_wallet/store/dashboard/trades_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
@ -27,8 +30,8 @@ part 'exchange_view_model.g.dart';
class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel; class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
abstract class ExchangeViewModelBase with Store { abstract class ExchangeViewModelBase with Store {
ExchangeViewModelBase( ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
this.wallet, this.trades, this._exchangeTemplateStore, this.tradesStore) { this.tradesStore, this._settingsStore) {
providerList = [ providerList = [
XMRTOExchangeProvider(), XMRTOExchangeProvider(),
ChangeNowExchangeProvider(), ChangeNowExchangeProvider(),
@ -104,10 +107,6 @@ abstract class ExchangeViewModelBase with Store {
@observable @observable
bool isReceiveAmountEntered; bool isReceiveAmountEntered;
Limits limits;
NumberFormat _cryptoNumberFormat;
@computed @computed
SyncStatus get status => wallet.syncStatus; SyncStatus get status => wallet.syncStatus;
@ -115,6 +114,15 @@ abstract class ExchangeViewModelBase with Store {
ObservableList<ExchangeTemplate> get templates => ObservableList<ExchangeTemplate> get templates =>
_exchangeTemplateStore.templates; _exchangeTemplateStore.templates;
bool get hasAllAmount =>
wallet.type == WalletType.bitcoin && depositCurrency == wallet.currency;
Limits limits;
NumberFormat _cryptoNumberFormat;
SettingsStore _settingsStore;
@action @action
void changeProvider({ExchangeProvider provider}) { void changeProvider({ExchangeProvider provider}) {
this.provider = provider; this.provider = provider;
@ -264,9 +272,8 @@ abstract class ExchangeViewModelBase with Store {
await trades.add(trade); await trades.add(trade);
tradeState = TradeIsCreatedSuccessfully(trade: trade); tradeState = TradeIsCreatedSuccessfully(trade: trade);
} catch (e) { } catch (e) {
tradeState = TradeIsCreatedFailure( tradeState =
title: provider.title, TradeIsCreatedFailure(title: provider.title, error: e.toString());
error: e.toString());
} }
} }
} else { } else {
@ -291,6 +298,22 @@ abstract class ExchangeViewModelBase with Store {
_onPairChange(); _onPairChange();
} }
@action
void calculateDepositAllAmount() {
if (wallet is BitcoinWallet) {
final availableBalance = wallet.balance.available;
final fee = BitcoinWalletBase.feeAmountForPriority(
_settingsStore.transactionPriority);
if (availableBalance < fee || availableBalance == 0) {
return;
}
final amount = availableBalance - fee;
changeDepositAmount(amount: bitcoinAmountToString(amount: amount));
}
}
void updateTemplate() => _exchangeTemplateStore.update(); void updateTemplate() => _exchangeTemplateStore.update();
void addTemplate( void addTemplate(

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/entities/balance_display_mode.dart'; import 'package:cake_wallet/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount_raw.dart';
import 'package:cake_wallet/entities/transaction_description.dart'; import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -37,7 +38,15 @@ abstract class SendViewModelBase with Store {
this._fiatConversationStore, this.transactionDescriptionBox) this._fiatConversationStore, this.transactionDescriptionBox)
: state = InitialExecutionState(), : state = InitialExecutionState(),
_cryptoNumberFormat = NumberFormat(), _cryptoNumberFormat = NumberFormat(),
note = '',
sendAll = false { sendAll = false {
final _priority = _settingsStore.transactionPriority;
if (!TransactionPriority.forWalletType(walletType).contains(_priority)) {
_settingsStore.transactionPriority =
TransactionPriority.forWalletType(walletType).first;
}
_setCryptoNumMaximumFractionDigits(); _setCryptoNumMaximumFractionDigits();
} }
@ -53,6 +62,9 @@ abstract class SendViewModelBase with Store {
@observable @observable
String address; String address;
@observable
String note;
@observable @observable
bool sendAll; bool sendAll;
@ -60,6 +72,50 @@ abstract class SendViewModelBase with Store {
double get estimatedFee => double get estimatedFee =>
_wallet.calculateEstimatedFee(_settingsStore.transactionPriority); _wallet.calculateEstimatedFee(_settingsStore.transactionPriority);
@computed
String get estimatedFeeFiatAmount {
try {
final fiat = calculateFiatAmountRaw(
price: _fiatConversationStore.prices[_wallet.currency],
cryptoAmount: estimatedFee);
return fiat;
} catch (_) {
return '0.00';
}
}
@computed
String get pendingTransactionFiatAmount {
try {
if (pendingTransaction != null) {
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[_wallet.currency],
cryptoAmount: pendingTransaction.amountFormatted);
return fiat;
} else {
return '0.00';
}
} catch (_) {
return '0.00';
}
}
@computed
String get pendingTransactionFeeFiatAmount {
try {
if (pendingTransaction != null) {
final fiat = calculateFiatAmount(
price: _fiatConversationStore.prices[_wallet.currency],
cryptoAmount: pendingTransaction.feeFormatted);
return fiat;
} else {
return '0.00';
}
} catch (_) {
return '0.00';
}
}
FiatCurrency get fiat => _settingsStore.fiatCurrency; FiatCurrency get fiat => _settingsStore.fiatCurrency;
TransactionPriority get transactionPriority => TransactionPriority get transactionPriority =>
@ -78,9 +134,7 @@ abstract class SendViewModelBase with Store {
PendingTransaction pendingTransaction; PendingTransaction pendingTransaction;
@computed @computed
String get balance => String get balance => _wallet.balance.formattedAvailableBalance ?? '0.0';
_wallet.balance.formattedBalance(BalanceDisplayMode.availableBalance)
as String ?? '0.0';
@computed @computed
bool get isReadyForSend => _wallet.syncStatus is SyncedSyncStatus; bool get isReadyForSend => _wallet.syncStatus is SyncedSyncStatus;
@ -105,6 +159,7 @@ abstract class SendViewModelBase with Store {
cryptoAmount = ''; cryptoAmount = '';
fiatAmount = ''; fiatAmount = '';
address = ''; address = '';
note = '';
} }
@action @action
@ -124,10 +179,14 @@ abstract class SendViewModelBase with Store {
state = TransactionCommitting(); state = TransactionCommitting();
await pendingTransaction.commit(); await pendingTransaction.commit();
if (_settingsStore.shouldSaveRecipientAddress && if (pendingTransaction.id?.isNotEmpty ?? false) {
(pendingTransaction.id?.isNotEmpty ?? false)) { _settingsStore.shouldSaveRecipientAddress
await transactionDescriptionBox.add(TransactionDescription( ? await transactionDescriptionBox.add(TransactionDescription(
id: pendingTransaction.id, recipientAddress: address)); id: pendingTransaction.id,
recipientAddress: address,
transactionNote: note))
: await transactionDescriptionBox.add(TransactionDescription(
id: pendingTransaction.id, transactionNote: note));
} }
state = TransactionCommitted(); state = TransactionCommitted();
@ -197,7 +256,7 @@ abstract class SendViewModelBase with Store {
switch (_wallet.type) { switch (_wallet.type) {
case WalletType.bitcoin: case WalletType.bitcoin:
final amount = !sendAll ? double.parse(_amount) : null; final amount = !sendAll ? _amount : null;
return BitcoinTransactionCredentials( return BitcoinTransactionCredentials(
address, amount, _settingsStore.transactionPriority); address, amount, _settingsStore.transactionPriority);

View file

@ -6,15 +6,13 @@ class PickerListItem<ItemType> extends SettingsListItem {
{@required String title, {@required String title,
@required this.selectedItem, @required this.selectedItem,
@required this.items, @required this.items,
void Function(ItemType item) onItemSelected, void Function(ItemType item) onItemSelected})
this.isAlwaysShowScrollThumb = false})
: _onItemSelected = onItemSelected, : _onItemSelected = onItemSelected,
super(title); super(title);
final ItemType Function() selectedItem; final ItemType Function() selectedItem;
final List<ItemType> items; final List<ItemType> items;
final void Function(ItemType item) _onItemSelected; final void Function(ItemType item) _onItemSelected;
final bool isAlwaysShowScrollThumb;
void onItemSelected(dynamic item) { void onItemSelected(dynamic item) {
if (item is ItemType) { if (item is ItemType) {

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/balance.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/themes/theme_list.dart'; import 'package:cake_wallet/themes/theme_list.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
@ -28,16 +29,23 @@ part 'settings_view_model.g.dart';
class SettingsViewModel = SettingsViewModelBase with _$SettingsViewModel; class SettingsViewModel = SettingsViewModelBase with _$SettingsViewModel;
abstract class SettingsViewModelBase with Store { abstract class SettingsViewModelBase with Store {
SettingsViewModelBase(this._settingsStore, WalletBase wallet) SettingsViewModelBase(this._settingsStore, WalletBase<Balance> wallet)
: itemHeaders = {}, : itemHeaders = {},
_walletType = wallet.type, _walletType = wallet.type,
_biometricAuth = BiometricAuth() { _biometricAuth = BiometricAuth() {
currentVersion = ''; currentVersion = '';
PackageInfo.fromPlatform().then( PackageInfo.fromPlatform().then(
(PackageInfo packageInfo) => currentVersion = packageInfo.version); (PackageInfo packageInfo) => currentVersion = packageInfo.version);
final _priority = _settingsStore.transactionPriority;
if (!TransactionPriority.forWalletType(_walletType).contains(_priority)) {
_settingsStore.transactionPriority =
TransactionPriority.forWalletType(_walletType).first;
}
sections = [ sections = [
[ [
if ((wallet.balance.availableModes as List).length > 1)
PickerListItem( PickerListItem(
title: S.current.settings_display_balance_as, title: S.current.settings_display_balance_as,
items: BalanceDisplayMode.all, items: BalanceDisplayMode.all,
@ -47,7 +55,6 @@ abstract class SettingsViewModelBase with Store {
PickerListItem( PickerListItem(
title: S.current.settings_currency, title: S.current.settings_currency,
items: FiatCurrency.all, items: FiatCurrency.all,
isAlwaysShowScrollThumb: true,
selectedItem: () => fiatCurrency, selectedItem: () => fiatCurrency,
onItemSelected: (FiatCurrency currency) => onItemSelected: (FiatCurrency currency) =>
setFiatCurrency(currency)), setFiatCurrency(currency)),
@ -55,7 +62,6 @@ abstract class SettingsViewModelBase with Store {
title: S.current.settings_fee_priority, title: S.current.settings_fee_priority,
items: TransactionPriority.forWalletType(wallet.type), items: TransactionPriority.forWalletType(wallet.type),
selectedItem: () => transactionPriority, selectedItem: () => transactionPriority,
isAlwaysShowScrollThumb: true,
onItemSelected: (TransactionPriority priority) => onItemSelected: (TransactionPriority priority) =>
_settingsStore.transactionPriority = priority), _settingsStore.transactionPriority = priority),
SwitcherListItem( SwitcherListItem(

View file

@ -0,0 +1,105 @@
import 'dart:async';
import 'package:cake_wallet/exchange/changenow/changenow_exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/exchange/morphtoken/morphtoken_exchange_provider.dart';
import 'package:cake_wallet/exchange/trade.dart';
import 'package:cake_wallet/exchange/xmrto/xmrto_exchange_provider.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
part 'trade_details_view_model.g.dart';
class TradeDetailsViewModel = TradeDetailsViewModelBase
with _$TradeDetailsViewModel;
abstract class TradeDetailsViewModelBase with Store {
TradeDetailsViewModelBase({Trade tradeForDetails, this.trades}) {
trade = tradeForDetails;
switch (trade.provider) {
case ExchangeProviderDescription.xmrto:
_provider = XMRTOExchangeProvider();
break;
case ExchangeProviderDescription.changeNow:
_provider = ChangeNowExchangeProvider();
break;
case ExchangeProviderDescription.morphToken:
_provider = MorphTokenExchangeProvider(trades: trades);
break;
}
items = ObservableList<StandartListItem>();
_updateItems();
_updateTrade();
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
}
final Box<Trade> trades;
@observable
Trade trade;
@observable
ObservableList<StandartListItem> items;
ExchangeProvider _provider;
Timer _timer;
@action
Future<void> _updateTrade() async {
try {
final updatedTrade = await _provider.findTradeById(id: trade.id);
if (updatedTrade.createdAt == null && trade.createdAt != null) {
updatedTrade.createdAt = trade.createdAt;
}
trade = updatedTrade;
_updateItems();
} catch (e) {
print(e.toString());
}
}
void _updateItems() {
final dateFormat = DateFormatter.withCurrentLocal();
items?.clear();
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()}'));
}
}
}

View file

@ -0,0 +1,120 @@
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
import 'package:cake_wallet/entities/transaction_info.dart';
import 'package:cake_wallet/monero/monero_transaction_info.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/textfield_list_item.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart';
import 'package:cake_wallet/utils/date_formatter.dart';
import 'package:cake_wallet/entities/transaction_description.dart';
import 'package:hive/hive.dart';
import 'package:mobx/mobx.dart';
import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/generated/i18n.dart';
part 'transaction_details_view_model.g.dart';
class TransactionDetailsViewModel = TransactionDetailsViewModelBase
with _$TransactionDetailsViewModel;
abstract class TransactionDetailsViewModelBase with Store {
TransactionDetailsViewModelBase(
{this.transactionInfo,
this.transactionDescriptionBox,
this.settingsStore})
: items = [] {
showRecipientAddress = settingsStore?.shouldSaveRecipientAddress ?? false;
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.transaction_details_fee, value: tx.feeFormatted()),
];
if (tx.key?.isNotEmpty ?? null) {
_items.add(
StandartListItem(title: S.current.transaction_key, value: tx.key));
}
items.addAll(_items);
items.add(StandartListItem(
title: "View in Block Explorer",
value: "https://xmrchain.net/search?value=${tx.id}"));
}
if (tx is BitcoinTransactionInfo) {
final _items = [
StandartListItem(
title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date)),
StandartListItem(
title: S.current.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()),
if (tx.feeFormatted()?.isNotEmpty)
StandartListItem(
title: S.current.transaction_details_fee,
value: tx.feeFormatted())
];
items.addAll(_items);
items.add(StandartListItem(
title: "View in Block Explorer",
value: "https://www.blockchain.com/btc/tx/${tx.id}"));
}
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));
}
}
final description = transactionDescriptionBox.values.firstWhere(
(val) => val.id == transactionInfo.id,
orElse: () => TransactionDescription(id: transactionInfo.id));
items.add(TextFieldListItem(
title: S.current.note_tap_to_change,
value: description.note,
onSubmitted: (value) {
description.transactionNote = value;
if (description.isInBox) {
description.save();
} else {
transactionDescriptionBox.add(description);
}
}));
}
final TransactionInfo transactionInfo;
final Box<TransactionDescription> transactionDescriptionBox;
final SettingsStore settingsStore;
final List<TransactionDetailsListItem> items;
bool showRecipientAddress;
}

View file

@ -63,7 +63,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
final wallet = _wallet; final wallet = _wallet;
if (wallet is BitcoinWallet) { if (wallet is BitcoinWallet) {
await wallet.generateNewAddress(label: label); await wallet.generateNewAddress();
} }
if (wallet is MoneroWallet) { if (wallet is MoneroWallet) {
@ -77,7 +77,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
final wallet = _wallet; final wallet = _wallet;
if (wallet is BitcoinWallet) { if (wallet is BitcoinWallet) {
await wallet.updateAddress(_item.address as String, label: label); await wallet.updateAddress(_item.address as String);
} }
if (wallet is MoneroWallet) { if (wallet is MoneroWallet) {

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/balance.dart';
import 'package:cake_wallet/store/app_store.dart'; import 'package:cake_wallet/store/app_store.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
@ -60,7 +61,7 @@ abstract class WalletAddressListViewModelBase with Store {
_appStore = appStore; _appStore = appStore;
_wallet = _appStore.wallet; _wallet = _appStore.wallet;
hasAccounts = _wallet?.type == WalletType.monero; hasAccounts = _wallet?.type == WalletType.monero;
_onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase wallet) { _onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase<Balance> wallet) {
_wallet = wallet; _wallet = wallet;
hasAccounts = _wallet.type == WalletType.monero; hasAccounts = _wallet.type == WalletType.monero;
}); });
@ -119,7 +120,7 @@ abstract class WalletAddressListViewModelBase with Store {
return WalletAddressListItem( return WalletAddressListItem(
isPrimary: isPrimary, isPrimary: isPrimary,
name: addr.label, name: null,
address: addr.address); address: addr.address);
}); });
addressList.addAll(bitcoinAddresses); addressList.addAll(bitcoinAddresses);
@ -131,15 +132,6 @@ abstract class WalletAddressListViewModelBase with Store {
@observable @observable
bool hasAccounts; bool hasAccounts;
@observable
WalletBase _wallet;
List<ListItem> _baseItems;
AppStore _appStore;
ReactionDisposer _onWalletChangeReaction;
@computed @computed
String get accountLabel { String get accountLabel {
final wallet = _wallet; final wallet = _wallet;
@ -151,6 +143,19 @@ abstract class WalletAddressListViewModelBase with Store {
return null; return null;
} }
@computed
bool get hasAddressList => _wallet.type == WalletType.monero;
@observable
WalletBase<Balance> _wallet;
List<ListItem> _baseItems;
AppStore _appStore;
ReactionDisposer _onWalletChangeReaction;
@action @action
void setAddress(WalletAddressListItem address) => void setAddress(WalletAddressListItem address) =>
_wallet.address = address.address; _wallet.address = address.address;
@ -164,4 +169,13 @@ abstract class WalletAddressListViewModelBase with Store {
_baseItems.add(WalletAddressListHeader()); _baseItems.add(WalletAddressListHeader());
} }
@action
void nextAddress() {
final wallet = _wallet;
if (wallet is BitcoinWallet) {
wallet.nextAddress();
}
}
} }

View file

@ -50,6 +50,7 @@ abstract class WalletCreationVMBase with Store {
dirPath: dirPath); dirPath: dirPath);
credentials.walletInfo = walletInfo; credentials.walletInfo = walletInfo;
final wallet = await process(credentials); final wallet = await process(credentials);
walletInfo.address = wallet.address;
await _walletInfoSource.add(walletInfo); await _walletInfoSource.add(walletInfo);
_appStore.changeCurrentWallet(wallet); _appStore.changeCurrentWallet(wallet);
_appStore.authenticationStore.allowed(); _appStore.authenticationStore.allowed();

View file

@ -11,7 +11,7 @@ description: Cake Wallet.
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 4.0.91+22 version: 4.1.0+33
environment: environment:
sdk: ">=2.7.0 <3.0.0" sdk: ">=2.7.0 <3.0.0"

View file

@ -1,7 +1,7 @@
{ {
"welcome" : "Willkommen zu", "welcome" : "Willkommen zu",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "tolle Brieftasche zum Monero", "first_wallet_text" : "tolle Brieftasche zum Monero und Bitcoin",
"please_make_selection" : "Bitte treffen Sie unten eine Auswahl zu Erstellen oder Wiederherstellen Ihrer Brieftasche.", "please_make_selection" : "Bitte treffen Sie unten eine Auswahl zu Erstellen oder Wiederherstellen Ihrer Brieftasche.",
"create_new" : "Neue Wallet erstellen", "create_new" : "Neue Wallet erstellen",
"restore_wallet" : "Wallet wiederherstellen", "restore_wallet" : "Wallet wiederherstellen",
@ -212,7 +212,7 @@
"send_name" : "Name", "send_name" : "Name",
"send_got_it" : "Ich habs", "send_got_it" : "Ich habs",
"send_sending" : "Senden...", "send_sending" : "Senden...",
"send_success" : "Ihr Monero wurde erfolgreich gesendet", "send_success" : "Ihr ${crypto} wurde erfolgreich gesendet",
"settings_title" : "die Einstellungen", "settings_title" : "die Einstellungen",
@ -282,6 +282,7 @@
"transaction_details_date" : "Datum", "transaction_details_date" : "Datum",
"transaction_details_height" : "Höhe", "transaction_details_height" : "Höhe",
"transaction_details_amount" : "Menge", "transaction_details_amount" : "Menge",
"transaction_details_fee" : "Gebühr",
"transaction_details_copied" : "${title} in die Zwischenablage kopiert", "transaction_details_copied" : "${title} in die Zwischenablage kopiert",
"transaction_details_recipient_address" : "Empfängeradresse", "transaction_details_recipient_address" : "Empfängeradresse",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Bitte warten Sie, bis Ihre Brieftasche synchronisiert ist", "exchange_sync_alert_content" : "Bitte warten Sie, bis Ihre Brieftasche synchronisiert ist",
"pre_seed_title" : "WICHTIG", "pre_seed_title" : "WICHTIG",
"pre_seed_description" : "Auf der nächsten Seite sehen Sie eine Reihe von 25 Wörtern. Dies ist Ihr einzigartiger und privater Samen und der EINZIGE Weg, um Ihren Geldbeutel im Falle eines Verlusts oder einer Fehlfunktion wiederherzustellen. Es liegt in IHRER Verantwortung, es aufzuschreiben und an einem sicheren Ort außerhalb der Cake Wallet App aufzubewahren.", "pre_seed_description" : "Auf der nächsten Seite sehen Sie eine Reihe von ${words} Wörtern. Dies ist Ihr einzigartiger und privater Samen und der EINZIGE Weg, um Ihren Geldbeutel im Falle eines Verlusts oder einer Fehlfunktion wiederherzustellen. Es liegt in IHRER Verantwortung, es aufzuschreiben und an einem sicheren Ort außerhalb der Cake Wallet App aufzubewahren.",
"pre_seed_button_text" : "Ich verstehe. Zeig mir meinen Samen", "pre_seed_button_text" : "Ich verstehe. Zeig mir meinen Samen",
"xmr_to_error" : "XMR.TO-Fehler", "xmr_to_error" : "XMR.TO-Fehler",
@ -428,5 +429,11 @@
"color_theme" : "Farbthema", "color_theme" : "Farbthema",
"light_theme" : "Licht", "light_theme" : "Licht",
"bright_theme" : "Hell", "bright_theme" : "Hell",
"dark_theme" : "Dunkel" "dark_theme" : "Dunkel",
"enter_your_note" : "Geben Sie Ihre Notiz ein…",
"note_optional" : "Hinweis (optional)",
"note_tap_to_change" : "Hinweis (zum Ändern tippen)",
"transaction_key" : "Transaktionsschlüssel",
"confirmations" : "Bestätigungen",
"recipient_address" : "Empfängeradresse"
} }

View file

@ -1,8 +1,8 @@
{ {
"welcome" : "Welcome to", "welcome" : "Welcome to",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "Awesome wallet for Monero", "first_wallet_text" : "Awesome wallet for Monero and Bitcoin",
"please_make_selection" : "Please make selection below to create or recover your wallet.", "please_make_selection" : "Please make a selection below to create or recover your wallet.",
"create_new" : "Create New Wallet", "create_new" : "Create New Wallet",
"restore_wallet" : "Restore Wallet", "restore_wallet" : "Restore Wallet",
@ -212,7 +212,7 @@
"send_name" : "Name", "send_name" : "Name",
"send_got_it" : "Got it", "send_got_it" : "Got it",
"send_sending" : "Sending...", "send_sending" : "Sending...",
"send_success" : "Your Monero was successfully sent", "send_success" : "Your ${crypto} was successfully sent",
"settings_title" : "Settings", "settings_title" : "Settings",
@ -282,6 +282,7 @@
"transaction_details_date" : "Date", "transaction_details_date" : "Date",
"transaction_details_height" : "Height", "transaction_details_height" : "Height",
"transaction_details_amount" : "Amount", "transaction_details_amount" : "Amount",
"transaction_details_fee" : "Fee",
"transaction_details_copied" : "${title} copied to Clipboard", "transaction_details_copied" : "${title} copied to Clipboard",
"transaction_details_recipient_address" : "Recipient address", "transaction_details_recipient_address" : "Recipient address",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Please wait until your wallet is synchronized", "exchange_sync_alert_content" : "Please wait until your wallet is synchronized",
"pre_seed_title" : "IMPORTANT", "pre_seed_title" : "IMPORTANT",
"pre_seed_description" : "On the next page you will see a series of 25 words. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cake Wallet app.", "pre_seed_description" : "On the next page you will see a series of ${words} words. This is your unique and private seed and it is the ONLY way to recover your wallet in case of loss or malfunction. It is YOUR responsibility to write it down and store it in a safe place outside of the Cake Wallet app.",
"pre_seed_button_text" : "I understand. Show me my seed", "pre_seed_button_text" : "I understand. Show me my seed",
"xmr_to_error" : "XMR.TO error", "xmr_to_error" : "XMR.TO error",
@ -428,5 +429,11 @@
"color_theme" : "Color theme", "color_theme" : "Color theme",
"light_theme" : "Light", "light_theme" : "Light",
"bright_theme" : "Bright", "bright_theme" : "Bright",
"dark_theme" : "Dark" "dark_theme" : "Dark",
"enter_your_note" : "Enter your note…",
"note_optional" : "Note (optional)",
"note_tap_to_change" : "Note (tap to change)",
"transaction_key" : "Transaction Key",
"confirmations" : "Confirmations",
"recipient_address" : "Recipient address"
} }

View file

@ -1,7 +1,7 @@
{ {
"welcome" : "Bienvenido", "welcome" : "Bienvenido",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "Impresionante billetera para Monero", "first_wallet_text" : "Impresionante billetera para Monero y Bitcoin",
"please_make_selection" : "Seleccione a continuación para crear o recuperar su billetera.", "please_make_selection" : "Seleccione a continuación para crear o recuperar su billetera.",
"create_new" : "Crear nueva billetera", "create_new" : "Crear nueva billetera",
"restore_wallet" : "Restaurar billetera", "restore_wallet" : "Restaurar billetera",
@ -212,7 +212,7 @@
"send_name" : "Nombre", "send_name" : "Nombre",
"send_got_it" : "Entendido", "send_got_it" : "Entendido",
"send_sending" : "Enviando...", "send_sending" : "Enviando...",
"send_success" : "Su Monero fue enviado con éxito", "send_success" : "Su ${crypto} fue enviado con éxito",
"settings_title" : "Configuraciones", "settings_title" : "Configuraciones",
@ -282,6 +282,7 @@
"transaction_details_date" : "Fecha", "transaction_details_date" : "Fecha",
"transaction_details_height" : "Altura", "transaction_details_height" : "Altura",
"transaction_details_amount" : "Cantidad", "transaction_details_amount" : "Cantidad",
"transaction_details_fee" : "Cuota",
"transaction_details_copied" : "${title} Copiado al portapapeles", "transaction_details_copied" : "${title} Copiado al portapapeles",
"transaction_details_recipient_address" : "Dirección del receptor", "transaction_details_recipient_address" : "Dirección del receptor",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Espere hasta que su billetera esté sincronizada", "exchange_sync_alert_content" : "Espere hasta que su billetera esté sincronizada",
"pre_seed_title" : "IMPORTANTE", "pre_seed_title" : "IMPORTANTE",
"pre_seed_description" : "En la página siguiente verá una serie de 25 palabras. Esta es su semilla única y privada y es la ÚNICA forma de recuperar su billetera en caso de pérdida o mal funcionamiento. Es SU responsabilidad escribirlo y guardarlo en un lugar seguro fuera de la aplicación Cake Wallet.", "pre_seed_description" : "En la página siguiente verá una serie de ${words} palabras. Esta es su semilla única y privada y es la ÚNICA forma de recuperar su billetera en caso de pérdida o mal funcionamiento. Es SU responsabilidad escribirlo y guardarlo en un lugar seguro fuera de la aplicación Cake Wallet.",
"pre_seed_button_text" : "Entiendo. Muéstrame mi semilla", "pre_seed_button_text" : "Entiendo. Muéstrame mi semilla",
"xmr_to_error" : "Error de XMR.TO", "xmr_to_error" : "Error de XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Tema de color", "color_theme" : "Tema de color",
"light_theme" : "Ligera", "light_theme" : "Ligera",
"bright_theme" : "Brillante", "bright_theme" : "Brillante",
"dark_theme" : "Oscura" "dark_theme" : "Oscura",
"enter_your_note" : "Ingresa tu nota…",
"note_optional" : "Nota (opcional)",
"note_tap_to_change" : "Nota (toque para cambiar)",
"transaction_key" : "Clave de transacción",
"confirmations" : "Confirmaciones",
"recipient_address" : "Dirección del receptor"
} }

View file

@ -1,7 +1,7 @@
{ {
"welcome" : "स्वागत हे सेवा मेरे", "welcome" : "स्वागत हे सेवा मेरे",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "बहुत बढ़िया बटुआ के लिये Monero", "first_wallet_text" : "Monero और Bitcoin के लिए बहुत बढ़िया बटुआ",
"please_make_selection" : "कृपया नीचे चयन करें अपना बटुआ बनाएं या पुनर्प्राप्त करें.", "please_make_selection" : "कृपया नीचे चयन करें अपना बटुआ बनाएं या पुनर्प्राप्त करें.",
"create_new" : "नया बटुआ बनाएँ", "create_new" : "नया बटुआ बनाएँ",
"restore_wallet" : "वॉलेट को पुनर्स्थापित करें", "restore_wallet" : "वॉलेट को पुनर्स्थापित करें",
@ -212,7 +212,7 @@
"send_name" : "नाम", "send_name" : "नाम",
"send_got_it" : "समझ गया", "send_got_it" : "समझ गया",
"send_sending" : "भेजना...", "send_sending" : "भेजना...",
"send_success" : "आपका Monero सफलतापूर्वक भेजा गया", "send_success" : "आपका ${crypto} सफलतापूर्वक भेजा गया",
"settings_title" : "सेटिंग्स", "settings_title" : "सेटिंग्स",
@ -282,6 +282,7 @@
"transaction_details_date" : "तारीख", "transaction_details_date" : "तारीख",
"transaction_details_height" : "ऊंचाई", "transaction_details_height" : "ऊंचाई",
"transaction_details_amount" : "रकम", "transaction_details_amount" : "रकम",
"transaction_details_fee" : "शुल्क",
"transaction_details_copied" : "${title} क्लिपबोर्ड पर नकल", "transaction_details_copied" : "${title} क्लिपबोर्ड पर नकल",
"transaction_details_recipient_address" : "प्राप्तकर्ता का पता", "transaction_details_recipient_address" : "प्राप्तकर्ता का पता",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "कृपया प्रतीक्षा करें जब तक आपका बटुआ सिंक्रनाइज़ नहीं किया जाता है", "exchange_sync_alert_content" : "कृपया प्रतीक्षा करें जब तक आपका बटुआ सिंक्रनाइज़ नहीं किया जाता है",
"pre_seed_title" : "महत्वपूर्ण", "pre_seed_title" : "महत्वपूर्ण",
"pre_seed_description" : "अगले पेज पर आपको 25 शब्दों की एक श्रृंखला दिखाई देगी। यह आपका अद्वितीय और निजी बीज है और नुकसान या खराबी के मामले में अपने बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। यह आपकी जिम्मेदारी है कि इसे नीचे लिखें और इसे Cake Wallet ऐप के बाहर सुरक्षित स्थान पर संग्रहीत करें।", "pre_seed_description" : "अगले पेज पर आपको ${words} शब्दों की एक श्रृंखला दिखाई देगी। यह आपका अद्वितीय और निजी बीज है और नुकसान या खराबी के मामले में अपने बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। यह आपकी जिम्मेदारी है कि इसे नीचे लिखें और इसे Cake Wallet ऐप के बाहर सुरक्षित स्थान पर संग्रहीत करें।",
"pre_seed_button_text" : "मै समझता हुँ। मुझे अपना बीज दिखाओ", "pre_seed_button_text" : "मै समझता हुँ। मुझे अपना बीज दिखाओ",
"xmr_to_error" : "XMR.TO त्रुटि", "xmr_to_error" : "XMR.TO त्रुटि",
@ -428,5 +429,11 @@
"color_theme" : "रंग विषय", "color_theme" : "रंग विषय",
"light_theme" : "रोशनी", "light_theme" : "रोशनी",
"bright_theme" : "उज्ज्वल", "bright_theme" : "उज्ज्वल",
"dark_theme" : "अंधेरा" "dark_theme" : "अंधेरा",
"enter_your_note" : "अपना नोट दर्ज करें ...",
"note_optional" : "नोट (वैकल्पिक)",
"note_tap_to_change" : "नोट (टैप टू चेंज)",
"transaction_key" : "लेन-देन की",
"confirmations" : "पुष्टिकरण",
"recipient_address" : "प्राप्तकर्ता का पता"
} }

View file

@ -1,7 +1,7 @@
{ {
"welcome" : "ようこそ に", "welcome" : "ようこそ に",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "素晴らしい財布 ために Monero", "first_wallet_text" : "Moneroとビットコインのための素晴らしい財布",
"please_make_selection" : "以下を選択してください ウォレットを作成または回復する.", "please_make_selection" : "以下を選択してください ウォレットを作成または回復する.",
"create_new" : "新しいウォレットを作成", "create_new" : "新しいウォレットを作成",
"restore_wallet" : "ウォレットを復元", "restore_wallet" : "ウォレットを復元",
@ -212,7 +212,7 @@
"send_name" : "名前", "send_name" : "名前",
"send_got_it" : "とった", "send_got_it" : "とった",
"send_sending" : "送信...", "send_sending" : "送信...",
"send_success" : "Moneroが送信されました", "send_success" : "${crypto}が送信されました",
"settings_title" : "設定", "settings_title" : "設定",
@ -282,6 +282,7 @@
"transaction_details_date" : "日付", "transaction_details_date" : "日付",
"transaction_details_height" : "高さ", "transaction_details_height" : "高さ",
"transaction_details_amount" : "量", "transaction_details_amount" : "量",
"transaction_details_fee" : "費用",
"transaction_details_copied" : "${title} クリップボードにコピーしました", "transaction_details_copied" : "${title} クリップボードにコピーしました",
"transaction_details_recipient_address" : "受取人の住所", "transaction_details_recipient_address" : "受取人の住所",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "ウォレットが同期されるまでお待ちください", "exchange_sync_alert_content" : "ウォレットが同期されるまでお待ちください",
"pre_seed_title" : "重要", "pre_seed_title" : "重要",
"pre_seed_description" : "次のページでは、一連の25語が表示されます。 これはあなたのユニークでプライベートなシードであり、紛失や誤動作が発生した場合にウォレットを回復する唯一の方法です。 それを書き留めて、Cake Wallet アプリの外の安全な場所に保管するのはあなたの責任です。", "pre_seed_description" : "次のページでは、一連の${words}語が表示されます。 これはあなたのユニークでプライベートなシードであり、紛失や誤動作が発生した場合にウォレットを回復する唯一の方法です。 それを書き留めて、Cake Wallet アプリの外の安全な場所に保管するのはあなたの責任です。",
"pre_seed_button_text" : "わかります。 種を見せて", "pre_seed_button_text" : "わかります。 種を見せて",
"xmr_to_error" : "XMR.TOエラー", "xmr_to_error" : "XMR.TOエラー",
@ -428,5 +429,11 @@
"color_theme" : "カラーテーマ", "color_theme" : "カラーテーマ",
"light_theme" : "光", "light_theme" : "光",
"bright_theme" : "明るい", "bright_theme" : "明るい",
"dark_theme" : "闇" "dark_theme" : "闇",
"enter_your_note" : "メモを入力してください…",
"note_optional" : "注(オプション)",
"note_tap_to_change" : "注(タップして変更)",
"transaction_key" : "トランザクションキー",
"confirmations" : "確認",
"recipient_address" : "受信者のアドレス"
} }

View file

@ -1,7 +1,7 @@
{ {
"welcome" : "환영 에", "welcome" : "환영 에",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "멋진 지갑 에 대한 Monero", "first_wallet_text" : "Monero 및 Bitcoin을위한 멋진 지갑",
"please_make_selection" : "아래에서 선택하십시오 지갑 만들기 또는 복구.", "please_make_selection" : "아래에서 선택하십시오 지갑 만들기 또는 복구.",
"create_new" : "새 월렛 만들기", "create_new" : "새 월렛 만들기",
"restore_wallet" : "월렛 복원", "restore_wallet" : "월렛 복원",
@ -212,7 +212,7 @@
"send_name" : "이름", "send_name" : "이름",
"send_got_it" : "알았다", "send_got_it" : "알았다",
"send_sending" : "배상...", "send_sending" : "배상...",
"send_success" : "Monero가 성공적으로 전송되었습니다", "send_success" : "${crypto}가 성공적으로 전송되었습니다",
"settings_title" : "설정", "settings_title" : "설정",
@ -282,6 +282,7 @@
"transaction_details_date" : "날짜", "transaction_details_date" : "날짜",
"transaction_details_height" : "신장", "transaction_details_height" : "신장",
"transaction_details_amount" : "양", "transaction_details_amount" : "양",
"transaction_details_fee" : "회비",
"transaction_details_copied" : "${title} 클립 보드에 복사", "transaction_details_copied" : "${title} 클립 보드에 복사",
"transaction_details_recipient_address" : "받는 사람 주소", "transaction_details_recipient_address" : "받는 사람 주소",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "지갑이 동기화 될 때까지 기다리십시오", "exchange_sync_alert_content" : "지갑이 동기화 될 때까지 기다리십시오",
"pre_seed_title" : "중대한", "pre_seed_title" : "중대한",
"pre_seed_description" : "다음 페이지에서 25 개의 단어를 볼 수 있습니다. 이것은 귀하의 고유하고 개인적인 시드이며 분실 또는 오작동시 지갑을 복구하는 유일한 방법입니다. 기록해두고 Cake Wallet 앱 외부의 안전한 장소에 보관하는 것은 귀하의 책임입니다.", "pre_seed_description" : "다음 페이지에서 ${words} 개의 단어를 볼 수 있습니다. 이것은 귀하의 고유하고 개인적인 시드이며 분실 또는 오작동시 지갑을 복구하는 유일한 방법입니다. 기록해두고 Cake Wallet 앱 외부의 안전한 장소에 보관하는 것은 귀하의 책임입니다.",
"pre_seed_button_text" : "이해 했어요. 내 씨앗을 보여줘", "pre_seed_button_text" : "이해 했어요. 내 씨앗을 보여줘",
"xmr_to_error" : "XMR.TO 오류", "xmr_to_error" : "XMR.TO 오류",
@ -428,5 +429,11 @@
"color_theme" : "색상 테마", "color_theme" : "색상 테마",
"light_theme" : "빛", "light_theme" : "빛",
"bright_theme" : "선명한", "bright_theme" : "선명한",
"dark_theme" : "어두운" "dark_theme" : "어두운",
"enter_your_note" : "메모를 입력하세요…",
"note_optional" : "참고 (선택 사항)",
"note_tap_to_change" : "메모 (변경하려면 탭하세요)",
"transaction_key" : "거래 키",
"confirmations" : "확인",
"recipient_address" : "받는 사람 주소"
} }

View file

@ -1,7 +1,7 @@
{ {
"welcome" : "Welkom bij", "welcome" : "Welkom bij",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "Geweldige portemonnee fvoor Monero", "first_wallet_text" : "Geweldige portemonnee voor Monero en Bitcoin",
"please_make_selection" : "Maak hieronder uw keuze tot maak of herstel je portemonnee.", "please_make_selection" : "Maak hieronder uw keuze tot maak of herstel je portemonnee.",
"create_new" : "Maak een nieuwe portemonnee", "create_new" : "Maak een nieuwe portemonnee",
"restore_wallet" : "Portemonnee herstellen", "restore_wallet" : "Portemonnee herstellen",
@ -212,7 +212,7 @@
"send_name" : "Naam", "send_name" : "Naam",
"send_got_it" : "Ik snap het", "send_got_it" : "Ik snap het",
"send_sending" : "Bezig met verzenden...", "send_sending" : "Bezig met verzenden...",
"send_success" : "Uw Monero is succesvol verzonden", "send_success" : "Uw ${crypto} is succesvol verzonden",
"settings_title" : "Instellingen", "settings_title" : "Instellingen",
@ -282,6 +282,7 @@
"transaction_details_date" : "Datum", "transaction_details_date" : "Datum",
"transaction_details_height" : "Hoogte", "transaction_details_height" : "Hoogte",
"transaction_details_amount" : "Bedrag", "transaction_details_amount" : "Bedrag",
"transaction_details_fee" : "Vergoeding",
"transaction_details_copied" : "${title} gekopieerd naar het klembord", "transaction_details_copied" : "${title} gekopieerd naar het klembord",
"transaction_details_recipient_address" : "Adres van de ontvanger", "transaction_details_recipient_address" : "Adres van de ontvanger",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Wacht tot uw portemonnee is gesynchroniseerd", "exchange_sync_alert_content" : "Wacht tot uw portemonnee is gesynchroniseerd",
"pre_seed_title" : "BELANGRIJK", "pre_seed_title" : "BELANGRIJK",
"pre_seed_description" : "Op de volgende pagina ziet u een reeks van 25 woorden. Dit is uw unieke en persoonlijke zaadje en het is de ENIGE manier om uw portemonnee te herstellen in geval van verlies of storing. Het is JOUW verantwoordelijkheid om het op te schrijven en op een veilige plaats op te slaan buiten de Cake Wallet app.", "pre_seed_description" : "Op de volgende pagina ziet u een reeks van ${words} woorden. Dit is uw unieke en persoonlijke zaadje en het is de ENIGE manier om uw portemonnee te herstellen in geval van verlies of storing. Het is JOUW verantwoordelijkheid om het op te schrijven en op een veilige plaats op te slaan buiten de Cake Wallet app.",
"pre_seed_button_text" : "Ik begrijp het. Laat me mijn zaad zien", "pre_seed_button_text" : "Ik begrijp het. Laat me mijn zaad zien",
"xmr_to_error" : "XMR.TO-fout", "xmr_to_error" : "XMR.TO-fout",
@ -428,5 +429,11 @@
"color_theme" : "Kleur thema", "color_theme" : "Kleur thema",
"light_theme" : "Licht", "light_theme" : "Licht",
"bright_theme" : "Helder", "bright_theme" : "Helder",
"dark_theme" : "Donker" "dark_theme" : "Donker",
"enter_your_note" : "Voer uw notitie in ...",
"note_optional" : "Opmerking (optioneel)",
"note_tap_to_change" : "Opmerking (tik om te wijzigen)",
"transaction_key" : "Transactiesleutel",
"confirmations" : "Bevestigingen",
"recipient_address" : "Adres ontvanger"
} }

View file

@ -1,7 +1,7 @@
{ {
"welcome" : "Witamy w", "welcome" : "Witamy w",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "Niesamowity portfel dla Monero", "first_wallet_text" : "Niesamowity portfel dla Monero i Bitcoin",
"please_make_selection" : "Wybierz poniżej, aby cutwórz lub odzyskaj swój portfel.", "please_make_selection" : "Wybierz poniżej, aby cutwórz lub odzyskaj swój portfel.",
"create_new" : "Utwórz nowy portfel", "create_new" : "Utwórz nowy portfel",
"restore_wallet" : "Przywróć portfel", "restore_wallet" : "Przywróć portfel",
@ -212,7 +212,7 @@
"send_name" : "Imię", "send_name" : "Imię",
"send_got_it" : "Rozumiem", "send_got_it" : "Rozumiem",
"send_sending" : "Wysyłanie...", "send_sending" : "Wysyłanie...",
"send_success" : "Twoje Monero zostało pomyślnie wysłane", "send_success" : "Twoje ${crypto} zostało pomyślnie wysłane",
"settings_title" : "Ustawienia", "settings_title" : "Ustawienia",
@ -282,6 +282,7 @@
"transaction_details_date" : "Data", "transaction_details_date" : "Data",
"transaction_details_height" : "Wysokość", "transaction_details_height" : "Wysokość",
"transaction_details_amount" : "Ilość", "transaction_details_amount" : "Ilość",
"transaction_details_fee" : "Opłata",
"transaction_details_copied" : "${title} skopiowane do schowka", "transaction_details_copied" : "${title} skopiowane do schowka",
"transaction_details_recipient_address" : "Adres odbiorcy", "transaction_details_recipient_address" : "Adres odbiorcy",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Poczekaj, aż portfel zostanie zsynchronizowany", "exchange_sync_alert_content" : "Poczekaj, aż portfel zostanie zsynchronizowany",
"pre_seed_title" : "WAŻNY", "pre_seed_title" : "WAŻNY",
"pre_seed_description" : "Na następnej stronie zobaczysz serię 25 słów. To jest Twoje unikalne i prywatne ziarno i jest to JEDYNY sposób na odzyskanie portfela w przypadku utraty lub awarii. Twoim obowiązkiem jest zapisanie go i przechowywanie w bezpiecznym miejscu poza aplikacją Cake Wallet.", "pre_seed_description" : "Na następnej stronie zobaczysz serię ${words} słów. To jest Twoje unikalne i prywatne ziarno i jest to JEDYNY sposób na odzyskanie portfela w przypadku utraty lub awarii. Twoim obowiązkiem jest zapisanie go i przechowywanie w bezpiecznym miejscu poza aplikacją Cake Wallet.",
"pre_seed_button_text" : "Rozumiem. Pokaż mi moje nasienie", "pre_seed_button_text" : "Rozumiem. Pokaż mi moje nasienie",
"xmr_to_error" : "Pomyłka XMR.TO", "xmr_to_error" : "Pomyłka XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Motyw kolorystyczny", "color_theme" : "Motyw kolorystyczny",
"light_theme" : "Lekki", "light_theme" : "Lekki",
"bright_theme" : "Jasny", "bright_theme" : "Jasny",
"dark_theme" : "Ciemny" "dark_theme" : "Ciemny",
"enter_your_note" : "Wpisz notatkę…",
"note_optional" : "Notatka (opcjonalnie)",
"note_tap_to_change" : "Notatka (dotknij, aby zmienić)",
"transaction_key" : "Klucz transakcji",
"confirmations" : "Potwierdzenia",
"recipient_address" : "Adres odbiorcy"
} }

View file

@ -1,7 +1,7 @@
{ {
"welcome" : "Bem-vindo ao", "welcome" : "Bem-vindo ao",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "Uma fantástica carteira para Monero", "first_wallet_text" : "Uma fantástica carteira para Monero e Bitcoin",
"please_make_selection" : "Escolha se quer criar uma carteira nova ou restaurar uma antiga.", "please_make_selection" : "Escolha se quer criar uma carteira nova ou restaurar uma antiga.",
"create_new" : "Criar nova carteira", "create_new" : "Criar nova carteira",
"restore_wallet" : "Restaurar carteira", "restore_wallet" : "Restaurar carteira",
@ -212,7 +212,7 @@
"send_name" : "Nome", "send_name" : "Nome",
"send_got_it" : "Entendi", "send_got_it" : "Entendi",
"send_sending" : "Enviando...", "send_sending" : "Enviando...",
"send_success" : "Seu Monero foi enviado com sucesso", "send_success" : "Seu ${crypto} foi enviado com sucesso",
"settings_title" : "Configurações", "settings_title" : "Configurações",
@ -282,6 +282,7 @@
"transaction_details_date" : "Data", "transaction_details_date" : "Data",
"transaction_details_height" : "Altura", "transaction_details_height" : "Altura",
"transaction_details_amount" : "Quantia", "transaction_details_amount" : "Quantia",
"transaction_details_fee" : "Taxa",
"transaction_details_copied" : "${title} copiados para a área de transferência", "transaction_details_copied" : "${title} copiados para a área de transferência",
"transaction_details_recipient_address" : "Endereço do destinatário", "transaction_details_recipient_address" : "Endereço do destinatário",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Por favor, espere até que sua carteira seja sincronizada", "exchange_sync_alert_content" : "Por favor, espere até que sua carteira seja sincronizada",
"pre_seed_title" : "IMPORTANTE", "pre_seed_title" : "IMPORTANTE",
"pre_seed_description" : "Na próxima página, você verá uma série de 25 palavras. Esta é a sua semente única e privada e é a ÚNICA maneira de recuperar sua carteira em caso de perda ou mau funcionamento. É SUA responsabilidade anotá-lo e armazená-lo em um local seguro fora do aplicativo Cake Wallet.", "pre_seed_description" : "Na próxima página, você verá uma série de ${words} palavras. Esta é a sua semente única e privada e é a ÚNICA maneira de recuperar sua carteira em caso de perda ou mau funcionamento. É SUA responsabilidade anotá-lo e armazená-lo em um local seguro fora do aplicativo Cake Wallet.",
"pre_seed_button_text" : "Compreendo. Me mostre minha semente", "pre_seed_button_text" : "Compreendo. Me mostre minha semente",
"xmr_to_error" : "Erro XMR.TO", "xmr_to_error" : "Erro XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Tema de cor", "color_theme" : "Tema de cor",
"light_theme" : "Luz", "light_theme" : "Luz",
"bright_theme" : "Brilhante", "bright_theme" : "Brilhante",
"dark_theme" : "Sombria" "dark_theme" : "Sombria",
"enter_your_note" : "Insira sua nota ...",
"note_optional" : "Nota (opcional)",
"note_tap_to_change" : "Nota (toque para alterar)",
"transaction_key" : "Chave de transação",
"confirmations" : "Confirmações",
"recipient_address" : "Endereço do destinatário"
} }

View file

@ -1,7 +1,7 @@
{ {
"welcome" : "Приветствуем в", "welcome" : "Приветствуем в",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "В самом удобном кошельке для Monero", "first_wallet_text" : "В самом удобном кошельке для Monero и Bitcoin",
"please_make_selection" : "Выберите способ создания кошелька: создать новый или восстановить ваш существующий.", "please_make_selection" : "Выберите способ создания кошелька: создать новый или восстановить ваш существующий.",
"create_new" : "Создать новый кошелёк", "create_new" : "Создать новый кошелёк",
"restore_wallet" : "Восстановить кошелёк", "restore_wallet" : "Восстановить кошелёк",
@ -212,7 +212,7 @@
"send_name" : "Имя", "send_name" : "Имя",
"send_got_it" : "Понял", "send_got_it" : "Понял",
"send_sending" : "Отправка...", "send_sending" : "Отправка...",
"send_success" : "Ваш Monero был успешно отправлен", "send_success" : "Ваш ${crypto} был успешно отправлен",
"settings_title" : "Настройки", "settings_title" : "Настройки",
@ -282,6 +282,7 @@
"transaction_details_date" : "Дата", "transaction_details_date" : "Дата",
"transaction_details_height" : "Высота", "transaction_details_height" : "Высота",
"transaction_details_amount" : "Сумма", "transaction_details_amount" : "Сумма",
"transaction_details_fee" : "Комиссия",
"transaction_details_copied" : "${title} скопировано в буфер обмена", "transaction_details_copied" : "${title} скопировано в буфер обмена",
"transaction_details_recipient_address" : "Адрес получателя", "transaction_details_recipient_address" : "Адрес получателя",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Подождите, пока ваш кошелек синхронизируется", "exchange_sync_alert_content" : "Подождите, пока ваш кошелек синхронизируется",
"pre_seed_title" : "ВАЖНО", "pre_seed_title" : "ВАЖНО",
"pre_seed_description" : "На следующей странице вы увидите серию из 25 слов. Это ваша уникальная и личная мнемоническая фраза, и это ЕДИНСТВЕННЫЙ способ восстановить свой кошелек в случае потери или неисправности. ВАМ необходимо записать ее и хранить в надежном месте вне приложения Cake Wallet.", "pre_seed_description" : "На следующей странице вы увидите серию из ${words} слов. Это ваша уникальная и личная мнемоническая фраза, и это ЕДИНСТВЕННЫЙ способ восстановить свой кошелек в случае потери или неисправности. ВАМ необходимо записать ее и хранить в надежном месте вне приложения Cake Wallet.",
"pre_seed_button_text" : "Понятно. Покажите мнемоническую фразу", "pre_seed_button_text" : "Понятно. Покажите мнемоническую фразу",
"xmr_to_error" : "Ошибка XMR.TO", "xmr_to_error" : "Ошибка XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Цветовая тема", "color_theme" : "Цветовая тема",
"light_theme" : "Светлая", "light_theme" : "Светлая",
"bright_theme" : "Яркая", "bright_theme" : "Яркая",
"dark_theme" : "Темная" "dark_theme" : "Темная",
"enter_your_note" : "Введите примечание…",
"note_optional" : "Примечание (необязательно)",
"note_tap_to_change" : "Примечание (нажмите для изменения)",
"transaction_key" : "Ключ транзакции",
"confirmations" : "Подтверждения",
"recipient_address" : "Адрес получателя"
} }

View file

@ -1,7 +1,7 @@
{ {
"welcome" : "Вітаємо в", "welcome" : "Вітаємо в",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "В самому зручному гаманці для Monero", "first_wallet_text" : "В самому зручному гаманці для Monero та Bitcoin",
"please_make_selection" : "Оберіть спосіб створення гаманця: створити новий чи відновити ваш існуючий.", "please_make_selection" : "Оберіть спосіб створення гаманця: створити новий чи відновити ваш існуючий.",
"create_new" : "Створити новий гаманець", "create_new" : "Створити новий гаманець",
"restore_wallet" : "Відновити гаманець", "restore_wallet" : "Відновити гаманець",
@ -212,7 +212,7 @@
"send_name" : "Ім'я", "send_name" : "Ім'я",
"send_got_it" : "Зрозумів", "send_got_it" : "Зрозумів",
"send_sending" : "Відправлення...", "send_sending" : "Відправлення...",
"send_success" : "Ваш Monero успішно надісланий", "send_success" : "Ваш ${crypto} успішно надісланий",
"settings_title" : "Налаштування", "settings_title" : "Налаштування",
@ -282,6 +282,7 @@
"transaction_details_date" : "Дата", "transaction_details_date" : "Дата",
"transaction_details_height" : "Висота", "transaction_details_height" : "Висота",
"transaction_details_amount" : "Сума", "transaction_details_amount" : "Сума",
"transaction_details_fee" : "Комісія",
"transaction_details_copied" : "${title} скопійовано в буфер обміну", "transaction_details_copied" : "${title} скопійовано в буфер обміну",
"transaction_details_recipient_address" : "Адреса отримувача", "transaction_details_recipient_address" : "Адреса отримувача",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Зачекайте, поки ваш гаманець не синхронізується", "exchange_sync_alert_content" : "Зачекайте, поки ваш гаманець не синхронізується",
"pre_seed_title" : "ВАЖЛИВО", "pre_seed_title" : "ВАЖЛИВО",
"pre_seed_description" : "На наступній сторінці ви побачите серію з 25 слів. Це ваша унікальна та приватна мнемонічна фраза, і це ЄДИНИЙ спосіб відновити ваш гаманець на випадок втрати або несправності. ВАМ необхідно записати її та зберігати в безпечному місці поза програмою Cake Wallet.", "pre_seed_description" : "На наступній сторінці ви побачите серію з ${words} слів. Це ваша унікальна та приватна мнемонічна фраза, і це ЄДИНИЙ спосіб відновити ваш гаманець на випадок втрати або несправності. ВАМ необхідно записати її та зберігати в безпечному місці поза програмою Cake Wallet.",
"pre_seed_button_text" : "Зрозуміло. Покажіть мнемонічну фразу", "pre_seed_button_text" : "Зрозуміло. Покажіть мнемонічну фразу",
"xmr_to_error" : "Помилка XMR.TO", "xmr_to_error" : "Помилка XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Кольорова тема", "color_theme" : "Кольорова тема",
"light_theme" : "Світла", "light_theme" : "Світла",
"bright_theme" : "Яскрава", "bright_theme" : "Яскрава",
"dark_theme" : "Темна" "dark_theme" : "Темна",
"enter_your_note" : "Введіть примітку…",
"note_optional" : "Примітка (необов’язково)",
"note_tap_to_change" : "Примітка (натисніть для зміни)",
"transaction_key" : "Ключ транзакції",
"confirmations" : "Підтвердження",
"recipient_address" : "Адреса одержувача"
} }

View file

@ -1,7 +1,7 @@
{ {
"welcome" : "歡迎來到", "welcome" : "歡迎來到",
"cake_wallet" : "Cake Wallet", "cake_wallet" : "Cake Wallet",
"first_wallet_text" : "很棒的钱包 对于 Monero", "first_wallet_text" : "很棒的Monero和比特幣錢包",
"please_make_selection" : "请在下面进行选择 创建或恢复您的钱包.", "please_make_selection" : "请在下面进行选择 创建或恢复您的钱包.",
"create_new" : "创建新钱包", "create_new" : "创建新钱包",
"restore_wallet" : "恢复钱包", "restore_wallet" : "恢复钱包",
@ -212,7 +212,7 @@
"send_name" : "名稱", "send_name" : "名稱",
"send_got_it" : "得到它了", "send_got_it" : "得到它了",
"send_sending" : "正在發送...", "send_sending" : "正在發送...",
"send_success" : "你Monero已成功發送", "send_success" : "你${crypto}已成功發送",
"settings_title" : "设定值", "settings_title" : "设定值",
@ -282,6 +282,7 @@
"transaction_details_date" : "日期", "transaction_details_date" : "日期",
"transaction_details_height" : "高度", "transaction_details_height" : "高度",
"transaction_details_amount" : "量", "transaction_details_amount" : "量",
"transaction_details_fee" : "費用",
"transaction_details_copied" : "${title} 复制到剪贴板", "transaction_details_copied" : "${title} 复制到剪贴板",
"transaction_details_recipient_address" : "收件人地址", "transaction_details_recipient_address" : "收件人地址",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "請等待,直到您的錢包同步", "exchange_sync_alert_content" : "請等待,直到您的錢包同步",
"pre_seed_title" : "重要", "pre_seed_title" : "重要",
"pre_seed_description" : "在下一頁上,您將看到一系列25個單詞。 這是您獨特的私人種子,是丟失或出現故障時恢復錢包的唯一方法。 您有責任將其寫下並存儲在Cake Wallet應用程序外部的安全地方。", "pre_seed_description" : "在下一頁上,您將看到一系列${words}個單詞。 這是您獨特的私人種子,是丟失或出現故障時恢復錢包的唯一方法。 您有責任將其寫下並存儲在Cake Wallet應用程序外部的安全地方。",
"pre_seed_button_text" : "我明白。 給我看我的種子", "pre_seed_button_text" : "我明白。 給我看我的種子",
"xmr_to_error" : "XMR.TO錯誤", "xmr_to_error" : "XMR.TO錯誤",
@ -428,5 +429,11 @@
"color_theme" : "顏色主題", "color_theme" : "顏色主題",
"light_theme" : "光", "light_theme" : "光",
"bright_theme" : "亮", "bright_theme" : "亮",
"dark_theme" : "黑暗" "dark_theme" : "黑暗",
"enter_your_note" : "輸入您的筆記...",
"note_optional" : "注意(可選)",
"note_tap_to_change" : "注意(輕按即可更改)",
"transaction_key" : "交易密碼",
"confirmations" : "確認書",
"recipient_address" : "收件人地址"
} }