1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-02 15:53:04 +02:00

Compare commits

...

29 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
m2049r
669516c60b add USE_FINGERPRINT again (#766)
and update sdk
2021-05-06 00:03:39 +02:00
121 changed files with 3060 additions and 1385 deletions

View File

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

View File

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

View File

@@ -4,18 +4,37 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<queries>
<intent>
<action android:name="org.torproject.android.intent.action.START" />
</intent>
<intent>
<action android:name="org.torproject.android.intent.action.STATUS" />
</intent>
<intent>
<action android:name="org.torproject.android.REQUEST_HS_PORT" />
</intent>
<intent>
<action android:name="org.torproject.android.REQUEST_V3_ONION_SERVICE" />
</intent>
<package android:name="org.torproject.android" />
</queries>
<application
android:preserveLegacyExternalStorage="true"
android:name=".XmrWalletApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:preserveLegacyExternalStorage="true"
android:supportsRtl="true"
android:theme="@style/MyMaterialTheme"
android:usesCleartextTraffic="true">
@@ -34,7 +53,7 @@
android:configChanges="orientation|keyboardHidden|uiMode"
android:label="@string/wallet_activity_name"
android:launchMode="singleTask"
android:screenOrientation="behind"/>
android:screenOrientation="behind" />
<activity
android:name=".LoginActivity"
android:configChanges="orientation|keyboardHidden|uiMode"

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -505,6 +505,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
Timber.d("onResume()");
}
@Override
public void saveWallet() {
if (mIsBound) { // no point in talking to unbound service
Intent intent = new Intent(getApplicationContext(), WalletService.class);
@@ -578,7 +579,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
try {
final WalletFragment walletFragment = getWalletFragment();
if (wallet.isSynchronized()) {
Timber.d("onRefreshed() synced");
releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced
if (!synced) { // first sync
onProgress(-1);
@@ -610,9 +610,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override
public void onWalletStored(final boolean success) {
runOnUiThread(() -> {
if (success) {
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unloaded), Toast.LENGTH_SHORT).show();
} else {
if (!success) {
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.Arrays;
import java.util.List;
import java.util.Objects;
import timber.log.Timber;
@@ -128,7 +127,7 @@ public class WalletFragment extends Fragment
currencies.add(Helper.BASE_CRYPTO);
if (Helper.SHOW_EXCHANGERATES)
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()), R.layout.item_spinner_balance, currencies);
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner_balance, currencies);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sCurrency.setAdapter(spinnerAdapter);
@@ -224,11 +223,13 @@ public class WalletFragment extends Fragment
}
void showUnconfirmed(double unconfirmedAmount) {
if (!activityCallback.isStreetMode()) {
if (activityCallback.isStreetMode() || unconfirmedAmount == 0) {
tvUnconfirmedAmount.setText(null);
tvUnconfirmedAmount.setVisibility(View.GONE);
} else {
String unconfirmed = Helper.getFormattedAmount(unconfirmedAmount, true);
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed));
} else {
tvUnconfirmedAmount.setText(null);
tvUnconfirmedAmount.setVisibility(View.VISIBLE);
}
}
@@ -346,13 +347,18 @@ public class WalletFragment extends Fragment
// if account index has changed scroll to top?
private int accountIndex = 0;
public void onRefreshed(final Wallet wallet, final boolean full) {
public void onRefreshed(final Wallet wallet, boolean full) {
Timber.d("onRefreshed(%b)", full);
if (adapter.needsTransactionUpdateOnNewBlock()) {
wallet.refreshHistory();
full = true;
}
if (full) {
List<TransactionInfo> list = new ArrayList<>();
final long streetHeight = activityCallback.getStreetModeHeight();
Timber.d("StreetHeight=%d", streetHeight);
wallet.refreshHistory();
for (TransactionInfo info : wallet.getHistory().getAll()) {
Timber.d("TxHeight=%d, Label=%s", info.blockheight, info.subaddressLabel);
if ((info.isPending || (info.blockheight >= streetHeight))
@@ -561,7 +567,7 @@ public class WalletFragment extends Fragment
//TODO figure out why gunther disappears on return from send although he is still set
if (enable) {
if (streetGunther == null)
streetGunther = ContextCompat.getDrawable(Objects.requireNonNull(getContext()), R.drawable.ic_gunther_streetmode);
streetGunther = ContextCompat.getDrawable(requireContext(), R.drawable.ic_gunther_streetmode);
ivStreetGunther.setImageDrawable(streetGunther);
} else
ivStreetGunther.setImageDrawable(null);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -37,7 +37,6 @@ import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.util.ServiceHelper;
import com.m2049r.xmrwallet.widget.SendProgressView;
@@ -359,40 +358,22 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
}
private RequestQuote xmrtoQuote = null;
private int stageARetries = 0;
private final int RETRIES = 3;
private double stageAPrice = 0;
private void processStageA(final RequestQuote requestQuote) {
Timber.d("processCreateOrder %s", requestQuote.getId());
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
// verify the BTC amount is correct (price can change and we can only specify XMR amount)
// verify the BTC amount is correct
if (requestQuote.getBtcAmount() != txDataBtc.getBtcAmount()) {
if (--stageARetries <= 0) {
Timber.d("Failed to get quote");
getView().post(() ->
showStageError(ShiftError.Error.SERVICE.toString(),
getString(R.string.shift_noquote),
getString(R.string.shift_checkamount)));
return; // just stop for now
}
if (stageAPrice == requestQuote.getPrice()) {
// same price but different BTC amount - something else is wrong (e.g. too many decimals)
Timber.d("Price unchanged");
getView().post(() ->
showStageError(ShiftError.Error.SERVICE.toString(),
getString(R.string.shift_noquote),
getString(R.string.shift_checkamount)));
return; // just stop for now
}
stageAPrice = requestQuote.getPrice();
// recalc XMR and try again
txDataBtc.setAmount(txDataBtc.getBtcAmount() / requestQuote.getPrice());
getView().post(this::stageAOneShot);
return; // stageA will run in the main thread
Timber.d("Failed to get quote");
getView().post(() -> showStageError(ShiftError.Error.SERVICE.toString(),
getString(R.string.shift_noquote),
getString(R.string.shift_checkamount)));
return; // just stop for now
}
xmrtoQuote = requestQuote;
txDataBtc.setAmount(xmrtoQuote.getXmrAmount());
getView().post(() -> {
// show data from the actual quote as that is what is used to
NumberFormat df = NumberFormat.getInstance(Locale.US);
df.setMaximumFractionDigits(12);
final String btcAmount = df.format(xmrtoQuote.getBtcAmount());
@@ -438,18 +419,12 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
}
private void stageA() {
stageARetries = RETRIES;
stageAOneShot();
}
private void stageAOneShot() {
if (!isResumed) return;
Timber.d("Request Quote");
xmrtoQuote = null;
xmrtoOrder = null;
showProgress(1, getString(R.string.label_send_progress_xmrto_create));
TxDataBtc txDataBtc = (TxDataBtc) sendListener.getTxData();
stageAPrice = 0;
ShiftCallback<RequestQuote> callback = new ShiftCallback<RequestQuote>() {
@Override
@@ -473,7 +448,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
}
};
getXmrToApi().requestQuote(txDataBtc.getAmountAsDouble(), callback);
getXmrToApi().requestQuote(txDataBtc.getBtcAmount(), callback);
}
private CreateOrder xmrtoOrder = null;
@@ -567,8 +542,7 @@ public class SendBtcConfirmWizardFragment extends SendWizardFragment implements
if (xmrToApi == null) {
synchronized (this) {
if (xmrToApi == null) {
xmrToApi = new SideShiftApiImpl(OkHttpHelper.getOkHttpClient(),
ServiceHelper.getXmrToBaseUrl());
xmrToApi = new SideShiftApiImpl(ServiceHelper.getXmrToBaseUrl());
}
}
}

View File

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

View File

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

View File

@@ -30,6 +30,7 @@ import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.progressindicator.CircularProgressIndicator;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.Crypto;
import com.m2049r.xmrwallet.data.UserNotes;
@@ -48,7 +49,7 @@ import java.util.TimeZone;
import timber.log.Timber;
public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfoAdapter.ViewHolder> {
private final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
private final static SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm");
private final int outboundColour;
private final int inboundColour;
@@ -77,6 +78,10 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
DATETIME_FORMATTER.setTimeZone(tz);
}
public boolean needsTransactionUpdateOnNewBlock() {
return (infoItems.size() > 0) && !infoItems.get(0).isConfirmed();
}
private static class TransactionInfoDiff extends DiffCallback<TransactionInfo> {
public TransactionInfoDiff(List<TransactionInfo> oldList, List<TransactionInfo> newList) {
@@ -95,6 +100,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
return (oldItem.direction == newItem.direction)
&& (oldItem.isPending == newItem.isPending)
&& (oldItem.isFailed == newItem.isFailed)
&& ((oldItem.confirmations == newItem.confirmations) || (oldItem.isConfirmed()))
&& (oldItem.subaddressLabel.equals(newItem.subaddressLabel))
&& (Objects.equals(oldItem.notes, newItem.notes));
}
@@ -146,18 +152,23 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final ImageView ivTxType;
final TextView tvAmount;
final TextView tvFee;
final TextView tvFailed;
final TextView tvPaymentId;
final TextView tvDateTime;
final CircularProgressIndicator pbConfirmations;
final TextView tvConfirmations;
TransactionInfo infoItem;
ViewHolder(View itemView) {
super(itemView);
ivTxType = itemView.findViewById(R.id.ivTxType);
tvAmount = itemView.findViewById(R.id.tx_amount);
tvFee = itemView.findViewById(R.id.tx_fee);
tvFailed = itemView.findViewById(R.id.tx_failed);
tvPaymentId = itemView.findViewById(R.id.tx_paymentid);
tvDateTime = itemView.findViewById(R.id.tx_datetime);
pbConfirmations = itemView.findViewById(R.id.pbConfirmations);
pbConfirmations.setMax(TransactionInfo.CONFIRMATION);
tvConfirmations = itemView.findViewById(R.id.tvConfirmations);
}
private String getDateTime(long time) {
@@ -182,7 +193,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
ivTxType.setVisibility(View.GONE);
}
} else {
ivTxType.setVisibility(View.GONE); // gives us more space for the amount
ivTxType.setVisibility(View.GONE);
}
String displayAmount = Helper.getDisplayAmount(infoItem.amount, Helper.DISPLAY_DIGITS_INFO);
@@ -192,25 +203,39 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
tvAmount.setText(context.getString(R.string.tx_list_amount_positive, displayAmount));
}
if ((infoItem.fee > 0)) {
String fee = Helper.getDisplayAmount(infoItem.fee, Helper.DISPLAY_DIGITS_INFO);
tvFee.setText(context.getString(R.string.tx_list_fee, fee));
tvFee.setVisibility(View.VISIBLE);
} else {
tvFee.setText("");
tvFee.setVisibility(View.GONE);
}
tvFailed.setVisibility(View.GONE);
if (infoItem.isFailed) {
this.tvAmount.setText(context.getString(R.string.tx_list_amount_failed, displayAmount));
this.tvFee.setText(context.getString(R.string.tx_list_failed_text));
tvFee.setVisibility(View.VISIBLE);
tvFailed.setVisibility(View.VISIBLE);
setTxColour(failedColour);
pbConfirmations.setVisibility(View.GONE);
tvConfirmations.setVisibility(View.GONE);
} else if (infoItem.isPending) {
setTxColour(pendingColour);
pbConfirmations.setVisibility(View.GONE);
pbConfirmations.setIndeterminate(true);
pbConfirmations.setVisibility(View.VISIBLE);
tvConfirmations.setVisibility(View.GONE);
} else if (infoItem.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(inboundColour);
if (!infoItem.isConfirmed()) {
pbConfirmations.setVisibility(View.VISIBLE);
final int confirmations = (int) infoItem.confirmations;
pbConfirmations.setProgressCompat(confirmations, true);
final String confCount = Integer.toString(confirmations);
tvConfirmations.setText(confCount);
if (confCount.length() == 1) // we only have space for character in the progress circle
tvConfirmations.setVisibility(View.VISIBLE);
else
tvConfirmations.setVisibility(View.GONE);
} else {
pbConfirmations.setVisibility(View.GONE);
tvConfirmations.setVisibility(View.GONE);
}
} else {
setTxColour(outboundColour);
pbConfirmations.setVisibility(View.GONE);
tvConfirmations.setVisibility(View.GONE);
}
String tag = null;

View File

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

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 a POJO for the TransactionInfoAdapter
public class TransactionInfo implements Parcelable, Comparable<TransactionInfo> {
public static final int CONFIRMATION = 10; // blocks
@RequiredArgsConstructor
public enum Direction {
Direction_In(0),
@@ -98,6 +100,10 @@ public class TransactionInfo implements Parcelable, Comparable<TransactionInfo>
this.transfers = transfers;
}
public boolean isConfirmed() {
return confirmations >= CONFIRMATION;
}
public String getDisplayLabel() {
if (subaddressLabel.isEmpty() || (Subaddress.DEFAULT_LABEL_FORMATTER.matcher(subaddressLabel).matches()))
return ("#" + addressIndex);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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