Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-438-add-nano

This commit is contained in:
fosse 2023-08-02 12:34:36 -04:00
commit c8d9fcbaf4
64 changed files with 1069 additions and 602 deletions

View file

@ -45,8 +45,8 @@ android {
defaultConfig { defaultConfig {
applicationId appProperties['id'] applicationId appProperties['id']
minSdkVersion 21 minSdkVersion 24
targetSdkVersion 31 targetSdkVersion 33
versionCode flutterVersionCode.toInteger() versionCode flutterVersionCode.toInteger()
versionName flutterVersionName versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

View file

@ -12,14 +12,14 @@ Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address
@HiveType(typeId: Node.typeId) @HiveType(typeId: Node.typeId)
class Node extends HiveObject with Keyable { class Node extends HiveObject with Keyable {
Node({ Node(
this.login, {this.login,
this.password, this.password,
this.useSSL, this.useSSL,
this.trusted = false, this.trusted = false,
String? uri, this.socksProxyAddress,
WalletType? type, String? uri,
}) { WalletType? type,}) {
if (uri != null) { if (uri != null) {
uriRaw = uri; uriRaw = uri;
} }
@ -33,7 +33,8 @@ class Node extends HiveObject with Keyable {
login = map['login'] as String?, login = map['login'] as String?,
password = map['password'] as String?, password = map['password'] as String?,
useSSL = map['useSSL'] as bool?, useSSL = map['useSSL'] as bool?,
trusted = map['trusted'] as bool? ?? false; trusted = map['trusted'] as bool? ?? false,
socksProxyAddress = map['socksProxyPort'] as String?;
static const typeId = 1; static const typeId = 1;
static const boxName = 'Nodes'; static const boxName = 'Nodes';
@ -56,8 +57,13 @@ class Node extends HiveObject with Keyable {
@HiveField(5, defaultValue: false) @HiveField(5, defaultValue: false)
bool trusted; bool trusted;
@HiveField(6)
String? socksProxyAddress;
bool get isSSL => useSSL ?? false; bool get isSSL => useSSL ?? false;
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
Uri get uri { Uri get uri {
switch (type) { switch (type) {
case WalletType.monero: case WalletType.monero:
@ -85,12 +91,13 @@ class Node extends HiveObject with Keyable {
@override @override
bool operator ==(other) => bool operator ==(other) =>
other is Node && other is Node &&
(other.uriRaw == uriRaw && (other.uriRaw == uriRaw &&
other.login == login && other.login == login &&
other.password == password && other.password == password &&
other.typeRaw == typeRaw && other.typeRaw == typeRaw &&
other.useSSL == useSSL && other.useSSL == useSSL &&
other.trusted == trusted); other.trusted == trusted &&
other.socksProxyAddress == socksProxyAddress);
@override @override
int get hashCode => int get hashCode =>
@ -99,7 +106,8 @@ class Node extends HiveObject with Keyable {
password.hashCode ^ password.hashCode ^
typeRaw.hashCode ^ typeRaw.hashCode ^
useSSL.hashCode ^ useSSL.hashCode ^
trusted.hashCode; trusted.hashCode ^
socksProxyAddress.hashCode;
@override @override
dynamic get keyIndex { dynamic get keyIndex {
@ -117,7 +125,7 @@ class Node extends HiveObject with Keyable {
try { try {
switch (type) { switch (type) {
case WalletType.monero: case WalletType.monero:
return requestMoneroNode(); return useSocksProxy ? requestNodeWithProxy(socksProxyAddress ?? '') : requestMoneroNode();
case WalletType.bitcoin: case WalletType.bitcoin:
return requestElectrumServer(); return requestElectrumServer();
case WalletType.litecoin: case WalletType.litecoin:
@ -166,6 +174,22 @@ class Node extends HiveObject with Keyable {
} }
} }
Future<bool> requestNodeWithProxy(String proxy) async {
if (proxy.isEmpty || !proxy.contains(':')) {
return false;
}
final proxyAddress = proxy.split(':')[0];
final proxyPort = int.parse(proxy.split(':')[1]);
try {
final socket = await Socket.connect(proxyAddress, proxyPort, timeout: Duration(seconds: 5));
socket.destroy();
return true;
} catch (_) {
return false;
}
}
Future<bool> requestElectrumServer() async { Future<bool> requestElectrumServer() async {
try { try {
await SecureSocket.connect(uri.host, uri.port, await SecureSocket.connect(uri.host, uri.port,

View file

@ -39,7 +39,7 @@ typedef get_node_height = Int64 Function();
typedef is_connected = Int8 Function(); typedef is_connected = Int8 Function();
typedef setup_node = Int8 Function( typedef setup_node = Int8 Function(
Pointer<Utf8>, Pointer<Utf8>?, Pointer<Utf8>?, Int8, Int8, Pointer<Utf8>); Pointer<Utf8>, Pointer<Utf8>?, Pointer<Utf8>?, Int8, Int8, Pointer<Utf8>?, Pointer<Utf8>);
typedef start_refresh = Void Function(); typedef start_refresh = Void Function();

View file

@ -39,7 +39,7 @@ typedef GetNodeHeight = int Function();
typedef IsConnected = int Function(); typedef IsConnected = int Function();
typedef SetupNode = int Function( typedef SetupNode = int Function(
Pointer<Utf8>, Pointer<Utf8>?, Pointer<Utf8>?, int, int, Pointer<Utf8>); Pointer<Utf8>, Pointer<Utf8>?, Pointer<Utf8>?, int, int, Pointer<Utf8>?, Pointer<Utf8>);
typedef StartRefresh = void Function(); typedef StartRefresh = void Function();

View file

@ -154,9 +154,11 @@ bool setupNodeSync(
String? login, String? login,
String? password, String? password,
bool useSSL = false, bool useSSL = false,
bool isLightWallet = false}) { bool isLightWallet = false,
String? socksProxyAddress}) {
final addressPointer = address.toNativeUtf8(); final addressPointer = address.toNativeUtf8();
Pointer<Utf8>? loginPointer; Pointer<Utf8>? loginPointer;
Pointer<Utf8>? socksProxyAddressPointer;
Pointer<Utf8>? passwordPointer; Pointer<Utf8>? passwordPointer;
if (login != null) { if (login != null) {
@ -167,6 +169,10 @@ bool setupNodeSync(
passwordPointer = password.toNativeUtf8(); passwordPointer = password.toNativeUtf8();
} }
if (socksProxyAddress != null) {
socksProxyAddressPointer = socksProxyAddress.toNativeUtf8();
}
final errorMessagePointer = ''.toNativeUtf8(); final errorMessagePointer = ''.toNativeUtf8();
final isSetupNode = setupNodeNative( final isSetupNode = setupNodeNative(
addressPointer, addressPointer,
@ -174,6 +180,7 @@ bool setupNodeSync(
passwordPointer, passwordPointer,
_boolToInt(useSSL), _boolToInt(useSSL),
_boolToInt(isLightWallet), _boolToInt(isLightWallet),
socksProxyAddressPointer,
errorMessagePointer) != errorMessagePointer) !=
0; 0;
@ -323,13 +330,15 @@ bool _setupNodeSync(Map args) {
final password = (args['password'] ?? '') as String; final password = (args['password'] ?? '') as String;
final useSSL = args['useSSL'] as bool; final useSSL = args['useSSL'] as bool;
final isLightWallet = args['isLightWallet'] as bool; final isLightWallet = args['isLightWallet'] as bool;
final socksProxyAddress = (args['socksProxyAddress'] ?? '') as String;
return setupNodeSync( return setupNodeSync(
address: address, address: address,
login: login, login: login,
password: password, password: password,
useSSL: useSSL, useSSL: useSSL,
isLightWallet: isLightWallet); isLightWallet: isLightWallet,
socksProxyAddress: socksProxyAddress);
} }
bool _isConnected(Object _) => isConnectedSync(); bool _isConnected(Object _) => isConnectedSync();
@ -343,13 +352,15 @@ Future<void> setupNode(
String? login, String? login,
String? password, String? password,
bool useSSL = false, bool useSSL = false,
String? socksProxyAddress,
bool isLightWallet = false}) => bool isLightWallet = false}) =>
compute<Map<String, Object?>, void>(_setupNodeSync, { compute<Map<String, Object?>, void>(_setupNodeSync, {
'address': address, 'address': address,
'login': login, 'login': login,
'password': password, 'password': password,
'useSSL': useSSL, 'useSSL': useSSL,
'isLightWallet': isLightWallet 'isLightWallet': isLightWallet,
'socksProxyAddress': socksProxyAddress
}); });
Future<void> store() => compute<int, void>(_storeSync, 0); Future<void> store() => compute<int, void>(_storeSync, 0);

View file

@ -125,7 +125,8 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
login: node.login, login: node.login,
password: node.password, password: node.password,
useSSL: node.useSSL ?? false, useSSL: node.useSSL ?? false,
isLightWallet: false); // FIXME: hardcoded value isLightWallet: false, // FIXME: hardcoded value
socksProxyAddress: node.socksProxyAddress);
haven_wallet.setTrustedDaemon(node.trusted); haven_wallet.setTrustedDaemon(node.trusted);
syncStatus = ConnectedSyncStatus(); syncStatus = ConnectedSyncStatus();

View file

@ -3,6 +3,7 @@
#include <chrono> #include <chrono>
#include <functional> #include <functional>
#include <iostream> #include <iostream>
#include <fstream>
#include <unistd.h> #include <unistd.h>
#include <mutex> #include <mutex>
#include "thread" #include "thread"
@ -405,13 +406,14 @@ extern "C"
return is_connected; return is_connected;
} }
bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *error) bool setup_node(char *address, char *login, char *password, bool use_ssl, bool is_light_wallet, char *socksProxyAddress, char *error)
{ {
nice(19); nice(19);
Monero::Wallet *wallet = get_current_wallet(); Monero::Wallet *wallet = get_current_wallet();
std::string _login = ""; std::string _login = "";
std::string _password = ""; std::string _password = "";
std::string _socksProxyAddress = "";
if (login != nullptr) if (login != nullptr)
{ {
@ -423,7 +425,12 @@ extern "C"
_password = std::string(password); _password = std::string(password);
} }
bool inited = wallet->init(std::string(address), 0, _login, _password, use_ssl, is_light_wallet); if (socksProxyAddress != nullptr)
{
_socksProxyAddress = std::string(socksProxyAddress);
}
bool inited = wallet->init(std::string(address), 0, _login, _password, use_ssl, is_light_wallet, _socksProxyAddress);
if (!inited) if (!inited)
{ {

View file

@ -35,7 +35,7 @@ typedef get_node_height = Int64 Function();
typedef is_connected = Int8 Function(); typedef is_connected = Int8 Function();
typedef setup_node = Int8 Function( typedef setup_node = Int8 Function(
Pointer<Utf8>, Pointer<Utf8>?, Pointer<Utf8>?, Int8, Int8, Pointer<Utf8>); Pointer<Utf8>, Pointer<Utf8>?, Pointer<Utf8>?, Int8, Int8, Pointer<Utf8>?, Pointer<Utf8>);
typedef start_refresh = Void Function(); typedef start_refresh = Void Function();

View file

@ -35,7 +35,7 @@ typedef GetNodeHeight = int Function();
typedef IsConnected = int Function(); typedef IsConnected = int Function();
typedef SetupNode = int Function( typedef SetupNode = int Function(
Pointer<Utf8>, Pointer<Utf8>?, Pointer<Utf8>?, int, int, Pointer<Utf8>); Pointer<Utf8>, Pointer<Utf8>?, Pointer<Utf8>?, int, int, Pointer<Utf8>?, Pointer<Utf8>);
typedef StartRefresh = void Function(); typedef StartRefresh = void Function();

View file

@ -158,9 +158,11 @@ bool setupNodeSync(
String? login, String? login,
String? password, String? password,
bool useSSL = false, bool useSSL = false,
bool isLightWallet = false}) { bool isLightWallet = false,
String? socksProxyAddress}) {
final addressPointer = address.toNativeUtf8(); final addressPointer = address.toNativeUtf8();
Pointer<Utf8>? loginPointer; Pointer<Utf8>? loginPointer;
Pointer<Utf8>? socksProxyAddressPointer;
Pointer<Utf8>? passwordPointer; Pointer<Utf8>? passwordPointer;
if (login != null) { if (login != null) {
@ -171,6 +173,10 @@ bool setupNodeSync(
passwordPointer = password.toNativeUtf8(); passwordPointer = password.toNativeUtf8();
} }
if (socksProxyAddress != null) {
socksProxyAddressPointer = socksProxyAddress.toNativeUtf8();
}
final errorMessagePointer = ''.toNativeUtf8(); final errorMessagePointer = ''.toNativeUtf8();
final isSetupNode = setupNodeNative( final isSetupNode = setupNodeNative(
addressPointer, addressPointer,
@ -178,6 +184,7 @@ bool setupNodeSync(
passwordPointer, passwordPointer,
_boolToInt(useSSL), _boolToInt(useSSL),
_boolToInt(isLightWallet), _boolToInt(isLightWallet),
socksProxyAddressPointer,
errorMessagePointer) != errorMessagePointer) !=
0; 0;
@ -328,13 +335,15 @@ bool _setupNodeSync(Map<String, Object?> args) {
final password = (args['password'] ?? '') as String; final password = (args['password'] ?? '') as String;
final useSSL = args['useSSL'] as bool; final useSSL = args['useSSL'] as bool;
final isLightWallet = args['isLightWallet'] as bool; final isLightWallet = args['isLightWallet'] as bool;
final socksProxyAddress = (args['socksProxyAddress'] ?? '') as String;
return setupNodeSync( return setupNodeSync(
address: address, address: address,
login: login, login: login,
password: password, password: password,
useSSL: useSSL, useSSL: useSSL,
isLightWallet: isLightWallet); isLightWallet: isLightWallet,
socksProxyAddress: socksProxyAddress);
} }
bool _isConnected(Object _) => isConnectedSync(); bool _isConnected(Object _) => isConnectedSync();
@ -348,13 +357,15 @@ Future<void> setupNode(
String? login, String? login,
String? password, String? password,
bool useSSL = false, bool useSSL = false,
String? socksProxyAddress,
bool isLightWallet = false}) => bool isLightWallet = false}) =>
compute<Map<String, Object?>, void>(_setupNodeSync, { compute<Map<String, Object?>, void>(_setupNodeSync, {
'address': address, 'address': address,
'login': login , 'login': login ,
'password': password, 'password': password,
'useSSL': useSSL, 'useSSL': useSSL,
'isLightWallet': isLightWallet 'isLightWallet': isLightWallet,
'socksProxyAddress': socksProxyAddress
}); });
Future<void> store() => compute<int, void>(_storeSync, 0); Future<void> store() => compute<int, void>(_storeSync, 0);

View file

@ -138,7 +138,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
login: node.login, login: node.login,
password: node.password, password: node.password,
useSSL: node.isSSL, useSSL: node.isSSL,
isLightWallet: false); // FIXME: hardcoded value isLightWallet: false, // FIXME: hardcoded value
socksProxyAddress: node.socksProxyAddress);
monero_wallet.setTrustedDaemon(node.trusted); monero_wallet.setTrustedDaemon(node.trusted);
syncStatus = ConnectedSyncStatus(); syncStatus = ConnectedSyncStatus();

View file

@ -20,6 +20,8 @@ class OnRamperBuyProvider {
switch (_wallet.currency) { switch (_wallet.currency) {
case CryptoCurrency.ltc: case CryptoCurrency.ltc:
return "LTC_LITECOIN"; return "LTC_LITECOIN";
case CryptoCurrency.xmr:
return "XMR_MONERO";
default: default:
return _wallet.currency.title; return _wallet.currency.title;
} }
@ -60,11 +62,12 @@ class OnRamperBuyProvider {
break; break;
} }
final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", "");
return Uri.https(_baseUrl, '', <String, dynamic>{ return Uri.https(_baseUrl, '', <String, dynamic>{
'apiKey': _apiKey, 'apiKey': _apiKey,
'defaultCrypto': _normalizeCryptoCurrency, 'defaultCrypto': _normalizeCryptoCurrency,
'wallets': '${_wallet.currency.title}:${_wallet.walletAddresses.address}', 'networkWallets': '${networkName}:${_wallet.walletAddresses.address}',
'supportSell': "false", 'supportSell': "false",
'supportSwap': "false", 'supportSwap': "false",
'primaryColor': primaryColor, 'primaryColor': primaryColor,

View file

@ -0,0 +1,10 @@
import 'package:cake_wallet/core/validator.dart';
import 'package:cake_wallet/generated/i18n.dart';
class SocksProxyNodeAddressValidator extends TextValidator {
SocksProxyNodeAddressValidator()
: super(
errorMessage: S.current.error_text_node_proxy_address,
pattern:
'^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}:[0-9]+\$');
}

View file

@ -37,7 +37,7 @@ class OpenaliasRecord {
required String ticker, required String ticker,
required List<RRecord> txtRecord, required List<RRecord> txtRecord,
}) { }) {
String address = formattedName; String address = '';
String name = formattedName; String name = formattedName;
String note = ''; String note = '';

View file

@ -1,7 +1,7 @@
import 'package:cake_wallet/entities/openalias_record.dart'; import 'package:cake_wallet/entities/openalias_record.dart';
import 'package:cake_wallet/entities/yat_record.dart'; import 'package:cake_wallet/entities/yat_record.dart';
enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter } enum ParseFrom { unstoppableDomains, openAlias, yatRecord, fio, notParsed, twitter, contact }
class ParsedAddress { class ParsedAddress {
ParsedAddress({ ParsedAddress({
@ -40,13 +40,17 @@ class ParsedAddress {
); );
} }
factory ParsedAddress.fetchOpenAliasAddress({required OpenaliasRecord record, required String name}){ factory ParsedAddress.fetchOpenAliasAddress(
return ParsedAddress( {required OpenaliasRecord record, required String name}) {
addresses: [record.address], if (record.address.isEmpty) {
name: record.name, return ParsedAddress(addresses: [name]);
description: record.description, }
parseFrom: ParseFrom.openAlias, return ParsedAddress(
); addresses: [record.address],
name: record.name,
description: record.description,
parseFrom: ParseFrom.openAlias,
);
} }
factory ParsedAddress.fetchFioAddress({required String address, required String name}){ factory ParsedAddress.fetchFioAddress({required String address, required String name}){
@ -65,6 +69,14 @@ class ParsedAddress {
); );
} }
factory ParsedAddress.fetchContactAddress({required String address, required String name}){
return ParsedAddress(
addresses: [address],
name: name,
parseFrom: ParseFrom.contact,
);
}
final List<String> addresses; final List<String> addresses;
final String name; final String name;
final String description; final String description;

View file

@ -4,14 +4,15 @@ part 'template.g.dart';
@HiveType(typeId: Template.typeId) @HiveType(typeId: Template.typeId)
class Template extends HiveObject { class Template extends HiveObject {
Template({ Template(
required this.nameRaw, {required this.nameRaw,
required this.isCurrencySelectedRaw, required this.isCurrencySelectedRaw,
required this.addressRaw, required this.addressRaw,
required this.cryptoCurrencyRaw, required this.cryptoCurrencyRaw,
required this.amountRaw, required this.amountRaw,
required this.fiatCurrencyRaw, required this.fiatCurrencyRaw,
required this.amountFiatRaw}); required this.amountFiatRaw,
this.additionalRecipientsRaw});
static const typeId = 6; static const typeId = 6;
static const boxName = 'Template'; static const boxName = 'Template';
@ -37,6 +38,9 @@ class Template extends HiveObject {
@HiveField(6) @HiveField(6)
String? amountFiatRaw; String? amountFiatRaw;
@HiveField(7)
List<Template>? additionalRecipientsRaw;
bool get isCurrencySelected => isCurrencySelectedRaw ?? false; bool get isCurrencySelected => isCurrencySelectedRaw ?? false;
String get fiatCurrency => fiatCurrencyRaw ?? ''; String get fiatCurrency => fiatCurrencyRaw ?? '';
@ -50,5 +54,6 @@ class Template extends HiveObject {
String get cryptoCurrency => cryptoCurrencyRaw ?? ''; String get cryptoCurrency => cryptoCurrencyRaw ?? '';
String get amount => amountRaw ?? ''; String get amount => amountRaw ?? '';
}
List<Template>? get additionalRecipients => additionalRecipientsRaw ?? null;
}

View file

@ -7,6 +7,7 @@ import 'package:cake_wallet/store/app_store.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart';
Timer? _timer; Timer? _timer;
@ -16,7 +17,7 @@ Future<void> startFiatRateUpdate(
return; return;
} }
_timer = Timer.periodic(Duration(seconds: 30), (_) async { final _updateFiat = (_) async {
try { try {
if (appStore.wallet == null || settingsStore.fiatApiMode == FiatApiMode.disabled) { if (appStore.wallet == null || settingsStore.fiatApiMode == FiatApiMode.disabled) {
return; return;
@ -45,5 +46,20 @@ Future<void> startFiatRateUpdate(
} catch (e) { } catch (e) {
print(e); print(e);
} }
};
_timer = Timer.periodic(Duration(seconds: 30), _updateFiat);
// also run immediately:
_updateFiat(null);
// setup autorun to listen to changes in fiatApiMode
autorun((_) {
// restart the timer if fiatApiMode was re-enabled
if (settingsStore.fiatApiMode != FiatApiMode.disabled) {
_timer = Timer.periodic(Duration(seconds: 30), _updateFiat);
_updateFiat(null);
} else {
_timer?.cancel();
}
}); });
} }

View file

@ -1,21 +1,22 @@
import 'dart:io'; import 'dart:io';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/utils/clipboard_util.dart';
import 'package:cake_wallet/utils/exception_handler.dart'; import 'package:cake_wallet/utils/exception_handler.dart';
import 'package:cake_wallet/utils/share_util.dart'; import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/backup_view_model.dart';
import 'package:file_picker/file_picker.dart'; import 'package:file_picker/file_picker.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/utils/show_pop_up.dart';
import 'package:cake_wallet/view_model/backup_view_model.dart';
import 'package:cake_wallet/core/execution_state.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
class BackupPage extends BasePage { class BackupPage extends BasePage {
@ -52,7 +53,7 @@ class BackupPage extends BasePage {
child: Observer( child: Observer(
builder: (_) => GestureDetector( builder: (_) => GestureDetector(
onTap: () { onTap: () {
Clipboard.setData( ClipboardUtil.setSensitiveDataToClipboard(
ClipboardData(text: backupViewModelBase.backupPassword)); ClipboardData(text: backupViewModelBase.backupPassword));
showBar<void>( showBar<void>(
context, context,
@ -75,15 +76,14 @@ class BackupPage extends BasePage {
]))), ]))),
Positioned( Positioned(
child: Observer( child: Observer(
builder: (_) => LoadingPrimaryButton( builder: (_) => LoadingPrimaryButton(
isLoading: backupViewModelBase.state is IsExecutingState, isLoading: backupViewModelBase.state is IsExecutingState,
onPressed: () => onExportBackup(context), onPressed: () => onExportBackup(context),
text: S.of(context).export_backup, text: S.of(context).export_backup,
color: Theme.of(context) color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
.accentTextTheme! textColor: Colors.white,
.bodyLarge! ),
.color!, ),
textColor: Colors.white)),
bottom: 24, bottom: 24,
left: 24, left: 24,
right: 24, right: 24,

View file

@ -1,5 +1,6 @@
import 'package:cake_wallet/core/node_address_validator.dart'; import 'package:cake_wallet/core/node_address_validator.dart';
import 'package:cake_wallet/core/node_port_validator.dart'; import 'package:cake_wallet/core/node_port_validator.dart';
import 'package:cake_wallet/core/socks_proxy_node_address_validator.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart'; import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/standard_checkbox.dart'; import 'package:cake_wallet/src/widgets/standard_checkbox.dart';
import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart'; import 'package:cake_wallet/view_model/node_list/node_create_or_edit_view_model.dart';
@ -17,7 +18,8 @@ class NodeForm extends StatelessWidget {
}) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()), }) : _addressController = TextEditingController(text: editingNode?.uri.host.toString()),
_portController = TextEditingController(text: editingNode?.uri.port.toString()), _portController = TextEditingController(text: editingNode?.uri.port.toString()),
_loginController = TextEditingController(text: editingNode?.login), _loginController = TextEditingController(text: editingNode?.login),
_passwordController = TextEditingController(text: editingNode?.password) { _passwordController = TextEditingController(text: editingNode?.password),
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress){
if (editingNode != null) { if (editingNode != null) {
nodeViewModel nodeViewModel
..setAddress((editingNode!.uri.host.toString())) ..setAddress((editingNode!.uri.host.toString()))
@ -25,7 +27,9 @@ class NodeForm extends StatelessWidget {
..setPassword((editingNode!.password ?? '')) ..setPassword((editingNode!.password ?? ''))
..setLogin((editingNode!.login ?? '')) ..setLogin((editingNode!.login ?? ''))
..setSSL((editingNode!.isSSL)) ..setSSL((editingNode!.isSSL))
..setTrusted((editingNode!.trusted)); ..setTrusted((editingNode!.trusted))
..setSocksProxy((editingNode!.useSocksProxy))
..setSocksProxyAddress((editingNode!.socksProxyAddress ?? ''));
} }
if (nodeViewModel.hasAuthCredentials) { if (nodeViewModel.hasAuthCredentials) {
reaction((_) => nodeViewModel.login, (String login) { reaction((_) => nodeViewModel.login, (String login) {
@ -45,6 +49,7 @@ class NodeForm extends StatelessWidget {
_portController.addListener(() => nodeViewModel.port = _portController.text); _portController.addListener(() => nodeViewModel.port = _portController.text);
_loginController.addListener(() => nodeViewModel.login = _loginController.text); _loginController.addListener(() => nodeViewModel.login = _loginController.text);
_passwordController.addListener(() => nodeViewModel.password = _passwordController.text); _passwordController.addListener(() => nodeViewModel.password = _passwordController.text);
_socksAddressController.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text);
} }
final NodeCreateOrEditViewModel nodeViewModel; final NodeCreateOrEditViewModel nodeViewModel;
@ -55,6 +60,7 @@ class NodeForm extends StatelessWidget {
final TextEditingController _portController; final TextEditingController _portController;
final TextEditingController _loginController; final TextEditingController _loginController;
final TextEditingController _passwordController; final TextEditingController _passwordController;
final TextEditingController _socksAddressController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -138,6 +144,43 @@ class NodeForm extends StatelessWidget {
], ],
), ),
), ),
Observer(
builder: (_) => Column(
children: [
Padding(
padding: EdgeInsets.only(top: 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
StandardCheckbox(
value: nodeViewModel.useSocksProxy,
onChanged: (value) {
if (!value) {
_socksAddressController.text = '';
}
nodeViewModel.useSocksProxy = value;
},
caption: 'SOCKS Proxy',
),
],
),
),
if (nodeViewModel.useSocksProxy) ...[
SizedBox(height: 10.0),
Row(
children: <Widget>[
Expanded(
child: BaseTextFormField(
controller: _socksAddressController,
hintText: '[<ip>:]<port>',
validator: SocksProxyNodeAddressValidator(),
))
],
),
]
],
)),
] ]
], ],
), ),

View file

@ -1,11 +1,11 @@
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart'; import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cw_core/wallet_type.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
class PreSeedPage extends BasePage { class PreSeedPage extends BasePage {
PreSeedPage(this.type) PreSeedPage(this.type)
@ -61,7 +61,7 @@ class PreSeedPage extends BasePage {
onPressed: () => onPressed: () =>
Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true), Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true),
text: S.of(context).pre_seed_button_text, text: S.of(context).pre_seed_button_text,
color: Theme.of(context).accentTextTheme!.bodyLarge!.color!, color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
textColor: Colors.white) textColor: Colors.white)
], ],
), ),

View file

@ -1,6 +1,7 @@
import 'package:cake_wallet/palette.dart'; import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cake_wallet/themes/theme_base.dart';
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart'; import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
import 'package:cake_wallet/utils/clipboard_util.dart';
import 'package:cake_wallet/utils/share_util.dart'; import 'package:cake_wallet/utils/share_util.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_bar.dart';
@ -92,8 +93,7 @@ class WalletSeedPage extends BasePage {
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[ children: <Widget>[
ConstrainedBox( ConstrainedBox(
constraints: constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
child: AspectRatio(aspectRatio: 1, child: image), child: AspectRatio(aspectRatio: 1, child: image),
), ),
Observer(builder: (_) { Observer(builder: (_) {
@ -159,7 +159,7 @@ class WalletSeedPage extends BasePage {
child: Builder( child: Builder(
builder: (context) => PrimaryButton( builder: (context) => PrimaryButton(
onPressed: () { onPressed: () {
Clipboard.setData( ClipboardUtil.setSensitiveDataToClipboard(
ClipboardData(text: walletSeedViewModel.seed)); ClipboardData(text: walletSeedViewModel.seed));
showBar<void>(context, S.of(context).copied_to_clipboard); showBar<void>(context, S.of(context).copied_to_clipboard);
}, },

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/entities/fiat_currency.dart'; import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart';
import 'package:cake_wallet/src/screens/send/widgets/send_card.dart'; import 'package:cake_wallet/src/screens/send/widgets/send_card.dart';
import 'package:cake_wallet/src/widgets/add_template_button.dart'; import 'package:cake_wallet/src/widgets/add_template_button.dart';
@ -241,6 +242,11 @@ class SendPage extends BasePage {
return TemplateTile( return TemplateTile(
key: UniqueKey(), key: UniqueKey(),
to: template.name, to: template.name,
hasMultipleRecipients:
template.additionalRecipients !=
null &&
template.additionalRecipients!
.length > 1,
amount: template.isCurrencySelected amount: template.isCurrencySelected
? template.amount ? template.amount
: template.amountFiat, : template.amountFiat,
@ -248,25 +254,36 @@ class SendPage extends BasePage {
? template.cryptoCurrency ? template.cryptoCurrency
: template.fiatCurrency, : template.fiatCurrency,
onTap: () async { onTap: () async {
final fiatFromTemplate = FiatCurrency if (template.additionalRecipients !=
.all null) {
.singleWhere((element) => sendViewModel.clearOutputs();
element.title ==
template.fiatCurrency); template.additionalRecipients!
final output = _defineCurrentOutput(); .forEach((currentElement) async {
output.address = template.address; int i = template
if (template.isCurrencySelected) { .additionalRecipients!
output .indexOf(currentElement);
.setCryptoAmount(template.amount);
Output output;
try {
output = sendViewModel.outputs[i];
} catch (e) {
sendViewModel.addOutput();
output = sendViewModel.outputs[i];
}
await _setInputsFromTemplate(
context,
output: output,
template: currentElement);
});
} else { } else {
sendViewModel.setFiatCurrency( final output = _defineCurrentOutput();
fiatFromTemplate); await _setInputsFromTemplate(
output.setFiatAmount( context,
template.amountFiat); output: output,
template: template);
} }
output.resetParsedAddress();
await output
.fetchParsedAddress(context);
}, },
onRemove: () { onRemove: () {
showPopUp<void>( showPopUp<void>(
@ -477,6 +494,24 @@ class SendPage extends BasePage {
_effectsInstalled = true; _effectsInstalled = true;
} }
Future<void> _setInputsFromTemplate(BuildContext context,
{required Output output, required Template template}) async {
final fiatFromTemplate = FiatCurrency.all
.singleWhere((element) => element.title == template.fiatCurrency);
output.address = template.address;
if (template.isCurrencySelected) {
output.setCryptoAmount(template.amount);
} else {
sendViewModel.setFiatCurrency(fiatFromTemplate);
output.setFiatAmount(template.amountFiat);
}
output.resetParsedAddress();
await output.fetchParsedAddress(context);
}
Output _defineCurrentOutput() { Output _defineCurrentOutput() {
if (controller.page == null) { if (controller.page == null) {
throw Exception('Controller page is null'); throw Exception('Controller page is null');

View file

@ -1,35 +1,21 @@
import 'package:cake_wallet/utils/payment_request.dart'; import 'package:cake_wallet/src/widgets/trail_button.dart';
import 'package:cake_wallet/view_model/send/template_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:mobx/mobx.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:keyboard_actions/keyboard_actions.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart'; import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:cake_wallet/src/widgets/keyboard_done_button.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart'; import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart'; import 'package:cake_wallet/src/widgets/scollable_with_bottom_section.dart';
import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart'; import 'package:cake_wallet/src/screens/send/widgets/send_template_card.dart';
import 'package:smooth_page_indicator/smooth_page_indicator.dart';
class SendTemplatePage extends BasePage { class SendTemplatePage extends BasePage {
SendTemplatePage({required this.sendTemplateViewModel}) { SendTemplatePage({required this.sendTemplateViewModel});
sendTemplateViewModel.output.reset();
}
final SendTemplateViewModel sendTemplateViewModel; final SendTemplateViewModel sendTemplateViewModel;
final _addressController = TextEditingController();
final _cryptoAmountController = TextEditingController();
final _fiatAmountController = TextEditingController();
final _nameController = TextEditingController();
final _formKey = GlobalKey<FormState>(); final _formKey = GlobalKey<FormState>();
final FocusNode _cryptoAmountFocus = FocusNode(); final controller = PageController(initialPage: 0);
final FocusNode _fiatAmountFocus = FocusNode();
bool _effectsInstalled = false;
@override @override
String get title => S.current.exchange_new_template; String get title => S.current.exchange_new_template;
@ -44,273 +30,146 @@ class SendTemplatePage extends BasePage {
AppBarStyle get appBarStyle => AppBarStyle.transparent; AppBarStyle get appBarStyle => AppBarStyle.transparent;
@override @override
Widget body(BuildContext context) { Widget trailing(context) => Observer(builder: (_) {
_setEffects(context); return sendTemplateViewModel.recipients.length > 1
? TrailButton(
caption: S.of(context).remove,
onPressed: () {
int pageToJump = (controller.page?.round() ?? 0) - 1;
pageToJump = pageToJump > 0 ? pageToJump : 0;
final recipient = _defineCurrentRecipient();
sendTemplateViewModel.removeRecipient(recipient);
controller.jumpToPage(pageToJump);
})
: TrailButton(
caption: S.of(context).clear,
onPressed: () {
final recipient = _defineCurrentRecipient();
_formKey.currentState?.reset();
recipient.reset();
});
});
return KeyboardActions( @override
config: KeyboardActionsConfig( Widget body(BuildContext context) {
keyboardActionsPlatform: KeyboardActionsPlatform.IOS, return Form(
keyboardBarColor: Theme.of(context) key: _formKey,
.accentTextTheme! child: ScrollableWithBottomSection(
.bodyLarge!
.backgroundColor!,
nextFocus: false,
actions: [
KeyboardActionsItem(
focusNode: _cryptoAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
),
KeyboardActionsItem(
focusNode: _fiatAmountFocus,
toolbarButtons: [(_) => KeyboardDoneButton()],
)
]),
child: Container(
height: 0,
color: Theme.of(context).colorScheme.background,
child: ScrollableWithBottomSection(
contentPadding: EdgeInsets.only(bottom: 24), contentPadding: EdgeInsets.only(bottom: 24),
content: Container( content: FocusTraversalGroup(
decoration: BoxDecoration( policy: OrderedTraversalPolicy(),
borderRadius: BorderRadius.only( child: Column(children: [
bottomLeft: Radius.circular(24), Container(
bottomRight: Radius.circular(24), height: 460,
), child: Observer(builder: (_) {
gradient: LinearGradient(colors: [ return PageView.builder(
Theme.of(context).primaryTextTheme!.titleMedium!.color!, scrollDirection: Axis.horizontal,
Theme.of(context) controller: controller,
.primaryTextTheme! itemCount: sendTemplateViewModel.recipients.length,
.titleMedium! itemBuilder: (_, index) {
.decorationColor!, final template =
], begin: Alignment.topLeft, end: Alignment.bottomRight), sendTemplateViewModel.recipients[index];
), return SendTemplateCard(
child: Form( template: template,
key: _formKey, index: index,
child: Column( sendTemplateViewModel: sendTemplateViewModel);
children: <Widget>[ });
Padding( })),
padding: EdgeInsets.fromLTRB(24, 90, 24, 32), Padding(
child: Column( padding: EdgeInsets.only(
children: <Widget>[ top: 10, left: 24, right: 24, bottom: 10),
BaseTextFormField( child: Container(
controller: _nameController, height: 10,
hintText: S.of(context).send_name, child: Observer(
borderColor: Theme.of(context) builder: (_) {
.primaryTextTheme! final count = sendTemplateViewModel.recipients.length;
.headlineSmall!
.color!, return count > 1
textStyle: TextStyle( ? SmoothPageIndicator(
fontSize: 14, controller: controller,
fontWeight: FontWeight.w500, count: count,
color: Colors.white), effect: ScrollingDotsEffect(
placeholderTextStyle: TextStyle( spacing: 6.0,
color: Theme.of(context) radius: 6.0,
.primaryTextTheme! dotWidth: 6.0,
.headlineSmall! dotHeight: 6.0,
.decorationColor!, dotColor: Theme.of(context)
fontWeight: FontWeight.w500, .primaryTextTheme
fontSize: 14), .displaySmall!
validator: sendTemplateViewModel.templateValidator, .backgroundColor!,
), activeDotColor: Theme.of(context)
Padding( .primaryTextTheme
padding: EdgeInsets.only(top: 20), .displayMedium!
child: AddressTextField( .backgroundColor!))
controller: _addressController, : Offstage();
onURIScanned: (uri) { },
final paymentRequest = PaymentRequest.fromUri(uri);
_addressController.text = paymentRequest.address;
_cryptoAmountController.text = paymentRequest.amount;
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
buttonColor: Theme.of(context)
.primaryTextTheme!
.headlineMedium!
.color!,
borderColor: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.color!,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
hintStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.decorationColor!),
),
),
Padding(
padding: const EdgeInsets.only(top: 20),
child: Focus(
onFocusChange: (hasFocus) {
if (hasFocus) {
sendTemplateViewModel.selectCurrency();
}
},
child: BaseTextFormField(
focusNode: _cryptoAmountFocus,
controller: _cryptoAmountController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
inputFormatters: [
FilteringTextInputFormatter.deny(
RegExp('[\\-|\\ ]'))
],
prefixIcon: Observer(
builder: (_) => PrefixCurrencyIcon(
title: sendTemplateViewModel
.currency.title,
isSelected:
sendTemplateViewModel
.isCurrencySelected,
)),
hintText: '0.0000',
borderColor: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.color!,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.decorationColor!,
fontWeight: FontWeight.w500,
fontSize: 14),
validator:
sendTemplateViewModel.amountValidator))),
Padding(
padding: const EdgeInsets.only(top: 20),
child: Focus(
onFocusChange: (hasFocus) {
if (hasFocus) {
sendTemplateViewModel.selectFiat();
}
},
child: BaseTextFormField(
focusNode: _fiatAmountFocus,
controller: _fiatAmountController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
inputFormatters: [
FilteringTextInputFormatter.deny(
RegExp('[\\-|\\ ]'))
],
prefixIcon: Observer(
builder: (_) => PrefixCurrencyIcon(
title: sendTemplateViewModel
.fiat.title,
isSelected: sendTemplateViewModel
.isFiatSelected,
)),
hintText: '0.00',
borderColor: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.color!,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme!
.headlineSmall!
.decorationColor!,
fontWeight: FontWeight.w500,
fontSize: 14),
))),
],
), ),
) ),
], ),
), ])),
),
),
bottomSectionPadding: bottomSectionPadding:
EdgeInsets.only(left: 24, right: 24, bottom: 24), EdgeInsets.only(left: 24, right: 24, bottom: 24),
bottomSection: PrimaryButton( bottomSection: Column(children: [
onPressed: () { // if (sendViewModel.hasMultiRecipient)
if (_formKey.currentState != null && _formKey.currentState!.validate()) { Padding(
sendTemplateViewModel.addTemplate( padding: EdgeInsets.only(bottom: 12),
isCurrencySelected: sendTemplateViewModel.isCurrencySelected, child: PrimaryButton(
name: _nameController.text, onPressed: () {
address: _addressController.text, sendTemplateViewModel.addRecipient();
cryptoCurrency:sendTemplateViewModel.currency.title, Future.delayed(const Duration(milliseconds: 250), () {
fiatCurrency: sendTemplateViewModel.fiat.title, controller.jumpToPage(
amount: _cryptoAmountController.text, sendTemplateViewModel.recipients.length - 1);
amountFiat: _fiatAmountController.text); });
Navigator.of(context).pop(); },
} text: S.of(context).add_receiver,
}, color: Colors.transparent,
text: S.of(context).save, textColor: Theme.of(context)
color: Colors.green, .accentTextTheme
textColor: Colors.white, .displaySmall!
), .decorationColor!,
), isDottedBorder: true,
)); borderColor: Theme.of(context)
.primaryTextTheme
.displaySmall!
.decorationColor!)),
PrimaryButton(
onPressed: () {
if (_formKey.currentState != null &&
_formKey.currentState!.validate()) {
final mainTemplate = sendTemplateViewModel.recipients[0];
print(sendTemplateViewModel.recipients.map((element) =>
element.toTemplate(
cryptoCurrency:
sendTemplateViewModel.cryptoCurrency.title,
fiatCurrency:
sendTemplateViewModel.fiatCurrency)));
sendTemplateViewModel.addTemplate(
isCurrencySelected: mainTemplate.isCurrencySelected,
name: mainTemplate.name,
address: mainTemplate.address,
amount: mainTemplate.output.cryptoAmount,
amountFiat: mainTemplate.output.fiatAmount,
additionalRecipients: sendTemplateViewModel.recipients
.map((element) => element.toTemplate(
cryptoCurrency: sendTemplateViewModel
.cryptoCurrency.title,
fiatCurrency:
sendTemplateViewModel.fiatCurrency))
.toList());
Navigator.of(context).pop();
}
},
text: S.of(context).save,
color: Colors.green,
textColor: Colors.white)
])));
} }
void _setEffects(BuildContext context) { TemplateViewModel _defineCurrentRecipient() {
if (_effectsInstalled) { if (controller.page == null) {
return; throw Exception('Controller page is null');
} }
final itemCount = controller.page!.round();
final output = sendTemplateViewModel.output; return sendTemplateViewModel.recipients[itemCount];
reaction((_) => output.fiatAmount, (String amount) {
if (amount != _fiatAmountController.text) {
_fiatAmountController.text = amount;
}
});
reaction((_) => output.cryptoAmount, (String amount) {
if (amount != _cryptoAmountController.text) {
_cryptoAmountController.text = amount;
}
});
reaction((_) => output.address, (String address) {
if (address != _addressController.text) {
_addressController.text = address;
}
});
_cryptoAmountController.addListener(() {
final amount = _cryptoAmountController.text;
if (amount != output.cryptoAmount) {
output.setCryptoAmount(amount);
}
});
_fiatAmountController.addListener(() {
final amount = _fiatAmountController.text;
if (amount != output.fiatAmount) {
output.setFiatAmount(amount);
}
});
_addressController.addListener(() {
final address = _addressController.text;
if (output.address != address) {
output.address = address;
}
});
_effectsInstalled = true;
} }
} }

View file

@ -68,6 +68,7 @@ Future<String> extractAddressFromParsed(
} }
return address; return address;
case ParseFrom.contact:
case ParseFrom.notParsed: case ParseFrom.notParsed:
address = parsedAddress.addresses.first; address = parsedAddress.addresses.first;
return address; return address;
@ -85,4 +86,4 @@ Future<String> extractAddressFromParsed(
}); });
return address; return address;
} }

View file

@ -172,8 +172,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
hintStyle: TextStyle( hintStyle: TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
color: color: Theme.of(context)
Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!), .primaryTextTheme!
.headlineSmall!
.decorationColor!),
onPushPasteButton: (context) async { onPushPasteButton: (context) async {
output.resetParsedAddress(); output.resetParsedAddress();
await output.fetchParsedAddress(context); await output.fetchParsedAddress(context);
@ -182,6 +184,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
output.resetParsedAddress(); output.resetParsedAddress();
await output.fetchParsedAddress(context); await output.fetchParsedAddress(context);
}, },
onSelectedContact: (contact) {
output.loadContact(contact);
},
validator: validator, validator: validator,
selectedCurrency: sendViewModel.currency, selectedCurrency: sendViewModel.currency,
); );
@ -464,7 +469,6 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
color: Colors.white), color: Colors.white),
), ),
Container( Container(

View file

@ -0,0 +1,267 @@
import 'package:cake_wallet/src/screens/send/widgets/prefix_currency_icon_widget.dart';
import 'package:cake_wallet/utils/payment_request.dart';
import 'package:cake_wallet/view_model/send/template_view_model.dart';
import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/view_model/send/send_template_view_model.dart';
import 'package:cake_wallet/src/widgets/address_text_field.dart';
import 'package:cake_wallet/src/widgets/base_text_form_field.dart';
import 'package:mobx/mobx.dart';
class SendTemplateCard extends StatelessWidget {
SendTemplateCard(
{super.key,
required this.template,
required this.index,
required this.sendTemplateViewModel});
final TemplateViewModel template;
final int index;
final SendTemplateViewModel sendTemplateViewModel;
final _addressController = TextEditingController();
final _cryptoAmountController = TextEditingController();
final _fiatAmountController = TextEditingController();
final _nameController = TextEditingController();
final FocusNode _cryptoAmountFocus = FocusNode();
final FocusNode _fiatAmountFocus = FocusNode();
bool _effectsInstalled = false;
@override
Widget build(BuildContext context) {
_setEffects(context);
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(24),
bottomRight: Radius.circular(24)),
gradient: LinearGradient(colors: [
Theme.of(context).primaryTextTheme.titleMedium!.color!,
Theme.of(context).primaryTextTheme.titleMedium!.decorationColor!
], begin: Alignment.topLeft, end: Alignment.bottomRight)),
child: Column(children: <Widget>[
Padding(
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
child: Column(children: <Widget>[
if (index == 0)
BaseTextFormField(
controller: _nameController,
hintText: sendTemplateViewModel.recipients.length > 1
? S.of(context).template_name
: S.of(context).send_name,
borderColor: Theme.of(context)
.primaryTextTheme
.headlineSmall!
.color!,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.headlineSmall!
.decorationColor!,
fontWeight: FontWeight.w500,
fontSize: 14),
validator: sendTemplateViewModel.templateValidator),
Padding(
padding: EdgeInsets.only(top: 20),
child: AddressTextField(
selectedCurrency: sendTemplateViewModel.cryptoCurrency,
controller: _addressController,
onURIScanned: (uri) {
final paymentRequest = PaymentRequest.fromUri(uri);
_addressController.text = paymentRequest.address;
_cryptoAmountController.text = paymentRequest.amount;
},
options: [
AddressTextFieldOption.paste,
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
onPushPasteButton: (context) async {
template.output.resetParsedAddress();
await template.output.fetchParsedAddress(context);
},
onPushAddressBookButton: (context) async {
template.output.resetParsedAddress();
await template.output.fetchParsedAddress(context);
},
buttonColor: Theme.of(context)
.primaryTextTheme
.headlineMedium!
.color!,
borderColor: Theme.of(context)
.primaryTextTheme
.headlineSmall!
.color!,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
hintStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Theme.of(context)
.primaryTextTheme
.headlineSmall!
.decorationColor!),
validator: sendTemplateViewModel.addressValidator)),
Padding(
padding: const EdgeInsets.only(top: 20),
child: Focus(
onFocusChange: (hasFocus) {
if (hasFocus) {
template.selectCurrency();
}
},
child: BaseTextFormField(
focusNode: _cryptoAmountFocus,
controller: _cryptoAmountController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
inputFormatters: [
FilteringTextInputFormatter.deny(
RegExp('[\\-|\\ ]'))
],
prefixIcon: Observer(
builder: (_) => PrefixCurrencyIcon(
title: sendTemplateViewModel
.cryptoCurrency.title,
isSelected: template.isCurrencySelected)),
hintText: '0.0000',
borderColor: Theme.of(context)
.primaryTextTheme
.headlineSmall!
.color!,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.headlineSmall!
.decorationColor!,
fontWeight: FontWeight.w500,
fontSize: 14),
validator: sendTemplateViewModel.amountValidator))),
Padding(
padding: const EdgeInsets.only(top: 20),
child: Focus(
onFocusChange: (hasFocus) {
if (hasFocus) {
template.selectFiat();
}
},
child: BaseTextFormField(
focusNode: _fiatAmountFocus,
controller: _fiatAmountController,
keyboardType: TextInputType.numberWithOptions(
signed: false, decimal: true),
inputFormatters: [
FilteringTextInputFormatter.deny(
RegExp('[\\-|\\ ]'))
],
prefixIcon: Observer(
builder: (_) => PrefixCurrencyIcon(
title: sendTemplateViewModel.fiatCurrency,
isSelected: template.isFiatSelected)),
hintText: '0.00',
borderColor: Theme.of(context)
.primaryTextTheme
.headlineSmall!
.color!,
textStyle: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w500,
color: Colors.white),
placeholderTextStyle: TextStyle(
color: Theme.of(context)
.primaryTextTheme
.headlineSmall!
.decorationColor!,
fontWeight: FontWeight.w500,
fontSize: 14))))
]))
]));
}
void _setEffects(BuildContext context) {
if (_effectsInstalled) {
return;
}
final output = template.output;
if (template.address.isNotEmpty) {
_addressController.text = template.address;
}
if (template.name.isNotEmpty) {
_nameController.text = template.name;
}
if (template.output.cryptoAmount.isNotEmpty) {
_cryptoAmountController.text = template.output.cryptoAmount;
}
if (template.output.fiatAmount.isNotEmpty) {
_fiatAmountController.text = template.output.fiatAmount;
}
_addressController.addListener(() {
final address = _addressController.text;
if (template.address != address) {
template.address = address;
}
});
_cryptoAmountController.addListener(() {
final amount = _cryptoAmountController.text;
if (amount != output.cryptoAmount) {
output.setCryptoAmount(amount);
}
});
_fiatAmountController.addListener(() {
final amount = _fiatAmountController.text;
if (amount != output.fiatAmount) {
output.setFiatAmount(amount);
}
});
_nameController.addListener(() {
final name = _nameController.text;
if (name != template.name) {
template.name = name;
}
});
reaction((_) => template.address, (String address) {
if (address != _addressController.text) {
_addressController.text = address;
}
});
reaction((_) => output.cryptoAmount, (String amount) {
if (amount != _cryptoAmountController.text) {
_cryptoAmountController.text = amount;
}
});
reaction((_) => output.fiatAmount, (String amount) {
if (amount != _fiatAmountController.text) {
_fiatAmountController.text = amount;
}
});
reaction((_) => template.name, (String name) {
if (name != _nameController.text) {
_nameController.text = name;
}
});
_effectsInstalled = true;
}
}

View file

@ -1,16 +1,18 @@
import 'package:cake_wallet/core/totp_request_details.dart'; import 'package:cake_wallet/core/totp_request_details.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/src/screens/base_page.dart'; import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart'; import 'package:cake_wallet/src/screens/receive/widgets/qr_image.dart';
import 'package:cake_wallet/src/widgets/primary_button.dart';
import 'package:cake_wallet/src/widgets/standard_list.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/palette.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/utils/clipboard_util.dart';
import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart'; import 'package:cake_wallet/view_model/set_up_2fa_viewmodel.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:qr_flutter/qr_flutter.dart' as qr; import 'package:qr_flutter/qr_flutter.dart' as qr;
import '../../../palette.dart';
import '../../widgets/primary_button.dart';
import '../../widgets/standard_list.dart';
class Setup2FAQRPage extends BasePage { class Setup2FAQRPage extends BasePage {
Setup2FAQRPage({required this.setup2FAViewModel}); Setup2FAQRPage({required this.setup2FAViewModel});
@ -106,7 +108,8 @@ class Setup2FAQRPage extends BasePage {
height: 32, height: 32,
child: InkWell( child: InkWell(
onTap: () { onTap: () {
Clipboard.setData(ClipboardData(text: '${setup2FAViewModel.secretKey}')); ClipboardUtil.setSensitiveDataToClipboard(
ClipboardData(text: '${setup2FAViewModel.secretKey}'));
showBar<void>(context, S.of(context).copied_to_clipboard); showBar<void>(context, S.of(context).copied_to_clipboard);
}, },
child: Container( child: Container(

View file

@ -1,16 +1,17 @@
import 'package:auto_size_text/auto_size_text.dart'; import 'package:auto_size_text/auto_size_text.dart';
import 'package:cake_wallet/entities/qr_view_data.dart'; import 'package:cake_wallet/entities/qr_view_data.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/src/widgets/section_divider.dart'; import 'package:cake_wallet/src/widgets/section_divider.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/routes.dart';
import 'package:cake_wallet/utils/clipboard_util.dart';
import 'package:cake_wallet/utils/show_bar.dart'; import 'package:cake_wallet/utils/show_bar.dart';
import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
import 'package:device_display_brightness/device_display_brightness.dart'; import 'package:device_display_brightness/device_display_brightness.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_mobx/flutter_mobx.dart'; import 'package:flutter_mobx/flutter_mobx.dart';
import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/src/screens/base_page.dart';
import 'package:cake_wallet/src/widgets/list_row.dart';
import 'package:cake_wallet/view_model/wallet_keys_view_model.dart';
import 'package:cake_wallet/routes.dart';
import 'package:qr_flutter/qr_flutter.dart'; import 'package:qr_flutter/qr_flutter.dart';
class WalletKeysPage extends BasePage { class WalletKeysPage extends BasePage {
@ -56,7 +57,7 @@ class WalletKeysPage extends BasePage {
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.0), borderRadius: BorderRadius.circular(12.0),
color: Theme.of(context).accentTextTheme!.bodySmall!.color!, color: Theme.of(context).accentTextTheme.bodySmall!.color!,
), ),
child: Center( child: Center(
child: Padding( child: Padding(
@ -84,7 +85,7 @@ class WalletKeysPage extends BasePage {
separatorBuilder: (context, index) => Container( separatorBuilder: (context, index) => Container(
height: 1, height: 1,
padding: EdgeInsets.only(left: 24), padding: EdgeInsets.only(left: 24),
color: Theme.of(context).accentTextTheme!.titleLarge!.backgroundColor!, color: Theme.of(context).accentTextTheme.titleLarge!.backgroundColor!,
child: const SectionDivider(), child: const SectionDivider(),
), ),
itemCount: walletKeysViewModel.items.length, itemCount: walletKeysViewModel.items.length,
@ -93,7 +94,7 @@ class WalletKeysPage extends BasePage {
return GestureDetector( return GestureDetector(
onTap: () { onTap: () {
Clipboard.setData(ClipboardData(text: item.value)); ClipboardUtil.setSensitiveDataToClipboard(ClipboardData(text: item.value));
showBar<void>(context, S.of(context).copied_key_to_clipboard(item.title)); showBar<void>(context, S.of(context).copied_key_to_clipboard(item.title));
}, },
child: ListRow( child: ListRow(

View file

@ -1,4 +1,3 @@
import 'package:cake_wallet/utils/device_info.dart'; import 'package:cake_wallet/utils/device_info.dart';
import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -16,10 +15,7 @@ class AddressTextField extends StatelessWidget {
{required this.controller, {required this.controller,
this.isActive = true, this.isActive = true,
this.placeholder, this.placeholder,
this.options = const [ this.options = const [AddressTextFieldOption.qrCode, AddressTextFieldOption.addressBook],
AddressTextFieldOption.qrCode,
AddressTextFieldOption.addressBook
],
this.onURIScanned, this.onURIScanned,
this.focusNode, this.focusNode,
this.isBorderExist = true, this.isBorderExist = true,
@ -31,6 +27,7 @@ class AddressTextField extends StatelessWidget {
this.validator, this.validator,
this.onPushPasteButton, this.onPushPasteButton,
this.onPushAddressBookButton, this.onPushAddressBookButton,
this.onSelectedContact,
this.selectedCurrency}); this.selectedCurrency});
static const prefixIconWidth = 34.0; static const prefixIconWidth = 34.0;
@ -52,6 +49,7 @@ class AddressTextField extends StatelessWidget {
final FocusNode? focusNode; final FocusNode? focusNode;
final Function(BuildContext context)? onPushPasteButton; final Function(BuildContext context)? onPushPasteButton;
final Function(BuildContext context)? onPushAddressBookButton; final Function(BuildContext context)? onPushAddressBookButton;
final Function(ContactBase contact)? onSelectedContact;
final CryptoCurrency? selectedCurrency; final CryptoCurrency? selectedCurrency;
@override @override
@ -66,34 +64,27 @@ class AddressTextField extends StatelessWidget {
controller: controller, controller: controller,
focusNode: focusNode, focusNode: focusNode,
style: textStyle ?? style: textStyle ??
TextStyle( TextStyle(fontSize: 16, color: Theme.of(context).primaryTextTheme.titleLarge!.color!),
fontSize: 16,
color: Theme.of(context).primaryTextTheme!.titleLarge!.color!),
decoration: InputDecoration( decoration: InputDecoration(
suffixIcon: SizedBox( suffixIcon: SizedBox(
width: prefixIconWidth * options.length + width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length),
(spaceBetweenPrefixIcons * options.length),
), ),
hintStyle: hintStyle ?? hintStyle: hintStyle ?? TextStyle(fontSize: 16, color: Theme.of(context).hintColor),
TextStyle(fontSize: 16, color: Theme.of(context).hintColor),
hintText: placeholder ?? S.current.widgets_address, hintText: placeholder ?? S.current.widgets_address,
focusedBorder: isBorderExist focusedBorder: isBorderExist
? UnderlineInputBorder( ? UnderlineInputBorder(
borderSide: BorderSide( borderSide: BorderSide(
color: borderColor ?? Theme.of(context).dividerColor, color: borderColor ?? Theme.of(context).dividerColor, width: 1.0))
width: 1.0))
: InputBorder.none, : InputBorder.none,
disabledBorder: isBorderExist disabledBorder: isBorderExist
? UnderlineInputBorder( ? UnderlineInputBorder(
borderSide: BorderSide( borderSide: BorderSide(
color: borderColor ?? Theme.of(context).dividerColor, color: borderColor ?? Theme.of(context).dividerColor, width: 1.0))
width: 1.0))
: InputBorder.none, : InputBorder.none,
enabledBorder: isBorderExist enabledBorder: isBorderExist
? UnderlineInputBorder( ? UnderlineInputBorder(
borderSide: BorderSide( borderSide: BorderSide(
color: borderColor ?? Theme.of(context).dividerColor, color: borderColor ?? Theme.of(context).dividerColor, width: 1.0))
width: 1.0))
: InputBorder.none, : InputBorder.none,
), ),
validator: validator, validator: validator,
@ -102,11 +93,11 @@ class AddressTextField extends StatelessWidget {
top: 2, top: 2,
right: 0, right: 0,
child: SizedBox( child: SizedBox(
width: prefixIconWidth * options.length + width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length),
(spaceBetweenPrefixIcons * options.length),
child: Row( child: Row(
mainAxisAlignment: ResponsiveLayoutUtil.instance.isMobile mainAxisAlignment: ResponsiveLayoutUtil.instance.isMobile
? MainAxisAlignment.spaceBetween : MainAxisAlignment.end, ? MainAxisAlignment.spaceBetween
: MainAxisAlignment.end,
children: [ children: [
SizedBox(width: 5), SizedBox(width: 5),
if (this.options.contains(AddressTextFieldOption.paste)) ...[ if (this.options.contains(AddressTextFieldOption.paste)) ...[
@ -122,20 +113,14 @@ class AddressTextField extends StatelessWidget {
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: buttonColor ?? color: buttonColor ??
Theme.of(context) Theme.of(context).accentTextTheme.titleLarge!.color!,
.accentTextTheme borderRadius: BorderRadius.all(Radius.circular(6))),
!
.titleLarge!
.color!,
borderRadius:
BorderRadius.all(Radius.circular(6))),
child: Image.asset( child: Image.asset(
'assets/images/paste_ios.png', 'assets/images/paste_ios.png',
color: iconColor ?? color: iconColor ??
Theme.of(context) Theme.of(context)
.primaryTextTheme .primaryTextTheme
! .headlineMedium!
.headlineMedium!
.decorationColor!, .decorationColor!,
)), )),
), ),
@ -155,28 +140,21 @@ class AddressTextField extends StatelessWidget {
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: buttonColor ?? color: buttonColor ??
Theme.of(context) Theme.of(context).accentTextTheme.titleLarge!.color!,
.accentTextTheme borderRadius: BorderRadius.all(Radius.circular(6))),
.titleLarge!
.color!,
borderRadius:
BorderRadius.all(Radius.circular(6))),
child: Image.asset( child: Image.asset(
'assets/images/qr_code_icon.png', 'assets/images/qr_code_icon.png',
color: iconColor ?? color: iconColor ??
Theme.of(context) Theme.of(context)
.primaryTextTheme .primaryTextTheme
!.headlineMedium! .headlineMedium!
.decorationColor!, .decorationColor!,
)), )),
), ),
)) ))
] else ] else
SizedBox(width: 5), SizedBox(width: 5),
if (this if (this.options.contains(AddressTextFieldOption.addressBook)) ...[
.options
.contains(AddressTextFieldOption.addressBook)) ...[
Container( Container(
width: prefixIconWidth, width: prefixIconWidth,
height: prefixIconHeight, height: prefixIconHeight,
@ -184,26 +162,19 @@ class AddressTextField extends StatelessWidget {
child: Semantics( child: Semantics(
label: S.of(context).address_book, label: S.of(context).address_book,
child: InkWell( child: InkWell(
onTap: () async => onTap: () async => _presetAddressBookPicker(context),
_presetAddressBookPicker(context),
child: Container( child: Container(
padding: EdgeInsets.all(8), padding: EdgeInsets.all(8),
decoration: BoxDecoration( decoration: BoxDecoration(
color: buttonColor ?? color: buttonColor ??
Theme.of(context) Theme.of(context).accentTextTheme.titleLarge!.color!,
.accentTextTheme borderRadius: BorderRadius.all(Radius.circular(6))),
!
.titleLarge!
.color!,
borderRadius:
BorderRadius.all(Radius.circular(6))),
child: Image.asset( child: Image.asset(
'assets/images/open_book.png', 'assets/images/open_book.png',
color: iconColor ?? color: iconColor ??
Theme.of(context) Theme.of(context)
.primaryTextTheme .primaryTextTheme
! .headlineMedium!
.headlineMedium!
.decorationColor!, .decorationColor!,
)), )),
), ),
@ -221,30 +192,31 @@ class AddressTextField extends StatelessWidget {
if (code.isEmpty) { if (code.isEmpty) {
return; return;
} }
try { try {
final uri = Uri.parse(code); final uri = Uri.parse(code);
controller?.text = uri.path; controller?.text = uri.path;
onURIScanned?.call(uri); onURIScanned?.call(uri);
} catch(_){ } catch (_) {
controller?.text = code; controller?.text = code;
} }
} }
Future<void> _presetAddressBookPicker(BuildContext context) async { Future<void> _presetAddressBookPicker(BuildContext context) async {
final contact = await Navigator.of(context) final contact = await Navigator.of(context)
.pushNamed(Routes.pickerAddressBook,arguments: selectedCurrency); .pushNamed(Routes.pickerAddressBook, arguments: selectedCurrency);
if (contact is ContactBase && contact.address != null) { if (contact is ContactBase) {
controller?.text = contact.address; controller?.text = contact.address;
onPushAddressBookButton?.call(context); onPushAddressBookButton?.call(context);
onSelectedContact?.call(contact);
} }
} }
Future<void> _pasteAddress(BuildContext context) async { Future<void> _pasteAddress(BuildContext context) async {
final clipboard = await Clipboard.getData('text/plain'); final clipboard = await Clipboard.getData('text/plain');
final address = clipboard?.text ?? ''; final address = clipboard?.text ?? '';
if (address.isNotEmpty) { if (address.isNotEmpty) {
controller?.text = address; controller?.text = address;
} }

View file

@ -66,6 +66,7 @@ class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget {
leading: leading, leading: leading,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
automaticallyImplyMiddle: false, automaticallyImplyMiddle: false,
transitionBetweenRoutes: false,
middle: middle, middle: middle,
trailing: trailing, trailing: trailing,
backgroundColor: backgroundColor, backgroundColor: backgroundColor,

View file

@ -8,7 +8,8 @@ class TemplateTile extends StatefulWidget {
required this.amount, required this.amount,
required this.from, required this.from,
required this.onTap, required this.onTap,
required this.onRemove required this.onRemove,
this.hasMultipleRecipients,
}) : super(key: key); }) : super(key: key);
final String to; final String to;
@ -16,6 +17,7 @@ class TemplateTile extends StatefulWidget {
final String from; final String from;
final VoidCallback onTap; final VoidCallback onTap;
final VoidCallback onRemove; final VoidCallback onRemove;
final bool? hasMultipleRecipients;
@override @override
TemplateTileState createState() => TemplateTileState( TemplateTileState createState() => TemplateTileState(
@ -51,45 +53,47 @@ class TemplateTileState extends State<TemplateTile> {
final toIcon = Image.asset('assets/images/to_icon.png', color: color); final toIcon = Image.asset('assets/images/to_icon.png', color: color);
final content = Row( final content = Row(
mainAxisAlignment: MainAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: <Widget>[ children: widget.hasMultipleRecipients ?? false
Text( ? [
amount, Text(
style: TextStyle( to,
fontSize: 16, style: TextStyle(
fontWeight: FontWeight.w600, fontSize: 16, fontWeight: FontWeight.w600, color: color),
color: color ),
), ]
), : [
Padding( Text(
padding: EdgeInsets.only(left: 5), amount,
child: Text( style: TextStyle(
from, fontSize: 16, fontWeight: FontWeight.w600, color: color),
style: TextStyle( ),
fontSize: 16, Padding(
fontWeight: FontWeight.w600, padding: EdgeInsets.only(left: 5),
color: color child: Text(
), from,
), style: TextStyle(
), fontSize: 16,
Padding( fontWeight: FontWeight.w600,
padding: EdgeInsets.only(left: 5), color: color),
child: toIcon, ),
), ),
Padding( Padding(
padding: EdgeInsets.only(left: 5), padding: EdgeInsets.only(left: 5),
child: Text( child: toIcon,
to, ),
style: TextStyle( Padding(
fontSize: 16, padding: EdgeInsets.only(left: 5),
fontWeight: FontWeight.w600, child: Text(
color: color to,
), style: TextStyle(
), fontSize: 16,
), fontWeight: FontWeight.w600,
], color: color),
); ),
),
]);
final tile = Container( final tile = Container(
padding: EdgeInsets.only(right: 10), padding: EdgeInsets.only(right: 10),

View file

@ -23,25 +23,27 @@ abstract class SendTemplateBase with Store {
templates.replaceRange(0, templates.length, templateSource.values.toList()); templates.replaceRange(0, templates.length, templateSource.values.toList());
@action @action
Future<void> addTemplate({ Future<void> addTemplate(
required String name, {required String name,
required bool isCurrencySelected, required bool isCurrencySelected,
required String address, required String address,
required String cryptoCurrency, required String cryptoCurrency,
required String fiatCurrency, required String fiatCurrency,
required String amount, required String amount,
required String amountFiat}) async { required String amountFiat,
required List<Template> additionalRecipients}) async {
final template = Template( final template = Template(
nameRaw: name, nameRaw: name,
isCurrencySelectedRaw: isCurrencySelected, isCurrencySelectedRaw: isCurrencySelected,
addressRaw: address, addressRaw: address,
cryptoCurrencyRaw: cryptoCurrency, cryptoCurrencyRaw: cryptoCurrency,
fiatCurrencyRaw: fiatCurrency, fiatCurrencyRaw: fiatCurrency,
amountRaw: amount, amountRaw: amount,
amountFiatRaw: amountFiat); amountFiatRaw: amountFiat,
additionalRecipientsRaw: additionalRecipients);
await templateSource.add(template); await templateSource.add(template);
} }
@action @action
Future<void> remove({required Template template}) async => await template.delete(); Future<void> remove({required Template template}) async => await template.delete();
} }

View file

@ -0,0 +1,15 @@
import 'package:flutter/services.dart';
import 'package:cake_wallet/utils/device_info.dart';
import 'package:sensitive_clipboard/sensitive_clipboard.dart';
class ClipboardUtil {
static Future<void> setSensitiveDataToClipboard(ClipboardData data) async {
if (DeviceInfo.instance.isMobile) {
await SensitiveClipboard.copy(data.text);
return;
}
return Clipboard.setData(data);
}
}

View file

@ -67,7 +67,8 @@ abstract class BalanceViewModelBase with Store {
final price = fiatConvertationStore.prices[appStore.wallet!.currency]; final price = fiatConvertationStore.prices[appStore.wallet!.currency];
if (price == null) { if (price == null) {
throw Exception('No price for ${appStore.wallet!.currency} (current wallet)'); // price should update on next fetch:
return 0;
} }
return price; return price;

View file

@ -19,7 +19,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
port = '', port = '',
login = '', login = '',
password = '', password = '',
trusted = false; trusted = false,
useSocksProxy = false,
socksProxyAddress = '';
@observable @observable
ExecutionState state; ExecutionState state;
@ -45,6 +47,12 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@observable @observable
bool trusted; bool trusted;
@observable
bool useSocksProxy;
@observable
String socksProxyAddress;
@computed @computed
bool get isReady => address.isNotEmpty && port.isNotEmpty; bool get isReady => address.isNotEmpty && port.isNotEmpty;
@ -73,6 +81,8 @@ abstract class NodeCreateOrEditViewModelBase with Store {
password = ''; password = '';
useSSL = false; useSSL = false;
trusted = false; trusted = false;
useSocksProxy = false;
socksProxyAddress = '';
} }
@action @action
@ -93,6 +103,12 @@ abstract class NodeCreateOrEditViewModelBase with Store {
@action @action
void setTrusted(bool val) => trusted = val; void setTrusted(bool val) => trusted = val;
@action
void setSocksProxy(bool val) => useSocksProxy = val;
@action
void setSocksProxyAddress(String val) => socksProxyAddress = val;
@action @action
Future<void> save({Node? editingNode, bool saveAsCurrent = false}) async { Future<void> save({Node? editingNode, bool saveAsCurrent = false}) async {
final node = Node( final node = Node(
@ -101,7 +117,8 @@ abstract class NodeCreateOrEditViewModelBase with Store {
login: login, login: login,
password: password, password: password,
useSSL: useSSL, useSSL: useSSL,
trusted: trusted); trusted: trusted,
socksProxyAddress: socksProxyAddress);
try { try {
state = IsExecutingState(); state = IsExecutingState();
if (editingNode != null) { if (editingNode != null) {
@ -130,7 +147,8 @@ abstract class NodeCreateOrEditViewModelBase with Store {
login: login, login: login,
password: password, password: password,
useSSL: useSSL, useSSL: useSSL,
trusted: trusted); trusted: trusted,
socksProxyAddress: socksProxyAddress);
try { try {
connectionState = IsExecutingState(); connectionState = IsExecutingState();
final isAlive = await node.requestNode(); final isAlive = await node.requestNode();

View file

@ -18,6 +18,8 @@ import 'package:cake_wallet/store/settings_store.dart';
import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/generated/i18n.dart';
import 'package:cake_wallet/bitcoin/bitcoin.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart';
import 'package:cake_wallet/entities/contact_base.dart';
part 'output.g.dart'; part 'output.g.dart';
const String cryptoNumberPattern = '0.0'; const String cryptoNumberPattern = '0.0';
@ -244,4 +246,11 @@ abstract class OutputBase with Store {
extractedAddress = await extractAddressFromParsed(context, parsedAddress); extractedAddress = await extractAddressFromParsed(context, parsedAddress);
note = parsedAddress.description; note = parsedAddress.description;
} }
void loadContact(ContactBase contact) {
address = contact.name;
parsedAddress = ParsedAddress.fetchContactAddress(address: contact.address, name: contact.name);
extractedAddress = parsedAddress.addresses.first;
note = parsedAddress.description;
}
} }

View file

@ -1,4 +1,5 @@
import 'package:cake_wallet/view_model/send/output.dart'; import 'package:cake_wallet/view_model/send/template_view_model.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cw_core/wallet_type.dart'; import 'package:cw_core/wallet_type.dart';
import 'package:mobx/mobx.dart'; import 'package:mobx/mobx.dart';
import 'package:cake_wallet/entities/template.dart'; import 'package:cake_wallet/entities/template.dart';
@ -6,10 +7,7 @@ import 'package:cake_wallet/store/templates/send_template_store.dart';
import 'package:cake_wallet/core/template_validator.dart'; import 'package:cake_wallet/core/template_validator.dart';
import 'package:cake_wallet/core/address_validator.dart'; import 'package:cake_wallet/core/address_validator.dart';
import 'package:cake_wallet/core/amount_validator.dart'; import 'package:cake_wallet/core/amount_validator.dart';
import 'package:cake_wallet/core/validator.dart';
import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/entities/fiat_currency.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart'; import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/store/settings_store.dart';
@ -19,72 +17,74 @@ class SendTemplateViewModel = SendTemplateViewModelBase
with _$SendTemplateViewModel; with _$SendTemplateViewModel;
abstract class SendTemplateViewModelBase with Store { abstract class SendTemplateViewModelBase with Store {
SendTemplateViewModelBase(this._wallet, this._settingsStore,
this._sendTemplateStore, this._fiatConversationStore)
: output = Output(_wallet, _settingsStore, _fiatConversationStore, () => _wallet.currency) {
output = Output(_wallet, _settingsStore, _fiatConversationStore, () => currency);
}
Output output;
Validator get amountValidator =>
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
Validator get addressValidator => AddressValidator(type: _wallet.currency);
Validator get templateValidator => TemplateValidator();
CryptoCurrency get currency => _wallet.currency;
FiatCurrency get fiat => _settingsStore.fiatCurrency;
@observable
bool isCurrencySelected = true;
@observable
bool isFiatSelected = false;
@action
void selectCurrency () {
isCurrencySelected = true;
isFiatSelected = false;
}
@action
void selectFiat () {
isFiatSelected = true;
isCurrencySelected = false;
}
@computed
ObservableList<Template> get templates => _sendTemplateStore.templates;
final WalletBase _wallet; final WalletBase _wallet;
final SettingsStore _settingsStore; final SettingsStore _settingsStore;
final SendTemplateStore _sendTemplateStore; final SendTemplateStore _sendTemplateStore;
final FiatConversionStore _fiatConversationStore; final FiatConversionStore _fiatConversationStore;
SendTemplateViewModelBase(this._wallet, this._settingsStore,
this._sendTemplateStore, this._fiatConversationStore)
: recipients = ObservableList<TemplateViewModel>() {
addRecipient();
}
ObservableList<TemplateViewModel> recipients;
@action
void addRecipient() {
recipients.add(TemplateViewModel(
cryptoCurrency: cryptoCurrency,
wallet: _wallet,
settingsStore: _settingsStore,
fiatConversationStore: _fiatConversationStore));
}
@action
void removeRecipient(TemplateViewModel recipient) {
recipients.remove(recipient);
}
AmountValidator get amountValidator =>
AmountValidator(currency: walletTypeToCryptoCurrency(_wallet.type));
AddressValidator get addressValidator =>
AddressValidator(type: _wallet.currency);
TemplateValidator get templateValidator => TemplateValidator();
@computed
CryptoCurrency get cryptoCurrency => _wallet.currency;
@computed
String get fiatCurrency => _settingsStore.fiatCurrency.title;
@computed
ObservableList<Template> get templates => _sendTemplateStore.templates;
@action
void updateTemplate() => _sendTemplateStore.update(); void updateTemplate() => _sendTemplateStore.update();
@action
void addTemplate( void addTemplate(
{required String name, {required String name,
required bool isCurrencySelected, required bool isCurrencySelected,
required String address, required String address,
required String cryptoCurrency, required String amount,
required String fiatCurrency, required String amountFiat,
required String amount, required List<Template> additionalRecipients}) {
required String amountFiat}) {
_sendTemplateStore.addTemplate( _sendTemplateStore.addTemplate(
name: name, name: name,
isCurrencySelected: isCurrencySelected, isCurrencySelected: isCurrencySelected,
address: address, address: address,
cryptoCurrency: cryptoCurrency, cryptoCurrency: cryptoCurrency.title,
fiatCurrency: fiatCurrency, fiatCurrency: fiatCurrency,
amount: amount, amount: amount,
amountFiat: amountFiat); amountFiat: amountFiat,
additionalRecipients: additionalRecipients);
updateTemplate(); updateTemplate();
} }
@action
void removeTemplate({required Template template}) { void removeTemplate({required Template template}) {
_sendTemplateStore.remove(template: template); _sendTemplateStore.remove(template: template);
updateTemplate(); updateTemplate();

View file

@ -0,0 +1,80 @@
import 'package:cake_wallet/entities/template.dart';
import 'package:cake_wallet/view_model/send/output.dart';
import 'package:mobx/mobx.dart';
import 'package:cw_core/wallet_base.dart';
import 'package:cw_core/crypto_currency.dart';
import 'package:cake_wallet/store/dashboard/fiat_conversion_store.dart';
import 'package:cake_wallet/store/settings_store.dart';
part 'template_view_model.g.dart';
class TemplateViewModel = TemplateViewModelBase with _$TemplateViewModel;
abstract class TemplateViewModelBase with Store {
final CryptoCurrency cryptoCurrency;
final WalletBase _wallet;
final SettingsStore _settingsStore;
final FiatConversionStore _fiatConversationStore;
TemplateViewModelBase(
{required this.cryptoCurrency,
required WalletBase wallet,
required SettingsStore settingsStore,
required FiatConversionStore fiatConversationStore})
: _wallet = wallet,
_settingsStore = settingsStore,
_fiatConversationStore = fiatConversationStore,
output = Output(wallet, settingsStore, fiatConversationStore,
() => wallet.currency) {
output = Output(
_wallet, _settingsStore, _fiatConversationStore, () => cryptoCurrency);
}
@observable
Output output;
@observable
String name = '';
@observable
String address = '';
@observable
bool isCurrencySelected = true;
@observable
bool isFiatSelected = false;
@action
void selectCurrency() {
isCurrencySelected = true;
isFiatSelected = false;
}
@action
void selectFiat() {
isFiatSelected = true;
isCurrencySelected = false;
}
@action
void reset() {
name = '';
address = '';
isCurrencySelected = true;
isFiatSelected = false;
output.reset();
}
Template toTemplate(
{required String cryptoCurrency, required String fiatCurrency}) {
return Template(
isCurrencySelectedRaw: isCurrencySelected,
nameRaw: name,
addressRaw: address,
cryptoCurrencyRaw: cryptoCurrency,
fiatCurrencyRaw: fiatCurrency,
amountRaw: output.cryptoAmount,
amountFiatRaw: output.fiatAmount);
}
}

View file

@ -3,7 +3,6 @@ dependencies:
sdk: flutter sdk: flutter
flutter_localizations: flutter_localizations:
sdk: flutter sdk: flutter
flutter_cupertino_localizations: ^1.0.1
intl: ^0.17.0 intl: ^0.17.0
url_launcher: ^6.1.4 url_launcher: ^6.1.4
qr_flutter: qr_flutter:
@ -82,6 +81,7 @@ dependencies:
path_provider_android: 2.0.24 path_provider_android: 2.0.24
shared_preferences_android: 2.0.17 shared_preferences_android: 2.0.17
url_launcher_android: 6.0.24 url_launcher_android: 6.0.24
sensitive_clipboard: ^1.0.0
bitcoin_flutter: bitcoin_flutter:
git: git:
url: https://github.com/cake-tech/bitcoin_flutter.git url: https://github.com/cake-tech/bitcoin_flutter.git
@ -130,4 +130,4 @@ flutter:
- asset: assets/fonts/Lato-Regular.ttf - asset: assets/fonts/Lato-Regular.ttf
- asset: assets/fonts/Lato-Medium.ttf - asset: assets/fonts/Lato-Medium.ttf
- asset: assets/fonts/Lato-Semibold.ttf - asset: assets/fonts/Lato-Semibold.ttf
- asset: assets/fonts/Lato-Bold.ttf - asset: assets/fonts/Lato-Bold.ttf

View file

@ -250,8 +250,8 @@
"transaction_details_recipient_address": "عناوين المستلم", "transaction_details_recipient_address": "عناوين المستلم",
"wallet_list_title": "محفظة Monero", "wallet_list_title": "محفظة Monero",
"wallet_list_create_new_wallet": "إنشاء محفظة جديدة", "wallet_list_create_new_wallet": "إنشاء محفظة جديدة",
"wallet_list_edit_wallet": "تحرير المحفظة", "wallet_list_edit_wallet" : "تحرير المحفظة",
"wallet_list_wallet_name": "اسم المحفظة", "wallet_list_wallet_name" : "اسم المحفظة",
"wallet_list_restore_wallet": "استعادة المحفظة", "wallet_list_restore_wallet": "استعادة المحفظة",
"wallet_list_load_wallet": "تحميل المحفظة", "wallet_list_load_wallet": "تحميل المحفظة",
"wallet_list_loading_wallet": "جار تحميل محفظة ${wallet_name}", "wallet_list_loading_wallet": "جار تحميل محفظة ${wallet_name}",
@ -269,6 +269,7 @@
"error_text_address": "يجب أن يتوافق عنوان المحفظة مع نوع\nالعملة المشفرة", "error_text_address": "يجب أن يتوافق عنوان المحفظة مع نوع\nالعملة المشفرة",
"error_text_node_address": "الرجاء إدخال عنوان IPv4", "error_text_node_address": "الرجاء إدخال عنوان IPv4",
"error_text_node_port": "منفذ العقدة يمكن أن يحتوي فقط على أرقام بين 0 و 65535", "error_text_node_port": "منفذ العقدة يمكن أن يحتوي فقط على أرقام بين 0 و 65535",
"error_text_node_proxy_address": "الرجاء إدخال <عنوان IPv4>: <port> ، على سبيل المثال 127.0.0.1:9050",
"error_text_payment_id": "يمكن أن يحتوي معرّف الدفع فقط من 16 إلى 64 حرفًا hex", "error_text_payment_id": "يمكن أن يحتوي معرّف الدفع فقط من 16 إلى 64 حرفًا hex",
"error_text_xmr": "لا يمكن أن تتجاوز قيمة XMR الرصيد المتاح.\nيجب أن يكون عدد الكسور أقل من أو يساوي 12", "error_text_xmr": "لا يمكن أن تتجاوز قيمة XMR الرصيد المتاح.\nيجب أن يكون عدد الكسور أقل من أو يساوي 12",
"error_text_fiat": "لا يمكن أن تتجاوز قيمة المبلغ الرصيد المتاح.\nيجب أن يكون عدد الكسور أقل أو يساوي 2", "error_text_fiat": "لا يمكن أن تتجاوز قيمة المبلغ الرصيد المتاح.\nيجب أن يكون عدد الكسور أقل أو يساوي 2",
@ -652,5 +653,6 @@
"generate_name": "توليد الاسم", "generate_name": "توليد الاسم",
"balance_page": "صفحة التوازن", "balance_page": "صفحة التوازن",
"share": "يشارك", "share": "يشارك",
"slidable": "قابل للانزلاق" "slidable": "قابل للانزلاق",
"template_name": "اسم القالب"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Адресът на портфейла трябва да отговаря \n на вида криптовалута", "error_text_address": "Адресът на портфейла трябва да отговаря \n на вида криптовалута",
"error_text_node_address": "Моля, въведете iPv4 адрес", "error_text_node_address": "Моля, въведете iPv4 адрес",
"error_text_node_port": "Node port-ът е цяло число между 0 и 65535", "error_text_node_port": "Node port-ът е цяло число между 0 и 65535",
"error_text_node_proxy_address": "Моля, въведете <IPv4 адрес>:<порт>, например 127.0.0.1:9050",
"error_text_payment_id": "Payment ID-то може да съдържа само между 16 и 64 шестнайсетични символа", "error_text_payment_id": "Payment ID-то може да съдържа само между 16 и 64 шестнайсетични символа",
"error_text_xmr": "XMR сумата не може да надхвърля наличния баланс.\nБроят на цифрите след десетичната запетая може да бъде най-много 12", "error_text_xmr": "XMR сумата не може да надхвърля наличния баланс.\nБроят на цифрите след десетичната запетая може да бъде най-много 12",
"error_text_fiat": "Сумата не може да надвишава наличния баланс.\nThe number of fraction digits must be less or equal to 2", "error_text_fiat": "Сумата не може да надвишава наличния баланс.\nThe number of fraction digits must be less or equal to 2",
@ -648,5 +649,6 @@
"generate_name": "Генериране на име", "generate_name": "Генериране на име",
"balance_page": "Страница за баланс", "balance_page": "Страница за баланс",
"share": "Дял", "share": "Дял",
"slidable": "Плъзгащ се" "slidable": "Плъзгащ се",
"template_name": "Име на шаблон"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Adresa peněženky musí odpovídat typu\nkryptoměny", "error_text_address": "Adresa peněženky musí odpovídat typu\nkryptoměny",
"error_text_node_address": "prosím zadejte IPv4 adresu", "error_text_node_address": "prosím zadejte IPv4 adresu",
"error_text_node_port": "Port uzlu musí být číslo mezi 0 a 65535", "error_text_node_port": "Port uzlu musí být číslo mezi 0 a 65535",
"error_text_node_proxy_address": "Zadejte prosím <adresa IPv4>:<port>, například 127.0.0.1:9050",
"error_text_payment_id": "ID platby se musí skládat z 16 až 64 hexadecimálních znaků", "error_text_payment_id": "ID platby se musí skládat z 16 až 64 hexadecimálních znaků",
"error_text_xmr": "Hodnota XMR nemůže překročit dostupný zůstatek.\nPočet desetinných míst musí být menší, nebo roven 12", "error_text_xmr": "Hodnota XMR nemůže překročit dostupný zůstatek.\nPočet desetinných míst musí být menší, nebo roven 12",
"error_text_fiat": "Částka nemůže překročit dostupný zůstatek.\nPočet desetinných míst musí být menší, nebo roven 2", "error_text_fiat": "Částka nemůže překročit dostupný zůstatek.\nPočet desetinných míst musí být menší, nebo roven 2",
@ -648,5 +649,6 @@
"generate_name": "Generovat jméno", "generate_name": "Generovat jméno",
"balance_page": "Stránka zůstatku", "balance_page": "Stránka zůstatku",
"share": "Podíl", "share": "Podíl",
"slidable": "Posuvné" "slidable": "Posuvné",
"template_name": "Název šablony"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Die Walletadresse muss dem Typ der Kryptowährung\nentsprechen", "error_text_address": "Die Walletadresse muss dem Typ der Kryptowährung\nentsprechen",
"error_text_node_address": "Bitte geben Sie eine iPv4-Adresse ein", "error_text_node_address": "Bitte geben Sie eine iPv4-Adresse ein",
"error_text_node_port": "Der Knotenport darf nur Nummern zwischen 0 und 65535 enthalten", "error_text_node_port": "Der Knotenport darf nur Nummern zwischen 0 und 65535 enthalten",
"error_text_node_proxy_address": "Bitte geben Sie <IPv4-Adresse>:<Port> ein, zum Beispiel 127.0.0.1:9050",
"error_text_payment_id": "Die Zahlungs-ID darf nur 16 bis 64 hexadezimale Zeichen enthalten", "error_text_payment_id": "Die Zahlungs-ID darf nur 16 bis 64 hexadezimale Zeichen enthalten",
"error_text_xmr": "Der XMR-Wert darf das verfügbare Guthaben nicht überschreiten.\nDie Anzahl der Nachkommastellen muss kleiner oder gleich 12 sein", "error_text_xmr": "Der XMR-Wert darf das verfügbare Guthaben nicht überschreiten.\nDie Anzahl der Nachkommastellen muss kleiner oder gleich 12 sein",
"error_text_fiat": "Der Wert des Betrags darf den verfügbaren Kontostand nicht überschreiten.\nDie Anzahl der Nachkommastellen muss kleiner oder gleich 2 sein", "error_text_fiat": "Der Wert des Betrags darf den verfügbaren Kontostand nicht überschreiten.\nDie Anzahl der Nachkommastellen muss kleiner oder gleich 2 sein",
@ -654,5 +655,6 @@
"generate_name": "Namen generieren", "generate_name": "Namen generieren",
"balance_page": "Balance-Seite", "balance_page": "Balance-Seite",
"share": "Aktie", "share": "Aktie",
"slidable": "Verschiebbar" "slidable": "Verschiebbar",
"template_name": "Vorlagenname"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Wallet address must correspond to the type\nof cryptocurrency", "error_text_address": "Wallet address must correspond to the type\nof cryptocurrency",
"error_text_node_address": "Please enter a iPv4 address", "error_text_node_address": "Please enter a iPv4 address",
"error_text_node_port": "Node port can only contain numbers between 0 and 65535", "error_text_node_port": "Node port can only contain numbers between 0 and 65535",
"error_text_node_proxy_address": "Please enter <IPv4 address>:<port>, for example 127.0.0.1:9050",
"error_text_payment_id": "Payment ID can only contain from 16 to 64 chars in hex", "error_text_payment_id": "Payment ID can only contain from 16 to 64 chars in hex",
"error_text_xmr": "XMR value can't exceed available balance.\nThe number of fraction digits must be less or equal to 12", "error_text_xmr": "XMR value can't exceed available balance.\nThe number of fraction digits must be less or equal to 12",
"error_text_fiat": "Value of amount can't exceed available balance.\nThe number of fraction digits must be less or equal to 2", "error_text_fiat": "Value of amount can't exceed available balance.\nThe number of fraction digits must be less or equal to 2",
@ -654,5 +655,6 @@
"generate_name": "Generate Name", "generate_name": "Generate Name",
"balance_page": "Balance Page", "balance_page": "Balance Page",
"share": "Share", "share": "Share",
"slidable": "Slidable" "slidable": "Slidable",
"template_name": "Template Name"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "La dirección de la billetera debe corresponder al tipo \nde criptomoneda", "error_text_address": "La dirección de la billetera debe corresponder al tipo \nde criptomoneda",
"error_text_node_address": "Por favor, introduzca una dirección iPv4", "error_text_node_address": "Por favor, introduzca una dirección iPv4",
"error_text_node_port": "El puerto de nodo solo puede contener números entre 0 y 65535", "error_text_node_port": "El puerto de nodo solo puede contener números entre 0 y 65535",
"error_text_node_proxy_address": "Ingrese <dirección IPv4>:<puerto>, por ejemplo 127.0.0.1:9050",
"error_text_payment_id": "La ID de pago solo puede contener de 16 a 64 caracteres en hexadecimal", "error_text_payment_id": "La ID de pago solo puede contener de 16 a 64 caracteres en hexadecimal",
"error_text_xmr": "El valor XMR no puede exceder el saldo disponible.\nTEl número de dígitos de fracción debe ser menor o igual a 12", "error_text_xmr": "El valor XMR no puede exceder el saldo disponible.\nTEl número de dígitos de fracción debe ser menor o igual a 12",
"error_text_fiat": "El valor de la cantidad no puede exceder el saldo disponible.\nEl número de dígitos de fracción debe ser menor o igual a 2", "error_text_fiat": "El valor de la cantidad no puede exceder el saldo disponible.\nEl número de dígitos de fracción debe ser menor o igual a 2",
@ -654,5 +655,6 @@
"generate_name": "Generar nombre", "generate_name": "Generar nombre",
"balance_page": "Página de saldo", "balance_page": "Página de saldo",
"share": "Compartir", "share": "Compartir",
"slidable": "deslizable" "slidable": "deslizable",
"template_name": "Nombre de la plantilla"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "L'adresse du portefeuille (wallet) doit correspondre au type de\ncryptomonnaie", "error_text_address": "L'adresse du portefeuille (wallet) doit correspondre au type de\ncryptomonnaie",
"error_text_node_address": "Merci d'entrer une adresse IPv4", "error_text_node_address": "Merci d'entrer une adresse IPv4",
"error_text_node_port": "Le port d'un nœud doit être un nombre compris entre 0 et 65535", "error_text_node_port": "Le port d'un nœud doit être un nombre compris entre 0 et 65535",
"error_text_node_proxy_address": "Veuillez saisir <adresse IPv4> :<port>, par exemple 127.0.0.1:9050",
"error_text_payment_id": "Un ID de paiement ne peut être constitué que de 16 à 64 caractères hexadécimaux", "error_text_payment_id": "Un ID de paiement ne peut être constitué que de 16 à 64 caractères hexadécimaux",
"error_text_xmr": "La valeur de XMR dépasse le solde disponible.\nLa partie décimale doit comporter au plus 12 chiffres", "error_text_xmr": "La valeur de XMR dépasse le solde disponible.\nLa partie décimale doit comporter au plus 12 chiffres",
"error_text_fiat": "La valeur du montant ne peut dépasser le solde disponible.\nLa partie décimale doit comporter au plus 2 chiffres", "error_text_fiat": "La valeur du montant ne peut dépasser le solde disponible.\nLa partie décimale doit comporter au plus 2 chiffres",
@ -654,5 +655,6 @@
"generate_name": "Générer un nom", "generate_name": "Générer un nom",
"balance_page": "Page Solde", "balance_page": "Page Solde",
"share": "Partager", "share": "Partager",
"slidable": "Glissable" "slidable": "Glissable",
"template_name": "Nom du modèle"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Adireshin hujja ya kamata ya dace da irin\nna cryptocurrency", "error_text_address": "Adireshin hujja ya kamata ya dace da irin\nna cryptocurrency",
"error_text_node_address": "Da fatan a shigar da iPv4 adireshin", "error_text_node_address": "Da fatan a shigar da iPv4 adireshin",
"error_text_node_port": "Node tashar jiragen ruwa zai iya ƙunsar lambobi tsakanin 0 zuwa 65535 kawai", "error_text_node_port": "Node tashar jiragen ruwa zai iya ƙunsar lambobi tsakanin 0 zuwa 65535 kawai",
"error_text_node_proxy_address": "Da fatan za a shigar da <IPv4 address>:<port>, misali 127.0.0.1:9050",
"error_text_payment_id": "ID na biyan kudi kawai zai iya ɗaukar daga 16 zuwa 64 haruffa a cikin hex", "error_text_payment_id": "ID na biyan kudi kawai zai iya ɗaukar daga 16 zuwa 64 haruffa a cikin hex",
"error_text_xmr": "XMR adadin ba zai iya wuce available balance.\nAdadin haruffan gaba zai kamata ya zama ko ƙasa daga na 12", "error_text_xmr": "XMR adadin ba zai iya wuce available balance.\nAdadin haruffan gaba zai kamata ya zama ko ƙasa daga na 12",
"error_text_fiat": "Adadin kudin ba zai iya wuce available balance.\nAdadin haruffan gaba zai kamata ya zama ko ƙasa daga na 2", "error_text_fiat": "Adadin kudin ba zai iya wuce available balance.\nAdadin haruffan gaba zai kamata ya zama ko ƙasa daga na 2",
@ -634,5 +635,6 @@
"generate_name": "Ƙirƙirar Suna", "generate_name": "Ƙirƙirar Suna",
"balance_page": "Ma'auni Page", "balance_page": "Ma'auni Page",
"share": "Raba", "share": "Raba",
"slidable": "Mai iya zamewa" "slidable": "Mai iya zamewa",
"template_name": "Sunan Samfura"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "वॉलेट पता प्रकार के अनुरूप होना चाहिए\nक्रिप्टोकरेंसी का", "error_text_address": "वॉलेट पता प्रकार के अनुरूप होना चाहिए\nक्रिप्टोकरेंसी का",
"error_text_node_address": "कृपया एक IPv4 पता दर्ज करें", "error_text_node_address": "कृपया एक IPv4 पता दर्ज करें",
"error_text_node_port": "नोड पोर्ट में केवल 0 और 65535 के बीच संख्याएँ हो सकती हैं", "error_text_node_port": "नोड पोर्ट में केवल 0 और 65535 के बीच संख्याएँ हो सकती हैं",
"error_text_node_proxy_address": "कृपया <IPv4 पता> दर्ज करें: <पोर्ट>, उदाहरण के लिए 127.0.0.1:9050",
"error_text_payment_id": "पेमेंट आईडी केवल हेक्स में 16 से 64 चार्ट तक हो सकती है", "error_text_payment_id": "पेमेंट आईडी केवल हेक्स में 16 से 64 चार्ट तक हो सकती है",
"error_text_xmr": "एक्सएमआर मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या 12 से कम या इसके बराबर होनी चाहिए", "error_text_xmr": "एक्सएमआर मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या 12 से कम या इसके बराबर होनी चाहिए",
"error_text_fiat": "राशि का मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या कम या 2 के बराबर होनी चाहिए", "error_text_fiat": "राशि का मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या कम या 2 के बराबर होनी चाहिए",
@ -654,5 +655,6 @@
"generate_name": "नाम जनरेट करें", "generate_name": "नाम जनरेट करें",
"balance_page": "बैलेंस पेज", "balance_page": "बैलेंस पेज",
"share": "शेयर करना", "share": "शेयर करना",
"slidable": "फिसलने लायक" "slidable": "फिसलने लायक",
"template_name": "टेम्पलेट नाम"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Adresa novčanika mora odgovarati\nvrsti kriptovalute", "error_text_address": "Adresa novčanika mora odgovarati\nvrsti kriptovalute",
"error_text_node_address": "Molimo unesite iPv4 adresu", "error_text_node_address": "Molimo unesite iPv4 adresu",
"error_text_node_port": "Node port smije sadržavati samo brojeve između 0 i 65535", "error_text_node_port": "Node port smije sadržavati samo brojeve između 0 i 65535",
"error_text_node_proxy_address": "Unesite <IPv4 adresa>:<port>, na primjer 127.0.0.1:9050",
"error_text_payment_id": "ID plaćanja smije sadržavati samo od 16 do 64 znakova hex vrijednosti", "error_text_payment_id": "ID plaćanja smije sadržavati samo od 16 do 64 znakova hex vrijednosti",
"error_text_xmr": "XMR vrijednost ne smije biti veća od raspoloživog iznosa.\nBroj decimala smije biti 12 ili manji.", "error_text_xmr": "XMR vrijednost ne smije biti veća od raspoloživog iznosa.\nBroj decimala smije biti 12 ili manji.",
"error_text_fiat": "Vrijednost iznosa ne smije biti veća od raspoloživog iznosa.\nBroj decimala smije biti 2 ili manji.", "error_text_fiat": "Vrijednost iznosa ne smije biti veća od raspoloživog iznosa.\nBroj decimala smije biti 2 ili manji.",
@ -654,5 +655,6 @@
"generate_name": "Generiraj ime", "generate_name": "Generiraj ime",
"balance_page": "Stranica sa stanjem", "balance_page": "Stranica sa stanjem",
"share": "Udio", "share": "Udio",
"slidable": "Klizna" "slidable": "Klizna",
"template_name": "Naziv predloška"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Alamat dompet harus sesuai dengan tipe\nmata uang kripto", "error_text_address": "Alamat dompet harus sesuai dengan tipe\nmata uang kripto",
"error_text_node_address": "Silakan masukkan alamat iPv4", "error_text_node_address": "Silakan masukkan alamat iPv4",
"error_text_node_port": "Port node hanya dapat berisi angka antara 0 dan 65535", "error_text_node_port": "Port node hanya dapat berisi angka antara 0 dan 65535",
"error_text_node_proxy_address": "Masukkan <alamat IPv4>:<port>, misalnya 127.0.0.1:9050",
"error_text_payment_id": "ID pembayaran hanya dapat berisi dari 16 hingga 64 karakter dalam hex", "error_text_payment_id": "ID pembayaran hanya dapat berisi dari 16 hingga 64 karakter dalam hex",
"error_text_xmr": "Nilai XMR tidak boleh melebihi saldo yang tersedia.\nJumlah digit pecahan harus kurang atau sama dengan 12", "error_text_xmr": "Nilai XMR tidak boleh melebihi saldo yang tersedia.\nJumlah digit pecahan harus kurang atau sama dengan 12",
"error_text_fiat": "Nilai jumlah tidak boleh melebihi saldo yang tersedia.\nJumlah digit pecahan harus kurang atau sama dengan 2", "error_text_fiat": "Nilai jumlah tidak boleh melebihi saldo yang tersedia.\nJumlah digit pecahan harus kurang atau sama dengan 2",
@ -644,5 +645,6 @@
"generate_name": "Hasilkan Nama", "generate_name": "Hasilkan Nama",
"balance_page": "Halaman Saldo", "balance_page": "Halaman Saldo",
"share": "Membagikan", "share": "Membagikan",
"slidable": "Dapat digeser" "slidable": "Dapat digeser",
"template_name": "Nama Templat"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "L'indirizzo del Portafoglio deve corrispondere alla tipologia\ndi criptovaluta", "error_text_address": "L'indirizzo del Portafoglio deve corrispondere alla tipologia\ndi criptovaluta",
"error_text_node_address": "Gentilmente inserisci un indirizzo iPv4", "error_text_node_address": "Gentilmente inserisci un indirizzo iPv4",
"error_text_node_port": "La porta del nodo può contenere solo numeri compresi tra 0 e 65535", "error_text_node_port": "La porta del nodo può contenere solo numeri compresi tra 0 e 65535",
"error_text_node_proxy_address": "Inserisci <indirizzo IPv4>:<porta>, ad esempio 127.0.0.1:9050",
"error_text_payment_id": "L'ID del pagamento può contenere solo da 16 a 64 caratteri in esadecimale", "error_text_payment_id": "L'ID del pagamento può contenere solo da 16 a 64 caratteri in esadecimale",
"error_text_xmr": "Il valore in XMR non può eccedere il saldo disponibile.\nIl numero delle cifre decimali deve essere inferiore o uguale a 12", "error_text_xmr": "Il valore in XMR non può eccedere il saldo disponibile.\nIl numero delle cifre decimali deve essere inferiore o uguale a 12",
"error_text_fiat": "L'ammontare non può eccedere il saldo dispoinibile.\nIl numero di cifre decimali deve essere inferiore o uguale a 2", "error_text_fiat": "L'ammontare non può eccedere il saldo dispoinibile.\nIl numero di cifre decimali deve essere inferiore o uguale a 2",
@ -654,5 +655,6 @@
"generate_name": "Genera nome", "generate_name": "Genera nome",
"balance_page": "Pagina di equilibrio", "balance_page": "Pagina di equilibrio",
"share": "Condividere", "share": "Condividere",
"slidable": "Scorrevole" "slidable": "Scorrevole",
"template_name": "Nome modello"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "ウォレットアドレスは、\n暗号通貨", "error_text_address": "ウォレットアドレスは、\n暗号通貨",
"error_text_node_address": "iPv4アドレスを入力してください", "error_text_node_address": "iPv4アドレスを入力してください",
"error_text_node_port": "ードポートには、0〜65535の数字のみを含めることができます", "error_text_node_port": "ードポートには、0〜65535の数字のみを含めることができます",
"error_text_node_proxy_address": "<IPv4 アドレス>:<ポート> を入力してください (例: 127.0.0.1:9050)",
"error_text_payment_id": "支払いIDには、16進数で16〜64文字しか含めることができません", "error_text_payment_id": "支払いIDには、16進数で16〜64文字しか含めることができません",
"error_text_xmr": "XMR値は利用可能な残高を超えることはできません.\n小数桁数は12以下でなければなりません", "error_text_xmr": "XMR値は利用可能な残高を超えることはできません.\n小数桁数は12以下でなければなりません",
"error_text_fiat": "金額は利用可能な残高を超えることはできません.\n小数桁の数は2以下でなければなりません", "error_text_fiat": "金額は利用可能な残高を超えることはできません.\n小数桁の数は2以下でなければなりません",
@ -654,5 +655,6 @@
"generate_name": "名前の生成", "generate_name": "名前の生成",
"balance_page": "残高ページ", "balance_page": "残高ページ",
"share": "共有", "share": "共有",
"slidable": "スライド可能" "slidable": "スライド可能",
"template_name": "テンプレート名"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "지갑 주소는 유형과 일치해야합니다\n암호 화폐", "error_text_address": "지갑 주소는 유형과 일치해야합니다\n암호 화폐",
"error_text_node_address": "iPv4 주소를 입력하십시오", "error_text_node_address": "iPv4 주소를 입력하십시오",
"error_text_node_port": "노드 포트는 0에서 65535 사이의 숫자 만 포함 할 수 있습니다", "error_text_node_port": "노드 포트는 0에서 65535 사이의 숫자 만 포함 할 수 있습니다",
"error_text_node_proxy_address": "<IPv4 주소>:<포트>를 입력하십시오(예: 127.0.0.1:9050).",
"error_text_payment_id": "지불 ID는 16 ~ 64 자의 16 진 문자 만 포함 할 수 있습니다", "error_text_payment_id": "지불 ID는 16 ~ 64 자의 16 진 문자 만 포함 할 수 있습니다",
"error_text_xmr": "XMR 값은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 12 이하 여야합니다", "error_text_xmr": "XMR 값은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 12 이하 여야합니다",
"error_text_fiat": "금액은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 2보다 작거나 같아야합니다", "error_text_fiat": "금액은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 2보다 작거나 같아야합니다",
@ -654,5 +655,6 @@
"generate_name": "이름 생성", "generate_name": "이름 생성",
"balance_page": "잔액 페이지", "balance_page": "잔액 페이지",
"share": "공유하다", "share": "공유하다",
"slidable": "슬라이딩 가능" "slidable": "슬라이딩 가능",
"template_name": "템플릿 이름"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Wallet လိပ်စာသည် အမျိုးအစား\no cryptocurrency နှင့် ကိုက်ညီရပါမည်။", "error_text_address": "Wallet လိပ်စာသည် အမျိုးအစား\no cryptocurrency နှင့် ကိုက်ညီရပါမည်။",
"error_text_node_address": "ကျေးဇူးပြု၍ iPv4 လိပ်စာကို ထည့်ပါ။", "error_text_node_address": "ကျေးဇူးပြု၍ iPv4 လိပ်စာကို ထည့်ပါ။",
"error_text_node_port": "နော်ဒီဆိပ်ကမ်း တွင် 0 နှင့် 65535 အကြား နံပါတ်များသာ ပါဝင်နိုင်သည်။", "error_text_node_port": "နော်ဒီဆိပ်ကမ်း တွင် 0 နှင့် 65535 အကြား နံပါတ်များသာ ပါဝင်နိုင်သည်။",
"error_text_node_proxy_address": "ကျေးဇူးပြု၍ <IPv4 လိပ်စာ>:<port>၊ ဥပမာ 127.0.0.1:9050 ထည့်ပါ",
"error_text_payment_id": "ငွေပေးချေမှု ID တွင် hex တွင် စာလုံး 16 လုံးမှ 64 လုံးသာ ပါဝင်နိုင်သည်။", "error_text_payment_id": "ငွေပေးချေမှု ID တွင် hex တွင် စာလုံး 16 လုံးမှ 64 လုံးသာ ပါဝင်နိုင်သည်။",
"error_text_xmr": "XMR တန်ဖိုးသည် ရနိုင်သောလက်ကျန်ကို ကျော်လွန်၍မရပါ။\nအပိုင်းကိန်းဂဏန်းများ သည် 12 နှင့် လျော့နည်းရမည်", "error_text_xmr": "XMR တန်ဖိုးသည် ရနိုင်သောလက်ကျန်ကို ကျော်လွန်၍မရပါ။\nအပိုင်းကိန်းဂဏန်းများ သည် 12 နှင့် လျော့နည်းရမည်",
"error_text_fiat": "ပမာဏ၏တန်ဖိုးသည် ရနိုင်သောလက်ကျန်ကို မကျော်လွန်နိုင်ပါ။\nအပိုင်းကိန်းဂဏန်းအရေအတွက်သည် 2 နှင့် လျော့နည်းရမည်", "error_text_fiat": "ပမာဏ၏တန်ဖိုးသည် ရနိုင်သောလက်ကျန်ကို မကျော်လွန်နိုင်ပါ။\nအပိုင်းကိန်းဂဏန်းအရေအတွက်သည် 2 နှင့် လျော့နည်းရမည်",
@ -654,5 +655,6 @@
"generate_name": "အမည်ဖန်တီးပါ။", "generate_name": "အမည်ဖန်တီးပါ။",
"balance_page": "လက်ကျန်စာမျက်နှာ", "balance_page": "လက်ကျန်စာမျက်နှာ",
"share": "မျှဝေပါ။", "share": "မျှဝေပါ။",
"slidable": "လျှောချနိုင်သည်။" "slidable": "လျှောချနိုင်သည်။",
"template_name": "နမူနာပုံစံ"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Portemonnee-adres moet overeenkomen met het type\nvan cryptocurrency", "error_text_address": "Portemonnee-adres moet overeenkomen met het type\nvan cryptocurrency",
"error_text_node_address": "Voer een iPv4-adres in", "error_text_node_address": "Voer een iPv4-adres in",
"error_text_node_port": "Knooppuntpoort kan alleen nummers tussen 0 en 65535 bevatten", "error_text_node_port": "Knooppuntpoort kan alleen nummers tussen 0 en 65535 bevatten",
"error_text_node_proxy_address": "Voer <IPv4-adres>:<poort> in, bijvoorbeeld 127.0.0.1:9050",
"error_text_payment_id": "Betalings-ID kan alleen 16 tot 64 tekens bevatten in hexadecimale volgorde", "error_text_payment_id": "Betalings-ID kan alleen 16 tot 64 tekens bevatten in hexadecimale volgorde",
"error_text_xmr": "XMR-waarde kan het beschikbare saldo niet overschrijden.\nHet aantal breukcijfers moet kleiner zijn dan of gelijk zijn aan 12", "error_text_xmr": "XMR-waarde kan het beschikbare saldo niet overschrijden.\nHet aantal breukcijfers moet kleiner zijn dan of gelijk zijn aan 12",
"error_text_fiat": "Waarde van bedrag kan het beschikbare saldo niet overschrijden.\nHet aantal breukcijfers moet kleiner zijn dan of gelijk zijn aan 2", "error_text_fiat": "Waarde van bedrag kan het beschikbare saldo niet overschrijden.\nHet aantal breukcijfers moet kleiner zijn dan of gelijk zijn aan 2",
@ -654,5 +655,6 @@
"generate_name": "Naam genereren", "generate_name": "Naam genereren",
"balance_page": "Saldo pagina", "balance_page": "Saldo pagina",
"share": "Deel", "share": "Deel",
"slidable": "Verschuifbaar" "slidable": "Verschuifbaar",
"template_name": "Sjabloonnaam"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Adres musi odpowiadać typowi kryptowaluty", "error_text_address": "Adres musi odpowiadać typowi kryptowaluty",
"error_text_node_address": "Wpisz adres iPv4", "error_text_node_address": "Wpisz adres iPv4",
"error_text_node_port": "Port węzła może zawierać tylko liczby od 0 do 65535", "error_text_node_port": "Port węzła może zawierać tylko liczby od 0 do 65535",
"error_text_node_proxy_address": "Wprowadź <adres IPv4>:<port>, na przykład 127.0.0.1:9050",
"error_text_payment_id": "ID może zawierać od 16 do 64 znaków w formacie szesnastkowym", "error_text_payment_id": "ID może zawierać od 16 do 64 znaków w formacie szesnastkowym",
"error_text_xmr": "Wartość XMR nie może przekraczać dostępnego salda.\nLiczba cyfr ułamkowych musi być mniejsza lub równa 12", "error_text_xmr": "Wartość XMR nie może przekraczać dostępnego salda.\nLiczba cyfr ułamkowych musi być mniejsza lub równa 12",
"error_text_fiat": "Wartość kwoty nie może przekroczyć dostępnego salda.\nLiczba cyfr ułamkowych musi być mniejsza lub równa 2", "error_text_fiat": "Wartość kwoty nie może przekroczyć dostępnego salda.\nLiczba cyfr ułamkowych musi być mniejsza lub równa 2",
@ -654,5 +655,6 @@
"generate_name": "Wygeneruj nazwę", "generate_name": "Wygeneruj nazwę",
"balance_page": "Strona salda", "balance_page": "Strona salda",
"share": "Udział", "share": "Udział",
"slidable": "Przesuwne" "slidable": "Przesuwne",
"template_name": "Nazwa szablonu"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "O endereço da carteira deve corresponder à\ncriptomoeda selecionada", "error_text_address": "O endereço da carteira deve corresponder à\ncriptomoeda selecionada",
"error_text_node_address": "Digite um endereço iPv4", "error_text_node_address": "Digite um endereço iPv4",
"error_text_node_port": "A porta do nó deve conter apenas números entre 0 e 65535", "error_text_node_port": "A porta do nó deve conter apenas números entre 0 e 65535",
"error_text_node_proxy_address": "Insira <endereço IPv4>:<porta>, por exemplo 127.0.0.1:9050",
"error_text_payment_id": "O ID de pagamento pode conter apenas de 16 a 64 caracteres em hexadecimal", "error_text_payment_id": "O ID de pagamento pode conter apenas de 16 a 64 caracteres em hexadecimal",
"error_text_xmr": "A quantia em XMR não pode exceder o saldo disponível.\nTO número de dígitos decimais deve ser menor ou igual a 12", "error_text_xmr": "A quantia em XMR não pode exceder o saldo disponível.\nTO número de dígitos decimais deve ser menor ou igual a 12",
"error_text_fiat": "O valor do valor não pode exceder o saldo disponível.\nO número de dígitos decimais deve ser menor ou igual a 2", "error_text_fiat": "O valor do valor não pode exceder o saldo disponível.\nO número de dígitos decimais deve ser menor ou igual a 2",
@ -653,5 +654,6 @@
"generate_name": "Gerar nome", "generate_name": "Gerar nome",
"balance_page": "Página de saldo", "balance_page": "Página de saldo",
"share": "Compartilhar", "share": "Compartilhar",
"slidable": "Deslizável" "slidable": "Deslizável",
"template_name": "Nome do modelo"
} }

View file

@ -269,6 +269,8 @@
"error_text_address": "Адрес кошелька должен соответствовать типу\nкриптовалюты", "error_text_address": "Адрес кошелька должен соответствовать типу\nкриптовалюты",
"error_text_node_address": "Пожалуйста, введите iPv4 адрес", "error_text_node_address": "Пожалуйста, введите iPv4 адрес",
"error_text_node_port": "Порт ноды может содержать только цифры от 0 до 65535", "error_text_node_port": "Порт ноды может содержать только цифры от 0 до 65535",
"error_text_node_proxy_address": "Введите <IPv4-адрес>:<порт>, например 127.0.0.1:9050.",
"error_text_payment_id": "Идентификатор платежа может содержать от 16 до 64 символов в hex", "error_text_payment_id": "Идентификатор платежа может содержать от 16 до 64 символов в hex",
"error_text_xmr": "Значение XMR не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 12", "error_text_xmr": "Значение XMR не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 12",
"error_text_fiat": "Значение суммы не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 2", "error_text_fiat": "Значение суммы не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 2",
@ -654,5 +656,6 @@
"generate_name": "Создать имя", "generate_name": "Создать имя",
"balance_page": "Страница баланса", "balance_page": "Страница баланса",
"share": "Делиться", "share": "Делиться",
"slidable": "Скользящий" "slidable": "Скользящий",
"template_name": "Имя Шаблона"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "ที่อยู่กระเป๋าจะต้องสอดคล้องกับประเภท\nของเหรียญคริปโตเนียม", "error_text_address": "ที่อยู่กระเป๋าจะต้องสอดคล้องกับประเภท\nของเหรียญคริปโตเนียม",
"error_text_node_address": "โปรดป้อนที่อยู่ iPv4", "error_text_node_address": "โปรดป้อนที่อยู่ iPv4",
"error_text_node_port": "พอร์ตโหนดสามารถมีตัวเลขเท่านั้นระหว่าง 0 ถึง 65535", "error_text_node_port": "พอร์ตโหนดสามารถมีตัวเลขเท่านั้นระหว่าง 0 ถึง 65535",
"error_text_node_proxy_address": "โปรดป้อน <ที่อยู่ IPv4>:<พอร์ต> เช่น 127.0.0.1:9050",
"error_text_payment_id": "Payment ID สามารถมีขนาดระหว่าง 16 ถึง 64 ตัวอักษรตามแบบ hex", "error_text_payment_id": "Payment ID สามารถมีขนาดระหว่าง 16 ถึง 64 ตัวอักษรตามแบบ hex",
"error_text_xmr": "มูลค่า XMR ไม่สามารถเกินยอดคงเหลือได้\nจำนวนสตริงทศนิยมต้องน้อยกว่าหรือเท่ากับ 12", "error_text_xmr": "มูลค่า XMR ไม่สามารถเกินยอดคงเหลือได้\nจำนวนสตริงทศนิยมต้องน้อยกว่าหรือเท่ากับ 12",
"error_text_fiat": "มูลค่าของจำนวนเงินไม่สามารถเกินยอดคงเหลือได้\nจำนวนสตริงทศนิยมต้องน้อยกว่าหรือเท่ากับ 2", "error_text_fiat": "มูลค่าของจำนวนเงินไม่สามารถเกินยอดคงเหลือได้\nจำนวนสตริงทศนิยมต้องน้อยกว่าหรือเท่ากับ 2",
@ -654,5 +655,6 @@
"generate_name": "สร้างชื่อ", "generate_name": "สร้างชื่อ",
"balance_page": "หน้ายอดคงเหลือ", "balance_page": "หน้ายอดคงเหลือ",
"share": "แบ่งปัน", "share": "แบ่งปัน",
"slidable": "เลื่อนได้" "slidable": "เลื่อนได้",
"template_name": "ชื่อแม่แบบ"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Cüzdan adresi kripto para biriminin\ntürüne karşılık gelmelidir", "error_text_address": "Cüzdan adresi kripto para biriminin\ntürüne karşılık gelmelidir",
"error_text_node_address": "Lütfen iPv4 adresi girin", "error_text_node_address": "Lütfen iPv4 adresi girin",
"error_text_node_port": "Düğüm port'u yalnızca 0 ve 65535 arasında rakam içerebilir", "error_text_node_port": "Düğüm port'u yalnızca 0 ve 65535 arasında rakam içerebilir",
"error_text_node_proxy_address": "Lütfen <IPv4 adresi>:<bağlantı noktası> girin, örneğin 127.0.0.1:9050",
"error_text_payment_id": "Ödeme ID'si yalnızca onaltılık (hex) olarak 16 veya 64 karakter içerebilir", "error_text_payment_id": "Ödeme ID'si yalnızca onaltılık (hex) olarak 16 veya 64 karakter içerebilir",
"error_text_xmr": "XMR miktarı kullanılabilir bakiyeyi aşamaz.\nKesir basamaklarının sayısı 12'ye eşit veya daha az olmalıdır", "error_text_xmr": "XMR miktarı kullanılabilir bakiyeyi aşamaz.\nKesir basamaklarının sayısı 12'ye eşit veya daha az olmalıdır",
"error_text_fiat": "Tutarın değeri, mevcut bakiyeyi aşamaz.\nKesir basamaklarının sayısı 2'ye eşit veya daha az olmalıdır", "error_text_fiat": "Tutarın değeri, mevcut bakiyeyi aşamaz.\nKesir basamaklarının sayısı 2'ye eşit veya daha az olmalıdır",
@ -654,5 +655,6 @@
"generate_name": "İsim Oluştur", "generate_name": "İsim Oluştur",
"balance_page": "Bakiye Sayfası", "balance_page": "Bakiye Sayfası",
"share": "Paylaşmak", "share": "Paylaşmak",
"slidable": "kaydırılabilir" "slidable": "kaydırılabilir",
"template_name": "şablon adı"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "Адреса гаманця повинна відповідати типу\nкриптовалюти", "error_text_address": "Адреса гаманця повинна відповідати типу\nкриптовалюти",
"error_text_node_address": "Будь ласка, введіть iPv4 адресу", "error_text_node_address": "Будь ласка, введіть iPv4 адресу",
"error_text_node_port": "Порт вузла може містити тільки цифри від 0 до 65535", "error_text_node_port": "Порт вузла може містити тільки цифри від 0 до 65535",
"error_text_node_proxy_address": "Будь ласка, введіть <IPv4-адреса>:<порт>, наприклад 127.0.0.1:9050",
"error_text_payment_id": "Ідентифікатор платежу може містити від 16 до 64 символів в hex", "error_text_payment_id": "Ідентифікатор платежу може містити від 16 до 64 символів в hex",
"error_text_xmr": "Значення XMR не може перевищувати доступний баланс.\nКількість цифр після коми повинно бути меншим або дорівнювати 12", "error_text_xmr": "Значення XMR не може перевищувати доступний баланс.\nКількість цифр після коми повинно бути меншим або дорівнювати 12",
"error_text_fiat": "Значення суми не може перевищувати доступний баланс.\nКількість цифр після коми повинно бути меншим або дорівнювати 2", "error_text_fiat": "Значення суми не може перевищувати доступний баланс.\nКількість цифр після коми повинно бути меншим або дорівнювати 2",
@ -654,5 +655,6 @@
"generate_name": "Згенерувати назву", "generate_name": "Згенерувати назву",
"balance_page": "Сторінка балансу", "balance_page": "Сторінка балансу",
"share": "Поділіться", "share": "Поділіться",
"slidable": "Розсувний" "slidable": "Розсувний",
"template_name": "Назва шаблону"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "والیٹ کا پتہ cryptocurrency کی قسم\\nکے مطابق ہونا چاہیے۔", "error_text_address": "والیٹ کا پتہ cryptocurrency کی قسم\\nکے مطابق ہونا چاہیے۔",
"error_text_node_address": "براہ کرم ایک iPv4 پتہ درج کریں۔", "error_text_node_address": "براہ کرم ایک iPv4 پتہ درج کریں۔",
"error_text_node_port": "نوڈ پورٹ میں صرف 0 اور 65535 کے درمیان نمبر ہوسکتے ہیں۔", "error_text_node_port": "نوڈ پورٹ میں صرف 0 اور 65535 کے درمیان نمبر ہوسکتے ہیں۔",
"error_text_node_proxy_address": "براہ کرم <IPv4 ایڈریس>:<port> درج کریں، مثال کے طور پر 127.0.0.1:9050",
"error_text_payment_id": "ادائیگی کی ID ہیکس میں صرف 16 سے 64 حروف پر مشتمل ہو سکتی ہے۔", "error_text_payment_id": "ادائیگی کی ID ہیکس میں صرف 16 سے 64 حروف پر مشتمل ہو سکتی ہے۔",
"error_text_xmr": "XMR قدر دستیاب بیلنس سے زیادہ نہیں ہو سکتی۔\\nفرکشن ہندسوں کی تعداد 12 سے کم یا اس کے برابر ہونی چاہیے۔", "error_text_xmr": "XMR قدر دستیاب بیلنس سے زیادہ نہیں ہو سکتی۔\\nفرکشن ہندسوں کی تعداد 12 سے کم یا اس کے برابر ہونی چاہیے۔",
"error_text_fiat": "رقم کی قدر دستیاب بیلنس سے زیادہ نہیں ہو سکتی۔\\nفرکشن ہندسوں کی تعداد 2 کے برابر یا کم ہونی چاہیے۔", "error_text_fiat": "رقم کی قدر دستیاب بیلنس سے زیادہ نہیں ہو سکتی۔\\nفرکشن ہندسوں کی تعداد 2 کے برابر یا کم ہونی چاہیے۔",
@ -648,5 +649,6 @@
"generate_name": "نام پیدا کریں۔", "generate_name": "نام پیدا کریں۔",
"balance_page": "بیلنس صفحہ", "balance_page": "بیلنس صفحہ",
"share": "بانٹیں", "share": "بانٹیں",
"slidable": "سلائیڈ ایبل" "slidable": "سلائیڈ ایبل",
"template_name": "ٹیمپلیٹ کا نام"
} }

View file

@ -250,8 +250,8 @@
"transaction_details_recipient_address": "Àwọn àdírẹ́sì olùgbà", "transaction_details_recipient_address": "Àwọn àdírẹ́sì olùgbà",
"wallet_list_title": "Àpamọ́wọ́ Monero", "wallet_list_title": "Àpamọ́wọ́ Monero",
"wallet_list_create_new_wallet": "Ṣe àpamọ́wọ́ títun", "wallet_list_create_new_wallet": "Ṣe àpamọ́wọ́ títun",
"wallet_list_edit_wallet": "Ṣatunkọ apamọwọ", "wallet_list_edit_wallet" : "Ṣatunkọ apamọwọ",
"wallet_list_wallet_name": "Orukọ apamọwọ", "wallet_list_wallet_name" : "Orukọ apamọwọ",
"wallet_list_restore_wallet": "Restore àpamọ́wọ́", "wallet_list_restore_wallet": "Restore àpamọ́wọ́",
"wallet_list_load_wallet": "Load àpamọ́wọ́", "wallet_list_load_wallet": "Load àpamọ́wọ́",
"wallet_list_loading_wallet": "Ń ṣí àpamọ́wọ́ ${wallet_name}", "wallet_list_loading_wallet": "Ń ṣí àpamọ́wọ́ ${wallet_name}",
@ -269,6 +269,7 @@
"error_text_address": "Àdírẹ́sì àpamọ́wọ́ gbọ́dọ̀ báramu irú owó", "error_text_address": "Àdírẹ́sì àpamọ́wọ́ gbọ́dọ̀ báramu irú owó",
"error_text_node_address": "Ẹ jọ̀wọ́ tẹ̀ àdírẹ́sì iPv4", "error_text_node_address": "Ẹ jọ̀wọ́ tẹ̀ àdírẹ́sì iPv4",
"error_text_node_port": "Ojú ìkànpọ̀ apẹka lè ni nìkan nínú òǹkà l'áàárín òdo àti márùn-úndínlógojí lé ní ẹ̀ẹ́dẹgbẹ̀ta lé ní ọ̀kẹ́ mẹ́ta lé ní ẹ̀ẹ́dẹ́gbàta", "error_text_node_port": "Ojú ìkànpọ̀ apẹka lè ni nìkan nínú òǹkà l'áàárín òdo àti márùn-úndínlógojí lé ní ẹ̀ẹ́dẹgbẹ̀ta lé ní ọ̀kẹ́ mẹ́ta lé ní ẹ̀ẹ́dẹ́gbàta",
"error_text_node_proxy_address": "Jọwọ tẹ <IPv4 adirẹsi>:<port>, fun apẹẹrẹ 127.0.0.1:9050",
"error_text_payment_id": "Iye ẹyọ ọ̀rọ̀ nínú àmì ìdánimọ̀ àránṣẹ́ gbọ́dọ̀ wà l'áàárín aárùndínlógún dé ẹẹ́rinlélọ́gọ́ta.", "error_text_payment_id": "Iye ẹyọ ọ̀rọ̀ nínú àmì ìdánimọ̀ àránṣẹ́ gbọ́dọ̀ wà l'áàárín aárùndínlógún dé ẹẹ́rinlélọ́gọ́ta.",
"error_text_xmr": "Iye XMR kò lè tóbi ju ìyókù.\nIye díjíìtì léyìn ẹsẹ kò gbọ́dọ̀ tóbi ju eéjìlá.", "error_text_xmr": "Iye XMR kò lè tóbi ju ìyókù.\nIye díjíìtì léyìn ẹsẹ kò gbọ́dọ̀ tóbi ju eéjìlá.",
"error_text_fiat": "Iye àránṣẹ́ kò tóbi ju ìyókù owó.\nIye díjíìtì léyìn ẹsẹ kò gbọ́dọ̀ tóbi ju eéjì.", "error_text_fiat": "Iye àránṣẹ́ kò tóbi ju ìyókù owó.\nIye díjíìtì léyìn ẹsẹ kò gbọ́dọ̀ tóbi ju eéjì.",
@ -650,5 +651,6 @@
"generate_name": "Ṣẹda Orukọ", "generate_name": "Ṣẹda Orukọ",
"balance_page": "Oju-iwe iwọntunwọnsi", "balance_page": "Oju-iwe iwọntunwọnsi",
"share": "Pinpin", "share": "Pinpin",
"slidable": "Slidable" "slidable": "Slidable",
"template_name": "Orukọ Awoṣe"
} }

View file

@ -269,6 +269,7 @@
"error_text_address": "钱包地址必须与类型对应\n加密货币", "error_text_address": "钱包地址必须与类型对应\n加密货币",
"error_text_node_address": "请输入一个IPv4地址", "error_text_node_address": "请输入一个IPv4地址",
"error_text_node_port": "节点端口只能包含0到65535之间的数字", "error_text_node_port": "节点端口只能包含0到65535之间的数字",
"error_text_node_proxy_address": "请输入<IPv4地址>:<端口>例如127.0.0.1:9050",
"error_text_payment_id": "付款ID只能包含16到64个字符十六进制", "error_text_payment_id": "付款ID只能包含16到64个字符十六进制",
"error_text_xmr": "XMR值不能超过可用余额.\n小数位数必须小于或等于12", "error_text_xmr": "XMR值不能超过可用余额.\n小数位数必须小于或等于12",
"error_text_fiat": "金额不能超过可用余额.\n小数位数必须小于或等于2", "error_text_fiat": "金额不能超过可用余额.\n小数位数必须小于或等于2",
@ -653,5 +654,6 @@
"generate_name": "生成名称", "generate_name": "生成名称",
"balance_page": "余额页", "balance_page": "余额页",
"share": "分享", "share": "分享",
"slidable": "可滑动" "slidable": "可滑动",
"template_name": "模板名称"
} }