1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-10 22:10:49 +02:00

Compare commits

..

8 Commits

Author SHA1 Message Date
m2049r
05720e63ab onion stuff (#796) 2021-12-05 22:24:26 +01:00
Diego Delmondes
cdc2b23257 Improve Brazillian Portuguese translation (#778)
* Improve translation

Fix typos, improve the translation in general and translate some English strings.

* Update strings.xml
2021-12-05 19:23:03 +01:00
m2049r
9f0e89719c remove arabic (#795) 2021-12-05 19:00:10 +01:00
m2049r
190726e61c Merge branch 'Fushko-it-transl' 2021-12-05 18:59:36 +01:00
m2049r
729fafdb48 fix apostrophes 2021-12-05 18:59:26 +01:00
m2049r
b7ae23ac64 Merge branch 'it-transl' of https://github.com/Fushko/xmrwallet into Fushko-it-transl 2021-12-05 18:58:32 +01:00
m2049r
27885f2c86 create wallet only by button & refactoring (#793) 2021-12-05 18:10:25 +01:00
Francesco Fusco
ce9046c7b5 Update italian translation 2021-12-01 21:36:22 +01:00
98 changed files with 2408 additions and 1793 deletions

View File

@@ -8,8 +8,8 @@ android {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 30
versionCode 1102
versionName "2.1.2 'Vertant'"
versionCode 1201
versionName "2.2.1 'René'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
@@ -113,27 +113,30 @@ android {
}
}
def getId(name) {
def Properties props = new Properties()
static def getId(name) {
Properties props = new Properties()
props.load(new FileInputStream(new File('monerujo.id')))
return props[name]
}
dependencies {
implementation 'androidx.core:core:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.core:core:1.6.0'
implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.4.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.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 "com.squareup.okhttp3:okhttp:4.9.0"
implementation "io.github.rburgst:okhttp-digest:2.5"
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 'dnsjava:dnsjava:2.1.9'
@@ -141,6 +144,7 @@ dependencies {
implementation 'org.slf4j:slf4j-nop:1.7.30'
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
//noinspection GradleDependency
testImplementation "junit:junit:$rootProject.ext.junitVersion"
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
@@ -150,3 +154,4 @@ dependencies {
compileOnly '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.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
android:name=".XmrWalletApplication"
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());
}
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);
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 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
Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance,
jint accountIndex) {

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -65,6 +65,7 @@ import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.LegacyStorageHelper;
import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper;
import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.util.ZipBackup;
@@ -701,6 +702,7 @@ public class LoginActivity extends BaseActivity
new AsyncWaitForService().execute();
}
if (!Ledger.isConnected()) attachLedger();
registerTor();
}
private class AsyncWaitForService extends AsyncTask<Void, Void, Void> {
@@ -1469,4 +1471,69 @@ public class LoginActivity extends BaseActivity
}
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.os.AsyncTask;
import android.os.Bundle;
import android.text.Html;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -28,22 +30,27 @@ import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.progressindicator.CircularProgressIndicator;
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.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.NodePinger;
import com.m2049r.xmrwallet.util.Notice;
import com.m2049r.xmrwallet.widget.Toolbar;
@@ -66,9 +73,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
private View tvGuntherSays;
private ImageView ivGunther;
private TextView tvNodeName;
private TextView tvNodeAddress;
private View pbNode;
private View llNode;
private TextView tvNodeInfo;
private ImageButton ibNetwork;
private CircularProgressIndicator pbNetwork;
private Listener activityCallback;
@@ -109,6 +116,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Set<NodeInfo> getOrPopulateFavourites();
boolean hasLedger();
void runOnNetCipher(Runnable runnable);
}
@Override
@@ -125,6 +134,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
public void onPause() {
Timber.d("onPause()");
torStatus = null;
super.onPause();
}
@@ -135,7 +145,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
activityCallback.setTitle(null);
activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS);
activityCallback.showNet();
pingSelectedNode();
showNetwork();
//activityCallback.runOnNetCipher(this::pingSelectedNode);
}
@Override
@@ -183,12 +194,14 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
ViewGroup llNotice = view.findViewById(R.id.llNotice);
Notice.showAll(llNotice, ".*_login");
pbNode = view.findViewById(R.id.pbNode);
llNode = view.findViewById(R.id.llNode);
llNode.setOnClickListener(v -> startNodePrefs());
view.findViewById(R.id.llNode).setOnClickListener(v -> startNodePrefs());
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());
ibNetwork = view.findViewById(R.id.ibNetwork);
ibNetwork.setOnClickListener(v -> changeNetwork());
ibNetwork.setEnabled(false);
pbNetwork = view.findViewById(R.id.pbNetwork);
Helper.hideKeyboard(getActivity());
@@ -387,15 +400,29 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
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> {
final static int PING_SELECTED = 0;
final static int FIND_BEST = 1;
private boolean netState;
@Override
protected void onPreExecute() {
super.onPreExecute();
pbNode.setVisibility(View.VISIBLE);
llNode.setVisibility(View.INVISIBLE);
tvNodeName.setVisibility(View.GONE);
pbNetwork.setVisibility(View.VISIBLE);
netState = ibNetwork.isClickable();
ibNetwork.setClickable(false);
setSubtext(getString(R.string.node_waiting));
}
@Override
@@ -417,8 +444,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
}
if (selectedNode == null) { // autoselect
selectedNode = autoselect(favourites);
} else
} else {
selectedNode.testRpcService();
}
} else throw new IllegalStateException();
if ((selectedNode != null) && selectedNode.isValid()) {
activityCallback.setNode(selectedNode);
@@ -432,16 +460,17 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
@Override
protected void onPostExecute(NodeInfo result) {
if (!isAdded()) return;
pbNode.setVisibility(View.INVISIBLE);
llNode.setVisibility(View.VISIBLE);
tvNodeName.setVisibility(View.VISIBLE);
pbNetwork.setVisibility(View.INVISIBLE);
ibNetwork.setClickable(netState);
if (result != null) {
Timber.d("found a good node %s", result.toString());
showNode(result);
} else {
tvNodeName.setText(getResources().getText(R.string.node_create_hint));
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
tvNodeAddress.setText(null);
tvNodeAddress.setVisibility(View.GONE);
tvNodeInfo.setText(null);
tvNodeInfo.setVisibility(View.GONE);
}
}
@@ -453,12 +482,70 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
private void showNode(NodeInfo nodeInfo) {
tvNodeName.setText(nodeInfo.getName());
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(NodeInfoAdapter.getPingIcon(nodeInfo), 0, 0, 0);
Helper.showTimeDifference(tvNodeAddress, nodeInfo.getTimestamp());
tvNodeAddress.setVisibility(View.VISIBLE);
nodeInfo.showInfo(tvNodeInfo);
tvNodeInfo.setVisibility(View.VISIBLE);
}
private void startNodePrefs() {
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.os.AsyncTask;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -59,7 +58,6 @@ import java.text.NumberFormat;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Objects;
import java.util.Set;
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
nodeItem.setSelecting(false);
try {
Objects.requireNonNull(getActivity()).runOnUiThread(() -> nodesAdapter.allowClick(true));
} catch (NullPointerException ex) {
requireActivity().runOnUiThread(() -> nodesAdapter.allowClick(true));
} catch (IllegalStateException ex) {
// it's ok
}
});
@@ -403,16 +401,12 @@ public class NodeFragment extends Fragment
etNodeHost.setError(getString(R.string.node_host_empty));
return false;
}
final boolean setHostSuccess = Helper.runWithNetwork(new Helper.Action() {
@Override
public boolean run() {
try {
nodeInfo.setHost(host);
return true;
} catch (UnknownHostException ex) {
etNodeHost.setError(getString(R.string.node_host_unresolved));
return false;
}
final boolean setHostSuccess = Helper.runWithNetwork(() -> {
try {
nodeInfo.setHost(host);
return true;
} catch (UnknownHostException ex) {
return false;
}
});
if (!setHostSuccess) {
@@ -421,14 +415,7 @@ public class NodeFragment extends Fragment
}
etNodeHost.setError(null);
nodeInfo.setRpcPort(port);
// setName() may trigger reverse DNS
Helper.runWithNetwork(new Helper.Action() {
@Override
public boolean run() {
nodeInfo.setName(etNodeName.getEditText().getText().toString().trim());
return true;
}
});
nodeInfo.setName(etNodeName.getEditText().getText().toString().trim());
nodeInfo.setUsername(etNodeUser.getEditText().getText().toString().trim());
nodeInfo.setPassword(etNodePass.getEditText().getText().toString()); // no trim for pw
return true;
@@ -532,20 +519,10 @@ public class NodeFragment extends Fragment
@Override
public void onShow(final DialogInterface dialog) {
Button testButton = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_NEUTRAL);
testButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
test();
}
});
testButton.setOnClickListener(view -> test());
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
apply();
}
});
button.setOnClickListener(view -> apply());
}
});
@@ -553,15 +530,13 @@ public class NodeFragment extends Fragment
editDialog.getWindow().setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
}
etNodePass.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
editDialog.getButton(DialogInterface.BUTTON_NEUTRAL).requestFocus();
test();
return true;
}
return false;
etNodePass.getEditText().setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) {
editDialog.getButton(DialogInterface.BUTTON_NEUTRAL).requestFocus();
test();
return true;
}
return false;
});
}
@@ -589,6 +564,7 @@ public class NodeFragment extends Fragment
} else {
nodesAdapter.setNodes();
}
nodesAdapter.notifyItemChanged(nodeInfo);
}
}
}

