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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -47,7 +47,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final out = vin['tx']['vout'][vout] as Map;
final outAddresses =
(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) {
direction = TransactionDirection.outgoing;
@ -58,7 +58,7 @@ class BitcoinTransactionInfo extends TransactionInfo {
final outAddresses =
out['scriptPubKey']['addresses'] as List<Object> ?? [];
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;
if ((direction == TransactionDirection.incoming && ntrs.isNotEmpty) ||

View file

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

View file

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

View file

@ -22,9 +22,8 @@ String jsonrpcparams(List<Object> params) {
}
String jsonrpc(
{String method, List<Object> params, int id, double version = 2.0}) =>
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json
.encode(params)}}\n';
{String method, List<Object> params, int id, double version = 2.0}) =>
'{"jsonrpc": "$version", "method": "$method", "id": "$id", "params": ${json.encode(params)}}\n';
class SocketTask {
SocketTask({this.completer, this.isSubscription, this.subject});
@ -38,7 +37,8 @@ class ElectrumClient {
ElectrumClient()
: _id = 0,
_isConnected = false,
_tasks = {};
_tasks = {},
unterminatedString = '';
static const connectionTimeout = Duration(seconds: 5);
static const aliveTimerDuration = Duration(seconds: 2);
@ -75,24 +75,43 @@ class ElectrumClient {
socket.listen((Uint8List event) {
try {
_handleResponse(utf8.decode(event.toList()));
final response =
json.decode(utf8.decode(event.toList())) as Map<String, Object>;
_handleResponse(response);
} on FormatException catch (e) {
final msg = e.message.toLowerCase();
if (msg == 'Unterminated string'.toLowerCase()) {
unterminatedString = e.source as String;
}
if (msg == 'Unexpected character'.toLowerCase()) {
if (e.source is String) {
unterminatedString += e.source as String;
}
if (msg.contains("not a subtype of type")) {
unterminatedString += e.source as String;
return;
}
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;
}
} catch (e) {
print(e);
print(e.toString());
}
}, onError: (Object error) {
print(error.toString());
@ -153,7 +172,7 @@ class ElectrumClient {
});
Future<List<Map<String, dynamic>>> getListUnspentWithAddress(
String address) =>
String address) =>
call(
method: 'blockchain.scripthash.listunspent',
params: [scriptHash(address)]).then((dynamic result) {
@ -204,7 +223,7 @@ class ElectrumClient {
});
Future<Map<String, Object>> getTransactionRaw(
{@required String hash}) async =>
{@required String hash}) async =>
call(method: 'blockchain.transaction.get', params: [hash, true])
.then((dynamic result) {
if (result is Map<String, Object>) {
@ -233,25 +252,25 @@ class ElectrumClient {
}
Future<String> broadcastTransaction(
{@required String transactionRaw}) async =>
{@required String transactionRaw}) async =>
call(method: 'blockchain.transaction.broadcast', params: [transactionRaw])
.then((dynamic result) {
if (result is String) {
return result;
}
print(result);
return '';
});
Future<Map<String, dynamic>> getMerkle(
{@required String hash, @required int height}) async =>
{@required String hash, @required int height}) async =>
await call(
method: 'blockchain.transaction.get_merkle',
params: [hash, height]) as Map<String, dynamic>;
Future<Map<String, dynamic>> getHeader({@required int height}) async =>
await call(method: 'blockchain.block.get_header', params: [height])
as Map<String, dynamic>;
as Map<String, dynamic>;
Future<double> estimatefee({@required int p}) =>
call(method: 'blockchain.estimatefee', params: [p])
@ -275,9 +294,10 @@ class ElectrumClient {
params: [scripthash]);
}
BehaviorSubject<T> subscribe<T>({@required String id,
@required String method,
List<Object> params = const []}) {
BehaviorSubject<T> subscribe<T>(
{@required String id,
@required String method,
List<Object> params = const []}) {
final subscription = BehaviorSubject<T>();
_regisrySubscription(id, subscription);
socket.write(jsonrpc(method: method, id: _id, params: params));
@ -296,9 +316,10 @@ class ElectrumClient {
return completer.future;
}
Future<dynamic> callWithTimeout({String method,
List<Object> params = const [],
int timeout = 2000}) async {
Future<dynamic> callWithTimeout(
{String method,
List<Object> params = const [],
int timeout = 2000}) async {
final completer = Completer<dynamic>();
_id += 1;
final id = _id;
@ -325,9 +346,8 @@ class ElectrumClient {
onConnectionStatusChange = null;
}
void _regisryTask(int id, Completer completer) =>
_tasks[id.toString()] =
SocketTask(completer: completer, isSubscription: false);
void _regisryTask(int id, Completer completer) => _tasks[id.toString()] =
SocketTask(completer: completer, isSubscription: false);
void _regisrySubscription(String id, BehaviorSubject subject) =>
_tasks[id] = SocketTask(subject: subject, isSubscription: true);
@ -371,22 +391,20 @@ class ElectrumClient {
_isConnected = isConnected;
}
void _handleResponse(String response) {
print('Response: $response');
final jsoned = json.decode(response) as Map<String, Object>;
// print(jsoned);
final method = jsoned['method'];
final id = jsoned['id'] as String;
final result = jsoned['result'];
void _handleResponse(Map<String, Object> response) {
final method = response['method'];
final id = response['id'] as String;
final result = response['result'];
if (method is String) {
_methodHandler(method: method, request: jsoned);
_methodHandler(method: method, request: response);
return;
}
_finish(id, result);
}
}
// FIXME: move me
bool isJSONStringCorrect(String source) {
try {

View file

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

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/settings.dart';
import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart';
import 'package:cake_wallet/src/screens/trade_details/trade_details_page.dart';
import 'package:cake_wallet/src/screens/transaction_details/transaction_details_page.dart';
import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart';
import 'package:cake_wallet/src/screens/exchange/exchange_page.dart';
@ -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/rescan_view_model.dart';
import 'package:cake_wallet/view_model/setup_pin_code_view_model.dart';
import 'package:cake_wallet/view_model/transaction_details_view_model.dart';
import 'package:cake_wallet/view_model/trade_details_view_model.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_edit_or_create_view_model.dart';
import 'package:cake_wallet/view_model/auth_view_model.dart';
import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
@ -327,7 +330,8 @@ Future setup(
(ContactRecord contact, _) =>
ContactViewModel(contactSource, contact: contact));
getIt.registerFactory(() => ContactListViewModel(contactSource));
getIt.registerFactory(
() => ContactListViewModel(contactSource, walletInfoSource));
getIt.registerFactoryParam<ContactListPage, bool, void>(
(bool isEditable, _) => ContactListPage(getIt.get<ContactListViewModel>(),
@ -355,7 +359,8 @@ Future setup(
getIt.get<AppStore>().wallet,
tradesSource,
getIt.get<ExchangeTemplateStore>(),
getIt.get<TradesStore>()));
getIt.get<TradesStore>(),
getIt.get<AppStore>().settingsStore));
getIt.registerFactory(() => ExchangeTradeViewModel(
wallet: getIt.get<AppStore>().wallet,
@ -415,11 +420,17 @@ Future setup(
getIt.registerFactoryParam<WalletRestorePage, WalletType, void>((type, _) =>
WalletRestorePage(getIt.get<WalletRestoreViewModel>(param1: type)));
getIt
.registerFactoryParam<TransactionDetailsViewModel, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => TransactionDetailsViewModel(
transactionInfo: transactionInfo,
transactionDescriptionBox: transactionDescriptionBox,
settingsStore: getIt.get<SettingsStore>()));
getIt.registerFactoryParam<TransactionDetailsPage, TransactionInfo, void>(
(TransactionInfo transactionInfo, _) => TransactionDetailsPage(
transactionInfo,
getIt.get<SettingsStore>().shouldSaveRecipientAddress,
transactionDescriptionBox));
transactionDetailsViewModel:
getIt.get<TransactionDetailsViewModel>(param1: transactionInfo)));
getIt.registerFactoryParam<NewWalletTypePage,
void Function(BuildContext, WalletType), bool>(
@ -428,4 +439,10 @@ Future setup(
getIt.registerFactoryParam<PreSeedPage, WalletType, void>(
(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 {
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);
static const all = [
BalanceDisplayMode.fullBalance,
BalanceDisplayMode.availableBalance,
BalanceDisplayMode.hiddenBalance
BalanceDisplayMode.hiddenBalance,
BalanceDisplayMode.displayableBalance,
];
static const fullBalance = BalanceDisplayMode(raw: 0, title: 'Full Balance');
static const availableBalance =
BalanceDisplayMode(raw: 1, title: 'Available Balance');
static const hiddenBalance =
BalanceDisplayMode(raw: 2, title: 'Hidden Balance');
static const displayableBalance =
BalanceDisplayMode(raw: 3, title: 'Displayable Balance');
static BalanceDisplayMode deserialize({int raw}) {
switch (raw) {
@ -25,6 +26,8 @@ class BalanceDisplayMode extends EnumerableItem<int> with Serializable<int> {
return availableBalance;
case 2:
return hiddenBalance;
case 3:
return displayableBalance;
default:
return null;
}
@ -39,6 +42,8 @@ class BalanceDisplayMode extends EnumerableItem<int> with Serializable<int> {
return S.current.xmr_available_balance;
case BalanceDisplayMode.hiddenBalance:
return S.current.xmr_hidden;
case BalanceDisplayMode.displayableBalance:
return S.current.displayable;
default:
return '';
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,3 +1,4 @@
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:hive/hive.dart';
part 'wallet_type.g.dart';
@ -59,3 +60,14 @@ String walletTypeToDisplayName(WalletType type) {
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:flutter/material.dart';
import 'package:flutter/services.dart';
@ -54,11 +55,11 @@ void main() async {
TransactionDescription.boxName,
encryptionKey: transactionDescriptionsBoxKey);
final trades =
await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
await Hive.openBox<Trade>(Trade.boxName, encryptionKey: tradesBoxKey);
final walletInfoSource = await Hive.openBox<WalletInfo>(WalletInfo.boxName);
final templates = await Hive.openBox<Template>(Template.boxName);
final exchangeTemplates =
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
await Hive.openBox<ExchangeTemplate>(ExchangeTemplate.boxName);
await initialSetup(
sharedPreferences: await SharedPreferences.getInstance(),
nodes: nodes,
@ -69,7 +70,7 @@ void main() async {
templates: templates,
exchangeTemplates: exchangeTemplates,
transactionDescriptions: transactionDescriptions,
initialMigrationVersion: 4);
initialMigrationVersion: 5);
runApp(App());
} catch (e) {
runApp(MaterialApp(
@ -77,24 +78,24 @@ void main() async {
home: Scaffold(
body: Container(
margin:
EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
EdgeInsets.only(top: 50, left: 20, right: 20, bottom: 20),
child: Text(
'Error:\n${e.toString()}',
style: TextStyle(fontSize: 22),
)))));
}
}
Future<void> initialSetup(
{@required SharedPreferences sharedPreferences,
@required Box<Node> nodes,
@required Box<WalletInfo> walletInfoSource,
@required Box<Contact> contactSource,
@required Box<Trade> tradesSource,
// @required FiatConvertationService fiatConvertationService,
@required Box<Template> templates,
@required Box<ExchangeTemplate> exchangeTemplates,
@required Box<TransactionDescription> transactionDescriptions,
int initialMigrationVersion = 5}) async {
Future<void> initialSetup({@required SharedPreferences sharedPreferences,
@required Box<Node> nodes,
@required Box<WalletInfo> walletInfoSource,
@required Box<Contact> contactSource,
@required Box<Trade> tradesSource,
// @required FiatConvertationService fiatConvertationService,
@required Box<Template> templates,
@required Box<ExchangeTemplate> exchangeTemplates,
@required Box<TransactionDescription> transactionDescriptions,
int initialMigrationVersion = 6}) async {
await defaultSettingsMigration(
version: initialMigrationVersion,
sharedPreferences: sharedPreferences,
@ -122,7 +123,9 @@ class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
final settingsStore = getIt.get<AppStore>().settingsStore;
final settingsStore = getIt
.get<AppStore>()
.settingsStore;
final statusBarColor = Colors.transparent;
final authenticationStore = getIt.get<AuthenticationStore>();
final initialRoute = authenticationStore.state == AuthenticationState.denied
@ -132,11 +135,11 @@ class App extends StatelessWidget {
return Observer(builder: (BuildContext context) {
final currentTheme = settingsStore.currentTheme;
final statusBarBrightness = currentTheme.type == ThemeType.dark
? Brightness.light
: Brightness.dark;
? Brightness.light
: Brightness.dark;
final statusBarIconBrightness = currentTheme.type == ThemeType.dark
? Brightness.light
: Brightness.dark;
? Brightness.light
: Brightness.dark;
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: statusBarColor,
statusBarBrightness: statusBarBrightness,

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,68 +1,109 @@
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:flutter/material.dart';
import 'package:cake_wallet/view_model/wallet_address_list/wallet_address_list_view_model.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_widget.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
class AddressPage extends StatelessWidget {
AddressPage({@required this.addressListViewModel});
AddressPage({@required this.addressListViewModel})
: _cryptoAmountFocus = FocusNode();
final WalletAddressListViewModel addressListViewModel;
final FocusNode _cryptoAmountFocus;
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: QRWidget(addressListViewModel: addressListViewModel),
)),
GestureDetector(
onTap: () => Navigator.of(context).pushNamed(Routes.receive),
child: Container(
height: 50,
padding: EdgeInsets.only(left: 24, right: 12),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(25)),
border: Border.all(
color: Theme.of(context).textTheme.subhead.color,
width: 1),
color: Theme.of(context).buttonColor),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Observer(
builder: (_) => Text(
addressListViewModel.hasAccounts
? S.of(context).accounts_subaddresses
: S.of(context).addresses,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
return KeyboardActions(
autoScroll: false,
disableScroll: true,
tapOutsideToDismiss: true,
config: KeyboardActionsConfig(
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
keyboardBarColor:
Theme.of(context).accentTextTheme.body2.backgroundColor,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
)
]),
child: Container(
height: 1,
padding: EdgeInsets.fromLTRB(24, 24, 24, 32),
child: Column(
children: <Widget>[
Expanded(
child: Center(
child: QRWidget(
addressListViewModel: addressListViewModel,
amountTextFieldFocusNode: _cryptoAmountFocus,
isAmountFieldShow: !addressListViewModel.hasAccounts),
)),
Observer(builder: (_) {
return addressListViewModel.hasAddressList
? GestureDetector(
onTap: () =>
Navigator.of(context).pushNamed(Routes.receive),
child: Container(
height: 50,
padding: EdgeInsets.only(left: 24, right: 12),
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(25)),
border: Border.all(
color:
Theme.of(context).textTheme.subhead.color,
width: 1),
color: Theme.of(context).buttonColor),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Observer(
builder: (_) => Text(
addressListViewModel.hasAccounts
? S
.of(context)
.accounts_subaddresses
: S.of(context).addresses,
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor),
)),
Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor),
)),
Icon(
Icons.arrow_forward_ios,
size: 14,
color: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor,
)
],
),
),
)
],
),
);
.backgroundColor,
)
],
),
),
)
: 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:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
@ -10,77 +11,87 @@ class BalancePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapUp: (_) {
if (dashboardViewModel.balanceViewModel.canReverse) {
dashboardViewModel.balanceViewModel.isReversing = false;
}
},
onTapDown: (_) {
if (dashboardViewModel.balanceViewModel.canReverse) {
dashboardViewModel.balanceViewModel.isReversing = true;
}
},
child: Container(
color: Colors.transparent,
padding: EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Observer(builder: (_) {
return Text(
dashboardViewModel.balanceViewModel.currency.toString(),
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.accentTextTheme
.display2
.backgroundColor,
height: 1),
);
}),
Observer(builder: (_) {
return Text(
dashboardViewModel.balanceViewModel.displayMode.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.cryptoBalance,
style: TextStyle(
fontSize: 54,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor,
height: 1),
maxLines: 1,
textAlign: TextAlign.center);
}),
SizedBox(height: 10),
Observer(builder: (_) {
return Text(dashboardViewModel.balanceViewModel.fiatBalance,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w500,
color: Theme.of(context).indicatorColor,
height: 1),
textAlign: TextAlign.center);
}),
],
),
));
return Container(
color: Colors.transparent,
padding: EdgeInsets.all(24),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Observer(builder: (_) {
return Text(
dashboardViewModel.balanceViewModel.currency.toString(),
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.accentTextTheme
.display2
.backgroundColor,
height: 1),
);
}),
SizedBox(height: 10),
Observer(builder: (_) {
return Text(
'${dashboardViewModel.balanceViewModel.availableBalanceLabel} (${dashboardViewModel.balanceViewModel.availableFiatBalance.toString()})',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.accentTextTheme
.display2
.backgroundColor,
height: 1),
);
}),
SizedBox(height: 10),
Observer(builder: (_) {
return AutoSizeText(
dashboardViewModel.balanceViewModel.availableBalance,
style: TextStyle(
fontSize: 54,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor,
height: 1),
maxLines: 1,
textAlign: TextAlign.center);
}),
SizedBox(height: 10),
Observer(builder: (_) {
return Text(
'${dashboardViewModel.balanceViewModel.additionalBalanceLabel} (${dashboardViewModel.balanceViewModel.additionalFiatBalance.toString()})',
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w600,
color: Theme.of(context)
.accentTextTheme
.display2
.backgroundColor,
height: 1),
);
}),
SizedBox(height: 10),
Observer(builder: (_) {
return AutoSizeText(
dashboardViewModel.balanceViewModel.additionalBalance
.toString(),
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.accentTextTheme
.display3
.backgroundColor,
height: 1),
maxLines: 1,
textAlign: TextAlign.center);
}),
],
),
);
}
}

