1
mirror of https://github.com/m2049r/xmrwallet synced 2024-11-22 13:04:00 +01:00

onion stuff (#796)

This commit is contained in:
m2049r 2021-12-05 22:24:26 +01:00 committed by GitHub
parent cdc2b23257
commit 05720e63ab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
94 changed files with 2210 additions and 860 deletions

View File

@ -8,8 +8,8 @@ android {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 30
versionCode 1102 versionCode 1201
versionName "2.1.2 'Vertant'" versionName "2.2.1 'René'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@ -113,27 +113,30 @@ android {
} }
} }
def getId(name) { static def getId(name) {
def Properties props = new Properties() Properties props = new Properties()
props.load(new FileInputStream(new File('monerujo.id'))) props.load(new FileInputStream(new File('monerujo.id')))
return props[name] return props[name]
} }
dependencies { dependencies {
implementation 'androidx.core:core:1.3.2' implementation 'androidx.core:core:1.6.0'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.2.0' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.1'
implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation "com.squareup.okhttp3:okhttp:4.9.0" implementation "com.squareup.okhttp3:okhttp:4.9.0"
implementation "io.github.rburgst:okhttp-digest:2.5" implementation "io.github.rburgst:okhttp-digest:2.5"
implementation "com.jakewharton.timber:timber:4.7.1" implementation "com.jakewharton.timber:timber:4.7.1"
implementation 'info.guardianproject.netcipher:netcipher:2.1.0'
//implementation 'info.guardianproject.netcipher:netcipher-okhttp3:2.1.0'
implementation fileTree(dir: 'libs/classes', include: ['*.jar'])
implementation 'com.nulab-inc:zxcvbn:1.3.0' implementation 'com.nulab-inc:zxcvbn:1.3.0'
implementation 'dnsjava:dnsjava:2.1.9' implementation 'dnsjava:dnsjava:2.1.9'
@ -141,6 +144,7 @@ dependencies {
implementation 'org.slf4j:slf4j-nop:1.7.30' implementation 'org.slf4j:slf4j-nop:1.7.30'
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2' implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
//noinspection GradleDependency
testImplementation "junit:junit:$rootProject.ext.junitVersion" testImplementation "junit:junit:$rootProject.ext.junitVersion"
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0" testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
@ -150,3 +154,4 @@ dependencies {
compileOnly 'org.projectlombok:lombok:1.18.16' compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16' annotationProcessor 'org.projectlombok:lombok:1.18.16'
} }

View File

@ -11,6 +11,24 @@
<uses-permission android:name="android.permission.NFC" /> <uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<queries>
<intent>
<action android:name="org.torproject.android.intent.action.START" />
</intent>
<intent>
<action android:name="org.torproject.android.intent.action.STATUS" />
</intent>
<intent>
<action android:name="org.torproject.android.REQUEST_HS_PORT" />
</intent>
<intent>
<action android:name="org.torproject.android.REQUEST_V3_ONION_SERVICE" />
</intent>
<package android:name="org.torproject.android" />
</queries>
<application <application
android:name=".XmrWalletApplication" android:name=".XmrWalletApplication"
android:allowBackup="false" android:allowBackup="false"

View File

@ -531,6 +531,17 @@ Java_com_m2049r_xmrwallet_model_WalletManager_resolveOpenAlias(JNIEnv *env, jobj
return env->NewStringUTF(resolvedAlias.c_str()); return env->NewStringUTF(resolvedAlias.c_str());
} }
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_setProxy(JNIEnv *env, jobject instance,
jstring address) {
const char *_address = env->GetStringUTFChars(address, nullptr);
bool rc =
Bitmonero::WalletManagerFactory::getWalletManager()->setProxy(std::string(_address));
env->ReleaseStringUTFChars(address, _address);
return rc;
}
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir); //TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
@ -727,6 +738,16 @@ Java_com_m2049r_xmrwallet_model_Wallet_getConnectionStatusJ(JNIEnv *env, jobject
//TODO virtual void setTrustedDaemon(bool arg) = 0; //TODO virtual void setTrustedDaemon(bool arg) = 0;
//TODO virtual bool trustedDaemon() const = 0; //TODO virtual bool trustedDaemon() const = 0;
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_setProxy(JNIEnv *env, jobject instance,
jstring address) {
const char *_address = env->GetStringUTFChars(address, nullptr);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
bool rc = wallet->setProxy(std::string(_address));
env->ReleaseStringUTFChars(address, _address);
return rc;
}
JNIEXPORT jlong JNICALL JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance, Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance,
jint accountIndex) { jint accountIndex) {

View File

@ -77,7 +77,7 @@ public class Dispatcher implements PeerRetriever.OnGetPeers {
final NodeInfo nodeInfo = retrievedPeer.getNodeInfo(); final NodeInfo nodeInfo = retrievedPeer.getNodeInfo();
Timber.d("Retrieved %s", nodeInfo); Timber.d("Retrieved %s", nodeInfo);
if ((nodeInfo.isValid() || nodeInfo.isFavourite())) { if ((nodeInfo.isValid() || nodeInfo.isFavourite())) {
nodeInfo.setName(); nodeInfo.setDefaultName();
rpcNodes.add(nodeInfo); rpcNodes.add(nodeInfo);
Timber.d("RPC: %s", nodeInfo); Timber.d("RPC: %s", nodeInfo);
// the following is not totally correct but it works (otherwise we need to // the following is not totally correct but it works (otherwise we need to

View File

@ -65,6 +65,7 @@ import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.LegacyStorageHelper; import com.m2049r.xmrwallet.util.LegacyStorageHelper;
import com.m2049r.xmrwallet.util.LocaleHelper; import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper; import com.m2049r.xmrwallet.util.NightmodeHelper;
import com.m2049r.xmrwallet.util.ThemeHelper; import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.util.ZipBackup; import com.m2049r.xmrwallet.util.ZipBackup;
@ -701,6 +702,7 @@ public class LoginActivity extends BaseActivity
new AsyncWaitForService().execute(); new AsyncWaitForService().execute();
} }
if (!Ledger.isConnected()) attachLedger(); if (!Ledger.isConnected()) attachLedger();
registerTor();
} }
private class AsyncWaitForService extends AsyncTask<Void, Void, Void> { private class AsyncWaitForService extends AsyncTask<Void, Void, Void> {
@ -1469,4 +1471,69 @@ public class LoginActivity extends BaseActivity
} }
return usbManager; return usbManager;
} }
//
// Tor (Orbot) stuff
//
void torNotify() {
final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment == null) return;
if (fragment instanceof LoginFragment) {
runOnUiThread(((LoginFragment) fragment)::showNetwork);
}
}
private void deregisterTor() {
NetCipherHelper.deregister();
}
private void registerTor() {
NetCipherHelper.register(new NetCipherHelper.OnStatusChangedListener() {
@Override
public void connected() {
Timber.d("CONNECTED");
WalletManager.getInstance().setProxy(NetCipherHelper.getProxy());
torNotify();
if (waitingUiTask != null) {
Timber.d("RUN");
runOnUiThread(waitingUiTask);
waitingUiTask = null;
}
}
@Override
public void disconnected() {
Timber.d("DISCONNECTED");
WalletManager.getInstance().setProxy("");
torNotify();
}
@Override
public void notInstalled() {
Timber.d("NOT INSTALLED");
WalletManager.getInstance().setProxy("");
torNotify();
}
@Override
public void notEnabled() {
Timber.d("NOT ENABLED");
notInstalled();
}
});
}
private Runnable waitingUiTask;
@Override
public void runOnNetCipher(Runnable uiTask) {
if (waitingUiTask != null) throw new IllegalStateException("only one tor task at a time");
if (NetCipherHelper.hasClient()) {
runOnUiThread(uiTask);
} else {
waitingUiTask = uiTask;
}
}
} }

View File

@ -19,6 +19,8 @@ package com.m2049r.xmrwallet;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.text.Html;
import android.text.Spanned;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -28,22 +30,27 @@ import android.view.ViewGroup;
import android.view.animation.Animation; import android.view.animation.Animation;
import android.view.animation.AnimationUtils; import android.view.animation.AnimationUtils;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton; import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.progressindicator.CircularProgressIndicator;
import com.m2049r.xmrwallet.data.NodeInfo; import com.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.layout.NodeInfoAdapter; import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.layout.WalletInfoAdapter; import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper; import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.NodePinger; import com.m2049r.xmrwallet.util.NodePinger;
import com.m2049r.xmrwallet.util.Notice; import com.m2049r.xmrwallet.util.Notice;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
@ -66,9 +73,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
private View tvGuntherSays; private View tvGuntherSays;
private ImageView ivGunther; private ImageView ivGunther;
private TextView tvNodeName; private TextView tvNodeName;
private TextView tvNodeAddress; private TextView tvNodeInfo;
private View pbNode; private ImageButton ibNetwork;
private View llNode; private CircularProgressIndicator pbNetwork;
private Listener activityCallback; private Listener activityCallback;
@ -109,6 +116,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Set<NodeInfo> getOrPopulateFavourites(); Set<NodeInfo> getOrPopulateFavourites();
boolean hasLedger(); boolean hasLedger();
void runOnNetCipher(Runnable runnable);
} }
@Override @Override
@ -125,6 +134,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override @Override
public void onPause() { public void onPause() {
Timber.d("onPause()"); Timber.d("onPause()");
torStatus = null;
super.onPause(); super.onPause();
} }
@ -135,7 +145,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
activityCallback.setTitle(null); activityCallback.setTitle(null);
activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS); activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS);
activityCallback.showNet(); activityCallback.showNet();
pingSelectedNode(); showNetwork();
//activityCallback.runOnNetCipher(this::pingSelectedNode);
} }
@Override @Override
@ -183,12 +194,14 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
ViewGroup llNotice = view.findViewById(R.id.llNotice); ViewGroup llNotice = view.findViewById(R.id.llNotice);
Notice.showAll(llNotice, ".*_login"); Notice.showAll(llNotice, ".*_login");
pbNode = view.findViewById(R.id.pbNode); view.findViewById(R.id.llNode).setOnClickListener(v -> startNodePrefs());
llNode = view.findViewById(R.id.llNode);
llNode.setOnClickListener(v -> startNodePrefs());
tvNodeName = view.findViewById(R.id.tvNodeName); tvNodeName = view.findViewById(R.id.tvNodeName);
tvNodeAddress = view.findViewById(R.id.tvNodeAddress); tvNodeInfo = view.findViewById(R.id.tvInfo);
view.findViewById(R.id.ibRenew).setOnClickListener(v -> findBestNode()); view.findViewById(R.id.ibRenew).setOnClickListener(v -> findBestNode());
ibNetwork = view.findViewById(R.id.ibNetwork);
ibNetwork.setOnClickListener(v -> changeNetwork());
ibNetwork.setEnabled(false);
pbNetwork = view.findViewById(R.id.pbNetwork);
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
@ -387,15 +400,29 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
return nodeList.get(0); return nodeList.get(0);
} }
private void setSubtext(String status) {
final Context ctx = getContext();
final Spanned text = Html.fromHtml(ctx.getString(R.string.status,
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoGreen) & 0xFFFFFF),
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoBackground) & 0xFFFFFF),
status, ""));
tvNodeInfo.setText(text);
}
private class AsyncFindBestNode extends AsyncTask<Integer, Void, NodeInfo> { private class AsyncFindBestNode extends AsyncTask<Integer, Void, NodeInfo> {
final static int PING_SELECTED = 0; final static int PING_SELECTED = 0;
final static int FIND_BEST = 1; final static int FIND_BEST = 1;
private boolean netState;
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
pbNode.setVisibility(View.VISIBLE); tvNodeName.setVisibility(View.GONE);
llNode.setVisibility(View.INVISIBLE); pbNetwork.setVisibility(View.VISIBLE);
netState = ibNetwork.isClickable();
ibNetwork.setClickable(false);
setSubtext(getString(R.string.node_waiting));
} }
@Override @Override
@ -417,8 +444,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
} }
if (selectedNode == null) { // autoselect if (selectedNode == null) { // autoselect
selectedNode = autoselect(favourites); selectedNode = autoselect(favourites);
} else } else {
selectedNode.testRpcService(); selectedNode.testRpcService();
}
} else throw new IllegalStateException(); } else throw new IllegalStateException();
if ((selectedNode != null) && selectedNode.isValid()) { if ((selectedNode != null) && selectedNode.isValid()) {
activityCallback.setNode(selectedNode); activityCallback.setNode(selectedNode);
@ -432,16 +460,17 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override @Override
protected void onPostExecute(NodeInfo result) { protected void onPostExecute(NodeInfo result) {
if (!isAdded()) return; if (!isAdded()) return;
pbNode.setVisibility(View.INVISIBLE); tvNodeName.setVisibility(View.VISIBLE);
llNode.setVisibility(View.VISIBLE); pbNetwork.setVisibility(View.INVISIBLE);
ibNetwork.setClickable(netState);
if (result != null) { if (result != null) {
Timber.d("found a good node %s", result.toString()); Timber.d("found a good node %s", result.toString());
showNode(result); showNode(result);
} else { } else {
tvNodeName.setText(getResources().getText(R.string.node_create_hint)); tvNodeName.setText(getResources().getText(R.string.node_create_hint));
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
tvNodeAddress.setText(null); tvNodeInfo.setText(null);
tvNodeAddress.setVisibility(View.GONE); tvNodeInfo.setVisibility(View.GONE);
} }
} }
@ -453,12 +482,70 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
private void showNode(NodeInfo nodeInfo) { private void showNode(NodeInfo nodeInfo) {
tvNodeName.setText(nodeInfo.getName()); tvNodeName.setText(nodeInfo.getName());
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(NodeInfoAdapter.getPingIcon(nodeInfo), 0, 0, 0); nodeInfo.showInfo(tvNodeInfo);
Helper.showTimeDifference(tvNodeAddress, nodeInfo.getTimestamp()); tvNodeInfo.setVisibility(View.VISIBLE);
tvNodeAddress.setVisibility(View.VISIBLE);
} }
private void startNodePrefs() { private void startNodePrefs() {
activityCallback.onNodePrefs(); activityCallback.onNodePrefs();
} }
// Network (Tor) stuff
private void changeNetwork() {
Timber.d("S: %s", NetCipherHelper.getStatus());
final NetCipherHelper.Status status = NetCipherHelper.getStatus();
if (status == NetCipherHelper.Status.NOT_INSTALLED) {
HelpFragment.display(requireActivity().getSupportFragmentManager(), R.string.help_tor);
} else if (status == NetCipherHelper.Status.NOT_ENABLED) {
Toast.makeText(getActivity(), getString(R.string.tor_enable_background), Toast.LENGTH_LONG).show();
} else {
pbNetwork.setVisibility(View.VISIBLE);
ibNetwork.setEnabled(false);
NetCipherHelper.getInstance().toggle();
}
}
private NetCipherHelper.Status torStatus = null;
void showNetwork() {
final NetCipherHelper.Status status = NetCipherHelper.getStatus();
Timber.d("SHOW %s", status);
if (status == torStatus) return;
torStatus = status;
switch (status) {
case ENABLED:
ibNetwork.setImageResource(R.drawable.ic_network_tor_on);
ibNetwork.setEnabled(true);
ibNetwork.setClickable(true);
pbNetwork.setVisibility(View.INVISIBLE);
break;
case NOT_ENABLED:
case DISABLED:
ibNetwork.setImageResource(R.drawable.ic_network_clearnet);
ibNetwork.setEnabled(true);
ibNetwork.setClickable(true);
pbNetwork.setVisibility(View.INVISIBLE);
break;
case STARTING:
ibNetwork.setImageResource(R.drawable.ic_network_clearnet);
ibNetwork.setEnabled(false);
pbNetwork.setVisibility(View.VISIBLE);
break;
case STOPPING:
ibNetwork.setImageResource(R.drawable.ic_network_clearnet);
ibNetwork.setEnabled(false);
pbNetwork.setVisibility(View.VISIBLE);
break;
case NOT_INSTALLED:
ibNetwork.setEnabled(true);
ibNetwork.setClickable(true);
pbNetwork.setVisibility(View.INVISIBLE);
ibNetwork.setImageResource(R.drawable.ic_network_clearnet);
break;
default:
return;
}
activityCallback.runOnNetCipher(this::pingSelectedNode);
}
} }

View File

