mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-02 15:53:04 +02:00
Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
05720e63ab | ||
![]() |
cdc2b23257 | ||
![]() |
9f0e89719c | ||
![]() |
190726e61c | ||
![]() |
729fafdb48 | ||
![]() |
b7ae23ac64 | ||
![]() |
27885f2c86 | ||
![]() |
ce9046c7b5 | ||
![]() |
506e6ce017 | ||
![]() |
f07b439731 | ||
![]() |
ac70ba8424 | ||
![]() |
84ec1ef418 | ||
![]() |
b576a9de3d | ||
![]() |
148faa00e4 | ||
![]() |
e82b471c14 | ||
![]() |
9ed92e5117 | ||
![]() |
303b3aa354 | ||
![]() |
d801a50962 | ||
![]() |
9cd8a75dc6 | ||
![]() |
002dfd5d58 | ||
![]() |
54e54b2a8a | ||
![]() |
aa768596a4 | ||
![]() |
c4958f6c54 | ||
![]() |
2c2a5314d4 | ||
![]() |
669516c60b |
@@ -171,6 +171,10 @@ add_library(wallet-crypto STATIC IMPORTED)
|
||||
set_target_properties(wallet-crypto PROPERTIES IMPORTED_LOCATION
|
||||
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libwallet-crypto.a)
|
||||
|
||||
add_library(cryptonote_format_utils_basic STATIC IMPORTED)
|
||||
set_target_properties(cryptonote_format_utils_basic PROPERTIES IMPORTED_LOCATION
|
||||
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libcryptonote_format_utils_basic.a)
|
||||
|
||||
#############
|
||||
# System
|
||||
#############
|
||||
@@ -193,6 +197,7 @@ target_link_libraries( monerujo
|
||||
wallet
|
||||
cryptonote_core
|
||||
cryptonote_basic
|
||||
cryptonote_format_utils_basic
|
||||
mnemonics
|
||||
ringct
|
||||
ringct_basic
|
||||
|
@@ -2,14 +2,14 @@ apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion '29.0.3'
|
||||
buildToolsVersion '30.0.3'
|
||||
ndkVersion '17.2.4988734'
|
||||
defaultConfig {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 1005
|
||||
versionName "2.0.5 'Puginarug'"
|
||||
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,8 +144,7 @@ dependencies {
|
||||
implementation 'org.slf4j:slf4j-nop:1.7.30'
|
||||
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
|
||||
|
||||
implementation 'com.github.aelstad:keccakj:1.1.0'
|
||||
|
||||
//noinspection GradleDependency
|
||||
testImplementation "junit:junit:$rootProject.ext.junitVersion"
|
||||
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
|
||||
@@ -152,3 +154,4 @@ dependencies {
|
||||
compileOnly 'org.projectlombok:lombok:1.18.16'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.16'
|
||||
}
|
||||
|
||||
|
@@ -4,18 +4,37 @@
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<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:preserveLegacyExternalStorage="true"
|
||||
android:name=".XmrWalletApplication"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:preserveLegacyExternalStorage="true"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/MyMaterialTheme"
|
||||
android:usesCleartextTraffic="true">
|
||||
@@ -34,7 +53,7 @@
|
||||
android:configChanges="orientation|keyboardHidden|uiMode"
|
||||
android:label="@string/wallet_activity_name"
|
||||
android:launchMode="singleTask"
|
||||
android:screenOrientation="behind"/>
|
||||
android:screenOrientation="behind" />
|
||||
<activity
|
||||
android:name=".LoginActivity"
|
||||
android:configChanges="orientation|keyboardHidden|uiMode"
|
||||
|
@@ -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) {
|
||||
@@ -914,7 +935,7 @@ Java_com_m2049r_xmrwallet_model_Wallet_refreshAsync(JNIEnv *env, jobject instanc
|
||||
|
||||
//virtual void rescanBlockchainAsync() = 0;
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_Wallet_rescanBlockchainAsync(JNIEnv *env, jobject instance) {
|
||||
Java_com_m2049r_xmrwallet_model_Wallet_rescanBlockchainAsyncJ(JNIEnv *env, jobject instance) {
|
||||
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
|
||||
wallet->rescanBlockchainAsync();
|
||||
}
|
||||
|
@@ -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
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -578,7 +578,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
|
||||
try {
|
||||
final WalletFragment walletFragment = getWalletFragment();
|
||||
if (wallet.isSynchronized()) {
|
||||
Timber.d("onRefreshed() synced");
|
||||
releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced
|
||||
if (!synced) { // first sync
|
||||
onProgress(-1);
|
||||
|
@@ -58,7 +58,6 @@ import java.text.NumberFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -128,7 +127,7 @@ public class WalletFragment extends Fragment
|
||||
currencies.add(Helper.BASE_CRYPTO);
|
||||
if (Helper.SHOW_EXCHANGERATES)
|
||||
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
|
||||
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()), R.layout.item_spinner_balance, currencies);
|
||||
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner_balance, currencies);
|
||||
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
sCurrency.setAdapter(spinnerAdapter);
|
||||
|
||||
@@ -224,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -346,13 +347,18 @@ public class WalletFragment extends Fragment
|
||||
// if account index has changed scroll to top?
|
||||
private int accountIndex = 0;
|
||||
|
||||
public void onRefreshed(final Wallet wallet, final boolean full) {
|
||||
public void onRefreshed(final Wallet wallet, boolean full) {
|
||||
Timber.d("onRefreshed(%b)", full);
|
||||
|
||||
if (adapter.needsTransactionUpdateOnNewBlock()) {
|
||||
wallet.refreshHistory();
|
||||
full = true;
|
||||
}
|
||||
if (full) {
|
||||
List<TransactionInfo> list = new ArrayList<>();
|
||||
final long streetHeight = activityCallback.getStreetModeHeight();
|
||||
Timber.d("StreetHeight=%d", streetHeight);
|
||||
wallet.refreshHistory();
|
||||
for (TransactionInfo info : wallet.getHistory().getAll()) {
|
||||
Timber.d("TxHeight=%d, Label=%s", info.blockheight, info.subaddressLabel);
|
||||
if ((info.isPending || (info.blockheight >= streetHeight))
|
||||
@@ -561,7 +567,7 @@ public class WalletFragment extends Fragment
|
||||
//TODO figure out why gunther disappears on return from send although he is still set
|
||||
if (enable) {
|
||||
if (streetGunther == null)
|
||||
streetGunther = ContextCompat.getDrawable(Objects.requireNonNull(getContext()), R.drawable.ic_gunther_streetmode);
|
||||
streetGunther = ContextCompat.getDrawable(requireContext(), R.drawable.ic_gunther_streetmode);
|
||||
ivStreetGunther.setImageDrawable(streetGunther);
|
||||
} else
|
||||
ivStreetGunther.setImageDrawable(null);
|
||||
|
@@ -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));
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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() ? " .onion " : ""), " " + 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);
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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() {
|
||||
@@ -349,7 +359,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
|
||||
|
||||
private boolean checkAddress() {
|
||||
boolean ok = checkAddressNoError();
|
||||
if (!ok) {
|
||||
if (possibleCryptos.isEmpty()) {
|
||||
etAddress.setError(getString(R.string.send_address_invalid));
|
||||
} else {
|
||||
etAddress.setError(null);
|
||||
@@ -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);
|
||||
|
@@ -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;
|
||||
@@ -90,7 +89,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onValidateFields() {
|
||||
Timber.i(maxBtc + "/" + minBtc);
|
||||
@@ -256,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
||||
@@ -359,40 +358,22 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
|
||||
private RequestQuote xmrtoQuote = null;
|
||||
private int stageARetries = 0;
|
||||
private final int RETRIES = 3;
|
||||
private double stageAPrice = 0;
|
||||
|
||||
private void processStageA(final RequestQuote requestQuote) {
|
||||
Timber.d("processCreateOrder %s", requestQuote.getId());
|
||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||
// verify the BTC amount is correct (price can change and we can only specify XMR amount)
|
||||
// verify the BTC amount is correct
|
||||
if (requestQuote.getBtcAmount() != txDataBtc.getBtcAmount()) {
|
||||
if (--stageARetries <= 0) {
|
||||
Timber.d("Failed to get quote");
|
||||
getView().post(() ->
|
||||
showStageError(ShiftError.Error.SERVICE.toString(),
|
||||
getString(R.string.shift_noquote),
|
||||
getString(R.string.shift_checkamount)));
|
||||
return; // just stop for now
|
||||
}
|
||||
if (stageAPrice == requestQuote.getPrice()) {
|
||||
// same price but different BTC amount - something else is wrong (e.g. too many decimals)
|
||||
Timber.d("Price unchanged");
|
||||
getView().post(() ->
|
||||
showStageError(ShiftError.Error.SERVICE.toString(),
|
||||
getString(R.string.shift_noquote),
|
||||
getString(R.string.shift_checkamount)));
|
||||
return; // just stop for now
|
||||
}
|
||||
stageAPrice = requestQuote.getPrice();
|
||||
// recalc XMR and try again
|
||||
txDataBtc.setAmount(txDataBtc.getBtcAmount() / requestQuote.getPrice());
|
||||
getView().post(this::stageAOneShot);
|
||||
return; // stageA will run in the main thread
|
||||
Timber.d("Failed to get quote");
|
||||
getView().post(() -> showStageError(ShiftError.Error.SERVICE.toString(),
|
||||
getString(R.string.shift_noquote),
|
||||
getString(R.string.shift_checkamount)));
|
||||
return; // just stop for now
|
||||
}
|
||||
xmrtoQuote = requestQuote;
|
||||
txDataBtc.setAmount(xmrtoQuote.getXmrAmount());
|
||||
getView().post(() -> {
|
||||
// show data from the actual quote as that is what is used to
|
||||
NumberFormat df = NumberFormat.getInstance(Locale.US);
|
||||
df.setMaximumFractionDigits(12);
|
||||
final String btcAmount = df.format(xmrtoQuote.getBtcAmount());
|
||||
@@ -438,18 +419,12 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
|
||||
private void stageA() {
|
||||
stageARetries = RETRIES;
|
||||
stageAOneShot();
|
||||
}
|
||||
|
||||
private void stageAOneShot() {
|
||||
if (!isResumed) return;
|
||||
Timber.d("Request Quote");
|
||||
xmrtoQuote = null;
|
||||
xmrtoOrder = null;
|
||||
showProgress(1, getString(R.string.label_send_progress_xmrto_create));
|
||||
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
|
||||
stageAPrice = 0;
|
||||
|
||||
ShiftCallback<RequestQuote> callback = new ShiftCallback<RequestQuote>() {
|
||||
@Override
|
||||
@@ -473,7 +448,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
|
||||
}
|
||||
};
|
||||
|
||||
getXmrToApi().requestQuote(txDataBtc.getAmountAsDouble(), callback);
|
||||
getXmrToApi().requestQuote(txDataBtc.getBtcAmount(), callback);
|
||||
}
|
||||
|
||||
private CreateOrder xmrtoOrder = null;
|
||||
@@ -567,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -30,6 +30,7 @@ import androidx.core.content.ContextCompat;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.google.android.material.progressindicator.CircularProgressIndicator;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.data.Crypto;
|
||||
import com.m2049r.xmrwallet.data.UserNotes;
|
||||
@@ -48,7 +49,7 @@ import java.util.TimeZone;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfoAdapter.ViewHolder> {
|
||||
private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
||||
private final static SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
|
||||
|
||||
private final int outboundColour;
|
||||
private final int inboundColour;
|
||||
@@ -77,6 +78,10 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
DATETIME_FORMATTER.setTimeZone(tz);
|
||||
}
|
||||
|
||||
public boolean needsTransactionUpdateOnNewBlock() {
|
||||
return (infoItems.size() > 0) && !infoItems.get(0).isConfirmed();
|
||||
}
|
||||
|
||||
private static class TransactionInfoDiff extends DiffCallback<TransactionInfo> {
|
||||
|
||||
public TransactionInfoDiff(List<TransactionInfo> oldList, List<TransactionInfo> newList) {
|
||||
@@ -95,6 +100,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
return (oldItem.direction == newItem.direction)
|
||||
&& (oldItem.isPending == newItem.isPending)
|
||||
&& (oldItem.isFailed == newItem.isFailed)
|
||||
&& ((oldItem.confirmations == newItem.confirmations) || (oldItem.isConfirmed()))
|
||||
&& (oldItem.subaddressLabel.equals(newItem.subaddressLabel))
|
||||
&& (Objects.equals(oldItem.notes, newItem.notes));
|
||||
}
|
||||
@@ -146,18 +152,23 @@ 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;
|
||||
final TextView tvConfirmations;
|
||||
TransactionInfo infoItem;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
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);
|
||||
pbConfirmations.setMax(TransactionInfo.CONFIRMATION);
|
||||
tvConfirmations = itemView.findViewById(R.id.tvConfirmations);
|
||||
}
|
||||
|
||||
private String getDateTime(long time) {
|
||||
@@ -182,7 +193,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
ivTxType.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
ivTxType.setVisibility(View.GONE); // gives us more space for the amount
|
||||
ivTxType.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
String displayAmount = Helper.getDisplayAmount(infoItem.amount, Helper.DISPLAY_DIGITS_INFO);
|
||||
@@ -192,25 +203,39 @@ 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);
|
||||
} else if (infoItem.isPending) {
|
||||
setTxColour(pendingColour);
|
||||
pbConfirmations.setVisibility(View.GONE);
|
||||
pbConfirmations.setIndeterminate(true);
|
||||
pbConfirmations.setVisibility(View.VISIBLE);
|
||||
tvConfirmations.setVisibility(View.GONE);
|
||||
} else if (infoItem.direction == TransactionInfo.Direction.Direction_In) {
|
||||
setTxColour(inboundColour);
|
||||
if (!infoItem.isConfirmed()) {
|
||||
pbConfirmations.setVisibility(View.VISIBLE);
|
||||
final int confirmations = (int) infoItem.confirmations;
|
||||
pbConfirmations.setProgressCompat(confirmations, true);
|
||||
final String confCount = Integer.toString(confirmations);
|
||||
tvConfirmations.setText(confCount);
|
||||
if (confCount.length() == 1) // we only have space for character in the progress circle
|
||||
tvConfirmations.setVisibility(View.VISIBLE);
|
||||
else
|
||||
tvConfirmations.setVisibility(View.GONE);
|
||||
} else {
|
||||
pbConfirmations.setVisibility(View.GONE);
|
||||
tvConfirmations.setVisibility(View.GONE);
|
||||
}
|
||||
} else {
|
||||
setTxColour(outboundColour);
|
||||
pbConfirmations.setVisibility(View.GONE);
|
||||
tvConfirmations.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
String tag = null;
|
||||
|
@@ -43,7 +43,7 @@ public class TransactionHistory {
|
||||
this.accountIndex = accountIndex;
|
||||
}
|
||||
|
||||
public void loadNotes(Wallet wallet) {
|
||||
private void loadNotes(Wallet wallet) {
|
||||
for (TransactionInfo info : transactions) {
|
||||
info.notes = wallet.getUserNote(info.hash);
|
||||
}
|
||||
@@ -61,30 +61,22 @@ public class TransactionHistory {
|
||||
|
||||
private List<TransactionInfo> transactions = new ArrayList<>();
|
||||
|
||||
public void refreshWithNotes(Wallet wallet) {
|
||||
void refreshWithNotes(Wallet wallet) {
|
||||
refresh();
|
||||
loadNotes(wallet);
|
||||
}
|
||||
|
||||
// public void refresh() {
|
||||
// transactions = refreshJ();
|
||||
// }
|
||||
|
||||
public void refresh() {
|
||||
private void refresh() {
|
||||
List<TransactionInfo> transactionInfos = refreshJ();
|
||||
Timber.d("refreshed %d", transactionInfos.size());
|
||||
Timber.d("refresh size=%d", transactionInfos.size());
|
||||
for (Iterator<TransactionInfo> iterator = transactionInfos.iterator(); iterator.hasNext(); ) {
|
||||
TransactionInfo info = iterator.next();
|
||||
if (info.accountIndex != accountIndex) {
|
||||
iterator.remove();
|
||||
Timber.d("removed %s", info.hash);
|
||||
} else {
|
||||
Timber.d("kept %s", info.hash);
|
||||
}
|
||||
}
|
||||
transactions = transactionInfos;
|
||||
}
|
||||
|
||||
private native List<TransactionInfo> refreshJ();
|
||||
|
||||
}
|
||||
|
@@ -29,6 +29,8 @@ import lombok.RequiredArgsConstructor;
|
||||
// this is not the TransactionInfo from the API as that is owned by the TransactionHistory
|
||||
// this is a POJO for the TransactionInfoAdapter
|
||||
public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> {
|
||||
public static final int CONFIRMATION = 10; // blocks
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum Direction {
|
||||
Direction_In(0),
|
||||
@@ -98,6 +100,10 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
|
||||
this.transfers = transfers;
|
||||
}
|
||||
|
||||
public boolean isConfirmed() {
|
||||
return confirmations >= CONFIRMATION;
|
||||
}
|
||||
|
||||
public String getDisplayLabel() {
|
||||
if (subaddressLabel.isEmpty() || (Subaddress.DEFAULT_LABEL_FORMATTER.matcher(subaddressLabel).matches()))
|
||||
return ("#" + addressIndex);
|
||||
|
@@ -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);
|
||||
}
|
||||
@@ -273,12 +275,14 @@ public class Wallet {
|
||||
|
||||
public native long getDaemonBlockChainTargetHeight();
|
||||
|
||||
public native boolean isSynchronizedJ();
|
||||
boolean synced = false;
|
||||
|
||||
public boolean isSynchronized() {
|
||||
final long daemonHeight = getDaemonBlockChainHeight();
|
||||
if (daemonHeight == 0) return false;
|
||||
return isSynchronizedJ() && (getBlockChainHeight() == daemonHeight);
|
||||
return synced;
|
||||
}
|
||||
|
||||
public void setSynchronized() {
|
||||
this.synced = true;
|
||||
}
|
||||
|
||||
public static native String getDisplayAmount(long amount);
|
||||
@@ -309,7 +313,12 @@ public class Wallet {
|
||||
|
||||
public native void refreshAsync();
|
||||
|
||||
public native void rescanBlockchainAsync();
|
||||
public native void rescanBlockchainAsyncJ();
|
||||
|
||||
public void rescanBlockchainAsync() {
|
||||
synced = false;
|
||||
rescanBlockchainAsyncJ();
|
||||
}
|
||||
|
||||
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
|
||||
//TODO virtual int autoRefreshInterval() const = 0;
|
||||
@@ -391,6 +400,10 @@ public class Wallet {
|
||||
|
||||
private native long getHistoryJ();
|
||||
|
||||
public void refreshHistory() {
|
||||
getHistory().refreshWithNotes(this);
|
||||
}
|
||||
|
||||
//virtual AddressBook * addressBook() const = 0;
|
||||
//virtual void setListener(WalletListener *) = 0;
|
||||
|
||||
@@ -455,7 +468,7 @@ public class Wallet {
|
||||
|
||||
public void setSubaddressLabel(int addressIndex, String label) {
|
||||
setSubaddressLabel(accountIndex, addressIndex, label);
|
||||
getHistory().refreshWithNotes(this);
|
||||
refreshHistory();
|
||||
}
|
||||
|
||||
public native void setSubaddressLabel(int accountIndex, int addressIndex, String label);
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
||||
@@ -108,23 +109,23 @@ public class WalletService extends Service {
|
||||
Timber.d("unconfirmedMoneyReceived() %d @ %s", amount, txId);
|
||||
}
|
||||
|
||||
long lastBlockTime = 0;
|
||||
int lastTxCount = 0;
|
||||
private long lastBlockTime = 0;
|
||||
private int lastTxCount = 0;
|
||||
|
||||
public void newBlock(long height) {
|
||||
Wallet wallet = getWallet();
|
||||
final Wallet wallet = getWallet();
|
||||
if (wallet == null) throw new IllegalStateException("No wallet!");
|
||||
// don't flood with an update for every block ...
|
||||
if (lastBlockTime < System.currentTimeMillis() - 2000) {
|
||||
Timber.d("newBlock() @ %d with observer %s", height, observer);
|
||||
lastBlockTime = System.currentTimeMillis();
|
||||
Timber.d("newBlock() @ %d with observer %s", height, observer);
|
||||
if (observer != null) {
|
||||
boolean fullRefresh = false;
|
||||
updateDaemonState(wallet, wallet.isSynchronized() ? height : 0);
|
||||
if (!wallet.isSynchronized()) {
|
||||
updated = true;
|
||||
// we want to see our transactions as they come in
|
||||
wallet.getHistory().refresh();
|
||||
wallet.refreshHistory();
|
||||
int txCount = wallet.getHistory().getCount();
|
||||
if (txCount > lastTxCount) {
|
||||
// update the transaction list only if we have more than before
|
||||
@@ -145,17 +146,16 @@ public class WalletService extends Service {
|
||||
updated = true;
|
||||
}
|
||||
|
||||
public void refreshed() {
|
||||
public void refreshed() { // this means it's synced
|
||||
Timber.d("refreshed()");
|
||||
Wallet wallet = getWallet();
|
||||
final Wallet wallet = getWallet();
|
||||
if (wallet == null) throw new IllegalStateException("No wallet!");
|
||||
wallet.setSynchronized();
|
||||
if (updated) {
|
||||
updateDaemonState(wallet, wallet.getBlockChainHeight());
|
||||
wallet.refreshHistory();
|
||||
if (observer != null) {
|
||||
updateDaemonState(wallet, 0);
|
||||
wallet.getHistory().refreshWithNotes(wallet);
|
||||
if (observer != null) {
|
||||
updated = !observer.onRefreshed(wallet, true);
|
||||
}
|
||||
updated = !observer.onRefreshed(wallet, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -532,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;
|
||||
|
@@ -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;
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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) {
|
||||
|
@@ -20,21 +20,21 @@ import androidx.annotation.NonNull;
|
||||
|
||||
import com.m2049r.xmrwallet.service.shift.NetworkCallback;
|
||||
import com.m2049r.xmrwallet.service.shift.ShiftApiCall;
|
||||
import com.m2049r.xmrwallet.util.DateHelper;
|
||||
import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote;
|
||||
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
|
||||
import com.m2049r.xmrwallet.service.shift.ShiftCallback;
|
||||
import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote;
|
||||
import com.m2049r.xmrwallet.util.DateHelper;
|
||||
import com.m2049r.xmrwallet.util.ServiceHelper;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
|
||||
import java.text.DecimalFormat;
|
||||
import java.text.DecimalFormatSymbols;
|
||||
import java.text.ParseException;
|
||||
import java.util.Date;
|
||||
import java.util.Locale;
|
||||
|
||||
import lombok.Getter;
|
||||
import timber.log.Timber;
|
||||
|
||||
class RequestQuoteImpl implements RequestQuote {
|
||||
@Getter
|
||||
@@ -74,10 +74,10 @@ class RequestQuoteImpl implements RequestQuote {
|
||||
price = jsonObject.getDouble("rate");
|
||||
}
|
||||
|
||||
public static void call(@NonNull final ShiftApiCall api, final double xmrAmount,
|
||||
public static void call(@NonNull final ShiftApiCall api, final double btcAmount,
|
||||
@NonNull final ShiftCallback<RequestQuote> callback) {
|
||||
try {
|
||||
final JSONObject request = createRequest(xmrAmount);
|
||||
final JSONObject request = createRequest(btcAmount);
|
||||
api.call("quotes", request, new NetworkCallback() {
|
||||
@Override
|
||||
public void onSuccess(JSONObject jsonObject) {
|
||||
@@ -101,18 +101,26 @@ class RequestQuoteImpl implements RequestQuote {
|
||||
/**
|
||||
* Create JSON request object
|
||||
*
|
||||
* @param xmrAmount how much XMR to shift to BTC
|
||||
* @param btcAmount how much XMR to shift to BTC
|
||||
*/
|
||||
|
||||
static JSONObject createRequest(final double xmrAmount) throws JSONException {
|
||||
static JSONObject createRequest(final double btcAmount) throws JSONException {
|
||||
final JSONObject jsonObject = new JSONObject();
|
||||
jsonObject.put("depositMethod", "xmr");
|
||||
jsonObject.put("settleMethod", ServiceHelper.ASSET);
|
||||
// #sideshift is silly and likes numbers as strings
|
||||
String amount = AmountFormatter.format(xmrAmount);
|
||||
jsonObject.put("depositAmount", amount);
|
||||
String amount = AmountFormatter.format(btcAmount);
|
||||
jsonObject.put("settleAmount", amount);
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
static DecimalFormat AmountFormatter = new DecimalFormat("#.############");
|
||||
static final DecimalFormat AmountFormatter;
|
||||
|
||||
static {
|
||||
AmountFormatter = new DecimalFormat();
|
||||
AmountFormatter.setDecimalFormatSymbols(new DecimalFormatSymbols(Locale.US));
|
||||
AmountFormatter.setMinimumIntegerDigits(1);
|
||||
AmountFormatter.setMaximumFractionDigits(12);
|
||||
AmountFormatter.setGroupingUsed(false);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
@@ -64,8 +56,8 @@ public class SideShiftApiImpl implements SideShiftApi, ShiftApiCall {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestQuote(final double xmrAmount, @NonNull final ShiftCallback<RequestQuote> callback) {
|
||||
RequestQuoteImpl.call(this, xmrAmount, callback);
|
||||
public void requestQuote(final double btcAmount, @NonNull final ShiftCallback<RequestQuote> callback) {
|
||||
RequestQuoteImpl.call(this, btcAmount, callback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user