View file

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

View file

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

View file

@ -6,8 +6,6 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/entities/crypto_currency.dart';
import 'package:cake_wallet/exchange/exchange_provider_description.dart';
import 'package:cake_wallet/src/screens/exchange_trade/information_page.dart';
import 'package:cake_wallet/src/screens/send/widgets/confirm_sending_alert.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
@ -172,36 +170,14 @@ class ExchangeTradeState extends State<ExchangeTradeForm> {
),
itemBuilder: (context, index) {
final item = widget.exchangeTradeViewModel.items[index];
String value;
final value = item.data ?? fetchingLabel;
final content = Observer(builder: (_) {
switch (index) {
case 0:
value =
'${widget.exchangeTradeViewModel.trade.id ?? fetchingLabel}';
break;
case 1:
value =
'${widget.exchangeTradeViewModel.trade.amount ?? fetchingLabel}';
break;
case 2:
value =
'${widget.exchangeTradeViewModel.trade.state ?? fetchingLabel}';
break;
case 3:
value = widget.exchangeTradeViewModel.trade
.inputAddress ??
fetchingLabel;
break;
}
return StandartListRow(
title: item.title,
value: value,
valueFontSize: 14,
image: item.isCopied ? copyImage : null,
);
});
final content = StandartListRow(
title: item.title,
value: value,
valueFontSize: 14,
image: item.isCopied ? copyImage : null,
);
return item.isCopied
? Builder(
@ -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
? Padding(
padding: EdgeInsets.only(top: 60),
padding: EdgeInsets.only(top: 40),
child: Row(
children: <Widget>[
Expanded(
@ -109,7 +109,7 @@ class QRWidget extends StatelessWidget {
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 16,
fontSize: 15,
fontWeight: FontWeight.w500,
color: Theme.of(context).accentTextTheme.
display3.backgroundColor),

View file

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

View file

@ -31,6 +31,7 @@ class SendPage extends BasePage {
: _addressController = TextEditingController(),
_cryptoAmountController = TextEditingController(),
_fiatAmountController = TextEditingController(),
_noteController = TextEditingController(),
_formKey = GlobalKey<FormState>(),
_cryptoAmountFocus = FocusNode(),
_fiatAmountFocus = FocusNode(),
@ -46,6 +47,7 @@ class SendPage extends BasePage {
final TextEditingController _addressController;
final TextEditingController _cryptoAmountController;
final TextEditingController _fiatAmountController;
final TextEditingController _noteController;
final GlobalKey<FormState> _formKey;
final FocusNode _cryptoAmountFocus;
final FocusNode _fiatAmountFocus;
@ -304,6 +306,30 @@ class SendPage extends BasePage {
fontWeight: FontWeight.w500,
fontSize: 14),
)),
Padding(
padding: EdgeInsets.only(top: 20),
child: BaseTextFormField(
controller: _noteController,
keyboardType: TextInputType.multiline,
maxLines: null,
borderColor: Theme.of(context)
.primaryTextTheme
.headline
.color,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
hintText: S.of(context).note_optional,
placeholderTextStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.headline
.decorationColor),
),
),
Observer(
builder: (_) => GestureDetector(
onTap: () =>
@ -313,6 +339,7 @@ class SendPage extends BasePage {
child: Row(
mainAxisAlignment:
MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
S
@ -326,23 +353,50 @@ class SendPage extends BasePage {
color: Colors.white)),
Container(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
sendViewModel
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
sendViewModel
.estimatedFee
.toString() +
' ' +
sendViewModel
' ' +
sendViewModel
.currency.title,
style: TextStyle(
fontSize: 12,
fontWeight:
style: TextStyle(
fontSize: 12,
fontWeight:
FontWeight.w600,
//color: Theme.of(context).primaryTextTheme.display2.color,
color:
//color: Theme.of(context).primaryTextTheme.display2.color,
color:
Colors.white)),
Padding(
padding:
EdgeInsets.only(top: 5),
child: Text(
sendViewModel
.estimatedFeeFiatAmount
+ ' ' +
sendViewModel
.fiat.title,
style: TextStyle(
fontSize: 12,
fontWeight:
FontWeight.w600,
color: Theme
.of(context)
.primaryTextTheme
.headline
.decorationColor))
),
],
),
Padding(
padding: EdgeInsets.only(
top: 2,
left: 5),
child: Icon(
Icons.arrow_forward_ios,
@ -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) {
if (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) {
if (state is FailureState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
@ -596,8 +664,14 @@ class SendPage extends BasePage {
amount: S.of(context).send_amount,
amountValue:
sendViewModel.pendingTransaction.amountFormatted,
fiatAmountValue: sendViewModel.pendingTransactionFiatAmount
+ ' ' + sendViewModel.fiat.title,
fee: S.of(context).send_fee,
feeValue: sendViewModel.pendingTransaction.feeFormatted,
feeFiatAmount: sendViewModel.pendingTransactionFeeFiatAmount
+ ' ' + sendViewModel.fiat.title,
recipientTitle: S.of(context).recipient_address,
recipientAddress: sendViewModel.address,
rightButtonText: S.of(context).ok,
leftButtonText: S.of(context).cancel,
actionRightButton: () {
@ -614,96 +688,17 @@ class SendPage extends BasePage {
}
if (state is TransactionCommitted) {
return Stack(
children: <Widget>[
Container(
color: Theme.of(context).backgroundColor,
child: Center(
child: Image.asset(
'assets/images/birthday_cake.png'),
),
),
Center(
child: Padding(
padding: EdgeInsets.only(
top: 220, left: 24, right: 24),
child: Text(
S.of(context).send_success(
sendViewModel.currency
.toString()),
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
decoration: TextDecoration.none,
),
),
),
),
Positioned(
left: 24,
right: 24,
bottom: 24,
child: PrimaryButton(
onPressed: () =>
Navigator.of(context).pop(),
text: S.of(context).send_got_it,
color: Theme.of(context)
.accentTextTheme
.body2
.color,
textColor: Colors.white))
],
);
return AlertWithOneAction(
alertTitle: '',
alertContent: S.of(context).send_success(
sendViewModel.currency
.toString()),
buttonText: S.of(context).ok,
buttonAction: () =>
Navigator.of(context).pop());
}
if (state is TransactionCommitting) {
return Stack(
children: <Widget>[
Container(
color: Theme.of(context).backgroundColor,
child: Center(
child: Image.asset(
'assets/images/birthday_cake.png'),
),
),
BackdropFilter(
filter: ImageFilter.blur(
sigmaX: 3.0, sigmaY: 3.0),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.backgroundColor
.withOpacity(0.25)),
child: Center(
child: Padding(
padding: EdgeInsets.only(top: 220),
child: Text(
S.of(context).send_sending,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
color: Theme.of(context)
.primaryTextTheme
.title
.color,
decoration: TextDecoration.none,
),
),
),
),
),
)
],
);
}
return Container();
return Offstage();
});
});
},
@ -746,6 +741,7 @@ class SendPage extends BasePage {
Future<void> _setTransactionPriority(BuildContext context) async {
final items = TransactionPriority.forWalletType(sendViewModel.walletType);
final selectedItem = items.indexOf(sendViewModel.transactionPriority);
final isShowScrollThumb = items.length > 3;
await showPopUp<void>(
builder: (_) => Picker(
@ -755,7 +751,6 @@ class SendPage extends BasePage {
mainAxisAlignment: MainAxisAlignment.center,
onItemSelected: (TransactionPriority priority) =>
sendViewModel.setTransactionPriority(priority),
isAlwaysShowScrollThumb: true,
),
context: context);
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,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/view_model/transaction_details_view_model.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/bitcoin/bitcoin_transaction_info.dart';
import 'package:cake_wallet/monero/monero_transaction_info.dart';
import 'package:cake_wallet/entities/transaction_info.dart';
import 'package:cake_wallet/src/widgets/standart_list_row.dart';
import 'package:cake_wallet/src/screens/transaction_details/standart_list_item.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
@ -29,114 +29,26 @@ String blockExplorerName(String inputName) {
}
class TransactionDetailsPage extends BasePage {
TransactionDetailsPage(this.transactionInfo, bool showRecipientAddress,
Box<TransactionDescription> transactionDescriptionBox)
: _items = [] {
final dateFormat = DateFormatter.withCurrentLocal();
final tx = transactionInfo;
if (tx is MoneroTransactionInfo) {
final items = [
StandartListItem(
title: S.current.transaction_details_transaction_id, value: tx.id),
StandartListItem(
title: S.current.transaction_details_date,
value: dateFormat.format(tx.date)),
StandartListItem(
title: S.current.transaction_details_height, value: '${tx.height}'),
StandartListItem(
title: S.current.transaction_details_amount,
value: tx.amountFormatted()),
StandartListItem(title: S.current.send_fee, value: tx.feeFormatted())
];
if (showRecipientAddress) {
final recipientAddress = transactionDescriptionBox.values
.firstWhere((val) => val.id == transactionInfo.id,
orElse: () => null)
?.recipientAddress;
if (recipientAddress?.isNotEmpty ?? false) {
items.add(StandartListItem(
title: S.current.transaction_details_recipient_address,
value: recipientAddress));
}
}
if (tx.key?.isNotEmpty ?? null) {
// FIXME: add translation
items.add(StandartListItem(title: 'Transaction Key', value: tx.key));
}
_items.addAll(items);
_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));
}
}
}
TransactionDetailsPage({this.transactionDetailsViewModel});
@override
String get title => S.current.transaction_details_title;
final TransactionInfo transactionInfo;
final List<StandartListItem> _items;
final TransactionDetailsViewModel transactionDetailsViewModel;
@override
Widget body(BuildContext context) {
return Container(
child: ListView.separated(
separatorBuilder: (context, index) => Container(
height: 1,
padding: EdgeInsets.only(left: 24),
color: Theme.of(context).backgroundColor,
child: Container(
height: 1,
color:
Theme.of(context).primaryTextTheme.title.backgroundColor,
),
),
itemCount: _items.length,
itemBuilder: (context, index) {
final item = _items[index];
return SectionStandardList(
sectionCount: 1,
itemCounter: (int _) => transactionDetailsViewModel.items.length,
itemBuilder: (_, __, index) {
final item = transactionDetailsViewModel.items[index];
if (item is StandartListItem) {
final isFinalBlockExplorerItem =
index == _items.length - 1 ? true : false;
index == transactionDetailsViewModel.items.length - 1
? true
: false;
if (isFinalBlockExplorerItem == false) {
return GestureDetector(
onTap: () {
@ -144,10 +56,8 @@ class TransactionDetailsPage extends BasePage {
showBar<void>(context,
S.of(context).transaction_details_copied(item.title));
},
child: StandartListRow(
title: '${item.title}:',
value: item.value,
isDrawBottom: isFinalBlockExplorerItem),
child:
StandartListRow(title: '${item.title}:', value: item.value),
);
} else {
return GestureDetector(
@ -157,11 +67,20 @@ class TransactionDetailsPage extends BasePage {
child: StandartListRow(
title: '${item.title}:',
value:
"View transaction on ${blockExplorerName(item.value)}",
isDrawBottom: isFinalBlockExplorerItem),
"View transaction on ${blockExplorerName(item.value)}"),
);
}
}),
);
}
if (item is TextFieldListItem) {
return TextFieldListRow(
title: item.title,
value: item.value,
onSubmitted: item.onSubmitted,
);
}
return null;
});
}
}

View file

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

View file

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

View file

@ -7,6 +7,7 @@ class BaseAlertDialog extends StatelessWidget {
String get contentText => '';
String get leftActionButtonText => '';
String get rightActionButtonText => '';
bool get isDividerExists => false;
VoidCallback get actionLeft => () {};
VoidCallback get actionRight => () {};
bool get barrierDismissible => true;
@ -127,18 +128,27 @@ class BaseAlertDialog extends StatelessWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(24, 32, 24, 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
title(context),
Padding(
padding: EdgeInsets.only(top: 8),
child: content(context),
)
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Padding(
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),
)
],
),
Container(
height: 1,

View file

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

View file

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

View file

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

View file

@ -7,77 +7,60 @@ class StandartListRow extends StatelessWidget {
this.value,
this.titleFontSize = 14,
this.valueFontSize = 16,
this.image,
this.isDrawBottom = false});
this.image});
final String title;
final String value;
final double titleFontSize;
final double valueFontSize;
final Image image;
final bool isDrawBottom;
@override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
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),
Padding(
padding: const EdgeInsets.only(top: 12),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(value,
style: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.title
.color)),
),
image != null
? Padding(
padding: EdgeInsets.only(left: 24),
child: image,
)
: Offstage()
],
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),
Padding(
padding: const EdgeInsets.only(top: 12),
child: Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: Text(value,
style: TextStyle(
fontSize: valueFontSize,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.title
.color)),
),
)
]),
),
),
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(),
],
image != null
? Padding(
padding: EdgeInsets.only(left: 24),
child: image,
)
: Offstage()
],
),
)
]),
),
);
}
}