@ -20,7 +20,6 @@ import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@ -59,7 +58,6 @@ import java.text.NumberFormat;
import java.util.Collection; import java.util.Collection;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.Objects;
import java.util.Set; import java.util.Set;
import timber.log.Timber; import timber.log.Timber;
@ -219,8 +217,8 @@ public class NodeFragment extends Fragment
activityCallback.setNode(nodeItem); // this marks it as selected & saves it as well activityCallback.setNode(nodeItem); // this marks it as selected & saves it as well
nodeItem.setSelecting(false); nodeItem.setSelecting(false);
try { try {
Objects.requireNonNull(getActivity()).runOnUiThread(() -> nodesAdapter.allowClick(true)); requireActivity().runOnUiThread(() -> nodesAdapter.allowClick(true));
} catch (NullPointerException ex) { } catch (IllegalStateException ex) {
// it's ok // it's ok
} }
}); });
@ -403,17 +401,13 @@ public class NodeFragment extends Fragment
etNodeHost.setError(getString(R.string.node_host_empty)); etNodeHost.setError(getString(R.string.node_host_empty));
return false; return false;
} }
final boolean setHostSuccess = Helper.runWithNetwork(new Helper.Action() { final boolean setHostSuccess = Helper.runWithNetwork(() -> {
@Override
public boolean run() {
try { try {
nodeInfo.setHost(host); nodeInfo.setHost(host);
return true; return true;
} catch (UnknownHostException ex) { } catch (UnknownHostException ex) {
etNodeHost.setError(getString(R.string.node_host_unresolved));
return false; return false;
} }
}
}); });
if (!setHostSuccess) { if (!setHostSuccess) {
etNodeHost.setError(getString(R.string.node_host_unresolved)); etNodeHost.setError(getString(R.string.node_host_unresolved));
@ -421,14 +415,7 @@ public class NodeFragment extends Fragment
} }
etNodeHost.setError(null); etNodeHost.setError(null);
nodeInfo.setRpcPort(port); nodeInfo.setRpcPort(port);
// setName() may trigger reverse DNS
Helper.runWithNetwork(new Helper.Action() {
@Override
public boolean run() {
nodeInfo.setName(etNodeName.getEditText().getText().toString().trim()); nodeInfo.setName(etNodeName.getEditText().getText().toString().trim());
return true;
}
});
nodeInfo.setUsername(etNodeUser.getEditText().getText().toString().trim()); nodeInfo.setUsername(etNodeUser.getEditText().getText().toString().trim());
nodeInfo.setPassword(etNodePass.getEditText().getText().toString()); // no trim for pw nodeInfo.setPassword(etNodePass.getEditText().getText().toString()); // no trim for pw
return true; return true;
@ -532,20 +519,10 @@ public class NodeFragment extends Fragment
@Override @Override
public void onShow(final DialogInterface dialog) { public void onShow(final DialogInterface dialog) {
Button testButton = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEUTRAL); Button testButton = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEUTRAL);
testButton.setOnClickListener(new View.OnClickListener() { testButton.setOnClickListener(view -> test());
@Override
public void onClick(View view) {
test();
}
});
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE); Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(new View.OnClickListener() { button.setOnClickListener(view -> apply());
@Override
public void onClick(View view) {
apply();
}
});
} }
}); });
@ -553,15 +530,13 @@ public class NodeFragment extends Fragment
editDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE); editDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
} }
etNodePass.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etNodePass.getEditText().setOnEditorActionListener((v, actionId, event) -> {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) { if (actionId == EditorInfo.IME_ACTION_DONE) {
editDialog.getButton(DialogInterface.BUTTON_NEUTRAL).requestFocus(); editDialog.getButton(DialogInterface.BUTTON_NEUTRAL).requestFocus();
test(); test();
return true; return true;
} }
return false; return false;
}
}); });
} }
@ -589,6 +564,7 @@ public class NodeFragment extends Fragment
} else { } else {
nodesAdapter.setNodes(); nodesAdapter.setNodes();
} }
nodesAdapter.notifyItemChanged(nodeInfo);
} }
} }
} }

View File

@ -223,11 +223,13 @@ public class WalletFragment extends Fragment
} }
void showUnconfirmed(double unconfirmedAmount) { void showUnconfirmed(double unconfirmedAmount) {
if (!activityCallback.isStreetMode()) { if (activityCallback.isStreetMode() || unconfirmedAmount == 0) {
tvUnconfirmedAmount.setText(null);
tvUnconfirmedAmount.setVisibility(View.GONE);
} else {
String unconfirmed = Helper.getFormattedAmount(unconfirmedAmount, true); String unconfirmed = Helper.getFormattedAmount(unconfirmedAmount, true);
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed)); tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed));
} else { tvUnconfirmedAmount.setVisibility(View.VISIBLE);
tvUnconfirmedAmount.setText(null);
} }
} }

View File

@ -21,14 +21,17 @@ import android.content.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import com.m2049r.xmrwallet.BuildConfig; import androidx.annotation.NonNull;
import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.util.LocaleHelper; import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper; import com.m2049r.xmrwallet.util.NightmodeHelper;
import timber.log.Timber; import timber.log.Timber;
public class XmrWalletApplication extends Application { public class XmrWalletApplication extends Application {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
@ -36,7 +39,10 @@ public class XmrWalletApplication extends Application {
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
Timber.plant(new Timber.DebugTree()); Timber.plant(new Timber.DebugTree());
} }
NightmodeHelper.setPreferredNightmode(this); NightmodeHelper.setPreferredNightmode(this);
NetCipherHelper.createInstance(this);
} }
@Override @Override
@ -45,7 +51,7 @@ public class XmrWalletApplication extends Application {
} }
@Override @Override
public void onConfigurationChanged(Configuration configuration) { public void onConfigurationChanged(@NonNull Configuration configuration) {
super.onConfigurationChanged(configuration); super.onConfigurationChanged(configuration);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
LocaleHelper.updateSystemDefaultLocale(configuration.getLocales().get(0)); LocaleHelper.updateSystemDefaultLocale(configuration.getLocales().get(0));

View File

@ -28,7 +28,11 @@ public enum DefaultNodes {
SUPPORTXMR("node.supportxmr.com:18081"), SUPPORTXMR("node.supportxmr.com:18081"),
HASHVAULT("nodes.hashvault.pro:18081"), HASHVAULT("nodes.hashvault.pro:18081"),
MONEROWORLD("node.moneroworld.com:18089"), MONEROWORLD("node.moneroworld.com:18089"),
XMRTW("opennode.xmr-tw.org:18089"); XMRTW("opennode.xmr-tw.org:18089"),
MONERUJO_ONION("monerujods7mbghwe6cobdr6ujih6c22zu5rl7zshmizz2udf7v7fsad.onion:18081/mainnet/monerujo.onion"),
Criminales78("56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089/mainnet/Criminales78.onion"),
xmrfail("mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081/mainnet/xmrfail.onion"),
boldsuck("6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081/mainnet/boldsuck.onion");
@Getter @Getter
private final String uri; private final String uri;

View File

@ -18,6 +18,7 @@ package com.m2049r.xmrwallet.data;
import com.m2049r.xmrwallet.model.NetworkType; import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.OnionHelper;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.InetAddress; import java.net.InetAddress;
@ -35,11 +36,63 @@ public class Node {
static public final String STAGENET = "stagenet"; static public final String STAGENET = "stagenet";
static public final String TESTNET = "testnet"; static public final String TESTNET = "testnet";
static class Address {
final private InetAddress inet;
final private String onion;
public boolean isOnion() {
return onion != null;
}
public String getHostName() {
if (inet != null) {
return inet.getHostName();
} else {
return onion;
}
}
public String getHostAddress() {
if (inet != null) {
return inet.getHostAddress();
} else {
return onion;
}
}
private Address(InetAddress address, String onion) {
this.inet = address;
this.onion = onion;
}
static Address of(InetAddress address) {
return new Address(address, null);
}
static Address of(String host) throws UnknownHostException {
if (OnionHelper.isOnionHost(host)) {
return new Address(null, host);
} else {
return new Address(InetAddress.getByName(host), null);
}
}
@Override
public int hashCode() {
return getHostAddress().hashCode();
}
@Override
public boolean equals(Object other) {
return (other instanceof Address) && (getHostAddress().equals(((Address) other).getHostAddress()));
}
}
@Getter @Getter
private String name = null; private String name = null;
@Getter @Getter
final private NetworkType networkType; final private NetworkType networkType;
InetAddress hostAddress; Address hostAddress;
@Getter @Getter
private String host; private String host;
@Getter @Getter
@ -74,6 +127,10 @@ public class Node {
&& (networkType == anotherNode.networkType)); && (networkType == anotherNode.networkType));
} }
public boolean isOnion() {
return hostAddress.isOnion();
}
static public Node fromString(String nodeString) { static public Node fromString(String nodeString) {
try { try {
return new Node(nodeString); return new Node(nodeString);
@ -205,7 +262,7 @@ public class Node {
// constructor used for created nodes from retrieved peer lists // constructor used for created nodes from retrieved peer lists
public Node(InetSocketAddress socketAddress) { public Node(InetSocketAddress socketAddress) {
this(); this();
this.hostAddress = socketAddress.getAddress(); this.hostAddress = Address.of(socketAddress.getAddress());
this.host = socketAddress.getHostString(); this.host = socketAddress.getHostString();
this.rpcPort = 0; // unknown this.rpcPort = 0; // unknown
this.levinPort = socketAddress.getPort(); this.levinPort = socketAddress.getPort();
@ -225,17 +282,25 @@ public class Node {
if ((host == null) || (host.isEmpty())) if ((host == null) || (host.isEmpty()))
throw new UnknownHostException("loopback not supported (yet?)"); throw new UnknownHostException("loopback not supported (yet?)");
this.host = host; this.host = host;
this.hostAddress = InetAddress.getByName(host); this.hostAddress = Address.of(host);
} }
public void setName() { public void setDefaultName() {
if (name == null) if (name != null) return;
this.name = hostAddress.getHostName(); String nodeName = hostAddress.getHostName();
if (hostAddress.isOnion()) {
nodeName = nodeName.substring(0, nodeName.length() - ".onion".length());
if (nodeName.length() > 16) {
nodeName = nodeName.substring(0, 8) + "" + nodeName.substring(nodeName.length() - 6);
}
nodeName = nodeName + ".onion";
}
this.name = nodeName;
} }
public void setName(String name) { public void setName(String name) {
if ((name == null) || (name.isEmpty())) if ((name == null) || (name.isEmpty()))
this.name = hostAddress.getHostName(); setDefaultName();
else else
this.name = name; this.name = name;
} }

View File

@ -16,14 +16,19 @@
package com.m2049r.xmrwallet.data; package com.m2049r.xmrwallet.data;
import com.burgstaller.okhttp.AuthenticationCacheInterceptor; import android.content.Context;
import com.burgstaller.okhttp.CachingAuthenticatorDecorator; import android.text.Html;
import com.burgstaller.okhttp.digest.CachingAuthenticator; import android.text.Spanned;
import com.burgstaller.okhttp.digest.Credentials; import android.widget.TextView;
import com.burgstaller.okhttp.digest.DigestAuthenticator;
import androidx.core.content.ContextCompat;
import com.m2049r.levin.scanner.LevinPeer; import com.m2049r.levin.scanner.LevinPeer;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.NetCipherHelper.Request;
import com.m2049r.xmrwallet.util.NodePinger; import com.m2049r.xmrwallet.util.NodePinger;
import com.m2049r.xmrwallet.util.OkHttpHelper; import com.m2049r.xmrwallet.util.ThemeHelper;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -32,17 +37,12 @@ import java.io.IOException;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.Calendar;
import java.util.Comparator; import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.ResponseBody; import okhttp3.ResponseBody;
import timber.log.Timber; import timber.log.Timber;
@ -94,7 +94,7 @@ public class NodeInfo extends Node {
synchronized public SocketAddress getLevinSocketAddress() { synchronized public SocketAddress getLevinSocketAddress() {
if (levinSocketAddress == null) { if (levinSocketAddress == null) {
// use default peer port if not set - very few peers use nonstandard port // use default peer port if not set - very few peers use nonstandard port
levinSocketAddress = new InetSocketAddress(hostAddress, getDefaultLevinPort()); levinSocketAddress = new InetSocketAddress(hostAddress.getHostAddress(), getDefaultLevinPort());
} }
return levinSocketAddress; return levinSocketAddress;
} }
@ -180,7 +180,7 @@ public class NodeInfo extends Node {
return sb.toString(); return sb.toString();
} }
private static final int HTTP_TIMEOUT = OkHttpHelper.HTTP_TIMEOUT; private static final int HTTP_TIMEOUT = 1000; //ms
public static final double PING_GOOD = HTTP_TIMEOUT / 3.0; //ms public static final double PING_GOOD = HTTP_TIMEOUT / 3.0; //ms
public static final double PING_MEDIUM = 2 * PING_GOOD; //ms public static final double PING_MEDIUM = 2 * PING_GOOD; //ms
public static final double PING_BAD = HTTP_TIMEOUT; public static final double PING_BAD = HTTP_TIMEOUT;
@ -196,32 +196,29 @@ public class NodeInfo extends Node {
return result; return result;
} }
private boolean testRpcService(int port) { private Request rpcServiceRequest(int port) {
Timber.d("Testing %s", toNodeString()); final HttpUrl url = new HttpUrl.Builder()
clear();
try {
OkHttpClient client = OkHttpHelper.getEagerClient();
if (!getUsername().isEmpty()) {
final DigestAuthenticator authenticator =
new DigestAuthenticator(new Credentials(getUsername(), getPassword()));
final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
client = client.newBuilder()
.authenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
.addInterceptor(new AuthenticationCacheInterceptor(authCache))
.build();
}
HttpUrl url = new HttpUrl.Builder()
.scheme("http") .scheme("http")
.host(getHostAddress()) .host(getHost())
.port(port) .port(port)
.addPathSegment("json_rpc") .addPathSegment("json_rpc")
.build(); .build();
final RequestBody reqBody = RequestBody final String json = "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getlastblockheader\"}";
.create(MediaType.parse("application/json"), return new Request(url, json, getUsername(), getPassword());
"{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getlastblockheader\"}"); }
Request request = OkHttpHelper.getPostRequest(url, reqBody);
private boolean testRpcService(int port) {
Timber.d("Testing %s", toNodeString());
clear();
if (hostAddress.isOnion() && !NetCipherHelper.isTor()) {
tested = true; // sortof
responseCode = 418; // I'm a teapot - or I need an Onion - who knows
return false; // autofail
}
try {
long ta = System.nanoTime(); long ta = System.nanoTime();
try (Response response = client.newCall(request).execute()) { try (Response response = rpcServiceRequest(port).execute()) {
Timber.d("%s: %s", response.code(), response.request().url());
responseTime = (System.nanoTime() - ta) / 1000000.0; responseTime = (System.nanoTime() - ta) / 1000000.0;
responseCode = response.code(); responseCode = response.code();
if (response.isSuccessful()) { if (response.isSuccessful()) {
@ -243,7 +240,7 @@ public class NodeInfo extends Node {
} }
} }
} catch (IOException | JSONException ex) { } catch (IOException | JSONException ex) {
Timber.d(ex); Timber.d("EX: %s", ex.getMessage()); //TODO: do something here (show error?)
} finally { } finally {
tested = true; tested = true;
} }
@ -264,4 +261,43 @@ public class NodeInfo extends Node {
} }
return false; return false;
} }
static public final int STALE_NODE_HOURS = 2;
public void showInfo(TextView view, String info, boolean isError) {
final Context ctx = view.getContext();
final Spanned text = Html.fromHtml(ctx.getString(R.string.status,
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoGreen) & 0xFFFFFF),
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoBackground) & 0xFFFFFF),
(hostAddress.isOnion() ? "&nbsp;.onion&nbsp;&nbsp;" : ""), " " + info));
view.setText(text);
if (isError)
view.setTextColor(ThemeHelper.getThemedColor(ctx, R.attr.colorError));
else
view.setTextColor(ThemeHelper.getThemedColor(ctx, R.attr.colorPrimary));
}
public void showInfo(TextView view) {
if (!isTested()) {
showInfo(view, "", false);
return;
}
final Context ctx = view.getContext();
final long now = Calendar.getInstance().getTimeInMillis() / 1000;
final long secs = (now - timestamp);
final long mins = secs / 60;
final long hours = mins / 60;
final long days = hours / 24;
String info;
if (mins < 2) {
info = ctx.getString(R.string.node_updated_now, secs);
} else if (hours < 2) {
info = ctx.getString(R.string.node_updated_mins, mins);
} else if (days < 2) {
info = ctx.getString(R.string.node_updated_hours, hours);
} else {
info = ctx.getString(R.string.node_updated_days, days);
}
showInfo(view, info, hours >= STALE_NODE_HOURS);
}
} }

View File

@ -17,13 +17,17 @@
package com.m2049r.xmrwallet.dialog; package com.m2049r.xmrwallet.dialog;
import android.app.Dialog; import android.app.Dialog;
import android.content.DialogInterface; import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.text.Html; import android.text.Html;
import android.text.Spanned;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment; import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
@ -31,15 +35,20 @@ import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.NetCipherHelper;
public class HelpFragment extends DialogFragment { public class HelpFragment extends DialogFragment {
static final String TAG = "HelpFragment"; static final String TAG = "HelpFragment";
private static final String HELP_ID = "HELP_ID"; private static final String HELP_ID = "HELP_ID";
private static final String TOR_BUTTON = "TOR";
public static HelpFragment newInstance(int helpResourceId) { public static HelpFragment newInstance(int helpResourceId) {
HelpFragment fragment = new HelpFragment(); HelpFragment fragment = new HelpFragment();
Bundle bundle = new Bundle(); Bundle bundle = new Bundle();
bundle.putInt(HELP_ID, helpResourceId); bundle.putInt(HELP_ID, helpResourceId);
// a hack for the tor button
if (helpResourceId == R.string.help_tor)
bundle.putInt(TOR_BUTTON, 7);
fragment.setArguments(bundle); fragment.setArguments(bundle);
return fragment; return fragment;
} }
@ -54,27 +63,53 @@ public class HelpFragment extends DialogFragment {
HelpFragment.newInstance(helpResourceId).show(ft, TAG); HelpFragment.newInstance(helpResourceId).show(ft, TAG);
} }
private Spanned getHtml(String html, double textSize) {
final Html.ImageGetter imageGetter = source -> {
final int imageId = getResources().getIdentifier(source.replace("/", ""), "drawable", requireActivity().getPackageName());
// Don't die if we don't find the image - use a heart instead
final Drawable drawable = ContextCompat.getDrawable(requireActivity(), imageId > 0 ? imageId : R.drawable.ic_favorite_24dp);
final double f = textSize / drawable.getIntrinsicHeight();
drawable.setBounds(0, 0, (int) (f * drawable.getIntrinsicWidth()), (int) textSize);
return drawable;
};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
return Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY, imageGetter, null);
} else {
return Html.fromHtml(html, imageGetter, null);
}
}
@NonNull
@Override @Override
public Dialog onCreateDialog(Bundle savedInstanceState) { public Dialog onCreateDialog(Bundle savedInstanceState) {
final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_help, null); final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_help, null);
int helpId = 0; int helpId = 0;
boolean torButton = false;
Bundle arguments = getArguments(); Bundle arguments = getArguments();
if (arguments != null) { if (arguments != null) {
helpId = arguments.getInt(HELP_ID); helpId = arguments.getInt(HELP_ID);
torButton = arguments.getInt(TOR_BUTTON) > 0;
} }
final TextView helpTv = view.findViewById(R.id.tvHelp);
if (helpId > 0) if (helpId > 0)
((TextView) view.findViewById(R.id.tvHelp)).setText(Html.fromHtml(getString(helpId))); helpTv.setText(getHtml(getString(helpId), helpTv.getTextSize()));
MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(getActivity()) MaterialAlertDialogBuilder builder =
.setView(view) new MaterialAlertDialogBuilder(requireActivity())
.setNegativeButton(R.string.help_ok, .setView(view);
new DialogInterface.OnClickListener() { if (torButton) {
@Override builder.setNegativeButton(R.string.help_nok,
public void onClick(DialogInterface dialog, int id) { (dialog, id) -> dialog.dismiss())
.setPositiveButton(R.string.help_getorbot,
(dialog, id) -> {
dialog.dismiss(); dialog.dismiss();
} NetCipherHelper.getInstance().installOrbot(requireActivity());
}); });
} else {
builder.setNegativeButton(R.string.help_ok,
(dialog, id) -> dialog.dismiss());
}
return builder.create(); return builder.create();
} }
} }

View File

@ -22,6 +22,7 @@ import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.Html; import android.text.Html;
import android.text.InputType; import android.text.InputType;
import android.text.Spanned;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Patterns; import android.util.Patterns;
import android.view.KeyEvent; import android.view.KeyEvent;
@ -91,6 +92,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private TextInputLayout etAddress; private TextInputLayout etAddress;
private TextInputLayout etNotes; private TextInputLayout etNotes;
private TextView tvXmrTo; private TextView tvXmrTo;
private TextView tvTor;
private Map<Crypto, ImageButton> ibCrypto; private Map<Crypto, ImageButton> ibCrypto;
final private Set<Crypto> possibleCryptos = new HashSet<>(); final private Set<Crypto> possibleCryptos = new HashSet<>();
private Crypto selectedCrypto = null; private Crypto selectedCrypto = null;
@ -117,11 +119,12 @@ public class SendAddressWizardFragment extends SendWizardFragment {
View view = inflater.inflate(R.layout.fragment_send_address, container, false); View view = inflater.inflate(R.layout.fragment_send_address, container, false);
if (Helper.ALLOW_SHIFT) { tvTor = view.findViewById(R.id.tvTor);
tvXmrTo = view.findViewById(R.id.tvXmrTo); tvXmrTo = view.findViewById(R.id.tvXmrTo);
ibCrypto = new HashMap<>(); ibCrypto = new HashMap<>();
for (Crypto crypto : Crypto.values()) { for (Crypto crypto : Crypto.values()) {
final ImageButton button = view.findViewById(crypto.getButtonId()); final ImageButton button = view.findViewById(crypto.getButtonId());
if (Helper.ALLOW_SHIFT || (crypto == Crypto.XMR)) {
ibCrypto.put(crypto, button); ibCrypto.put(crypto, button);
button.setOnClickListener(v -> { button.setOnClickListener(v -> {
if (possibleCryptos.contains(crypto)) { if (possibleCryptos.contains(crypto)) {
@ -137,14 +140,21 @@ public class SendAddressWizardFragment extends SendWizardFragment {
} else { } else {
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help_xmr))); tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help_xmr)));
tvXmrTo.setVisibility(View.VISIBLE); tvXmrTo.setVisibility(View.VISIBLE);
tvTor.setVisibility(View.INVISIBLE);
} }
} }
}); });
} else {
button.setImageResource(crypto.getIconDisabledId());
button.setImageAlpha(128);
button.setEnabled(false);
}
}
if (!Helper.ALLOW_SHIFT) {
tvTor.setVisibility(View.VISIBLE);
} }
updateCryptoButtons(true); updateCryptoButtons(true);
} else {
view.findViewById(R.id.llExchange).setVisibility(View.GONE);
}
etAddress = view.findViewById(R.id.etAddress); etAddress = view.findViewById(R.id.etAddress);
etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etAddress.getEditText().setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() { etAddress.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
@ -455,6 +465,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
Timber.d("BUT ONLY XMR SUPPORTED"); Timber.d("BUT ONLY XMR SUPPORTED");
barcodeData = null; barcodeData = null;
sendListener.setBarcodeData(barcodeData); sendListener.setBarcodeData(barcodeData);
return;
} }
if (barcodeData.address != null) { if (barcodeData.address != null) {
etAddress.getEditText().setText(barcodeData.address); etAddress.getEditText().setText(barcodeData.address);

View File

@ -35,7 +35,6 @@ import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl; import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.util.ServiceHelper; import com.m2049r.xmrwallet.util.ServiceHelper;
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText; import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
import com.m2049r.xmrwallet.widget.SendProgressView; import com.m2049r.xmrwallet.widget.SendProgressView;
@ -255,8 +254,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
if (xmrToApi == null) { if (xmrToApi == null) {
synchronized (this) { synchronized (this) {
if (xmrToApi == null) { if (xmrToApi == null) {
xmrToApi = new SideShiftApiImpl(OkHttpHelper.getOkHttpClient(), xmrToApi = new SideShiftApiImpl(ServiceHelper.getXmrToBaseUrl());
ServiceHelper.getXmrToBaseUrl());
} }
} }
} }

View File

@ -37,7 +37,6 @@ import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl; import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.util.ServiceHelper; import com.m2049r.xmrwallet.util.ServiceHelper;
import com.m2049r.xmrwallet.widget.SendProgressView; import com.m2049r.xmrwallet.widget.SendProgressView;
@ -543,8 +542,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
if (xmrToApi == null) { if (xmrToApi == null) {
synchronized (this) { synchronized (this) {
if (xmrToApi == null) { if (xmrToApi == null) {
xmrToApi = new SideShiftApiImpl(OkHttpHelper.getOkHttpClient(), xmrToApi = new SideShiftApiImpl(ServiceHelper.getXmrToBaseUrl());
ServiceHelper.getXmrToBaseUrl());
} }
} }
} }

View File

@ -39,7 +39,6 @@ import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderStatus;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl; import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.util.ServiceHelper; import com.m2049r.xmrwallet.util.ServiceHelper;
import java.text.NumberFormat; import java.text.NumberFormat;
@ -245,8 +244,7 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
if (xmrToApi == null) { if (xmrToApi == null) {
synchronized (this) { synchronized (this) {
if (xmrToApi == null) { if (xmrToApi == null) {
xmrToApi = new SideShiftApiImpl(OkHttpHelper.getOkHttpClient(), xmrToApi = new SideShiftApiImpl(ServiceHelper.getXmrToBaseUrl());
ServiceHelper.getXmrToBaseUrl());
} }
} }
} }

View File

@ -23,28 +23,25 @@ import android.view.ViewGroup;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.NodeInfo; import com.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.util.ThemeHelper; import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.NetCipherHelper;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.text.SimpleDateFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.TimeZone;
public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHolder> { public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHolder> {
private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public interface OnInteractionListener { public interface OnInteractionListener {
void onInteraction(View view, NodeInfo item); void onInteraction(View view, NodeInfo item);
@ -54,14 +51,16 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
private final List<NodeInfo> nodeItems = new ArrayList<>(); private final List<NodeInfo> nodeItems = new ArrayList<>();
private final OnInteractionListener listener; private final OnInteractionListener listener;
private final Context context; private final FragmentActivity activity;
public NodeInfoAdapter(Context context, OnInteractionListener listener) { public NodeInfoAdapter(FragmentActivity activity, OnInteractionListener listener) {
this.context = context; this.activity = activity;
this.listener = listener; this.listener = listener;
Calendar cal = Calendar.getInstance(); }
TimeZone tz = cal.getTimeZone(); //get the local time zone.
TS_FORMATTER.setTimeZone(tz); public void notifyItemChanged(NodeInfo nodeInfo) {
final int pos = nodeItems.indexOf(nodeInfo);
if (pos >= 0) notifyItemChanged(pos);
} }
private static class NodeDiff extends DiffCallback<NodeInfo> { private static class NodeDiff extends DiffCallback<NodeInfo> {
@ -142,7 +141,7 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
final ImageButton ibBookmark; final ImageButton ibBookmark;
final View pbBookmark; final View pbBookmark;
final TextView tvName; final TextView tvName;
final TextView tvIp; final TextView tvInfo;
final ImageView ivPing; final ImageView ivPing;
NodeInfo nodeItem; NodeInfo nodeItem;
@ -151,7 +150,7 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
ibBookmark = itemView.findViewById(R.id.ibBookmark); ibBookmark = itemView.findViewById(R.id.ibBookmark);
pbBookmark = itemView.findViewById(R.id.pbBookmark); pbBookmark = itemView.findViewById(R.id.pbBookmark);
tvName = itemView.findViewById(R.id.tvName); tvName = itemView.findViewById(R.id.tvName);
tvIp = itemView.findViewById(R.id.tvAddress); tvInfo = itemView.findViewById(R.id.tvInfo);
ivPing = itemView.findViewById(R.id.ivPing); ivPing = itemView.findViewById(R.id.ivPing);
ibBookmark.setOnClickListener(v -> { ibBookmark.setOnClickListener(v -> {
nodeItem.toggleFavourite(); nodeItem.toggleFavourite();
@ -179,13 +178,12 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
ivPing.setImageResource(getPingIcon(nodeItem)); ivPing.setImageResource(getPingIcon(nodeItem));
if (nodeItem.isTested()) { if (nodeItem.isTested()) {
if (nodeItem.isValid()) { if (nodeItem.isValid()) {
Helper.showTimeDifference(tvIp, nodeItem.getTimestamp()); nodeItem.showInfo(tvInfo);
} else { } else {
tvIp.setText(getResponseErrorText(context, nodeItem.getResponseCode())); nodeItem.showInfo(tvInfo, getResponseErrorText(activity, nodeItem.getResponseCode()), true);
tvIp.setTextColor(ThemeHelper.getThemedColor(context, R.attr.colorError));
} }
} else { } else {
tvIp.setText(context.getResources().getString(R.string.node_testing, nodeItem.getHostAddress())); nodeItem.showInfo(tvInfo);
} }
itemView.setSelected(nodeItem.isSelected()); itemView.setSelected(nodeItem.isSelected());
itemView.setClickable(itemsClickable); itemView.setClickable(itemsClickable);
@ -201,6 +199,16 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
int position = getAdapterPosition(); // gets item position int position = getAdapterPosition(); // gets item position
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
final NodeInfo node = nodeItems.get(position); final NodeInfo node = nodeItems.get(position);
if (node.isOnion()) {
switch (NetCipherHelper.getStatus()) {
case NOT_INSTALLED:
HelpFragment.display(activity.getSupportFragmentManager(), R.string.help_tor);
return;
case DISABLED:
HelpFragment.display(activity.getSupportFragmentManager(), R.string.help_tor_enable);
return;
}
}
node.setSelecting(true); node.setSelecting(true);
allowClick(false); allowClick(false);
listener.onInteraction(view, node); listener.onInteraction(view, node);
@ -245,6 +253,8 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
return ctx.getResources().getString(R.string.node_general_error); return ctx.getResources().getString(R.string.node_general_error);
} else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) { } else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
return ctx.getResources().getString(R.string.node_auth_error); return ctx.getResources().getString(R.string.node_auth_error);
} else if (responseCode == 418) {
return ctx.getResources().getString(R.string.node_tor_error);
} else { } else {
return ctx.getResources().getString(R.string.node_test_error, responseCode); return ctx.getResources().getString(R.string.node_test_error, responseCode);
} }

View File

@ -152,7 +152,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final ImageView ivTxType; final ImageView ivTxType;
final TextView tvAmount; final TextView tvAmount;
final TextView tvFee; final TextView tvFailed;
final TextView tvPaymentId; final TextView tvPaymentId;
final TextView tvDateTime; final TextView tvDateTime;
final CircularProgressIndicator pbConfirmations; final CircularProgressIndicator pbConfirmations;
@ -163,7 +163,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
super(itemView); super(itemView);
ivTxType = itemView.findViewById(R.id.ivTxType); ivTxType = itemView.findViewById(R.id.ivTxType);
tvAmount = itemView.findViewById(R.id.tx_amount); tvAmount = itemView.findViewById(R.id.tx_amount);
tvFee = itemView.findViewById(R.id.tx_fee); tvFailed = itemView.findViewById(R.id.tx_failed);
tvPaymentId = itemView.findViewById(R.id.tx_paymentid); tvPaymentId = itemView.findViewById(R.id.tx_paymentid);
tvDateTime = itemView.findViewById(R.id.tx_datetime); tvDateTime = itemView.findViewById(R.id.tx_datetime);
pbConfirmations = itemView.findViewById(R.id.pbConfirmations); pbConfirmations = itemView.findViewById(R.id.pbConfirmations);
@ -203,18 +203,10 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
tvAmount.setText(context.getString(R.string.tx_list_amount_positive, displayAmount)); tvAmount.setText(context.getString(R.string.tx_list_amount_positive, displayAmount));
} }
if ((infoItem.fee > 0)) { tvFailed.setVisibility(View.GONE);
String fee = Helper.getDisplayAmount(infoItem.fee, Helper.DISPLAY_DIGITS_INFO);
tvFee.setText(context.getString(R.string.tx_list_fee, fee));
tvFee.setVisibility(View.VISIBLE);
} else {
tvFee.setText("");
tvFee.setVisibility(View.GONE);
}
if (infoItem.isFailed) { if (infoItem.isFailed) {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, displayAmount)); this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, displayAmount));
this.tvFee.setText(context.getString(R.string.tx_list_failed_text)); tvFailed.setVisibility(View.VISIBLE);
tvFee.setVisibility(View.VISIBLE);
setTxColour(failedColour); setTxColour(failedColour);
pbConfirmations.setVisibility(View.GONE); pbConfirmations.setVisibility(View.GONE);
tvConfirmations.setVisibility(View.GONE); tvConfirmations.setVisibility(View.GONE);

View File

@ -75,7 +75,7 @@ public class Wallet {
@Override @Override
@NonNull @NonNull
public String toString() { public String toString() {
return "Wallet.Status: (" + status + "/" + errorString + ", " + connectionStatus; return "Wallet.Status: " + status + "/" + errorString + "/" + connectionStatus;
} }
} }
@ -247,6 +247,8 @@ public class Wallet {
//TODO virtual void setTrustedDaemon(bool arg) = 0; //TODO virtual void setTrustedDaemon(bool arg) = 0;
//TODO virtual bool trustedDaemon() const = 0; //TODO virtual bool trustedDaemon() const = 0;
public native boolean setProxy(String address);
public long getBalance() { public long getBalance() {
return getBalance(accountIndex); return getBalance(accountIndex);
} }

View File

@ -309,6 +309,8 @@ public class WalletManager {
public native String resolveOpenAlias(String address, boolean dnssec_valid); public native String resolveOpenAlias(String address, boolean dnssec_valid);
public native boolean setProxy(String address);
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir); //TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
static public native void initLogger(String argv0, String defaultLogBaseName); static public native void initLogger(String argv0, String defaultLogBaseName);

View File

@ -45,6 +45,7 @@ import com.m2049r.xmrwallet.model.WalletListener;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.LocaleHelper; import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import timber.log.Timber; import timber.log.Timber;
@ -531,6 +532,7 @@ public class WalletService extends Service {
Timber.d("Using daemon %s", WalletManager.getInstance().getDaemonAddress()); Timber.d("Using daemon %s", WalletManager.getInstance().getDaemonAddress());
showProgress(55); showProgress(55);
wallet.init(0); wallet.init(0);
wallet.setProxy(NetCipherHelper.getProxy());
showProgress(90); showProgress(90);
} }
return wallet; return wallet;

View File

@ -25,6 +25,7 @@ import com.m2049r.xmrwallet.service.exchange.api.ExchangeApi;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback; import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeException; import com.m2049r.xmrwallet.service.exchange.api.ExchangeException;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import org.w3c.dom.Document; import org.w3c.dom.Document;
import org.w3c.dom.Element; import org.w3c.dom.Element;
@ -48,26 +49,21 @@ import javax.xml.parsers.ParserConfigurationException;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber; import timber.log.Timber;
public class ExchangeApiImpl implements ExchangeApi { public class ExchangeApiImpl implements ExchangeApi {
@NonNull
private final OkHttpClient okHttpClient;
@NonNull @NonNull
private final HttpUrl baseUrl; private final HttpUrl baseUrl;
//so we can inject the mockserver url //so we can inject the mockserver url
@VisibleForTesting @VisibleForTesting
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, @NonNull final HttpUrl baseUrl) { public ExchangeApiImpl(@NonNull final HttpUrl baseUrl) {
this.okHttpClient = okHttpClient;
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
} }
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) { public ExchangeApiImpl() {
this(okHttpClient, HttpUrl.parse("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml")); this(HttpUrl.parse("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"));
// data is daily and is refreshed around 16:00 CET every working day // data is daily and is refreshed around 16:00 CET every working day
} }
@ -122,9 +118,8 @@ public class ExchangeApiImpl implements ExchangeApi {
} }
} }
final Request httpRequest = createHttpRequest(baseUrl); final NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(baseUrl);
httpRequest.enqueue(new okhttp3.Callback() {
okHttpClient.newCall(httpRequest).enqueue(new okhttp3.Callback() {
@Override @Override
public void onFailure(final Call call, final IOException ex) { public void onFailure(final Call call, final IOException ex) {
callback.onError(ex); callback.onError(ex);
@ -155,13 +150,6 @@ public class ExchangeApiImpl implements ExchangeApi {
}); });
} }
private Request createHttpRequest(final HttpUrl url) {
return new Request.Builder()
.url(url)
.get()
.build();
}
final private Map<String, Double> fxEntries = new HashMap<>(); final private Map<String, Double> fxEntries = new HashMap<>();
private Calendar fxDate = null; private Calendar fxDate = null;
private Calendar fetchDate = null; private Calendar fetchDate = null;

View File

@ -24,6 +24,7 @@ import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeException; import com.m2049r.xmrwallet.service.exchange.api.ExchangeException;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
@ -33,27 +34,21 @@ import java.io.IOException;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber; import timber.log.Timber;
public class ExchangeApiImpl implements ExchangeApi { public class ExchangeApiImpl implements ExchangeApi {
@NonNull
private final OkHttpClient okHttpClient;
private final HttpUrl baseUrl; private final HttpUrl baseUrl;
//so we can inject the mockserver url //so we can inject the mockserver url
@VisibleForTesting @VisibleForTesting
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, final HttpUrl baseUrl) { public ExchangeApiImpl(final HttpUrl baseUrl) {
this.okHttpClient = okHttpClient;
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
} }
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) { public ExchangeApiImpl() {
this(okHttpClient, HttpUrl.parse("https://api.kraken.com/0/public/Ticker")); this(HttpUrl.parse("https://api.kraken.com/0/public/Ticker"));
} }
@Override @Override
@ -86,9 +81,8 @@ public class ExchangeApiImpl implements ExchangeApi {
.addQueryParameter("pair", base + (quote.equals("BTC") ? "XBT" : quote)) .addQueryParameter("pair", base + (quote.equals("BTC") ? "XBT" : quote))
.build(); .build();
final Request httpRequest = createHttpRequest(url); final NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(url);
httpRequest.enqueue(new okhttp3.Callback() {
okHttpClient.newCall(httpRequest).enqueue(new okhttp3.Callback() {
@Override @Override
public void onFailure(final Call call, final IOException ex) { public void onFailure(final Call call, final IOException ex) {
callback.onError(ex); callback.onError(ex);
@ -127,11 +121,4 @@ public class ExchangeApiImpl implements ExchangeApi {
callback.onError(ex); callback.onError(ex);
} }
} }
private Request createHttpRequest(final HttpUrl url) {
return new Request.Builder()
.url(url)
.get()
.build();
}
} }

View File

@ -35,13 +35,6 @@ import timber.log.Timber;
public class ExchangeApiImpl implements ExchangeApi { public class ExchangeApiImpl implements ExchangeApi {
static public final String BASE_FIAT = "EUR"; static public final String BASE_FIAT = "EUR";
@NonNull
private final OkHttpClient okHttpClient;
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
this.okHttpClient = okHttpClient;
}
@Override @Override
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency, public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback) { @NonNull final ExchangeCallback callback) {
@ -61,13 +54,13 @@ public class ExchangeApiImpl implements ExchangeApi {
final String quote = Helper.BASE_CRYPTO.equals(baseCurrency) ? quoteCurrency : baseCurrency; final String quote = Helper.BASE_CRYPTO.equals(baseCurrency) ? quoteCurrency : baseCurrency;
final ExchangeApi krakenApi = final ExchangeApi krakenApi =
new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl(okHttpClient); new com.m2049r.xmrwallet.service.exchange.kraken.ExchangeApiImpl();
krakenApi.queryExchangeRate(Helper.BASE_CRYPTO, BASE_FIAT, new ExchangeCallback() { krakenApi.queryExchangeRate(Helper.BASE_CRYPTO, BASE_FIAT, new ExchangeCallback() {
@Override @Override
public void onSuccess(final ExchangeRate krakenRate) { public void onSuccess(final ExchangeRate krakenRate) {
Timber.d("kraken = %f", krakenRate.getRate()); Timber.d("kraken = %f", krakenRate.getRate());
final ExchangeApi ecbApi = final ExchangeApi ecbApi =
new com.m2049r.xmrwallet.service.exchange.ecb.ExchangeApiImpl(okHttpClient); new com.m2049r.xmrwallet.service.exchange.ecb.ExchangeApiImpl();
ecbApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() { ecbApi.queryExchangeRate(BASE_FIAT, quote, new ExchangeCallback() {
@Override @Override
public void onSuccess(final ExchangeRate ecbRate) { public void onSuccess(final ExchangeRate ecbRate) {

View File

@ -30,7 +30,7 @@ import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters;
import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderStatus; import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderStatus;
import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote; import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.util.OkHttpHelper; import com.m2049r.xmrwallet.util.NetCipherHelper;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
@ -39,22 +39,14 @@ import java.io.IOException;
import okhttp3.Call; import okhttp3.Call;
import okhttp3.HttpUrl; import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response; import okhttp3.Response;
import timber.log.Timber; import timber.log.Timber;
public class SideShiftApiImpl implements SideShiftApi, ShiftApiCall { public class SideShiftApiImpl implements SideShiftApi, ShiftApiCall {
@NonNull
private final OkHttpClient okHttpClient;
private final HttpUrl baseUrl; private final HttpUrl baseUrl;
public SideShiftApiImpl(@NonNull final OkHttpClient okHttpClient, final HttpUrl baseUrl) { public SideShiftApiImpl(final HttpUrl baseUrl) {
this.okHttpClient = okHttpClient;
this.baseUrl = baseUrl; this.baseUrl = baseUrl;
} }
@ -95,9 +87,9 @@ public class SideShiftApiImpl implements SideShiftApi, ShiftApiCall {
final HttpUrl url = baseUrl.newBuilder() final HttpUrl url = baseUrl.newBuilder()
.addPathSegments(path) .addPathSegments(path)
.build(); .build();
final Request httpRequest = createHttpRequest(request, url);
okHttpClient.newCall(httpRequest).enqueue(new okhttp3.Callback() { NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(url, request);
httpRequest.enqueue(new okhttp3.Callback() {
@Override @Override
public void onFailure(final Call call, final IOException ex) { public void onFailure(final Call call, final IOException ex) {
callback.onError(ex); callback.onError(ex);
@ -127,13 +119,4 @@ public class SideShiftApiImpl implements SideShiftApi, ShiftApiCall {
} }
}); });
} }
private Request createHttpRequest(final JSONObject request, final HttpUrl url) {
if (request != null) {
final RequestBody body = RequestBody.create(request.toString(), MediaType.parse("application/json"));
return OkHttpHelper.getPostRequest(url, body);
} else {
return OkHttpHelper.getGetRequest(url);
}
}
} }

View File

@ -84,7 +84,7 @@ public class Helper {
static public final long ONE_XMR = Math.round(Math.pow(10, Helper.XMR_DECIMALS)); static public final long ONE_XMR = Math.round(Math.pow(10, Helper.XMR_DECIMALS));
static public final boolean SHOW_EXCHANGERATES = true; static public final boolean SHOW_EXCHANGERATES = true;
static public final boolean ALLOW_SHIFT = true; static public boolean ALLOW_SHIFT = false;
static private final String WALLET_DIR = "wallets"; static private final String WALLET_DIR = "wallets";
static private final String MONERO_DIR = "monero"; static private final String MONERO_DIR = "monero";
@ -606,30 +606,4 @@ public class Helper {
static public boolean preventScreenshot() { static public boolean preventScreenshot() {
return !(BuildConfig.DEBUG || BuildConfig.FLAVOR_type.equals("alpha")); return !(BuildConfig.DEBUG || BuildConfig.FLAVOR_type.equals("alpha"));
} }
static public final int STALE_NODE_HOURS = 2;
static public void showTimeDifference(TextView view, long timeInSeconds) {
final Context ctx = view.getContext();
final long now = Calendar.getInstance().getTimeInMillis() / 1000;
final long secs = (now - timeInSeconds);
final long mins = secs / 60; // in minutes
final long hours = mins / 60;
final long days = hours / 24;
String msg;
if (mins < 2) {
msg = ctx.getString(R.string.node_updated_now, secs);
} else if (hours < 2) {
msg = ctx.getString(R.string.node_updated_mins, mins);
} else if (days < 2) {
msg = ctx.getString(R.string.node_updated_hours, hours);
} else {
msg = ctx.getString(R.string.node_updated_days, days);
}
view.setText(msg);
if (hours >= STALE_NODE_HOURS)
view.setTextColor(ThemeHelper.getThemedColor(view.getContext(), R.attr.colorError));
else
view.setTextColor(ThemeHelper.getThemedColor(view.getContext(), android.R.attr.textColorPrimary));
}
} }

View File

@ -0,0 +1,389 @@
/*
* Copyright (c) 2021 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import com.burgstaller.okhttp.AuthenticationCacheInterceptor;
import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.Credentials;
import com.burgstaller.okhttp.digest.DigestAuthenticator;
import org.json.JSONObject;
import java.io.IOException;
import java.net.Proxy;
import java.util.ArrayList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import info.guardianproject.netcipher.client.StrongOkHttpClientBuilder;
import info.guardianproject.netcipher.proxy.OrbotHelper;
import info.guardianproject.netcipher.proxy.SignatureUtils;
import info.guardianproject.netcipher.proxy.StatusCallback;
import lombok.RequiredArgsConstructor;
import lombok.ToString;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.RequestBody;
import okhttp3.Response;
import timber.log.Timber;
@RequiredArgsConstructor
public class NetCipherHelper implements StatusCallback {
public static final String USER_AGENT = "Monerujo/1.0";
public static final int HTTP_TIMEOUT = 1000; //ms
public static final int TOR_TIMEOUT_CONNECT = 5000; //ms
public static final int TOR_TIMEOUT = 2000; //ms
public interface OnStatusChangedListener {
void connected();
void disconnected();
void notInstalled();
void notEnabled();
}
final private Context context;
final private OrbotHelper orbot;
@SuppressLint("StaticFieldLeak")
private static NetCipherHelper Instance;
public static void createInstance(Context context) {
if (Instance == null) {
synchronized (NetCipherHelper.class) {
if (Instance == null) {
final Context applicationContext = context.getApplicationContext();
Instance = new NetCipherHelper(applicationContext, OrbotHelper.get(context).statusTimeout(5000));
}
}
}
}
public static NetCipherHelper getInstance() {
if (Instance == null) throw new IllegalStateException("NetCipherHelper is null");
return Instance;
}
private OkHttpClient client;
private void createTorClient(Intent statusIntent) {
String orbotStatus = statusIntent.getStringExtra(OrbotHelper.EXTRA_STATUS);
if (orbotStatus == null) throw new IllegalStateException("status is null");
if (!orbotStatus.equals(OrbotHelper.STATUS_ON))
throw new IllegalStateException("Orbot is not ON");
try {
final OkHttpClient.Builder okBuilder = new OkHttpClient.Builder()
.connectTimeout(TOR_TIMEOUT_CONNECT, TimeUnit.MILLISECONDS)
.writeTimeout(TOR_TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(TOR_TIMEOUT, TimeUnit.MILLISECONDS);
client = new StrongOkHttpClientBuilder(context)
.withSocksProxy()
.applyTo(okBuilder, statusIntent)
.build();
Helper.ALLOW_SHIFT = false; // no shifting with Tor
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private void createClearnetClient() {
try {
client = new OkHttpClient.Builder()
.connectTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
.build();
Helper.ALLOW_SHIFT = true;
} catch (Exception ex) {
throw new IllegalStateException(ex);
}
}
private OnStatusChangedListener onStatusChangedListener;
public static void deregister() {
getInstance().onStatusChangedListener = null;
}
public static void register(OnStatusChangedListener listener) {
final NetCipherHelper me = getInstance();
me.onStatusChangedListener = listener;
// NOT_INSTALLED is dealt with through the callbacks
me.orbot.removeStatusCallback(me) // make sure we are registered just once
.addStatusCallback(me);
// deal with org.torproject.android.intent.action.STATUS = STARTS_DISABLED
me.context.registerReceiver(orbotStatusReceiver, new IntentFilter(OrbotHelper.ACTION_STATUS));
me.startTor();
}
// for StatusCallback
public enum Status {
STARTING,
ENABLED,
STOPPING,
DISABLED,
NOT_INSTALLED,
NOT_ENABLED,
UNKNOWN;
}
private Status status = Status.UNKNOWN;
@Override
public void onStarting() {
Timber.d("onStarting");
status = Status.STARTING;
}
@Override
public void onEnabled(Intent statusIntent) {
Timber.d("onEnabled");
if (getTorPref() != Status.ENABLED) return; // do we want Tor?
createTorClient(statusIntent);
status = Status.ENABLED;
if (onStatusChangedListener != null) {
new Thread(() -> onStatusChangedListener.connected()).start();
}
}
@Override
public void onStopping() {
Timber.d("onStopping");
status = Status.STOPPING;
}
@Override
public void onDisabled() {
Timber.d("onDisabled");
createClearnetClient();
status = Status.DISABLED;
if (onStatusChangedListener != null) {
new Thread(() -> onStatusChangedListener.disconnected()).start();
}
}
@Override
public void onStatusTimeout() {
Timber.d("onStatusTimeout");
createClearnetClient();
// (timeout does not not change the status)
if (onStatusChangedListener != null) {
new Thread(() -> onStatusChangedListener.disconnected()).start();
}
orbotInit = false; // do init() next time we try to open Tor
}
@Override
public void onNotYetInstalled() {
Timber.d("onNotYetInstalled");
// never mind then
orbot.removeStatusCallback(this);
createClearnetClient();
status = Status.NOT_INSTALLED;
if (onStatusChangedListener != null) {
new Thread(() -> onStatusChangedListener.notInstalled()).start();
}
}
// user has not enabled background Orbot starts
public void onNotEnabled() {
Timber.d("onNotEnabled");
// keep the callback in case they turn it on manually
setTorPref(Status.DISABLED);
createClearnetClient();
status = Status.NOT_ENABLED;
if (onStatusChangedListener != null) {
new Thread(() -> onStatusChangedListener.notEnabled()).start();
}
}
static public Status getStatus() {
return getInstance().status;
}
public void toggle() {
switch (getStatus()) {
case ENABLED:
onDisabled();
setTorPref(Status.DISABLED);
break;
case DISABLED:
setTorPref(Status.ENABLED);
startTor();
break;
}
}
private boolean orbotInit = false;
private void startTor() {
if (!isOrbotInstalled()) {
onNotYetInstalled();
} else if (getTorPref() == Status.DISABLED) {
onDisabled();
} else if (!orbotInit) {
orbotInit = orbot.init();
} else {
orbot.requestStart(context);
}
}
// extracted from OrbotHelper
private boolean isOrbotInstalled() {
ArrayList<String> hashes = new ArrayList<>();
// Tor Project signing key
hashes.add("A4:54:B8:7A:18:47:A8:9E:D7:F5:E7:0F:BA:6B:BA:96:F3:EF:29:C2:6E:09:81:20:4F:E3:47:BF:23:1D:FD:5B");
// f-droid.org signing key
hashes.add("A7:02:07:92:4F:61:FF:09:37:1D:54:84:14:5C:4B:EE:77:2C:55:C1:9E:EE:23:2F:57:70:E1:82:71:F7:CB:AE");
return null != SignatureUtils.validateBroadcastIntent(context,
OrbotHelper.getOrbotStartIntent(context),
hashes, false);
}
static public boolean hasClient() {
return getInstance().client != null;
}
static public boolean isTor() {
return getStatus() == Status.ENABLED;
}
static public String getProxy() {
if (!isTor()) return "";
final Proxy proxy = getInstance().client.proxy();
if (proxy == null) return "";
return proxy.address().toString().substring(1);
}
@ToString
static public class Request {
final HttpUrl url;
final String json;
final String username;
final String password;
public Request(final HttpUrl url, final String json, final String username, final String password) {
this.url = url;
this.json = json;
this.username = username;
this.password = password;
}
public Request(final HttpUrl url, final JSONObject json) {
this(url, json == null ? null : json.toString(), null, null);
}
public Request(final HttpUrl url) {
this(url, null, null, null);
}
public void enqueue(Callback callback) {
newCall().enqueue(callback);
}
public Response execute() throws IOException {
return newCall().execute();
}
private Call newCall() {
return getClient().newCall(getRequest());
}
private OkHttpClient getClient() {
final OkHttpClient client = getInstance().client;
if ((username != null) && (!username.isEmpty())) {
final DigestAuthenticator authenticator = new DigestAuthenticator(new Credentials(username, password));
final Map<String, CachingAuthenticator> authCache = new ConcurrentHashMap<>();
return client.newBuilder()
.authenticator(new CachingAuthenticatorDecorator(authenticator, authCache))
.addInterceptor(new AuthenticationCacheInterceptor(authCache))
.build();
// TODO: maybe cache & reuse the client for these credentials?
} else {
return client;
}
}
private okhttp3.Request getRequest() {
final okhttp3.Request.Builder builder =
new okhttp3.Request.Builder()
.url(url)
.header("User-Agent", USER_AGENT);
if (json != null) {
builder.post(RequestBody.create(json, MediaType.parse("application/json")));
} else {
builder.get();
}
return builder.build();
}
}
private static final String PREFS_NAME = "tor";
private static final String PREFS_STATUS = "status";
private Status currentPref = Status.UNKNOWN;
private Status getTorPref() {
if (currentPref != Status.UNKNOWN) return currentPref;
currentPref = Status.valueOf(context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.getString(PREFS_STATUS, "DISABLED"));
return currentPref;
}
private void setTorPref(Status status) {
if (getTorPref() == status) return; // no change
context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
.edit()
.putString(PREFS_STATUS, status.name())
.apply();
currentPref = status;
}
private static final BroadcastReceiver orbotStatusReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
Timber.d("%s/%s", intent.getAction(), intent.getStringExtra(OrbotHelper.EXTRA_STATUS));
if (OrbotHelper.ACTION_STATUS.equals(intent.getAction())) {
if (OrbotHelper.STATUS_STARTS_DISABLED.equals(intent.getStringExtra(OrbotHelper.EXTRA_STATUS))) {
getInstance().onNotEnabled();
}
}
}
};
public void installOrbot(Activity host) {
host.startActivity(OrbotHelper.getOrbotInstallIntent(context));
}
}

View File

@ -1,72 +0,0 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util;
import java.util.concurrent.TimeUnit;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
public class OkHttpHelper {
static private OkHttpClient Singleton;
static public OkHttpClient getOkHttpClient() {
if (Singleton == null) {
synchronized (OkHttpHelper.class) {
if (Singleton == null) {
Singleton = new OkHttpClient();
}
}
}
return Singleton;
}
public static final int HTTP_TIMEOUT = 1000; //ms
static private OkHttpClient EagerSingleton;
static public OkHttpClient getEagerClient() {
if (EagerSingleton == null) {
synchronized (OkHttpHelper.class) {
if (EagerSingleton == null) {
EagerSingleton = new OkHttpClient.Builder()
.connectTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
.writeTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
.readTimeout(HTTP_TIMEOUT, TimeUnit.MILLISECONDS)
.build();
}
}
}
return EagerSingleton;
}
static final public String USER_AGENT = "Monerujo/1.0";
static public Request getPostRequest(HttpUrl url, RequestBody requestBody) {
return new Request.Builder().url(url).post(requestBody)
.header("User-Agent", USER_AGENT)
.build();
}
static public Request getGetRequest(HttpUrl url) {
return new Request.Builder().url(url).get()
.header("User-Agent", USER_AGENT)
.build();
}
}

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2021 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.util;
public class OnionHelper {
public static boolean isOnionHost(String hostname) {
return hostname.endsWith(".onion");
}
}

View File

@ -19,6 +19,6 @@ public class ServiceHelper {
} }
static public ExchangeApi getExchangeApi() { static public ExchangeApi getExchangeApi() {
return new com.m2049r.xmrwallet.service.exchange.krakenEcb.ExchangeApiImpl(OkHttpHelper.getOkHttpClient()); return new com.m2049r.xmrwallet.service.exchange.krakenEcb.ExchangeApiImpl();
} }
} }

View File

@ -344,8 +344,8 @@ public class ExchangeEditText extends LinearLayout {
if (rate > 0) { if (rate > 0) {
tvAmountB.setText(Helper.getFormattedAmount(rate * amount, getCurrencyB() == 0)); tvAmountB.setText(Helper.getFormattedAmount(rate * amount, getCurrencyB() == 0));
} else { } else {
tvAmountB.setText(null); tvAmountB.setText("--");
Timber.w("No rate!"); Timber.d("No rate!");
} }
} }

View File

@ -321,23 +321,13 @@ public class ExchangeView extends LinearLayout {
@Override @Override
public void onSuccess(final ExchangeRate exchangeRate) { public void onSuccess(final ExchangeRate exchangeRate) {
if (isAttachedToWindow()) if (isAttachedToWindow())
new Handler(Looper.getMainLooper()).post(new Runnable() { new Handler(Looper.getMainLooper()).post(() -> exchange(exchangeRate));
@Override
public void run() {
exchange(exchangeRate);
}
});
} }
@Override @Override
public void onError(final Exception e) { public void onError(final Exception e) {
Timber.e(e.getLocalizedMessage()); Timber.e(e.getLocalizedMessage());
new Handler(Looper.getMainLooper()).post(new Runnable() { new Handler(Looper.getMainLooper()).post(() -> exchangeFailed());
@Override
public void run() {
exchangeFailed();
}
});
} }
}); });
} }
@ -362,11 +352,10 @@ public class ExchangeView extends LinearLayout {
} }
tvAmountB.setText(xmrAmount); tvAmountB.setText(xmrAmount);
} else { // no XMR currency - cannot happen! } else { // no XMR currency - cannot happen!
Timber.e("No XMR currency!"); throw new IllegalStateException("No XMR currency!");
setXmr(null);
notXmrAmount = null;
return;
} }
if (rate == 0)
tvAmountB.setText("--");
} }
boolean prepareExchange() { boolean prepareExchange() {

View File

@ -0,0 +1,115 @@
/*
* Copyright 2012-2016 Nathan Freitas
* Copyright 2015 str4d
* Portions Copyright (c) 2016 CommonsWare, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package info.guardianproject.netcipher.client;
import android.content.Context;
import android.content.Intent;
import javax.net.ssl.SSLSocketFactory;
import okhttp3.OkHttpClient;
import okhttp3.Request;
/**
* Creates an OkHttpClient using NetCipher configuration. Use
* build() if you have no other OkHttpClient configuration
* that you need to perform. Or, use applyTo() to augment an
* existing OkHttpClient.Builder with NetCipher.
*/
public class StrongOkHttpClientBuilder extends
StrongBuilderBase<StrongOkHttpClientBuilder, OkHttpClient> {
/**
* Creates a StrongOkHttpClientBuilder using the strongest set
* of options for security. Use this if the strongest set of
* options is what you want; otherwise, create a
* builder via the constructor and configure it as you see fit.
*
* @param context any Context will do
* @return a configured StrongOkHttpClientBuilder
* @throws Exception
*/
static public StrongOkHttpClientBuilder forMaxSecurity(Context context)
throws Exception {
return(new StrongOkHttpClientBuilder(context)
.withBestProxy());
}
/**
* Creates a builder instance.
*
* @param context any Context will do; builder will hold onto
* Application context
*/
public StrongOkHttpClientBuilder(Context context) {
super(context);
}
/**
* Copy constructor.
*
* @param original builder to clone
*/
public StrongOkHttpClientBuilder(StrongOkHttpClientBuilder original) {
super(original);
}
/**
* OkHttp3 does not support SOCKS proxies:
* https://github.com/square/okhttp/issues/2315
*
* @return false
*/
@Override
public boolean supportsSocksProxy() {
return(true);
}
/**
* {@inheritDoc}
*/
@Override
public OkHttpClient build(Intent status) {
return(applyTo(new OkHttpClient.Builder(), status).build());
}
/**
* Adds NetCipher configuration to an existing OkHttpClient.Builder,
* in case you have additional configuration that you wish to
* perform.
*
* @param builder a new or partially-configured OkHttpClient.Builder
* @return the same builder
*/
public OkHttpClient.Builder applyTo(OkHttpClient.Builder builder, Intent status) {
SSLSocketFactory factory=buildSocketFactory();
if (factory!=null) {
builder.sslSocketFactory(factory);
}
return(builder
.proxy(buildProxy(status)));
}
@Override
protected String get(Intent status, OkHttpClient connection,
String url) throws Exception {
Request request=new Request.Builder().url(url).build();
return(connection.newCall(request).execute().body().string());
}
}

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorSecondary" android:state_enabled="true"/> <item android:color="?attr/colorPrimary" android:state_enabled="true"/>
<item android:color="?attr/colorOnBackground"/> <item android:color="?attr/colorPrimaryVariant"/>
</selector> </selector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#2D1A2E"
android:pathData="M11,17h2v-6h-2v6zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM12,20c-4.41,0 -8,-3.59 -8,-8s3.59,-8 8,-8 8,3.59 8,8 -3.59,8 -8,8zM11,9h2L13,7h-2v2z"/>
</vector>

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:fillColor="@color/btn_color_selector"
android:pathData="M50,8.33A41.67,41.67 0,1 0,91.67 50,41.65 41.65,0 0,0 50,8.33ZM78.87,33.33L66.54,33.33A65.26,65.26 0,0 0,60.79 18.5,33.48 33.48,0 0,1 78.83,33.33ZM50,16.83a58.59,58.59 0,0 1,8 16.5L42,33.33A58.72,58.72 0,0 1,50 16.83ZM17.75,58.33a32.66,32.66 0,0 1,0 -16.66L31.83,41.67A69.49,69.49 0,0 0,31.25 50a69.49,69.49 0,0 0,0.58 8.33ZM21.17,66.67L33.46,66.67A64.9,64.9 0,0 0,39.21 81.5,33.26 33.26,0 0,1 21.17,66.67ZM33.46,33.33L21.17,33.33a33.26,33.26 0,0 1,18 -14.83A65.26,65.26 0,0 0,33.46 33.33ZM50,83.17a58.72,58.72 0,0 1,-8 -16.5L58,66.67A58.72,58.72 0,0 1,50 83.17ZM59.75,58.33L40.25,58.33A60.51,60.51 0,0 1,39.58 50a59.88,59.88 0,0 1,0.67 -8.33h19.5A59.88,59.88 0,0 1,60.42 50,60.51 60.51,0 0,1 59.75,58.33ZM60.75,81.5a65.08,65.08 0,0 0,5.75 -14.83L78.83,66.67A33.44,33.44 0,0 1,60.79 81.5ZM68.13,58.33A69.49,69.49 0,0 0,68.75 50a69.49,69.49 0,0 0,-0.58 -8.33L82.25,41.67a32.66,32.66 0,0 1,0 16.66Z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:fillColor="@color/btn_color_selector"
android:fillType="evenOdd"
android:pathData="M50.08,84.36L50.08,79.27a29.27,29.27 0,0 0,0 -58.54L50.08,15.64a34.36,34.36 0,0 1,0 68.72ZM50.08,66.54a16.55,16.55 0,0 0,0 -33.09L50.08,28.36a21.64,21.64 0,0 1,0 43.27ZM50.08,41.09a8.91,8.91 0,0 1,0 17.82ZM8,50A42,42 0,1 0,50 8,42 42,0 0,0 8,50Z" />
</vector>

View File

@ -7,16 +7,6 @@
android:paddingTop="8dp" android:paddingTop="8dp"
android:paddingEnd="16dp"> android:paddingEnd="16dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="match_parent"
android:layout_height="48dp"
android:src="@mipmap/ic_launcher" />
<TextView <TextView
android:id="@+id/tvHelp" android:id="@+id/tvHelp"
style="@style/MoneroText" style="@style/MoneroText"
@ -27,6 +17,4 @@
android:gravity="start" android:gravity="start"
android:textSize="14sp" android:textSize="14sp"
tools:text="@string/menu_help" /> tools:text="@string/menu_help" />
</LinearLayout>
</ScrollView> </ScrollView>

View File

@ -29,28 +29,49 @@
<RelativeLayout <RelativeLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"> android:layout_marginBottom="8dp">
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/pbNetwork"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_alignStart="@id/ibNetwork"
android:layout_alignTop="@+id/ibNetwork"
android:layout_alignEnd="@id/ibNetwork"
android:layout_alignBottom="@id/ibNetwork"
android:layout_gravity="center"
android:indeterminate="true"
android:progress="100"
android:visibility="visible"
app:indicatorInset="9dp"
app:indicatorSize="30dp"
app:trackThickness="2dp" />
<ImageButton
android:id="@+id/ibNetwork"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentStart="true"
android:layout_centerInParent="true"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:gravity="center"
android:padding="12dp"
android:src="@drawable/ic_network_clearnet" />
<FrameLayout <FrameLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="0dp"
android:layout_alignParentTop="true" android:layout_alignTop="@+id/ibNetwork"
android:layout_marginStart="16dp" android:layout_alignBottom="@id/ibNetwork"
android:layout_toStartOf="@+id/ibRenew"> android:layout_toStartOf="@+id/ibRenew"
android:layout_toEndOf="@+id/ibNetwork">
<ProgressBar
android:id="@+id/pbNode"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:visibility="invisible" />
<LinearLayout <LinearLayout
android:id="@+id/llNode" android:id="@+id/llNode"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="?android:attr/selectableItemBackground" android:background="?android:attr/selectableItemBackground"
android:gravity="start|center_vertical" android:gravity="center_vertical"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
@ -58,17 +79,14 @@
style="@style/MoneroText.PosAmount" style="@style/MoneroText.PosAmount"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:drawablePadding="8dp"
app:drawableStartCompat="@drawable/ic_refresh_black_24dp"
tools:text="monero-v9.monerujo.io" /> tools:text="monero-v9.monerujo.io" />
<TextView <TextView
android:id="@+id/tvNodeAddress" android:id="@+id/tvInfo"
style="@style/MoneroText.Small" style="@style/MoneroText.Small"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="32dp" tools:text="Last Block: 2 minutes ago" />
tools:text="128.130.233.151:18089" />
</LinearLayout> </LinearLayout>
</FrameLayout> </FrameLayout>

View File

@ -111,8 +111,9 @@
android:text="@string/label_receive_info_gen_qr_code" android:text="@string/label_receive_info_gen_qr_code"
android:textAlignment="center" android:textAlignment="center"
android:textSize="16sp" android:textSize="16sp"
android:visibility="gone" android:textColor="#2D1A2E"
card_view:drawableStartCompat="@drawable/ic_info_outline_gray_24dp" /> android:visibility="visible"
card_view:drawableStartCompat="@drawable/ic_info_outline_black_24dp" />
<ImageView <ImageView
android:id="@+id/qrCode" android:id="@+id/qrCode"

View File

@ -126,23 +126,38 @@
android:src="@drawable/ic_xmrto_doge_off" /> android:src="@drawable/ic_xmrto_doge_off" />
</LinearLayout> </LinearLayout>
<TextView <FrameLayout
android:id="@+id/tvXmrTo"
style="@style/MoneroText.Info"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="8dp" android:layout_marginLeft="8dp"
android:layout_marginTop="16dp" android:layout_marginTop="16dp"
android:layout_marginRight="8dp" android:layout_marginRight="8dp"
android:layout_marginBottom="16dp" android:layout_marginBottom="16dp">
<TextView
android:id="@+id/tvXmrTo"
style="@style/MoneroText.Info"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawablePadding="8dp" android:drawablePadding="8dp"
android:gravity="start" android:gravity="start|center_vertical"
android:singleLine="false" android:singleLine="false"
android:text="@string/label_send_btc_xmrto_key"
android:textSize="18sp" android:textSize="18sp"
android:visibility="invisible" android:visibility="invisible"
app:drawableStartCompat="@drawable/gunther_24dp" app:drawableStartCompat="@drawable/gunther_24dp"
tools:text="@string/info_xmrto" /> tools:text="@string/info_xmrto" />
<TextView
android:id="@+id/tvTor"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:drawablePadding="8dp"
android:gravity="start|center_vertical"
android:singleLine="false"
android:text="@string/tor_noshift"
android:visibility="invisible"
app:drawableStartCompat="@drawable/gunther_24dp" />
</FrameLayout>
</LinearLayout> </LinearLayout>
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout

View File

@ -53,7 +53,7 @@
tools:text="monero-v9.monerujo.io" /> tools:text="monero-v9.monerujo.io" />
<TextView <TextView
android:id="@+id/tvAddress" android:id="@+id/tvInfo"
style="@style/MoneroText.Small" style="@style/MoneroText.Small"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"

View File

@ -73,12 +73,12 @@
tools:text="+ 999.999999" /> tools:text="+ 999.999999" />
<TextView <TextView
android:id="@+id/tx_fee" android:id="@+id/tx_failed"
style="@style/MoneroText.PosFee" style="@style/MoneroText.PosFee"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="end" android:gravity="end"
tools:text="Fee 0.06817" /> android:text="@string/tx_list_failed_text" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>

View File

@ -4,11 +4,11 @@
<h1>Crear Portamonedes - New</h1> <h1>Crear Portamonedes - New</h1>
<p>Per si necessiteu una nova adreça de Monero!</p> <p>Per si necessiteu una nova adreça de Monero!</p>
<p>Introduïu un nom i contrasenya únics del portamonedes. <p>Introduïu un nom i contrasenya únics del portamonedes.
         La contrasenya sutilitza per protegir les dades del seu portamonedes en el dispositiu. Utilitzeu una contrasenya sòlida - encara millor si utiliza una frase.</p> La contrasenya sutilitza per protegir les dades del seu portamonedes en el dispositiu. Utilitzeu una contrasenya sòlida - encara millor si utiliza una frase.</p>
<h2>Apunteu la vostra llavor mnemotècnica!</h2> <h2>Apunteu la vostra llavor mnemotècnica!</h2>
<p>A la pantalla següent trobareu la vostra "Llavor Mnemotècnica" de 25 paraules. <p>A la pantalla següent trobareu la vostra "Llavor Mnemotècnica" de 25 paraules.
         Aquestes són les úniques dades necessàries per a recuperar el vostre portamonedes i obtenir accés total als vostres fons. Aquestes són les úniques dades necessàries per a recuperar el vostre portamonedes i obtenir accés total als vostres fons.
         Mantenir-la segura i privada és molt important ja que permet que <em> qualsevol persona</em> tingui accés total als vostres fons!.</p> Mantenir-la segura i privada és molt important ja que permet que <em> qualsevol persona</em> tingui accés total als vostres fons!.</p>
<p>Si perdeu la contrasenya del portamonedes encara podrieu recuperar-lo amb la llavor Mnemotècnica.</p> <p>Si perdeu la contrasenya del portamonedes encara podrieu recuperar-lo amb la llavor Mnemotècnica.</p>
<p>La llavor Mnemotècnica tampoc no es pot canviar mai, i si és robada o compromesa dalguna manera haureu de traslladar els vostres fons a un nou portamonedes (amb una nova llavor mnemotècnica). Per tant, es recomana que feu còpies de seguretat de la vostra llavor mnemotècnica escrivint-la i emmagatzemant-la a <em> múltiple </em> llocs de forma segura.</p> <p>La llavor Mnemotècnica tampoc no es pot canviar mai, i si és robada o compromesa dalguna manera haureu de traslladar els vostres fons a un nou portamonedes (amb una nova llavor mnemotècnica). Per tant, es recomana que feu còpies de seguretat de la vostra llavor mnemotècnica escrivint-la i emmagatzemant-la a <em> múltiple </em> llocs de forma segura.</p>
]]></string> ]]></string>
@ -17,7 +17,7 @@
<h1>Crear Portamonedes - Llavor</h1> <h1>Crear Portamonedes - Llavor</h1>
<p>Si ja teniu una adreça Monero i voleu recuperar les transaccions de la blockchain!</p> <p>Si ja teniu una adreça Monero i voleu recuperar les transaccions de la blockchain!</p>
<p>Introduïu un nom i contrasenya únics del portamonedes. <p>Introduïu un nom i contrasenya únics del portamonedes.
         La contrasenya sutilitza per protegir les dades del seu portamonedes en el dispositiu. Utilitzeu una contrasenya sòlida - encara millor si utiliza una frase.</p> La contrasenya sutilitza per protegir les dades del seu portamonedes en el dispositiu. Utilitzeu una contrasenya sòlida - encara millor si utiliza una frase.</p>
<p>Introduïu la Llavor en el camp \"Llavor Mnemotècnica\".<p> <p>Introduïu la Llavor en el camp \"Llavor Mnemotècnica\".<p>
<p>Introduïu el número de bloc de la primera transacció realitzada en aquesta adreça en el camp "Restaurar Alçada". També podeu utilitzar una data en el format AAAA-MM-DD. Si no esteu segurs, introduïu una data / alçada de bloc aproximada <em> abans </em> dhaver fer servir aquesta adreça del portamonedes.</p> <p>Introduïu el número de bloc de la primera transacció realitzada en aquesta adreça en el camp "Restaurar Alçada". També podeu utilitzar una data en el format AAAA-MM-DD. Si no esteu segurs, introduïu una data / alçada de bloc aproximada <em> abans </em> dhaver fer servir aquesta adreça del portamonedes.</p>
]]></string> ]]></string>
@ -36,7 +36,7 @@
<p>Introduïu un nom i contrasenya únics del portamonedes. La contrasenya sutilitza per protegir les dades de la seva cartera al dispositiu. Utilitzeu una contrasenya sòlida - encara millor si utiliza una frase.<p> <p>Introduïu un nom i contrasenya únics del portamonedes. La contrasenya sutilitza per protegir les dades de la seva cartera al dispositiu. Utilitzeu una contrasenya sòlida - encara millor si utiliza una frase.<p>
<p>Introduïu la vostra Adreça Monero en el camp \"Adreça Pública\" i ompliu \"Clau de Visualització\" i \"Clau de Despesa\" </p> <p>Introduïu la vostra Adreça Monero en el camp \"Adreça Pública\" i ompliu \"Clau de Visualització\" i \"Clau de Despesa\" </p>
<p>Introduïu el número de bloc de la primera transacció realitzada en aquesta adreça en el <p>Introduïu el número de bloc de la primera transacció realitzada en aquesta adreça en el
         camp "Restaurar Alçada". També podeu utilitzar una data en el format AAAA-MM-DD. Si no esteu segurs, introduïu una data / alçada de bloc aproximada <em> abans </em> dhaver fer servir aquesta adreça del portamonedes.</p> camp "Restaurar Alçada". També podeu utilitzar una data en el format AAAA-MM-DD. Si no esteu segurs, introduïu una data / alçada de bloc aproximada <em> abans </em> dhaver fer servir aquesta adreça del portamonedes.</p>
]]></string> ]]></string>
<string name="help_create_view"><![CDATA[ <string name="help_create_view"><![CDATA[
@ -53,18 +53,18 @@
La vostra adreça pública és com el vostre número de compte bancari que podeu compartir amb qualsevol persona sense haver de tenir por de perdre el vostres Monero. La gent enviarà Monero al seu portamonedes mitjançant aquesta adreça. La vostra adreça pública és com el vostre número de compte bancari que podeu compartir amb qualsevol persona sense haver de tenir por de perdre el vostres Monero. La gent enviarà Monero al seu portamonedes mitjançant aquesta adreça.
<h2>Clau Mnemotècnica</h2> <h2>Clau Mnemotècnica</h2>
Aquestes són les úniques dades necessàries per a recuperar el vostre portamonedes i obtenir accés total als vostres fons. Aquestes són les úniques dades necessàries per a recuperar el vostre portamonedes i obtenir accés total als vostres fons.
         Mantenir-la segura i privada és molt important ja que permet que <em> qualsevol persona</em> tingui accés total als vostres fons! Si no us heu escrit en cap lloc segur, feu-ho! Mantenir-la segura i privada és molt important ja que permet que <em> qualsevol persona</em> tingui accés total als vostres fons! Si no us heu escrit en cap lloc segur, feu-ho!
<h2>Contrasenya de Recuperació dArxius del Portamonedes</h2> <h2>Contrasenya de Recuperació dArxius del Portamonedes</h2>
Assegureu-vos que heu escrit aquesta contrasenya. Si restabliu el dispositiu o desinstal·leu laplicació, la necessitarà per tornar a accedir al seu portamonedes.<br/> Assegureu-vos que heu escrit aquesta contrasenya. Si restabliu el dispositiu o desinstal·leu laplicació, la necessitarà per tornar a accedir al seu portamonedes.<br/>
<h3>CrAzYsenya</h3> <h3>CrAzYsenya</h3>
Si la contrasenya que es mostra aquí és de 52 caràcters alfanumèrics en grups de 4 - felicitats! Si la contrasenya que es mostra aquí és de 52 caràcters alfanumèrics en grups de 4 - felicitats!
        Els fitxers del portamonedes estan protegits amb una clau de 256 bits generada per la funció de seguretat del vostre dispositiu basades en la contrasenya que heu triat (durant la creació o al canviar-la). Això ho fa extremadament difícil de piratejar!<br/> Els fitxers del portamonedes estan protegits amb una clau de 256 bits generada per la funció de seguretat del vostre dispositiu basades en la contrasenya que heu triat (durant la creació o al canviar-la). Això ho fa extremadament difícil de piratejar!<br/>
Aquesta opció és obligatoria per a tots els nous portamonedes. Aquesta opció és obligatoria per a tots els nous portamonedes.
<h3>Contrasenya de Llegat</h3> <h3>Contrasenya de Llegat</h3>
Si veieu la vostra contrasenya aquí, els fitxers del portamonedes no estan tan segurs com quan sutilitza la CrAzYsenya. Per solucionar-ho, seleccioneu \"Canviar Contrasenya\" des del menú. Després dentrar una nova contrasenya (potser fins i tot la mateixa que abans) laplicació generarà una CrAzYsenya per a protegir els vostres arxius del portamonedes. Anoteu-la! Si veieu la vostra contrasenya aquí, els fitxers del portamonedes no estan tan segurs com quan sutilitza la CrAzYsenya. Per solucionar-ho, seleccioneu \"Canviar Contrasenya\" des del menú. Després dentrar una nova contrasenya (potser fins i tot la mateixa que abans) laplicació generarà una CrAzYsenya per a protegir els vostres arxius del portamonedes. Anoteu-la!
<h3>CrAzYsenya del Portamonedes</h3> <h3>CrAzYsenya del Portamonedes</h3>
Si mai necessiteu tornar a instal·lar Monerujo (per exemple, després de restablir el telèfon o canviar-lo per un de nou) o voleu utilitzar els arxius del portamonedes en un altre dispositiu o PC, cal que ho feu utilitzant aquesta Contrasenya de Recuperació (CrAzYsenya) per tornar a accedir al vostre portamonedes. <br/> Si mai necessiteu tornar a instal·lar Monerujo (per exemple, després de restablir el telèfon o canviar-lo per un de nou) o voleu utilitzar els arxius del portamonedes en un altre dispositiu o PC, cal que ho feu utilitzant aquesta Contrasenya de Recuperació (CrAzYsenya) per tornar a accedir al vostre portamonedes. <br/>
        En seleccionar \"Canviar Contrasenya\" des del menú, podeu triar una nova contrasenya. Aneu amb compte que això generarà una nova Contrasenya de Recuperació (CrAzYsenya). Anoteu-la! En seleccionar \"Canviar Contrasenya\" des del menú, podeu triar una nova contrasenya. Aneu amb compte que això generarà una nova Contrasenya de Recuperació (CrAzYsenya). Anoteu-la!
<h2>Clau de Visualització</h2> <h2>Clau de Visualització</h2>
La vostra clau de visualització es pot utilitzar per monitoritzar les transaccions entrants al vostre portamonedes sense donar-ne permís per gastar els fons a dins seu. La vostra clau de visualització es pot utilitzar per monitoritzar les transaccions entrants al vostre portamonedes sense donar-ne permís per gastar els fons a dins seu.
<h2>Clau de Despesa</h2> <h2>Clau de Despesa</h2>
@ -75,13 +75,13 @@
<h1>Llista de Portamonedes</h1> <h1>Llista de Portamonedes</h1>
<h2>Node</h2> <h2>Node</h2>
<p>Monerujo utilitza un node remot per comunicar-se amb la xarxa Monero sense necessitat <p>Monerujo utilitza un node remot per comunicar-se amb la xarxa Monero sense necessitat
         de descarregar i emmagatzemar una còpia de tota la blockchain sencera. Podeu trobar una llista dels nodes remots més populars o aprendre a configurar el vostre propi node remot aquí https://moneroworld.com/<p> de descarregar i emmagatzemar una còpia de tota la blockchain sencera. Podeu trobar una llista dels nodes remots més populars o aprendre a configurar el vostre propi node remot aquí https://moneroworld.com/<p>
<p>Monerujo té alguns Nodes remots incorporats. Sen recorda dels últims cinc nodes empleats.</p> <p>Monerujo té alguns Nodes remots incorporats. Sen recorda dels últims cinc nodes empleats.</p>
<h2>Portamonedes</h2> <h2>Portamonedes</h2>
<p>Aquí podeu veure els portamonedes. Es troben a la carpeta <tt> monerujo </tt> <p>Aquí podeu veure els portamonedes. Es troben a la carpeta <tt> monerujo </tt>
         dins lemmagatzematge intern del dispositiu. Podeu utilitzar una aplicació dexploració darxius per veurels. Haurieu de fer còpies de seguretat daquesta carpeta de manera regular en un emmagatzematge extern al dispositiu en cas que el vostre dispositiu exploti o el robin.</p> dins lemmagatzematge intern del dispositiu. Podeu utilitzar una aplicació dexploració darxius per veurels. Haurieu de fer còpies de seguretat daquesta carpeta de manera regular en un emmagatzematge extern al dispositiu en cas que el vostre dispositiu exploti o el robin.</p>
<p>Seleccioneu una portamonedes per obrir-lo o premeu el botó "+" per crear-ne un de nou. <p>Seleccioneu una portamonedes per obrir-lo o premeu el botó "+" per crear-ne un de nou.
         O seleccioneu una de les operacions del portamonedes:</p> O seleccioneu una de les operacions del portamonedes:</p>
<h3>Detalls</h3> <h3>Detalls</h3>
<p>Mostra els detalls del portamonedes, la llavor i les seves claus.</p> <p>Mostra els detalls del portamonedes, la llavor i les seves claus.</p>
<h3>Rebre</h3> <h3>Rebre</h3>
@ -90,7 +90,7 @@
<p>Canvia el nom del portamonedes. El canvi de nom no afecta a les còpies de seguretat.</p> <p>Canvia el nom del portamonedes. El canvi de nom no afecta a les còpies de seguretat.</p>
<h3>Còpia de Seguretat</h3> <h3>Còpia de Seguretat</h3>
<p>Feu una còpia del portamonedes en la carpeta <tt> backups </tt> dins del <tt> monerujo </tt> <p>Feu una còpia del portamonedes en la carpeta <tt> backups </tt> dins del <tt> monerujo </tt>
         per a sobreescriure còpies anteriors.</p> per a sobreescriure còpies anteriors.</p>
<h3>Arxiu</h3> <h3>Arxiu</h3>
<p>Feu una còpia de seguretat i a continuació elimineu el portamonedes. La còpia es mantindrà a la carpeta <tt> backups </tt>. Si ja no necessiteu les vostres còpies de seguretat les haureu deliminar amb un explorador darxius o una aplicació segura.</p> <p>Feu una còpia de seguretat i a continuació elimineu el portamonedes. La còpia es mantindrà a la carpeta <tt> backups </tt>. Si ja no necessiteu les vostres còpies de seguretat les haureu deliminar amb un explorador darxius o una aplicació segura.</p>
]]></string> ]]></string>
@ -122,14 +122,14 @@
<h1>Enviant BTC</h1> <h1>Enviant BTC</h1>
<h2>SideShift.ai</h2> <h2>SideShift.ai</h2>
<p>SideShift.ai és un servei de tercers que fa proporciona el canvi de Monero a Bitcoin. <p>SideShift.ai és un servei de tercers que fa proporciona el canvi de Monero a Bitcoin.
         Nosaltres fem servir lAPI SideShift.ai per integrar els pagaments de Bitcoin a Monerujo. Si us plau, doneu un cop dull a https://sideshift.ai i decidiu vosaltres mateixos si és una cosa que volgueu utilitzar. Lequip de Monerujo no està associat amb SideShift.ai i no pot oferir assistència amb el seu servei.</p> Nosaltres fem servir lAPI SideShift.ai per integrar els pagaments de Bitcoin a Monerujo. Si us plau, doneu un cop dull a https://sideshift.ai i decidiu vosaltres mateixos si és una cosa que volgueu utilitzar. Lequip de Monerujo no està associat amb SideShift.ai i no pot oferir assistència amb el seu servei.</p>
<h2>Tipus de Canvi SideShift.ai<h2> <h2>Tipus de Canvi SideShift.ai<h2>
<p>A la pantalla \"Quantitat\" es mostraran els paràmetres actuals del servei SideShift.ai. Aquests <p>A la pantalla \"Quantitat\" es mostraran els paràmetres actuals del servei SideShift.ai. Aquests
         inclouen el tipus de canvi actual, així com els límits de BTC superiors i inferiors. Tingueu en compte que la tarifa no està garantida en aquell mateix moment.</p> inclouen el tipus de canvi actual, així com els límits de BTC superiors i inferiors. Tingueu en compte que la tarifa no està garantida en aquell mateix moment.</p>
<h2>Ordre de compra SideShift.ai<h2> <h2>Ordre de compra SideShift.ai<h2>
<p>A la pantalla \"Confirmar\", veureu lordre de compra SideShift.ai real. Aquesta comanda és vàlida per a <p>A la pantalla \"Confirmar\", veureu lordre de compra SideShift.ai real. Aquesta comanda és vàlida per a
         un temps limitat: és possible que veieu un compte enrere en el botó \"Gastar\". El tipus de canvi pot un temps limitat: és possible que veieu un compte enrere en el botó \"Gastar\". El tipus de canvi pot
         ser diferent del mostrat en pantalles anteriors.</p> ser diferent del mostrat en pantalles anteriors.</p>
<h2>Clau Secreta SideShift.ai<h2> <h2>Clau Secreta SideShift.ai<h2>
<p>Com que Monerujo només gestiona la part Monero de la vostra transacció, la vostra clau secreta per SideShift.ai es pot utilitzar per fer el seguiment de la part de Bitcoin de la vostra comanda a la pàgina principal de SideShift.ai.</p> <p>Com que Monerujo només gestiona la part Monero de la vostra transacció, la vostra clau secreta per SideShift.ai es pot utilitzar per fer el seguiment de la part de Bitcoin de la vostra comanda a la pàgina principal de SideShift.ai.</p>
<h2>Compte enrere SideShift.ai!</h2> <h2>Compte enrere SideShift.ai!</h2>
@ -140,14 +140,14 @@
<h1>Enviant BTC</h1> <h1>Enviant BTC</h1>
<h2>SideShift.ai</h2> <h2>SideShift.ai</h2>
<p>SideShift.ai és un servei de tercers que fa proporciona el canvi de Monero a Bitcoin. <p>SideShift.ai és un servei de tercers que fa proporciona el canvi de Monero a Bitcoin.
         Nosaltres fem servir lAPI SideShift.ai per integrar els pagaments de Bitcoin a Monerujo. Si us plau, doneu un cop dull a https://sideshift.ai i decidiu vosaltres mateixos si és una cosa que volgueu utilitzar. Lequip de Monerujo no està associat amb SideShift.ai i no pot oferir assistència amb el seu servei.</p> Nosaltres fem servir lAPI SideShift.ai per integrar els pagaments de Bitcoin a Monerujo. Si us plau, doneu un cop dull a https://sideshift.ai i decidiu vosaltres mateixos si és una cosa que volgueu utilitzar. Lequip de Monerujo no està associat amb SideShift.ai i no pot oferir assistència amb el seu servei.</p>
<h2>Tipus de Canvi SideShift.ai<h2> <h2>Tipus de Canvi SideShift.ai<h2>
<p>A la pantalla \"Quantitat\" es mostraran els paràmetres actuals del servei SideShift.ai. Aquests <p>A la pantalla \"Quantitat\" es mostraran els paràmetres actuals del servei SideShift.ai. Aquests
         inclouen el tipus de canvi actual, així com els límits de BTC superiors i inferiors. Tingueu en compte que la tarifa no està garantida en aquell mateix moment.</p> inclouen el tipus de canvi actual, així com els límits de BTC superiors i inferiors. Tingueu en compte que la tarifa no està garantida en aquell mateix moment.</p>
<h2>Ordre de compra SideShift.ai<h2> <h2>Ordre de compra SideShift.ai<h2>
<p>A la pantalla \"Confirmar\", veureu lordre de compra SideShift.ai real. Aquesta comanda és vàlida per a <p>A la pantalla \"Confirmar\", veureu lordre de compra SideShift.ai real. Aquesta comanda és vàlida per a
         un temps limitat: és possible que veieu un compte enrere en el botó \"Gastar\". El tipus de canvi pot un temps limitat: és possible que veieu un compte enrere en el botó \"Gastar\". El tipus de canvi pot
         ser diferent del mostrat en pantalles anteriors.</p> ser diferent del mostrat en pantalles anteriors.</p>
<h2>Clau Secreta SideShift.ai<h2> <h2>Clau Secreta SideShift.ai<h2>
<p>Com que Monerujo només gestiona la part Monero de la vostra transacció, la vostra clau secreta per SideShift.ai es pot utilitzar per fer el seguiment de la part de Bitcoin de la vostra comanda a la pàgina principal de SideShift.ai.</p> <p>Com que Monerujo només gestiona la part Monero de la vostra transacció, la vostra clau secreta per SideShift.ai es pot utilitzar per fer el seguiment de la part de Bitcoin de la vostra comanda a la pàgina principal de SideShift.ai.</p>
<h2>Compte enrere SideShift.ai!</h2> <h2>Compte enrere SideShift.ai!</h2>
@ -164,7 +164,7 @@
<p><b>Ajuda! El balanç del meu portamonedes ha desaparegut o consta com a no confirmat!</b><br/> <p><b>Ajuda! El balanç del meu portamonedes ha desaparegut o consta com a no confirmat!</b><br/>
No patiu! Quan envieu fons desde el vostre portamonedes part del balanç apareixerà com a no confirmat de forma temporal. No patiu! Quan envieu fons desde el vostre portamonedes part del balanç apareixerà com a no confirmat de forma temporal.
Això succeeix pel fet de com Monero és intercanviat a través de la blockchain i com es produeix el canvi. Això succeeix pel fet de com Monero és intercanviat a través de la blockchain i com es produeix el canvi.
        Podeu llegir més sobre el canvi a https://getmonero.org/resources/moneropedia/change.html Podeu llegir més sobre el canvi a https://getmonero.org/resources/moneropedia/change.html
<h2>Llista de Transacció</h2> <h2>Llista de Transacció</h2>
<p>Llistat de les transaccions del portamonedes. Els portamonedes de visualització només mostraran les transaccions entrants.</p> <p>Llistat de les transaccions del portamonedes. Els portamonedes de visualització només mostraran les transaccions entrants.</p>
]]></string> ]]></string>
@ -173,49 +173,49 @@
<h1>Nodes</h1> <h1>Nodes</h1>
<h2>TL;DR</h2> <h2>TL;DR</h2>
<p>Actualitzeu la llista de nodes prement cap avall; marqueu 3&#8211;5 nodes per permetre a Monerujo <p>Actualitzeu la llista de nodes prement cap avall; marqueu 3&#8211;5 nodes per permetre a Monerujo
         triar el millor per a tu!</p> triar el millor per a tu!</p>
