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

Compare commits

..

50 Commits

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

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

* Update strings.xml
2021-12-05 19:23:03 +01:00
m2049r
9f0e89719c remove arabic (#795) 2021-12-05 19:00:10 +01:00
m2049r
190726e61c Merge branch 'Fushko-it-transl' 2021-12-05 18:59:36 +01:00
m2049r
729fafdb48 fix apostrophes 2021-12-05 18:59:26 +01:00
m2049r
b7ae23ac64 Merge branch 'it-transl' of https://github.com/Fushko/xmrwallet into Fushko-it-transl 2021-12-05 18:58:32 +01:00
m2049r
27885f2c86 create wallet only by button & refactoring (#793) 2021-12-05 18:10:25 +01:00
Francesco Fusco
ce9046c7b5 Update italian translation 2021-12-01 21:36:22 +01:00
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
m2049r
a56a29a6c4 bump version 2021-05-02 10:36:52 +02:00
m2049r
4e31f47482 new logo (#764) 2021-05-02 09:56:26 +02:00
m2049r
c1f14f9653 update gradle & deps & bump version (#760) 2021-04-25 18:24:30 +02:00
m2049r
2746c52d7b confirm checkboxes for delete confirmation dialogs (#759) 2021-04-25 14:26:46 +02:00
Baltsar
5df323bacb Update Swedish strings.xml (#754) 2021-04-23 23:32:31 +02:00
m2049r
776cc26377 refactor magic number (#756) 2021-04-23 08:57:08 +02:00
m2049r
bdfb6a90b6 update & clean build (#755) 2021-04-22 20:17:02 +02:00
m2049r
6db44dfab1 cleanup backup code (#753) 2021-04-21 19:54:20 +02:00
m2049r
c68ac7db6d bump version 2021-04-20 17:08:51 +02:00
Katant Savelev
e09862e940 Ru update for #751 (#752) 2021-04-20 17:01:01 +02:00
m2049r
c7bd7469a1 reset wallet by deleting wallet cache file (#751) 2021-04-20 13:06:14 +02:00
Katant Savelev
b39857fd2e Russian translation update (#746)
* Ru update

* Better external libs build guide

* Update BUILDING-external-libs.md
2021-04-20 12:50:48 +02:00
m2049r
8170f823ab bump version 2021-04-19 18:41:09 +02:00
m2049r
38c0ead45c random fixes (#747) 2021-04-19 17:39:12 +02:00
m2049r
1d027c1694 fix translation 2021-04-18 19:39:00 +02:00
Vlad
45dc21fbf7 Update Romanian translation (#739)
Co-authored-by: m2049r <m2049r@monerujo.io>
2021-04-18 19:19:42 +02:00
netrik182
d4f4de234a pt-br translation of new strings (#735) 2021-04-18 19:17:29 +02:00
m2049r
394d5471e3 show total incoming amount (#745) 2021-04-18 19:16:52 +02:00
m2049r
beb098adb3 build version 2021-04-18 17:38:28 +02:00
m2049r
fda370d35b bump version 2021-04-18 16:57:58 +02:00
m2049r
99681e1bbb fix permissions Q needs to read our old wallet files (#744) 2021-04-18 16:55:14 +02:00
m2049r
21f44380b1 remove persmissions 2021-04-18 13:29:16 +02:00
m2049r
f4c1af1bb8 Some refactoring (#742)
* configure enabling of shift & exchange
* refactor qr logo code
* fix rename wallet
2021-04-17 21:03:32 +02:00
m2049r
c002b81ebd use default mixin 2021-04-15 19:10:56 +02:00
m2049r
d2f07ba3b6 migrate to scoped storage and API 30 (#741) 2021-04-15 19:10:01 +02:00
135 changed files with 4547 additions and 2633 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

@@ -1,14 +1,15 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
buildToolsVersion '29.0.3'
compileSdkVersion 30
buildToolsVersion '30.0.3'
ndkVersion '17.2.4988734'
defaultConfig {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 29
versionCode 801
versionName "1.18.1 'ChAdOx1'"
targetSdkVersion 30
versionCode 1201
versionName "2.2.1 'René'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
@@ -112,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.1.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 "com.burgstaller:okhttp-digest:2.1"
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'
@@ -140,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"
@@ -151,3 +154,4 @@ dependencies {
compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16'
}

View File

@@ -4,20 +4,37 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_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.USE_FINGERPRINT" />
<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:requestLegacyExternalStorage="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">
@@ -36,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

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

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

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

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

@@ -385,24 +385,18 @@ public class ReceiveFragment extends Fragment {
}
private Bitmap addLogo(Bitmap qrBitmap) {
// addume logo & qrcode are both square
Bitmap logo = getMoneroLogo();
int qrWidth = qrBitmap.getWidth();
int qrHeight = qrBitmap.getHeight();
int logoWidth = logo.getWidth();
int logoHeight = logo.getHeight();
final int qrSize = qrBitmap.getWidth();
final int logoSize = logo.getWidth();
Bitmap logoBitmap = Bitmap.createBitmap(qrWidth, qrHeight, Bitmap.Config.ARGB_8888);
Bitmap logoBitmap = Bitmap.createBitmap(qrSize, qrSize, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(logoBitmap);
canvas.drawBitmap(qrBitmap, 0, 0, null);
canvas.save();
// figure out how to scale the logo
float scaleSize = 1.0f;
while ((logoWidth / scaleSize) > (qrWidth / 5.) || (logoHeight / scaleSize) > (qrHeight / 5.)) {
scaleSize *= 2;
}
float sx = 1.0f / scaleSize;
canvas.scale(sx, sx, qrWidth / 2f, qrHeight / 2f);
canvas.drawBitmap(logo, (qrWidth - logoWidth) / 2f, (qrHeight - logoHeight) / 2f, null);
final float sx = 0.2f * qrSize / logoSize;
canvas.scale(sx, sx, qrSize / 2f, qrSize / 2f);
canvas.drawBitmap(logo, (qrSize - logoSize) / 2f, (qrSize - logoSize) / 2f, null);
canvas.restore();
return logoBitmap;
}

View File

@@ -48,7 +48,7 @@ import lombok.RequiredArgsConstructor;
import timber.log.Timber;
public class SubaddressFragment extends Fragment implements SubaddressInfoAdapter.OnInteractionListener,
View.OnClickListener {
View.OnClickListener, OnBlockUpdateListener {
static public final String KEY_MODE = "mode";
static public final String MODE_MANAGER = "manager";
@@ -169,6 +169,11 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
adapter.setInfos(list);
}
@Override
public void onBlockUpdate(Wallet wallet) {
loadList();
}
@Override
public void onClick(View v) {
int id = v.getId();

View File

@@ -578,7 +578,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
try {
final WalletFragment walletFragment = getWalletFragment();
if (wallet.isSynchronized()) {
Timber.d("onRefreshed() synced");
releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced
if (!synced) { // first sync
onProgress(-1);

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;
@@ -126,8 +125,9 @@ public class WalletFragment extends Fragment
sCurrency = view.findViewById(R.id.sCurrency);
List<String> currencies = new ArrayList<>();
currencies.add(Helper.BASE_CRYPTO);
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()), R.layout.item_spinner_balance, currencies);
if (Helper.SHOW_EXCHANGERATES)
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner_balance, currencies);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sCurrency.setAdapter(spinnerAdapter);
@@ -223,11 +223,13 @@ public class WalletFragment extends Fragment
}
void showUnconfirmed(double unconfirmedAmount) {
if (!activityCallback.isStreetMode()) {
if (activityCallback.isStreetMode() || unconfirmedAmount == 0) {
tvUnconfirmedAmount.setText(null);
tvUnconfirmedAmount.setVisibility(View.GONE);
} else {
String unconfirmed = Helper.getFormattedAmount(unconfirmedAmount, true);
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed));
} else {
tvUnconfirmedAmount.setText(null);
tvUnconfirmedAmount.setVisibility(View.VISIBLE);
}
}
@@ -345,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))
@@ -560,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

@@ -18,12 +18,13 @@ package com.m2049r.xmrwallet.data;
import java.util.regex.Pattern;
import lombok.AllArgsConstructor;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@AllArgsConstructor
@RequiredArgsConstructor
@ToString
@EqualsAndHashCode
public class Subaddress implements Comparable<Subaddress> {
@@ -35,6 +36,9 @@ public class Subaddress implements Comparable<Subaddress> {
final private String address;
@Getter
private final String label;
@Getter
@Setter
private long amount;
@Override
public int compareTo(Subaddress another) { // newer is <

View File

@@ -21,6 +21,7 @@ import android.os.Parcelable;
import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
public class TxData implements Parcelable {
@@ -54,7 +55,7 @@ public class TxData implements Parcelable {
}
public double getAmountAsDouble() {
return 1.0 * amount / 1000000000000L;
return 1.0 * amount / Helper.ONE_XMR;
}
public int getMixin() {

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,29 +119,39 @@ public class SendAddressWizardFragment extends SendWizardFragment {
View view = inflater.inflate(R.layout.fragment_send_address, container, false);
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());
ibCrypto.put(crypto, button);
button.setOnClickListener(v -> {
if (possibleCryptos.contains(crypto)) {
selectedCrypto = crypto;
updateCryptoButtons(false);
} else {
// show help what to do:
if (button.getId() != R.id.ibXMR) {
final String name = getResources().getStringArray(R.array.cryptos)[crypto.ordinal()];
final String symbol = getCryptoForButton(button).getSymbol();
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help, name, symbol)));
tvXmrTo.setVisibility(View.VISIBLE);
if (Helper.ALLOW_SHIFT || (crypto == Crypto.XMR)) {
ibCrypto.put(crypto, button);
button.setOnClickListener(v -> {
if (possibleCryptos.contains(crypto)) {
selectedCrypto = crypto;
updateCryptoButtons(false);
} else {
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help_xmr)));
tvXmrTo.setVisibility(View.VISIBLE);
// show help what to do:
if (button.getId() != R.id.ibXMR) {
final String name = getResources().getStringArray(R.array.cryptos)[crypto.ordinal()];
final String symbol = getCryptoForButton(button).getSymbol();
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help, name, symbol)));
tvXmrTo.setVisibility(View.VISIBLE);
} 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);
}
}
if (!Helper.ALLOW_SHIFT) {
tvTor.setVisibility(View.VISIBLE);
}
updateCryptoButtons(true);
@@ -181,6 +193,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
selectedCrypto = Crypto.XMR;
sendListener.setMode(SendFragment.Mode.XMR);
}
if (!Helper.ALLOW_SHIFT) return;
if ((selectedCrypto == null) && isEthAddress(address)) {
Timber.d("isEthAddress");
possibleCryptos.add(Crypto.ETH);
@@ -285,6 +298,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
}
private void updateCryptoButtons(boolean noAddress) {
if (!Helper.ALLOW_SHIFT) return;
for (Crypto crypto : Crypto.values()) {
if (crypto == selectedCrypto) {
selectedCrypto(crypto);
@@ -345,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);
@@ -447,7 +461,12 @@ public class SendAddressWizardFragment extends SendWizardFragment {
BarcodeData barcodeData = sendListener.getBarcodeData();
if (barcodeData != null) {
Timber.d("GOT DATA");
if (!Helper.ALLOW_SHIFT && (barcodeData.asset != Crypto.XMR)) {
Timber.d("BUT ONLY XMR SUPPORTED");
barcodeData = null;
sendListener.setBarcodeData(barcodeData);
return;
}
if (barcodeData.address != null) {
etAddress.getEditText().setText(barcodeData.address);
possibleCryptos.clear();
@@ -458,7 +477,8 @@ public class SendAddressWizardFragment extends SendWizardFragment {
possibleCryptos.add(barcodeData.asset);
selectedCrypto = barcodeData.asset;
}
updateCryptoButtons(false);
if (Helper.ALLOW_SHIFT)
updateCryptoButtons(false);
if (checkAddress()) {
if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC)
etAddress.setError(getString(R.string.send_address_no_dnssec));

View File

@@ -139,7 +139,7 @@ public class SendAmountWizardFragment extends SendWizardFragment {
Timber.d("onResumeFragment()");
Helper.showKeyboard(getActivity());
final long funds = getTotalFunds();
maxFunds = 1.0 * funds / 1000000000000L;
maxFunds = 1.0 * funds / Helper.ONE_XMR;
if (!sendListener.getActivityCallback().isStreetMode()) {
tvFunds.setText(getString(R.string.send_available,
Wallet.getDisplayAmount(funds)));

View File

@@ -34,7 +34,7 @@ import com.m2049r.xmrwallet.service.shift.ShiftException;
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.OkHttpHelper;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.ServiceHelper;
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
import com.m2049r.xmrwallet.widget.SendProgressView;
@@ -89,7 +89,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
return view;
}
@Override
public boolean onValidateFields() {
Timber.i(maxBtc + "/" + minBtc);
@@ -179,7 +178,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
tvXmrToParms.setText(xmrParmText);
final long funds = getTotalFunds();
double availableXmr = 1.0 * funds / 1000000000000L;
double availableXmr = 1.0 * funds / Helper.ONE_XMR;
String availBtcString;
String availXmrString;
@@ -255,8 +254,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
if (xmrToApi == null) {
synchronized (this) {
if (xmrToApi == null) {
xmrToApi = new SideShiftApiImpl(OkHttpHelper.getOkHttpClient(),
ServiceHelper.getXmrToBaseUrl());
xmrToApi = new SideShiftApiImpl(ServiceHelper.getXmrToBaseUrl());
}
}
}

View File

@@ -37,7 +37,6 @@ import com.m2049r.xmrwallet.service.shift.sideshift.api.RequestQuote;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.OkHttpHelper;
import com.m2049r.xmrwallet.util.ServiceHelper;
import com.m2049r.xmrwallet.widget.SendProgressView;
@@ -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

@@ -66,7 +66,7 @@ public class SendFragment extends Fragment
SendSuccessWizardFragment.Listener,
OnBackPressedListener, OnUriScannedListener {
final static public int MIXIN = 10;
final static public int MIXIN = 0;
private Listener activityCallback;

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

@@ -28,6 +28,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.data.Subaddress;
import com.m2049r.xmrwallet.util.Helper;
import java.util.ArrayList;
import java.util.Collections;
@@ -110,12 +111,14 @@ public class SubaddressInfoAdapter extends RecyclerView.Adapter<SubaddressInfoAd
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener, View.OnLongClickListener {
final TextView tvName;
final TextView tvAddress;
final TextView tvAmount;
Subaddress item;
ViewHolder(View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tvName);
tvAddress = itemView.findViewById(R.id.tvAddress);
tvAmount = itemView.findViewById(R.id.tx_amount);
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
@@ -129,6 +132,12 @@ public class SubaddressInfoAdapter extends RecyclerView.Adapter<SubaddressInfoAd
item.getAddressIndex(), item.getSquashedAddress());
tvName.setText(label.isEmpty() ? address : label);
tvAddress.setText(address);
final long amount = item.getAmount();
if (amount > 0)
tvAmount.setText(context.getString(R.string.tx_list_amount_positive,
Helper.getDisplayAmount(amount, Helper.DISPLAY_DIGITS_INFO)));
else
tvAmount.setText("");
}
@Override

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

@@ -74,7 +74,7 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
@Override
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
return mOldList.get(oldItemPosition).name.equals(mNewList.get(newItemPosition).name);
return mOldList.get(oldItemPosition).getName().equals(mNewList.get(newItemPosition).getName());
}
@Override
@@ -86,9 +86,9 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
@NonNull
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_wallet, parent, false);
return new ViewHolder(view);
return new ViewHolder(
LayoutInflater.from(parent.getContext()).inflate(R.layout.item_wallet, parent, false)
);
}
@Override
@@ -122,7 +122,6 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
final TextView tvName;
final TextView tvAddress;
final ImageButton ibOptions;
WalletManager.WalletInfo infoItem;
boolean popupOpen = false;
@@ -130,7 +129,6 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
ViewHolder(View itemView) {
super(itemView);
tvName = itemView.findViewById(R.id.tvName);
tvAddress = itemView.findViewById(R.id.tvAddress);
ibOptions = itemView.findViewById(R.id.ibOptions);
ibOptions.setOnClickListener(view -> {
if (popupOpen) return;
@@ -160,8 +158,7 @@ public class WalletInfoAdapter extends RecyclerView.Adapter<WalletInfoAdapter.Vi
void bind(int position) {
infoItem = infoItems.get(position);
tvName.setText(infoItem.name);
tvAddress.setText(infoItem.address.substring(0, 16) + "...");
tvName.setText(infoItem.getName());
}
@Override

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);

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