View File

@@ -223,11 +223,13 @@ public class WalletFragment extends Fragment
}
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);
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed));
} else {
tvUnconfirmedAmount.setText(null);
tvUnconfirmedAmount.setVisibility(View.VISIBLE);
}
}

View File

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

View File

@@ -28,7 +28,11 @@ public enum DefaultNodes {
SUPPORTXMR("node.supportxmr.com:18081"),
HASHVAULT("nodes.hashvault.pro:18081"),
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
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.WalletManager;
import com.m2049r.xmrwallet.util.OnionHelper;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
@@ -35,11 +36,63 @@ public class Node {
static public final String STAGENET = "stagenet";
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
private String name = null;
@Getter
final private NetworkType networkType;
InetAddress hostAddress;
Address hostAddress;
@Getter
private String host;
@Getter
@@ -74,6 +127,10 @@ public class Node {
&& (networkType == anotherNode.networkType));
}
public boolean isOnion() {
return hostAddress.isOnion();
}
static public Node fromString(String nodeString) {
try {
return new Node(nodeString);
@@ -205,7 +262,7 @@ public class Node {
// constructor used for created nodes from retrieved peer lists
public Node(InetSocketAddress socketAddress) {
this();
this.hostAddress = socketAddress.getAddress();
this.hostAddress = Address.of(socketAddress.getAddress());
this.host = socketAddress.getHostString();
this.rpcPort = 0; // unknown
this.levinPort = socketAddress.getPort();
@@ -225,17 +282,25 @@ public class Node {
if ((host == null) || (host.isEmpty()))
throw new UnknownHostException("loopback not supported (yet?)");
this.host = host;
this.hostAddress = InetAddress.getByName(host);
this.hostAddress = Address.of(host);
}
public void setName() {
if (name == null)
this.name = hostAddress.getHostName();
public void setDefaultName() {
if (name != null) return;
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) {
if ((name == null) || (name.isEmpty()))
this.name = hostAddress.getHostName();
setDefaultName();
else
this.name = name;
}

View File

@@ -16,14 +16,19 @@
package com.m2049r.xmrwallet.data;
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 android.content.Context;
import android.text.Html;
import android.text.Spanned;
import android.widget.TextView;
import androidx.core.content.ContextCompat;
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.OkHttpHelper;
import com.m2049r.xmrwallet.util.ThemeHelper;
import org.json.JSONException;
import org.json.JSONObject;
@@ -32,17 +37,12 @@ import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.Calendar;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;
import lombok.Setter;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import okhttp3.ResponseBody;
import timber.log.Timber;
@@ -94,7 +94,7 @@ public class NodeInfo extends Node {
synchronized public SocketAddress getLevinSocketAddress() {
if (levinSocketAddress == null) {
// 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;
}
@@ -180,7 +180,7 @@ public class NodeInfo extends Node {
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_MEDIUM = 2 * PING_GOOD; //ms
public static final double PING_BAD = HTTP_TIMEOUT;
@@ -196,32 +196,29 @@ public class NodeInfo extends Node {
return result;
}
private Request rpcServiceRequest(int port) {
final HttpUrl url = new HttpUrl.Builder()
.scheme("http")
.host(getHost())
.port(port)
.addPathSegment("json_rpc")
.build();
final String json = "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getlastblockheader\"}";
return new Request(url, json, getUsername(), getPassword());
}
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 {
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")
.host(getHostAddress())
.port(port)
.addPathSegment("json_rpc")
.build();
final RequestBody reqBody = RequestBody
.create(MediaType.parse("application/json"),
"{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getlastblockheader\"}");
Request request = OkHttpHelper.getPostRequest(url, reqBody);
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;
responseCode = response.code();
if (response.isSuccessful()) {
@@ -243,7 +240,7 @@ public class NodeInfo extends Node {
}
}
} catch (IOException | JSONException ex) {
Timber.d(ex);
Timber.d("EX: %s", ex.getMessage()); //TODO: do something here (show error?)
} finally {
tested = true;
}
@@ -264,4 +261,43 @@ public class NodeInfo extends Node {
}
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;
import android.app.Dialog;
import android.content.DialogInterface;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.text.Html;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
@@ -31,15 +35,20 @@ import androidx.fragment.app.FragmentTransaction;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.NetCipherHelper;
public class HelpFragment extends DialogFragment {
static final String TAG = "HelpFragment";
private static final String HELP_ID = "HELP_ID";
private static final String TOR_BUTTON = "TOR";
public static HelpFragment newInstance(int helpResourceId) {
HelpFragment fragment = new HelpFragment();
Bundle bundle = new Bundle();
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);
return fragment;
}
@@ -54,27 +63,53 @@ public class HelpFragment extends DialogFragment {
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
public Dialog onCreateDialog(Bundle savedInstanceState) {
final View view = LayoutInflater.from(getActivity()).inflate(R.layout.fragment_help, null);
int helpId = 0;
boolean torButton = false;
Bundle arguments = getArguments();
if (arguments != null) {
helpId = arguments.getInt(HELP_ID);
torButton = arguments.getInt(TOR_BUTTON) > 0;
}
final TextView helpTv = view.findViewById(R.id.tvHelp);
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())
.setView(view)
.setNegativeButton(R.string.help_ok,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int id) {
MaterialAlertDialogBuilder builder =
new MaterialAlertDialogBuilder(requireActivity())
.setView(view);
if (torButton) {
builder.setNegativeButton(R.string.help_nok,
(dialog, id) -> dialog.dismiss())
.setPositiveButton(R.string.help_getorbot,
(dialog, id) -> {
dialog.dismiss();
}
});
NetCipherHelper.getInstance().installOrbot(requireActivity());
});
} else {
builder.setNegativeButton(R.string.help_ok,
(dialog, id) -> dialog.dismiss());
}
return builder.create();
}
}

View File

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

View File

@@ -23,28 +23,25 @@ import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import java.net.HttpURLConnection;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.TimeZone;
public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHolder> {
private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
public interface OnInteractionListener {
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 OnInteractionListener listener;
private final Context context;
private final FragmentActivity activity;
public NodeInfoAdapter(Context context, OnInteractionListener listener) {
this.context = context;
public NodeInfoAdapter(FragmentActivity activity, OnInteractionListener listener) {
this.activity = activity;
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> {
@@ -142,7 +141,7 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
final ImageButton ibBookmark;
final View pbBookmark;
final TextView tvName;
final TextView tvIp;
final TextView tvInfo;
final ImageView ivPing;
NodeInfo nodeItem;
@@ -151,7 +150,7 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
ibBookmark = itemView.findViewById(R.id.ibBookmark);
pbBookmark = itemView.findViewById(R.id.pbBookmark);
tvName = itemView.findViewById(R.id.tvName);
tvIp = itemView.findViewById(R.id.tvAddress);
tvInfo = itemView.findViewById(R.id.tvInfo);
ivPing = itemView.findViewById(R.id.ivPing);
ibBookmark.setOnClickListener(v -> {
nodeItem.toggleFavourite();
@@ -179,13 +178,12 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
ivPing.setImageResource(getPingIcon(nodeItem));
if (nodeItem.isTested()) {
if (nodeItem.isValid()) {
Helper.showTimeDifference(tvIp, nodeItem.getTimestamp());
nodeItem.showInfo(tvInfo);
} else {
tvIp.setText(getResponseErrorText(context, nodeItem.getResponseCode()));
tvIp.setTextColor(ThemeHelper.getThemedColor(context, R.attr.colorError));
nodeItem.showInfo(tvInfo, getResponseErrorText(activity, nodeItem.getResponseCode()), true);
}
} else {
tvIp.setText(context.getResources().getString(R.string.node_testing, nodeItem.getHostAddress()));
nodeItem.showInfo(tvInfo);
}
itemView.setSelected(nodeItem.isSelected());
itemView.setClickable(itemsClickable);
@@ -201,6 +199,16 @@ public class NodeInfoAdapter extends RecyclerView.Adapter<NodeInfoAdapter.ViewHo
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
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);
allowClick(false);
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);
} else if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
return ctx.getResources().getString(R.string.node_auth_error);
} else if (responseCode == 418) {
return ctx.getResources().getString(R.string.node_tor_error);
} else {
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 {
final ImageView ivTxType;
final TextView tvAmount;
final TextView tvFee;
final TextView tvFailed;
final TextView tvPaymentId;
final TextView tvDateTime;
final CircularProgressIndicator pbConfirmations;
@@ -163,7 +163,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
super(itemView);
ivTxType = itemView.findViewById(R.id.ivTxType);
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);
tvDateTime = itemView.findViewById(R.id.tx_datetime);
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));
}
if ((infoItem.fee > 0)) {
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);
}
tvFailed.setVisibility(View.GONE);
if (infoItem.isFailed) {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, displayAmount));
this.tvFee.setText(context.getString(R.string.tx_list_failed_text));
tvFee.setVisibility(View.VISIBLE);
tvFailed.setVisibility(View.VISIBLE);
setTxColour(failedColour);
pbConfirmations.setVisibility(View.GONE);
tvConfirmations.setVisibility(View.GONE);

View File

@@ -75,7 +75,7 @@ public class Wallet {
@Override
@NonNull
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 bool trustedDaemon() const = 0;
public native boolean setProxy(String address);
public long getBalance() {
return getBalance(accountIndex);
}

View File

@@ -309,6 +309,8 @@ public class WalletManager {
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);
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.util.Helper;
import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import timber.log.Timber;
@@ -531,6 +532,7 @@ public class WalletService extends Service {
Timber.d("Using daemon %s", WalletManager.getInstance().getDaemonAddress());
showProgress(55);
wallet.init(0);
wallet.setProxy(NetCipherHelper.getProxy());
showProgress(90);
}
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.ExchangeException;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -48,26 +49,21 @@ import javax.xml.parsers.ParserConfigurationException;
import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class ExchangeApiImpl implements ExchangeApi {
@NonNull
private final OkHttpClient okHttpClient;
@NonNull
private final HttpUrl baseUrl;
//so we can inject the mockserver url
@VisibleForTesting
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, @NonNull final HttpUrl baseUrl) {
this.okHttpClient = okHttpClient;
public ExchangeApiImpl(@NonNull final HttpUrl baseUrl) {
this.baseUrl = baseUrl;
}
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
this(okHttpClient, HttpUrl.parse("https://www.ecb.europa.eu/stats/eurofxref/eurofxref-daily.xml"));
public ExchangeApiImpl() {
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
}
@@ -122,9 +118,8 @@ public class ExchangeApiImpl implements ExchangeApi {
}
}
final Request httpRequest = createHttpRequest(baseUrl);
okHttpClient.newCall(httpRequest).enqueue(new okhttp3.Callback() {
final NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(baseUrl);
httpRequest.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(final Call call, final IOException 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<>();
private Calendar fxDate = 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.ExchangeRate;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import org.json.JSONArray;
import org.json.JSONException;
@@ -33,27 +34,21 @@ import java.io.IOException;
import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;
import timber.log.Timber;
public class ExchangeApiImpl implements ExchangeApi {
@NonNull
private final OkHttpClient okHttpClient;
private final HttpUrl baseUrl;
//so we can inject the mockserver url
@VisibleForTesting
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient, final HttpUrl baseUrl) {
this.okHttpClient = okHttpClient;
public ExchangeApiImpl(final HttpUrl baseUrl) {
this.baseUrl = baseUrl;
}
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
this(okHttpClient, HttpUrl.parse("https://api.kraken.com/0/public/Ticker"));
public ExchangeApiImpl() {
this(HttpUrl.parse("https://api.kraken.com/0/public/Ticker"));
}
@Override
@@ -86,9 +81,8 @@ public class ExchangeApiImpl implements ExchangeApi {
.addQueryParameter("pair", base + (quote.equals("BTC") ? "XBT" : quote))
.build();
final Request httpRequest = createHttpRequest(url);
okHttpClient.newCall(httpRequest).enqueue(new okhttp3.Callback() {
final NetCipherHelper.Request httpRequest = new NetCipherHelper.Request(url);
httpRequest.enqueue(new okhttp3.Callback() {
@Override
public void onFailure(final Call call, final IOException ex) {
callback.onError(ex);
@@ -127,11 +121,4 @@ public class ExchangeApiImpl implements ExchangeApi {
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 {
static public final String BASE_FIAT = "EUR";
@NonNull
private final OkHttpClient okHttpClient;
public ExchangeApiImpl(@NonNull final OkHttpClient okHttpClient) {
this.okHttpClient = okHttpClient;
}
@Override
public void queryExchangeRate(@NonNull final String baseCurrency, @NonNull final String quoteCurrency,
@NonNull final ExchangeCallback callback) {
@@ -61,13 +54,13 @@ public class ExchangeApiImpl implements ExchangeApi {
final String quote = Helper.BASE_CRYPTO.equals(baseCurrency) ? quoteCurrency : baseCurrency;
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() {
@Override
public void onSuccess(final ExchangeRate krakenRate) {
Timber.d("kraken = %f", krakenRate.getRate());
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() {
@Override
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.RequestQuote;
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.JSONObject;
@@ -39,22 +39,14 @@ import java.io.IOException;
import okhttp3.Call;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import timber.log.Timber;
public class SideShiftApiImpl implements SideShiftApi, ShiftApiCall {
@NonNull
private final OkHttpClient okHttpClient;
private final HttpUrl baseUrl;
public SideShiftApiImpl(@NonNull final OkHttpClient okHttpClient, final HttpUrl baseUrl) {
this.okHttpClient = okHttpClient;
public SideShiftApiImpl(final HttpUrl baseUrl) {
this.baseUrl = baseUrl;
}
@@ -95,9 +87,9 @@ public class SideShiftApiImpl implements SideShiftApi, ShiftApiCall {
final HttpUrl url = baseUrl.newBuilder()
.addPathSegments(path)
.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
public void onFailure(final Call call, final IOException 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 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 MONERO_DIR = "monero";
@@ -606,30 +606,4 @@ public class Helper {
static public boolean preventScreenshot() {
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));
}
}

File diff suppressed because it is too large Load Diff

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() {
return new com.m2049r.xmrwallet.service.exchange.krakenEcb.ExchangeApiImpl(OkHttpHelper.getOkHttpClient());
return new com.m2049r.xmrwallet.service.exchange.krakenEcb.ExchangeApiImpl();
}
}

Some files were not shown because too many files have changed in this diff Show More