mirror of
https://github.com/cake-tech/cake_wallet.git
synced 2025-01-22 18:54:47 +00:00
Merge branch 'main' of https://github.com/cake-tech/cake_wallet into CW-438-add-nano
This commit is contained in:
commit
c8d9fcbaf4
64 changed files with 1069 additions and 602 deletions
|
@ -45,8 +45,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId appProperties['id']
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 33
|
||||
versionCode flutterVersionCode.toInteger()
|
||||
versionName flutterVersionName
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
|
|
|
@ -12,14 +12,14 @@ Uri createUriFromElectrumAddress(String address) => Uri.tryParse('tcp://$address
|
|||
|
||||
@HiveType(typeId: Node.typeId)
|
||||
class Node extends HiveObject with Keyable {
|
||||
Node({
|
||||
this.login,
|
||||
this.password,
|
||||
this.useSSL,
|
||||
this.trusted = false,
|
||||
String? uri,
|
||||
WalletType? type,
|
||||
}) {
|
||||
Node(
|
||||
{this.login,
|
||||
this.password,
|
||||
this.useSSL,
|
||||
this.trusted = false,
|
||||
this.socksProxyAddress,
|
||||
String? uri,
|
||||
WalletType? type,}) {
|
||||
if (uri != null) {
|
||||
uriRaw = uri;
|
||||
}
|
||||
|
@ -33,7 +33,8 @@ class Node extends HiveObject with Keyable {
|
|||
login = map['login'] as String?,
|
||||
password = map['password'] as String?,
|
||||
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 boxName = 'Nodes';
|
||||
|
@ -56,8 +57,13 @@ class Node extends HiveObject with Keyable {
|
|||
@HiveField(5, defaultValue: false)
|
||||
bool trusted;
|
||||
|
||||
@HiveField(6)
|
||||
String? socksProxyAddress;
|
||||
|
||||
bool get isSSL => useSSL ?? false;
|
||||
|
||||
bool get useSocksProxy => socksProxyAddress == null ? false : socksProxyAddress!.isNotEmpty;
|
||||
|
||||
Uri get uri {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
|
@ -85,12 +91,13 @@ class Node extends HiveObject with Keyable {
|
|||
@override
|
||||
bool operator ==(other) =>
|
||||
other is Node &&
|
||||
(other.uriRaw == uriRaw &&
|
||||
other.login == login &&
|
||||
other.password == password &&
|
||||
other.typeRaw == typeRaw &&
|
||||
other.useSSL == useSSL &&
|
||||
other.trusted == trusted);
|
||||
(other.uriRaw == uriRaw &&
|
||||
other.login == login &&
|
||||
other.password == password &&
|
||||
other.typeRaw == typeRaw &&
|
||||
other.useSSL == useSSL &&
|
||||
other.trusted == trusted &&
|
||||
other.socksProxyAddress == socksProxyAddress);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
|
@ -99,7 +106,8 @@ class Node extends HiveObject with Keyable {
|
|||
password.hashCode ^
|
||||
typeRaw.hashCode ^
|
||||
useSSL.hashCode ^
|
||||
trusted.hashCode;
|
||||
trusted.hashCode ^
|
||||
socksProxyAddress.hashCode;
|
||||
|
||||
@override
|
||||
dynamic get keyIndex {
|
||||
|
@ -117,7 +125,7 @@ class Node extends HiveObject with Keyable {
|
|||
try {
|
||||
switch (type) {
|
||||
case WalletType.monero:
|
||||
return requestMoneroNode();
|
||||
return useSocksProxy ? requestNodeWithProxy(socksProxyAddress ?? '') : requestMoneroNode();
|
||||
case WalletType.bitcoin:
|
||||
return requestElectrumServer();
|
||||
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 {
|
||||
try {
|
||||
await SecureSocket.connect(uri.host, uri.port,
|
||||
|
|
|
@ -39,7 +39,7 @@ typedef get_node_height = Int64 Function();
|
|||
typedef is_connected = 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();
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ typedef GetNodeHeight = int Function();
|
|||
typedef IsConnected = 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();
|
||||
|
||||
|
|
|
@ -154,9 +154,11 @@ bool setupNodeSync(
|
|||
String? login,
|
||||
String? password,
|
||||
bool useSSL = false,
|
||||
bool isLightWallet = false}) {
|
||||
bool isLightWallet = false,
|
||||
String? socksProxyAddress}) {
|
||||
final addressPointer = address.toNativeUtf8();
|
||||
Pointer<Utf8>? loginPointer;
|
||||
Pointer<Utf8>? socksProxyAddressPointer;
|
||||
Pointer<Utf8>? passwordPointer;
|
||||
|
||||
if (login != null) {
|
||||
|
@ -167,6 +169,10 @@ bool setupNodeSync(
|
|||
passwordPointer = password.toNativeUtf8();
|
||||
}
|
||||
|
||||
if (socksProxyAddress != null) {
|
||||
socksProxyAddressPointer = socksProxyAddress.toNativeUtf8();
|
||||
}
|
||||
|
||||
final errorMessagePointer = ''.toNativeUtf8();
|
||||
final isSetupNode = setupNodeNative(
|
||||
addressPointer,
|
||||
|
@ -174,6 +180,7 @@ bool setupNodeSync(
|
|||
passwordPointer,
|
||||
_boolToInt(useSSL),
|
||||
_boolToInt(isLightWallet),
|
||||
socksProxyAddressPointer,
|
||||
errorMessagePointer) !=
|
||||
0;
|
||||
|
||||
|
@ -323,13 +330,15 @@ bool _setupNodeSync(Map args) {
|
|||
final password = (args['password'] ?? '') as String;
|
||||
final useSSL = args['useSSL'] as bool;
|
||||
final isLightWallet = args['isLightWallet'] as bool;
|
||||
final socksProxyAddress = (args['socksProxyAddress'] ?? '') as String;
|
||||
|
||||
return setupNodeSync(
|
||||
address: address,
|
||||
login: login,
|
||||
password: password,
|
||||
useSSL: useSSL,
|
||||
isLightWallet: isLightWallet);
|
||||
isLightWallet: isLightWallet,
|
||||
socksProxyAddress: socksProxyAddress);
|
||||
}
|
||||
|
||||
bool _isConnected(Object _) => isConnectedSync();
|
||||
|
@ -343,13 +352,15 @@ Future<void> setupNode(
|
|||
String? login,
|
||||
String? password,
|
||||
bool useSSL = false,
|
||||
String? socksProxyAddress,
|
||||
bool isLightWallet = false}) =>
|
||||
compute<Map<String, Object?>, void>(_setupNodeSync, {
|
||||
'address': address,
|
||||
'login': login,
|
||||
'password': password,
|
||||
'useSSL': useSSL,
|
||||
'isLightWallet': isLightWallet
|
||||
'isLightWallet': isLightWallet,
|
||||
'socksProxyAddress': socksProxyAddress
|
||||
});
|
||||
|
||||
Future<void> store() => compute<int, void>(_storeSync, 0);
|
||||
|
|
|
@ -125,7 +125,8 @@ abstract class HavenWalletBase extends WalletBase<MoneroBalance,
|
|||
login: node.login,
|
||||
password: node.password,
|
||||
useSSL: node.useSSL ?? false,
|
||||
isLightWallet: false); // FIXME: hardcoded value
|
||||
isLightWallet: false, // FIXME: hardcoded value
|
||||
socksProxyAddress: node.socksProxyAddress);
|
||||
|
||||
haven_wallet.setTrustedDaemon(node.trusted);
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
#include <chrono>
|
||||
#include <functional>
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <unistd.h>
|
||||
#include <mutex>
|
||||
#include "thread"
|
||||
|
@ -405,13 +406,14 @@ extern "C"
|
|||
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);
|
||||
Monero::Wallet *wallet = get_current_wallet();
|
||||
|
||||
std::string _login = "";
|
||||
std::string _password = "";
|
||||
std::string _socksProxyAddress = "";
|
||||
|
||||
if (login != nullptr)
|
||||
{
|
||||
|
@ -423,7 +425,12 @@ extern "C"
|
|||
_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)
|
||||
{
|
||||
|
|
|
@ -35,7 +35,7 @@ typedef get_node_height = Int64 Function();
|
|||
typedef is_connected = 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();
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ typedef GetNodeHeight = int Function();
|
|||
typedef IsConnected = 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();
|
||||
|
||||
|
|
|
@ -158,9 +158,11 @@ bool setupNodeSync(
|
|||
String? login,
|
||||
String? password,
|
||||
bool useSSL = false,
|
||||
bool isLightWallet = false}) {
|
||||
bool isLightWallet = false,
|
||||
String? socksProxyAddress}) {
|
||||
final addressPointer = address.toNativeUtf8();
|
||||
Pointer<Utf8>? loginPointer;
|
||||
Pointer<Utf8>? socksProxyAddressPointer;
|
||||
Pointer<Utf8>? passwordPointer;
|
||||
|
||||
if (login != null) {
|
||||
|
@ -171,6 +173,10 @@ bool setupNodeSync(
|
|||
passwordPointer = password.toNativeUtf8();
|
||||
}
|
||||
|
||||
if (socksProxyAddress != null) {
|
||||
socksProxyAddressPointer = socksProxyAddress.toNativeUtf8();
|
||||
}
|
||||
|
||||
final errorMessagePointer = ''.toNativeUtf8();
|
||||
final isSetupNode = setupNodeNative(
|
||||
addressPointer,
|
||||
|
@ -178,6 +184,7 @@ bool setupNodeSync(
|
|||
passwordPointer,
|
||||
_boolToInt(useSSL),
|
||||
_boolToInt(isLightWallet),
|
||||
socksProxyAddressPointer,
|
||||
errorMessagePointer) !=
|
||||
0;
|
||||
|
||||
|
@ -328,13 +335,15 @@ bool _setupNodeSync(Map<String, Object?> args) {
|
|||
final password = (args['password'] ?? '') as String;
|
||||
final useSSL = args['useSSL'] as bool;
|
||||
final isLightWallet = args['isLightWallet'] as bool;
|
||||
final socksProxyAddress = (args['socksProxyAddress'] ?? '') as String;
|
||||
|
||||
return setupNodeSync(
|
||||
address: address,
|
||||
login: login,
|
||||
password: password,
|
||||
useSSL: useSSL,
|
||||
isLightWallet: isLightWallet);
|
||||
isLightWallet: isLightWallet,
|
||||
socksProxyAddress: socksProxyAddress);
|
||||
}
|
||||
|
||||
bool _isConnected(Object _) => isConnectedSync();
|
||||
|
@ -348,13 +357,15 @@ Future<void> setupNode(
|
|||
String? login,
|
||||
String? password,
|
||||
bool useSSL = false,
|
||||
String? socksProxyAddress,
|
||||
bool isLightWallet = false}) =>
|
||||
compute<Map<String, Object?>, void>(_setupNodeSync, {
|
||||
'address': address,
|
||||
'login': login ,
|
||||
'password': password,
|
||||
'useSSL': useSSL,
|
||||
'isLightWallet': isLightWallet
|
||||
'isLightWallet': isLightWallet,
|
||||
'socksProxyAddress': socksProxyAddress
|
||||
});
|
||||
|
||||
Future<void> store() => compute<int, void>(_storeSync, 0);
|
||||
|
|
|
@ -138,7 +138,8 @@ abstract class MoneroWalletBase extends WalletBase<MoneroBalance,
|
|||
login: node.login,
|
||||
password: node.password,
|
||||
useSSL: node.isSSL,
|
||||
isLightWallet: false); // FIXME: hardcoded value
|
||||
isLightWallet: false, // FIXME: hardcoded value
|
||||
socksProxyAddress: node.socksProxyAddress);
|
||||
|
||||
monero_wallet.setTrustedDaemon(node.trusted);
|
||||
syncStatus = ConnectedSyncStatus();
|
||||
|
|
|
@ -20,6 +20,8 @@ class OnRamperBuyProvider {
|
|||
switch (_wallet.currency) {
|
||||
case CryptoCurrency.ltc:
|
||||
return "LTC_LITECOIN";
|
||||
case CryptoCurrency.xmr:
|
||||
return "XMR_MONERO";
|
||||
default:
|
||||
return _wallet.currency.title;
|
||||
}
|
||||
|
@ -60,11 +62,12 @@ class OnRamperBuyProvider {
|
|||
break;
|
||||
}
|
||||
|
||||
final networkName = _wallet.currency.fullName?.toUpperCase().replaceAll(" ", "");
|
||||
|
||||
return Uri.https(_baseUrl, '', <String, dynamic>{
|
||||
'apiKey': _apiKey,
|
||||
'defaultCrypto': _normalizeCryptoCurrency,
|
||||
'wallets': '${_wallet.currency.title}:${_wallet.walletAddresses.address}',
|
||||
'networkWallets': '${networkName}:${_wallet.walletAddresses.address}',
|
||||
'supportSell': "false",
|
||||
'supportSwap': "false",
|
||||
'primaryColor': primaryColor,
|
||||
|
|
10
lib/core/socks_proxy_node_address_validator.dart
Normal file
10
lib/core/socks_proxy_node_address_validator.dart
Normal 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]+\$');
|
||||
}
|
|
@ -37,7 +37,7 @@ class OpenaliasRecord {
|
|||
required String ticker,
|
||||
required List<RRecord> txtRecord,
|
||||
}) {
|
||||
String address = formattedName;
|
||||
String address = '';
|
||||
String name = formattedName;
|
||||
String note = '';
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import 'package:cake_wallet/entities/openalias_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 {
|
||||
ParsedAddress({
|
||||
|
@ -40,13 +40,17 @@ class ParsedAddress {
|
|||
);
|
||||
}
|
||||
|
||||
factory ParsedAddress.fetchOpenAliasAddress({required OpenaliasRecord record, required String name}){
|
||||
return ParsedAddress(
|
||||
addresses: [record.address],
|
||||
name: record.name,
|
||||
description: record.description,
|
||||
parseFrom: ParseFrom.openAlias,
|
||||
);
|
||||
factory ParsedAddress.fetchOpenAliasAddress(
|
||||
{required OpenaliasRecord record, required String name}) {
|
||||
if (record.address.isEmpty) {
|
||||
return ParsedAddress(addresses: [name]);
|
||||
}
|
||||
return ParsedAddress(
|
||||
addresses: [record.address],
|
||||
name: record.name,
|
||||
description: record.description,
|
||||
parseFrom: ParseFrom.openAlias,
|
||||
);
|
||||
}
|
||||
|
||||
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 String name;
|
||||
final String description;
|
||||
|
|
|
@ -4,14 +4,15 @@ part 'template.g.dart';
|
|||
|
||||
@HiveType(typeId: Template.typeId)
|
||||
class Template extends HiveObject {
|
||||
Template({
|
||||
required this.nameRaw,
|
||||
required this.isCurrencySelectedRaw,
|
||||
required this.addressRaw,
|
||||
required this.cryptoCurrencyRaw,
|
||||
required this.amountRaw,
|
||||
required this.fiatCurrencyRaw,
|
||||
required this.amountFiatRaw});
|
||||
Template(
|
||||
{required this.nameRaw,
|
||||
required this.isCurrencySelectedRaw,
|
||||
required this.addressRaw,
|
||||
required this.cryptoCurrencyRaw,
|
||||
required this.amountRaw,
|
||||
required this.fiatCurrencyRaw,
|
||||
required this.amountFiatRaw,
|
||||
this.additionalRecipientsRaw});
|
||||
|
||||
static const typeId = 6;
|
||||
static const boxName = 'Template';
|
||||
|
@ -37,6 +38,9 @@ class Template extends HiveObject {
|
|||
@HiveField(6)
|
||||
String? amountFiatRaw;
|
||||
|
||||
@HiveField(7)
|
||||
List<Template>? additionalRecipientsRaw;
|
||||
|
||||
bool get isCurrencySelected => isCurrencySelectedRaw ?? false;
|
||||
|
||||
String get fiatCurrency => fiatCurrencyRaw ?? '';
|
||||
|
@ -50,5 +54,6 @@ class Template extends HiveObject {
|
|||
String get cryptoCurrency => cryptoCurrencyRaw ?? '';
|
||||
|
||||
String get amount => amountRaw ?? '';
|
||||
}
|
||||
|
||||
List<Template>? get additionalRecipients => additionalRecipientsRaw ?? null;
|
||||
}
|
||||
|
|
|
@ -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/settings_store.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:mobx/mobx.dart';
|
||||
|
||||
Timer? _timer;
|
||||
|
||||
|
@ -16,7 +17,7 @@ Future<void> startFiatRateUpdate(
|
|||
return;
|
||||
}
|
||||
|
||||
_timer = Timer.periodic(Duration(seconds: 30), (_) async {
|
||||
final _updateFiat = (_) async {
|
||||
try {
|
||||
if (appStore.wallet == null || settingsStore.fiatApiMode == FiatApiMode.disabled) {
|
||||
return;
|
||||
|
@ -45,5 +46,20 @@ Future<void> startFiatRateUpdate(
|
|||
} catch (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();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -1,21 +1,22 @@
|
|||
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/routes.dart';
|
||||
import 'package:cake_wallet/utils/clipboard_util.dart';
|
||||
import 'package:cake_wallet/utils/exception_handler.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:flutter/material.dart';
|
||||
import 'package:flutter/services.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';
|
||||
|
||||
class BackupPage extends BasePage {
|
||||
|
@ -52,7 +53,7 @@ class BackupPage extends BasePage {
|
|||
child: Observer(
|
||||
builder: (_) => GestureDetector(
|
||||
onTap: () {
|
||||
Clipboard.setData(
|
||||
ClipboardUtil.setSensitiveDataToClipboard(
|
||||
ClipboardData(text: backupViewModelBase.backupPassword));
|
||||
showBar<void>(
|
||||
context,
|
||||
|
@ -75,15 +76,14 @@ class BackupPage extends BasePage {
|
|||
]))),
|
||||
Positioned(
|
||||
child: Observer(
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
isLoading: backupViewModelBase.state is IsExecutingState,
|
||||
onPressed: () => onExportBackup(context),
|
||||
text: S.of(context).export_backup,
|
||||
color: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.bodyLarge!
|
||||
.color!,
|
||||
textColor: Colors.white)),
|
||||
builder: (_) => LoadingPrimaryButton(
|
||||
isLoading: backupViewModelBase.state is IsExecutingState,
|
||||
onPressed: () => onExportBackup(context),
|
||||
text: S.of(context).export_backup,
|
||||
color: Theme.of(context).accentTextTheme.bodyLarge!.color!,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
bottom: 24,
|
||||
left: 24,
|
||||
right: 24,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import 'package:cake_wallet/core/node_address_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/standard_checkbox.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()),
|
||||
_portController = TextEditingController(text: editingNode?.uri.port.toString()),
|
||||
_loginController = TextEditingController(text: editingNode?.login),
|
||||
_passwordController = TextEditingController(text: editingNode?.password) {
|
||||
_passwordController = TextEditingController(text: editingNode?.password),
|
||||
_socksAddressController = TextEditingController(text: editingNode?.socksProxyAddress){
|
||||
if (editingNode != null) {
|
||||
nodeViewModel
|
||||
..setAddress((editingNode!.uri.host.toString()))
|
||||
|
@ -25,7 +27,9 @@ class NodeForm extends StatelessWidget {
|
|||
..setPassword((editingNode!.password ?? ''))
|
||||
..setLogin((editingNode!.login ?? ''))
|
||||
..setSSL((editingNode!.isSSL))
|
||||
..setTrusted((editingNode!.trusted));
|
||||
..setTrusted((editingNode!.trusted))
|
||||
..setSocksProxy((editingNode!.useSocksProxy))
|
||||
..setSocksProxyAddress((editingNode!.socksProxyAddress ?? ''));
|
||||
}
|
||||
if (nodeViewModel.hasAuthCredentials) {
|
||||
reaction((_) => nodeViewModel.login, (String login) {
|
||||
|
@ -45,6 +49,7 @@ class NodeForm extends StatelessWidget {
|
|||
_portController.addListener(() => nodeViewModel.port = _portController.text);
|
||||
_loginController.addListener(() => nodeViewModel.login = _loginController.text);
|
||||
_passwordController.addListener(() => nodeViewModel.password = _passwordController.text);
|
||||
_socksAddressController.addListener(() => nodeViewModel.socksProxyAddress = _socksAddressController.text);
|
||||
}
|
||||
|
||||
final NodeCreateOrEditViewModel nodeViewModel;
|
||||
|
@ -55,6 +60,7 @@ class NodeForm extends StatelessWidget {
|
|||
final TextEditingController _portController;
|
||||
final TextEditingController _loginController;
|
||||
final TextEditingController _passwordController;
|
||||
final TextEditingController _socksAddressController;
|
||||
|
||||
@override
|
||||
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(),
|
||||
))
|
||||
],
|
||||
),
|
||||
]
|
||||
],
|
||||
)),
|
||||
]
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cw_core/wallet_type.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.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/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: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 {
|
||||
PreSeedPage(this.type)
|
||||
|
@ -61,7 +61,7 @@ class PreSeedPage extends BasePage {
|
|||
onPressed: () =>
|
||||
Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true),
|
||||
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)
|
||||
],
|
||||
),
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import 'package:cake_wallet/palette.dart';
|
||||
import 'package:cake_wallet/themes/theme_base.dart';
|
||||
import 'package:cake_wallet/src/widgets/alert_with_two_actions.dart';
|
||||
import 'package:cake_wallet/utils/clipboard_util.dart';
|
||||
import 'package:cake_wallet/utils/share_util.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:cake_wallet/utils/show_bar.dart';
|
||||
|
@ -92,8 +93,7 @@ class WalletSeedPage extends BasePage {
|
|||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
|
||||
constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3),
|
||||
child: AspectRatio(aspectRatio: 1, child: image),
|
||||
),
|
||||
Observer(builder: (_) {
|
||||
|
@ -159,7 +159,7 @@ class WalletSeedPage extends BasePage {
|
|||
child: Builder(
|
||||
builder: (context) => PrimaryButton(
|
||||
onPressed: () {
|
||||
Clipboard.setData(
|
||||
ClipboardUtil.setSensitiveDataToClipboard(
|
||||
ClipboardData(text: walletSeedViewModel.seed));
|
||||
showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||
},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
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/send/widgets/send_card.dart';
|
||||
import 'package:cake_wallet/src/widgets/add_template_button.dart';
|
||||
|
@ -241,6 +242,11 @@ class SendPage extends BasePage {
|
|||
return TemplateTile(
|
||||
key: UniqueKey(),
|
||||
to: template.name,
|
||||
hasMultipleRecipients:
|
||||
template.additionalRecipients !=
|
||||
null &&
|
||||
template.additionalRecipients!
|
||||
.length > 1,
|
||||
amount: template.isCurrencySelected
|
||||
? template.amount
|
||||
: template.amountFiat,
|
||||
|
@ -248,25 +254,36 @@ class SendPage extends BasePage {
|
|||
? template.cryptoCurrency
|
||||
: template.fiatCurrency,
|
||||
onTap: () async {
|
||||
final fiatFromTemplate = FiatCurrency
|
||||
.all
|
||||
.singleWhere((element) =>
|
||||
element.title ==
|
||||
template.fiatCurrency);
|
||||
final output = _defineCurrentOutput();
|
||||
output.address = template.address;
|
||||
if (template.isCurrencySelected) {
|
||||
output
|
||||
.setCryptoAmount(template.amount);
|
||||
if (template.additionalRecipients !=
|
||||
null) {
|
||||
sendViewModel.clearOutputs();
|
||||
|
||||
template.additionalRecipients!
|
||||
.forEach((currentElement) async {
|
||||
int i = template
|
||||
.additionalRecipients!
|
||||
.indexOf(currentElement);
|
||||
|
||||
Output output;
|
||||
try {
|
||||
output = sendViewModel.outputs[i];
|
||||
} catch (e) {
|
||||
sendViewModel.addOutput();
|
||||
output = sendViewModel.outputs[i];
|
||||
}
|
||||
|
||||
await _setInputsFromTemplate(
|
||||
context,
|
||||
output: output,
|
||||
template: currentElement);
|
||||
});
|
||||
} else {
|
||||
sendViewModel.setFiatCurrency(
|
||||
fiatFromTemplate);
|
||||
output.setFiatAmount(
|
||||
template.amountFiat);
|
||||
final output = _defineCurrentOutput();
|
||||
await _setInputsFromTemplate(
|
||||
context,
|
||||
output: output,
|
||||
template: template);
|
||||
}
|
||||
output.resetParsedAddress();
|
||||
await output
|
||||
.fetchParsedAddress(context);
|
||||
},
|
||||
onRemove: () {
|
||||
showPopUp<void>(
|
||||
|
@ -477,6 +494,24 @@ class SendPage extends BasePage {
|
|||
_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() {
|
||||
if (controller.page == null) {
|
||||
throw Exception('Controller page is null');
|
||||
|
|
|
@ -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:mobx/mobx.dart';
|
||||
import 'package:flutter/cupertino.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/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:cake_wallet/src/widgets/keyboard_done_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/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 {
|
||||
SendTemplatePage({required this.sendTemplateViewModel}) {
|
||||
sendTemplateViewModel.output.reset();
|
||||
}
|
||||
SendTemplatePage({required this.sendTemplateViewModel});
|
||||
|
||||
final SendTemplateViewModel sendTemplateViewModel;
|
||||
final _addressController = TextEditingController();
|
||||
final _cryptoAmountController = TextEditingController();
|
||||
final _fiatAmountController = TextEditingController();
|
||||
final _nameController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
final FocusNode _cryptoAmountFocus = FocusNode();
|
||||
final FocusNode _fiatAmountFocus = FocusNode();
|
||||
|
||||
bool _effectsInstalled = false;
|
||||
final controller = PageController(initialPage: 0);
|
||||
|
||||
@override
|
||||
String get title => S.current.exchange_new_template;
|
||||
|
@ -44,273 +30,146 @@ class SendTemplatePage extends BasePage {
|
|||
AppBarStyle get appBarStyle => AppBarStyle.transparent;
|
||||
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
_setEffects(context);
|
||||
Widget trailing(context) => Observer(builder: (_) {
|
||||
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(
|
||||
config: KeyboardActionsConfig(
|
||||
keyboardActionsPlatform: KeyboardActionsPlatform.IOS,
|
||||
keyboardBarColor: Theme.of(context)
|
||||
.accentTextTheme!
|
||||
.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(
|
||||
@override
|
||||
Widget body(BuildContext context) {
|
||||
return Form(
|
||||
key: _formKey,
|
||||
child: ScrollableWithBottomSection(
|
||||
contentPadding: EdgeInsets.only(bottom: 24),
|
||||
content: 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: Form(
|
||||
key: _formKey,
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.fromLTRB(24, 90, 24, 32),
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
BaseTextFormField(
|
||||
controller: _nameController,
|
||||
hintText: 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(
|
||||
controller: _addressController,
|
||||
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),
|
||||
))),
|
||||
],
|
||||
content: FocusTraversalGroup(
|
||||
policy: OrderedTraversalPolicy(),
|
||||
child: Column(children: [
|
||||
Container(
|
||||
height: 460,
|
||||
child: Observer(builder: (_) {
|
||||
return PageView.builder(
|
||||
scrollDirection: Axis.horizontal,
|
||||
controller: controller,
|
||||
itemCount: sendTemplateViewModel.recipients.length,
|
||||
itemBuilder: (_, index) {
|
||||
final template =
|
||||
sendTemplateViewModel.recipients[index];
|
||||
return SendTemplateCard(
|
||||
template: template,
|
||||
index: index,
|
||||
sendTemplateViewModel: sendTemplateViewModel);
|
||||
});
|
||||
})),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
top: 10, left: 24, right: 24, bottom: 10),
|
||||
child: Container(
|
||||
height: 10,
|
||||
child: Observer(
|
||||
builder: (_) {
|
||||
final count = sendTemplateViewModel.recipients.length;
|
||||
|
||||
return count > 1
|
||||
? SmoothPageIndicator(
|
||||
controller: controller,
|
||||
count: count,
|
||||
effect: ScrollingDotsEffect(
|
||||
spacing: 6.0,
|
||||
radius: 6.0,
|
||||
dotWidth: 6.0,
|
||||
dotHeight: 6.0,
|
||||
dotColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.displaySmall!
|
||||
.backgroundColor!,
|
||||
activeDotColor: Theme.of(context)
|
||||
.primaryTextTheme
|
||||
.displayMedium!
|
||||
.backgroundColor!))
|
||||
: Offstage();
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
])),
|
||||
bottomSectionPadding:
|
||||
EdgeInsets.only(left: 24, right: 24, bottom: 24),
|
||||
bottomSection: PrimaryButton(
|
||||
onPressed: () {
|
||||
if (_formKey.currentState != null && _formKey.currentState!.validate()) {
|
||||
sendTemplateViewModel.addTemplate(
|
||||
isCurrencySelected: sendTemplateViewModel.isCurrencySelected,
|
||||
name: _nameController.text,
|
||||
address: _addressController.text,
|
||||
cryptoCurrency:sendTemplateViewModel.currency.title,
|
||||
fiatCurrency: sendTemplateViewModel.fiat.title,
|
||||
amount: _cryptoAmountController.text,
|
||||
amountFiat: _fiatAmountController.text);
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
},
|
||||
text: S.of(context).save,
|
||||
color: Colors.green,
|
||||
textColor: Colors.white,
|
||||
),
|
||||
),
|
||||
));
|
||||
bottomSection: Column(children: [
|
||||
// if (sendViewModel.hasMultiRecipient)
|
||||
Padding(
|
||||
padding: EdgeInsets.only(bottom: 12),
|
||||
child: PrimaryButton(
|
||||
onPressed: () {
|
||||
sendTemplateViewModel.addRecipient();
|
||||
Future.delayed(const Duration(milliseconds: 250), () {
|
||||
controller.jumpToPage(
|
||||
sendTemplateViewModel.recipients.length - 1);
|
||||
});
|
||||
},
|
||||
text: S.of(context).add_receiver,
|
||||
color: Colors.transparent,
|
||||
textColor: Theme.of(context)
|
||||
.accentTextTheme
|
||||
.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) {
|
||||
if (_effectsInstalled) {
|
||||
return;
|
||||
TemplateViewModel _defineCurrentRecipient() {
|
||||
if (controller.page == null) {
|
||||
throw Exception('Controller page is null');
|
||||
}
|
||||
|
||||
final output = sendTemplateViewModel.output;
|
||||
|
||||
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;
|
||||
final itemCount = controller.page!.round();
|
||||
return sendTemplateViewModel.recipients[itemCount];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -68,6 +68,7 @@ Future<String> extractAddressFromParsed(
|
|||
}
|
||||
|
||||
return address;
|
||||
case ParseFrom.contact:
|
||||
case ParseFrom.notParsed:
|
||||
address = parsedAddress.addresses.first;
|
||||
return address;
|
||||
|
@ -85,4 +86,4 @@ Future<String> extractAddressFromParsed(
|
|||
});
|
||||
|
||||
return address;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -172,8 +172,10 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
|||
hintStyle: TextStyle(
|
||||
fontSize: 14,
|
||||
fontWeight: FontWeight.w500,
|
||||
color:
|
||||
Theme.of(context).primaryTextTheme.headlineSmall!.decorationColor!),
|
||||
color: Theme.of(context)
|
||||
.primaryTextTheme!
|
||||
.headlineSmall!
|
||||
.decorationColor!),
|
||||
onPushPasteButton: (context) async {
|
||||
output.resetParsedAddress();
|
||||
await output.fetchParsedAddress(context);
|
||||
|
@ -182,6 +184,9 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
|||
output.resetParsedAddress();
|
||||
await output.fetchParsedAddress(context);
|
||||
},
|
||||
onSelectedContact: (contact) {
|
||||
output.loadContact(contact);
|
||||
},
|
||||
validator: validator,
|
||||
selectedCurrency: sendViewModel.currency,
|
||||
);
|
||||
|
@ -464,7 +469,6 @@ class SendCardState extends State<SendCard> with AutomaticKeepAliveClientMixin<S
|
|||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w500,
|
||||
//color: Theme.of(context).primaryTextTheme!.displaySmall!.color!,
|
||||
color: Colors.white),
|
||||
),
|
||||
Container(
|
||||
|
|
267
lib/src/screens/send/widgets/send_template_card.dart
Normal file
267
lib/src/screens/send/widgets/send_template_card.dart
Normal 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;
|
||||
}
|
||||
}
|
|
@ -1,16 +1,18 @@
|
|||
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/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/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 '../../../palette.dart';
|
||||
import '../../widgets/primary_button.dart';
|
||||
import '../../widgets/standard_list.dart';
|
||||
|
||||
|
||||
class Setup2FAQRPage extends BasePage {
|
||||
Setup2FAQRPage({required this.setup2FAViewModel});
|
||||
|
@ -106,7 +108,8 @@ class Setup2FAQRPage extends BasePage {
|
|||
height: 32,
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
Clipboard.setData(ClipboardData(text: '${setup2FAViewModel.secretKey}'));
|
||||
ClipboardUtil.setSensitiveDataToClipboard(
|
||||
ClipboardData(text: '${setup2FAViewModel.secretKey}'));
|
||||
showBar<void>(context, S.of(context).copied_to_clipboard);
|
||||
},
|
||||
child: Container(
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
import 'package:auto_size_text/auto_size_text.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/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/view_model/wallet_keys_view_model.dart';
|
||||
import 'package:device_display_brightness/device_display_brightness.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_mobx/flutter_mobx.dart';
|
||||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/src/screens/base_page.dart';
|
||||
import 'package:cake_wallet/src/widgets/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';
|
||||
|
||||
class WalletKeysPage extends BasePage {
|
||||
|
@ -56,7 +57,7 @@ class WalletKeysPage extends BasePage {
|
|||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(12.0),
|
||||
color: Theme.of(context).accentTextTheme!.bodySmall!.color!,
|
||||
color: Theme.of(context).accentTextTheme.bodySmall!.color!,
|
||||
),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
|
@ -84,7 +85,7 @@ class WalletKeysPage extends BasePage {
|
|||
separatorBuilder: (context, index) => Container(
|
||||
height: 1,
|
||||
padding: EdgeInsets.only(left: 24),
|
||||
color: Theme.of(context).accentTextTheme!.titleLarge!.backgroundColor!,
|
||||
color: Theme.of(context).accentTextTheme.titleLarge!.backgroundColor!,
|
||||
child: const SectionDivider(),
|
||||
),
|
||||
itemCount: walletKeysViewModel.items.length,
|
||||
|
@ -93,7 +94,7 @@ class WalletKeysPage extends BasePage {
|
|||
|
||||
return GestureDetector(
|
||||
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));
|
||||
},
|
||||
child: ListRow(
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
import 'package:cake_wallet/utils/device_info.dart';
|
||||
import 'package:cake_wallet/utils/responsive_layout_util.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
@ -16,10 +15,7 @@ class AddressTextField extends StatelessWidget {
|
|||
{required this.controller,
|
||||
this.isActive = true,
|
||||
this.placeholder,
|
||||
this.options = const [
|
||||
AddressTextFieldOption.qrCode,
|
||||
AddressTextFieldOption.addressBook
|
||||
],
|
||||
this.options = const [AddressTextFieldOption.qrCode, AddressTextFieldOption.addressBook],
|
||||
this.onURIScanned,
|
||||
this.focusNode,
|
||||
this.isBorderExist = true,
|
||||
|
@ -31,6 +27,7 @@ class AddressTextField extends StatelessWidget {
|
|||
this.validator,
|
||||
this.onPushPasteButton,
|
||||
this.onPushAddressBookButton,
|
||||
this.onSelectedContact,
|
||||
this.selectedCurrency});
|
||||
|
||||
static const prefixIconWidth = 34.0;
|
||||
|
@ -52,6 +49,7 @@ class AddressTextField extends StatelessWidget {
|
|||
final FocusNode? focusNode;
|
||||
final Function(BuildContext context)? onPushPasteButton;
|
||||
final Function(BuildContext context)? onPushAddressBookButton;
|
||||
final Function(ContactBase contact)? onSelectedContact;
|
||||
final CryptoCurrency? selectedCurrency;
|
||||
|
||||
@override
|
||||
|
@ -66,34 +64,27 @@ class AddressTextField extends StatelessWidget {
|
|||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
style: textStyle ??
|
||||
TextStyle(
|
||||
fontSize: 16,
|
||||
color: Theme.of(context).primaryTextTheme!.titleLarge!.color!),
|
||||
TextStyle(fontSize: 16, color: Theme.of(context).primaryTextTheme.titleLarge!.color!),
|
||||
decoration: InputDecoration(
|
||||
suffixIcon: SizedBox(
|
||||
width: prefixIconWidth * options.length +
|
||||
(spaceBetweenPrefixIcons * options.length),
|
||||
width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length),
|
||||
),
|
||||
hintStyle: hintStyle ??
|
||||
TextStyle(fontSize: 16, color: Theme.of(context).hintColor),
|
||||
hintStyle: hintStyle ?? TextStyle(fontSize: 16, color: Theme.of(context).hintColor),
|
||||
hintText: placeholder ?? S.current.widgets_address,
|
||||
focusedBorder: isBorderExist
|
||||
? UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ?? Theme.of(context).dividerColor,
|
||||
width: 1.0))
|
||||
color: borderColor ?? Theme.of(context).dividerColor, width: 1.0))
|
||||
: InputBorder.none,
|
||||
disabledBorder: isBorderExist
|
||||
? UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ?? Theme.of(context).dividerColor,
|
||||
width: 1.0))
|
||||
color: borderColor ?? Theme.of(context).dividerColor, width: 1.0))
|
||||
: InputBorder.none,
|
||||
enabledBorder: isBorderExist
|
||||
? UnderlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: borderColor ?? Theme.of(context).dividerColor,
|
||||
width: 1.0))
|
||||
color: borderColor ?? Theme.of(context).dividerColor, width: 1.0))
|
||||
: InputBorder.none,
|
||||
),
|
||||
validator: validator,
|
||||
|
@ -102,11 +93,11 @@ class AddressTextField extends StatelessWidget {
|
|||
top: 2,
|
||||
right: 0,
|
||||
child: SizedBox(
|
||||
width: prefixIconWidth * options.length +
|
||||
(spaceBetweenPrefixIcons * options.length),
|
||||
width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length),
|
||||
child: Row(
|
||||
mainAxisAlignment: ResponsiveLayoutUtil.instance.isMobile
|
||||
? MainAxisAlignment.spaceBetween : MainAxisAlignment.end,
|
||||
mainAxisAlignment: ResponsiveLayoutUtil.instance.isMobile
|
||||
? MainAxisAlignment.spaceBetween
|
||||
: MainAxisAlignment.end,
|
||||
children: [
|
||||
SizedBox(width: 5),
|
||||
if (this.options.contains(AddressTextFieldOption.paste)) ...[
|
||||
|
@ -122,20 +113,14 @@ class AddressTextField extends StatelessWidget {
|
|||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: buttonColor ??
|
||||
Theme.of(context)
|
||||
.accentTextTheme
|
||||
!
|
||||
.titleLarge!
|
||||
.color!,
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(6))),
|
||||
Theme.of(context).accentTextTheme.titleLarge!.color!,
|
||||
borderRadius: BorderRadius.all(Radius.circular(6))),
|
||||
child: Image.asset(
|
||||
'assets/images/paste_ios.png',
|
||||
color: iconColor ??
|
||||
Theme.of(context)
|
||||
.primaryTextTheme
|
||||
!
|
||||
.headlineMedium!
|
||||
.headlineMedium!
|
||||
.decorationColor!,
|
||||
)),
|
||||
),
|
||||
|
@ -155,28 +140,21 @@ class AddressTextField extends StatelessWidget {
|
|||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: buttonColor ??
|
||||
Theme.of(context)
|
||||
.accentTextTheme
|
||||
|
||||
.titleLarge!
|
||||
.color!,
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(6))),
|
||||
Theme.of(context).accentTextTheme.titleLarge!.color!,
|
||||
borderRadius: BorderRadius.all(Radius.circular(6))),
|
||||
child: Image.asset(
|
||||
'assets/images/qr_code_icon.png',
|
||||
color: iconColor ??
|
||||
Theme.of(context)
|
||||
.primaryTextTheme
|
||||
!.headlineMedium!
|
||||
.headlineMedium!
|
||||
.decorationColor!,
|
||||
)),
|
||||
),
|
||||
))
|
||||
] else
|
||||
SizedBox(width: 5),
|
||||
if (this
|
||||
.options
|
||||
.contains(AddressTextFieldOption.addressBook)) ...[
|
||||
if (this.options.contains(AddressTextFieldOption.addressBook)) ...[
|
||||
Container(
|
||||
width: prefixIconWidth,
|
||||
height: prefixIconHeight,
|
||||
|
@ -184,26 +162,19 @@ class AddressTextField extends StatelessWidget {
|
|||
child: Semantics(
|
||||
label: S.of(context).address_book,
|
||||
child: InkWell(
|
||||
onTap: () async =>
|
||||
_presetAddressBookPicker(context),
|
||||
onTap: () async => _presetAddressBookPicker(context),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: buttonColor ??
|
||||
Theme.of(context)
|
||||
.accentTextTheme
|
||||
!
|
||||
.titleLarge!
|
||||
.color!,
|
||||
borderRadius:
|
||||
BorderRadius.all(Radius.circular(6))),
|
||||
Theme.of(context).accentTextTheme.titleLarge!.color!,
|
||||
borderRadius: BorderRadius.all(Radius.circular(6))),
|
||||
child: Image.asset(
|
||||
'assets/images/open_book.png',
|
||||
color: iconColor ??
|
||||
Theme.of(context)
|
||||
.primaryTextTheme
|
||||
!
|
||||
.headlineMedium!
|
||||
.headlineMedium!
|
||||
.decorationColor!,
|
||||
)),
|
||||
),
|
||||
|
@ -221,30 +192,31 @@ class AddressTextField extends StatelessWidget {
|
|||
if (code.isEmpty) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
final uri = Uri.parse(code);
|
||||
controller?.text = uri.path;
|
||||
onURIScanned?.call(uri);
|
||||
} catch(_){
|
||||
} catch (_) {
|
||||
controller?.text = code;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _presetAddressBookPicker(BuildContext context) async {
|
||||
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;
|
||||
onPushAddressBookButton?.call(context);
|
||||
onSelectedContact?.call(contact);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _pasteAddress(BuildContext context) async {
|
||||
final clipboard = await Clipboard.getData('text/plain');
|
||||
final address = clipboard?.text ?? '';
|
||||
|
||||
|
||||
if (address.isNotEmpty) {
|
||||
controller?.text = address;
|
||||
}
|
||||
|
|
|
@ -66,6 +66,7 @@ class NavBar extends StatelessWidget implements ObstructingPreferredSizeWidget {
|
|||
leading: leading,
|
||||
automaticallyImplyLeading: false,
|
||||
automaticallyImplyMiddle: false,
|
||||
transitionBetweenRoutes: false,
|
||||
middle: middle,
|
||||
trailing: trailing,
|
||||
backgroundColor: backgroundColor,
|
||||
|
|
|
@ -8,7 +8,8 @@ class TemplateTile extends StatefulWidget {
|
|||
required this.amount,
|
||||
required this.from,
|
||||
required this.onTap,
|
||||
required this.onRemove
|
||||
required this.onRemove,
|
||||
this.hasMultipleRecipients,
|
||||
}) : super(key: key);
|
||||
|
||||
final String to;
|
||||
|
@ -16,6 +17,7 @@ class TemplateTile extends StatefulWidget {
|
|||
final String from;
|
||||
final VoidCallback onTap;
|
||||
final VoidCallback onRemove;
|
||||
final bool? hasMultipleRecipients;
|
||||
|
||||
@override
|
||||
TemplateTileState createState() => TemplateTileState(
|
||||
|
@ -51,45 +53,47 @@ class TemplateTileState extends State<TemplateTile> {
|
|||
final toIcon = Image.asset('assets/images/to_icon.png', color: color);
|
||||
|
||||
final content = Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Text(
|
||||
amount,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 5),
|
||||
child: Text(
|
||||
from,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 5),
|
||||
child: toIcon,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 5),
|
||||
child: Text(
|
||||
to,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: widget.hasMultipleRecipients ?? false
|
||||
? [
|
||||
Text(
|
||||
to,
|
||||
style: TextStyle(
|
||||
fontSize: 16, fontWeight: FontWeight.w600, color: color),
|
||||
),
|
||||
]
|
||||
: [
|
||||
Text(
|
||||
amount,
|
||||
style: TextStyle(
|
||||
fontSize: 16, fontWeight: FontWeight.w600, color: color),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 5),
|
||||
child: Text(
|
||||
from,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 5),
|
||||
child: toIcon,
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 5),
|
||||
child: Text(
|
||||
to,
|
||||
style: TextStyle(
|
||||
fontSize: 16,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: color),
|
||||
),
|
||||
),
|
||||
]);
|
||||
|
||||
final tile = Container(
|
||||
padding: EdgeInsets.only(right: 10),
|
||||
|
|
|
@ -23,25 +23,27 @@ abstract class SendTemplateBase with Store {
|
|||
templates.replaceRange(0, templates.length, templateSource.values.toList());
|
||||
|
||||
@action
|
||||
Future<void> addTemplate({
|
||||
required String name,
|
||||
required bool isCurrencySelected,
|
||||
required String address,
|
||||
required String cryptoCurrency,
|
||||
required String fiatCurrency,
|
||||
required String amount,
|
||||
required String amountFiat}) async {
|
||||
Future<void> addTemplate(
|
||||
{required String name,
|
||||
required bool isCurrencySelected,
|
||||
required String address,
|
||||
required String cryptoCurrency,
|
||||
required String fiatCurrency,
|
||||
required String amount,
|
||||
required String amountFiat,
|
||||
required List<Template> additionalRecipients}) async {
|
||||
final template = Template(
|
||||
nameRaw: name,
|
||||
isCurrencySelectedRaw: isCurrencySelected,
|
||||
addressRaw: address,
|
||||
cryptoCurrencyRaw: cryptoCurrency,
|
||||
fiatCurrencyRaw: fiatCurrency,
|
||||
amountRaw: amount,
|
||||
amountFiatRaw: amountFiat);
|
||||
nameRaw: name,
|
||||
isCurrencySelectedRaw: isCurrencySelected,
|
||||
addressRaw: address,
|
||||
cryptoCurrencyRaw: cryptoCurrency,
|
||||
fiatCurrencyRaw: fiatCurrency,
|
||||
amountRaw: amount,
|
||||
amountFiatRaw: amountFiat,
|
||||
additionalRecipientsRaw: additionalRecipients);
|
||||
await templateSource.add(template);
|
||||
}
|
||||
|
||||
@action
|
||||
Future<void> remove({required Template template}) async => await template.delete();
|
||||
}
|
||||
}
|
||||
|
|
15
lib/utils/clipboard_util.dart
Normal file
15
lib/utils/clipboard_util.dart
Normal 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);
|
||||
}
|
||||
}
|
|
@ -67,7 +67,8 @@ abstract class BalanceViewModelBase with Store {
|
|||
final price = fiatConvertationStore.prices[appStore.wallet!.currency];
|
||||
|
||||
if (price == null) {
|
||||
throw Exception('No price for ${appStore.wallet!.currency} (current wallet)');
|
||||
// price should update on next fetch:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return price;
|
||||
|
|
|
@ -19,7 +19,9 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
port = '',
|
||||
login = '',
|
||||
password = '',
|
||||
trusted = false;
|
||||
trusted = false,
|
||||
useSocksProxy = false,
|
||||
socksProxyAddress = '';
|
||||
|
||||
@observable
|
||||
ExecutionState state;
|
||||
|
@ -45,6 +47,12 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
@observable
|
||||
bool trusted;
|
||||
|
||||
@observable
|
||||
bool useSocksProxy;
|
||||
|
||||
@observable
|
||||
String socksProxyAddress;
|
||||
|
||||
@computed
|
||||
bool get isReady => address.isNotEmpty && port.isNotEmpty;
|
||||
|
||||
|
@ -73,6 +81,8 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
password = '';
|
||||
useSSL = false;
|
||||
trusted = false;
|
||||
useSocksProxy = false;
|
||||
socksProxyAddress = '';
|
||||
}
|
||||
|
||||
@action
|
||||
|
@ -93,6 +103,12 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
@action
|
||||
void setTrusted(bool val) => trusted = val;
|
||||
|
||||
@action
|
||||
void setSocksProxy(bool val) => useSocksProxy = val;
|
||||
|
||||
@action
|
||||
void setSocksProxyAddress(String val) => socksProxyAddress = val;
|
||||
|
||||
@action
|
||||
Future<void> save({Node? editingNode, bool saveAsCurrent = false}) async {
|
||||
final node = Node(
|
||||
|
@ -101,7 +117,8 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
login: login,
|
||||
password: password,
|
||||
useSSL: useSSL,
|
||||
trusted: trusted);
|
||||
trusted: trusted,
|
||||
socksProxyAddress: socksProxyAddress);
|
||||
try {
|
||||
state = IsExecutingState();
|
||||
if (editingNode != null) {
|
||||
|
@ -130,7 +147,8 @@ abstract class NodeCreateOrEditViewModelBase with Store {
|
|||
login: login,
|
||||
password: password,
|
||||
useSSL: useSSL,
|
||||
trusted: trusted);
|
||||
trusted: trusted,
|
||||
socksProxyAddress: socksProxyAddress);
|
||||
try {
|
||||
connectionState = IsExecutingState();
|
||||
final isAlive = await node.requestNode();
|
||||
|
|
|
@ -18,6 +18,8 @@ import 'package:cake_wallet/store/settings_store.dart';
|
|||
import 'package:cake_wallet/generated/i18n.dart';
|
||||
import 'package:cake_wallet/bitcoin/bitcoin.dart';
|
||||
|
||||
import 'package:cake_wallet/entities/contact_base.dart';
|
||||
|
||||
part 'output.g.dart';
|
||||
|
||||
const String cryptoNumberPattern = '0.0';
|
||||
|
@ -244,4 +246,11 @@ abstract class OutputBase with Store {
|
|||
extractedAddress = await extractAddressFromParsed(context, parsedAddress);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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:mobx/mobx.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/address_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/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/settings_store.dart';
|
||||
|
||||
|
@ -19,72 +17,74 @@ class SendTemplateViewModel = SendTemplateViewModelBase
|
|||
with _$SendTemplateViewModel;
|
||||
|
||||
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 SettingsStore _settingsStore;
|
||||
final SendTemplateStore _sendTemplateStore;
|
||||
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();
|
||||
|
||||
@action
|
||||
void addTemplate(
|
||||
{required String name,
|
||||
required bool isCurrencySelected,
|
||||
required String address,
|
||||
required String cryptoCurrency,
|
||||
required String fiatCurrency,
|
||||
required String amount,
|
||||
required String amountFiat}) {
|
||||
required bool isCurrencySelected,
|
||||
required String address,
|
||||
required String amount,
|
||||
required String amountFiat,
|
||||
required List<Template> additionalRecipients}) {
|
||||
_sendTemplateStore.addTemplate(
|
||||
name: name,
|
||||
isCurrencySelected: isCurrencySelected,
|
||||
address: address,
|
||||
cryptoCurrency: cryptoCurrency,
|
||||
cryptoCurrency: cryptoCurrency.title,
|
||||
fiatCurrency: fiatCurrency,
|
||||
amount: amount,
|
||||
amountFiat: amountFiat);
|
||||
amountFiat: amountFiat,
|
||||
additionalRecipients: additionalRecipients);
|
||||
updateTemplate();
|
||||
}
|
||||
|
||||
@action
|
||||
void removeTemplate({required Template template}) {
|
||||
_sendTemplateStore.remove(template: template);
|
||||
updateTemplate();
|
||||
|
|
80
lib/view_model/send/template_view_model.dart
Normal file
80
lib/view_model/send/template_view_model.dart
Normal 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);
|
||||
}
|
||||
}
|
|
@ -3,7 +3,6 @@ dependencies:
|
|||
sdk: flutter
|
||||
flutter_localizations:
|
||||
sdk: flutter
|
||||
flutter_cupertino_localizations: ^1.0.1
|
||||
intl: ^0.17.0
|
||||
url_launcher: ^6.1.4
|
||||
qr_flutter:
|
||||
|
@ -82,6 +81,7 @@ dependencies:
|
|||
path_provider_android: 2.0.24
|
||||
shared_preferences_android: 2.0.17
|
||||
url_launcher_android: 6.0.24
|
||||
sensitive_clipboard: ^1.0.0
|
||||
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-Medium.ttf
|
||||
- asset: assets/fonts/Lato-Semibold.ttf
|
||||
- asset: assets/fonts/Lato-Bold.ttf
|
||||
- asset: assets/fonts/Lato-Bold.ttf
|
||||
|
|
|
@ -250,8 +250,8 @@
|
|||
"transaction_details_recipient_address": "عناوين المستلم",
|
||||
"wallet_list_title": "محفظة Monero",
|
||||
"wallet_list_create_new_wallet": "إنشاء محفظة جديدة",
|
||||
"wallet_list_edit_wallet": "تحرير المحفظة",
|
||||
"wallet_list_wallet_name": "اسم المحفظة",
|
||||
"wallet_list_edit_wallet" : "تحرير المحفظة",
|
||||
"wallet_list_wallet_name" : "اسم المحفظة",
|
||||
"wallet_list_restore_wallet": "استعادة المحفظة",
|
||||
"wallet_list_load_wallet": "تحميل المحفظة",
|
||||
"wallet_list_loading_wallet": "جار تحميل محفظة ${wallet_name}",
|
||||
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "يجب أن يتوافق عنوان المحفظة مع نوع\nالعملة المشفرة",
|
||||
"error_text_node_address": "الرجاء إدخال عنوان IPv4",
|
||||
"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_xmr": "لا يمكن أن تتجاوز قيمة XMR الرصيد المتاح.\nيجب أن يكون عدد الكسور أقل من أو يساوي 12",
|
||||
"error_text_fiat": "لا يمكن أن تتجاوز قيمة المبلغ الرصيد المتاح.\nيجب أن يكون عدد الكسور أقل أو يساوي 2",
|
||||
|
@ -652,5 +653,6 @@
|
|||
"generate_name": "توليد الاسم",
|
||||
"balance_page": "صفحة التوازن",
|
||||
"share": "يشارك",
|
||||
"slidable": "قابل للانزلاق"
|
||||
"slidable": "قابل للانزلاق",
|
||||
"template_name": "اسم القالب"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "Адресът на портфейла трябва да отговаря \n на вида криптовалута",
|
||||
"error_text_node_address": "Моля, въведете iPv4 адрес",
|
||||
"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_xmr": "XMR сумата не може да надхвърля наличния баланс.\nБроят на цифрите след десетичната запетая може да бъде най-много 12",
|
||||
"error_text_fiat": "Сумата не може да надвишава наличния баланс.\nThe number of fraction digits must be less or equal to 2",
|
||||
|
@ -648,5 +649,6 @@
|
|||
"generate_name": "Генериране на име",
|
||||
"balance_page": "Страница за баланс",
|
||||
"share": "Дял",
|
||||
"slidable": "Плъзгащ се"
|
||||
"slidable": "Плъзгащ се",
|
||||
"template_name": "Име на шаблон"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "Adresa peněženky musí odpovídat typu\nkryptoměny",
|
||||
"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_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_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",
|
||||
|
@ -648,5 +649,6 @@
|
|||
"generate_name": "Generovat jméno",
|
||||
"balance_page": "Stránka zůstatku",
|
||||
"share": "Podíl",
|
||||
"slidable": "Posuvné"
|
||||
"slidable": "Posuvné",
|
||||
"template_name": "Název šablony"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"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_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_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",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "Namen generieren",
|
||||
"balance_page": "Balance-Seite",
|
||||
"share": "Aktie",
|
||||
"slidable": "Verschiebbar"
|
||||
"slidable": "Verschiebbar",
|
||||
"template_name": "Vorlagenname"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "Wallet address must correspond to the type\nof cryptocurrency",
|
||||
"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_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_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",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "Generate Name",
|
||||
"balance_page": "Balance Page",
|
||||
"share": "Share",
|
||||
"slidable": "Slidable"
|
||||
"slidable": "Slidable",
|
||||
"template_name": "Template Name"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"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_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_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",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "Generar nombre",
|
||||
"balance_page": "Página de saldo",
|
||||
"share": "Compartir",
|
||||
"slidable": "deslizable"
|
||||
"slidable": "deslizable",
|
||||
"template_name": "Nombre de la plantilla"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"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_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_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",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "Générer un nom",
|
||||
"balance_page": "Page Solde",
|
||||
"share": "Partager",
|
||||
"slidable": "Glissable"
|
||||
"slidable": "Glissable",
|
||||
"template_name": "Nom du modèle"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"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_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_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",
|
||||
|
@ -634,5 +635,6 @@
|
|||
"generate_name": "Ƙirƙirar Suna",
|
||||
"balance_page": "Ma'auni Page",
|
||||
"share": "Raba",
|
||||
"slidable": "Mai iya zamewa"
|
||||
"slidable": "Mai iya zamewa",
|
||||
"template_name": "Sunan Samfura"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "वॉलेट पता प्रकार के अनुरूप होना चाहिए\nक्रिप्टोकरेंसी का",
|
||||
"error_text_node_address": "कृपया एक IPv4 पता दर्ज करें",
|
||||
"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_xmr": "एक्सएमआर मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या 12 से कम या इसके बराबर होनी चाहिए",
|
||||
"error_text_fiat": "राशि का मूल्य उपलब्ध शेष राशि से अधिक नहीं हो सकता.\nअंश अंकों की संख्या कम या 2 के बराबर होनी चाहिए",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "नाम जनरेट करें",
|
||||
"balance_page": "बैलेंस पेज",
|
||||
"share": "शेयर करना",
|
||||
"slidable": "फिसलने लायक"
|
||||
"slidable": "फिसलने लायक",
|
||||
"template_name": "टेम्पलेट नाम"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "Adresa novčanika mora odgovarati\nvrsti kriptovalute",
|
||||
"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_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_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.",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "Generiraj ime",
|
||||
"balance_page": "Stranica sa stanjem",
|
||||
"share": "Udio",
|
||||
"slidable": "Klizna"
|
||||
"slidable": "Klizna",
|
||||
"template_name": "Naziv predloška"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "Alamat dompet harus sesuai dengan tipe\nmata uang kripto",
|
||||
"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_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_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",
|
||||
|
@ -644,5 +645,6 @@
|
|||
"generate_name": "Hasilkan Nama",
|
||||
"balance_page": "Halaman Saldo",
|
||||
"share": "Membagikan",
|
||||
"slidable": "Dapat digeser"
|
||||
"slidable": "Dapat digeser",
|
||||
"template_name": "Nama Templat"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"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_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_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",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "Genera nome",
|
||||
"balance_page": "Pagina di equilibrio",
|
||||
"share": "Condividere",
|
||||
"slidable": "Scorrevole"
|
||||
"slidable": "Scorrevole",
|
||||
"template_name": "Nome modello"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "ウォレットアドレスは、\n暗号通貨",
|
||||
"error_text_node_address": "iPv4アドレスを入力してください",
|
||||
"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_xmr": "XMR値は利用可能な残高を超えることはできません.\n小数桁数は12以下でなければなりません",
|
||||
"error_text_fiat": "金額は利用可能な残高を超えることはできません.\n小数桁の数は2以下でなければなりません",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "名前の生成",
|
||||
"balance_page": "残高ページ",
|
||||
"share": "共有",
|
||||
"slidable": "スライド可能"
|
||||
"slidable": "スライド可能",
|
||||
"template_name": "テンプレート名"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "지갑 주소는 유형과 일치해야합니다\n암호 화폐",
|
||||
"error_text_node_address": "iPv4 주소를 입력하십시오",
|
||||
"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_xmr": "XMR 값은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 12 이하 여야합니다",
|
||||
"error_text_fiat": "금액은 사용 가능한 잔액을 초과 할 수 없습니다.\n소수 자릿수는 2보다 작거나 같아야합니다",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "이름 생성",
|
||||
"balance_page": "잔액 페이지",
|
||||
"share": "공유하다",
|
||||
"slidable": "슬라이딩 가능"
|
||||
"slidable": "슬라이딩 가능",
|
||||
"template_name": "템플릿 이름"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "Wallet လိပ်စာသည် အမျိုးအစား\no cryptocurrency နှင့် ကိုက်ညီရပါမည်။",
|
||||
"error_text_node_address": "ကျေးဇူးပြု၍ iPv4 လိပ်စာကို ထည့်ပါ။",
|
||||
"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_xmr": "XMR တန်ဖိုးသည် ရနိုင်သောလက်ကျန်ကို ကျော်လွန်၍မရပါ။\nအပိုင်းကိန်းဂဏန်းများ သည် 12 နှင့် လျော့နည်းရမည်",
|
||||
"error_text_fiat": "ပမာဏ၏တန်ဖိုးသည် ရနိုင်သောလက်ကျန်ကို မကျော်လွန်နိုင်ပါ။\nအပိုင်းကိန်းဂဏန်းအရေအတွက်သည် 2 နှင့် လျော့နည်းရမည်",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "အမည်ဖန်တီးပါ။",
|
||||
"balance_page": "လက်ကျန်စာမျက်နှာ",
|
||||
"share": "မျှဝေပါ။",
|
||||
"slidable": "လျှောချနိုင်သည်။"
|
||||
"slidable": "လျှောချနိုင်သည်။",
|
||||
"template_name": "နမူနာပုံစံ"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "Portemonnee-adres moet overeenkomen met het type\nvan cryptocurrency",
|
||||
"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_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_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",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "Naam genereren",
|
||||
"balance_page": "Saldo pagina",
|
||||
"share": "Deel",
|
||||
"slidable": "Verschuifbaar"
|
||||
"slidable": "Verschuifbaar",
|
||||
"template_name": "Sjabloonnaam"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "Adres musi odpowiadać typowi kryptowaluty",
|
||||
"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_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_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",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "Wygeneruj nazwę",
|
||||
"balance_page": "Strona salda",
|
||||
"share": "Udział",
|
||||
"slidable": "Przesuwne"
|
||||
"slidable": "Przesuwne",
|
||||
"template_name": "Nazwa szablonu"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "O endereço da carteira deve corresponder à\ncriptomoeda selecionada",
|
||||
"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_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_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",
|
||||
|
@ -653,5 +654,6 @@
|
|||
"generate_name": "Gerar nome",
|
||||
"balance_page": "Página de saldo",
|
||||
"share": "Compartilhar",
|
||||
"slidable": "Deslizável"
|
||||
"slidable": "Deslizável",
|
||||
"template_name": "Nome do modelo"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,8 @@
|
|||
"error_text_address": "Адрес кошелька должен соответствовать типу\nкриптовалюты",
|
||||
"error_text_node_address": "Пожалуйста, введите iPv4 адрес",
|
||||
"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_xmr": "Значение XMR не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 12",
|
||||
"error_text_fiat": "Значение суммы не может превышать доступный баланс.\nКоличество цифр после запятой должно быть меньше или равно 2",
|
||||
|
@ -654,5 +656,6 @@
|
|||
"generate_name": "Создать имя",
|
||||
"balance_page": "Страница баланса",
|
||||
"share": "Делиться",
|
||||
"slidable": "Скользящий"
|
||||
"slidable": "Скользящий",
|
||||
"template_name": "Имя Шаблона"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "ที่อยู่กระเป๋าจะต้องสอดคล้องกับประเภท\nของเหรียญคริปโตเนียม",
|
||||
"error_text_node_address": "โปรดป้อนที่อยู่ iPv4",
|
||||
"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_xmr": "มูลค่า XMR ไม่สามารถเกินยอดคงเหลือได้\nจำนวนสตริงทศนิยมต้องน้อยกว่าหรือเท่ากับ 12",
|
||||
"error_text_fiat": "มูลค่าของจำนวนเงินไม่สามารถเกินยอดคงเหลือได้\nจำนวนสตริงทศนิยมต้องน้อยกว่าหรือเท่ากับ 2",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "สร้างชื่อ",
|
||||
"balance_page": "หน้ายอดคงเหลือ",
|
||||
"share": "แบ่งปัน",
|
||||
"slidable": "เลื่อนได้"
|
||||
"slidable": "เลื่อนได้",
|
||||
"template_name": "ชื่อแม่แบบ"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"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_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_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",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "İsim Oluştur",
|
||||
"balance_page": "Bakiye Sayfası",
|
||||
"share": "Paylaşmak",
|
||||
"slidable": "kaydırılabilir"
|
||||
"slidable": "kaydırılabilir",
|
||||
"template_name": "şablon adı"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "Адреса гаманця повинна відповідати типу\nкриптовалюти",
|
||||
"error_text_node_address": "Будь ласка, введіть iPv4 адресу",
|
||||
"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_xmr": "Значення XMR не може перевищувати доступний баланс.\nКількість цифр після коми повинно бути меншим або дорівнювати 12",
|
||||
"error_text_fiat": "Значення суми не може перевищувати доступний баланс.\nКількість цифр після коми повинно бути меншим або дорівнювати 2",
|
||||
|
@ -654,5 +655,6 @@
|
|||
"generate_name": "Згенерувати назву",
|
||||
"balance_page": "Сторінка балансу",
|
||||
"share": "Поділіться",
|
||||
"slidable": "Розсувний"
|
||||
"slidable": "Розсувний",
|
||||
"template_name": "Назва шаблону"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "والیٹ کا پتہ cryptocurrency کی قسم\\nکے مطابق ہونا چاہیے۔",
|
||||
"error_text_node_address": "براہ کرم ایک iPv4 پتہ درج کریں۔",
|
||||
"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_xmr": "XMR قدر دستیاب بیلنس سے زیادہ نہیں ہو سکتی۔\\nفرکشن ہندسوں کی تعداد 12 سے کم یا اس کے برابر ہونی چاہیے۔",
|
||||
"error_text_fiat": "رقم کی قدر دستیاب بیلنس سے زیادہ نہیں ہو سکتی۔\\nفرکشن ہندسوں کی تعداد 2 کے برابر یا کم ہونی چاہیے۔",
|
||||
|
@ -648,5 +649,6 @@
|
|||
"generate_name": "نام پیدا کریں۔",
|
||||
"balance_page": "بیلنس صفحہ",
|
||||
"share": "بانٹیں",
|
||||
"slidable": "سلائیڈ ایبل"
|
||||
"slidable": "سلائیڈ ایبل",
|
||||
"template_name": "ٹیمپلیٹ کا نام"
|
||||
}
|
||||
|
|
|
@ -250,8 +250,8 @@
|
|||
"transaction_details_recipient_address": "Àwọn àdírẹ́sì olùgbà",
|
||||
"wallet_list_title": "Àpamọ́wọ́ Monero",
|
||||
"wallet_list_create_new_wallet": "Ṣe àpamọ́wọ́ títun",
|
||||
"wallet_list_edit_wallet": "Ṣatunkọ apamọwọ",
|
||||
"wallet_list_wallet_name": "Orukọ apamọwọ",
|
||||
"wallet_list_edit_wallet" : "Ṣatunkọ apamọwọ",
|
||||
"wallet_list_wallet_name" : "Orukọ apamọwọ",
|
||||
"wallet_list_restore_wallet": "Restore àpamọ́wọ́",
|
||||
"wallet_list_load_wallet": "Load àpamọ́wọ́",
|
||||
"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_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_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_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ì.",
|
||||
|
@ -650,5 +651,6 @@
|
|||
"generate_name": "Ṣẹda Orukọ",
|
||||
"balance_page": "Oju-iwe iwọntunwọnsi",
|
||||
"share": "Pinpin",
|
||||
"slidable": "Slidable"
|
||||
"slidable": "Slidable",
|
||||
"template_name": "Orukọ Awoṣe"
|
||||
}
|
||||
|
|
|
@ -269,6 +269,7 @@
|
|||
"error_text_address": "钱包地址必须与类型对应\n加密货币",
|
||||
"error_text_node_address": "请输入一个IPv4地址",
|
||||
"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_xmr": "XMR值不能超过可用余额.\n小数位数必须小于或等于12",
|
||||
"error_text_fiat": "金额不能超过可用余额.\n小数位数必须小于或等于2",
|
||||
|
@ -653,5 +654,6 @@
|
|||
"generate_name": "生成名称",
|
||||
"balance_page": "余额页",
|
||||
"share": "分享",
|
||||
"slidable": "可滑动"
|
||||
"slidable": "可滑动",
|
||||
"template_name": "模板名称"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue