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

Compare commits

...

40 Commits

Author SHA1 Message Date
m2049r
41a7b7ba61 bump version 2025-06-17 19:27:03 +02:00
XD22
d739d8049e Fix node port input field (#943) 2025-06-15 22:48:39 +02:00
Lucas
91384b827a Weblate (#996)
* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 88.4% (329 of 372 strings)

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/pt_BR/

* Update translation files

Updated by "Cleanup translation files" add-on in Weblate.

Translation: monerujo/App - Help
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-help/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 75.0% (15 of 20 strings)

Translation: monerujo/App - Help
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-help/pt_BR/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (5 of 5 strings)

Translation: monerujo/App - About
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-about/pt_BR/

* Translated using Weblate (Spanish)

Currently translated at 95.1% (354 of 372 strings)

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/es/

* Translated using Weblate (Spanish)

Currently translated at 80.0% (4 of 5 strings)

Translation: monerujo/App - About
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-about/es/

* Translated using Weblate (Spanish)

Currently translated at 95.0% (19 of 20 strings)

Translation: monerujo/App - Help
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-help/es/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (5 of 5 strings)

Translation: monerujo/App - About
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-about/pt_BR/

* Translated using Weblate (French)

Currently translated at 100.0% (372 of 372 strings)

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/fr/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (372 of 372 strings)

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/tr/

* Translated using Weblate (Turkish)

Currently translated at 100.0% (372 of 372 strings)

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/tr/

* Added translation using Weblate (Greek)

* Added translation using Weblate (Serbian)

* Added translation using Weblate (Estonian)

* Added translation using Weblate (Serbian)

* Translated using Weblate (French)

Currently translated at 100.0% (5 of 5 strings)

Translation: monerujo/App - About
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-about/fr/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (20 of 20 strings)

Translation: monerujo/App - Help
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-help/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (372 of 372 strings)

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (20 of 20 strings)

Translation: monerujo/App - Help
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-help/uk/

* Translated using Weblate (Tamil)

Currently translated at 100.0% (20 of 20 strings)

Translation: monerujo/App - Help
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-help/ta/

* Translated using Weblate (Tamil)

Currently translated at 100.0% (372 of 372 strings)

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/ta/

* Translated using Weblate (Tamil)

Currently translated at 100.0% (20 of 20 strings)

Translation: monerujo/App - Help
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-help/ta/

* Translated using Weblate (French)

Currently translated at 100.0% (20 of 20 strings)

Translation: monerujo/App - Help
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-help/fr/

* Address build errors

We can remove translations since Weblate will just tag as them 'previously translated', and they're broken so what can we do

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (20 of 20 strings)

Translation: monerujo/App - Help
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-help/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (20 of 20 strings)

Translation: monerujo/App - Help
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-help/uk/

* Translated using Weblate (Ukrainian)

Currently translated at 100.0% (20 of 20 strings)

Translation: monerujo/App - Help
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-help/uk/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (372 of 372 strings)

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/pt_BR/

* Translated using Weblate (Russian)

Currently translated at 94.8% (353 of 372 strings)

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/ru/

* Translated using Weblate (Russian)

Currently translated at 100.0% (372 of 372 strings)

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/ru/

* Added translation using Weblate (Czech)

* Added translation using Weblate (Czech)

* Added translation using Weblate (Czech)

* Translated using Weblate (Czech)

Currently translated at 80.0% (4 of 5 strings)

Translation: monerujo/App - About
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-about/cs/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (372 of 372 strings)

Translation: monerujo/App - Main
Translate-URL: https://hosted.weblate.org/projects/monerujo/app-main/pt_BR/

* Translated using Weblate (French)

Currently translated at 100.0% (20 of 20 strings)

Translation: monerujo/Help page
Translate-URL: https://hosted.weblate.org/projects/monerujo/app/app-help/fr/

* Translated using Weblate (German)

Currently translated at 80.0% (16 of 20 strings)

Translation: monerujo/Help page
Translate-URL: https://hosted.weblate.org/projects/monerujo/app/app-help/de/

* Translated using Weblate (German)

Currently translated at 97.3% (362 of 372 strings)

Translation: monerujo/Main strings
Translate-URL: https://hosted.weblate.org/projects/monerujo/app/app-main/de/

* Translated using Weblate (Portuguese (Brazil))

Currently translated at 100.0% (20 of 20 strings)

Translation: monerujo/Help page
Translate-URL: https://hosted.weblate.org/projects/monerujo/app/app-help/pt_BR/

---------

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: gallegonovato <fran-carro@hotmail.es>
Co-authored-by: John Doe <thraex@numericable.fr>
Co-authored-by: Languages add-on <noreply-addon-languages@weblate.org>
Co-authored-by: Максим Горпиніч <maksimgorpinic2005a@gmail.com>
Co-authored-by: தமிழ்நேரம் <anishprabu.t@gmail.com>
Co-authored-by: Yurt Page <yurtpage@gmail.com>
Co-authored-by: Fjuro <fjuro@alius.cz>
Co-authored-by: tetic frisbook <tetic90548@frisbook.com>
2025-06-15 22:46:11 +02:00
m2049r
386f6e744b keep barcode data if unchanged (#1003) 2025-06-15 22:44:52 +02:00
m2049r
1f5ad86b07 update dependencies & gradle (#1002) 2025-06-15 22:42:57 +02:00
m2049r
ef3a19b66e upgrade dependecies; bump version 2025-02-06 08:19:18 +01:00
m2049r
9fd382e622 fix build errors 2025-02-06 08:09:13 +01:00
Lucas
f7c1cc9713 Update Swedish translation (#985)
Co-authored-by: ordtrogen <ordtrogen@users.noreply.github.com>
2025-01-20 18:38:18 +01:00
Pavel Haluza
30023e85bb Add monochrome launcher icon (#962) 2025-01-20 18:34:49 +01:00
m2049r
bf8780c874 update exolix api (#994) 2025-01-20 18:34:05 +01:00
m2049r
9379269f89 fix toolbar under notification bar (#995) 2025-01-20 18:26:44 +01:00
boldsuck
a9b4abc01f Update DefaultNodes.java (#987)
Change incorrect P2P port to RPC port.

Just created a gist for my nodes:
https://gist.github.com/boldsuck/0eee41138b724715b669f4c4961fb9ca
2025-01-20 18:25:53 +01:00
retrnull
899ac775b1 make node parsing and formatting ipv6-friendly (#939)
Co-authored-by: m2049r <m2049r@monerujo.io>
2024-12-10 09:57:46 +01:00
Lucas
8b7c5a2450 Update Turkish and French translations from cardpuncher (#984)
* Update French & Turkish translations

Mainly for the Sidekick part, also added a few missing strings to the French translation and reverted to use the original "Ledger" word in the Turkish one.

* Fix a dumb copy & paste error

Hopefully this will silence the error.

* Yet another copy & paste error... :/

* Complete Turkish translation

* Update French translations

and fix a few typos in the process.

* Squash some syntax errors, there are others

* Error hunting goes on

Hopefully there aren't many left.

* Still on it

* All errors should have been cleared now

Crossing fingers, click & pray.

* Update help.xml

* Add files via upload

Let's see if this resolves the conflicts.

* Add files via upload

Forgot to remove the  translatable="false" strings without which my diff tool freaked out, and fixed a few typos. Let's hope this will resolve the conflicts.

* Acutally remove the  translatable="false" strings

Forgot to save the file... :/

* Update strings.xml

Remove duplicate string.

* fix building ?

---------

Co-authored-by: cardpuncher <64688741+cardpuncher@users.noreply.github.com>
2024-12-08 20:14:29 +01:00
m2049r
05c1ca5082 fix toolbar under notification bar (#983) 2024-12-07 12:52:11 +01:00
Lucas
6abf11841e Fix translatable state of some strings (#978) 2024-12-07 10:44:32 +01:00
Lucas
d46b8bf79a Remove translations with duplicate English strings (#980)
Linter no longer complains about this, this causes confusion on how much of monerujo is translated, this breaks translation language fallback (e.g. get translations from pt if pt-br doesn't have them)

Includes mixed strings unfortunately, mostly found in the help strings, where there were a few that have english in the middle, unfortunately... having them would mean they would likely be skipped over, there's no way I know to fix this, a lot of these though need overall maintenance, and it's better to make the job easier for the translators than to keep a potentially confusing/breaking translation
2024-12-07 10:41:03 +01:00
Lucas
149fd6376e Update credits in translation files (#981)
Quick find and replace
2024-12-07 10:37:19 +01:00
XD22
de70d64eb8 Add Arabic Translation ! (#925)
* Add Arabic Translation

* improve some lines & fix out of context words

* Improves and fixes

* Fix wrong context

* Fix wrong typo in _en

* Fixes

* fix legal name "monerujo"

* Fix moneroj coins context

Dropped 90a0654a9c and changed to "moneroj" as anhdres  comment.

* Drop english Lowercase changes
2024-12-03 08:36:15 +01:00
Toyo
232f7b801e change some formatting and typos? (#970) 2024-12-03 08:33:29 +01:00
Lucas
8962bd3050 Minimum weblate requirements (#976)
* Remove tools:ignore flags

* Disable linting for MissingTranslation

* Point README translation to Weblate
2024-12-03 08:31:20 +01:00
m2049r
989d52b33d Option to remember and reuse selected fiat (#967) 2024-11-08 12:01:35 +01:00
m2049r
a3c0ca7ebe bump version 2024-11-05 08:46:32 +01:00
m2049r
c9132d7d97 more permission checks 2024-11-05 08:42:14 +01:00
m2049r
758b042680 fix some crashing if bluetooth pemissions are not given (#965)
* show error instead of crashing

* ignore if fragement is no longer

* bump version
2024-11-03 16:19:35 +01:00
m2049r
84ce392192 Use EXOLIX as exchange (#964) 2024-11-01 19:51:28 +01:00
m2049r
4ebcda2b14 bump version 2024-10-17 13:39:16 +02:00
m2049r
c49351a8a9 own orbothelper & fix receiver visibility (#961) 2024-10-17 13:01:12 +02:00
m2049r
41e84f2e29 fix receiver visibility 2024-10-17 12:42:26 +02:00
m2049r
f08ddf93c8 bump version 2024-10-16 21:31:09 +02:00
m2049r
4bfc9c1bdd fix RECEIVER_NOT_EXPORTED crash 2024-10-16 20:18:12 +02:00
m2049r
1a13bdde91 add some default nodes 2024-10-07 23:42:23 +02:00
m2049r
3e56d5a54b update default nodes (#959) 2024-09-24 09:00:22 +02:00
m2049r
c64fb1a769 Merge pull request #954 from m2049r/feature/show_connected
show when sidekick is connected
2024-09-19 22:44:00 +02:00
m2049r
2d2a7d2db0 show when sidekick is connected 2024-09-15 20:46:00 +02:00
m2049r
ac9e24ee9f Merge pull request #952 from m2049r/feature/sidekick_v
Support for Sidekick device
2024-09-06 00:45:48 +02:00
m2049r
059438a09e targetSdkVersion 35 2024-09-05 23:13:11 +02:00
m2049r
d22fdfe2dc Sidekick Support for Monerokon 2024-09-05 23:12:42 +02:00
m2049r
48577e46aa lock in background (#936) 2024-04-05 16:03:30 +02:00
m2049r
451371cd92 refactor onBackPressed to use the callback dispatcher (#937) 2024-04-05 15:57:37 +02:00
286 changed files with 9779 additions and 9801 deletions

8
.gitignore vendored
View File

@@ -8,12 +8,8 @@
.DS_Store .DS_Store
/app/build /app/build
/app/release /app/release
/app/alpha /app/alpha*
/app/prod /app/prod*
/app/alphaMainnet
/app/prodMainnet
/app/alphaStagenet
/app/prodStagenet
/app/.cxx /app/.cxx
/monerujo.id /monerujo.id
/external-libs/VERSION /external-libs/VERSION

File diff suppressed because one or more lines are too long

View File

@@ -4,12 +4,12 @@ android {
ndkVersion '17.2.4988734' ndkVersion '17.2.4988734'
defaultConfig { defaultConfig {
applicationId "com.m2049r.xmrwallet" applicationId "com.m2049r.xmrwallet"
buildToolsVersion = '34.0.0' buildToolsVersion = '35.0.0'
compileSdk 34 compileSdk 35
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 33 targetSdkVersion 35
versionCode 3311 versionCode 4107
versionName "3.3.11 'Argentina'" versionName "4.1.7 'Exolix'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { cmake {
@@ -24,7 +24,7 @@ android {
} }
} }
flavorDimensions 'type', 'net' flavorDimensions = ['type', 'net']
productFlavors { productFlavors {
mainnet { mainnet {
dimension 'net' dimension 'net'
@@ -58,7 +58,7 @@ android {
applicationIdSuffix ".debug" applicationIdSuffix ".debug"
} }
applicationVariants.all { variant -> applicationVariants.all { variant ->
variant.buildConfigField "String", "ID_A", "\"" + getId("ID_A") + "\"" variant.buildConfigField "String", "ID_F", "\"" + getId("ID_F") + "\""
} }
} }
@@ -108,8 +108,8 @@ android {
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_17
} }
namespace 'com.m2049r.xmrwallet' namespace 'com.m2049r.xmrwallet'
buildFeatures { buildFeatures {
@@ -123,6 +123,10 @@ android {
} }
} }
} }
lint {
disable "MissingTranslation" // Translation is crowd-sourced.
}
} }
static def getId(name) { static def getId(name) {
@@ -132,40 +136,42 @@ static def getId(name) {
} }
dependencies { dependencies {
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0")) implementation(platform('org.jetbrains.kotlin:kotlin-bom:2.1.21'))
implementation 'androidx.core:core:1.12.0' implementation 'androidx.core:core:1.16.0'
implementation 'androidx.appcompat:appcompat:1.6.1' implementation 'androidx.appcompat:appcompat:1.7.1'
implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.legacy:legacy-support-v4:1.0.0'
implementation 'androidx.recyclerview:recyclerview:1.3.2' implementation 'androidx.recyclerview:recyclerview:1.4.0'
implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.cardview:cardview:1.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.2.1'
implementation 'androidx.preference:preference:1.2.1' implementation 'androidx.preference:preference:1.2.1'
implementation 'com.google.android.material:material:1.11.0' implementation 'com.google.android.material:material:1.12.0'
implementation 'com.google.guava:guava:33.4.8-android'
implementation 'me.dm7.barcodescanner:zxing:1.9.8' implementation 'me.dm7.barcodescanner:zxing:1.9.8'
implementation "com.squareup.okhttp3:okhttp:4.12.0" implementation "com.squareup.okhttp3:okhttp:4.12.0"
implementation "io.github.rburgst:okhttp-digest:3.1.0" implementation 'io.github.rburgst:okhttp-digest:3.1.1'
implementation "com.jakewharton.timber:timber:5.0.1" implementation "com.jakewharton.timber:timber:5.0.1"
implementation 'info.guardianproject.netcipher:netcipher:2.1.0' implementation 'info.guardianproject.netcipher:netcipher:2.1.0'
//implementation 'info.guardianproject.netcipher:netcipher-okhttp3:2.1.0' //implementation 'info.guardianproject.netcipher:netcipher-okhttp3:2.1.0'
implementation fileTree(dir: 'libs/classes', include: ['*.jar']) implementation fileTree(dir: 'libs/classes', include: ['*.jar'])
implementation 'com.nulab-inc:zxcvbn:1.8.2' implementation 'com.nulab-inc:zxcvbn:1.9.0'
implementation 'dnsjava:dnsjava:3.5.3' implementation 'dnsjava:dnsjava:3.6.3'
implementation 'org.slf4j:slf4j-nop:2.0.11' implementation 'org.slf4j:slf4j-nop:2.0.17'
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2' implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
//noinspection GradleDependency //noinspection GradleDependency
testImplementation "junit:junit:4.13.2" testImplementation "junit:junit:4.13.2"
testImplementation "org.mockito:mockito-all:1.10.19" testImplementation "org.mockito:mockito-all:1.10.19"
testImplementation "com.squareup.okhttp3:mockwebserver:4.12.0" testImplementation "com.squareup.okhttp3:mockwebserver:4.12.0"
testImplementation 'org.json:json:20231013' testImplementation 'org.json:json:20250517'
testImplementation 'net.jodah:concurrentunit:0.4.6' testImplementation 'net.jodah:concurrentunit:0.4.6'
compileOnly 'org.projectlombok:lombok:1.18.30' compileOnly 'org.projectlombok:lombok:1.18.38'
annotationProcessor 'org.projectlombok:lombok:1.18.30' annotationProcessor 'org.projectlombok:lombok:1.18.38'
} }

View File

@@ -5,6 +5,11 @@
android:name="android.hardware.camera" android:name="android.hardware.camera"
android:required="false" /> android:required="false" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<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.USE_FINGERPRINT" />
@@ -12,6 +17,7 @@
<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.FOREGROUND_SERVICE" /> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
<queries> <queries>
<intent> <intent>
@@ -98,7 +104,12 @@
android:name=".service.WalletService" android:name=".service.WalletService"
android:description="@string/service_description" android:description="@string/service_description"
android:exported="false" android:exported="false"
android:label="Monero Wallet Service" /> android:foregroundServiceType="specialUse"
android:label="Monero Wallet Service">
<property
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
android:value="Keeps app in sync with the blockchain" />
</service>
<provider <provider
android:name="androidx.core.content.FileProvider" android:name="androidx.core.content.FileProvider"

View File

@@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2017 m2049r * Copyright (c) 2017-2024 m2049r
* <p> * <p>
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -18,8 +18,6 @@
#include "monerujo.h" #include "monerujo.h"
#include "wallet2_api.h" #include "wallet2_api.h"
//TODO explicit casting jlong, jint, jboolean to avoid warnings
#ifdef __cplusplus #ifdef __cplusplus
extern "C" extern "C"
{ {
@@ -34,7 +32,6 @@ extern "C"
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOG_TAG,__VA_ARGS__)
static JavaVM *cachedJVM; static JavaVM *cachedJVM;
static jclass class_String;
static jclass class_ArrayList; static jclass class_ArrayList;
static jclass class_WalletListener; static jclass class_WalletListener;
static jclass class_CoinsInfo; static jclass class_CoinsInfo;
@@ -42,17 +39,11 @@ static jclass class_TransactionInfo;
static jclass class_Transfer; static jclass class_Transfer;
static jclass class_Ledger; static jclass class_Ledger;
static jclass class_WalletStatus; static jclass class_WalletStatus;
static jclass class_BluetoothService;
static jclass class_SidekickService;
std::mutex _listenerMutex; std::mutex _listenerMutex;
//void jstringToString(JNIEnv *env, std::string &str, jstring jstr) {
// if (!jstr) return;
// const int len = env->GetStringUTFLength(jstr);
// const char *chars = env->GetStringUTFChars(jstr, nullptr);
// str.assign(chars, len);
// env->ReleaseStringUTFChars(jstr, chars);
//}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) { JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
cachedJVM = jvm; cachedJVM = jvm;
LOGI("JNI_OnLoad"); LOGI("JNI_OnLoad");
@@ -62,8 +53,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
} }
//LOGI("JNI_OnLoad ok"); //LOGI("JNI_OnLoad ok");
class_String = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("java/lang/String")));
class_ArrayList = static_cast<jclass>(jenv->NewGlobalRef( class_ArrayList = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("java/util/ArrayList"))); jenv->FindClass("java/util/ArrayList")));
class_CoinsInfo = static_cast<jclass>(jenv->NewGlobalRef( class_CoinsInfo = static_cast<jclass>(jenv->NewGlobalRef(
@@ -78,6 +67,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
jenv->FindClass("com/m2049r/xmrwallet/ledger/Ledger"))); jenv->FindClass("com/m2049r/xmrwallet/ledger/Ledger")));
class_WalletStatus = static_cast<jclass>(jenv->NewGlobalRef( class_WalletStatus = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/model/Wallet$Status"))); jenv->FindClass("com/m2049r/xmrwallet/model/Wallet$Status")));
class_BluetoothService = static_cast<jclass>(jenv->NewGlobalRef(
jenv->FindClass("com/m2049r/xmrwallet/service/BluetoothService")));
return JNI_VERSION_1_6; return JNI_VERSION_1_6;
} }
#ifdef __cplusplus #ifdef __cplusplus
@@ -1686,6 +1677,79 @@ int LedgerFind(char *buffer, size_t len) {
return ret; return ret;
} }
//
// SidekickWallet Stuff
//
/**
* @brief BtExchange - exchange data with Monerujo Device
* @param request - buffer for data to send
* @param request_len - length of data to send
* @param response - buffer for received data
* @param max_resp_len - size of receive buffer
*
* @return length of received data in response or -1 if error, -2 if response buffer too small
*/
int BtExchange(
unsigned char *request,
unsigned int request_len,
unsigned char *response,
unsigned int max_resp_len) {
JNIEnv *jenv;
int envStat = attachJVM(&jenv);
if (envStat == JNI_ERR) return -16;
jmethodID exchangeMethod = jenv->GetStaticMethodID(class_BluetoothService, "Exchange",
"([B)[B");
auto reqLen = static_cast<jsize>(request_len);
jbyteArray reqData = jenv->NewByteArray(reqLen);
jenv->SetByteArrayRegion(reqData, 0, reqLen, (jbyte *) request);
LOGD("BtExchange cmd: 0x%02x with %u bytes", request[0], reqLen);
auto dataRecv = (jbyteArray)
jenv->CallStaticObjectMethod(class_BluetoothService, exchangeMethod, reqData);
jenv->DeleteLocalRef(reqData);
if (dataRecv == nullptr) {
detachJVM(jenv, envStat);
LOGD("BtExchange: error reading");
return -1;
}
jsize respLen = jenv->GetArrayLength(dataRecv);
LOGD("BtExchange response is %u bytes", respLen);
if (respLen <= max_resp_len) {
jenv->GetByteArrayRegion(dataRecv, 0, respLen, (jbyte *) response);
jenv->DeleteLocalRef(dataRecv);
detachJVM(jenv, envStat);
return static_cast<int>(respLen);;
} else {
jenv->DeleteLocalRef(dataRecv);
detachJVM(jenv, envStat);
LOGE("BtExchange response buffer too small: %u < %u", respLen, max_resp_len);
return -2;
}
}
/**
* @brief ConfirmTransfers
* @param transfers - string of "fee (':' address ':' amount)+"
*
* @return true on accept, false on reject
*/
bool ConfirmTransfers(const char *transfers) {
JNIEnv *jenv;
int envStat = attachJVM(&jenv);
if (envStat == JNI_ERR) return -16;
jmethodID confirmMethod = jenv->GetStaticMethodID(class_SidekickService, "ConfirmTransfers",
"(Ljava/lang/String;)Z");
jstring _transfers = jenv->NewStringUTF(transfers);
auto confirmed =
jenv->CallStaticBooleanMethod(class_SidekickService, confirmMethod, _transfers);
jenv->DeleteLocalRef(_transfers);
return confirmed;
}
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -1,5 +1,5 @@
/** /**
* Copyright (c) 2017 m2049r * Copyright (c) 2017-2024 m2049r
* <p> * <p>
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@@ -19,6 +19,8 @@
#include <jni.h> #include <jni.h>
#include <string>
/* /*
#include <android/log.h> #include <android/log.h>
@@ -28,6 +30,10 @@
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
*/ */
void ThrowException(JNIEnv *jenv, const char* type, const char* msg) {
jenv->ThrowNew(jenv->FindClass(type), msg);
}
jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handle") { jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
jclass c = env->GetObjectClass(obj); jclass c = env->GetObjectClass(obj);
return env->GetFieldID(c, fieldName, "J"); // of type long return env->GetFieldID(c, fieldName, "J"); // of type long
@@ -35,8 +41,16 @@ jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handl
template<typename T> template<typename T>
T *getHandle(JNIEnv *env, jobject obj, const char *fieldName = "handle") { T *getHandle(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
return reinterpret_cast<T *>(env->GetLongField(obj, getHandleField(env, obj, fieldName)));
}
template<typename T>
void destroyNativeObject(JNIEnv *env, T nativeObjectHandle, jobject obj, const char *fieldName = "handle") {
jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName)); jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName));
return reinterpret_cast<T *>(handle); if (handle != 0) {
ThrowException(env, "java/lang/IllegalStateException", "invalid handle (destroy)");
}
delete reinterpret_cast<T *>(nativeObjectHandle);
} }
void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) { void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) {
@@ -54,7 +68,7 @@ extern "C"
{ {
#endif #endif
extern const char* const MONERO_VERSION; // the actual monero core version extern const char *const MONERO_VERSION; // the actual monero core version
// from monero-core crypto/hash-ops.h - avoid #including monero code here // from monero-core crypto/hash-ops.h - avoid #including monero code here
enum { enum {
@@ -62,18 +76,40 @@ enum {
HASH_DATA_AREA = 136 HASH_DATA_AREA = 136
}; };
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height); void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed,
uint64_t height);
inline void slow_hash(const void *data, const size_t length, char *hash) { inline void slow_hash(const void *data, const size_t length, char *hash) {
cn_slow_hash(data, length, hash, 0 /*variant*/, 0 /*prehashed*/, 0 /*height*/); cn_slow_hash(data, length, hash, 0 /*variant*/, 0 /*prehashed*/, 0 /*height*/);
} }
inline void slow_hash_broken(const void *data, char *hash, int variant) { inline void slow_hash_broken(const void *data, char *hash, int variant) {
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/, 0 /*height*/); cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/,
0 /*height*/);
} }
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
namespace Monerujo {
class SidekickWallet {
public:
enum Status {
Status_Ok,
Status_Error,
Status_Critical
};
SidekickWallet(uint8_t networkType, std::string a, std::string b);
~SidekickWallet();
std::string call(int commandId, const std::string &request);
void reset();
Status status() const;
};
}
#endif //XMRWALLET_WALLET_LIB_H #endif //XMRWALLET_WALLET_LIB_H

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -16,12 +16,26 @@
package com.m2049r.xmrwallet; package com.m2049r.xmrwallet;
import android.Manifest;
import android.app.AlertDialog;
import android.content.Context; import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.PowerManager; import android.os.PowerManager;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.CallSuper; import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import com.m2049r.xmrwallet.data.BarcodeData; import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.dialog.ProgressDialog; import com.m2049r.xmrwallet.dialog.ProgressDialog;
@@ -35,18 +49,13 @@ public class BaseActivity extends SecureActivity
ProgressDialog progressDialog = null; ProgressDialog progressDialog = null;
private class SimpleProgressDialog extends ProgressDialog { private static class SimpleProgressDialog extends ProgressDialog {
SimpleProgressDialog(Context context, int msgId) { SimpleProgressDialog(Context context, int msgId) {
super(context); super(context);
setCancelable(false); setCancelable(false);
setMessage(context.getString(msgId)); setMessage(context.getString(msgId));
} }
@Override
public void onBackPressed() {
// prevent back button
}
} }
@Override @Override
@@ -59,13 +68,15 @@ public class BaseActivity extends SecureActivity
progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId); progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId);
if (delayMillis > 0) { if (delayMillis > 0) {
Handler handler = new Handler(); Handler handler = new Handler();
handler.postDelayed(new Runnable() { handler.postDelayed(() -> {
public void run() { if (progressDialog != null) {
if (progressDialog != null) progressDialog.show(); progressDialog.show();
disableBackPressed();
} }
}, delayMillis); }, delayMillis);
} else { } else {
progressDialog.show(); progressDialog.show();
disableBackPressed();
} }
} }
@@ -75,6 +86,7 @@ public class BaseActivity extends SecureActivity
progressDialog = new LedgerProgressDialog(BaseActivity.this, mode); progressDialog = new LedgerProgressDialog(BaseActivity.this, mode);
Ledger.setListener((Ledger.Listener) progressDialog); Ledger.setListener((Ledger.Listener) progressDialog);
progressDialog.show(); progressDialog.show();
disableBackPressed();
} }
@Override @Override
@@ -87,6 +99,28 @@ public class BaseActivity extends SecureActivity
progressDialog.dismiss(); progressDialog.dismiss();
} }
progressDialog = null; progressDialog = null;
enableBackPressed();
}
OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
// no going back
}
};
public void disableBackPressed() {
backPressedCallback.setEnabled(true);
}
public void enableBackPressed() {
backPressedCallback.setEnabled(false);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
} }
static final int RELEASE_WAKE_LOCK_DELAY = 5000; // millisconds static final int RELEASE_WAKE_LOCK_DELAY = 5000; // millisconds
@@ -136,4 +170,87 @@ public class BaseActivity extends SecureActivity
barcodeData = null; barcodeData = null;
return popped; return popped;
} }
public boolean isNetworkAvailable() {
ConnectivityManager connectivityManager = (ConnectivityManager) getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Network network = connectivityManager.getActiveNetwork();
if (network == null) return false;
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
return networkCapabilities != null && (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH));
} else {
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
return networkInfo != null && networkInfo.isConnected();
}
}
static private final int REQUEST_CODE_BLUETOOTH_PERMISSIONS = 32423;
void btPermissionGranted() {
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_BLUETOOTH_PERMISSIONS) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
btPermissionGranted();
} // else onResume() takes care of trying again
}
}
private void showBtPermissionsDialog() {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setMessage(R.string.bluetooth_permissions);
alertDialogBuilder.setPositiveButton(R.string.bluetooth_permissions_ok,
(dialog, which) -> requestBtPermissions());
alertDialogBuilder.setCancelable(false);
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
private void showAppInfoDialog() {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
alertDialogBuilder.setMessage(R.string.bluetooth_permissions);
alertDialogBuilder.setPositiveButton(R.string.bluetooth_permissions_settings, (dialog, which) -> {
Intent i = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
i.addCategory(Intent.CATEGORY_DEFAULT);
i.setData(Uri.parse("package:" + getPackageName()));
startActivity(i);
});
alertDialogBuilder.setNegativeButton(R.string.bluetooth_permissions_cancel, (dialog, which) -> {
finish();
});
alertDialogBuilder.setCancelable(false);
AlertDialog alertDialog = alertDialogBuilder.create();
alertDialog.show();
}
private void requestBtPermissions() {
ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN}, REQUEST_CODE_BLUETOOTH_PERMISSIONS);
}
private boolean firstCheck = true;
void checkBtPermissions() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if ((ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) ||
(ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED)) {
if (shouldShowRequestPermissionRationale(android.Manifest.permission.BLUETOOTH_SCAN) || shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) {
showBtPermissionsDialog();
} else {
if (firstCheck) {
requestBtPermissions();
} else {
showAppInfoDialog();
}
}
} else {
btPermissionGranted();
}
firstCheck = false;
} else {
btPermissionGranted();
}
}
} }

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,6 @@ package com.m2049r.xmrwallet;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.Html; import android.text.Html;
@@ -67,8 +66,20 @@ public class GenerateFragment extends Fragment {
static final String TYPE_KEY = "key"; static final String TYPE_KEY = "key";
static final String TYPE_SEED = "seed"; static final String TYPE_SEED = "seed";
static final String TYPE_LEDGER = "ledger"; static final String TYPE_LEDGER = "ledger";
static final String TYPE_SIDEKICK = "sidekick";
static final String TYPE_VIEWONLY = "view"; static final String TYPE_VIEWONLY = "view";
static Wallet.Device getDeviceType(String type) {
switch (type) {
case TYPE_SIDEKICK:
return Wallet.Device.Sidekick;
case TYPE_LEDGER:
return Wallet.Device.Ledger;
default:
return Wallet.Device.Software;
}
}
private TextInputLayout etWalletName; private TextInputLayout etWalletName;
private PasswordEntryView etWalletPassword; private PasswordEntryView etWalletPassword;
private LinearLayout llFingerprintAuth; private LinearLayout llFingerprintAuth;
@@ -195,6 +206,7 @@ public class GenerateFragment extends Fragment {
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_UNSPECIFIED); etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_UNSPECIFIED);
break; break;
case TYPE_LEDGER: case TYPE_LEDGER:
case TYPE_SIDEKICK:
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE); etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
etWalletPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> { etWalletPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN)) if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
@@ -308,7 +320,7 @@ public class GenerateFragment extends Fragment {
private boolean checkName() { private boolean checkName() {
String name = etWalletName.getEditText().getText().toString(); String name = etWalletName.getEditText().getText().toString();
boolean ok = true; boolean ok = true;
if (name.length() == 0) { if (name.isEmpty()) {
etWalletName.setError(getString(R.string.generate_wallet_name)); etWalletName.setError(getString(R.string.generate_wallet_name));
ok = false; ok = false;
} else if (name.charAt(0) == '.') { } else if (name.charAt(0) == '.') {
@@ -450,11 +462,12 @@ public class GenerateFragment extends Fragment {
activityCallback.onGenerate(name, crazyPass, seed, offset, height); activityCallback.onGenerate(name, crazyPass, seed, offset, height);
break; break;
case TYPE_LEDGER: case TYPE_LEDGER:
case TYPE_SIDEKICK:
bGenerate.setEnabled(false); bGenerate.setEnabled(false);
if (fingerprintAuthAllowed) { if (fingerprintAuthAllowed) {
KeyStoreHelper.saveWalletUserPass(requireActivity(), name, password); KeyStoreHelper.saveWalletUserPass(requireActivity(), name, password);
} }
activityCallback.onGenerateLedger(name, crazyPass, height); activityCallback.onGenerateDevice(getDeviceType(type), name, crazyPass, height);
break; break;
case TYPE_KEY: case TYPE_KEY:
case TYPE_VIEWONLY: case TYPE_VIEWONLY:
@@ -498,6 +511,8 @@ public class GenerateFragment extends Fragment {
return getString(R.string.generate_wallet_type_seed); return getString(R.string.generate_wallet_type_seed);
case TYPE_LEDGER: case TYPE_LEDGER:
return getString(R.string.generate_wallet_type_ledger); return getString(R.string.generate_wallet_type_ledger);
case TYPE_SIDEKICK:
return getString(R.string.generate_wallet_type_sidekick);
case TYPE_VIEWONLY: case TYPE_VIEWONLY:
return getString(R.string.generate_wallet_type_view); return getString(R.string.generate_wallet_type_view);
default: default:
@@ -515,7 +530,7 @@ public class GenerateFragment extends Fragment {
void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height); void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height);
void onGenerateLedger(String name, String password, long height); void onGenerateDevice(Wallet.Device device, String name, String password, long height);
void setTitle(String title); void setTitle(String title);
@@ -555,6 +570,9 @@ public class GenerateFragment extends Fragment {
case TYPE_LEDGER: case TYPE_LEDGER:
inflater.inflate(R.menu.create_wallet_ledger, menu); inflater.inflate(R.menu.create_wallet_ledger, menu);
break; break;
case TYPE_SIDEKICK:
inflater.inflate(R.menu.create_wallet_sidekick, menu);
break;
case TYPE_VIEWONLY: case TYPE_VIEWONLY:
inflater.inflate(R.menu.create_wallet_view, menu); inflater.inflate(R.menu.create_wallet_view, menu);
break; break;
@@ -581,13 +599,11 @@ public class GenerateFragment extends Fragment {
.setCancelable(false) .setCancelable(false)
.setPositiveButton(getString(R.string.label_ok), null) .setPositiveButton(getString(R.string.label_ok), null)
.setNegativeButton(getString(R.string.label_cancel), .setNegativeButton(getString(R.string.label_cancel),
new DialogInterface.OnClickListener() { (dialog, id) -> {
public void onClick(DialogInterface dialog, int id) { Helper.hideKeyboardAlways(activity);
Helper.hideKeyboardAlways(activity); etWalletMnemonic.getEditText().getText().clear();
etWalletMnemonic.getEditText().getText().clear(); dialog.cancel();
dialog.cancel(); ledgerDialog = null;
ledgerDialog = null;
}
}); });
ledgerDialog = alertDialogBuilder.create(); ledgerDialog = alertDialogBuilder.create();

View File

@@ -40,6 +40,7 @@ import android.widget.ScrollView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
@@ -98,6 +99,13 @@ public class GenerateReviewFragment extends Fragment {
private String walletPath; private String walletPath;
private String walletName; private String walletName;
private final OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
// nothing
}
};
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
@@ -156,10 +164,14 @@ public class GenerateReviewFragment extends Fragment {
}); });
Bundle args = getArguments(); Bundle args = getArguments();
assert args != null;
type = args.getString(REQUEST_TYPE); type = args.getString(REQUEST_TYPE);
walletPath = args.getString(REQUEST_PATH); walletPath = args.getString(REQUEST_PATH);
localPassword = args.getString(REQUEST_PASSWORD); localPassword = args.getString(REQUEST_PASSWORD);
showDetails(); showDetails();
if (type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT)) {
backPressedCallback.setEnabled(true);
}
return view; return view;
} }
@@ -243,7 +255,7 @@ public class GenerateReviewFragment extends Fragment {
showProgress(); showProgress();
if ((walletPath != null) if ((walletPath != null)
&& (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", getPassword()) && (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", getPassword())
== Wallet.Device.Device_Ledger) == Wallet.Device.Ledger)
&& (progressCallback != null)) { && (progressCallback != null)) {
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE); progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
dialogOpened = true; dialogOpened = true;
@@ -275,10 +287,11 @@ public class GenerateReviewFragment extends Fragment {
height = wallet.getRestoreHeight(); height = wallet.getRestoreHeight();
seed = wallet.getSeed(getSeedOffset()); seed = wallet.getSeed(getSeedOffset());
switch (wallet.getDeviceType()) { switch (wallet.getDeviceType()) {
case Device_Ledger: case Ledger:
viewKey = Ledger.Key(); viewKey = Ledger.Key();
break; break;
case Device_Software: case Software:
case Sidekick:
viewKey = wallet.getSecretViewKey(); viewKey = wallet.getSecretViewKey();
break; break;
default: default:
@@ -420,14 +433,11 @@ public class GenerateReviewFragment extends Fragment {
pbProgress.setVisibility(View.INVISIBLE); pbProgress.setVisibility(View.INVISIBLE);
} }
boolean backOk() {
return !type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT);
}
@Override @Override
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
requireActivity().getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
} }
@Override @Override

