1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-04 17:28:42 +02:00

Compare commits

...

51 Commits

Author SHA1 Message Date
m2049r
dd92f7bb36 bump version 2022-03-13 15:37:28 +01:00
m2049r
46808d306b clean transitions 2022-03-12 23:10:34 +01:00
m2049r
20503d2cbd remove send/receive transitions 2022-03-11 00:25:50 +01:00
m2049r
604691ca7e fix FABs 2022-03-10 21:06:19 +01:00
m2049r
1b626ba2b0 improve transitions 2022-03-10 20:57:57 +01:00
m2049r
0ed7bdfcee remove test Dockerfile 2022-03-10 09:07:14 +01:00
m2049r
524c3dd79f upgrade ci image to 2022.03-ndk (#822) 2022-03-10 09:04:43 +01:00
m2049r
197dffeae1 fix tests 2022-03-10 07:53:52 +01:00
m2049r
cdb29bbc2e themes (#821)
* Support multiple colour themes
* Fix sharing
* Add settings
* Fantastic UI tweaks
2022-03-09 19:10:30 +01:00
m2049r
7b96baeca7 fix watch only 2022-01-03 12:53:31 +01:00
m2049r
0712efec78 allow seed offset generation & wallet restore (#804) 2022-01-02 20:50:07 +01:00
Katant Savelev
341df6c6a3 Update RU translation (#798)
* Update help.xml

* Update strings.xml
2022-01-02 14:10:33 +01:00
m2049r
ab8fb82c1b fix unit tests (#803) 2022-01-02 14:07:23 +01:00
m2049r
22d9173cea store only through service & setPassword (#802) 2022-01-02 13:05:46 +01:00
m2049r
05720e63ab onion stuff (#796) 2021-12-05 22:24:26 +01:00
Diego Delmondes
cdc2b23257 Improve Brazillian Portuguese translation (#778)
* Improve translation

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

* Update strings.xml
2021-12-05 19:23:03 +01:00
m2049r
9f0e89719c remove arabic (#795) 2021-12-05 19:00:10 +01:00
m2049r
190726e61c Merge branch 'Fushko-it-transl' 2021-12-05 18:59:36 +01:00
m2049r
729fafdb48 fix apostrophes 2021-12-05 18:59:26 +01:00
m2049r
b7ae23ac64 Merge branch 'it-transl' of https://github.com/Fushko/xmrwallet into Fushko-it-transl 2021-12-05 18:58:32 +01:00
m2049r
27885f2c86 create wallet only by button & refactoring (#793) 2021-12-05 18:10:25 +01:00
Francesco Fusco
ce9046c7b5 Update italian translation 2021-12-01 21:36:22 +01:00
m2049r
506e6ce017 bump version 2021-10-27 08:37:52 +02:00
m2049r
f07b439731 correct settleAmount formatting for non-US locales (#791) 2021-10-26 17:36:03 +02:00
m2049r
ac70ba8424 bump version 2021-09-08 13:53:57 +02:00
m2049r
84ec1ef418 load notes on refresh (#783) 2021-09-08 13:52:23 +02:00
m2049r
b576a9de3d bump version 2021-09-05 21:58:19 +02:00
m2049r
148faa00e4 minor fixes for confirmations indicator (#782) 2021-09-05 21:24:53 +02:00
m2049r
e82b471c14 adaptaions for monero v0.17.2.3 (#781) 2021-09-05 21:23:12 +02:00
m2049r
9ed92e5117 remove defunct strings in arabic 2021-08-07 12:31:42 +02:00
m2049r
303b3aa354 dont update on every single block (#776) 2021-08-07 11:58:54 +02:00
phwright
d801a50962 Arabic Translation (#767)
* Arabic Translation
2021-08-07 11:19:24 +02:00
m2049r
9cd8a75dc6 confirmations indicator (#775) 2021-08-07 11:17:13 +02:00
m2049r
002dfd5d58 rewrite Wallet.isSynchronized() (#774) 2021-08-07 11:04:18 +02:00
paxriel
54e54b2a8a Fixed boost JFrog repo (#772) 2021-08-04 10:37:31 +02:00
m2049r
aa768596a4 bump version 2021-05-21 08:49:13 +02:00
m2049r
c4958f6c54 use settleAmount (#768) 2021-05-21 08:45:45 +02:00
m2049r
2c2a5314d4 fix migration of devices with permission timeouts 2021-05-19 22:20:47 +02:00
m2049r
669516c60b add USE_FINGERPRINT again (#766)
and update sdk
2021-05-06 00:03:39 +02:00
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
252 changed files with 5110 additions and 2584 deletions

View File

@@ -3,13 +3,11 @@ jobs:
build: build:
working_directory: ~/code working_directory: ~/code
docker: docker:
- image: circleci/android:api-28-ndk - image: cimg/android:2022.03-ndk
environment: environment:
JVM_OPTS: -Xmx3200m JVM_OPTS: -Xmx3200m
steps: steps:
- checkout - checkout
- run: yes | sdkmanager --licenses || exit 0
- run: yes | sdkmanager --update || exit 0
- restore_cache: - restore_cache:
key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
- run: - run:

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 30 compileSdkVersion 31
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 30 targetSdkVersion 31
versionCode 1002 versionCode 1401
versionName "2.0.2 'Puginarug'" versionName "2.4.1 'Baldaŭ'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@@ -112,42 +113,46 @@ 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.7.0'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.2.0' implementation 'androidx.recyclerview:recyclerview:1.2.1'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'androidx.preference:preference:1.2.0'
implementation 'com.google.android.material:material:1.5.0'
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.3"
implementation "com.burgstaller:okhttp-digest:2.1" implementation "io.github.rburgst:okhttp-digest:2.6"
implementation "com.jakewharton.timber:timber:4.7.1" implementation "com.jakewharton.timber:timber:5.0.1"
implementation 'com.nulab-inc:zxcvbn:1.3.0' 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.5.2'
implementation 'dnsjava:dnsjava:2.1.9' implementation 'dnsjava:dnsjava:2.1.9'
implementation 'org.jitsi:dnssecjava:1.2.0' implementation 'org.jitsi:dnssecjava:1.2.0'
implementation 'org.slf4j:slf4j-nop:1.7.30' implementation 'org.slf4j:slf4j-nop:1.7.36'
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:4.13.2"
testImplementation "org.mockito:mockito-all:1.10.19"
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
testImplementation 'org.json:json:20211205'
testImplementation 'net.jodah:concurrentunit:0.4.6'
testImplementation "junit:junit:$rootProject.ext.junitVersion" compileOnly 'org.projectlombok:lombok:1.18.22'
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion" annotationProcessor 'org.projectlombok:lombok:1.18.22'
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
testImplementation 'org.json:json:20180813'
testImplementation 'net.jodah:concurrentunit:0.4.4'
compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16'
} }

View File

@@ -4,28 +4,48 @@
<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.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.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:preserveLegacyExternalStorage="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/MyMaterialThemeClassic"
android:usesCleartextTraffic="true"> android:usesCleartextTraffic="true">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="orientation|keyboardHidden|uiMode" android:configChanges="orientation|keyboardHidden|uiMode"
android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="portrait"> android:screenOrientation="portrait">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
@@ -34,27 +54,28 @@
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"
android:exported="true"
android:label="@string/app_name" android:label="@string/app_name"
android:launchMode="singleTop" android:launchMode="singleTop"
android:screenOrientation="locked"> android:screenOrientation="locked">
<intent-filter> <intent-filter>
<action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" /> <action android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED" />
</intent-filter> </intent-filter>
<intent-filter android:label="@string/app_name"> <intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<data android:scheme="monero" /> <data android:scheme="monero" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" /> <category android:name="android.intent.category.BROWSABLE" />
</intent-filter> </intent-filter>
<intent-filter android:label="@string/app_name"> <intent-filter android:label="@string/app_name">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<data android:scheme="bitcoin" /> <data android:scheme="bitcoin" />
<category android:name="android.intent.category.DEFAULT" /> <category android:name="android.intent.category.DEFAULT" />
@@ -87,4 +108,5 @@
android:resource="@xml/filepaths" /> android:resource="@xml/filepaths" />
</provider> </provider>
</application> </application>
</manifest>
</manifest>

View File

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

View File

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

View File

@@ -27,6 +27,7 @@ import android.nfc.NfcAdapter;
import android.nfc.Tag; import android.nfc.Tag;
import android.nfc.tech.Ndef; import android.nfc.tech.Ndef;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
@@ -180,7 +181,7 @@ public class BaseActivity extends SecureActivity
return; return;
nfcPendingIntent = PendingIntent.getActivity(this, 0, nfcPendingIntent = PendingIntent.getActivity(this, 0,
new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP), new Intent(this, getClass()).addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP),
0); Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? PendingIntent.FLAG_IMMUTABLE : 0);
} }
private void processNfcIntent(Intent intent) { private void processNfcIntent(Intent intent) {

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

View File

@@ -46,10 +46,7 @@ import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.widget.ShareActionProvider;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider; import androidx.core.content.FileProvider;
import androidx.core.view.MenuItemCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
@@ -89,6 +86,7 @@ public class ReceiveFragment extends Fragment {
private ImageView ivQrCodeFull; private ImageView ivQrCodeFull;
private EditText etDummy; private EditText etDummy;
private ImageButton bCopyAddress; private ImageButton bCopyAddress;
private MenuItem shareItem;
private Wallet wallet = null; private Wallet wallet = null;
private boolean isMyWallet = false; private boolean isMyWallet = false;
@@ -128,6 +126,7 @@ public class ReceiveFragment extends Fragment {
evAmount.setOnNewAmountListener(xmr -> { evAmount.setOnNewAmountListener(xmr -> {
Timber.d("new amount = %s", xmr); Timber.d("new amount = %s", xmr);
generateQr(); generateQr();
if (shareRequested && (xmr != null)) share();
}); });
evAmount.setOnFailedExchangeListener(() -> { evAmount.setOnFailedExchangeListener(() -> {
@@ -211,39 +210,38 @@ public class ReceiveFragment extends Fragment {
setSharedElementEnterTransition(transform); setSharedElementEnterTransition(transform);
} }
private ShareActionProvider shareActionProvider; private boolean shareRequested = false;
@Override @Override
public void onCreateOptionsMenu(@NonNull Menu menu, final MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull Menu menu, final MenuInflater inflater) {
inflater.inflate(R.menu.receive_menu, menu); inflater.inflate(R.menu.receive_menu, menu);
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
// Locate MenuItem with ShareActionProvider shareItem = menu.findItem(R.id.menu_item_share);
MenuItem item = menu.findItem(R.id.menu_item_share); shareItem.setOnMenuItemClickListener(item -> {
if (shareRequested) return true;
// Fetch and store ShareActionProvider shareRequested = true;
shareActionProvider = (ShareActionProvider) MenuItemCompat.getActionProvider(item); if (!qrValid) {
evAmount.doExchange();
shareActionProvider.setOnShareTargetSelectedListener(new ShareActionProvider.OnShareTargetSelectedListener() { } else {
@Override share();
public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) {
saveQrCode(); // save it only if we need it
return false;
} }
return true;
}); });
} }
private void setShareIntent() { private void share() {
if (shareActionProvider != null) { shareRequested = false;
if (qrValid) { if (saveQrCode()) {
shareActionProvider.setShareIntent(getShareIntent()); final Intent sendIntent = getSendIntent();
} else { if (sendIntent != null)
shareActionProvider.setShareIntent(null); startActivity(Intent.createChooser(sendIntent, null));
} } else {
Toast.makeText(getActivity(), getString(R.string.message_qr_failed), Toast.LENGTH_SHORT).show();
} }
} }
private void saveQrCode() { private boolean saveQrCode() {
if (!qrValid) throw new IllegalStateException("trying to save null qr code!"); if (!qrValid) throw new IllegalStateException("trying to save null qr code!");
File cachePath = new File(getActivity().getCacheDir(), "images"); File cachePath = new File(getActivity().getCacheDir(), "images");
@@ -255,33 +253,35 @@ public class ReceiveFragment extends Fragment {
Bitmap qrBitmap = ((BitmapDrawable) ivQrCode.getDrawable()).getBitmap(); Bitmap qrBitmap = ((BitmapDrawable) ivQrCode.getDrawable()).getBitmap();
qrBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream); qrBitmap.compress(Bitmap.CompressFormat.PNG, 100, stream);
stream.close(); stream.close();
return true;
} catch (IOException ex) { } catch (IOException ex) {
Timber.e(ex); Timber.e(ex);
// make sure we don't share an old qr code // make sure we don't share an old qr code
if (!png.delete()) throw new IllegalStateException("cannot delete old qr code"); if (!png.delete()) throw new IllegalStateException("cannot delete old qr code");
// if we manage to delete it, the URI points to nothing and the user gets a toast with the error // if we manage to delete it, the URI points to nothing and the user gets a toast with the error
} }
return false;
} }
private Intent getShareIntent() { private Intent getSendIntent() {
File imagePath = new File(getActivity().getCacheDir(), "images"); File imagePath = new File(requireActivity().getCacheDir(), "images");
File png = new File(imagePath, "QR.png"); File png = new File(imagePath, "QR.png");
Uri contentUri = FileProvider.getUriForFile(getActivity(), Uri contentUri = FileProvider.getUriForFile(requireActivity(), BuildConfig.APPLICATION_ID + ".fileprovider", png);
BuildConfig.APPLICATION_ID + ".fileprovider", png);
if (contentUri != null) { if (contentUri != null) {
Intent shareIntent = new Intent(); Intent shareIntent = new Intent();
shareIntent.setAction(Intent.ACTION_SEND); shareIntent.setAction(Intent.ACTION_SEND);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); // temp permission for receiving app to read this file
shareIntent.setDataAndType(contentUri, getActivity().getContentResolver().getType(contentUri)); shareIntent.setTypeAndNormalize("image/png");
shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri); shareIntent.putExtra(Intent.EXTRA_STREAM, contentUri);
shareIntent.putExtra(Intent.EXTRA_TEXT, bcData.getUriString()); if (bcData != null)
shareIntent.putExtra(Intent.EXTRA_TEXT, bcData.getUriString());
return shareIntent; return shareIntent;
} }
return null; return null;
} }
void copyAddress() { void copyAddress() {
Helper.clipBoardCopy(Objects.requireNonNull(getActivity()), getString(R.string.label_copy_address), subaddress.getAddress()); Helper.clipBoardCopy(requireActivity(), getString(R.string.label_copy_address), subaddress.getAddress());
Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), getString(R.string.message_copy_address), Toast.LENGTH_SHORT).show();
} }
@@ -291,7 +291,6 @@ public class ReceiveFragment extends Fragment {
if (qrValid) { if (qrValid) {
ivQrCode.setImageBitmap(null); ivQrCode.setImageBitmap(null);
qrValid = false; qrValid = false;
setShareIntent();
if (isLoaded) if (isLoaded)
tvQrCode.setVisibility(View.VISIBLE); tvQrCode.setVisibility(View.VISIBLE);
} }
@@ -300,7 +299,6 @@ public class ReceiveFragment extends Fragment {
void setQR(Bitmap qr) { void setQR(Bitmap qr) {
ivQrCode.setImageBitmap(qr); ivQrCode.setImageBitmap(qr);
qrValid = true; qrValid = true;
setShareIntent();
tvQrCode.setVisibility(View.GONE); tvQrCode.setVisibility(View.GONE);
} }
@@ -462,8 +460,8 @@ public class ReceiveFragment extends Fragment {
subaddress = newSubaddress; subaddress = newSubaddress;
final Context context = getContext(); final Context context = getContext();
Spanned label = Html.fromHtml(context.getString(R.string.receive_subaddress, Spanned label = Html.fromHtml(context.getString(R.string.receive_subaddress,
Integer.toHexString(ContextCompat.getColor(context, R.color.monerujoGreen) & 0xFFFFFF), Integer.toHexString(ThemeHelper.getThemedColor(context, R.attr.positiveColor) & 0xFFFFFF),
Integer.toHexString(ContextCompat.getColor(context, R.color.monerujoBackground) & 0xFFFFFF), Integer.toHexString(ThemeHelper.getThemedColor(context, android.R.attr.colorBackground) & 0xFFFFFF),
subaddress.getDisplayLabel(), subaddress.getAddress())); subaddress.getDisplayLabel(), subaddress.getAddress()));
tvAddress.setText(label); tvAddress.setText(label);
generateQr(); generateQr();

View File

@@ -66,11 +66,7 @@ public abstract class SecureActivity extends AppCompatActivity {
Locale locale = LocaleHelper.getPreferredLocale(this); Locale locale = LocaleHelper.getPreferredLocale(this);
if (locale != null) { if (locale != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { config.setLocale(locale);
config.setLocale(locale);
} else {
config.locale = locale;
}
} }
return config; return config;
} }

View File

@@ -0,0 +1,125 @@
package com.m2049r.xmrwallet;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import androidx.annotation.StyleRes;
import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import com.m2049r.xmrwallet.dialog.AboutFragment;
import com.m2049r.xmrwallet.dialog.CreditsFragment;
import com.m2049r.xmrwallet.dialog.PrivacyFragment;
import com.m2049r.xmrwallet.util.DayNightMode;
import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper;
import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Locale;
import timber.log.Timber;
public class SettingsFragment extends PreferenceFragmentCompat
implements SharedPreferences.OnSharedPreferenceChangeListener {
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
setPreferencesFromResource(R.xml.root_preferences, rootKey);
findPreference(getString(R.string.about_info)).setOnPreferenceClickListener(preference -> {
AboutFragment.display(getParentFragmentManager());
return true;
});
findPreference(getString(R.string.privacy_info)).setOnPreferenceClickListener(preference -> {
PrivacyFragment.display(getParentFragmentManager());
return true;
});
findPreference(getString(R.string.credits_info)).setOnPreferenceClickListener(preference -> {
CreditsFragment.display(getParentFragmentManager());
return true;
});
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(getString(R.string.preferred_locale))) {
activity.recreate();
} else if (key.equals(getString(R.string.preferred_nightmode))) {
NightmodeHelper.setNightMode(DayNightMode.valueOf(sharedPreferences.getString(key, "AUTO")));
} else if (key.equals(getString(R.string.preferred_theme))) {
ThemeHelper.setTheme((Activity) activity, sharedPreferences.getString(key, "Classic"));
activity.recreate();
}
}
private SettingsFragment.Listener activity;
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof SettingsFragment.Listener) {
activity = (SettingsFragment.Listener) context;
} else {
throw new ClassCastException(context + " must implement Listener");
}
}
@Override
public void onResume() {
super.onResume();
Timber.d("onResume()");
activity.setSubtitle(getString(R.string.menu_settings));
activity.setToolbarButton(Toolbar.BUTTON_BACK);
populateLanguages();
PreferenceManager.getDefaultSharedPreferences(requireContext())
.registerOnSharedPreferenceChangeListener(this);
}
@Override
public void onPause() {
super.onPause();
PreferenceManager.getDefaultSharedPreferences(requireContext())
.unregisterOnSharedPreferenceChangeListener(this);
}
public interface Listener {
void setToolbarButton(int type);
void setSubtitle(String title);
void recreate();
void setTheme(@StyleRes final int resId);
}
public void populateLanguages() {
ListPreference language = findPreference(getString(R.string.preferred_locale));
assert language != null;
final ArrayList<Locale> availableLocales = LocaleHelper.getAvailableLocales(requireContext());
Collections.sort(availableLocales, (locale1, locale2) -> {
String localeString1 = LocaleHelper.getDisplayName(locale1, true);
String localeString2 = LocaleHelper.getDisplayName(locale2, true);
return localeString1.compareTo(localeString2);
});
String[] localeDisplayNames = new String[1 + availableLocales.size()];
localeDisplayNames[0] = getString(R.string.language_system_default);
for (int i = 1; i < localeDisplayNames.length; i++) {
localeDisplayNames[i] = LocaleHelper.getDisplayName(availableLocales.get(i - 1), true);
}
language.setEntries(localeDisplayNames);
String[] languageTags = new String[1 + availableLocales.size()];
languageTags[0] = "";
for (int i = 1; i < languageTags.length; i++) {
languageTags[i] = availableLocales.get(i - 1).toLanguageTag();
}
language.setEntryValues(languageTags);
}
}

View File

@@ -30,7 +30,6 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.transition.MaterialElevationScale;
import com.m2049r.xmrwallet.data.Subaddress; import com.m2049r.xmrwallet.data.Subaddress;
import com.m2049r.xmrwallet.layout.SubaddressInfoAdapter; import com.m2049r.xmrwallet.layout.SubaddressInfoAdapter;
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog; import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
@@ -67,6 +66,8 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
void setToolbarButton(int type); void setToolbarButton(int type);
void showSubaddress(View view, final int subaddressIndex); void showSubaddress(View view, final int subaddressIndex);
void saveWallet();
} }
public interface ProgressListener { public interface ProgressListener {
@@ -115,14 +116,6 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
managerMode = ((b != null) && (MODE_MANAGER.equals(b.getString(KEY_MODE)))); managerMode = ((b != null) && (MODE_MANAGER.equals(b.getString(KEY_MODE))));
View view = inflater.inflate(R.layout.fragment_subaddress, container, false); 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); view.findViewById(R.id.fab).setOnClickListener(this);
if (managerMode) { if (managerMode) {
@@ -152,11 +145,6 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
postponeEnterTransition();
view.getViewTreeObserver().addOnPreDrawListener(() -> {
startPostponedEnterTransition();
return true;
});
} }
public void loadList() { public void loadList() {
@@ -217,7 +205,9 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
protected Boolean doInBackground(Void... params) { protected Boolean doInBackground(Void... params) {
if (params.length != 0) return false; if (params.length != 0) return false;
wallet.getNewSubaddress(); wallet.getNewSubaddress();
wallet.store(); if (activityCallback != null) {
activityCallback.saveWallet();
}
return true; return true;
} }

View File

@@ -29,15 +29,15 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.transition.Transition;
import androidx.transition.TransitionInflater;
import com.google.android.material.textfield.TextInputLayout; import com.google.android.material.textfield.TextInputLayout;
import com.google.android.material.transition.MaterialContainerTransform;
import com.m2049r.xmrwallet.data.Subaddress; import com.m2049r.xmrwallet.data.Subaddress;
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter; import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
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.util.ArrayList; import java.util.ArrayList;
@@ -102,10 +102,8 @@ public class SubaddressInfoFragment extends Fragment
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
final MaterialContainerTransform transform = new MaterialContainerTransform(); Transition transform = TransitionInflater.from(requireContext())
transform.setDrawingViewId(R.id.fragment_container); .inflateTransition(R.transition.details);
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground));
setSharedElementEnterTransition(transform); setSharedElementEnterTransition(transform);
} }

View File

@@ -35,11 +35,10 @@ import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.transition.Transition;
import androidx.transition.TransitionInflater;
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.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;
@@ -97,13 +96,6 @@ public class TxFragment extends Fragment {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(LayoutInflater inflater, ViewGroup container, 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);
@@ -220,8 +212,8 @@ public class TxFragment extends Fragment {
final Context ctx = getContext(); final Context ctx = getContext();
Spanned label = Html.fromHtml(ctx.getString(R.string.tx_account_formatted, Spanned label = Html.fromHtml(ctx.getString(R.string.tx_account_formatted,
info.accountIndex, info.addressIndex, info.accountIndex, info.addressIndex,
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoGreen) & 0xFFFFFF), Integer.toHexString(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF),
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoBackground) & 0xFFFFFF), Integer.toHexString(ThemeHelper.getThemedColor(ctx, android.R.attr.colorBackground) & 0xFFFFFF),
subaddress.getDisplayLabel())); subaddress.getDisplayLabel()));
tvAccount.setText(label); tvAccount.setText(label);
tvAccount.setOnClickListener(v -> activityCallback.showSubaddress(v, info.addressIndex)); tvAccount.setOnClickListener(v -> activityCallback.showSubaddress(v, info.addressIndex));
@@ -266,13 +258,13 @@ public class TxFragment extends Fragment {
if (info.isFailed) { if (info.isFailed) {
tvTxAmount.setText(getString(R.string.tx_list_amount_failed, Wallet.getDisplayAmount(info.amount))); tvTxAmount.setText(getString(R.string.tx_list_amount_failed, Wallet.getDisplayAmount(info.amount)));
tvTxFee.setText(getString(R.string.tx_list_failed_text)); tvTxFee.setText(getString(R.string.tx_list_failed_text));
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_failed)); setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor));
} else if (info.isPending) { } else if (info.isPending) {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_pending)); setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor));
} else if (info.direction == TransactionInfo.Direction.Direction_In) { } else if (info.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_plus)); setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.positiveColor));
} else { } else {
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_minus)); setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.negativeColor));
} }
Set<String> destinations = new HashSet<>(); Set<String> destinations = new HashSet<>();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -348,10 +340,8 @@ 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(); Transition transform = TransitionInflater.from(requireContext())
transform.setDrawingViewId(R.id.fragment_container); .inflateTransition(R.transition.details);
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground));
setSharedElementEnterTransition(transform); setSharedElementEnterTransition(transform);
} }