<h2>Què és un Node?</h2> <h2>Què és un Node?</h2>
<p>Monerujo utilitza un Node Remot (de vegades també anomenat daemon) per comunicar-se <p>Monerujo utilitza un Node Remot (de vegades també anomenat daemon) per comunicar-se
         la xarxa Monero sense haver de descarregar i emmagatzemar una còpia de la xarxa Monero sense haver de descarregar i emmagatzemar una còpia de
         tota la blockchain mateixa.<p> tota la blockchain mateixa.<p>
<h2>Llista de Nodes</h2> <h2>Llista de Nodes</h2>
<p>Si la llista està buida, podeu afegir nous nodes manualment o deixar que Monerujo <p>Si la llista està buida, podeu afegir nous nodes manualment o deixar que Monerujo
         escanegi la xarxa per vostè. O ambdós. Llegiu &#8230;</p> escanegi la xarxa per vostè. O ambdós. Llegiu &#8230;</p>
<p>La llista de nodes mostra tots els nodes coneguts. A més, la marca de temps <p>La llista de nodes mostra tots els nodes coneguts. A més, la marca de temps
         de lúltim bloc conegut per a cada node es mostra sota el nom del node. La icona de lúltim bloc conegut per a cada node es mostra sota el nom del node. La icona
         que representa el temps de resposta del node que representa el temps de resposta del node
         (que indica el nivell de connectivitat estimat) (que indica el nivell de connectivitat estimat)
         es mostra al costat de cada node.</p> es mostra al costat de cada node.</p>