View File

@@ -0,0 +1,38 @@
/*
* Copyright (c) 2024 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import timber.log.Timber;
public class LockFragment extends Fragment {
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Timber.d("onCreateView");
final FrameLayout frame = new FrameLayout(requireContext());
frame.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
return frame;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -35,6 +35,7 @@ import android.widget.RelativeLayout;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -45,6 +46,7 @@ import com.google.android.material.progressindicator.CircularProgressIndicator;
import com.m2049r.xmrwallet.data.NodeInfo; import com.m2049r.xmrwallet.data.NodeInfo;
import com.m2049r.xmrwallet.dialog.HelpFragment; import com.m2049r.xmrwallet.dialog.HelpFragment;
import com.m2049r.xmrwallet.layout.WalletInfoAdapter; import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager; import com.m2049r.xmrwallet.model.WalletManager;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper; import com.m2049r.xmrwallet.util.KeyStoreHelper;
@@ -60,6 +62,7 @@ import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import lombok.Getter;
import timber.log.Timber; import timber.log.Timber;
public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInteractionListener, public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInteractionListener,
@@ -114,7 +117,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Set<NodeInfo> getOrPopulateFavourites(); Set<NodeInfo> getOrPopulateFavourites();
boolean hasLedger(); boolean hasDevice(Wallet.Device type);
boolean isNetworkAvailable();
void runOnNetCipher(Runnable runnable); void runOnNetCipher(Runnable runnable);
} }
@@ -145,9 +150,15 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
activityCallback.setToolbarButton(Toolbar.BUTTON_SETTINGS); activityCallback.setToolbarButton(Toolbar.BUTTON_SETTINGS);
activityCallback.showNet(); activityCallback.showNet();
showNetwork(); showNetwork();
//activityCallback.runOnNetCipher(this::pingSelectedNode);
} }
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
animateFAB();
}
};
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
@@ -164,6 +175,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabSeed = view.findViewById(R.id.fabSeed); fabSeed = view.findViewById(R.id.fabSeed);
fabImport = view.findViewById(R.id.fabImport); fabImport = view.findViewById(R.id.fabImport);
fabLedger = view.findViewById(R.id.fabLedger); fabLedger = view.findViewById(R.id.fabLedger);
fabSidekick = view.findViewById(R.id.fabSidekick);
fabNewL = view.findViewById(R.id.fabNewL); fabNewL = view.findViewById(R.id.fabNewL);
fabViewL = view.findViewById(R.id.fabViewL); fabViewL = view.findViewById(R.id.fabViewL);
@@ -171,6 +183,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabSeedL = view.findViewById(R.id.fabSeedL); fabSeedL = view.findViewById(R.id.fabSeedL);
fabImportL = view.findViewById(R.id.fabImportL); fabImportL = view.findViewById(R.id.fabImportL);
fabLedgerL = view.findViewById(R.id.fabLedgerL); fabLedgerL = view.findViewById(R.id.fabLedgerL);
fabSidekickL = view.findViewById(R.id.fabSidekickL);
fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse); fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse);
fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen); fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen);
@@ -186,6 +199,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabSeed.setOnClickListener(this); fabSeed.setOnClickListener(this);
fabImport.setOnClickListener(this); fabImport.setOnClickListener(this);
fabLedger.setOnClickListener(this); fabLedger.setOnClickListener(this);
fabSidekick.setOnClickListener(this);
fabScreen.setOnClickListener(this); fabScreen.setOnClickListener(this);
RecyclerView recyclerView = view.findViewById(R.id.list); RecyclerView recyclerView = view.findViewById(R.id.list);
@@ -194,6 +208,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
recyclerView.setAdapter(adapter); recyclerView.setAdapter(adapter);
ViewGroup llNotice = view.findViewById(R.id.llNotice); ViewGroup llNotice = view.findViewById(R.id.llNotice);
Notice.showAll(llNotice, ".*_login"); Notice.showAll(llNotice, ".*_login");
view.findViewById(R.id.llNode).setOnClickListener(v -> startNodePrefs()); view.findViewById(R.id.llNode).setOnClickListener(v -> startNodePrefs());
@@ -268,7 +283,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
} }
// remove information of non-existent wallet // remove information of non-existent wallet
Set<String> removedWallets = getActivity() Set<String> removedWallets = requireActivity()
.getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE) .getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
.getAll().keySet(); .getAll().keySet();
for (WalletManager.WalletInfo s : walletList) { for (WalletManager.WalletInfo s : walletList) {
@@ -287,6 +302,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
} }
@Override @Override
@@ -295,25 +311,30 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
} }
private boolean isFabOpen = false; @Getter
private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabImport, fabLedger; private boolean fabOpen = false;
private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabImport, fabLedger, fabSidekick;
private RelativeLayout fabScreen; private RelativeLayout fabScreen;
private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabImportL, fabLedgerL; private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabImportL, fabLedgerL, fabSidekickL;
private Animation fab_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen; private Animation fab_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen;
private Animation fab_pulse; private Animation fab_pulse;
public boolean isFabOpen() { private void setFabOpen(boolean value) {
return isFabOpen; fabOpen = value;
onBackPressedCallback.setEnabled(value);
} }
public void animateFAB() { public void animateFAB() {
if (isFabOpen) { // close the fab if (isFabOpen()) { // close the fab
fabScreen.setClickable(false); fabScreen.setClickable(false);
fabScreen.startAnimation(fab_close_screen); fabScreen.startAnimation(fab_close_screen);
fab.startAnimation(rotate_backward); fab.startAnimation(rotate_backward);
if (fabLedgerL.getVisibility() == View.VISIBLE) { if (fabLedgerL.getVisibility() == View.VISIBLE) {
fabLedgerL.startAnimation(fab_close); fabLedgerL.startAnimation(fab_close);
fabLedger.setClickable(false); fabLedger.setClickable(false);
} else if (fabSidekickL.getVisibility() == View.VISIBLE) {
fabSidekickL.startAnimation(fab_close);
fabSidekick.setClickable(false);
} else { } else {
fabNewL.startAnimation(fab_close); fabNewL.startAnimation(fab_close);
fabNew.setClickable(false); fabNew.setClickable(false);
@@ -326,22 +347,30 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabImportL.startAnimation(fab_close); fabImportL.startAnimation(fab_close);
fabImport.setClickable(false); fabImport.setClickable(false);
} }
isFabOpen = false; setFabOpen(false);
} else { // open the fab } else { // open the fab
fabScreen.setClickable(true); fabScreen.setClickable(true);
fabScreen.startAnimation(fab_open_screen); fabScreen.startAnimation(fab_open_screen);
fab.startAnimation(rotate_forward); fab.startAnimation(rotate_forward);
if (activityCallback.hasLedger()) { if ((activityCallback.hasDevice(Wallet.Device.Ledger)
fabLedgerL.setVisibility(View.VISIBLE); || activityCallback.hasDevice(Wallet.Device.Sidekick))) {
fabNewL.setVisibility(View.GONE); fabNewL.setVisibility(View.GONE);
fabViewL.setVisibility(View.GONE); fabViewL.setVisibility(View.GONE);
fabKeyL.setVisibility(View.GONE); fabKeyL.setVisibility(View.GONE);
fabSeedL.setVisibility(View.GONE); fabSeedL.setVisibility(View.GONE);
fabImportL.setVisibility(View.GONE); fabImportL.setVisibility(View.GONE);
fabLedgerL.startAnimation(fab_open); if (activityCallback.hasDevice(Wallet.Device.Ledger)) {
fabLedger.setClickable(true); fabLedgerL.setVisibility(View.VISIBLE);
fabLedgerL.startAnimation(fab_open);
fabLedger.setClickable(true);
} else { // Sidekick
fabSidekickL.setVisibility(View.VISIBLE);
fabSidekickL.startAnimation(fab_open);
fabSidekick.setClickable(true);
}
} else { } else {
fabSidekickL.setVisibility(View.GONE);
fabLedgerL.setVisibility(View.GONE); fabLedgerL.setVisibility(View.GONE);
fabNewL.setVisibility(View.VISIBLE); fabNewL.setVisibility(View.VISIBLE);
fabViewL.setVisibility(View.VISIBLE); fabViewL.setVisibility(View.VISIBLE);
@@ -360,7 +389,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
fabImportL.startAnimation(fab_open); fabImportL.startAnimation(fab_open);
fabImport.setClickable(true); fabImport.setClickable(true);
} }
isFabOpen = true; setFabOpen(true);
} }
} }
@@ -372,7 +401,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
animateFAB(); animateFAB();
} else if (id == R.id.fabNew) { } else if (id == R.id.fabNew) {
fabScreen.setVisibility(View.INVISIBLE); fabScreen.setVisibility(View.INVISIBLE);
isFabOpen = false; setFabOpen(false);
activityCallback.onAddWallet(GenerateFragment.TYPE_NEW); activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
} else if (id == R.id.fabView) { } else if (id == R.id.fabView) {
animateFAB(); animateFAB();
@@ -390,6 +419,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
Timber.d("FAB_LEDGER"); Timber.d("FAB_LEDGER");
animateFAB(); animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER); activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
} else if (id == R.id.fabSidekick) {
Timber.d("FAB_SIDEKICK");
animateFAB();
activityCallback.onAddWallet(GenerateFragment.TYPE_SIDEKICK);
} else if (id == R.id.fabScreen) { } else if (id == R.id.fabScreen) {
animateFAB(); animateFAB();
} }
@@ -412,7 +445,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
} }
private void setSubtext(String status) { private void setSubtext(String status) {
final Context ctx = getContext(); final Context ctx = requireContext();
final Spanned text = Html.fromHtml(ctx.getString(R.string.status, final Spanned text = Html.fromHtml(ctx.getString(R.string.status,
Integer.toHexString(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF), Integer.toHexString(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF),
Integer.toHexString(ThemeHelper.getThemedColor(ctx, android.R.attr.colorBackground) & 0xFFFFFF), Integer.toHexString(ThemeHelper.getThemedColor(ctx, android.R.attr.colorBackground) & 0xFFFFFF),

View File

@@ -31,6 +31,8 @@ import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.OnBackPressedCallback;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
@@ -67,7 +69,7 @@ public class NodeFragment extends Fragment
static private int NODES_TO_FIND = 10; static private int NODES_TO_FIND = 10;
static private NumberFormat FORMATTER = NumberFormat.getInstance(); static private final NumberFormat FORMATTER = NumberFormat.getInstance();
private SwipeRefreshLayout pullToRefresh; private SwipeRefreshLayout pullToRefresh;
private TextView tvPull; private TextView tvPull;
@@ -103,7 +105,7 @@ public class NodeFragment extends Fragment
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(@NonNull Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof Listener) { if (context instanceof Listener) {
this.activityCallback = (Listener) context; this.activityCallback = (Listener) context;
@@ -145,6 +147,13 @@ public class NodeFragment extends Fragment
} }
} }
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override
public void handleOnBackPressed() {
Toast.makeText(requireActivity(), getString(R.string.node_refresh_wait), Toast.LENGTH_LONG).show();
}
};
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) { Bundle savedInstanceState) {
@@ -187,6 +196,7 @@ public class NodeFragment extends Fragment
private boolean refresh(int type) { private boolean refresh(int type) {
if (asyncFindNodes != null) return false; // ignore refresh request as one is ongoing if (asyncFindNodes != null) return false; // ignore refresh request as one is ongoing
onBackPressedCallback.setEnabled(true);
asyncFindNodes = new AsyncFindNodes(); asyncFindNodes = new AsyncFindNodes();
updateRefreshElements(); updateRefreshElements();
asyncFindNodes.execute(type); asyncFindNodes.execute(type);
@@ -197,10 +207,11 @@ public class NodeFragment extends Fragment
public void onCreate(@Nullable Bundle savedInstanceState) { public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setHasOptionsMenu(true); setHasOptionsMenu(true);
requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.node_menu, menu); inflater.inflate(R.menu.node_menu, menu);
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
} }
@@ -279,8 +290,7 @@ public class NodeFragment extends Fragment
} else if (params[0] == SCAN) { } else if (params[0] == SCAN) {
// otherwise scan the network // otherwise scan the network
Timber.d("scanning"); Timber.d("scanning");
Set<NodeInfo> seedList = new HashSet<>(); Set<NodeInfo> seedList = new HashSet<>(nodeList);
seedList.addAll(nodeList);
nodeList.clear(); nodeList.clear();
Timber.d("seed %d", seedList.size()); Timber.d("seed %d", seedList.size());
Dispatcher d = new Dispatcher(info -> publishProgress(info)); Dispatcher d = new Dispatcher(info -> publishProgress(info));
@@ -342,6 +352,7 @@ public class NodeFragment extends Fragment
private void complete() { private void complete() {
asyncFindNodes = null; asyncFindNodes = null;
onBackPressedCallback.setEnabled(false);
if (!isAdded()) return; if (!isAdded()) return;
//if (isCancelled()) return; //if (isCancelled()) return;
tvPull.setText(getString(R.string.node_pull_hint)); tvPull.setText(getString(R.string.node_pull_hint));
@@ -444,7 +455,7 @@ public class NodeFragment extends Fragment
private void closeDialog() { private void closeDialog() {
if (editDialog == null) throw new IllegalStateException(); if (editDialog == null) throw new IllegalStateException();
Helper.hideKeyboardAlways(getActivity()); Helper.hideKeyboardAlways(requireActivity());
editDialog.dismiss(); editDialog.dismiss();
editDialog = null; editDialog = null;
NodeFragment.this.editDialog = null; NodeFragment.this.editDialog = null;
@@ -575,6 +586,7 @@ public class NodeFragment extends Fragment
} }
} }
} }
} }
void restoreDefaultNodes() { void restoreDefaultNodes() {

View File

@@ -32,12 +32,10 @@ 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;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.EditText; import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
@@ -84,8 +82,6 @@ public class ReceiveFragment extends Fragment {
private ImageView ivQrCode; private ImageView ivQrCode;
private ImageView ivQrCodeFull; private ImageView ivQrCodeFull;
private EditText etDummy; private EditText etDummy;
private ImageButton bCopyAddress;
private MenuItem shareItem;
private Wallet wallet = null; private Wallet wallet = null;
private boolean isMyWallet = false; private boolean isMyWallet = false;
@@ -116,11 +112,10 @@ public class ReceiveFragment extends Fragment {
tvQrCode = view.findViewById(R.id.tvQrCode); tvQrCode = view.findViewById(R.id.tvQrCode);
ivQrCodeFull = view.findViewById(R.id.qrCodeFull); ivQrCodeFull = view.findViewById(R.id.qrCodeFull);
etDummy = view.findViewById(R.id.etDummy); etDummy = view.findViewById(R.id.etDummy);
bCopyAddress = view.findViewById(R.id.bCopyAddress);
etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS); etDummy.setRawInputType(InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
bCopyAddress.setOnClickListener(v -> copyAddress()); view.findViewById(R.id.bCopyAddress).setOnClickListener(v -> copyAddress());
evAmount.setOnNewAmountListener(xmr -> { evAmount.setOnNewAmountListener(xmr -> {
Timber.d("new amount = %s", xmr); Timber.d("new amount = %s", xmr);
@@ -211,8 +206,7 @@ public class ReceiveFragment extends Fragment {
inflater.inflate(R.menu.receive_menu, menu); inflater.inflate(R.menu.receive_menu, menu);
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
shareItem = menu.findItem(R.id.menu_item_share); menu.findItem(R.id.menu_item_share).setOnMenuItemClickListener(item -> {
shareItem.setOnMenuItemClickListener(item -> {
if (shareRequested) return true; if (shareRequested) return true;
shareRequested = true; shareRequested = true;
if (!qrValid) { if (!qrValid) {
@@ -238,7 +232,7 @@ public class ReceiveFragment extends Fragment {
private boolean 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(requireActivity().getCacheDir(), "images");
if (!cachePath.exists()) if (!cachePath.exists())
if (!cachePath.mkdirs()) throw new IllegalStateException("cannot create images folder"); if (!cachePath.mkdirs()) throw new IllegalStateException("cannot create images folder");
File png = new File(cachePath, "QR.png"); File png = new File(cachePath, "QR.png");
@@ -452,7 +446,7 @@ public class ReceiveFragment extends Fragment {
.withEndAction(resetSize).start(); .withEndAction(resetSize).start();
} }
subaddress = newSubaddress; subaddress = newSubaddress;
final Context context = getContext(); final Context context = requireContext();
Spanned label = Html.fromHtml(context.getString(R.string.receive_subaddress, Spanned label = Html.fromHtml(context.getString(R.string.receive_subaddress,
Integer.toHexString(ThemeHelper.getThemedColor(context, R.attr.positiveColor) & 0xFFFFFF), Integer.toHexString(ThemeHelper.getThemedColor(context, R.attr.positiveColor) & 0xFFFFFF),
Integer.toHexString(ThemeHelper.getThemedColor(context, android.R.attr.colorBackground) & 0xFFFFFF), Integer.toHexString(ThemeHelper.getThemedColor(context, android.R.attr.colorBackground) & 0xFFFFFF),

View File

@@ -24,6 +24,7 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import com.google.zxing.BarcodeFormat; import com.google.zxing.BarcodeFormat;
@@ -43,7 +44,7 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
private ZXingScannerView mScannerView; private ZXingScannerView mScannerView;
@Override @Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Timber.d("onCreateView"); Timber.d("onCreateView");
mScannerView = new ZXingScannerView(getActivity()); mScannerView = new ZXingScannerView(getActivity());
return mScannerView; return mScannerView;
@@ -85,7 +86,7 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(@NonNull Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof OnScannedListener) { if (context instanceof OnScannedListener) {
this.onScannedListener = (OnScannedListener) context; this.onScannedListener = (OnScannedListener) context;

View File

@@ -5,6 +5,7 @@ import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.StyleRes; import androidx.annotation.StyleRes;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.PreferenceFragmentCompat; import androidx.preference.PreferenceFragmentCompat;
@@ -60,7 +61,7 @@ public class SettingsFragment extends PreferenceFragmentCompat
private SettingsFragment.Listener activity; private SettingsFragment.Listener activity;
@Override @Override
public void onAttach(Context context) { public void onAttach(@NonNull Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof SettingsFragment.Listener) { if (context instanceof SettingsFragment.Listener) {
activity = (SettingsFragment.Listener) context; activity = (SettingsFragment.Listener) context;

View File

@@ -0,0 +1,188 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothClass;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.m2049r.xmrwallet.data.BluetoothInfo;
import com.m2049r.xmrwallet.layout.BluetoothInfoAdapter;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.util.ArrayList;
import java.util.List;
import timber.log.Timber;
public class SidekickConnectFragment extends Fragment
implements BluetoothInfoAdapter.OnInteractionListener {
private BluetoothAdapter bluetoothAdapter;
private SwipeRefreshLayout pullToRefresh;
private BluetoothInfoAdapter infoAdapter;
private Listener activityCallback;
public interface Listener {
void setToolbarButton(int type);
void setSubtitle(String title);
}
@Override
public void onAttach(@NonNull Context context) {
super.onAttach(context);
if (context instanceof Listener) {
this.activityCallback = (Listener) context;
} else {
throw new ClassCastException(context + " must implement Listener");
}
}
@Override
public void onPause() {
Timber.d("onPause()");
if (ActivityCompat.checkSelfPermission(requireContext(), android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(requireContext(), "Bluetooth permission not granted", Toast.LENGTH_LONG).show();
} else {
if (bluetoothAdapter.isDiscovering()) {
bluetoothAdapter.cancelDiscovery();
}
}
super.onPause();
}
@Override
public void onResume() {
super.onResume();
Timber.d("onResume()");
activityCallback.setSubtitle(getString(R.string.label_bluetooth));
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
final BluetoothFragment btFragment = (BluetoothFragment) getChildFragmentManager().findFragmentById(R.id.bt_fragment);
assert btFragment != null;
btFragment.start();
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
Timber.d("onCreateView");
View view = inflater.inflate(R.layout.fragment_sidekick_connect, container, false);
RecyclerView recyclerView = view.findViewById(R.id.list);
infoAdapter = new BluetoothInfoAdapter(this);
recyclerView.setAdapter(infoAdapter);
pullToRefresh = view.findViewById(R.id.pullToRefresh);
pullToRefresh.setOnRefreshListener(() -> {
populateList();
pullToRefresh.setRefreshing(false);
});
return view;
}
private void populateList() {
List<BluetoothInfo> items = new ArrayList<>();
if (ActivityCompat.checkSelfPermission(requireContext(), android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(requireContext(), "Bluetooth permission not granted", Toast.LENGTH_LONG).show();
return;
}
for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) {
final int deviceCLass = device.getBluetoothClass().getDeviceClass();
switch (deviceCLass) {
case BluetoothClass.Device.PHONE_SMART:
//TODO verify these are correct
case BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA:
case BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA:
items.add(new BluetoothInfo(device));
}
}
infoAdapter.setItems(items);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
Helper.hideKeyboard(getActivity());
// Get the local Bluetooth adapter
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
populateList();
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);
}
@Override
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.sidekick_connect_menu, menu);
super.onCreateOptionsMenu(menu, inflater);
}
@Override
public void onDestroy() {
super.onDestroy();
// Make sure we're not doing discovery anymore
if (bluetoothAdapter != null) {
if (ActivityCompat.checkSelfPermission(requireContext(), android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(requireContext(), "Bluetooth permission not granted", Toast.LENGTH_LONG).show();
return;
}
bluetoothAdapter.cancelDiscovery();
}
}
@Override
public void onInteraction(final View view, final BluetoothInfo item) {
Timber.d("onInteraction %s", item);
if (ActivityCompat.checkSelfPermission(requireContext(), android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED)
throw new IllegalStateException("Bluetooth permission not granted");
bluetoothAdapter.cancelDiscovery();
final BluetoothFragment btFragment = (BluetoothFragment) getChildFragmentManager().findFragmentById(R.id.bt_fragment);
assert btFragment != null;
btFragment.connectDevice(item.getAddress());
}
public void allowClick() {
infoAdapter.allowClick(true);
}
}

View File

@@ -195,7 +195,7 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
@Override @Override
protected void onPreExecute() { protected void onPreExecute() {
super.onPreExecute(); super.onPreExecute();
if ((wallet.getDeviceType() == Wallet.Device.Device_Ledger) && (progressCallback != null)) { if ((wallet.getDeviceType() == Wallet.Device.Ledger) && (progressCallback != null)) {
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS); progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS);
dialogOpened = true; dialogOpened = true;
} }
@@ -226,7 +226,7 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
// Callbacks from SubaddressInfoAdapter // Callbacks from SubaddressInfoAdapter
@Override @Override
public void onInteraction(final View view, final Subaddress subaddress) { public void onInteraction(final View view, @NonNull final Subaddress subaddress) {
if (managerMode) if (managerMode)
activityCallback.showSubaddress(view, subaddress.getAddressIndex()); activityCallback.showSubaddress(view, subaddress.getAddressIndex());
else else

View File

@@ -52,7 +52,6 @@ public class SubaddressInfoFragment extends Fragment
private Subaddress subaddress; private Subaddress subaddress;
private TextInputLayout etName; private TextInputLayout etName;
private TextView tvAddress;
private TextView tvTxLabel; private TextView tvTxLabel;
@Override @Override
@@ -61,7 +60,6 @@ public class SubaddressInfoFragment extends Fragment
View view = inflater.inflate(R.layout.fragment_subaddressinfo, container, false); View view = inflater.inflate(R.layout.fragment_subaddressinfo, container, false);
etName = view.findViewById(R.id.etName); etName = view.findViewById(R.id.etName);
tvAddress = view.findViewById(R.id.tvAddress);
tvTxLabel = view.findViewById(R.id.tvTxLabel); tvTxLabel = view.findViewById(R.id.tvTxLabel);
final RecyclerView list = view.findViewById(R.id.list); final RecyclerView list = view.findViewById(R.id.list);
@@ -71,11 +69,13 @@ public class SubaddressInfoFragment extends Fragment
final Wallet wallet = activityCallback.getWallet(); final Wallet wallet = activityCallback.getWallet();
Bundle b = getArguments(); Bundle b = getArguments();
assert b != null;
final int subaddressIndex = b.getInt("subaddressIndex"); final int subaddressIndex = b.getInt("subaddressIndex");
subaddress = wallet.getSubaddressObject(subaddressIndex); subaddress = wallet.getSubaddressObject(subaddressIndex);
etName.getEditText().setText(subaddress.getDisplayLabel()); etName.getEditText().setText(subaddress.getDisplayLabel());
tvAddress.setText(getContext().getString(R.string.subbaddress_info_subtitle, final TextView tvAddress = view.findViewById(R.id.tvAddress);
tvAddress.setText(requireContext().getString(R.string.subbaddress_info_subtitle,
subaddress.getAddressIndex(), subaddress.getAddress())); subaddress.getAddressIndex(), subaddress.getAddress()));
etName.getEditText().setOnFocusChangeListener((v, hasFocus) -> { etName.getEditText().setOnFocusChangeListener((v, hasFocus) -> {

View File

@@ -20,7 +20,6 @@ import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.graphics.Paint; import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.text.Html; import android.text.Html;
import android.text.InputType; import android.text.InputType;
@@ -34,6 +33,7 @@ import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.transition.Transition; import androidx.transition.Transition;
@@ -44,6 +44,8 @@ import com.m2049r.xmrwallet.data.UserNotes;
import com.m2049r.xmrwallet.model.TransactionInfo; import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer; import com.m2049r.xmrwallet.model.Transfer;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.service.shift.ShiftService;
import com.m2049r.xmrwallet.service.shift.api.ShiftApi;
import com.m2049r.xmrwallet.util.Helper; import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.ThemeHelper; import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
@@ -61,6 +63,7 @@ public class TxFragment extends Fragment {
static public final String ARG_INFO = "info"; static public final String ARG_INFO = "info";
@SuppressLint("SimpleDateFormat")
private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z"); private final SimpleDateFormat TS_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z");
public TxFragment() { public TxFragment() {
@@ -106,6 +109,7 @@ public class TxFragment extends Fragment {
tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc); tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc);
tvXmrToSupport = view.findViewById(R.id.tvXmrToSupport); tvXmrToSupport = view.findViewById(R.id.tvXmrToSupport);
tvXmrToKeyLabel = view.findViewById(R.id.tvXmrToKeyLabel); tvXmrToKeyLabel = view.findViewById(R.id.tvXmrToKeyLabel);
tvXmrToLogo = view.findViewById(R.id.tvXmrToLogo); tvXmrToLogo = view.findViewById(R.id.tvXmrToLogo);
tvAccount = view.findViewById(R.id.tvAccount); tvAccount = view.findViewById(R.id.tvAccount);
@@ -127,18 +131,20 @@ public class TxFragment extends Fragment {
tvWarning = view.findViewById(R.id.tvWarning); tvWarning = view.findViewById(R.id.tvWarning);
tvTxXmrToKey.setOnClickListener(v -> { tvTxXmrToKey.setOnClickListener(v -> {
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString()); Helper.clipBoardCopy(requireActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show(); Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
}); });
info = getArguments().getParcelable(ARG_INFO); final Bundle args = getArguments();
assert args != null;
info = args.getParcelable(ARG_INFO);
show(); show();
return view; return view;
} }
void shareTxInfo() { void shareTxInfo() {
if (this.info == null) return; if (this.info == null) return;
StringBuffer sb = new StringBuffer(); StringBuilder sb = new StringBuilder();
sb.append(getString(R.string.tx_timestamp)).append(":\n"); sb.append(getString(R.string.tx_timestamp)).append(":\n");
sb.append(TS_FORMATTER.format(new Date(info.timestamp * 1000))).append("\n\n"); sb.append(TS_FORMATTER.format(new Date(info.timestamp * 1000))).append("\n\n");
@@ -216,7 +222,7 @@ public class TxFragment extends Fragment {
private void showSubaddressLabel() { private void showSubaddressLabel() {
final Subaddress subaddress = activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex); final Subaddress subaddress = activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex);
final Context ctx = getContext(); final Context ctx = requireContext();
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(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF), Integer.toHexString(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor) & 0xFFFFFF),
@@ -264,16 +270,17 @@ public class TxFragment extends Fragment {
tvTxFee.setVisibility(View.GONE); tvTxFee.setVisibility(View.GONE);
} }
final Context ctx = requireContext();
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(ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor)); setTxColour(ThemeHelper.getThemedColor(ctx, R.attr.neutralColor));
} else if (info.isPending) { } else if (info.isPending) {
setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.neutralColor)); setTxColour(ThemeHelper.getThemedColor(ctx, R.attr.neutralColor));
} else if (info.direction == TransactionInfo.Direction.Direction_In) { } else if (info.direction == TransactionInfo.Direction.Direction_In) {
setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.positiveColor)); setTxColour(ThemeHelper.getThemedColor(ctx, R.attr.positiveColor));
} else { } else {
setTxColour(ThemeHelper.getThemedColor(getContext(), R.attr.negativeColor)); setTxColour(ThemeHelper.getThemedColor(ctx, R.attr.negativeColor));
} }
Set<String> destinations = new HashSet<>(); Set<String> destinations = new HashSet<>();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@@ -328,31 +335,29 @@ public class TxFragment extends Fragment {
void showBtcInfo() { void showBtcInfo() {
if (userNotes.xmrtoKey != null) { if (userNotes.xmrtoKey != null) {
cvXmrTo.setVisibility(View.VISIBLE); cvXmrTo.setVisibility(View.VISIBLE);
String key = userNotes.xmrtoKey;
if ("xmrto".equals(userNotes.xmrtoTag)) { // legacy xmr.to service :( ShiftService service = ShiftService.findWithTag(userNotes.xmrtoTag);
key = "xmrto-" + key; tvXmrToKeyLabel.setText(getString(R.string.label_send_btc_xmrto_key_lb, service.getLabel()));
} if (service.getIconId() == 0)
tvTxXmrToKey.setText(key); tvXmrToLogo.setVisibility(View.GONE);
else
tvXmrToLogo.setImageResource(service.getLogoId());
tvTxXmrToKey.setText(userNotes.xmrtoKey);
tvDestinationBtc.setText(userNotes.xmrtoDestination); tvDestinationBtc.setText(userNotes.xmrtoDestination);
tvTxAmountBtc.setText(userNotes.xmrtoAmount + " " + userNotes.xmrtoCurrency); tvTxAmountBtc.setText(userNotes.xmrtoAmount + " " + userNotes.xmrtoCurrency);
switch (userNotes.xmrtoTag) {
case "xmrto": ShiftApi shiftApi = service.getShiftApi();
tvXmrToSupport.setVisibility(View.GONE); if (shiftApi != null) {
tvXmrToKeyLabel.setVisibility(View.INVISIBLE); tvXmrToSupport.setText(getString(R.string.label_send_btc_xmrto_info, service.getLabel()));
tvXmrToLogo.setImageResource(R.drawable.ic_xmrto_logo); tvXmrToSupport.setPaintFlags(tvXmrToSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
break; tvXmrToSupport.setOnClickListener(v -> {
case "side": // defaults in layout - just add underline startActivity(new Intent(Intent.ACTION_VIEW, shiftApi.getQueryOrderUri(userNotes.xmrtoKey)));
tvXmrToSupport.setPaintFlags(tvXmrToSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG); });
tvXmrToSupport.setOnClickListener(v -> { } else {
Uri uri = Uri.parse("https://sideshift.ai/orders/" + userNotes.xmrtoKey); tvXmrToSupport.setVisibility(View.GONE);
Intent intent = new Intent(Intent.ACTION_VIEW, uri); tvXmrToKeyLabel.setVisibility(View.INVISIBLE);
startActivity(intent);
});
break;
default:
tvXmrToSupport.setVisibility(View.GONE);
tvXmrToKeyLabel.setVisibility(View.INVISIBLE);
tvXmrToLogo.setVisibility(View.GONE);
} }
} else { } else {
cvXmrTo.setVisibility(View.GONE); cvXmrTo.setVisibility(View.GONE);
@@ -369,7 +374,7 @@ public class TxFragment extends Fragment {
} }
@Override @Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.tx_info_menu, menu); inflater.inflate(R.menu.tx_info_menu, menu);
super.onCreateOptionsMenu(menu, inflater); super.onCreateOptionsMenu(menu, inflater);
} }
@@ -397,7 +402,7 @@ public class TxFragment extends Fragment {
} }
@Override @Override
public void onAttach(Context context) { public void onAttach(@NonNull Context context) {
super.onAttach(context); super.onAttach(context);
if (context instanceof TxFragment.Listener) { if (context instanceof TxFragment.Listener) {
this.activityCallback = (TxFragment.Listener) context; this.activityCallback = (TxFragment.Listener) context;

File diff suppressed because it is too large Load Diff

View File

@@ -51,6 +51,7 @@ 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.StickyFiatHelper;
import com.m2049r.xmrwallet.util.ThemeHelper; import com.m2049r.xmrwallet.util.ThemeHelper;
import com.m2049r.xmrwallet.widget.Toolbar; import com.m2049r.xmrwallet.widget.Toolbar;
@@ -112,7 +113,7 @@ public class WalletFragment extends Fragment
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( setColorFilter(
ThemeHelper.getThemedColor(getContext(), com.google.android.material.R.attr.colorPrimaryVariant), ThemeHelper.getThemedColor(requireContext(), com.google.android.material.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);
@@ -131,6 +132,7 @@ public class WalletFragment extends Fragment
ArrayAdapter<String> spinnerAdapter = new ArrayAdapter<>(requireContext(), 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);
StickyFiatHelper.setPreferredCurrencyPosition(sCurrency);
bSend = view.findViewById(R.id.bSend); bSend = view.findViewById(R.id.bSend);
bReceive = view.findViewById(R.id.bReceive); bReceive = view.findViewById(R.id.bReceive);
@@ -182,6 +184,7 @@ public class WalletFragment extends Fragment
sCurrency.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { sCurrency.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override @Override
public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) { public void onItemSelected(AdapterView<?> parentView, View selectedItemView, int position, long id) {
StickyFiatHelper.setPreferredFiatSymbol(requireContext(), (String) sCurrency.getSelectedItem());
refreshBalance(); refreshBalance();
} }
@@ -315,13 +318,7 @@ public class WalletFragment extends Fragment
balanceCurrency = Helper.BASE_CRYPTO; balanceCurrency = Helper.BASE_CRYPTO;
balanceRate = 1.0; balanceRate = 1.0;
} else { } else {
int spinnerPosition = ((ArrayAdapter) sCurrency.getAdapter()).getPosition(exchangeRate.getQuoteCurrency()); StickyFiatHelper.setCurrencyPosition(sCurrency, exchangeRate.getQuoteCurrency());
if (spinnerPosition < 0) { // requested currency not in list
Timber.e("Requested currency not in list %s", exchangeRate.getQuoteCurrency());
sCurrency.setSelection(0, true);
} else {
sCurrency.setSelection(spinnerPosition, true);
}
balanceCurrency = exchangeRate.getQuoteCurrency(); balanceCurrency = exchangeRate.getQuoteCurrency();
balanceRate = exchangeRate.getRate(); balanceRate = exchangeRate.getRate();
} }

View File

@@ -22,27 +22,19 @@ import android.content.res.Configuration;
import android.os.Build; import android.os.Build;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.OptIn;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentStateManagerControl;
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.NetCipherHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper; import com.m2049r.xmrwallet.util.NightmodeHelper;
import com.m2049r.xmrwallet.util.ServiceHelper;
import java.util.Arrays;
import timber.log.Timber; import timber.log.Timber;
public class XmrWalletApplication extends Application { public class XmrWalletApplication extends Application {
@Override @Override
@OptIn(markerClass = FragmentStateManagerControl.class)
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());
} }

View File

@@ -24,43 +24,38 @@ import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
import lombok.Getter;
import lombok.ToString; import lombok.ToString;
import timber.log.Timber; import timber.log.Timber;
@Getter
@ToString @ToString
public class BarcodeData { public class BarcodeData {
public enum Security { public enum Security {
NORMAL, NORMAL,
OA_NO_DNSSEC, OA_NO_DNSSEC,
OA_DNSSEC OA_DNSSEC
} }
final public Crypto asset; final private Set<Crypto> possibleAssets = new HashSet<>();
final public List<Crypto> ambiguousAssets; private String address = null;
final public String address; private String addressName = null;
final public String addressName; private String amount = null;
final public String amount; private String description = null;
final public String description; private Security security = null;
final public Security security;
public BarcodeData(List<Crypto> assets, String address) { public BarcodeData(List<Crypto> assets, String address) {
if (assets.isEmpty()) if (assets.isEmpty())
throw new IllegalArgumentException("no assets specified"); throw new IllegalArgumentException("no assets specified");
this.addressName = null; security = Security.NORMAL;
this.description = null;
this.amount = null;
this.security = Security.NORMAL;
this.address = address; this.address = address;
if (assets.size() == 1) { possibleAssets.addAll(assets);
this.asset = assets.get(0);
this.ambiguousAssets = null;
} else {
this.asset = null;
this.ambiguousAssets = assets;
}
} }
public BarcodeData(Crypto asset, String address, String description, String amount) { public BarcodeData(Crypto asset, String address, String description, String amount) {
@@ -68,8 +63,7 @@ public class BarcodeData {
} }
public BarcodeData(Crypto asset, String address, String addressName, String description, String amount, Security security) { public BarcodeData(Crypto asset, String address, String addressName, String description, String amount, Security security) {
this.ambiguousAssets = null; possibleAssets.add(asset);
this.asset = asset;
this.address = address; this.address = address;
this.addressName = addressName; this.addressName = addressName;
this.description = description; this.description = description;
@@ -82,7 +76,7 @@ public class BarcodeData {
} }
public String getUriString() { public String getUriString() {
if (asset != Crypto.XMR) throw new IllegalStateException("We can only do XMR stuff!"); if (getAsset() != Crypto.XMR) throw new IllegalStateException("We can only do XMR stuff!");
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append(Crypto.XMR.getUriScheme()) sb.append(Crypto.XMR.getUriScheme())
.append(':') .append(':')
@@ -227,6 +221,20 @@ public class BarcodeData {
} }
public boolean isAmbiguous() { public boolean isAmbiguous() {
return ambiguousAssets != null; return possibleAssets.size() > 1;
}
public Crypto getAsset() {
if (possibleAssets.size() == 1) {
return possibleAssets.iterator().next();
} else {
return null;
}
}
// return true if we still have possible assets
public boolean filter(Set<Crypto> assets) {
possibleAssets.retainAll(assets);
return !possibleAssets.isEmpty();
} }
} }

View File

@@ -0,0 +1,42 @@
/*
* Copyright (c) 2018 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.data;
import android.bluetooth.BluetoothDevice;
import java.util.Comparator;
import lombok.Data;
import lombok.Getter;
@Data
public class BluetoothInfo {
@Getter
final private String name;
@Getter
final private String address;
@Getter
private boolean bonded;
public BluetoothInfo(BluetoothDevice device) {
name = device.getName().trim();
address = device.getAddress();
bonded = device.getBondState() == BluetoothDevice.BOND_BONDED;
}
static public Comparator<BluetoothInfo> NameComparator = (o1, o2) -> o1.name.compareTo(o2.name);
}

View File

@@ -1,3 +1,19 @@
/*
* Copyright (c) 2024 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.data; package com.m2049r.xmrwallet.data;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
@@ -5,35 +21,30 @@ import androidx.annotation.Nullable;
import com.m2049r.xmrwallet.R; import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.model.Wallet; import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.util.validator.BitcoinAddressType;
import com.m2049r.xmrwallet.util.validator.BitcoinAddressValidator; import java.util.regex.Pattern;
import com.m2049r.xmrwallet.util.validator.EthAddressValidator;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor @RequiredArgsConstructor
public enum Crypto { public enum Crypto {
XMR("XMR", true, "monero:tx_amount:recipient_name:tx_description", R.id.ibXMR, R.drawable.ic_monero, R.drawable.ic_monero_bw, Wallet::isAddressValid), XMR("XMR", "XMR", "XMR", "monero:tx_amount:recipient_name:tx_description", R.id.ibXMR, R.drawable.ic_monero, R.drawable.ic_monero_bw, Pattern.compile("^[48][a-zA-Z|\\d]{94}([a-zA-Z|\\d]{11})?$")),
BTC("BTC", true, "bitcoin:amount:label:message", R.id.ibBTC, R.drawable.ic_xmrto_btc, R.drawable.ic_xmrto_btc_off, address -> { BTC("BTC", "BTC", "BTC", "bitcoin:amount:label:message", R.id.ibBTC, R.drawable.ic_xmrto_btc, R.drawable.ic_xmrto_btc_off, Pattern.compile("^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$|^(bc1q)|(bc1p)[0-9A-Za-z]{37,62}$")),
return BitcoinAddressValidator.validate(address, BitcoinAddressType.BTC); LTC("LTC", "LTC", "LTC", "litecoin:amount:label:message", R.id.ibLTC, R.drawable.ic_xmrto_ltc, R.drawable.ic_xmrto_ltc_off, Pattern.compile("^([LM3])[A-Za-z0-9]{33}$|^(ltc1)[0-9A-Za-z]{39}$")),
}), ETH("ETH", "ETH", "ETH", "ethereum:amount:label:message", R.id.ibETH, R.drawable.ic_xmrto_eth, R.drawable.ic_xmrto_eth_off, Pattern.compile("^(0x)[0-9A-Fa-f]{40}$")),
DASH("DASH", true, "dash:amount:label:message", R.id.ibDASH, R.drawable.ic_xmrto_dash, R.drawable.ic_xmrto_dash_off, address -> { USDT("USDT", "TRX", "USDT(TRC20)", "usdt:amount:label:message", R.id.ibUSDT, R.drawable.ic_xmrto_usdt_trc20, R.drawable.ic_xmrto_usdt_trc20_off, Pattern.compile("^T[1-9A-HJ-NP-Za-km-z]{33}$")),
return BitcoinAddressValidator.validate(address, BitcoinAddressType.DASH); SOLANA("SOL", "SOL", "SOL", "solana:amount:label:message", R.id.ibSOL, R.drawable.ic_xmrto_sol, R.drawable.ic_xmrto_sol_off, Pattern.compile("^[1-9A-HJ-NP-Za-km-z]{32,44}$"));
}),
DOGE("DOGE", true, "dogecoin:amount:label:message", R.id.ibDOGE, R.drawable.ic_xmrto_doge, R.drawable.ic_xmrto_doge_off, address -> {
return BitcoinAddressValidator.validate(address, BitcoinAddressType.DOGE);
}),
ETH("ETH", false, "ethereum:amount:label:message", R.id.ibETH, R.drawable.ic_xmrto_eth, R.drawable.ic_xmrto_eth_off, EthAddressValidator::validate),
LTC("LTC", true, "litecoin:amount:label:message", R.id.ibLTC, R.drawable.ic_xmrto_ltc, R.drawable.ic_xmrto_ltc_off, address -> {
return BitcoinAddressValidator.validate(address, BitcoinAddressType.LTC);
});
@Getter @Getter
@NonNull @NonNull
private final String symbol; private final String symbol;
@Getter @Getter
private final boolean casefull; @NonNull
private final String network;
@Getter
@NonNull
private final String label;
@NonNull @NonNull
private final String uriSpec; private final String uriSpec;
@Getter @Getter
@@ -43,7 +54,7 @@ public enum Crypto {
@Getter @Getter
private final int iconDisabledId; private final int iconDisabledId;
@NonNull @NonNull
private final Validator validator; private final Pattern regex;
@Nullable @Nullable
public static Crypto withScheme(@NonNull String scheme) { public static Crypto withScheme(@NonNull String scheme) {
@@ -62,10 +73,6 @@ public enum Crypto {
return null; return null;
} }
interface Validator {
boolean validate(String address);
}
// TODO maybe cache these segments // TODO maybe cache these segments
String getUriScheme() { String getUriScheme() {
return uriSpec.split(":")[0]; return uriSpec.split(":")[0];
@@ -83,7 +90,8 @@ public enum Crypto {
return uriSpec.split(":")[3]; return uriSpec.split(":")[3];
} }
boolean validate(String address) { public boolean validate(String address) {
return validator.validate(address); if (this == XMR) return Wallet.isAddressValid(address);
return regex.matcher(address).find();
} }
} }

View File

@@ -0,0 +1,29 @@
/*
* Copyright (c) 2024 m2049r
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.m2049r.xmrwallet.data;
import lombok.Value;
@Value
public class CryptoAmount {
Crypto crypto;
double amount;
public CryptoAmount newWithAmount(double amount) {
return new CryptoAmount(this.crypto, amount);
}
}

View File

@@ -19,23 +19,26 @@ package com.m2049r.xmrwallet.data;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
// Nodes stolen from https://moneroworld.com/#nodes @Getter
@AllArgsConstructor @AllArgsConstructor
public enum DefaultNodes { public enum DefaultNodes {
MONERUJO("nodex.monerujo.io:18081"), AGORIST("xmr.agor.ist:18089/mainnet/agor.ist"),
XMRTO("node.xmr.to:18081"), BOLDSUCK("xmr-de.boldsuck.org:18081/mainnet/boldsuck.org"),
SUPPORTXMR("node.supportxmr.com:18081"),
HASHVAULT("nodes.hashvault.pro:18081"),
MONEROWORLD("node.moneroworld.com:18089"),
XMRTW("opennode.xmr-tw.org:18089"),
ds_jetzt("monero.ds-jetzt.de: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"), boldsuck("6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081/mainnet/boldsuck.onion"),
ds_jetzt_onion("qvlr4w7yhnjrdg3txa72jwtpnjn4ezsrivzvocbnvpfbdo342fahhoad.onion:18089/mainnet/ds-jetzt.onion"); CAKE("xmr-node.cakewallet.com:18081/mainnet/cakewallet.com"),
DS_JETZT("monero.ds-jetzt.de:18089/mainnet/ds-jetzt.de"),
ds_jetzt("qvlr4w7yhnjrdg3txa72jwtpnjn4ezsrivzvocbnvpfbdo342fahhoad.onion:18089/mainnet/ds-jetzt.onion"),
MONERODEVS("node.monerodevs.org:18089/mainnet/monerodevs.org"),
MONERUJO("nodex.monerujo.io:18081/mainnet/monerujo.io"),
monerujo("monerujods7mbghwe6cobdr6ujih6c22zu5rl7zshmizz2udf7v7fsad.onion:18081/mainnet/monerujo.onion"),
SETH("node.sethforprivacy.com:18089/mainnet/sethforprivacy.com"),
seth("sfpp2p7wnfjv3lrvfan4jmmkvhnbsbimpa3cqyuf7nt6zd24xhcqcsyd.onion/mainnet/sethforprivacy.onion"),
STACK("monero.stackwallet.com:18081/mainnet/stackwallet.com"),
STORMYCLOUD("xmr.stormycloud.org:18089/mainnet/stormycloud.org"),
TENZ("monero.10z.com.ar:18089/mainnet/10z.com.ar"),
XMRROCKS("node.xmr.rocks:18089/mainnet/xmr.rocks"),
xmrrocks("xqnnz2xmlmtpy2p4cm4cphg2elkwu5oob7b7so5v4wwgt44p6vbx5ryd.onion/mainnet/xmr.rocks.onion"),
XMRTW("opennode.xmr-tw.org:18089/mainnet/xmr-tw.org");
@Getter
private final String uri; private final String uri;
} }

View File

@@ -16,6 +16,7 @@
package com.m2049r.xmrwallet.data; package com.m2049r.xmrwallet.data;
import com.google.common.net.HostAndPort;
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 com.m2049r.xmrwallet.util.OnionHelper;
@@ -144,7 +145,7 @@ public class Node {
if ((nodeString == null) || nodeString.isEmpty()) if ((nodeString == null) || nodeString.isEmpty())
throw new IllegalArgumentException("daemon is empty"); throw new IllegalArgumentException("daemon is empty");
String daemonAddress; String daemonAddress;
String a[] = nodeString.split("@"); String[] a = nodeString.split("@");
if (a.length == 1) { // no credentials if (a.length == 1) { // no credentials
daemonAddress = a[0]; daemonAddress = a[0];
username = ""; username = "";
@@ -169,10 +170,10 @@ public class Node {
throw new IllegalArgumentException("Too many '/' or too few"); throw new IllegalArgumentException("Too many '/' or too few");
daemonAddress = daParts[0]; daemonAddress = daParts[0];
String da[] = daemonAddress.split(":"); HostAndPort hostAndPort = HostAndPort.fromString(daemonAddress)
if ((da.length > 2) || (da.length < 1)) .withDefaultPort(getDefaultRpcPort())
throw new IllegalArgumentException("Too many ':' or too few"); .requireBracketsForIPv6();
String host = da[0]; String host = hostAndPort.getHost();
if (daParts.length == 1) { if (daParts.length == 1) {
networkType = NetworkType.NetworkType_Mainnet; networkType = NetworkType.NetworkType_Mainnet;
@@ -204,22 +205,12 @@ public class Node {
} }
this.name = name; this.name = name;
int port;
if (da.length == 2) {
try {
port = Integer.parseInt(da[1]);
} catch (NumberFormatException ex) {
throw new IllegalArgumentException("Port not numeric");
}
} else {
port = getDefaultRpcPort();
}
try { try {
setHost(host); setHost(host);
} catch (UnknownHostException ex) { } catch (UnknownHostException ex) {
throw new IllegalArgumentException("cannot resolve host " + host); throw new IllegalArgumentException("cannot resolve host " + host);
} }
this.rpcPort = port; this.rpcPort = hostAndPort.getPort();
this.levinPort = getDefaultLevinPort(); this.levinPort = getDefaultLevinPort();
} }
@@ -233,7 +224,8 @@ public class Node {
if (!username.isEmpty() && !password.isEmpty()) { if (!username.isEmpty() && !password.isEmpty()) {
sb.append(username).append(":").append(password).append("@"); sb.append(username).append(":").append(password).append("@");
} }
sb.append(host).append(":").append(rpcPort); HostAndPort address = HostAndPort.fromParts(host, rpcPort);
sb.append(address.toString());
sb.append("/"); sb.append("/");
switch (networkType) { switch (networkType) {
case NetworkType_Mainnet: case NetworkType_Mainnet:
@@ -271,7 +263,7 @@ public class Node {
} }
public String getAddress() { public String getAddress() {
return getHostAddress() + ":" + rpcPort; return HostAndPort.fromParts(getHostAddress(), rpcPort).toString();
} }
public String getHostAddress() { public String getHostAddress() {

View File

@@ -201,8 +201,13 @@ public class NodeInfo extends Node {
.port(port) .port(port)
.addPathSegment("json_rpc") .addPathSegment("json_rpc")
.build(); .build();
final String json = "{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getlastblockheader\"}";
return new Request(url, json, getUsername(), getPassword()); try {
final JSONObject json = new JSONObject("{\"jsonrpc\":\"2.0\",\"id\":\"0\",\"method\":\"getlastblockheader\"}");
return new Request(url, json, getUsername(), getPassword());
} catch (JSONException ex) {
throw new IllegalStateException(ex);
}
} }
private boolean testRpcService(int port) { private boolean testRpcService(int port) {

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