1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-14 08:40:49 +02:00

Compare commits

..

28 Commits

Author SHA1 Message Date
m2049r
0712efec78 allow seed offset generation & wallet restore (#804) 2022-01-02 20:50:07 +01:00
Katant Savelev
341df6c6a3 Update RU translation (#798)
* Update help.xml

* Update strings.xml
2022-01-02 14:10:33 +01:00
m2049r
ab8fb82c1b fix unit tests (#803) 2022-01-02 14:07:23 +01:00
m2049r
22d9173cea store only through service & setPassword (#802) 2022-01-02 13:05:46 +01:00
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
m2049r
506e6ce017 bump version 2021-10-27 08:37:52 +02:00
m2049r
f07b439731 correct settleAmount formatting for non-US locales (#791) 2021-10-26 17:36:03 +02:00
m2049r
ac70ba8424 bump version 2021-09-08 13:53:57 +02:00
m2049r
84ec1ef418 load notes on refresh (#783) 2021-09-08 13:52:23 +02:00
m2049r
b576a9de3d bump version 2021-09-05 21:58:19 +02:00
m2049r
148faa00e4 minor fixes for confirmations indicator (#782) 2021-09-05 21:24:53 +02:00
m2049r
e82b471c14 adaptaions for monero v0.17.2.3 (#781) 2021-09-05 21:23:12 +02:00
m2049r
9ed92e5117 remove defunct strings in arabic 2021-08-07 12:31:42 +02:00
m2049r
303b3aa354 dont update on every single block (#776) 2021-08-07 11:58:54 +02:00
phwright
d801a50962 Arabic Translation (#767)
* Arabic Translation
2021-08-07 11:19:24 +02:00
m2049r
9cd8a75dc6 confirmations indicator (#775) 2021-08-07 11:17:13 +02:00
m2049r
002dfd5d58 rewrite Wallet.isSynchronized() (#774) 2021-08-07 11:04:18 +02:00
paxriel
54e54b2a8a Fixed boost JFrog repo (#772) 2021-08-04 10:37:31 +02:00
m2049r
aa768596a4 bump version 2021-05-21 08:49:13 +02:00
m2049r
c4958f6c54 use settleAmount (#768) 2021-05-21 08:45:45 +02:00
m2049r
2c2a5314d4 fix migration of devices with permission timeouts 2021-05-19 22:20:47 +02:00
121 changed files with 3056 additions and 1382 deletions

View File

@@ -171,6 +171,10 @@ add_library(wallet-crypto STATIC IMPORTED)
set_target_properties(wallet-crypto PROPERTIES IMPORTED_LOCATION set_target_properties(wallet-crypto PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libwallet-crypto.a) ${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 # System
############# #############
@@ -193,6 +197,7 @@ target_link_libraries( monerujo
wallet wallet
cryptonote_core cryptonote_core
cryptonote_basic cryptonote_basic
cryptonote_format_utils_basic
mnemonics mnemonics
ringct ringct
ringct_basic ringct_basic

View File

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

View File

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

View File

@@ -229,7 +229,7 @@ std::vector<std::string> java2cpp(JNIEnv *env, jobject arrayList) {
return result; return result;
} }
jobject cpp2java(JNIEnv *env, const std::vector<std::string>& vector) { jobject cpp2java(JNIEnv *env, const std::vector<std::string> &vector) {
jmethodID java_util_ArrayList_ = env->GetMethodID(class_ArrayList, "<init>", "(I)V"); jmethodID java_util_ArrayList_ = env->GetMethodID(class_ArrayList, "<init>", "(I)V");
jmethodID java_util_ArrayList_add = env->GetMethodID(class_ArrayList, "add", jmethodID java_util_ArrayList_add = env->GetMethodID(class_ArrayList, "add",
@@ -301,12 +301,13 @@ Java_com_m2049r_xmrwallet_model_WalletManager_openWalletJ(JNIEnv *env, jobject i
JNIEXPORT jlong JNICALL JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_recoveryWalletJ(JNIEnv *env, jobject instance, Java_com_m2049r_xmrwallet_model_WalletManager_recoveryWalletJ(JNIEnv *env, jobject instance,
jstring path, jstring password, jstring path, jstring password,
jstring mnemonic, jstring mnemonic, jstring offset,
jint networkType, jint networkType,
jlong restoreHeight) { jlong restoreHeight) {
const char *_path = env->GetStringUTFChars(path, nullptr); const char *_path = env->GetStringUTFChars(path, nullptr);
const char *_password = env->GetStringUTFChars(password, nullptr); const char *_password = env->GetStringUTFChars(password, nullptr);
const char *_mnemonic = env->GetStringUTFChars(mnemonic, nullptr); const char *_mnemonic = env->GetStringUTFChars(mnemonic, nullptr);
const char *_offset = env->GetStringUTFChars(offset, nullptr);
Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType); Monero::NetworkType _networkType = static_cast<Monero::NetworkType>(networkType);
Bitmonero::Wallet *wallet = Bitmonero::Wallet *wallet =
@@ -315,11 +316,14 @@ Java_com_m2049r_xmrwallet_model_WalletManager_recoveryWalletJ(JNIEnv *env, jobje
std::string(_password), std::string(_password),
std::string(_mnemonic), std::string(_mnemonic),
_networkType, _networkType,
(uint64_t) restoreHeight); (uint64_t) restoreHeight,
1, // kdf_rounds
std::string(_offset));
env->ReleaseStringUTFChars(path, _path); env->ReleaseStringUTFChars(path, _path);
env->ReleaseStringUTFChars(password, _password); env->ReleaseStringUTFChars(password, _password);
env->ReleaseStringUTFChars(mnemonic, _mnemonic); env->ReleaseStringUTFChars(mnemonic, _mnemonic);
env->ReleaseStringUTFChars(offset, _offset);
return reinterpret_cast<jlong>(wallet); return reinterpret_cast<jlong>(wallet);
} }
@@ -531,6 +535,17 @@ Java_com_m2049r_xmrwallet_model_WalletManager_resolveOpenAlias(JNIEnv *env, jobj
return env->NewStringUTF(resolvedAlias.c_str()); return env->NewStringUTF(resolvedAlias.c_str());
} }
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_setProxy(JNIEnv *env, jobject instance,
jstring address) {
const char *_address = env->GetStringUTFChars(address, nullptr);
bool rc =
Bitmonero::WalletManagerFactory::getWalletManager()->setProxy(std::string(_address));
env->ReleaseStringUTFChars(address, _address);
return rc;
}
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir); //TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
@@ -559,9 +574,12 @@ Java_com_m2049r_xmrwallet_model_WalletManager_closeJ(JNIEnv *env, jobject instan
/**********************************/ /**********************************/
JNIEXPORT jstring JNICALL JNIEXPORT jstring JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getSeed(JNIEnv *env, jobject instance) { Java_com_m2049r_xmrwallet_model_Wallet_getSeed(JNIEnv *env, jobject instance, jstring seedOffset) {
const char *_seedOffset = env->GetStringUTFChars(seedOffset, nullptr);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance); Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
return env->NewStringUTF(wallet->seed().c_str()); jstring seed = env->NewStringUTF(wallet->seed(std::string(_seedOffset)).c_str());
env->ReleaseStringUTFChars(seedOffset, _seedOffset);
return seed;
} }
JNIEXPORT jstring JNICALL JNIEXPORT jstring JNICALL
@@ -727,6 +745,16 @@ Java_com_m2049r_xmrwallet_model_Wallet_getConnectionStatusJ(JNIEnv *env, jobject
//TODO virtual void setTrustedDaemon(bool arg) = 0; //TODO virtual void setTrustedDaemon(bool arg) = 0;
//TODO virtual bool trustedDaemon() const = 0; //TODO virtual bool trustedDaemon() const = 0;
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_setProxy(JNIEnv *env, jobject instance,
jstring address) {
const char *_address = env->GetStringUTFChars(address, nullptr);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
bool rc = wallet->setProxy(std::string(_address));
env->ReleaseStringUTFChars(address, _address);
return rc;
}
JNIEXPORT jlong JNICALL JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance, Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance,
jint accountIndex) { jint accountIndex) {
@@ -914,7 +942,7 @@ Java_com_m2049r_xmrwallet_model_Wallet_refreshAsync(JNIEnv *env, jobject instanc
//virtual void rescanBlockchainAsync() = 0; //virtual void rescanBlockchainAsync() = 0;
JNIEXPORT void JNICALL 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); Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
wallet->rescanBlockchainAsync(); wallet->rescanBlockchainAsync();
} }
@@ -1241,7 +1269,7 @@ jobject newTransactionInfo(JNIEnv *env, Bitmonero::TransactionInfo *info) {
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
jobject cpp2java(JNIEnv *env, const std::vector<Bitmonero::TransactionInfo *>& vector) { jobject cpp2java(JNIEnv *env, const std::vector<Bitmonero::TransactionInfo *> &vector) {
jmethodID java_util_ArrayList_ = env->GetMethodID(class_ArrayList, "<init>", "(I)V"); jmethodID java_util_ArrayList_ = env->GetMethodID(class_ArrayList, "<init>", "(I)V");
jmethodID java_util_ArrayList_add = env->GetMethodID(class_ArrayList, "add", jmethodID java_util_ArrayList_add = env->GetMethodID(class_ArrayList, "add",

View File

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

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.LegacyStorageHelper;
import com.m2049r.xmrwallet.util.LocaleHelper; import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor; import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper; import com.m2049r.xmrwallet.util.NightmodeHelper;
import com.m2049r.xmrwallet.util.ThemeHelper; import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.util.ZipBackup; import com.m2049r.xmrwallet.util.ZipBackup;
@@ -701,6 +702,7 @@ public class LoginActivity extends BaseActivity
new AsyncWaitForService().execute(); new AsyncWaitForService().execute();
} }
if (!Ledger.isConnected()) attachLedger(); if (!Ledger.isConnected()) attachLedger();
registerTor();
} }
private class AsyncWaitForService extends AsyncTask<Void, Void, Void> { private class AsyncWaitForService extends AsyncTask<Void, Void, Void> {
@@ -927,7 +929,8 @@ public class LoginActivity extends BaseActivity
} }
@Override @Override
public void onGenerate(final String name, final String password, final String seed, public void onGenerate(final String name, final String password,
final String seed, final String offset,
final long restoreHeight) { final long restoreHeight) {
createWallet(name, password, createWallet(name, password,
new WalletCreator() { new WalletCreator() {
@@ -939,7 +942,7 @@ public class LoginActivity extends BaseActivity
@Override @Override
public boolean createWallet(File aFile, String password) { public boolean createWallet(File aFile, String password) {
Wallet newWallet = WalletManager.getInstance() Wallet newWallet = WalletManager.getInstance()
.recoveryWallet(aFile, password, seed, restoreHeight); .recoveryWallet(aFile, password, seed, offset, restoreHeight);
return checkAndCloseWallet(newWallet); return checkAndCloseWallet(newWallet);
} }
}); });
@@ -1469,4 +1472,69 @@ public class LoginActivity extends BaseActivity
} }
return usbManager; return usbManager;
} }
//
// Tor (Orbot) stuff
//
void torNotify() {
final Fragment fragment = getSupportFragmentManager().findFragmentById(R.id.fragment_container);
if (fragment == null) return;
if (fragment instanceof LoginFragment) {
runOnUiThread(((LoginFragment) fragment)::showNetwork);
}
}
private void deregisterTor() {
NetCipherHelper.deregister();
}
private void registerTor() {
NetCipherHelper.register(new NetCipherHelper.OnStatusChangedListener() {
@Override
public void connected() {
Timber.d("CONNECTED");
WalletManager.getInstance().setProxy(NetCipherHelper.getProxy());
torNotify();
if (waitingUiTask != null) {
Timber.d("RUN");
runOnUiThread(waitingUiTask);
waitingUiTask = null;
}
}
@Override
public void disconnected() {
Timber.d("DISCONNECTED");
WalletManager.getInstance().setProxy("");
torNotify();
}
@Override
public void notInstalled() {
Timber.d("NOT INSTALLED");
WalletManager.getInstance().setProxy("");
torNotify();
}
@Override
public void notEnabled() {
Timber.d("NOT ENABLED");
notInstalled();
}
});
}
private Runnable waitingUiTask;
@Override
public void runOnNetCipher(Runnable uiTask) {
if (waitingUiTask != null) throw new IllegalStateException("only one tor task at a time");
if (NetCipherHelper.hasClient()) {
runOnUiThread(uiTask);
} else {
waitingUiTask = uiTask;
}
}
} }

View File

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

View File

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

View File

@@ -67,6 +67,8 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
void setToolbarButton(int type); void setToolbarButton(int type);
void showSubaddress(View view, final int subaddressIndex); void showSubaddress(View view, final int subaddressIndex);
void saveWallet();
} }
public interface ProgressListener { public interface ProgressListener {
@@ -217,7 +219,9 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
protected Boolean doInBackground(Void... params) { protected Boolean doInBackground(Void... params) {
if (params.length != 0) return false; if (params.length != 0) return false;
wallet.getNewSubaddress(); wallet.getNewSubaddress();
wallet.store(); if (activityCallback != null) {
activityCallback.saveWallet();
}
return true; return true;
} }

View File

@@ -505,6 +505,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
Timber.d("onResume()"); Timber.d("onResume()");
} }
@Override
public void saveWallet() { public void saveWallet() {
if (mIsBound) { // no point in talking to unbound service if (mIsBound) { // no point in talking to unbound service
Intent intent = new Intent(getApplicationContext(), WalletService.class); Intent intent = new Intent(getApplicationContext(), WalletService.class);
@@ -578,7 +579,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
try { try {
final WalletFragment walletFragment = getWalletFragment(); final WalletFragment walletFragment = getWalletFragment();
if (wallet.isSynchronized()) { if (wallet.isSynchronized()) {
Timber.d("onRefreshed() synced");
releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced
if (!synced) { // first sync if (!synced) { // first sync
onProgress(-1); onProgress(-1);
@@ -610,9 +610,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override @Override
public void onWalletStored(final boolean success) { public void onWalletStored(final boolean success) {
runOnUiThread(() -> { runOnUiThread(() -> {
if (success) { if (!success) {
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unloaded), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unload_failed), Toast.LENGTH_LONG).show(); Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unload_failed), Toast.LENGTH_LONG).show();
} }
}); });

View File

@@ -58,7 +58,6 @@ import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects;
import timber.log.Timber; import timber.log.Timber;
@@ -128,7 +127,7 @@ public class WalletFragment extends Fragment
currencies.add(Helper.BASE_CRYPTO); currencies.add(Helper.BASE_CRYPTO);
if (Helper.SHOW_EXCHANGERATES) if (Helper.SHOW_EXCHANGERATES)
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency))); 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); spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sCurrency.setAdapter(spinnerAdapter); sCurrency.setAdapter(spinnerAdapter);
@@ -224,11 +223,13 @@ public class WalletFragment extends Fragment
} }
void showUnconfirmed(double unconfirmedAmount) { void showUnconfirmed(double unconfirmedAmount) {
if (!activityCallback.isStreetMode()) { if (activityCallback.isStreetMode() || unconfirmedAmount == 0) {
tvUnconfirmedAmount.setText(null);
tvUnconfirmedAmount.setVisibility(View.GONE);
} else {
String unconfirmed = Helper.getFormattedAmount(unconfirmedAmount, true); String unconfirmed = Helper.getFormattedAmount(unconfirmedAmount, true);
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed)); tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed));
} else { tvUnconfirmedAmount.setVisibility(View.VISIBLE);
tvUnconfirmedAmount.setText(null);
} }
} }
@@ -346,13 +347,18 @@ public class WalletFragment extends Fragment
// if account index has changed scroll to top? // if account index has changed scroll to top?
private int accountIndex = 0; 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); Timber.d("onRefreshed(%b)", full);
if (adapter.needsTransactionUpdateOnNewBlock()) {
wallet.refreshHistory();
full = true;
}
if (full) { if (full) {
List<TransactionInfo> list = new ArrayList<>(); List<TransactionInfo> list = new ArrayList<>();
final long streetHeight = activityCallback.getStreetModeHeight(); final long streetHeight = activityCallback.getStreetModeHeight();
Timber.d("StreetHeight=%d", streetHeight); Timber.d("StreetHeight=%d", streetHeight);
wallet.refreshHistory();
for (TransactionInfo info : wallet.getHistory().getAll()) { for (TransactionInfo info : wallet.getHistory().getAll()) {
Timber.d("TxHeight=%d, Label=%s", info.blockheight, info.subaddressLabel); Timber.d("TxHeight=%d, Label=%s", info.blockheight, info.subaddressLabel);
if ((info.isPending || (info.blockheight >= streetHeight)) 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 //TODO figure out why gunther disappears on return from send although he is still set
if (enable) { if (enable) {
if (streetGunther == null) 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); ivStreetGunther.setImageDrawable(streetGunther);
} else } else
ivStreetGunther.setImageDrawable(null); ivStreetGunther.setImageDrawable(null);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -35,7 +35,6 @@ import com.m2049r.xmrwallet.service.shift.sideshift.api.QueryOrderParameters;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl; import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.util.ServiceHelper; import com.m2049r.xmrwallet.util.ServiceHelper;
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText; import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
import com.m2049r.xmrwallet.widget.SendProgressView; import com.m2049r.xmrwallet.widget.SendProgressView;
@@ -90,7 +89,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
return view; return view;
} }
@Override @Override
public boolean onValidateFields() { public boolean onValidateFields() {
Timber.i(maxBtc + "/" + minBtc); Timber.i(maxBtc + "/" + minBtc);
@@ -256,8 +254,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
if (xmrToApi == null) { if (xmrToApi == null) {
synchronized (this) { synchronized (this) {
if (xmrToApi == null) { if (xmrToApi == null) {
xmrToApi = new SideShiftApiImpl(OkHttpHelper.getOkHttpClient(), xmrToApi = new SideShiftApiImpl(ServiceHelper.getXmrToBaseUrl());
ServiceHelper.getXmrToBaseUrl());
} }
} }
} }

View File

@@ -37,7 +37,6 @@ import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl; import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.util.ServiceHelper; import com.m2049r.xmrwallet.util.ServiceHelper;
import com.m2049r.xmrwallet.widget.SendProgressView; import com.m2049r.xmrwallet.widget.SendProgressView;
@@ -359,40 +358,22 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
} }
private RequestQuote xmrtoQuote = null; private RequestQuote xmrtoQuote = null;
private int stageARetries = 0;
private final int RETRIES = 3;
private double stageAPrice = 0;
private void processStageA(final RequestQuote requestQuote) { private void processStageA(final RequestQuote requestQuote) {
Timber.d("processCreateOrder %s", requestQuote.getId()); Timber.d("processCreateOrder %s", requestQuote.getId());
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData(); 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 (requestQuote.getBtcAmount() != txDataBtc.getBtcAmount()) {
if (--stageARetries <= 0) {
Timber.d("Failed to get quote"); Timber.d("Failed to get quote");
getView().post(() -> getView().post(() -> showStageError(ShiftError.Error.SERVICE.toString(),
showStageError(ShiftError.Error.SERVICE.toString(),
getString(R.string.shift_noquote), getString(R.string.shift_noquote),
getString(R.string.shift_checkamount))); getString(R.string.shift_checkamount)));
return; // just stop for now 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
}
xmrtoQuote = requestQuote; xmrtoQuote = requestQuote;
txDataBtc.setAmount(xmrtoQuote.getXmrAmount());
getView().post(() -> { getView().post(() -> {
// show data from the actual quote as that is what is used to
NumberFormat df = NumberFormat.getInstance(Locale.US); NumberFormat df = NumberFormat.getInstance(Locale.US);
df.setMaximumFractionDigits(12); df.setMaximumFractionDigits(12);
final String btcAmount = df.format(xmrtoQuote.getBtcAmount()); final String btcAmount = df.format(xmrtoQuote.getBtcAmount());
@@ -438,18 +419,12 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
} }
private void stageA() { private void stageA() {
stageARetries = RETRIES;
stageAOneShot();
}
private void stageAOneShot() {
if (!isResumed) return; if (!isResumed) return;
Timber.d("Request Quote"); Timber.d("Request Quote");
xmrtoQuote = null; xmrtoQuote = null;
xmrtoOrder = null; xmrtoOrder = null;
showProgress(1, getString(R.string.label_send_progress_xmrto_create)); showProgress(1, getString(R.string.label_send_progress_xmrto_create));
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData(); TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
stageAPrice = 0;
ShiftCallback<RequestQuote> callback = new ShiftCallback<RequestQuote>() { ShiftCallback<RequestQuote> callback = new ShiftCallback<RequestQuote>() {
@Override @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; private CreateOrder xmrtoOrder = null;
@@ -567,8 +542,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
if (xmrToApi == null) { if (xmrToApi == null) {
synchronized (this) { synchronized (this) {
if (xmrToApi == null) { if (xmrToApi == null) {
xmrToApi = new SideShiftApiImpl(OkHttpHelper.getOkHttpClient(), xmrToApi = new SideShiftApiImpl(ServiceHelper.getXmrToBaseUrl());
ServiceHelper.getXmrToBaseUrl());
} }
} }
} }

View File

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

View File

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

View File

@@ -30,6 +30,7 @@ import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.progressindicator.CircularProgressIndicator;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.Crypto; import com.m2049r.xmrwallet.data.Crypto;
import com.m2049r.xmrwallet.data.UserNotes; import com.m2049r.xmrwallet.data.UserNotes;
@@ -48,7 +49,7 @@ import java.util.TimeZone;
import timber.log.Timber; import timber.log.Timber;
public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfoAdapter.ViewHolder> { 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 outboundColour;
private final int inboundColour; private final int inboundColour;
@@ -77,6 +78,10 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
DATETIME_FORMATTER.setTimeZone(tz); DATETIME_FORMATTER.setTimeZone(tz);
} }
public boolean needsTransactionUpdateOnNewBlock() {
return (infoItems.size() > 0) && !infoItems.get(0).isConfirmed();
}
private static class TransactionInfoDiff extends DiffCallback<TransactionInfo> { private static class TransactionInfoDiff extends DiffCallback<TransactionInfo> {
public TransactionInfoDiff(List<TransactionInfo> oldList, List<TransactionInfo> newList) { public TransactionInfoDiff(List<TransactionInfo> oldList, List<TransactionInfo> newList) {
@@ -95,6 +100,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
return (oldItem.direction == newItem.direction) return (oldItem.direction == newItem.direction)
&& (oldItem.isPending == newItem.isPending) && (oldItem.isPending == newItem.isPending)
&& (oldItem.isFailed == newItem.isFailed) && (oldItem.isFailed == newItem.isFailed)
&& ((oldItem.confirmations == newItem.confirmations) || (oldItem.isConfirmed()))
&& (oldItem.subaddressLabel.equals(newItem.subaddressLabel)) && (oldItem.subaddressLabel.equals(newItem.subaddressLabel))
&& (Objects.equals(oldItem.notes, newItem.notes)); && (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 { class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final ImageView ivTxType; final ImageView ivTxType;
final TextView tvAmount; final TextView tvAmount;
final TextView tvFee; final TextView tvFailed;
final TextView tvPaymentId; final TextView tvPaymentId;
final TextView tvDateTime; final TextView tvDateTime;
final CircularProgressIndicator pbConfirmations;
final TextView tvConfirmations;
TransactionInfo infoItem; TransactionInfo infoItem;
ViewHolder(View itemView) { ViewHolder(View itemView) {
super(itemView); super(itemView);
ivTxType = itemView.findViewById(R.id.ivTxType); ivTxType = itemView.findViewById(R.id.ivTxType);
tvAmount = itemView.findViewById(R.id.tx_amount); tvAmount = itemView.findViewById(R.id.tx_amount);
tvFee = itemView.findViewById(R.id.tx_fee); tvFailed = itemView.findViewById(R.id.tx_failed);
tvPaymentId = itemView.findViewById(R.id.tx_paymentid); tvPaymentId = itemView.findViewById(R.id.tx_paymentid);
tvDateTime = itemView.findViewById(R.id.tx_datetime); tvDateTime = itemView.findViewById(R.id.tx_datetime);
pbConfirmations = itemView.findViewById(R.id.pbConfirmations);
pbConfirmations.setMax(TransactionInfo.CONFIRMATION);
tvConfirmations = itemView.findViewById(R.id.tvConfirmations);
} }
private String getDateTime(long time) { private String getDateTime(long time) {
@@ -182,7 +193,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
ivTxType.setVisibility(View.GONE); ivTxType.setVisibility(View.GONE);
} }
} else { } 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); 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)); tvAmount.setText(context.getString(R.string.tx_list_amount_positive, displayAmount));
} }
if ((infoItem.fee > 0)) { tvFailed.setVisibility(View.GONE);
String fee = Helper.getDisplayAmount(infoItem.fee, Helper.DISPLAY_DIGITS_INFO);
tvFee.setText(context.getString(R.string.tx_list_fee, fee));
tvFee.setVisibility(View.VISIBLE);
} else {
tvFee.setText("");
tvFee.setVisibility(View.GONE);
}
if (infoItem.isFailed) { if (infoItem.isFailed) {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, displayAmount)); this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, displayAmount));
this.tvFee.setText(context.getString(R.string.tx_list_failed_text)); tvFailed.setVisibility(View.VISIBLE);
tvFee.setVisibility(View.VISIBLE);
setTxColour(failedColour); setTxColour(failedColour);
pbConfirmations.setVisibility(View.GONE);
tvConfirmations.setVisibility(View.GONE);
} else if (infoItem.isPending) { } else if (infoItem.isPending) {
setTxColour(pendingColour); 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) { } else if (infoItem.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(inboundColour); 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 { } else {
setTxColour(outboundColour); setTxColour(outboundColour);
pbConfirmations.setVisibility(View.GONE);
tvConfirmations.setVisibility(View.GONE);
} }
String tag = null; String tag = null;

View File

@@ -43,7 +43,7 @@ public class TransactionHistory {
this.accountIndex = accountIndex; this.accountIndex = accountIndex;
} }
public void loadNotes(Wallet wallet) { private void loadNotes(Wallet wallet) {
for (TransactionInfo info : transactions) { for (TransactionInfo info : transactions) {
info.notes = wallet.getUserNote(info.hash); info.notes = wallet.getUserNote(info.hash);
} }
@@ -61,30 +61,22 @@ public class TransactionHistory {
private List<TransactionInfo> transactions = new ArrayList<>(); private List<TransactionInfo> transactions = new ArrayList<>();
public void refreshWithNotes(Wallet wallet) { void refreshWithNotes(Wallet wallet) {
refresh(); refresh();
loadNotes(wallet); loadNotes(wallet);
} }
// public void refresh() { private void refresh() {
// transactions = refreshJ();
// }
public void refresh() {
List<TransactionInfo> transactionInfos = refreshJ(); 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(); ) { for (Iterator<TransactionInfo> iterator = transactionInfos.iterator(); iterator.hasNext(); ) {
TransactionInfo info = iterator.next(); TransactionInfo info = iterator.next();
if (info.accountIndex != accountIndex) { if (info.accountIndex != accountIndex) {
iterator.remove(); iterator.remove();
Timber.d("removed %s", info.hash);
} else {
Timber.d("kept %s", info.hash);
} }
} }
transactions = transactionInfos; transactions = transactionInfos;
} }
private native List<TransactionInfo> refreshJ(); private native List<TransactionInfo> refreshJ();
} }

View File

@@ -29,6 +29,8 @@ import lombok.RequiredArgsConstructor;
// this is not the TransactionInfo from the API as that is owned by the TransactionHistory // this is not the TransactionInfo from the API as that is owned by the TransactionHistory
// this is a POJO for the TransactionInfoAdapter // this is a POJO for the TransactionInfoAdapter
public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> { public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> {
public static final int CONFIRMATION = 10; // blocks
@RequiredArgsConstructor @RequiredArgsConstructor
public enum Direction { public enum Direction {
Direction_In(0), Direction_In(0),
@@ -98,6 +100,10 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
this.transfers = transfers; this.transfers = transfers;
} }
public boolean isConfirmed() {
return confirmations >= CONFIRMATION;
}
public String getDisplayLabel() { public String getDisplayLabel() {
if (subaddressLabel.isEmpty() || (Subaddress.DEFAULT_LABEL_FORMATTER.matcher(subaddressLabel).matches())) if (subaddressLabel.isEmpty() || (Subaddress.DEFAULT_LABEL_FORMATTER.matcher(subaddressLabel).matches()))
return ("#" + addressIndex); return ("#" + addressIndex);

View File

@@ -75,7 +75,7 @@ public class Wallet {
@Override @Override
@NonNull @NonNull
public String toString() { public String toString() {
return "Wallet.Status: (" + status + "/" + errorString + ", " + connectionStatus; return "Wallet.Status: " + status + "/" + errorString + "/" + connectionStatus;
} }
} }
@@ -129,7 +129,7 @@ public class Wallet {
ConnectionStatus_WrongVersion ConnectionStatus_WrongVersion
} }
public native String getSeed(); public native String getSeed(String offset);
public native String getSeedLanguage(); public native String getSeedLanguage();
@@ -147,7 +147,7 @@ public class Wallet {
private native Status statusWithErrorString(); private native Status statusWithErrorString();
public native boolean setPassword(String password); public native synchronized boolean setPassword(String password);
public String getAddress() { public String getAddress() {
return getAddress(accountIndex); return getAddress(accountIndex);
@@ -203,12 +203,10 @@ public class Wallet {
public native String getSecretSpendKey(); public native String getSecretSpendKey();
public boolean store() { public boolean store() {
final boolean ok = store(""); return store("");
Timber.d("stored");
return ok;
} }
public native boolean store(String path); public native synchronized boolean store(String path);
public boolean close() { public boolean close() {
disposePendingTransaction(); disposePendingTransaction();
@@ -247,6 +245,8 @@ public class Wallet {
//TODO virtual void setTrustedDaemon(bool arg) = 0; //TODO virtual void setTrustedDaemon(bool arg) = 0;
//TODO virtual bool trustedDaemon() const = 0; //TODO virtual bool trustedDaemon() const = 0;
public native boolean setProxy(String address);
public long getBalance() { public long getBalance() {
return getBalance(accountIndex); return getBalance(accountIndex);
} }
@@ -273,12 +273,14 @@ public class Wallet {
public native long getDaemonBlockChainTargetHeight(); public native long getDaemonBlockChainTargetHeight();
public native boolean isSynchronizedJ(); boolean synced = false;
public boolean isSynchronized() { public boolean isSynchronized() {
final long daemonHeight = getDaemonBlockChainHeight(); return synced;
if (daemonHeight == 0) return false; }
return isSynchronizedJ() && (getBlockChainHeight() == daemonHeight);
public void setSynchronized() {
this.synced = true;
} }
public static native String getDisplayAmount(long amount); public static native String getDisplayAmount(long amount);
@@ -309,7 +311,12 @@ public class Wallet {
public native void refreshAsync(); 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 void setAutoRefreshInterval(int millis) = 0;
//TODO virtual int autoRefreshInterval() const = 0; //TODO virtual int autoRefreshInterval() const = 0;
@@ -391,6 +398,10 @@ public class Wallet {
private native long getHistoryJ(); private native long getHistoryJ();
public void refreshHistory() {
getHistory().refreshWithNotes(this);
}
//virtual AddressBook * addressBook() const = 0; //virtual AddressBook * addressBook() const = 0;
//virtual void setListener(WalletListener *) = 0; //virtual void setListener(WalletListener *) = 0;
@@ -455,7 +466,7 @@ public class Wallet {
public void setSubaddressLabel(int addressIndex, String label) { public void setSubaddressLabel(int addressIndex, String label) {
setSubaddressLabel(accountIndex, addressIndex, label); setSubaddressLabel(accountIndex, addressIndex, label);
getHistory().refreshWithNotes(this); refreshHistory();
} }
public native void setSubaddressLabel(int accountIndex, int addressIndex, String label); public native void setSubaddressLabel(int accountIndex, int addressIndex, String label);

View File

@@ -124,19 +124,19 @@ public class WalletManager {
private native long openWalletJ(String path, String password, int networkType); private native long openWalletJ(String path, String password, int networkType);
public Wallet recoveryWallet(File aFile, String password, String mnemonic) { public Wallet recoveryWallet(File aFile, String password,
return recoveryWallet(aFile, password, mnemonic, 0); String mnemonic, String offset,
} long restoreHeight) {
long walletHandle = recoveryWalletJ(aFile.getAbsolutePath(), password,
public Wallet recoveryWallet(File aFile, String password, String mnemonic, long restoreHeight) { mnemonic, offset,
long walletHandle = recoveryWalletJ(aFile.getAbsolutePath(), password, mnemonic,
getNetworkType().getValue(), restoreHeight); getNetworkType().getValue(), restoreHeight);
Wallet wallet = new Wallet(walletHandle); Wallet wallet = new Wallet(walletHandle);
manageWallet(wallet); manageWallet(wallet);
return wallet; return wallet;
} }
private native long recoveryWalletJ(String path, String password, String mnemonic, private native long recoveryWalletJ(String path, String password,
String mnemonic, String offset,
int networkType, long restoreHeight); int networkType, long restoreHeight);
public Wallet createWalletWithKeys(File aFile, String password, String language, long restoreHeight, public Wallet createWalletWithKeys(File aFile, String password, String language, long restoreHeight,
@@ -309,6 +309,8 @@ public class WalletManager {
public native String resolveOpenAlias(String address, boolean dnssec_valid); public native String resolveOpenAlias(String address, boolean dnssec_valid);
public native boolean setProxy(String address);
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir); //TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
static public native void initLogger(String argv0, String defaultLogBaseName); static public native void initLogger(String argv0, String defaultLogBaseName);

View File

@@ -45,6 +45,7 @@ import com.m2049r.xmrwallet.model.WalletListener;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.LocaleHelper; import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NetCipherHelper;
import timber.log.Timber; import timber.log.Timber;
@@ -108,23 +109,23 @@ public class WalletService extends Service {
Timber.d("unconfirmedMoneyReceived() %d @ %s", amount, txId); Timber.d("unconfirmedMoneyReceived() %d @ %s", amount, txId);
} }
long lastBlockTime = 0; private long lastBlockTime = 0;
int lastTxCount = 0; private int lastTxCount = 0;
public void newBlock(long height) { public void newBlock(long height) {
Wallet wallet = getWallet(); final Wallet wallet = getWallet();
if (wallet == null) throw new IllegalStateException("No wallet!"); if (wallet == null) throw new IllegalStateException("No wallet!");
// don't flood with an update for every block ... // don't flood with an update for every block ...
if (lastBlockTime < System.currentTimeMillis() - 2000) { if (lastBlockTime < System.currentTimeMillis() - 2000) {
Timber.d("newBlock() @ %d with observer %s", height, observer);
lastBlockTime = System.currentTimeMillis(); lastBlockTime = System.currentTimeMillis();
Timber.d("newBlock() @ %d with observer %s", height, observer);
if (observer != null) { if (observer != null) {
boolean fullRefresh = false; boolean fullRefresh = false;
updateDaemonState(wallet, wallet.isSynchronized() ? height : 0); updateDaemonState(wallet, wallet.isSynchronized() ? height : 0);
if (!wallet.isSynchronized()) { if (!wallet.isSynchronized()) {
updated = true; updated = true;
// we want to see our transactions as they come in // we want to see our transactions as they come in
wallet.getHistory().refresh(); wallet.refreshHistory();
int txCount = wallet.getHistory().getCount(); int txCount = wallet.getHistory().getCount();
if (txCount > lastTxCount) { if (txCount > lastTxCount) {
// update the transaction list only if we have more than before // update the transaction list only if we have more than before
@@ -145,21 +146,20 @@ public class WalletService extends Service {
updated = true; updated = true;
} }
public void refreshed() { public void refreshed() { // this means it's synced
Timber.d("refreshed()"); Timber.d("refreshed()");
Wallet wallet = getWallet(); final Wallet wallet = getWallet();
if (wallet == null) throw new IllegalStateException("No wallet!"); if (wallet == null) throw new IllegalStateException("No wallet!");
wallet.setSynchronized();
if (updated) { if (updated) {
if (observer != null) { updateDaemonState(wallet, wallet.getBlockChainHeight());
updateDaemonState(wallet, 0); wallet.refreshHistory();
wallet.getHistory().refreshWithNotes(wallet);
if (observer != null) { if (observer != null) {
updated = !observer.onRefreshed(wallet, true); updated = !observer.onRefreshed(wallet, true);
} }
} }
} }
} }
}
private long lastDaemonStatusUpdate = 0; private long lastDaemonStatusUpdate = 0;
private long daemonHeight = 0; private long daemonHeight = 0;
@@ -532,6 +532,7 @@ public class WalletService extends Service {
Timber.d("Using daemon %s", WalletManager.getInstance().getDaemonAddress()); Timber.d("Using daemon %s", WalletManager.getInstance().getDaemonAddress());
showProgress(55); showProgress(55);
wallet.init(0); wallet.init(0);
wallet.setProxy(NetCipherHelper.getProxy());
showProgress(90); showProgress(90);
} }
return wallet; return wallet;

View File

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

View File

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

View File

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

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