<p>Es pot marcar qualsevol node de la llista per utilitzar-lo més endevant. <p>Es pot marcar qualsevol node de la llista per utilitzar-lo més endevant.
         Es descartaràn els nodes que no hagin estat seleccionats.<p> Es descartaràn els nodes que no hagin estat seleccionats.<p>
<p>Monerujo escollirà el node òptim (marcat) cada vegada que lutilitzeu. <p>Monerujo escollirà el node òptim (marcat) cada vegada que lutilitzeu.
         Això ho fa mitjançant la comprovació de lalçada de bloc (com dactualitzat Això ho fa mitjançant la comprovació de lalçada de bloc (com dactualitzat
         està el node?), així com el temps de resposta (què tan ràpidament respon el node a les peticions?).</p> està el node?), així com el temps de resposta (què tan ràpidament respon el node a les peticions?).</p>
<p>La llista sordena per aquestes característiques, de manera que el node superior seria el que Monerujo <p>La llista sordena per aquestes característiques, de manera que el node superior seria el que Monerujo
         triaria ara mateix. La part inferior de la llista mostraria els nodes més lents o no disponibles.</p> triaria ara mateix. La part inferior de la llista mostraria els nodes més lents o no disponibles.</p>
<h2>Afegir Node</h2> <h2>Afegir Node</h2>
<p>Si premeu el botó &quot;Afegir Node&quot; a la part inferior, se us demanarà <p>Si premeu el botó &quot;Afegir Node&quot; a la part inferior, se us demanarà
        que introduïu els detalls del node al següent diàleg. que introduïu els detalls del node al següent diàleg.
        El &quot;Adreça&quot; és el nom del servidor o adreça IP del node - aquesta és la única El &quot;Adreça&quot; és el nom del servidor o adreça IP del node - aquesta és la única
        entrada obligatòria. entrada obligatòria.
        Introduïu el &quot;Port&quot; si el node sexecuta en un port no predeterminat (per exemple, 18089). Introduïu el &quot;Port&quot; si el node sexecuta en un port no predeterminat (per exemple, 18089).
        També podeu anomenar opcionalment el node, de manera que el pugueu identificar més fàcilment més endavant. També podeu anomenar opcionalment el node, de manera que el pugueu identificar més fàcilment més endavant.
        Alguns nodes necessiten credencials per utilitzar-los. Introduïu el nom dusuari i Alguns nodes necessiten credencials per utilitzar-los. Introduïu el nom dusuari i
        contrasenya proporcionats als camps corresponents. Ara podeu &quot;Test&quot; aquesta configuració. contrasenya proporcionats als camps corresponents. Ara podeu &quot;Test&quot; aquesta configuració.
        Els &quot;Resultats de les Proves&quot; mostraran lalçada de bloc, el temps de resposta i lIP real. Els &quot;Resultats de les Proves&quot; mostraran lalçada de bloc, el temps de resposta i lIP real.
        El resultat també pot ser un error - generalment perquè el nom del servidor proporcionat no és El resultat també pot ser un error - generalment perquè el nom del servidor proporcionat no és
        accessible dins dun temps raonable o les credencials són incorrectes. accessible dins dun temps raonable o les credencials són incorrectes.
        O la combinació de nom del servidor/port no apunta cap a un node real de Monero! O la combinació de nom del servidor/port no apunta cap a un node real de Monero!
        Un cop aprovada la prova (sense error), ja estás llest per prémer &quot;Dacord&quot; per desar iamp; Un cop aprovada la prova (sense error), ja estás llest per prémer &quot;Dacord&quot; per desar iamp;
        marcar aquest node.</p> marcar aquest node.</p>