View File

@@ -356,6 +356,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
Timber.d("onCreate()"); Timber.d("onCreate()");
ThemeHelper.setPreferred(this);
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
if (savedInstanceState != null) { if (savedInstanceState != null) {
// activity restarted // activity restarted
@@ -385,7 +386,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
case Toolbar.BUTTON_CLOSE: case Toolbar.BUTTON_CLOSE:
finish(); finish();
break; break;
case Toolbar.BUTTON_CREDITS: case Toolbar.BUTTON_SETTINGS:
Toast.makeText(WalletActivity.this, getString(R.string.label_credits), Toast.LENGTH_SHORT).show(); Toast.makeText(WalletActivity.this, getString(R.string.label_credits), Toast.LENGTH_SHORT).show();
case Toolbar.BUTTON_NONE: case Toolbar.BUTTON_NONE:
default: default:
@@ -505,6 +506,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
Timber.d("onResume()"); Timber.d("onResume()");
} }
@Override
public void saveWallet() { public void saveWallet() {
if (mIsBound) { // no point in talking to unbound service if (mIsBound) { // no point in talking to unbound service
Intent intent = new Intent(getApplicationContext(), WalletService.class); Intent intent = new Intent(getApplicationContext(), WalletService.class);
@@ -537,7 +539,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override @Override
public void onSendRequest(View view) { public void onSendRequest(View view) {
replaceFragmentWithTransition(view, SendFragment.newInstance(uri), null, null); replaceFragment(SendFragment.newInstance(uri), null, null);
uri = null; // only use uri once uri = null; // only use uri once
} }
@@ -578,7 +580,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
try { try {
final WalletFragment walletFragment = getWalletFragment(); final WalletFragment walletFragment = getWalletFragment();
if (wallet.isSynchronized()) { if (wallet.isSynchronized()) {
Timber.d("onRefreshed() synced");
releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced releaseWakeLock(RELEASE_WAKE_LOCK_DELAY); // the idea is to stay awake until synced
if (!synced) { // first sync if (!synced) { // first sync
onProgress(-1); onProgress(-1);
@@ -610,9 +611,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override @Override
public void onWalletStored(final boolean success) { public void onWalletStored(final boolean success) {
runOnUiThread(() -> { runOnUiThread(() -> {
if (success) { if (!success) {
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unloaded), Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unload_failed), Toast.LENGTH_LONG).show(); Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unload_failed), Toast.LENGTH_LONG).show();
} }
}); });
@@ -801,10 +800,6 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
int transition; int transition;
if (newFragment instanceof TxFragment) if (newFragment instanceof TxFragment)
transition = R.string.tx_details_transition_name; transition = R.string.tx_details_transition_name;
else if (newFragment instanceof ReceiveFragment)
transition = R.string.receive_transition_name;
else if (newFragment instanceof SendFragment)
transition = R.string.send_transition_name;
else if (newFragment instanceof SubaddressInfoFragment) else if (newFragment instanceof SubaddressInfoFragment)
transition = R.string.subaddress_info_transition_name; transition = R.string.subaddress_info_transition_name;
else else
@@ -942,6 +937,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
@Override @Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
Timber.d("onRequestPermissionsResult()"); Timber.d("onRequestPermissionsResult()");
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == Helper.PERMISSIONS_REQUEST_CAMERA) { // If request is cancelled, the result arrays are empty. if (requestCode == Helper.PERMISSIONS_REQUEST_CAMERA) { // If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) { && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
@@ -960,7 +956,7 @@ public class WalletActivity extends BaseActivity implements WalletFragment.Liste
Bundle b = new Bundle(); Bundle b = new Bundle();
b.putString("address", address); b.putString("address", address);
b.putString("name", getWalletName()); b.putString("name", getWalletName());
replaceFragmentWithTransition(view, new ReceiveFragment(), null, b); replaceFragment(new ReceiveFragment(), null, b);
Timber.d("ReceiveFragment placed"); Timber.d("ReceiveFragment placed");
} }

View File

@@ -43,7 +43,6 @@ import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.github.brnunes.swipeablerecyclerview.SwipeableRecyclerViewTouchListener; import com.github.brnunes.swipeablerecyclerview.SwipeableRecyclerViewTouchListener;
import com.google.android.material.transition.MaterialElevationScale;
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter; import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
@@ -52,13 +51,13 @@ import com.m2049r.xmrwallet.service.exchange.api.ExchangeCallback;
import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate; import com.m2049r.xmrwallet.service.exchange.api.ExchangeRate;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.ServiceHelper; import com.m2049r.xmrwallet.util.ServiceHelper;
import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects;
import timber.log.Timber; import timber.log.Timber;
@@ -112,7 +111,8 @@ public class WalletFragment extends Fragment
llBalance = view.findViewById(R.id.llBalance); llBalance = view.findViewById(R.id.llBalance);
flExchange = view.findViewById(R.id.flExchange); flExchange = view.findViewById(R.id.flExchange);
((ProgressBar) view.findViewById(R.id.pbExchange)).getIndeterminateDrawable(). ((ProgressBar) view.findViewById(R.id.pbExchange)).getIndeterminateDrawable().
setColorFilter(getResources().getColor(R.color.progress_circle), setColorFilter(
ThemeHelper.getThemedColor(getContext(), R.attr.colorPrimaryVariant),
android.graphics.PorterDuff.Mode.MULTIPLY); android.graphics.PorterDuff.Mode.MULTIPLY);
tvProgress = view.findViewById(R.id.tvProgress); tvProgress = view.findViewById(R.id.tvProgress);
@@ -128,7 +128,7 @@ public class WalletFragment extends Fragment
currencies.add(Helper.BASE_CRYPTO); currencies.add(Helper.BASE_CRYPTO);
if (Helper.SHOW_EXCHANGERATES) if (Helper.SHOW_EXCHANGERATES)
currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency))); currencies.addAll(Arrays.asList(getResources().getStringArray(R.array.currency)));
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(Objects.requireNonNull(getContext()), R.layout.item_spinner_balance, currencies); ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(requireContext(), R.layout.item_spinner_balance, currencies);
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
sCurrency.setAdapter(spinnerAdapter); sCurrency.setAdapter(spinnerAdapter);
@@ -203,11 +203,6 @@ public class WalletFragment extends Fragment
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
postponeEnterTransition();
view.getViewTreeObserver().addOnPreDrawListener(() -> {
startPostponedEnterTransition();
return true;
});
} }
void showBalance(String balance) { void showBalance(String balance) {
@@ -224,11 +219,13 @@ public class WalletFragment extends Fragment
} }
void showUnconfirmed(double unconfirmedAmount) { void showUnconfirmed(double unconfirmedAmount) {
if (!activityCallback.isStreetMode()) { if (activityCallback.isStreetMode() || unconfirmedAmount == 0) {
tvUnconfirmedAmount.setText(null);
tvUnconfirmedAmount.setVisibility(View.GONE);
} else {
String unconfirmed = Helper.getFormattedAmount(unconfirmedAmount, true); String unconfirmed = Helper.getFormattedAmount(unconfirmedAmount, true);
tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed)); tvUnconfirmedAmount.setText(getResources().getString(R.string.xmr_unconfirmed_amount, unconfirmed));
} else { tvUnconfirmedAmount.setVisibility(View.VISIBLE);
tvUnconfirmedAmount.setText(null);
} }
} }
@@ -330,29 +327,24 @@ public class WalletFragment extends Fragment
// Callbacks from TransactionInfoAdapter // Callbacks from TransactionInfoAdapter
@Override @Override
public void onInteraction(final View view, final TransactionInfo infoItem) { public void onInteraction(final View view, final TransactionInfo infoItem) {
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);
activityCallback.onTxDetailsRequest(view, infoItem); activityCallback.onTxDetailsRequest(view, infoItem);
} }
// called from activity
// if account index has changed scroll to top? // if account index has changed scroll to top?
private int accountIndex = 0; private int accountIndex = 0;
public void onRefreshed(final Wallet wallet, final boolean full) { public void onRefreshed(final Wallet wallet, boolean full) {
Timber.d("onRefreshed(%b)", full); Timber.d("onRefreshed(%b)", full);
if (adapter.needsTransactionUpdateOnNewBlock()) {
wallet.refreshHistory();
full = true;
}
if (full) { if (full) {
List<TransactionInfo> list = new ArrayList<>(); List<TransactionInfo> list = new ArrayList<>();
final long streetHeight = activityCallback.getStreetModeHeight(); final long streetHeight = activityCallback.getStreetModeHeight();
Timber.d("StreetHeight=%d", streetHeight); Timber.d("StreetHeight=%d", streetHeight);
wallet.refreshHistory();
for (TransactionInfo info : wallet.getHistory().getAll()) { for (TransactionInfo info : wallet.getHistory().getAll()) {
Timber.d("TxHeight=%d, Label=%s", info.blockheight, info.subaddressLabel); Timber.d("TxHeight=%d, Label=%s", info.blockheight, info.subaddressLabel);
if ((info.isPending || (info.blockheight >= streetHeight)) if ((info.isPending || (info.blockheight >= streetHeight))
@@ -530,8 +522,6 @@ public class WalletFragment extends Fragment
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
setExitTransition(null);
setReenterTransition(null);
Timber.d("onResume()"); Timber.d("onResume()");
activityCallback.setTitle(walletTitle, walletSubtitle); activityCallback.setTitle(walletTitle, walletSubtitle);
activityCallback.setToolbarButton(Toolbar.BUTTON_NONE); activityCallback.setToolbarButton(Toolbar.BUTTON_NONE);
@@ -561,7 +551,7 @@ public class WalletFragment extends Fragment
//TODO figure out why gunther disappears on return from send although he is still set //TODO figure out why gunther disappears on return from send although he is still set
if (enable) { if (enable) {
if (streetGunther == null) if (streetGunther == null)
streetGunther = ContextCompat.getDrawable(Objects.requireNonNull(getContext()), R.drawable.ic_gunther_streetmode); streetGunther = ContextCompat.getDrawable(requireContext(), R.drawable.ic_gunther_streetmode);
ivStreetGunther.setImageDrawable(streetGunther); ivStreetGunther.setImageDrawable(streetGunther);
} else } else
ivStreetGunther.setImageDrawable(null); ivStreetGunther.setImageDrawable(null);

View File

@@ -21,22 +21,29 @@ 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 androidx.fragment.app.FragmentManager;
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();
FragmentManager.enableNewStateManager(false);
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 +52,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(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF),
Integer.toHexString(ThemeHelper.getThemedColor(ctx, android.R.attr.colorBackground) & 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, android.R.attr.textColorSecondary));
}
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

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

View File

@@ -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,8 +39,8 @@ 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 com.m2049r.xmrwallet.util.ThemeHelper;
import java.text.NumberFormat; import java.text.NumberFormat;
import java.util.Locale; import java.util.Locale;
@@ -213,19 +213,27 @@ public class SendBtcSuccessWizardFragment extends SendWizardFragment {
if (status.isError()) { if (status.isError()) {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_error, status.toString())); tvXmrToStatus.setText(getString(R.string.info_send_xmrto_error, status.toString()));
statusResource = R.drawable.ic_error_red_24dp; statusResource = R.drawable.ic_error_red_24dp;
pbXmrto.getIndeterminateDrawable().setColorFilter(0xff8b0000, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(
ThemeHelper.getThemedColor(getContext(), android.R.attr.colorError),
android.graphics.PorterDuff.Mode.MULTIPLY);
} else if (status.isSent() || status.isPaid()) { } else if (status.isSent() || status.isPaid()) {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_sent, btcData.getBtcSymbol())); tvXmrToStatus.setText(getString(R.string.info_send_xmrto_sent, btcData.getBtcSymbol()));
statusResource = R.drawable.ic_success_green_24dp; statusResource = R.drawable.ic_success;
pbXmrto.getIndeterminateDrawable().setColorFilter(0xFF417505, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(
ThemeHelper.getThemedColor(getContext(), R.attr.positiveColor),
android.graphics.PorterDuff.Mode.MULTIPLY);
} else if (status.isWaiting()) { } else if (status.isWaiting()) {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_unpaid)); tvXmrToStatus.setText(getString(R.string.info_send_xmrto_unpaid));
statusResource = R.drawable.ic_pending_orange_24dp; statusResource = R.drawable.ic_pending;
pbXmrto.getIndeterminateDrawable().setColorFilter(0xFFFF6105, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(
ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor),
android.graphics.PorterDuff.Mode.MULTIPLY);
} else if (status.isPending()) { } else if (status.isPending()) {
tvXmrToStatus.setText(getString(R.string.info_send_xmrto_paid)); tvXmrToStatus.setText(getString(R.string.info_send_xmrto_paid));
statusResource = R.drawable.ic_pending_orange_24dp; statusResource = R.drawable.ic_pending;
pbXmrto.getIndeterminateDrawable().setColorFilter(0xFFFF6105, android.graphics.PorterDuff.Mode.MULTIPLY); pbXmrto.getIndeterminateDrawable().setColorFilter(
ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor),
android.graphics.PorterDuff.Mode.MULTIPLY);
} else { } else {
throw new IllegalStateException("status is broken: " + status.toString()); throw new IllegalStateException("status is broken: " + status.toString());
} }
@@ -245,8 +253,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

@@ -55,7 +55,6 @@ 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;
@@ -202,14 +201,14 @@ public class SendFragment extends Fragment
CharSequence nextLabel = pagerAdapter.getPageTitle(position + 1); CharSequence nextLabel = pagerAdapter.getPageTitle(position + 1);
bNext.setText(nextLabel); bNext.setText(nextLabel);
if (nextLabel != null) { if (nextLabel != null) {
bNext.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_navigate_next_white_24dp, 0); bNext.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.ic_navigate_next, 0);
} else { } else {
bNext.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); bNext.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
} }
CharSequence prevLabel = pagerAdapter.getPageTitle(position - 1); CharSequence prevLabel = pagerAdapter.getPageTitle(position - 1);
bPrev.setText(prevLabel); bPrev.setText(prevLabel);
if (prevLabel != null) { if (prevLabel != null) {
bPrev.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_navigate_prev_white_24dp, 0, 0, 0); bPrev.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_navigate_prev, 0, 0, 0);
} else { } else {
bPrev.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0); bPrev.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
} }

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