1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-06 02:27:11 +02:00

Compare commits

...

61 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
m2049r
24fc27b09e show new incoming tx in subaddress details (#734) 2021-03-23 19:05:30 +01:00
m2049r
cf4ff856d5 Subaddresses as first class citizens (#733) 2021-03-22 00:31:01 +01:00
m2049r
b01de1ad6e bump version 2021-03-20 09:14:51 +01:00
anhdres
5fb1bcb552 Update strings.xml (#732)
several gender mistakes ("monedero" we're using now is masculine)
ledger translations
onboarding
sideshift integration
2021-03-19 23:38:03 +01:00
anhdres
b937ba38b6 Update help.xml (#731)
translated new text and help_ok string
2021-03-19 23:36:32 +01:00
m2049r
849718fdc7 bump version 2021-03-15 09:38:01 +01:00
m2049r
a6372f701d Merge pull request #730 from m2049r/feature_sr_transitions_s
Send & Receive UI transitions
2021-03-14 21:24:18 +01:00
m2049r
12f135bb14 refactoring 2021-03-14 21:22:49 +01:00
m2049r
16870fcbb9 Receive & send transition 2021-03-14 21:22:37 +01:00
m2049r
2fbd152fb3 bump version 2021-03-13 13:16:26 +01:00
m2049r
a7b178e024 Tweaking UI & some bugfixing on the way (#729) 2021-03-13 13:12:46 +01:00
149 changed files with 6270 additions and 3374 deletions

1
.gitignore vendored
View File

@@ -15,3 +15,4 @@
/app/alphaStagenet /app/alphaStagenet
/app/prodStagenet /app/prodStagenet
/app/.cxx /app/.cxx
/monerujo.id

View File

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

View File

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

View File

@@ -4,20 +4,37 @@
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <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.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" /> <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.NFC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<queries>
<intent>
<action android:name="org.torproject.android.intent.action.START" />
</intent>
<intent>
<action android:name="org.torproject.android.intent.action.STATUS" />
</intent>
<intent>
<action android:name="org.torproject.android.REQUEST_HS_PORT" />
</intent>
<intent>
<action android:name="org.torproject.android.REQUEST_V3_ONION_SERVICE" />
</intent>
<package android:name="org.torproject.android" />
</queries>
<application <application
android:requestLegacyExternalStorage="true"
android:name=".XmrWalletApplication" android:name=".XmrWalletApplication"
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:preserveLegacyExternalStorage="true"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/MyMaterialTheme" android:theme="@style/MyMaterialTheme"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true">
@@ -36,7 +53,7 @@
android:configChanges="orientation|keyboardHidden|uiMode" android:configChanges="orientation|keyboardHidden|uiMode"
android:label="@string/wallet_activity_name" android:label="@string/wallet_activity_name"
android:launchMode="singleTask" android:launchMode="singleTask"
android:screenOrientation="behind"/> android:screenOrientation="behind" />
<activity <activity
android:name=".LoginActivity" android:name=".LoginActivity"
android:configChanges="orientation|keyboardHidden|uiMode" 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()); return env->NewStringUTF(resolvedAlias.c_str());
} }
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_setProxy(JNIEnv *env, jobject instance,
jstring address) {
const char *_address = env->GetStringUTFChars(address, nullptr);
bool rc =
Bitmonero::WalletManagerFactory::getWalletManager()->setProxy(std::string(_address));
env->ReleaseStringUTFChars(address, _address);
return rc;
}
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir); //TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
JNIEXPORT jboolean JNICALL JNIEXPORT jboolean JNICALL
@@ -727,6 +738,16 @@ Java_com_m2049r_xmrwallet_model_Wallet_getConnectionStatusJ(JNIEnv *env, jobject
//TODO virtual void setTrustedDaemon(bool arg) = 0; //TODO virtual void setTrustedDaemon(bool arg) = 0;
//TODO virtual bool trustedDaemon() const = 0; //TODO virtual bool trustedDaemon() const = 0;
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_setProxy(JNIEnv *env, jobject instance,
jstring address) {
const char *_address = env->GetStringUTFChars(address, nullptr);
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
bool rc = wallet->setProxy(std::string(_address));
env->ReleaseStringUTFChars(address, _address);
return rc;
}
JNIEXPORT jlong JNICALL JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance, Java_com_m2049r_xmrwallet_model_Wallet_getBalance(JNIEnv *env, jobject instance,
jint accountIndex) { jint accountIndex) {
@@ -914,7 +935,7 @@ Java_com_m2049r_xmrwallet_model_Wallet_refreshAsync(JNIEnv *env, jobject instanc
//virtual void rescanBlockchainAsync() = 0; //virtual void rescanBlockchainAsync() = 0;
JNIEXPORT void JNICALL JNIEXPORT void JNICALL
Java_com_m2049r_xmrwallet_model_Wallet_rescanBlockchainAsync(JNIEnv *env, jobject instance) { Java_com_m2049r_xmrwallet_model_Wallet_rescanBlockchainAsyncJ(JNIEnv *env, jobject instance) {
Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance); Bitmonero::Wallet *wallet = getHandle<Bitmonero::Wallet>(env, instance);
wallet->rescanBlockchainAsync(); wallet->rescanBlockchainAsync();
} }

View File

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

View File

@@ -31,10 +31,11 @@ import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.PowerManager; import android.os.PowerManager;
import android.widget.Toast;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import android.widget.Toast;
import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.dialog.ProgressDialog; import com.m2049r.xmrwallet.dialog.ProgressDialog;
@@ -46,7 +47,8 @@ import java.io.IOException;
import timber.log.Timber; import timber.log.Timber;
public class BaseActivity extends SecureActivity implements GenerateReviewFragment.ProgressListener { public class BaseActivity extends SecureActivity
implements GenerateReviewFragment.ProgressListener, SubaddressFragment.ProgressListener {
ProgressDialog progressDialog = null; ProgressDialog progressDialog = null;

File diff suppressed because it is too large Load Diff

View File

@@ -307,7 +307,6 @@ public class GenerateReviewFragment extends Fragment {
void dismissProgressDialog(); void dismissProgressDialog();
} }
public interface AcceptListener { public interface AcceptListener {
void onAccept(String name, String password); void onAccept(String name, String password);
} }

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

View File

@@ -0,0 +1,23 @@
/*
* Copyright (c) 2021 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import com.m2049r.xmrwallet.model.Wallet;
public interface OnBlockUpdateListener {
void onBlockUpdate(final Wallet wallet);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,251 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.transition.MaterialElevationScale;
import com.m2049r.xmrwallet.data.Subaddress;
import com.m2049r.xmrwallet.layout.SubaddressInfoAdapter;
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.util.ArrayList;
import java.util.List;
import lombok.RequiredArgsConstructor;
import timber.log.Timber;
public class SubaddressFragment extends Fragment implements SubaddressInfoAdapter.OnInteractionListener,
View.OnClickListener, OnBlockUpdateListener {
static public final String KEY_MODE = "mode";
static public final String MODE_MANAGER = "manager";
private SubaddressInfoAdapter adapter;
private Listener activityCallback;
private Wallet wallet;
// Container Activity must implement this interface
public interface Listener {
void onSubaddressSelected(Subaddress subaddress);
void setSubtitle(String title);
void setToolbarButton(int type);
void showSubaddress(View view, final int subaddressIndex);
}
public interface ProgressListener {
void showProgressDialog(int msgId);
void showLedgerProgressDialog(int mode);
void dismissProgressDialog();
}
private ProgressListener progressCallback = null;
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof ProgressListener) {
progressCallback = (ProgressListener) context;
}
if (context instanceof Listener) {
activityCallback = (Listener) context;
} else {
throw new ClassCastException(context.toString()
+ " must implement Listener");
}
}
@Override
public void onPause() {
Timber.d("onPause()");
super.onPause();
}
@Override
public void onResume() {
super.onResume();
activityCallback.setSubtitle(getString(R.string.subbaddress_title));
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Timber.d("onCreateView");
final Bundle b = getArguments();
managerMode = ((b != null) && (MODE_MANAGER.equals(b.getString(KEY_MODE))));
View view = inflater.inflate(R.layout.fragment_subaddress, container, false);
final MaterialElevationScale exitTransition = new MaterialElevationScale(false);
exitTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
setExitTransition(exitTransition);
final MaterialElevationScale reenterTransition = new MaterialElevationScale(true);
reenterTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
setReenterTransition(reenterTransition);
view.findViewById(R.id.fab).setOnClickListener(this);
if (managerMode) {
view.findViewById(R.id.tvInstruction).setVisibility(View.GONE);
view.findViewById(R.id.tvHint).setVisibility(View.GONE);
}
final RecyclerView list = view.findViewById(R.id.list);
adapter = new SubaddressInfoAdapter(getActivity(), this);
list.setAdapter(adapter);
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
list.scrollToPosition(positionStart);
}
});
Helper.hideKeyboard(getActivity());
wallet = WalletManager.getInstance().getWallet();
loadList();
return view;
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
postponeEnterTransition();
view.getViewTreeObserver().addOnPreDrawListener(() -> {
startPostponedEnterTransition();
return true;
});
}
public void loadList() {
Timber.d("loadList()");
final int numSubaddresses = wallet.getNumSubaddresses();
final List<Subaddress> list = new ArrayList<>();
for (int i = 0; i < numSubaddresses; i++) {
list.add(wallet.getSubaddressObject(i));
}
adapter.setInfos(list);
}
@Override
public void onBlockUpdate(Wallet wallet) {
loadList();
}
@Override
public void onClick(View v) {
int id = v.getId();
if (id == R.id.fab) {
getNewSubaddress();
}
}
private int lastUsedSubaddress() {
int lastUsedSubaddress = 0;
for (TransactionInfo info : wallet.getHistory().getAll()) {
if (info.addressIndex > lastUsedSubaddress)
lastUsedSubaddress = info.addressIndex;
}
return lastUsedSubaddress;
}
private void getNewSubaddress() {
final int maxSubaddresses = lastUsedSubaddress() + wallet.getDeviceType().getSubaddressLookahead();
if (wallet.getNumSubaddresses() < maxSubaddresses)
new AsyncSubaddress().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR);
else
Toast.makeText(getActivity(), getString(R.string.max_subaddress_warning), Toast.LENGTH_LONG).show();
}
@SuppressLint("StaticFieldLeak")
@RequiredArgsConstructor
private class AsyncSubaddress extends AsyncTask<Void, Void, Boolean> {
boolean dialogOpened = false;
@Override
protected void onPreExecute() {
super.onPreExecute();
if ((wallet.getDeviceType() == Wallet.Device.Device_Ledger) && (progressCallback != null)) {
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS);
dialogOpened = true;
}
}
@Override
protected Boolean doInBackground(Void... params) {
if (params.length != 0) return false;
wallet.getNewSubaddress();
wallet.store();
return true;
}
@Override
protected void onPostExecute(Boolean result) {
super.onPostExecute(result);
if (dialogOpened)
progressCallback.dismissProgressDialog();
if (!isAdded()) // never mind then
return;
loadList();
}
}
boolean managerMode = false;
// Callbacks from SubaddressInfoAdapter
@Override
public void onInteraction(final View view, final Subaddress subaddress) {
if (managerMode)
activityCallback.showSubaddress(view, subaddress.getAddressIndex());
else
activityCallback.onSubaddressSelected(subaddress); // also closes the fragment with onBackpressed()
}
@Override
public boolean onLongInteraction(View view, Subaddress subaddress) {
activityCallback.showSubaddress(view, subaddress.getAddressIndex());
return false;
}
}

View File

@@ -0,0 +1,175 @@
/*
* Copyright (c) 2017 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import android.content.Context;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.textfield.TextInputLayout;
import com.google.android.material.transition.MaterialContainerTransform;
import com.m2049r.xmrwallet.data.Subaddress;
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.util.ArrayList;
import java.util.List;
import timber.log.Timber;
public class SubaddressInfoFragment extends Fragment
implements TransactionInfoAdapter.OnInteractionListener, OnBlockUpdateListener {
private TransactionInfoAdapter adapter;
private Subaddress subaddress;
private TextInputLayout etName;
private TextView tvAddress;
private TextView tvTxLabel;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_subaddressinfo, container, false);
etName = view.findViewById(R.id.etName);
tvAddress = view.findViewById(R.id.tvAddress);
tvTxLabel = view.findViewById(R.id.tvTxLabel);
final RecyclerView list = view.findViewById(R.id.list);
adapter = new TransactionInfoAdapter(getActivity(), this);
list.setAdapter(adapter);
final Wallet wallet = activityCallback.getWallet();
Bundle b = getArguments();
final int subaddressIndex = b.getInt("subaddressIndex");
subaddress = wallet.getSubaddressObject(subaddressIndex);
etName.getEditText().setText(subaddress.getDisplayLabel());
tvAddress.setText(getContext().getString(R.string.subbaddress_info_subtitle,
subaddress.getAddressIndex(), subaddress.getSquashedAddress()));
etName.getEditText().setOnFocusChangeListener((v, hasFocus) -> {
if (!hasFocus) {
wallet.setSubaddressLabel(subaddressIndex, etName.getEditText().getText().toString());
}
});
etName.getEditText().setOnEditorActionListener((v, actionId, event) -> {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
Helper.hideKeyboard(getActivity());
wallet.setSubaddressLabel(subaddressIndex, etName.getEditText().getText().toString());
onRefreshed(wallet);
return true;
}
return false;
});
onRefreshed(wallet);
return view;
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final MaterialContainerTransform transform = new MaterialContainerTransform();
transform.setDrawingViewId(R.id.fragment_container);
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground));
setSharedElementEnterTransition(transform);
}
public void onRefreshed(final Wallet wallet) {
Timber.d("onRefreshed");
List<TransactionInfo> list = new ArrayList<>();
for (TransactionInfo info : wallet.getHistory().getAll()) {
if (info.addressIndex == subaddress.getAddressIndex())
list.add(info);
}
adapter.setInfos(list);
if (list.isEmpty())
tvTxLabel.setText(R.string.subaddress_notx_label);
else
tvTxLabel.setText(R.string.subaddress_tx_label);
}
@Override
public void onBlockUpdate(Wallet wallet) {
onRefreshed(wallet);
}
// Callbacks from TransactionInfoAdapter
@Override
public void onInteraction(final View view, final TransactionInfo infoItem) {
activityCallback.onTxDetailsRequest(view, infoItem);
}
Listener activityCallback;
// Container Activity must implement this interface
public interface Listener {
void onTxDetailsRequest(View view, TransactionInfo info);
Wallet getWallet();
void setToolbarButton(int type);
void setTitle(String title, String subtitle);
void setSubtitle(String subtitle);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof Listener) {
this.activityCallback = (Listener) context;
} else {
throw new ClassCastException(context.toString()
+ " must implement Listener");
}
}
@Override
public void onResume() {
super.onResume();
Timber.d("onResume()");
activityCallback.setSubtitle(getString(R.string.subbaddress_title));
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
}
@Override
public void onPause() {
super.onPause();
}
}

View File

@@ -22,7 +22,9 @@ import android.content.Intent;
import android.graphics.Paint; import android.graphics.Paint;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.Html;
import android.text.InputType; import android.text.InputType;
import android.text.Spanned;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
@@ -36,11 +38,15 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.google.android.material.transition.MaterialContainerTransform;
import com.google.android.material.transition.MaterialElevationScale;
import com.m2049r.xmrwallet.data.Subaddress;
import com.m2049r.xmrwallet.data.UserNotes; import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer; import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.SimpleDateFormat; import java.text.SimpleDateFormat;
@@ -50,6 +56,8 @@ import java.util.HashSet;
import java.util.Set; import java.util.Set;
import java.util.TimeZone; import java.util.TimeZone;
import timber.log.Timber;
public class TxFragment extends Fragment { public class TxFragment extends Fragment {
static public final String ARG_INFO = "info"; static public final String ARG_INFO = "info";
@@ -86,11 +94,16 @@ public class TxFragment extends Fragment {
private ImageView tvXmrToLogo; private ImageView tvXmrToLogo;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_tx_info, container, false); View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
final MaterialElevationScale exitTransition = new MaterialElevationScale(false);
exitTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
setExitTransition(exitTransition);
final MaterialElevationScale reenterTransition = new MaterialElevationScale(true);
reenterTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
setReenterTransition(reenterTransition);
cvXmrTo = view.findViewById(R.id.cvXmrTo); cvXmrTo = view.findViewById(R.id.cvXmrTo);
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey); tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc); tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc);
@@ -119,9 +132,8 @@ public class TxFragment extends Fragment {
Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
}); });
Bundle args = getArguments(); info = getArguments().getParcelable(ARG_INFO);
TransactionInfo info = args.getParcelable(ARG_INFO); show();
show(info);
return view; return view;
} }
@@ -190,7 +202,7 @@ public class TxFragment extends Fragment {
TransactionInfo info = null; TransactionInfo info = null;
UserNotes userNotes = null; UserNotes userNotes = null;
void loadNotes(TransactionInfo info) { void loadNotes() {
if ((userNotes == null) || (info.notes == null)) { if ((userNotes == null) || (info.notes == null)) {
info.notes = activityCallback.getTxNotes(info.hash); info.notes = activityCallback.getTxNotes(info.hash);
} }
@@ -203,19 +215,28 @@ public class TxFragment extends Fragment {
tvTxFee.setTextColor(clr); tvTxFee.setTextColor(clr);
} }
private void show(TransactionInfo info) { private void showSubaddressLabel() {
final Subaddress subaddress = activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex);
final Context ctx = getContext();
Spanned label = Html.fromHtml(ctx.getString(R.string.tx_account_formatted,
info.accountIndex, info.addressIndex,
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoGreen) & 0xFFFFFF),
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoBackground) & 0xFFFFFF),
subaddress.getDisplayLabel()));
tvAccount.setText(label);
tvAccount.setOnClickListener(v -> activityCallback.showSubaddress(v, info.addressIndex));
}
private void show() {
if (info.txKey == null) { if (info.txKey == null) {
info.txKey = activityCallback.getTxKey(info.hash); info.txKey = activityCallback.getTxKey(info.hash);
} }
if (info.address == null) { if (info.address == null) {
info.address = activityCallback.getTxAddress(info.account, info.subaddress); info.address = activityCallback.getTxAddress(info.accountIndex, info.addressIndex);
} }
loadNotes(info); loadNotes();
activityCallback.setSubtitle(getString(R.string.tx_title)); showSubaddressLabel();
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
tvAccount.setText(getString(R.string.tx_account_formatted, info.account, info.subaddress));
tvAddress.setText(info.address); tvAddress.setText(info.address);
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000))); tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
@@ -254,8 +275,8 @@ public class TxFragment extends Fragment {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_minus)); setTxColour(ContextCompat.getColor(getContext(), R.color.tx_minus));
} }
Set<String> destinations = new HashSet<>(); Set<String> destinations = new HashSet<>();
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
StringBuffer dstSb = new StringBuffer(); StringBuilder dstSb = new StringBuilder();
if (info.transfers != null) { if (info.transfers != null) {
boolean newline = false; boolean newline = false;
for (Transfer transfer : info.transfers) { for (Transfer transfer : info.transfers) {
@@ -279,14 +300,12 @@ public class TxFragment extends Fragment {
} }
} else { } else {
sb.append("-"); sb.append("-");
dstSb.append(info.direction == dstSb.append(info.direction == TransactionInfo.Direction.Direction_In ?
TransactionInfo.Direction.Direction_In ? activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex).getAddress() :
activityCallback.getWalletSubaddress(info.account, info.subaddress) :
"-"); "-");
} }
tvTxTransfers.setText(sb.toString()); tvTxTransfers.setText(sb.toString());
tvDestination.setText(dstSb.toString()); tvDestination.setText(dstSb.toString());
this.info = info;
showBtcInfo(); showBtcInfo();
} }
@@ -300,7 +319,7 @@ public class TxFragment extends Fragment {
} }
tvTxXmrToKey.setText(key); tvTxXmrToKey.setText(key);
tvDestinationBtc.setText(userNotes.xmrtoDestination); tvDestinationBtc.setText(userNotes.xmrtoDestination);
tvTxAmountBtc.setText(userNotes.xmrtoAmount + " "+ userNotes.xmrtoCurrency); tvTxAmountBtc.setText(userNotes.xmrtoAmount + " " + userNotes.xmrtoCurrency);
switch (userNotes.xmrtoTag) { switch (userNotes.xmrtoTag) {
case "xmrto": case "xmrto":
tvXmrToSupport.setVisibility(View.GONE); tvXmrToSupport.setVisibility(View.GONE);
@@ -329,6 +348,11 @@ public class TxFragment extends Fragment {
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
final MaterialContainerTransform transform = new MaterialContainerTransform();
transform.setDrawingViewId(R.id.fragment_container);
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground));
setSharedElementEnterTransition(transform);
} }
@Override @Override
@@ -340,7 +364,7 @@ public class TxFragment extends Fragment {
Listener activityCallback; Listener activityCallback;
public interface Listener { public interface Listener {
String getWalletSubaddress(int accountIndex, int subaddressIndex); Subaddress getWalletSubaddress(int accountIndex, int subaddressIndex);
String getTxKey(String hash); String getTxKey(String hash);
@@ -354,6 +378,8 @@ public class TxFragment extends Fragment {
void setSubtitle(String subtitle); void setSubtitle(String subtitle);
void showSubaddress(View view, final int subaddressIndex);
} }
@Override @Override
@@ -378,4 +404,13 @@ public class TxFragment extends Fragment {
Helper.hideKeyboard(getActivity()); Helper.hideKeyboard(getActivity());
super.onPause(); super.onPause();
} }
@Override
public void onResume() {
super.onResume();
Timber.d("onResume()");
activityCallback.setSubtitle(getString(R.string.tx_title));
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
showSubaddressLabel();
}
} }

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,63 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.data;
import java.util.regex.Pattern;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import lombok.ToString;
@RequiredArgsConstructor
@ToString
@EqualsAndHashCode
public class Subaddress implements Comparable<Subaddress> {
@Getter
final private int accountIndex;
@Getter
final private int addressIndex;
@Getter
final private String address;
@Getter
private final String label;
@Getter
@Setter
private long amount;
@Override
public int compareTo(Subaddress another) { // newer is <
final int compareAccountIndex = another.accountIndex - accountIndex;
if (compareAccountIndex == 0)
return another.addressIndex - addressIndex;
return compareAccountIndex;
}
public String getSquashedAddress() {
return address.substring(0, 8) + "…" + address.substring(address.length() - 8);
}
public static final Pattern DEFAULT_LABEL_FORMATTER = Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}$");
public String getDisplayLabel() {
if (label.isEmpty() || (DEFAULT_LABEL_FORMATTER.matcher(label).matches()))
return ("#" + addressIndex);
else
return label;
}
}

View File

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

View File

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

View File

@@ -22,6 +22,7 @@ import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.Html; import android.text.Html;
import android.text.InputType; import android.text.InputType;
import android.text.Spanned;
import android.text.TextWatcher; import android.text.TextWatcher;
import android.util.Patterns; import android.util.Patterns;
import android.view.KeyEvent; import android.view.KeyEvent;
@@ -91,6 +92,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private TextInputLayout etAddress; private TextInputLayout etAddress;
private TextInputLayout etNotes; private TextInputLayout etNotes;
private TextView tvXmrTo; private TextView tvXmrTo;
private TextView tvTor;
private Map<Crypto, ImageButton> ibCrypto; private Map<Crypto, ImageButton> ibCrypto;
final private Set<Crypto> possibleCryptos = new HashSet<>(); final private Set<Crypto> possibleCryptos = new HashSet<>();
private Crypto selectedCrypto = null; private Crypto selectedCrypto = null;
@@ -117,29 +119,39 @@ public class SendAddressWizardFragment extends SendWizardFragment {
View view = inflater.inflate(R.layout.fragment_send_address, container, false); View view = inflater.inflate(R.layout.fragment_send_address, container, false);
tvTor = view.findViewById(R.id.tvTor);
tvXmrTo = view.findViewById(R.id.tvXmrTo); tvXmrTo = view.findViewById(R.id.tvXmrTo);
ibCrypto = new HashMap<>(); ibCrypto = new HashMap<>();
for (Crypto crypto : Crypto.values()) { for (Crypto crypto : Crypto.values()) {
final ImageButton button = view.findViewById(crypto.getButtonId()); final ImageButton button = view.findViewById(crypto.getButtonId());
ibCrypto.put(crypto, button); if (Helper.ALLOW_SHIFT || (crypto == Crypto.XMR)) {
button.setOnClickListener(v -> { ibCrypto.put(crypto, button);
if (possibleCryptos.contains(crypto)) { button.setOnClickListener(v -> {
selectedCrypto = crypto; if (possibleCryptos.contains(crypto)) {
updateCryptoButtons(false); selectedCrypto = crypto;
} else { updateCryptoButtons(false);
// 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 { } else {
tvXmrTo.setText(Html.fromHtml(getString(R.string.info_xmrto_help_xmr))); // show help what to do:
tvXmrTo.setVisibility(View.VISIBLE); 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); updateCryptoButtons(true);
@@ -181,6 +193,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
selectedCrypto = Crypto.XMR; selectedCrypto = Crypto.XMR;
sendListener.setMode(SendFragment.Mode.XMR); sendListener.setMode(SendFragment.Mode.XMR);
} }
if (!Helper.ALLOW_SHIFT) return;
if ((selectedCrypto == null) && isEthAddress(address)) { if ((selectedCrypto == null) && isEthAddress(address)) {
Timber.d("isEthAddress"); Timber.d("isEthAddress");
possibleCryptos.add(Crypto.ETH); possibleCryptos.add(Crypto.ETH);
@@ -285,6 +298,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
} }
private void updateCryptoButtons(boolean noAddress) { private void updateCryptoButtons(boolean noAddress) {
if (!Helper.ALLOW_SHIFT) return;
for (Crypto crypto : Crypto.values()) { for (Crypto crypto : Crypto.values()) {
if (crypto == selectedCrypto) { if (crypto == selectedCrypto) {
selectedCrypto(crypto); selectedCrypto(crypto);
@@ -345,7 +359,7 @@ public class SendAddressWizardFragment extends SendWizardFragment {
private boolean checkAddress() { private boolean checkAddress() {
boolean ok = checkAddressNoError(); boolean ok = checkAddressNoError();
if (!ok) { if (possibleCryptos.isEmpty()) {
etAddress.setError(getString(R.string.send_address_invalid)); etAddress.setError(getString(R.string.send_address_invalid));
} else { } else {
etAddress.setError(null); etAddress.setError(null);
@@ -447,7 +461,12 @@ public class SendAddressWizardFragment extends SendWizardFragment {
BarcodeData barcodeData = sendListener.getBarcodeData(); BarcodeData barcodeData = sendListener.getBarcodeData();
if (barcodeData != null) { if (barcodeData != null) {
Timber.d("GOT DATA"); 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) { if (barcodeData.address != null) {
etAddress.getEditText().setText(barcodeData.address); etAddress.getEditText().setText(barcodeData.address);
possibleCryptos.clear(); possibleCryptos.clear();
@@ -458,7 +477,8 @@ public class SendAddressWizardFragment extends SendWizardFragment {
possibleCryptos.add(barcodeData.asset); possibleCryptos.add(barcodeData.asset);
selectedCrypto = barcodeData.asset; selectedCrypto = barcodeData.asset;
} }
updateCryptoButtons(false); if (Helper.ALLOW_SHIFT)
updateCryptoButtons(false);
if (checkAddress()) { if (checkAddress()) {
if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC) if (barcodeData.security == BarcodeData.Security.OA_NO_DNSSEC)
etAddress.setError(getString(R.string.send_address_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()"); Timber.d("onResumeFragment()");
Helper.showKeyboard(getActivity()); Helper.showKeyboard(getActivity());
final long funds = getTotalFunds(); final long funds = getTotalFunds();
maxFunds = 1.0 * funds / 1000000000000L; maxFunds = 1.0 * funds / Helper.ONE_XMR;
if (!sendListener.getActivityCallback().isStreetMode()) { if (!sendListener.getActivityCallback().isStreetMode()) {
tvFunds.setText(getString(R.string.send_available, tvFunds.setText(getString(R.string.send_available,
Wallet.getDisplayAmount(funds))); 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.QueryOrderParameters;
import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi; import com.m2049r.xmrwallet.service.shift.sideshift.api.SideShiftApi;
import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl; import com.m2049r.xmrwallet.service.shift.sideshift.network.SideShiftApiImpl;
import com.m2049r.xmrwallet.util.OkHttpHelper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.ServiceHelper; import com.m2049r.xmrwallet.util.ServiceHelper;
import com.m2049r.xmrwallet.widget.ExchangeOtherEditText; import com.m2049r.xmrwallet.widget.ExchangeOtherEditText;
import com.m2049r.xmrwallet.widget.SendProgressView; import com.m2049r.xmrwallet.widget.SendProgressView;
@@ -89,7 +89,6 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
return view; return view;
} }
@Override @Override
public boolean onValidateFields() { public boolean onValidateFields() {
Timber.i(maxBtc + "/" + minBtc); Timber.i(maxBtc + "/" + minBtc);
@@ -179,7 +178,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
tvXmrToParms.setText(xmrParmText); tvXmrToParms.setText(xmrParmText);
final long funds = getTotalFunds(); final long funds = getTotalFunds();
double availableXmr = 1.0 * funds / 1000000000000L; double availableXmr = 1.0 * funds / Helper.ONE_XMR;
String availBtcString; String availBtcString;
String availXmrString; String availXmrString;
@@ -255,8 +254,7 @@ public class SendBtcAmountWizardFragment extends SendWizardFragment {
if (xmrToApi == null) { if (xmrToApi == null) {
synchronized (this) { synchronized (this) {
if (xmrToApi == null) { if (xmrToApi == null) {
xmrToApi = new SideShiftApiImpl(OkHttpHelper.getOkHttpClient(), xmrToApi = new SideShiftApiImpl(ServiceHelper.getXmrToBaseUrl());
ServiceHelper.getXmrToBaseUrl());
} }
} }
} }

View File

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

View File

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

View File

@@ -36,6 +36,7 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.fragment.app.FragmentStatePagerAdapter;
import androidx.viewpager.widget.ViewPager; import androidx.viewpager.widget.ViewPager;
import com.google.android.material.transition.MaterialContainerTransform;
import com.m2049r.xmrwallet.OnBackPressedListener; import com.m2049r.xmrwallet.OnBackPressedListener;
import com.m2049r.xmrwallet.OnUriScannedListener; import com.m2049r.xmrwallet.OnUriScannedListener;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
@@ -49,10 +50,12 @@ import com.m2049r.xmrwallet.layout.SpendViewPager;
import com.m2049r.xmrwallet.model.PendingTransaction; import com.m2049r.xmrwallet.model.PendingTransaction;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.Notice; import com.m2049r.xmrwallet.util.Notice;
import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.widget.DotBar; import com.m2049r.xmrwallet.widget.DotBar;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
import java.lang.ref.WeakReference; import java.lang.ref.WeakReference;
import java.util.Objects;
import timber.log.Timber; import timber.log.Timber;
@@ -63,7 +66,7 @@ public class SendFragment extends Fragment
SendSuccessWizardFragment.Listener, SendSuccessWizardFragment.Listener,
OnBackPressedListener, OnUriScannedListener { OnBackPressedListener, OnUriScannedListener {
final static public int MIXIN = 10; final static public int MIXIN = 0;
private Listener activityCallback; private Listener activityCallback;
@@ -309,7 +312,7 @@ public class SendFragment extends Fragment
SparseArray<WeakReference<SendWizardFragment>> myFragments = new SparseArray<>(); SparseArray<WeakReference<SendWizardFragment>> myFragments = new SparseArray<>();
public SpendPagerAdapter(FragmentManager fm) { public SpendPagerAdapter(FragmentManager fm) {
super(fm); super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT);
} }
public void addSuccess() { public void addSuccess() {
@@ -538,14 +541,18 @@ public class SendFragment extends Fragment
enableNavigation(); enableNavigation();
} }
@Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
final MaterialContainerTransform transform = new MaterialContainerTransform();
transform.setDrawingViewId(R.id.fragment_container);
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground));
setSharedElementEnterTransition(transform);
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.send_menu, menu); inflater.inflate(R.menu.send_menu, menu);
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
} }

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