<h2>Escanejar Nodes</h2> <h2>Escanejar Nodes</h2>
<p>A més, podeu escanejar la xarxa per buscar nodes. Monerujo començarà <p>A més, podeu escanejar la xarxa per buscar nodes. Monerujo començarà
         escanejant la xarxa per als nodes remots al port 18089. Començarà per preguntar als vostres escanejant la xarxa per als nodes remots al port 18089. Començarà per preguntar als vostres
         nodes marcats per altres companys de la xarxa P2P de Monero, i després continuarà nodes marcats per altres companys de la xarxa P2P de Monero, i després continuarà
         preguntant-los per als seus companys, etc. Si no teniu cap node marcat preguntant-los per als seus companys, etc. Si no teniu cap node marcat
         (o no ens informen sobre els seus companys), (o no ens informen sobre els seus companys),
         Monerujo anirà directament als nodes de llavor de Monero codificats a dins de Monero. Monerujo anirà directament als nodes de llavor de Monero codificats a dins de Monero.
         Lescaneig satura quan troba un total de 10 nodes remots.</p> Lescaneig satura quan troba un total de 10 nodes remots.</p>
]]></string> ]]></string>
<string name="help_uri"><![CDATA[ <string name="help_uri"><![CDATA[
@ -230,4 +230,25 @@
]]></string> ]]></string>
<string name="help_ok">Ja ho tinc!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Ja ho tinc!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -13,7 +13,7 @@
<string name="menu_changepw">Canvi de contrasenya</string> <string name="menu_changepw">Canvi de contrasenya</string>
<string name="password_weak">Segueix escrivint &#8230;</string> <string name="password_weak">Segueix escrivint &#8230;</string>
<string name="password_fair">Més o menys... &#8230;</string> <string name="password_fair">Més o menys</string>
<string name="password_good">Vinga, ho pots fer millor!</string> <string name="password_good">Vinga, ho pots fer millor!</string>
<string name="password_strong">Força bé, pero millorbale &#8230;</string> <string name="password_strong">Força bé, pero millorbale &#8230;</string>
<string name="password_very_strong">Genial, ets tot un hacker!</string> <string name="password_very_strong">Genial, ets tot un hacker!</string>
@ -236,7 +236,7 @@
<string name="send_create_tx_error_title">Error creant la transacció</string> <string name="send_create_tx_error_title">Error creant la transacció</string>
<string name="tx_list_fee">- Comissió %1$s</string> <string name="tx_list_fee">Comissió %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">fallit</string> <string name="tx_list_failed_text">fallit</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -425,5 +425,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -290,4 +290,25 @@
]]></string> ]]></string>
<string name="help_ok">Hab\'s verstanden!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Hab\'s verstanden!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -232,7 +232,7 @@
<string name="send_create_tx_error_title">Fehler bei Transaktionserstellung</string> <string name="send_create_tx_error_title">Fehler bei Transaktionserstellung</string>
<string name="tx_list_fee">- Gebühr %1$s</string> <string name="tx_list_fee">Gebühr %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">fehlgeschlagen</string> <string name="tx_list_failed_text">fehlgeschlagen</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -426,5 +426,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -272,4 +272,25 @@
]]></string> ]]></string>
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -205,7 +205,7 @@
<string name="send_create_tx_error_title">Σφάλμα Δημιουργίας Συναλλαγής</string> <string name="send_create_tx_error_title">Σφάλμα Δημιουργίας Συναλλαγής</string>
<string name="tx_list_fee">- Κόμιστρο %1$s</string> <string name="tx_list_fee">Κόμιστρο %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">απέτυχε</string> <string name="tx_list_failed_text">απέτυχε</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -427,5 +427,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -316,4 +316,25 @@
]]></string> ]]></string>
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -236,7 +236,7 @@
<string name="send_create_tx_error_title">Krei transakci-eraron</string> <string name="send_create_tx_error_title">Krei transakci-eraron</string>
<string name="tx_list_fee">- Kosto %1$s</string> <string name="tx_list_fee">Kosto %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">masukcesis</string> <string name="tx_list_failed_text">masukcesis</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -427,5 +427,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -312,4 +312,25 @@
]]></string> ]]></string>
<string name="help_ok">¡Entendido!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">¡Entendido!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -173,7 +173,7 @@
<string name="send_create_tx_error_title">Error creando la transacción</string> <string name="send_create_tx_error_title">Error creando la transacción</string>
<string name="tx_list_fee">- Comisión %1$s</string> <string name="tx_list_fee">Comisión %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">fallido</string> <string name="tx_list_failed_text">fallido</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -418,5 +418,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -292,4 +292,25 @@
]]></string> ]]></string>
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -231,7 +231,7 @@
<string name="send_create_tx_error_title">Viga ülekande genereerimisel</string> <string name="send_create_tx_error_title">Viga ülekande genereerimisel</string>
<string name="tx_list_fee">- teenustasu %1$s</string> <string name="tx_list_fee">teenustasu %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">ebaõnnestus</string> <string name="tx_list_failed_text">ebaõnnestus</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -425,5 +425,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -310,4 +310,25 @@
]]></string> ]]></string>
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -233,7 +233,7 @@
<string name="send_create_tx_error_title">Erreur de Création de Transaction</string> <string name="send_create_tx_error_title">Erreur de Création de Transaction</string>
<string name="tx_list_fee">- Frais %1$s</string> <string name="tx_list_fee">Frais %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">Echoué</string> <string name="tx_list_failed_text">Echoué</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -431,5 +431,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -294,4 +294,25 @@
]]></string> ]]></string>
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -230,7 +230,7 @@
<string name="send_create_tx_error_title">Tranzakciólétrehozási hiba</string> <string name="send_create_tx_error_title">Tranzakciólétrehozási hiba</string>
<string name="tx_list_fee">- %1$s díj</string> <string name="tx_list_fee">%1$s díj</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">sikertelen</string> <string name="tx_list_failed_text">sikertelen</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -429,5 +429,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -295,4 +295,25 @@
]]></string> ]]></string>
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -232,7 +232,7 @@
<string name="send_create_tx_error_title">Errore nella creazione della transazione</string> <string name="send_create_tx_error_title">Errore nella creazione della transazione</string>
<string name="tx_list_fee">- Commissione %1$s</string> <string name="tx_list_fee">Commissione %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">fallita</string> <string name="tx_list_failed_text">fallita</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -431,4 +431,9 @@
<string name="menu_deletecache">Resetta portafogli!</string> <string name="menu_deletecache">Resetta portafogli!</string>
<string name="deletecache_alert_message">Il reset del portafogli cancellerà tutte le informazioni locali (note, nomi di account &amp; sottoindirizzi, chiavi di transazione private, ...)! Procedi solo se il portafogli è corrotto e non si carica!</string> <string name="deletecache_alert_message">Il reset del portafogli cancellerà tutte le informazioni locali (note, nomi di account &amp; sottoindirizzi, chiavi di transazione private, ...)! Procedi solo se il portafogli è corrotto e non si carica!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -423,4 +423,25 @@
]]></string> ]]></string>
<string name="help_ok">分かりました!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">分かりました!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -236,7 +236,7 @@
<string name="send_create_tx_error_title">取引作成エラー</string> <string name="send_create_tx_error_title">取引作成エラー</string>
<string name="tx_list_fee">- 手数料 %1$s</string> <string name="tx_list_fee">手数料 %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">失敗</string> <string name="tx_list_failed_text">失敗</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -430,5 +430,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -292,4 +292,25 @@
]]></string> ]]></string>
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -230,7 +230,7 @@
<string name="send_create_tx_error_title">Error med å lage transaksjon</string> <string name="send_create_tx_error_title">Error med å lage transaksjon</string>
<string name="tx_list_fee">- Avgift %1$s</string> <string name="tx_list_fee">Avgift %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">feila</string> <string name="tx_list_failed_text">feila</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -427,5 +427,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -29,7 +29,6 @@
<style name="AppCard" parent="Widget.MaterialComponents.CardView"> <style name="AppCard" parent="Widget.MaterialComponents.CardView">
<item name="cardElevation">0dp</item> <item name="cardElevation">0dp</item>
<item name="cardCornerRadius">1dp</item>
</style> </style>
</resources> </resources>