View file

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

View file

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

View file

@ -1,4 +1,7 @@
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:mobx/mobx.dart';
import 'package:cake_wallet/entities/contact_record.dart';
@ -11,15 +14,22 @@ class ContactListViewModel = ContactListViewModelBase
with _$ContactListViewModel;
abstract class ContactListViewModelBase with Store {
ContactListViewModelBase(this.contactSource)
: contacts = ObservableList<ContactRecord>() {
ContactListViewModelBase(this.contactSource, this.walletInfoSource)
: 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(
contacts, (Contact contact) => ContactRecord(contactSource, contact),
initialFire: true);
}
final Box<Contact> contactSource;
final Box<WalletInfo> walletInfoSource;
final ObservableList<ContactRecord> contacts;
final List<WalletContact> walletContacts;
StreamSubscription<BoxEvent> _subscription;
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/core/wallet_base.dart';
import 'package:cake_wallet/entities/balance.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/entities/balance_display_mode.dart';
import 'package:cake_wallet/entities/calculate_fiat_amount.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/dashboard/fiat_conversion_store.dart';
import 'package:flutter/cupertino.dart';
@ -18,19 +21,46 @@ abstract class BalanceViewModelBase with Store {
BalanceViewModelBase(
{@required this.appStore,
@required this.settingsStore,
@required this.fiatConvertationStore})
: isReversing = false;
@required this.fiatConvertationStore}) {
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 SettingsStore settingsStore;
final FiatConversionStore fiatConvertationStore;
bool get canReverse =>
(appStore.wallet.balance.availableModes as List).length > 1;
bool get canReverse => false;
@observable
bool isReversing;
@observable
Balance balance;
@observable
WalletBase<Balance> wallet;
@computed
double get price => fiatConvertationStore.prices[appStore.wallet.currency];
@ -45,66 +75,97 @@ abstract class BalanceViewModelBase with Store {
: savedDisplayMode;
@computed
String get cryptoBalance {
final walletBalance = _walletBalance;
var balance = '---';
if (displayMode == BalanceDisplayMode.availableBalance) {
balance = walletBalance.unlockedBalance ?? '0.0';
String get availableBalanceLabel {
if (wallet.type == WalletType.monero) {
return S.current.xmr_available_balance;
}
if (displayMode == BalanceDisplayMode.fullBalance) {
balance = walletBalance.totalBalance ?? '0.0';
}
return balance;
return S.current.confirmed;
}
@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 fiatCurrency = settingsStore.fiatCurrency;
var balance = '---';
final totalBalance =
_getFiatBalance(price: price, cryptoAmount: walletBalance.totalBalance);
final unlockedBalance = _getFiatBalance(
price: price, cryptoAmount: walletBalance.unlockedBalance);
if (displayMode == BalanceDisplayMode.availableBalance) {
balance = fiatCurrency.toString() + ' ' + unlockedBalance ?? '0.00';
if (settingsStore.balanceDisplayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
if (displayMode == BalanceDisplayMode.fullBalance) {
balance = fiatCurrency.toString() + ' ' + totalBalance ?? '0.00';
}
return balance;
return fiatCurrency.toString() +
' ' +
_getFiatBalance(
price: price,
cryptoAmount: walletBalance.formattedAvailableBalance);
}
@computed
WalletBalance get _walletBalance {
final _wallet = appStore.wallet;
String get additionalFiatBalance {
final walletBalance = _walletBalance;
final fiatCurrency = settingsStore.fiatCurrency;
if (_wallet is MoneroWallet) {
return WalletBalance(
unlockedBalance: _wallet.balance.formattedUnlockedBalance,
totalBalance: _wallet.balance.formattedFullBalance);
if (settingsStore.balanceDisplayMode == BalanceDisplayMode.hiddenBalance) {
return '---';
}
if (_wallet is BitcoinWallet) {
return WalletBalance(
unlockedBalance: _wallet.balance.availableBalanceFormatted,
totalBalance: _wallet.balance.totalFormatted);
}
return null;
return fiatCurrency.toString() +
' ' +
_getFiatBalance(
price: price,
cryptoAmount: walletBalance.formattedAdditionalBalance);
}
@computed
Balance get _walletBalance => wallet.balance;
@computed
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}) {
if (cryptoAmount == null) {
return '0.00';

View file

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

View file

@ -39,20 +39,11 @@ abstract class ExchangeTradeViewModelBase with Store {
}
items = ObservableList<ExchangeTradeItem>();
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),
]);
_updateItems();
_updateTrade();
_timer = Timer.periodic(Duration(seconds: 20), (_) async => _updateTrade());
}
@ -95,8 +86,27 @@ abstract class ExchangeTradeViewModelBase with Store {
}
trade = updatedTrade;
_updateItems();
} catch (e) {
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/entities/crypto_currency.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/limits_state.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:mobx/mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
@ -27,8 +30,8 @@ part 'exchange_view_model.g.dart';
class ExchangeViewModel = ExchangeViewModelBase with _$ExchangeViewModel;
abstract class ExchangeViewModelBase with Store {
ExchangeViewModelBase(
this.wallet, this.trades, this._exchangeTemplateStore, this.tradesStore) {
ExchangeViewModelBase(this.wallet, this.trades, this._exchangeTemplateStore,
this.tradesStore, this._settingsStore) {
providerList = [
XMRTOExchangeProvider(),
ChangeNowExchangeProvider(),
@ -104,10 +107,6 @@ abstract class ExchangeViewModelBase with Store {
@observable
bool isReceiveAmountEntered;
Limits limits;
NumberFormat _cryptoNumberFormat;
@computed
SyncStatus get status => wallet.syncStatus;
@ -115,6 +114,15 @@ abstract class ExchangeViewModelBase with Store {
ObservableList<ExchangeTemplate> get templates =>
_exchangeTemplateStore.templates;
bool get hasAllAmount =>
wallet.type == WalletType.bitcoin && depositCurrency == wallet.currency;
Limits limits;
NumberFormat _cryptoNumberFormat;
SettingsStore _settingsStore;
@action
void changeProvider({ExchangeProvider provider}) {
this.provider = provider;
@ -264,9 +272,8 @@ abstract class ExchangeViewModelBase with Store {
await trades.add(trade);
tradeState = TradeIsCreatedSuccessfully(trade: trade);
} catch (e) {
tradeState = TradeIsCreatedFailure(
title: provider.title,
error: e.toString());
tradeState =
TradeIsCreatedFailure(title: provider.title, error: e.toString());
}
}
} else {
@ -291,6 +298,22 @@ abstract class ExchangeViewModelBase with Store {
_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 addTemplate(

View file

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

View file

@ -6,15 +6,13 @@ class PickerListItem<ItemType> extends SettingsListItem {
{@required String title,
@required this.selectedItem,
@required this.items,
void Function(ItemType item) onItemSelected,
this.isAlwaysShowScrollThumb = false})
void Function(ItemType item) onItemSelected})
: _onItemSelected = onItemSelected,
super(title);
final ItemType Function() selectedItem;
final List<ItemType> items;
final void Function(ItemType item) _onItemSelected;
final bool isAlwaysShowScrollThumb;
void onItemSelected(dynamic item) {
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_list.dart';
import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart';
@ -28,26 +29,32 @@ part 'settings_view_model.g.dart';
class SettingsViewModel = SettingsViewModelBase with _$SettingsViewModel;
abstract class SettingsViewModelBase with Store {
SettingsViewModelBase(this._settingsStore, WalletBase wallet)
SettingsViewModelBase(this._settingsStore, WalletBase<Balance> wallet)
: itemHeaders = {},
_walletType = wallet.type,
_biometricAuth = BiometricAuth() {
currentVersion = '';
PackageInfo.fromPlatform().then(
(PackageInfo packageInfo) => currentVersion = packageInfo.version);
final _priority = _settingsStore.transactionPriority;
if (!TransactionPriority.forWalletType(_walletType).contains(_priority)) {
_settingsStore.transactionPriority =
TransactionPriority.forWalletType(_walletType).first;
}
sections = [
[
if ((wallet.balance.availableModes as List).length > 1)
PickerListItem(
title: S.current.settings_display_balance_as,
items: BalanceDisplayMode.all,
selectedItem: () => balanceDisplayMode,
onItemSelected: (BalanceDisplayMode mode) =>
_settingsStore.balanceDisplayMode = mode),
PickerListItem(
title: S.current.settings_display_balance_as,
items: BalanceDisplayMode.all,
selectedItem: () => balanceDisplayMode,
onItemSelected: (BalanceDisplayMode mode) =>
_settingsStore.balanceDisplayMode = mode),
PickerListItem(
title: S.current.settings_currency,
items: FiatCurrency.all,
isAlwaysShowScrollThumb: true,
selectedItem: () => fiatCurrency,
onItemSelected: (FiatCurrency currency) =>
setFiatCurrency(currency)),
@ -55,7 +62,6 @@ abstract class SettingsViewModelBase with Store {
title: S.current.settings_fee_priority,
items: TransactionPriority.forWalletType(wallet.type),
selectedItem: () => transactionPriority,
isAlwaysShowScrollThumb: true,
onItemSelected: (TransactionPriority priority) =>
_settingsStore.transactionPriority = priority),
SwitcherListItem(
@ -114,7 +120,7 @@ abstract class SettingsViewModelBase with Store {
items: ThemeList.all,
selectedItem: () => theme,
onItemSelected: (ThemeBase theme) =>
_settingsStore.currentTheme = theme)
_settingsStore.currentTheme = theme)
],
[
LinkListItem(

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;
if (wallet is BitcoinWallet) {
await wallet.generateNewAddress(label: label);
await wallet.generateNewAddress();
}
if (wallet is MoneroWallet) {
@ -77,7 +77,7 @@ abstract class WalletAddressEditOrCreateViewModelBase with Store {
final wallet = _wallet;
if (wallet is BitcoinWallet) {
await wallet.updateAddress(_item.address as String, label: label);
await wallet.updateAddress(_item.address as String);
}
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:flutter/foundation.dart';
import 'package:mobx/mobx.dart';
@ -60,7 +61,7 @@ abstract class WalletAddressListViewModelBase with Store {
_appStore = appStore;
_wallet = _appStore.wallet;
hasAccounts = _wallet?.type == WalletType.monero;
_onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase wallet) {
_onWalletChangeReaction = reaction((_) => _appStore.wallet, (WalletBase<Balance> wallet) {
_wallet = wallet;
hasAccounts = _wallet.type == WalletType.monero;
});
@ -119,7 +120,7 @@ abstract class WalletAddressListViewModelBase with Store {
return WalletAddressListItem(
isPrimary: isPrimary,
name: addr.label,
name: null,
address: addr.address);
});
addressList.addAll(bitcoinAddresses);
@ -131,15 +132,6 @@ abstract class WalletAddressListViewModelBase with Store {
@observable
bool hasAccounts;
@observable
WalletBase _wallet;
List<ListItem> _baseItems;
AppStore _appStore;
ReactionDisposer _onWalletChangeReaction;
@computed
String get accountLabel {
final wallet = _wallet;
@ -151,6 +143,19 @@ abstract class WalletAddressListViewModelBase with Store {
return null;
}
@computed
bool get hasAddressList => _wallet.type == WalletType.monero;
@observable
WalletBase<Balance> _wallet;
List<ListItem> _baseItems;
AppStore _appStore;
ReactionDisposer _onWalletChangeReaction;
@action
void setAddress(WalletAddressListItem address) =>
_wallet.address = address.address;
@ -164,4 +169,13 @@ abstract class WalletAddressListViewModelBase with Store {
_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);
credentials.walletInfo = walletInfo;
final wallet = await process(credentials);
walletInfo.address = wallet.address;
await _walletInfoSource.add(walletInfo);
_appStore.changeCurrentWallet(wallet);
_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.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 4.0.91+22
version: 4.1.0+33
environment:
sdk: ">=2.7.0 <3.0.0"

View file

@ -1,7 +1,7 @@
{
"welcome" : "Willkommen zu",
"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.",
"create_new" : "Neue Wallet erstellen",
"restore_wallet" : "Wallet wiederherstellen",
@ -212,7 +212,7 @@
"send_name" : "Name",
"send_got_it" : "Ich habs",
"send_sending" : "Senden...",
"send_success" : "Ihr Monero wurde erfolgreich gesendet",
"send_success" : "Ihr ${crypto} wurde erfolgreich gesendet",
"settings_title" : "die Einstellungen",
@ -282,6 +282,7 @@
"transaction_details_date" : "Datum",
"transaction_details_height" : "Höhe",
"transaction_details_amount" : "Menge",
"transaction_details_fee" : "Gebühr",
"transaction_details_copied" : "${title} in die Zwischenablage kopiert",
"transaction_details_recipient_address" : "Empfängeradresse",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Bitte warten Sie, bis Ihre Brieftasche synchronisiert ist",
"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",
"xmr_to_error" : "XMR.TO-Fehler",
@ -428,5 +429,11 @@
"color_theme" : "Farbthema",
"light_theme" : "Licht",
"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",
"cake_wallet" : "Cake Wallet",
"first_wallet_text" : "Awesome wallet for Monero",
"please_make_selection" : "Please make selection below to create or recover your wallet.",
"first_wallet_text" : "Awesome wallet for Monero and Bitcoin",
"please_make_selection" : "Please make a selection below to create or recover your wallet.",
"create_new" : "Create New Wallet",
"restore_wallet" : "Restore Wallet",
@ -212,7 +212,7 @@
"send_name" : "Name",
"send_got_it" : "Got it",
"send_sending" : "Sending...",
"send_success" : "Your Monero was successfully sent",
"send_success" : "Your ${crypto} was successfully sent",
"settings_title" : "Settings",
@ -282,6 +282,7 @@
"transaction_details_date" : "Date",
"transaction_details_height" : "Height",
"transaction_details_amount" : "Amount",
"transaction_details_fee" : "Fee",
"transaction_details_copied" : "${title} copied to Clipboard",
"transaction_details_recipient_address" : "Recipient address",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Please wait until your wallet is synchronized",
"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",
"xmr_to_error" : "XMR.TO error",
@ -428,5 +429,11 @@
"color_theme" : "Color theme",
"light_theme" : "Light",
"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",
"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.",
"create_new" : "Crear nueva billetera",
"restore_wallet" : "Restaurar billetera",
@ -212,7 +212,7 @@
"send_name" : "Nombre",
"send_got_it" : "Entendido",
"send_sending" : "Enviando...",
"send_success" : "Su Monero fue enviado con éxito",
"send_success" : "Su ${crypto} fue enviado con éxito",
"settings_title" : "Configuraciones",
@ -282,6 +282,7 @@
"transaction_details_date" : "Fecha",
"transaction_details_height" : "Altura",
"transaction_details_amount" : "Cantidad",
"transaction_details_fee" : "Cuota",
"transaction_details_copied" : "${title} Copiado al portapapeles",
"transaction_details_recipient_address" : "Dirección del receptor",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Espere hasta que su billetera esté sincronizada",
"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",
"xmr_to_error" : "Error de XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Tema de color",
"light_theme" : "Ligera",
"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" : "स्वागत हे सेवा मेरे",
"cake_wallet" : "Cake Wallet",
"first_wallet_text" : "बहुत बढ़िया बटुआ के लिये Monero",
"first_wallet_text" : "Monero और Bitcoin के लिए बहुत बढ़िया बटुआ",
"please_make_selection" : "कृपया नीचे चयन करें अपना बटुआ बनाएं या पुनर्प्राप्त करें.",
"create_new" : "नया बटुआ बनाएँ",
"restore_wallet" : "वॉलेट को पुनर्स्थापित करें",
@ -212,7 +212,7 @@
"send_name" : "नाम",
"send_got_it" : "समझ गया",
"send_sending" : "भेजना...",
"send_success" : "आपका Monero सफलतापूर्वक भेजा गया",
"send_success" : "आपका ${crypto} सफलतापूर्वक भेजा गया",
"settings_title" : "सेटिंग्स",
@ -282,6 +282,7 @@
"transaction_details_date" : "तारीख",
"transaction_details_height" : "ऊंचाई",
"transaction_details_amount" : "रकम",
"transaction_details_fee" : "शुल्क",
"transaction_details_copied" : "${title} क्लिपबोर्ड पर नकल",
"transaction_details_recipient_address" : "प्राप्तकर्ता का पता",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "कृपया प्रतीक्षा करें जब तक आपका बटुआ सिंक्रनाइज़ नहीं किया जाता है",
"pre_seed_title" : "महत्वपूर्ण",
"pre_seed_description" : "अगले पेज पर आपको 25 शब्दों की एक श्रृंखला दिखाई देगी। यह आपका अद्वितीय और निजी बीज है और नुकसान या खराबी के मामले में अपने बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। यह आपकी जिम्मेदारी है कि इसे नीचे लिखें और इसे Cake Wallet ऐप के बाहर सुरक्षित स्थान पर संग्रहीत करें।",
"pre_seed_description" : "अगले पेज पर आपको ${words} शब्दों की एक श्रृंखला दिखाई देगी। यह आपका अद्वितीय और निजी बीज है और नुकसान या खराबी के मामले में अपने बटुए को पुनर्प्राप्त करने का एकमात्र तरीका है। यह आपकी जिम्मेदारी है कि इसे नीचे लिखें और इसे Cake Wallet ऐप के बाहर सुरक्षित स्थान पर संग्रहीत करें।",
"pre_seed_button_text" : "मै समझता हुँ। मुझे अपना बीज दिखाओ",
"xmr_to_error" : "XMR.TO त्रुटि",
@ -428,5 +429,11 @@
"color_theme" : "रंग विषय",
"light_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" : "ようこそ に",
"cake_wallet" : "Cake Wallet",
"first_wallet_text" : "素晴らしい財布 ために Monero",
"first_wallet_text" : "Moneroとビットコインのための素晴らしい財布",
"please_make_selection" : "以下を選択してください ウォレットを作成または回復する.",
"create_new" : "新しいウォレットを作成",
"restore_wallet" : "ウォレットを復元",
@ -212,7 +212,7 @@
"send_name" : "名前",
"send_got_it" : "とった",
"send_sending" : "送信...",
"send_success" : "Moneroが送信されました",
"send_success" : "${crypto}が送信されました",
"settings_title" : "設定",
@ -282,6 +282,7 @@
"transaction_details_date" : "日付",
"transaction_details_height" : "高さ",
"transaction_details_amount" : "量",
"transaction_details_fee" : "費用",
"transaction_details_copied" : "${title} クリップボードにコピーしました",
"transaction_details_recipient_address" : "受取人の住所",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "ウォレットが同期されるまでお待ちください",
"pre_seed_title" : "重要",
"pre_seed_description" : "次のページでは、一連の25語が表示されます。 これはあなたのユニークでプライベートなシードであり、紛失や誤動作が発生した場合にウォレットを回復する唯一の方法です。 それを書き留めて、Cake Wallet アプリの外の安全な場所に保管するのはあなたの責任です。",
"pre_seed_description" : "次のページでは、一連の${words}語が表示されます。 これはあなたのユニークでプライベートなシードであり、紛失や誤動作が発生した場合にウォレットを回復する唯一の方法です。 それを書き留めて、Cake Wallet アプリの外の安全な場所に保管するのはあなたの責任です。",
"pre_seed_button_text" : "わかります。 種を見せて",
"xmr_to_error" : "XMR.TOエラー",
@ -428,5 +429,11 @@
"color_theme" : "カラーテーマ",
"light_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" : "환영 에",
"cake_wallet" : "Cake Wallet",
"first_wallet_text" : "멋진 지갑 에 대한 Monero",
"first_wallet_text" : "Monero 및 Bitcoin을위한 멋진 지갑",
"please_make_selection" : "아래에서 선택하십시오 지갑 만들기 또는 복구.",
"create_new" : "새 월렛 만들기",
"restore_wallet" : "월렛 복원",
@ -212,7 +212,7 @@
"send_name" : "이름",
"send_got_it" : "알았다",
"send_sending" : "배상...",
"send_success" : "Monero가 성공적으로 전송되었습니다",
"send_success" : "${crypto}가 성공적으로 전송되었습니다",
"settings_title" : "설정",
@ -282,6 +282,7 @@
"transaction_details_date" : "날짜",
"transaction_details_height" : "신장",
"transaction_details_amount" : "양",
"transaction_details_fee" : "회비",
"transaction_details_copied" : "${title} 클립 보드에 복사",
"transaction_details_recipient_address" : "받는 사람 주소",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "지갑이 동기화 될 때까지 기다리십시오",
"pre_seed_title" : "중대한",
"pre_seed_description" : "다음 페이지에서 25 개의 단어를 볼 수 있습니다. 이것은 귀하의 고유하고 개인적인 시드이며 분실 또는 오작동시 지갑을 복구하는 유일한 방법입니다. 기록해두고 Cake Wallet 앱 외부의 안전한 장소에 보관하는 것은 귀하의 책임입니다.",
"pre_seed_description" : "다음 페이지에서 ${words} 개의 단어를 볼 수 있습니다. 이것은 귀하의 고유하고 개인적인 시드이며 분실 또는 오작동시 지갑을 복구하는 유일한 방법입니다. 기록해두고 Cake Wallet 앱 외부의 안전한 장소에 보관하는 것은 귀하의 책임입니다.",
"pre_seed_button_text" : "이해 했어요. 내 씨앗을 보여줘",
"xmr_to_error" : "XMR.TO 오류",
@ -428,5 +429,11 @@
"color_theme" : "색상 테마",
"light_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",
"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.",
"create_new" : "Maak een nieuwe portemonnee",
"restore_wallet" : "Portemonnee herstellen",
@ -212,7 +212,7 @@
"send_name" : "Naam",
"send_got_it" : "Ik snap het",
"send_sending" : "Bezig met verzenden...",
"send_success" : "Uw Monero is succesvol verzonden",
"send_success" : "Uw ${crypto} is succesvol verzonden",
"settings_title" : "Instellingen",
@ -282,6 +282,7 @@
"transaction_details_date" : "Datum",
"transaction_details_height" : "Hoogte",
"transaction_details_amount" : "Bedrag",
"transaction_details_fee" : "Vergoeding",
"transaction_details_copied" : "${title} gekopieerd naar het klembord",
"transaction_details_recipient_address" : "Adres van de ontvanger",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Wacht tot uw portemonnee is gesynchroniseerd",
"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",
"xmr_to_error" : "XMR.TO-fout",
@ -428,5 +429,11 @@
"color_theme" : "Kleur thema",
"light_theme" : "Licht",
"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",
"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.",
"create_new" : "Utwórz nowy portfel",
"restore_wallet" : "Przywróć portfel",
@ -212,7 +212,7 @@
"send_name" : "Imię",
"send_got_it" : "Rozumiem",
"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",
@ -282,6 +282,7 @@
"transaction_details_date" : "Data",
"transaction_details_height" : "Wysokość",
"transaction_details_amount" : "Ilość",
"transaction_details_fee" : "Opłata",
"transaction_details_copied" : "${title} skopiowane do schowka",
"transaction_details_recipient_address" : "Adres odbiorcy",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Poczekaj, aż portfel zostanie zsynchronizowany",
"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",
"xmr_to_error" : "Pomyłka XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Motyw kolorystyczny",
"light_theme" : "Lekki",
"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",
"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.",
"create_new" : "Criar nova carteira",
"restore_wallet" : "Restaurar carteira",
@ -212,7 +212,7 @@
"send_name" : "Nome",
"send_got_it" : "Entendi",
"send_sending" : "Enviando...",
"send_success" : "Seu Monero foi enviado com sucesso",
"send_success" : "Seu ${crypto} foi enviado com sucesso",
"settings_title" : "Configurações",
@ -282,6 +282,7 @@
"transaction_details_date" : "Data",
"transaction_details_height" : "Altura",
"transaction_details_amount" : "Quantia",
"transaction_details_fee" : "Taxa",
"transaction_details_copied" : "${title} copiados para a área de transferência",
"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",
"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",
"xmr_to_error" : "Erro XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Tema de cor",
"light_theme" : "Luz",
"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" : "Приветствуем в",
"cake_wallet" : "Cake Wallet",
"first_wallet_text" : "В самом удобном кошельке для Monero",
"first_wallet_text" : "В самом удобном кошельке для Monero и Bitcoin",
"please_make_selection" : "Выберите способ создания кошелька: создать новый или восстановить ваш существующий.",
"create_new" : "Создать новый кошелёк",
"restore_wallet" : "Восстановить кошелёк",
@ -212,7 +212,7 @@
"send_name" : "Имя",
"send_got_it" : "Понял",
"send_sending" : "Отправка...",
"send_success" : "Ваш Monero был успешно отправлен",
"send_success" : "Ваш ${crypto} был успешно отправлен",
"settings_title" : "Настройки",
@ -282,6 +282,7 @@
"transaction_details_date" : "Дата",
"transaction_details_height" : "Высота",
"transaction_details_amount" : "Сумма",
"transaction_details_fee" : "Комиссия",
"transaction_details_copied" : "${title} скопировано в буфер обмена",
"transaction_details_recipient_address" : "Адрес получателя",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Подождите, пока ваш кошелек синхронизируется",
"pre_seed_title" : "ВАЖНО",
"pre_seed_description" : "На следующей странице вы увидите серию из 25 слов. Это ваша уникальная и личная мнемоническая фраза, и это ЕДИНСТВЕННЫЙ способ восстановить свой кошелек в случае потери или неисправности. ВАМ необходимо записать ее и хранить в надежном месте вне приложения Cake Wallet.",
"pre_seed_description" : "На следующей странице вы увидите серию из ${words} слов. Это ваша уникальная и личная мнемоническая фраза, и это ЕДИНСТВЕННЫЙ способ восстановить свой кошелек в случае потери или неисправности. ВАМ необходимо записать ее и хранить в надежном месте вне приложения Cake Wallet.",
"pre_seed_button_text" : "Понятно. Покажите мнемоническую фразу",
"xmr_to_error" : "Ошибка XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Цветовая тема",
"light_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" : "Вітаємо в",
"cake_wallet" : "Cake Wallet",
"first_wallet_text" : "В самому зручному гаманці для Monero",
"first_wallet_text" : "В самому зручному гаманці для Monero та Bitcoin",
"please_make_selection" : "Оберіть спосіб створення гаманця: створити новий чи відновити ваш існуючий.",
"create_new" : "Створити новий гаманець",
"restore_wallet" : "Відновити гаманець",
@ -212,7 +212,7 @@
"send_name" : "Ім'я",
"send_got_it" : "Зрозумів",
"send_sending" : "Відправлення...",
"send_success" : "Ваш Monero успішно надісланий",
"send_success" : "Ваш ${crypto} успішно надісланий",
"settings_title" : "Налаштування",
@ -282,6 +282,7 @@
"transaction_details_date" : "Дата",
"transaction_details_height" : "Висота",
"transaction_details_amount" : "Сума",
"transaction_details_fee" : "Комісія",
"transaction_details_copied" : "${title} скопійовано в буфер обміну",
"transaction_details_recipient_address" : "Адреса отримувача",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "Зачекайте, поки ваш гаманець не синхронізується",
"pre_seed_title" : "ВАЖЛИВО",
"pre_seed_description" : "На наступній сторінці ви побачите серію з 25 слів. Це ваша унікальна та приватна мнемонічна фраза, і це ЄДИНИЙ спосіб відновити ваш гаманець на випадок втрати або несправності. ВАМ необхідно записати її та зберігати в безпечному місці поза програмою Cake Wallet.",
"pre_seed_description" : "На наступній сторінці ви побачите серію з ${words} слів. Це ваша унікальна та приватна мнемонічна фраза, і це ЄДИНИЙ спосіб відновити ваш гаманець на випадок втрати або несправності. ВАМ необхідно записати її та зберігати в безпечному місці поза програмою Cake Wallet.",
"pre_seed_button_text" : "Зрозуміло. Покажіть мнемонічну фразу",
"xmr_to_error" : "Помилка XMR.TO",
@ -428,5 +429,11 @@
"color_theme" : "Кольорова тема",
"light_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" : "歡迎來到",
"cake_wallet" : "Cake Wallet",
"first_wallet_text" : "很棒的钱包 对于 Monero",
"first_wallet_text" : "很棒的Monero和比特幣錢包",
"please_make_selection" : "请在下面进行选择 创建或恢复您的钱包.",
"create_new" : "创建新钱包",
"restore_wallet" : "恢复钱包",
@ -212,7 +212,7 @@
"send_name" : "名稱",
"send_got_it" : "得到它了",
"send_sending" : "正在發送...",
"send_success" : "你Monero已成功發送",
"send_success" : "你${crypto}已成功發送",
"settings_title" : "设定值",
@ -282,6 +282,7 @@
"transaction_details_date" : "日期",
"transaction_details_height" : "高度",
"transaction_details_amount" : "量",
"transaction_details_fee" : "費用",
"transaction_details_copied" : "${title} 复制到剪贴板",
"transaction_details_recipient_address" : "收件人地址",
@ -415,7 +416,7 @@
"exchange_sync_alert_content" : "請等待,直到您的錢包同步",
"pre_seed_title" : "重要",
"pre_seed_description" : "在下一頁上,您將看到一系列25個單詞。 這是您獨特的私人種子,是丟失或出現故障時恢復錢包的唯一方法。 您有責任將其寫下並存儲在Cake Wallet應用程序外部的安全地方。",
"pre_seed_description" : "在下一頁上,您將看到一系列${words}個單詞。 這是您獨特的私人種子,是丟失或出現故障時恢復錢包的唯一方法。 您有責任將其寫下並存儲在Cake Wallet應用程序外部的安全地方。",
"pre_seed_button_text" : "我明白。 給我看我的種子",
"xmr_to_error" : "XMR.TO錯誤",
@ -428,5 +429,11 @@
"color_theme" : "顏色主題",
"light_theme" : "光",
"bright_theme" : "亮",
"dark_theme" : "黑暗"
"dark_theme" : "黑暗",
"enter_your_note" : "輸入您的筆記...",
"note_optional" : "注意(可選)",
"note_tap_to_change" : "注意(輕按即可更改)",
"transaction_key" : "交易密碼",
"confirmations" : "確認書",
"recipient_address" : "收件人地址"
}