View File

@ -230,4 +230,25 @@
]]></string> ]]></string>
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -227,7 +227,7 @@
<string name="send_create_tx_error_title">Fout bij transactie maken</string> <string name="send_create_tx_error_title">Fout bij transactie maken</string>
<string name="tx_list_fee">- kosten %1$s</string> <string name="tx_list_fee">kosten %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">mislukt</string> <string name="tx_list_failed_text">mislukt</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -427,5 +427,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -291,4 +291,25 @@
]]></string> ]]></string>
<string name="help_ok">Entendi!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Entendi!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -229,7 +229,7 @@
<string name="send_create_tx_error_title">Erro ao criar a transação</string> <string name="send_create_tx_error_title">Erro ao criar a transação</string>
<string name="tx_list_fee">- taxa de %1$s</string> <string name="tx_list_fee">taxa de %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">falhou</string> <string name="tx_list_failed_text">falhou</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -421,4 +421,9 @@ aqui.</string>
<string name="menu_deletecache">Resetar carteira!</string> <string name="menu_deletecache">Resetar carteira!</string>
<string name="deletecache_alert_message">Esta carteira será resetada, perdendo todos os dados "off-chain" (como notas, contas &amp; nomes de subendereços, chaves de transações privadas, ...)! Use isso SOMENTE se esta carteira estiver corrompida e não carrega!</string> <string name="deletecache_alert_message">Esta carteira será resetada, perdendo todos os dados "off-chain" (como notas, contas &amp; nomes de subendereços, chaves de transações privadas, ...)! Use isso SOMENTE se esta carteira estiver corrompida e não carrega!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -291,4 +291,25 @@
]]></string> ]]></string>
<string name="help_ok">Entendido!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Entendido!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -229,7 +229,7 @@
<string name="send_create_tx_error_title">Erro ao criar a transacção</string> <string name="send_create_tx_error_title">Erro ao criar a transacção</string>
<string name="tx_list_fee">- Taxa %1$s</string> <string name="tx_list_fee">Taxa %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">falhou</string> <string name="tx_list_failed_text">falhou</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -431,5 +431,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -298,4 +298,25 @@
]]></string> ]]></string>
<string name="help_ok">Am înțeles!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Am înțeles!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -205,7 +205,7 @@
<string name="send_create_tx_error_title">Eroare creare tranzacție</string> <string name="send_create_tx_error_title">Eroare creare tranzacție</string>
<string name="tx_list_fee">- Comision %1$s</string> <string name="tx_list_fee">Comision %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">eșuat</string> <string name="tx_list_failed_text">eșuat</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -427,5 +427,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -297,4 +297,25 @@
]]></string> ]]></string>
<string name="help_ok">Я понял!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Я понял!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -231,7 +231,7 @@
<string name="send_create_tx_error_title">Ошибка создания транзакции</string> <string name="send_create_tx_error_title">Ошибка создания транзакции</string>
<string name="tx_list_fee">- Комиссия %1$s</string> <string name="tx_list_fee">Комиссия %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">Не удалось</string> <string name="tx_list_failed_text">Не удалось</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -431,5 +431,10 @@
<string name="restore_failed">Ошибка импорта!</string> <string name="restore_failed">Ошибка импорта!</string>
<string name="menu_deletecache">Сбросить кошелек</string> <string name="menu_deletecache">Сбросить кошелек</string>
<string name="deletecache_alert_message">Этот кошелек будет сброшен, вы потеряете все данные, которые не находятся в блокчейне (например примечания, имена подадресов, приватные ключи транзакций, ...)! Используйте это ТОЛЬКО если ваш кошелек поврежден и не загружается!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -259,4 +259,25 @@
]]></string> ]]></string>
<string name="help_ok">Rozumiem!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Rozumiem!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -228,7 +228,7 @@
<string name="send_create_tx_error_title">Vytvor Chybu Transakcie</string> <string name="send_create_tx_error_title">Vytvor Chybu Transakcie</string>
<string name="tx_list_fee">- poplatok %1$s</string> <string name="tx_list_fee">poplatok %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">zlyhala</string> <string name="tx_list_failed_text">zlyhala</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -428,5 +428,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -290,4 +290,25 @@
]]></string> ]]></string>
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -237,7 +237,7 @@
<string name="send_create_tx_error_title">Kreiraj transakcijsku grešku</string> <string name="send_create_tx_error_title">Kreiraj transakcijsku grešku</string>
<string name="tx_list_fee">- naknada %1$s</string> <string name="tx_list_fee">naknada %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">neuspelo</string> <string name="tx_list_failed_text">neuspelo</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -426,5 +426,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -279,4 +279,25 @@
]]></string> ]]></string>
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -218,7 +218,7 @@
<string name="send_create_tx_error_title">Ett fel uppstod när transaktion skapades</string> <string name="send_create_tx_error_title">Ett fel uppstod när transaktion skapades</string>
<string name="tx_list_fee">- Avgift %1$s</string> <string name="tx_list_fee">Avgift %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">misslyckades</string> <string name="tx_list_failed_text">misslyckades</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -419,5 +419,10 @@
<string name="restore_failed">Importeringen misslyckad!</string> <string name="restore_failed">Importeringen misslyckad!</string>
<string name="menu_deletecache">Återställ plånbok!</string> <string name="menu_deletecache">Återställ plånbok!</string>
<string name="deletecache_alert_message">Denna plånbok kommer att återställas och förlorar all off-chain data (som anteckningar, konto- och subadresser, privata transaktionsnycklar, ...)! Använd ENDAST detta om den här plånboken är skadad och inte laddas!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -4,18 +4,18 @@
<h1>Створити гаманець - Новий</h1> <h1>Створити гаманець - Новий</h1>
<p>Якщо вам потрібна нова адреса Monero!</p> <p>Якщо вам потрібна нова адреса Monero!</p>
<p>Введіть унікальне і\'мя та пароль гаманця. <p>Введіть унікальне і\'мя та пароль гаманця.
        Пароль використовується для захисту даних вашого гаманця на Android пристрої. Пароль використовується для захисту даних вашого гаманця на Android пристрої.
Необхідно використовувати надійний пароль. Краще навіть використовувати фразу-пароль. Необхідно використовувати надійний пароль. Краще навіть використовувати фразу-пароль.
</p> </p>
<h2>Запишіть вашу мнемонічну фразу!</h2> <h2>Запишіть вашу мнемонічну фразу!</h2>
<p>Наступний екран буде містити вашу \"мнемонічну фразу\", що складається з 25 слів. <p>Наступний екран буде містити вашу \"мнемонічну фразу\", що складається з 25 слів.
        Це єдині дані, які необхідні для відновлення вашого гаманця в Це єдині дані, які необхідні для відновлення вашого гаманця в
        майбутньому і отримання доступу до ваших коштів. майбутньому і отримання доступу до ваших коштів.
        Підтримувати її безпеку і конфіденційність дуже важливо, так як це Підтримувати її безпеку і конфіденційність дуже важливо, так як це
     гарантує, що ніхто не зможе отримати доступ до ваших коштів!</p> гарантує, що ніхто не зможе отримати доступ до ваших коштів!</p>
<p>Якщо ви втратите пароль від вашого гаманця, ви зможете відновити його, використовуючи мнемонічну фразу.</p> <p>Якщо ви втратите пароль від вашого гаманця, ви зможете відновити його, використовуючи мнемонічну фразу.</p>
<p>Спосіб відновлення мнемонічної фрази відсутній. У разі її втрати - будуть втрачені <p>Спосіб відновлення мнемонічної фрази відсутній. У разі її втрати - будуть втрачені
     і всі ваші кошти! Також ви ніколи не зможете змінити мнемонічну фразу, і якщо вона і всі ваші кошти! Також ви ніколи не зможете змінити мнемонічну фразу, і якщо вона
буде вкрадена або розкрита будь-яким іншим чином, то вам доведеться перенести ваші кошти буде вкрадена або розкрита будь-яким іншим чином, то вам доведеться перенести ваші кошти
в новий гаманець (з новою мнемонічною фразою). Тому рекомендується створити резервну копію в новий гаманець (з новою мнемонічною фразою). Тому рекомендується створити резервну копію
вашої мнемонічною фрази, записавши її, і зберігати декілька копій в безпечному місці.</p> вашої мнемонічною фрази, записавши її, і зберігати декілька копій в безпечному місці.</p>
@ -61,7 +61,7 @@
не боячись втратити свої Monero. Monero відправлятимуть на ваш гаманець, використовуючи саме цю адресу. не боячись втратити свої Monero. Monero відправлятимуть на ваш гаманець, використовуючи саме цю адресу.
<h2>Мнемонічна фраза</h2> <h2>Мнемонічна фраза</h2>
Це єдині дані, які необхідні для відновлення вашого гаманця в майбутньому, і отримання Це єдині дані, які необхідні для відновлення вашого гаманця в майбутньому, і отримання
        доступу до ваших коштів. Підтримувати її безпеку і конфіденційність дуже важливо, доступу до ваших коштів. Підтримувати її безпеку і конфіденційність дуже важливо,
так як це гарантує, що <em>ніхто</em> не зможе отримати доступ до ваших коштів! так як це гарантує, що <em>ніхто</em> не зможе отримати доступ до ваших коштів!
Якщо ви не записали цю фразу, і не зберегли її в безпечному місці, зробіть це негайно! Якщо ви не записали цю фразу, і не зберегли її в безпечному місці, зробіть це негайно!
<h2>Файли відновлення паролю гаманця</h2> <h2>Файли відновлення паролю гаманця</h2>
@ -207,10 +207,10 @@
<p>Ваші секретні ключі ніколи не покинуть пристрій Ledger. Вам потрібен <p>Ваші секретні ключі ніколи не покинуть пристрій Ledger. Вам потрібен
пристрій кожен раз, коли ви хочете отримати доступ до свого гаманця.</p> пристрій кожен раз, коли ви хочете отримати доступ до свого гаманця.</p>
<p>Необхідно ввести унікальні і&apos:мя і пароль гаманця. Пароль використовується для захисту даних вашого <p>Необхідно ввести унікальні і&apos:мя і пароль гаманця. Пароль використовується для захисту даних вашого
         гаманця на пристрої. Необхідно використовувати надійний пароль. Краще навіть використовувати фразу-пароль.</p> гаманця на пристрої. Необхідно використовувати надійний пароль. Краще навіть використовувати фразу-пароль.</p>
<p>Якщо вам відомий номер блоку першої транзакції, який використовувався з цією адресою, слід <p>Якщо вам відомий номер блоку першої транзакції, який використовувався з цією адресою, слід
         ввести його в поле \"Відновити висоту\". Ви також можете використовувати дату в форматі YYYY-MM-DD. ввести його в поле \"Відновити висоту\". Ви також можете використовувати дату в форматі YYYY-MM-DD.
  Якщо ви не впевнені, введіть приблизну дату/висоту блоку <em>до</em> того, як ви вперше використали цей гаманець.</p> Якщо ви не впевнені, введіть приблизну дату/висоту блоку <em>до</em> того, як ви вперше використали цей гаманець.</p>
]]></string> ]]></string>
<string name="help_wallet"><![CDATA[ <string name="help_wallet"><![CDATA[
@ -229,7 +229,7 @@
<p><b>Допоможіть! Баланс мого гаманця кудись зник або не підтверджений!</b><br/> <p><b>Допоможіть! Баланс мого гаманця кудись зник або не підтверджений!</b><br/>
Не панікуйте! Якщо ви відправляєте кошти з вашого гаманця, ваш баланс тимчасово залишається Не панікуйте! Якщо ви відправляєте кошти з вашого гаманця, ваш баланс тимчасово залишається
непідтвердженим. Це відбувається в результаті процесу обміну Monero в блокчейні, і роботи механізму непідтвердженим. Це відбувається в результаті процесу обміну Monero в блокчейні, і роботи механізму
        решти. Подробиці обміну можна дізнатися за посиланням: решти. Подробиці обміну можна дізнатися за посиланням:
https://getmonero.org/resources/moneropedia/change.html https://getmonero.org/resources/moneropedia/change.html
<h2>Список транзакцій</h2> <h2>Список транзакцій</h2>
<p>Список транзакцій, проведених з використанням цього гаманця. У гаманцях перегляду можна побачити <p>Список транзакцій, проведених з використанням цього гаманця. У гаманцях перегляду можна побачити
@ -278,8 +278,6 @@
Сканування зупиниться, як тільки буде знайдено 10 віддалених вузлів.</p> Сканування зупиниться, як тільки буде знайдено 10 віддалених вузлів.</p>
]]></string> ]]></string>
<string name="help_uri"><![CDATA[ <string name="help_uri"><![CDATA[
<h1>Використання плажіного посилання</h1> <h1>Використання плажіного посилання</h1>
<p>Ви почали використовувати Monerujo з платіжним посиланням. Для надсилання коштів, будь ласка, виконайте найступне:</p> <p>Ви почали використовувати Monerujo з платіжним посиланням. Для надсилання коштів, будь ласка, виконайте найступне:</p>
@ -292,4 +290,25 @@
]]></string> ]]></string>
<string name="help_ok">Я зрозумів!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Я зрозумів!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -231,7 +231,7 @@
<string name="send_create_tx_error_title">Помилка створення транзакції</string> <string name="send_create_tx_error_title">Помилка створення транзакції</string>
<string name="tx_list_fee">- Комісія %1$s</string> <string name="tx_list_fee">Комісія %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">Не вдалося</string> <string name="tx_list_failed_text">Не вдалося</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -431,5 +431,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -230,4 +230,25 @@
]]></string> ]]></string>
<string name="help_ok">我明白了!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">我明白了!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -182,7 +182,7 @@
<string name="send_amount">%1$s XMR</string> <string name="send_amount">%1$s XMR</string>
<string name="send_fee">+%1$s 手续费</string> <string name="send_fee">+%1$s 手续费</string>
<string name="send_create_tx_error_title">创建交易发生错误</string> <string name="send_create_tx_error_title">创建交易发生错误</string>
<string name="tx_list_fee">- 手续费 %1$s</string> <string name="tx_list_fee">手续费 %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">失败</string> <string name="tx_list_failed_text">失败</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -351,5 +351,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -231,4 +231,25 @@
]]></string> ]]></string>
<string name="help_ok">我知道了!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">我知道了!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -229,7 +229,7 @@
<string name="send_create_tx_error_title">建立交易錯誤</string> <string name="send_create_tx_error_title">建立交易錯誤</string>
<string name="tx_list_fee">- 手續費 %1$s</string> <string name="tx_list_fee">手續費 %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">失敗</string> <string name="tx_list_failed_text">失敗</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -426,5 +426,10 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -290,4 +290,25 @@
]]></string> ]]></string>
<string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" --> <string name="help_ok">Got it!</string> <!-- Note: "Got it" as in "I understand this" -->
<string name="help_nok">Nah…</string> <!-- Note: "Nah..." as in "I don't want this" -->
<string name="help_getorbot">Get Orbot!</string>
<string name="help_tor"><![CDATA[
<h1>Tor</h1>
<p>Tor, short for The Onion Router, is free and open-source software for enabling anonymous
communication.</p>
<p>Enabling Tor will route your connection through several relays and hide your IP address
from the node. Keep in mind this is more private but also <b>slower</b>.</p>
<p>In order to use Tor with Monerujo, you\'ll need Orbot installed on your phone. After
installing Orbot, make sure to enable it by clicking the network icon on the wallet list
screen.</p>
<p>If you have issues connecting with Tor, try to get a new Identity in the Orbot App (icon
on the top right).</p>
]]></string>
<string name="help_tor_enable"><![CDATA[
<h1>Tor Node</h1>
<p>This is an .onion node. In order to use it, you must enable Tor mode by touching the
<img src="ic_network_clearnet"/> icon near the top of the Wallet List page.</p>
]]></string>
</resources> </resources>

View File

@ -100,7 +100,7 @@
<string name="changepw_failed">Change Password failed!</string> <string name="changepw_failed">Change Password failed!</string>
<string name="changepw_success">Password changed</string> <string name="changepw_success">Password changed</string>
<string name="label_daemon">Node</string> <string name="label_daemon">Network</string>
<string name="connect_stagenet" translatable="false">Stagenet</string> <string name="connect_stagenet" translatable="false">Stagenet</string>
<string name="connect_testnet" translatable="false">Testnet</string> <string name="connect_testnet" translatable="false">Testnet</string>
<string name="status_wallet_loading">Loading Wallet &#8230;</string> <string name="status_wallet_loading">Loading Wallet &#8230;</string>
@ -243,7 +243,7 @@
<string name="send_create_tx_error_title">Create Transaction Error</string> <string name="send_create_tx_error_title">Create Transaction Error</string>
<string name="tx_list_fee">- Fee %1$s</string> <string name="tx_list_fee">Fee %1$s</string>
<string name="tx_list_amount_failed">(%1$s)</string> <string name="tx_list_amount_failed">(%1$s)</string>
<string name="tx_list_failed_text">failed</string> <string name="tx_list_failed_text">failed</string>
<string name="tx_list_amount_negative">- %1$s</string> <string name="tx_list_amount_negative">- %1$s</string>
@ -499,5 +499,12 @@
<string name="restore_failed">Import failed!</string> <string name="restore_failed">Import failed!</string>
<string name="menu_deletecache">Reset wallet!</string> <string name="menu_deletecache">Reset wallet!</string>
<string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, ...)! Use this ONLY if this wallet is corrupt and does not load!</string> <string name="deletecache_alert_message">This wallet will be reset, losing all off-chain data (like notes, account &amp; subaddress names, private transaction keys, &#8230;)! Use this ONLY if this wallet is corrupt and does not load!</string>
<string name="status" translatable="false">&lt;span style=\"background-color: #%1$s; color: #%2$s;\"&gt;%3$s&lt;/span&gt;%4$s</string>
<string name="node_tor_error">Tor required</string>
<string name="node_waiting">\u00A0WAITING FOR NODE\u00A0</string>
<string name="tor_enable_background">"Allow Background Starts" in Orbot Settings to use Tor!</string>
<string name="tor_noshift">SideShift.ai doesn\'t support Tor.\nDisable Tor to swap XMR.</string>
</resources> </resources>

View File

@ -36,8 +36,7 @@
</style> </style>
<style name="AppCard" parent="Widget.MaterialComponents.CardView"> <style name="AppCard" parent="Widget.MaterialComponents.CardView">
<item name="cardElevation">4dp</item> <item name="cardElevation">2dp</item>
<item name="cardCornerRadius">1dp</item>
</style> </style>
<style name="AppButton" parent="@style/Widget.MaterialComponents.Button"> <style name="AppButton" parent="@style/Widget.MaterialComponents.Button">

View File

@ -1,5 +1,4 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
repositories { repositories {
mavenCentral() mavenCentral()