mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-06 19:00:30 +02:00
Compare commits
42 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
24fc27b09e | ||
![]() |
cf4ff856d5 | ||
![]() |
b01de1ad6e | ||
![]() |
5fb1bcb552 | ||
![]() |
b937ba38b6 | ||
![]() |
849718fdc7 | ||
![]() |
a6372f701d | ||
![]() |
12f135bb14 | ||
![]() |
16870fcbb9 | ||
![]() |
2fbd152fb3 | ||
![]() |
a7b178e024 | ||
![]() |
c5a035437b | ||
![]() |
75c550fd19 | ||
![]() |
1b680344b1 | ||
![]() |
3329636d32 | ||
![]() |
77e9bf7c43 | ||
![]() |
40f5c9365e | ||
![]() |
850ba7efef | ||
![]() |
548369ca0b | ||
![]() |
afc0d5b7bc | ||
![]() |
02e92db59a | ||
![]() |
2b34454214 | ||
![]() |
12706119d3 | ||
![]() |
f7a762f32b | ||
![]() |
86d5d2a2cb | ||
![]() |
e56369b91a | ||
![]() |
b04aa24269 | ||
![]() |
dda86bd5de | ||
![]() |
a19ad7fd52 | ||
![]() |
0443fd808c | ||
![]() |
faf57c96fc | ||
![]() |
57ddddfce2 | ||
![]() |
ab6069058b | ||
![]() |
d94d6e6925 | ||
![]() |
4a819cc159 | ||
![]() |
f40c3d6c6d | ||
![]() |
82b25df7ad | ||
![]() |
08f815e830 | ||
![]() |
4e59be2dff | ||
![]() |
45c5883e11 | ||
![]() |
067a23e6a5 | ||
![]() |
d21fd41c44 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,3 +15,4 @@
|
|||||||
/app/alphaStagenet
|
/app/alphaStagenet
|
||||||
/app/prodStagenet
|
/app/prodStagenet
|
||||||
/app/.cxx
|
/app/.cxx
|
||||||
|
/monerujo.id
|
||||||
|
@@ -13,7 +13,7 @@ set(EXTERNAL_LIBS_DIR ${CMAKE_SOURCE_DIR}/../external-libs)
|
|||||||
|
|
||||||
add_library(sodium STATIC IMPORTED)
|
add_library(sodium STATIC IMPORTED)
|
||||||
set_target_properties(sodium PROPERTIES IMPORTED_LOCATION
|
set_target_properties(sodium PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/libsodium/lib/${ANDROID_ABI}/libsodium.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libsodium.a)
|
||||||
|
|
||||||
############
|
############
|
||||||
# OpenSSL
|
# OpenSSL
|
||||||
@@ -21,11 +21,11 @@ set_target_properties(sodium PROPERTIES IMPORTED_LOCATION
|
|||||||
|
|
||||||
add_library(crypto STATIC IMPORTED)
|
add_library(crypto STATIC IMPORTED)
|
||||||
set_target_properties(crypto PROPERTIES IMPORTED_LOCATION
|
set_target_properties(crypto PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libcrypto.a)
|
||||||
|
|
||||||
add_library(ssl STATIC IMPORTED)
|
add_library(ssl STATIC IMPORTED)
|
||||||
set_target_properties(ssl PROPERTIES IMPORTED_LOCATION
|
set_target_properties(ssl PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libssl.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libssl.a)
|
||||||
|
|
||||||
############
|
############
|
||||||
# Boost
|
# Boost
|
||||||
@@ -33,39 +33,39 @@ set_target_properties(ssl PROPERTIES IMPORTED_LOCATION
|
|||||||
|
|
||||||
add_library(boost_chrono STATIC IMPORTED)
|
add_library(boost_chrono STATIC IMPORTED)
|
||||||
set_target_properties(boost_chrono PROPERTIES IMPORTED_LOCATION
|
set_target_properties(boost_chrono PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_chrono.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libboost_chrono.a)
|
||||||
|
|
||||||
add_library(boost_date_time STATIC IMPORTED)
|
add_library(boost_date_time STATIC IMPORTED)
|
||||||
set_target_properties(boost_date_time PROPERTIES IMPORTED_LOCATION
|
set_target_properties(boost_date_time PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_date_time.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libboost_date_time.a)
|
||||||
|
|
||||||
add_library(boost_filesystem STATIC IMPORTED)
|
add_library(boost_filesystem STATIC IMPORTED)
|
||||||
set_target_properties(boost_filesystem PROPERTIES IMPORTED_LOCATION
|
set_target_properties(boost_filesystem PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_filesystem.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libboost_filesystem.a)
|
||||||
|
|
||||||
add_library(boost_program_options STATIC IMPORTED)
|
add_library(boost_program_options STATIC IMPORTED)
|
||||||
set_target_properties(boost_program_options PROPERTIES IMPORTED_LOCATION
|
set_target_properties(boost_program_options PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_program_options.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libboost_program_options.a)
|
||||||
|
|
||||||
add_library(boost_regex STATIC IMPORTED)
|
add_library(boost_regex STATIC IMPORTED)
|
||||||
set_target_properties(boost_regex PROPERTIES IMPORTED_LOCATION
|
set_target_properties(boost_regex PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_regex.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libboost_regex.a)
|
||||||
|
|
||||||
add_library(boost_serialization STATIC IMPORTED)
|
add_library(boost_serialization STATIC IMPORTED)
|
||||||
set_target_properties(boost_serialization PROPERTIES IMPORTED_LOCATION
|
set_target_properties(boost_serialization PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_serialization.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libboost_serialization.a)
|
||||||
|
|
||||||
add_library(boost_system STATIC IMPORTED)
|
add_library(boost_system STATIC IMPORTED)
|
||||||
set_target_properties(boost_system PROPERTIES IMPORTED_LOCATION
|
set_target_properties(boost_system PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_system.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libboost_system.a)
|
||||||
|
|
||||||
add_library(boost_thread STATIC IMPORTED)
|
add_library(boost_thread STATIC IMPORTED)
|
||||||
set_target_properties(boost_thread PROPERTIES IMPORTED_LOCATION
|
set_target_properties(boost_thread PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_thread.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libboost_thread.a)
|
||||||
|
|
||||||
add_library(boost_wserialization STATIC IMPORTED)
|
add_library(boost_wserialization STATIC IMPORTED)
|
||||||
set_target_properties(boost_wserialization PROPERTIES IMPORTED_LOCATION
|
set_target_properties(boost_wserialization PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/boost/lib/${ANDROID_ABI}/libboost_wserialization.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libboost_wserialization.a)
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# Monero
|
# Monero
|
||||||
@@ -73,99 +73,103 @@ set_target_properties(boost_wserialization PROPERTIES IMPORTED_LOCATION
|
|||||||
|
|
||||||
add_library(wallet_api STATIC IMPORTED)
|
add_library(wallet_api STATIC IMPORTED)
|
||||||
set_target_properties(wallet_api PROPERTIES IMPORTED_LOCATION
|
set_target_properties(wallet_api PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet_api.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libwallet_api.a)
|
||||||
|
|
||||||
add_library(wallet STATIC IMPORTED)
|
add_library(wallet STATIC IMPORTED)
|
||||||
set_target_properties(wallet PROPERTIES IMPORTED_LOCATION
|
set_target_properties(wallet PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libwallet.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libwallet.a)
|
||||||
|
|
||||||
add_library(cryptonote_core STATIC IMPORTED)
|
add_library(cryptonote_core STATIC IMPORTED)
|
||||||
set_target_properties(cryptonote_core PROPERTIES IMPORTED_LOCATION
|
set_target_properties(cryptonote_core PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcryptonote_core.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libcryptonote_core.a)
|
||||||
|
|
||||||
add_library(cryptonote_basic STATIC IMPORTED)
|
add_library(cryptonote_basic STATIC IMPORTED)
|
||||||
set_target_properties(cryptonote_basic PROPERTIES IMPORTED_LOCATION
|
set_target_properties(cryptonote_basic PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcryptonote_basic.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libcryptonote_basic.a)
|
||||||
|
|
||||||
add_library(mnemonics STATIC IMPORTED)
|
add_library(mnemonics STATIC IMPORTED)
|
||||||
set_target_properties(mnemonics PROPERTIES IMPORTED_LOCATION
|
set_target_properties(mnemonics PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libmnemonics.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libmnemonics.a)
|
||||||
|
|
||||||
add_library(common STATIC IMPORTED)
|
add_library(common STATIC IMPORTED)
|
||||||
set_target_properties(common PROPERTIES IMPORTED_LOCATION
|
set_target_properties(common PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcommon.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libcommon.a)
|
||||||
|
|
||||||
add_library(cncrypto STATIC IMPORTED)
|
add_library(cncrypto STATIC IMPORTED)
|
||||||
set_target_properties(cncrypto PROPERTIES IMPORTED_LOCATION
|
set_target_properties(cncrypto PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcncrypto.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libcncrypto.a)
|
||||||
|
|
||||||
add_library(ringct STATIC IMPORTED)
|
add_library(ringct STATIC IMPORTED)
|
||||||
set_target_properties(ringct PROPERTIES IMPORTED_LOCATION
|
set_target_properties(ringct PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libringct.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libringct.a)
|
||||||
|
|
||||||
add_library(ringct_basic STATIC IMPORTED)
|
add_library(ringct_basic STATIC IMPORTED)
|
||||||
set_target_properties(ringct_basic PROPERTIES IMPORTED_LOCATION
|
set_target_properties(ringct_basic PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libringct_basic.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libringct_basic.a)
|
||||||
|
|
||||||
add_library(blockchain_db STATIC IMPORTED)
|
add_library(blockchain_db STATIC IMPORTED)
|
||||||
set_target_properties(blockchain_db PROPERTIES IMPORTED_LOCATION
|
set_target_properties(blockchain_db PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libblockchain_db.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libblockchain_db.a)
|
||||||
|
|
||||||
add_library(lmdb STATIC IMPORTED)
|
add_library(lmdb STATIC IMPORTED)
|
||||||
set_target_properties(lmdb PROPERTIES IMPORTED_LOCATION
|
set_target_properties(lmdb PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/liblmdb.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/liblmdb.a)
|
||||||
|
|
||||||
add_library(easylogging STATIC IMPORTED)
|
add_library(easylogging STATIC IMPORTED)
|
||||||
set_target_properties(easylogging PROPERTIES IMPORTED_LOCATION
|
set_target_properties(easylogging PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libeasylogging.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libeasylogging.a)
|
||||||
|
|
||||||
add_library(unbound STATIC IMPORTED)
|
add_library(unbound STATIC IMPORTED)
|
||||||
set_target_properties(unbound PROPERTIES IMPORTED_LOCATION
|
set_target_properties(unbound PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libunbound.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libunbound.a)
|
||||||
|
|
||||||
add_library(epee STATIC IMPORTED)
|
add_library(epee STATIC IMPORTED)
|
||||||
set_target_properties(epee PROPERTIES IMPORTED_LOCATION
|
set_target_properties(epee PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libepee.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libepee.a)
|
||||||
|
|
||||||
add_library(blocks STATIC IMPORTED)
|
add_library(blocks STATIC IMPORTED)
|
||||||
set_target_properties(blocks PROPERTIES IMPORTED_LOCATION
|
set_target_properties(blocks PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libblocks.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libblocks.a)
|
||||||
|
|
||||||
add_library(checkpoints STATIC IMPORTED)
|
add_library(checkpoints STATIC IMPORTED)
|
||||||
set_target_properties(checkpoints PROPERTIES IMPORTED_LOCATION
|
set_target_properties(checkpoints PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libcheckpoints.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libcheckpoints.a)
|
||||||
|
|
||||||
add_library(device STATIC IMPORTED)
|
add_library(device STATIC IMPORTED)
|
||||||
set_target_properties(device PROPERTIES IMPORTED_LOCATION
|
set_target_properties(device PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libdevice.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libdevice.a)
|
||||||
|
|
||||||
add_library(device_trezor STATIC IMPORTED)
|
add_library(device_trezor STATIC IMPORTED)
|
||||||
set_target_properties(device_trezor PROPERTIES IMPORTED_LOCATION
|
set_target_properties(device_trezor PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libdevice_trezor.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libdevice_trezor.a)
|
||||||
|
|
||||||
add_library(multisig STATIC IMPORTED)
|
add_library(multisig STATIC IMPORTED)
|
||||||
set_target_properties(multisig PROPERTIES IMPORTED_LOCATION
|
set_target_properties(multisig PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libmultisig.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libmultisig.a)
|
||||||
|
|
||||||
add_library(version STATIC IMPORTED)
|
add_library(version STATIC IMPORTED)
|
||||||
set_target_properties(version PROPERTIES IMPORTED_LOCATION
|
set_target_properties(version PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libversion.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libversion.a)
|
||||||
|
|
||||||
add_library(net STATIC IMPORTED)
|
add_library(net STATIC IMPORTED)
|
||||||
set_target_properties(net PROPERTIES IMPORTED_LOCATION
|
set_target_properties(net PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libnet.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libnet.a)
|
||||||
|
|
||||||
add_library(hardforks STATIC IMPORTED)
|
add_library(hardforks STATIC IMPORTED)
|
||||||
set_target_properties(hardforks PROPERTIES IMPORTED_LOCATION
|
set_target_properties(hardforks PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/libhardforks.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libhardforks.a)
|
||||||
|
|
||||||
add_library(randomx STATIC IMPORTED)
|
add_library(randomx STATIC IMPORTED)
|
||||||
set_target_properties(randomx PROPERTIES IMPORTED_LOCATION
|
set_target_properties(randomx PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/librandomx.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/librandomx.a)
|
||||||
|
|
||||||
add_library(rpc_base STATIC IMPORTED)
|
add_library(rpc_base STATIC IMPORTED)
|
||||||
set_target_properties(rpc_base PROPERTIES IMPORTED_LOCATION
|
set_target_properties(rpc_base PROPERTIES IMPORTED_LOCATION
|
||||||
${EXTERNAL_LIBS_DIR}/monero/lib/${ANDROID_ABI}/librpc_base.a)
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/librpc_base.a)
|
||||||
|
|
||||||
|
add_library(wallet-crypto STATIC IMPORTED)
|
||||||
|
set_target_properties(wallet-crypto PROPERTIES IMPORTED_LOCATION
|
||||||
|
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/monero/libwallet-crypto.a)
|
||||||
|
|
||||||
#############
|
#############
|
||||||
# System
|
# System
|
||||||
@@ -173,10 +177,16 @@ set_target_properties(rpc_base PROPERTIES IMPORTED_LOCATION
|
|||||||
|
|
||||||
find_library( log-lib log )
|
find_library( log-lib log )
|
||||||
|
|
||||||
include_directories( ${EXTERNAL_LIBS_DIR}/monero/include )
|
include_directories( ${EXTERNAL_LIBS_DIR}/include )
|
||||||
|
|
||||||
message(STATUS EXTERNAL_LIBS_DIR : ${EXTERNAL_LIBS_DIR})
|
message(STATUS EXTERNAL_LIBS_DIR : ${EXTERNAL_LIBS_DIR})
|
||||||
|
|
||||||
|
if(${ANDROID_ABI} STREQUAL "x86_64")
|
||||||
|
set(EXTRA_LIBS "wallet-crypto")
|
||||||
|
else()
|
||||||
|
set(EXTRA_LIBS "")
|
||||||
|
endif()
|
||||||
|
|
||||||
target_link_libraries( monerujo
|
target_link_libraries( monerujo
|
||||||
|
|
||||||
wallet_api
|
wallet_api
|
||||||
@@ -203,6 +213,7 @@ target_link_libraries( monerujo
|
|||||||
randomx
|
randomx
|
||||||
hardforks
|
hardforks
|
||||||
rpc_base
|
rpc_base
|
||||||
|
${EXTRA_LIBS}
|
||||||
|
|
||||||
boost_chrono
|
boost_chrono
|
||||||
boost_date_time
|
boost_date_time
|
||||||
|
@@ -1,14 +1,14 @@
|
|||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 28
|
compileSdkVersion 29
|
||||||
buildToolsVersion '29.0.2'
|
buildToolsVersion '29.0.3'
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "com.m2049r.xmrwallet"
|
applicationId "com.m2049r.xmrwallet"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 28
|
targetSdkVersion 29
|
||||||
versionCode 503
|
versionCode 801
|
||||||
versionName "1.15.3 'Dark Fork'"
|
versionName "1.18.1 'ChAdOx1'"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
cmake {
|
cmake {
|
||||||
@@ -56,6 +56,9 @@ android {
|
|||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix ".debug"
|
||||||
}
|
}
|
||||||
|
applicationVariants.all { variant ->
|
||||||
|
variant.buildConfigField "String", "ID_A", "\"" + getId("ID_A") + "\""
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
@@ -109,20 +112,26 @@ android {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def getId(name) {
|
||||||
|
def Properties props = new Properties()
|
||||||
|
props.load(new FileInputStream(new File('monerujo.id')))
|
||||||
|
return props[name]
|
||||||
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.core:core:1.3.2'
|
implementation 'androidx.core:core:1.3.2'
|
||||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||||
implementation group: 'com.google.android.material', name: 'material', version: '1.3.0-alpha03'
|
implementation 'com.google.android.material:material:1.3.0'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
implementation 'androidx.recyclerview:recyclerview:1.1.0'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.2.0-alpha01'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
|
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||||
|
|
||||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||||
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
implementation "com.squareup.okhttp3:okhttp:4.9.0"
|
||||||
implementation "com.burgstaller:okhttp-digest:2.0"
|
implementation "com.burgstaller:okhttp-digest:2.1"
|
||||||
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
|
implementation "com.jakewharton.timber:timber:4.7.1"
|
||||||
|
|
||||||
implementation 'com.nulab-inc:zxcvbn:1.3.0'
|
implementation 'com.nulab-inc:zxcvbn:1.3.0'
|
||||||
|
|
||||||
@@ -131,12 +140,14 @@ dependencies {
|
|||||||
implementation 'org.slf4j:slf4j-nop:1.7.30'
|
implementation 'org.slf4j:slf4j-nop:1.7.30'
|
||||||
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
|
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
|
||||||
|
|
||||||
// https://mvnrepository.com/artifact/com.github.aelstad/keccakj
|
|
||||||
implementation 'com.github.aelstad:keccakj:1.1.0'
|
implementation 'com.github.aelstad:keccakj:1.1.0'
|
||||||
|
|
||||||
testImplementation "junit:junit:$rootProject.ext.junitVersion"
|
testImplementation "junit:junit:$rootProject.ext.junitVersion"
|
||||||
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
testImplementation "org.mockito:mockito-all:$rootProject.ext.mockitoVersion"
|
||||||
testImplementation "com.squareup.okhttp3:mockwebserver:$rootProject.ext.okHttpVersion"
|
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.0"
|
||||||
testImplementation 'org.json:json:20180813'
|
testImplementation 'org.json:json:20180813'
|
||||||
testImplementation 'net.jodah:concurrentunit:0.4.4'
|
testImplementation 'net.jodah:concurrentunit:0.4.4'
|
||||||
|
|
||||||
|
compileOnly 'org.projectlombok:lombok:1.18.16'
|
||||||
|
annotationProcessor 'org.projectlombok:lombok:1.18.16'
|
||||||
}
|
}
|
||||||
|
@@ -13,6 +13,7 @@
|
|||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:requestLegacyExternalStorage="true"
|
||||||
android:name=".XmrWalletApplication"
|
android:name=".XmrWalletApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
@@ -185,7 +185,6 @@ public class Dispatcher implements PeerRetriever.OnGetPeers {
|
|||||||
|
|
||||||
public void seedPeers(Collection<NodeInfo> seedNodes) {
|
public void seedPeers(Collection<NodeInfo> seedNodes) {
|
||||||
for (NodeInfo node : seedNodes) {
|
for (NodeInfo node : seedNodes) {
|
||||||
node.clear();
|
|
||||||
if (node.isFavourite()) {
|
if (node.isFavourite()) {
|
||||||
rpcNodes.add(node);
|
rpcNodes.add(node);
|
||||||
if (listener != null) listener.onGet(node);
|
if (listener != null) listener.onGet(node);
|
||||||
|
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* 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.levin.scanner;
|
package com.m2049r.levin.scanner;
|
||||||
|
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
@@ -1,3 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017-2020 m2049r et al.
|
||||||
|
*
|
||||||
|
* 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;
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
import android.app.PendingIntent;
|
import android.app.PendingIntent;
|
||||||
@@ -15,10 +31,11 @@ import android.os.Bundle;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.CallSuper;
|
import androidx.annotation.CallSuper;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.data.BarcodeData;
|
import com.m2049r.xmrwallet.data.BarcodeData;
|
||||||
import com.m2049r.xmrwallet.dialog.ProgressDialog;
|
import com.m2049r.xmrwallet.dialog.ProgressDialog;
|
||||||
@@ -30,7 +47,8 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class BaseActivity extends SecureActivity implements GenerateReviewFragment.ProgressListener {
|
public class BaseActivity extends SecureActivity
|
||||||
|
implements GenerateReviewFragment.ProgressListener, SubaddressFragment.ProgressListener {
|
||||||
|
|
||||||
ProgressDialog progressDialog = null;
|
ProgressDialog progressDialog = null;
|
||||||
|
|
||||||
@@ -197,7 +215,7 @@ public class BaseActivity extends SecureActivity implements GenerateReviewFragme
|
|||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show();
|
Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show();
|
||||||
} else {
|
} else {
|
||||||
BarcodeData bc = BarcodeData.fromQrCode(uri.toString());
|
BarcodeData bc = BarcodeData.fromString(uri.toString());
|
||||||
if (bc == null)
|
if (bc == null)
|
||||||
Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show();
|
Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show();
|
||||||
else
|
else
|
||||||
|
@@ -50,9 +50,8 @@ import com.m2049r.xmrwallet.util.Helper;
|
|||||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||||
import com.m2049r.xmrwallet.util.ledger.Monero;
|
import com.m2049r.xmrwallet.util.ledger.Monero;
|
||||||
|
import com.m2049r.xmrwallet.widget.PasswordEntryView;
|
||||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
import com.nulabinc.zxcvbn.Strength;
|
|
||||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
@@ -70,7 +69,7 @@ public class GenerateFragment extends Fragment {
|
|||||||
static final String TYPE_VIEWONLY = "view";
|
static final String TYPE_VIEWONLY = "view";
|
||||||
|
|
||||||
private TextInputLayout etWalletName;
|
private TextInputLayout etWalletName;
|
||||||
private TextInputLayout etWalletPassword;
|
private PasswordEntryView etWalletPassword;
|
||||||
private LinearLayout llFingerprintAuth;
|
private LinearLayout llFingerprintAuth;
|
||||||
private TextInputLayout etWalletAddress;
|
private TextInputLayout etWalletAddress;
|
||||||
private TextInputLayout etWalletMnemonic;
|
private TextInputLayout etWalletMnemonic;
|
||||||
@@ -131,21 +130,6 @@ public class GenerateFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
clearErrorOnTextEntry(etWalletName);
|
clearErrorOnTextEntry(etWalletName);
|
||||||
|
|
||||||
etWalletPassword.getEditText().addTextChangedListener(new TextWatcher() {
|
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable editable) {
|
|
||||||
checkPassword();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start, int before, int count) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
etWalletMnemonic.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
etWalletMnemonic.getEditText().setOnFocusChangeListener(new View.OnFocusChangeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onFocusChange(View v, boolean hasFocus) {
|
public void onFocusChange(View v, boolean hasFocus) {
|
||||||
@@ -354,45 +338,10 @@ public class GenerateFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
|
|
||||||
etWalletName.requestFocus();
|
etWalletName.requestFocus();
|
||||||
initZxcvbn();
|
|
||||||
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
Zxcvbn zxcvbn = new Zxcvbn();
|
|
||||||
|
|
||||||
// initialize zxcvbn engine in background thread
|
|
||||||
private void initZxcvbn() {
|
|
||||||
new Thread(new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
zxcvbn.measure("");
|
|
||||||
}
|
|
||||||
}).start();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkPassword() {
|
|
||||||
String password = etWalletPassword.getEditText().getText().toString();
|
|
||||||
if (!password.isEmpty()) {
|
|
||||||
Strength strength = zxcvbn.measure(password);
|
|
||||||
int msg;
|
|
||||||
double guessesLog10 = strength.getGuessesLog10();
|
|
||||||
if (guessesLog10 < 10)
|
|
||||||
msg = R.string.password_weak;
|
|
||||||
else if (guessesLog10 < 11)
|
|
||||||
msg = R.string.password_fair;
|
|
||||||
else if (guessesLog10 < 12)
|
|
||||||
msg = R.string.password_good;
|
|
||||||
else if (guessesLog10 < 13)
|
|
||||||
msg = R.string.password_strong;
|
|
||||||
else
|
|
||||||
msg = R.string.password_very_strong;
|
|
||||||
etWalletPassword.setError(getResources().getString(msg));
|
|
||||||
} else {
|
|
||||||
etWalletPassword.setError(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean checkName() {
|
private boolean checkName() {
|
||||||
String name = etWalletName.getEditText().getText().toString();
|
String name = etWalletName.getEditText().getText().toString();
|
||||||
boolean ok = true;
|
boolean ok = true;
|
||||||
@@ -429,7 +378,7 @@ public class GenerateFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private long getHeight() {
|
private long getHeight() {
|
||||||
long height = 0;
|
long height = -1;
|
||||||
|
|
||||||
String restoreHeight = etWalletRestoreHeight.getEditText().getText().toString().trim();
|
String restoreHeight = etWalletRestoreHeight.getEditText().getText().toString().trim();
|
||||||
if (restoreHeight.isEmpty()) return -1;
|
if (restoreHeight.isEmpty()) return -1;
|
||||||
@@ -440,7 +389,7 @@ public class GenerateFragment extends Fragment {
|
|||||||
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
||||||
} catch (ParseException ex) {
|
} catch (ParseException ex) {
|
||||||
}
|
}
|
||||||
if ((height <= 0) && (restoreHeight.length() == 8))
|
if ((height < 0) && (restoreHeight.length() == 8))
|
||||||
try {
|
try {
|
||||||
// is it a date without dashes?
|
// is it a date without dashes?
|
||||||
SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd");
|
SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd");
|
||||||
@@ -448,7 +397,7 @@ public class GenerateFragment extends Fragment {
|
|||||||
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
|
||||||
} catch (ParseException ex) {
|
} catch (ParseException ex) {
|
||||||
}
|
}
|
||||||
if (height <= 0)
|
if (height < 0)
|
||||||
try {
|
try {
|
||||||
// or is it a height?
|
// or is it a height?
|
||||||
height = Long.parseLong(restoreHeight);
|
height = Long.parseLong(restoreHeight);
|
||||||
@@ -654,24 +603,7 @@ public class GenerateFragment extends Fragment {
|
|||||||
final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed);
|
final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed);
|
||||||
final TextInputLayout etPassphrase = promptsView.findViewById(R.id.etPassphrase);
|
final TextInputLayout etPassphrase = promptsView.findViewById(R.id.etPassphrase);
|
||||||
|
|
||||||
etSeed.getEditText().addTextChangedListener(new TextWatcher() {
|
clearErrorOnTextEntry(etSeed);
|
||||||
@Override
|
|
||||||
public void afterTextChanged(Editable s) {
|
|
||||||
if (etSeed.getError() != null) {
|
|
||||||
etSeed.setError(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void beforeTextChanged(CharSequence s, int start,
|
|
||||||
int count, int after) {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTextChanged(CharSequence s, int start,
|
|
||||||
int before, int count) {
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
alertDialogBuilder
|
alertDialogBuilder
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
|
@@ -56,6 +56,7 @@ import com.m2049r.xmrwallet.util.FingerprintHelper;
|
|||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||||
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||||
|
import com.m2049r.xmrwallet.widget.PasswordEntryView;
|
||||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
|
|
||||||
import java.text.NumberFormat;
|
import java.text.NumberFormat;
|
||||||
@@ -306,7 +307,6 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
void dismissProgressDialog();
|
void dismissProgressDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public interface AcceptListener {
|
public interface AcceptListener {
|
||||||
void onAccept(String name, String password);
|
void onAccept(String name, String password);
|
||||||
}
|
}
|
||||||
@@ -473,7 +473,7 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(getActivity());
|
AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(getActivity());
|
||||||
alertDialogBuilder.setView(promptsView);
|
alertDialogBuilder.setView(promptsView);
|
||||||
|
|
||||||
final TextInputLayout etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA);
|
final PasswordEntryView etPasswordA = promptsView.findViewById(R.id.etWalletPasswordA);
|
||||||
etPasswordA.setHint(getString(R.string.prompt_changepw, walletName));
|
etPasswordA.setHint(getString(R.string.prompt_changepw, walletName));
|
||||||
|
|
||||||
final TextInputLayout etPasswordB = promptsView.findViewById(R.id.etWalletPasswordB);
|
final TextInputLayout etPasswordB = promptsView.findViewById(R.id.etWalletPasswordB);
|
||||||
@@ -509,9 +509,6 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
etPasswordA.getEditText().addTextChangedListener(new TextWatcher() {
|
etPasswordA.getEditText().addTextChangedListener(new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
if (etPasswordA.getError() != null) {
|
|
||||||
etPasswordA.setError(null);
|
|
||||||
}
|
|
||||||
if (etPasswordB.getError() != null) {
|
if (etPasswordB.getError() != null) {
|
||||||
etPasswordB.setError(null);
|
etPasswordB.setError(null);
|
||||||
}
|
}
|
||||||
@@ -531,9 +528,6 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
etPasswordB.getEditText().addTextChangedListener(new TextWatcher() {
|
etPasswordB.getEditText().addTextChangedListener(new TextWatcher() {
|
||||||
@Override
|
@Override
|
||||||
public void afterTextChanged(Editable s) {
|
public void afterTextChanged(Editable s) {
|
||||||
if (etPasswordA.getError() != null) {
|
|
||||||
etPasswordA.setError(null);
|
|
||||||
}
|
|
||||||
if (etPasswordB.getError() != null) {
|
if (etPasswordB.getError() != null) {
|
||||||
etPasswordB.setError(null);
|
etPasswordB.setError(null);
|
||||||
}
|
}
|
||||||
@@ -564,29 +558,20 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
|
|
||||||
openDialog = alertDialogBuilder.create();
|
openDialog = alertDialogBuilder.create();
|
||||||
openDialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
openDialog.setOnShowListener(dialog -> {
|
||||||
@Override
|
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
|
||||||
public void onShow(final DialogInterface dialog) {
|
button.setOnClickListener(view -> {
|
||||||
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
|
String newPasswordA = etPasswordA.getEditText().getText().toString();
|
||||||
button.setOnClickListener(new View.OnClickListener() {
|
String newPasswordB = etPasswordB.getEditText().getText().toString();
|
||||||
@Override
|
if (!newPasswordA.equals(newPasswordB)) {
|
||||||
public void onClick(View view) {
|
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
|
||||||
String newPasswordA = etPasswordA.getEditText().getText().toString();
|
} else {
|
||||||
String newPasswordB = etPasswordB.getEditText().getText().toString();
|
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
|
||||||
// disallow empty passwords
|
Helper.hideKeyboardAlways(getActivity());
|
||||||
if (newPasswordA.isEmpty()) {
|
openDialog.dismiss();
|
||||||
etPasswordA.setError(getString(R.string.generate_empty_passwordB));
|
openDialog = null;
|
||||||
} else if (!newPasswordA.equals(newPasswordB)) {
|
}
|
||||||
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
|
});
|
||||||
} else {
|
|
||||||
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
|
|
||||||
Helper.hideKeyboardAlways(getActivity());
|
|
||||||
openDialog.dismiss();
|
|
||||||
openDialog = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// accept keyboard "ok"
|
// accept keyboard "ok"
|
||||||
@@ -597,9 +582,7 @@ public class GenerateReviewFragment extends Fragment {
|
|||||||
String newPasswordA = etPasswordA.getEditText().getText().toString();
|
String newPasswordA = etPasswordA.getEditText().getText().toString();
|
||||||
String newPasswordB = etPasswordB.getEditText().getText().toString();
|
String newPasswordB = etPasswordB.getEditText().getText().toString();
|
||||||
// disallow empty passwords
|
// disallow empty passwords
|
||||||
if (newPasswordA.isEmpty()) {
|
if (!newPasswordA.equals(newPasswordB)) {
|
||||||
etPasswordA.setError(getString(R.string.generate_empty_passwordB));
|
|
||||||
} else if (!newPasswordA.equals(newPasswordB)) {
|
|
||||||
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
|
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
|
||||||
} else {
|
} else {
|
||||||
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
|
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -45,6 +45,7 @@ import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
|
|||||||
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;
|
||||||
|
import com.m2049r.xmrwallet.util.NodePinger;
|
||||||
import com.m2049r.xmrwallet.util.Notice;
|
import com.m2049r.xmrwallet.util.Notice;
|
||||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
|
|
||||||
@@ -81,8 +82,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
|
|
||||||
void onWalletDetails(String wallet);
|
void onWalletDetails(String wallet);
|
||||||
|
|
||||||
void onWalletReceive(String wallet);
|
|
||||||
|
|
||||||
void onWalletRename(String name);
|
void onWalletRename(String name);
|
||||||
|
|
||||||
void onWalletBackup(String name);
|
void onWalletBackup(String name);
|
||||||
@@ -105,6 +104,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
|
|
||||||
Set<NodeInfo> getFavouriteNodes();
|
Set<NodeInfo> getFavouriteNodes();
|
||||||
|
|
||||||
|
Set<NodeInfo> getOrPopulateFavourites();
|
||||||
|
|
||||||
boolean hasLedger();
|
boolean hasLedger();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,11 +133,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
activityCallback.setTitle(null);
|
activityCallback.setTitle(null);
|
||||||
activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS);
|
activityCallback.setToolbarButton(Toolbar.BUTTON_CREDITS);
|
||||||
activityCallback.showNet();
|
activityCallback.showNet();
|
||||||
NodeInfo node = activityCallback.getNode();
|
pingSelectedNode();
|
||||||
if (node == null)
|
|
||||||
findBestNode();
|
|
||||||
else
|
|
||||||
showNode(node);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -186,23 +183,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
|
|
||||||
pbNode = view.findViewById(R.id.pbNode);
|
pbNode = view.findViewById(R.id.pbNode);
|
||||||
llNode = view.findViewById(R.id.llNode);
|
llNode = view.findViewById(R.id.llNode);
|
||||||
llNode.setOnClickListener(new View.OnClickListener() {
|
llNode.setOnClickListener(v -> startNodePrefs());
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
if (activityCallback.getFavouriteNodes().isEmpty())
|
|
||||||
startNodePrefs();
|
|
||||||
else
|
|
||||||
findBestNode();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
tvNodeName = view.findViewById(R.id.tvNodeName);
|
tvNodeName = view.findViewById(R.id.tvNodeName);
|
||||||
tvNodeAddress = view.findViewById(R.id.tvNodeAddress);
|
tvNodeAddress = view.findViewById(R.id.tvNodeAddress);
|
||||||
view.findViewById(R.id.ibOption).setOnClickListener(new View.OnClickListener() {
|
view.findViewById(R.id.ibRenew).setOnClickListener(v -> findBestNode());
|
||||||
@Override
|
|
||||||
public void onClick(View v) {
|
|
||||||
startNodePrefs();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Helper.hideKeyboard(getActivity());
|
Helper.hideKeyboard(getActivity());
|
||||||
|
|
||||||
@@ -237,9 +221,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
case R.id.action_info:
|
case R.id.action_info:
|
||||||
showInfo(listItem.name);
|
showInfo(listItem.name);
|
||||||
break;
|
break;
|
||||||
case R.id.action_receive:
|
|
||||||
showReceive(listItem.name);
|
|
||||||
break;
|
|
||||||
case R.id.action_rename:
|
case R.id.action_rename:
|
||||||
activityCallback.onWalletRename(listItem.name);
|
activityCallback.onWalletRename(listItem.name);
|
||||||
break;
|
break;
|
||||||
@@ -272,7 +253,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
walletList.addAll(walletInfos);
|
walletList.addAll(walletInfos);
|
||||||
filterList();
|
filterList();
|
||||||
adapter.setInfos(displayedList);
|
adapter.setInfos(displayedList);
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
|
|
||||||
// deal with Gunther & FAB animation
|
// deal with Gunther & FAB animation
|
||||||
if (displayedList.isEmpty()) {
|
if (displayedList.isEmpty()) {
|
||||||
@@ -305,10 +285,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
activityCallback.onWalletDetails(name);
|
activityCallback.onWalletDetails(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showReceive(@NonNull String name) {
|
|
||||||
activityCallback.onWalletReceive(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
@@ -421,32 +397,57 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void findBestNode() {
|
public void findBestNode() {
|
||||||
new AsyncFindBestNode().execute();
|
new AsyncFindBestNode().execute(AsyncFindBestNode.FIND_BEST);
|
||||||
}
|
}
|
||||||
|
|
||||||
private class AsyncFindBestNode extends AsyncTask<Void, Void, NodeInfo> {
|
public void pingSelectedNode() {
|
||||||
|
new AsyncFindBestNode().execute(AsyncFindBestNode.PING_SELECTED);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NodeInfo autoselect(Set<NodeInfo> nodes) {
|
||||||
|
if (nodes.isEmpty()) return null;
|
||||||
|
NodePinger.execute(nodes, null);
|
||||||
|
List<NodeInfo> nodeList = new ArrayList<>(nodes);
|
||||||
|
Collections.sort(nodeList, NodeInfo.BestNodeComparator);
|
||||||
|
return nodeList.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AsyncFindBestNode extends AsyncTask<Integer, Void, NodeInfo> {
|
||||||
|
final static int PING_SELECTED = 0;
|
||||||
|
final static int FIND_BEST = 1;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onPreExecute() {
|
protected void onPreExecute() {
|
||||||
super.onPreExecute();
|
super.onPreExecute();
|
||||||
pbNode.setVisibility(View.VISIBLE);
|
pbNode.setVisibility(View.VISIBLE);
|
||||||
llNode.setVisibility(View.INVISIBLE);
|
llNode.setVisibility(View.INVISIBLE);
|
||||||
activityCallback.setNode(null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected NodeInfo doInBackground(Void... params) {
|
protected NodeInfo doInBackground(Integer... params) {
|
||||||
List<NodeInfo> nodesToTest = new ArrayList<>(activityCallback.getFavouriteNodes());
|
Set<NodeInfo> favourites = activityCallback.getOrPopulateFavourites();
|
||||||
Timber.d("testing best node from %d", nodesToTest.size());
|
NodeInfo selectedNode;
|
||||||
if (nodesToTest.isEmpty()) return null;
|
if (params[0] == FIND_BEST) {
|
||||||
for (NodeInfo node : nodesToTest) {
|
selectedNode = autoselect(favourites);
|
||||||
node.testRpcService(); // TODO: do this in parallel?
|
} else if (params[0] == PING_SELECTED) {
|
||||||
// no: it's better if it looks like it's doing something
|
selectedNode = activityCallback.getNode();
|
||||||
}
|
if (!activityCallback.getFavouriteNodes().contains(selectedNode))
|
||||||
Collections.sort(nodesToTest, NodeInfo.BestNodeComparator);
|
selectedNode = null; // it's not in the favourites (any longer)
|
||||||
NodeInfo bestNode = nodesToTest.get(0);
|
if (selectedNode == null)
|
||||||
if (bestNode.isValid()) {
|
for (NodeInfo node : favourites) {
|
||||||
activityCallback.setNode(bestNode);
|
if (node.isSelected()) {
|
||||||
return bestNode;
|
selectedNode = node;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (selectedNode == null) { // autoselect
|
||||||
|
selectedNode = autoselect(favourites);
|
||||||
|
} else
|
||||||
|
selectedNode.testRpcService();
|
||||||
|
} else throw new IllegalStateException();
|
||||||
|
if ((selectedNode != null) && selectedNode.isValid()) {
|
||||||
|
activityCallback.setNode(selectedNode);
|
||||||
|
return selectedNode;
|
||||||
} else {
|
} else {
|
||||||
activityCallback.setNode(null);
|
activityCallback.setNode(null);
|
||||||
return null;
|
return null;
|
||||||
@@ -462,17 +463,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
Timber.d("found a good node %s", result.toString());
|
Timber.d("found a good node %s", result.toString());
|
||||||
showNode(result);
|
showNode(result);
|
||||||
} else {
|
} else {
|
||||||
if (!activityCallback.getFavouriteNodes().isEmpty()) {
|
tvNodeName.setText(getResources().getText(R.string.node_create_hint));
|
||||||
tvNodeName.setText(getResources().getText(R.string.node_refresh_hint));
|
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
||||||
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(R.drawable.ic_refresh_black_24dp, 0, 0, 0);
|
tvNodeAddress.setText(null);
|
||||||
tvNodeAddress.setText(null);
|
tvNodeAddress.setVisibility(View.GONE);
|
||||||
tvNodeAddress.setVisibility(View.GONE);
|
|
||||||
} else {
|
|
||||||
tvNodeName.setText(getResources().getText(R.string.node_create_hint));
|
|
||||||
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
|
|
||||||
tvNodeAddress.setText(null);
|
|
||||||
tvNodeAddress.setVisibility(View.GONE);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -485,12 +479,11 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
|||||||
private void showNode(NodeInfo nodeInfo) {
|
private void showNode(NodeInfo nodeInfo) {
|
||||||
tvNodeName.setText(nodeInfo.getName());
|
tvNodeName.setText(nodeInfo.getName());
|
||||||
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(NodeInfoAdapter.getPingIcon(nodeInfo), 0, 0, 0);
|
tvNodeName.setCompoundDrawablesWithIntrinsicBounds(NodeInfoAdapter.getPingIcon(nodeInfo), 0, 0, 0);
|
||||||
tvNodeAddress.setText(nodeInfo.getAddress());
|
Helper.showTimeDifference(tvNodeAddress, nodeInfo.getTimestamp());
|
||||||
tvNodeAddress.setVisibility(View.VISIBLE);
|
tvNodeAddress.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startNodePrefs() {
|
private void startNodePrefs() {
|
||||||
activityCallback.setNode(null);
|
|
||||||
activityCallback.onNodePrefs();
|
activityCallback.onNodePrefs();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2021 m2049r
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
|
|
||||||
|
public interface OnBlockUpdateListener {
|
||||||
|
void onBlockUpdate(final Wallet wallet);
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@@ -73,14 +73,7 @@ public class ScannerFragment extends Fragment implements ZXingScannerView.Result
|
|||||||
// * On older devices continuously stopping and resuming camera preview can result in freezing the app.
|
// * On older devices continuously stopping and resuming camera preview can result in freezing the app.
|
||||||
// * I don't know why this is the case but I don't have the time to figure out.
|
// * I don't know why this is the case but I don't have the time to figure out.
|
||||||
Handler handler = new Handler();
|
Handler handler = new Handler();
|
||||||
handler.postDelayed(new
|
handler.postDelayed(() -> mScannerView.resumeCameraPreview(ScannerFragment.this), 2000);
|
||||||
|
|
||||||
Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
mScannerView.resumeCameraPreview(ScannerFragment.this);
|
|
||||||
}
|
|
||||||
}, 2000);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
246
app/src/main/java/com/m2049r/xmrwallet/SubaddressFragment.java
Normal file
246
app/src/main/java/com/m2049r/xmrwallet/SubaddressFragment.java
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017 m2049r
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.android.material.transition.MaterialElevationScale;
|
||||||
|
import com.m2049r.xmrwallet.data.Subaddress;
|
||||||
|
import com.m2049r.xmrwallet.layout.SubaddressInfoAdapter;
|
||||||
|
import com.m2049r.xmrwallet.ledger.LedgerProgressDialog;
|
||||||
|
import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
|
import com.m2049r.xmrwallet.model.WalletManager;
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
|
||||||
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class SubaddressFragment extends Fragment implements SubaddressInfoAdapter.OnInteractionListener,
|
||||||
|
View.OnClickListener {
|
||||||
|
static public final String KEY_MODE = "mode";
|
||||||
|
static public final String MODE_MANAGER = "manager";
|
||||||
|
|
||||||
|
private SubaddressInfoAdapter adapter;
|
||||||
|
|
||||||
|
private Listener activityCallback;
|
||||||
|
|
||||||
|
private Wallet wallet;
|
||||||
|
|
||||||
|
// Container Activity must implement this interface
|
||||||
|
public interface Listener {
|
||||||
|
void onSubaddressSelected(Subaddress subaddress);
|
||||||
|
|
||||||
|
void setSubtitle(String title);
|
||||||
|
|
||||||
|
void setToolbarButton(int type);
|
||||||
|
|
||||||
|
void showSubaddress(View view, final int subaddressIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ProgressListener {
|
||||||
|
void showProgressDialog(int msgId);
|
||||||
|
|
||||||
|
void showLedgerProgressDialog(int mode);
|
||||||
|
|
||||||
|
void dismissProgressDialog();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ProgressListener progressCallback = null;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(@NonNull Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
if (context instanceof ProgressListener) {
|
||||||
|
progressCallback = (ProgressListener) context;
|
||||||
|
}
|
||||||
|
if (context instanceof Listener) {
|
||||||
|
activityCallback = (Listener) context;
|
||||||
|
} else {
|
||||||
|
throw new ClassCastException(context.toString()
|
||||||
|
+ " must implement Listener");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
Timber.d("onPause()");
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
activityCallback.setSubtitle(getString(R.string.subbaddress_title));
|
||||||
|
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
Timber.d("onCreateView");
|
||||||
|
|
||||||
|
final Bundle b = getArguments();
|
||||||
|
managerMode = ((b != null) && (MODE_MANAGER.equals(b.getString(KEY_MODE))));
|
||||||
|
|
||||||
|
View view = inflater.inflate(R.layout.fragment_subaddress, container, false);
|
||||||
|
|
||||||
|
final MaterialElevationScale exitTransition = new MaterialElevationScale(false);
|
||||||
|
exitTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
|
setExitTransition(exitTransition);
|
||||||
|
final MaterialElevationScale reenterTransition = new MaterialElevationScale(true);
|
||||||
|
reenterTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
|
setReenterTransition(reenterTransition);
|
||||||
|
|
||||||
|
view.findViewById(R.id.fab).setOnClickListener(this);
|
||||||
|
|
||||||
|
if (managerMode) {
|
||||||
|
view.findViewById(R.id.tvInstruction).setVisibility(View.GONE);
|
||||||
|
view.findViewById(R.id.tvHint).setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
final RecyclerView list = view.findViewById(R.id.list);
|
||||||
|
adapter = new SubaddressInfoAdapter(getActivity(), this);
|
||||||
|
list.setAdapter(adapter);
|
||||||
|
adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
|
||||||
|
@Override
|
||||||
|
public void onItemRangeInserted(int positionStart, int itemCount) {
|
||||||
|
list.scrollToPosition(positionStart);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Helper.hideKeyboard(getActivity());
|
||||||
|
|
||||||
|
wallet = WalletManager.getInstance().getWallet();
|
||||||
|
|
||||||
|
loadList();
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
postponeEnterTransition();
|
||||||
|
view.getViewTreeObserver().addOnPreDrawListener(() -> {
|
||||||
|
startPostponedEnterTransition();
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadList() {
|
||||||
|
Timber.d("loadList()");
|
||||||
|
final int numSubaddresses = wallet.getNumSubaddresses();
|
||||||
|
final List<Subaddress> list = new ArrayList<>();
|
||||||
|
for (int i = 0; i < numSubaddresses; i++) {
|
||||||
|
list.add(wallet.getSubaddressObject(i));
|
||||||
|
}
|
||||||
|
adapter.setInfos(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
int id = v.getId();
|
||||||
|
if (id == R.id.fab) {
|
||||||
|
getNewSubaddress();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int lastUsedSubaddress() {
|
||||||
|
int lastUsedSubaddress = 0;
|
||||||
|
for (TransactionInfo info : wallet.getHistory().getAll()) {
|
||||||
|
if (info.addressIndex > lastUsedSubaddress)
|
||||||
|
lastUsedSubaddress = info.addressIndex;
|
||||||
|
}
|
||||||
|
return lastUsedSubaddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void getNewSubaddress() {
|
||||||
|
final int maxSubaddresses = lastUsedSubaddress() + wallet.getDeviceType().getSubaddressLookahead();
|
||||||
|
if (wallet.getNumSubaddresses() < maxSubaddresses)
|
||||||
|
new AsyncSubaddress().executeOnExecutor(MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR);
|
||||||
|
else
|
||||||
|
Toast.makeText(getActivity(), getString(R.string.max_subaddress_warning), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("StaticFieldLeak")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
private class AsyncSubaddress extends AsyncTask<Void, Void, Boolean> {
|
||||||
|
boolean dialogOpened = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPreExecute() {
|
||||||
|
super.onPreExecute();
|
||||||
|
if ((wallet.getDeviceType() == Wallet.Device.Device_Ledger) && (progressCallback != null)) {
|
||||||
|
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS);
|
||||||
|
dialogOpened = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Boolean doInBackground(Void... params) {
|
||||||
|
if (params.length != 0) return false;
|
||||||
|
wallet.getNewSubaddress();
|
||||||
|
wallet.store();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPostExecute(Boolean result) {
|
||||||
|
super.onPostExecute(result);
|
||||||
|
if (dialogOpened)
|
||||||
|
progressCallback.dismissProgressDialog();
|
||||||
|
if (!isAdded()) // never mind then
|
||||||
|
return;
|
||||||
|
loadList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean managerMode = false;
|
||||||
|
|
||||||
|
// Callbacks from SubaddressInfoAdapter
|
||||||
|
@Override
|
||||||
|
public void onInteraction(final View view, final Subaddress subaddress) {
|
||||||
|
if (managerMode)
|
||||||
|
activityCallback.showSubaddress(view, subaddress.getAddressIndex());
|
||||||
|
else
|
||||||
|
activityCallback.onSubaddressSelected(subaddress); // also closes the fragment with onBackpressed()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onLongInteraction(View view, Subaddress subaddress) {
|
||||||
|
activityCallback.showSubaddress(view, subaddress.getAddressIndex());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2017 m2049r
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.fragment.app.Fragment;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
|
|
||||||
|
import com.google.android.material.textfield.TextInputLayout;
|
||||||
|
import com.google.android.material.transition.MaterialContainerTransform;
|
||||||
|
import com.m2049r.xmrwallet.data.Subaddress;
|
||||||
|
import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
|
||||||
|
import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
import com.m2049r.xmrwallet.util.ThemeHelper;
|
||||||
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
|
public class SubaddressInfoFragment extends Fragment
|
||||||
|
implements TransactionInfoAdapter.OnInteractionListener, OnBlockUpdateListener {
|
||||||
|
private TransactionInfoAdapter adapter;
|
||||||
|
|
||||||
|
private Subaddress subaddress;
|
||||||
|
|
||||||
|
private TextInputLayout etName;
|
||||||
|
private TextView tvAddress;
|
||||||
|
private TextView tvTxLabel;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
View view = inflater.inflate(R.layout.fragment_subaddressinfo, container, false);
|
||||||
|
|
||||||
|
etName = view.findViewById(R.id.etName);
|
||||||
|
tvAddress = view.findViewById(R.id.tvAddress);
|
||||||
|
tvTxLabel = view.findViewById(R.id.tvTxLabel);
|
||||||
|
|
||||||
|
final RecyclerView list = view.findViewById(R.id.list);
|
||||||
|
adapter = new TransactionInfoAdapter(getActivity(), this);
|
||||||
|
list.setAdapter(adapter);
|
||||||
|
|
||||||
|
final Wallet wallet = activityCallback.getWallet();
|
||||||
|
|
||||||
|
Bundle b = getArguments();
|
||||||
|
final int subaddressIndex = b.getInt("subaddressIndex");
|
||||||
|
subaddress = wallet.getSubaddressObject(subaddressIndex);
|
||||||
|
|
||||||
|
etName.getEditText().setText(subaddress.getDisplayLabel());
|
||||||
|
tvAddress.setText(getContext().getString(R.string.subbaddress_info_subtitle,
|
||||||
|
subaddress.getAddressIndex(), subaddress.getSquashedAddress()));
|
||||||
|
|
||||||
|
etName.getEditText().setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
|
if (!hasFocus) {
|
||||||
|
wallet.setSubaddressLabel(subaddressIndex, etName.getEditText().getText().toString());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
etName.getEditText().setOnEditorActionListener((v, actionId, event) -> {
|
||||||
|
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||||
|
|| (actionId == EditorInfo.IME_ACTION_DONE)) {
|
||||||
|
Helper.hideKeyboard(getActivity());
|
||||||
|
wallet.setSubaddressLabel(subaddressIndex, etName.getEditText().getText().toString());
|
||||||
|
onRefreshed(wallet);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
onRefreshed(wallet);
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
final MaterialContainerTransform transform = new MaterialContainerTransform();
|
||||||
|
transform.setDrawingViewId(R.id.fragment_container);
|
||||||
|
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
|
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground));
|
||||||
|
setSharedElementEnterTransition(transform);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRefreshed(final Wallet wallet) {
|
||||||
|
Timber.d("onRefreshed");
|
||||||
|
List<TransactionInfo> list = new ArrayList<>();
|
||||||
|
for (TransactionInfo info : wallet.getHistory().getAll()) {
|
||||||
|
if (info.addressIndex == subaddress.getAddressIndex())
|
||||||
|
list.add(info);
|
||||||
|
}
|
||||||
|
adapter.setInfos(list);
|
||||||
|
if (list.isEmpty())
|
||||||
|
tvTxLabel.setText(R.string.subaddress_notx_label);
|
||||||
|
else
|
||||||
|
tvTxLabel.setText(R.string.subaddress_tx_label);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBlockUpdate(Wallet wallet) {
|
||||||
|
onRefreshed(wallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callbacks from TransactionInfoAdapter
|
||||||
|
@Override
|
||||||
|
public void onInteraction(final View view, final TransactionInfo infoItem) {
|
||||||
|
activityCallback.onTxDetailsRequest(view, infoItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
Listener activityCallback;
|
||||||
|
|
||||||
|
// Container Activity must implement this interface
|
||||||
|
public interface Listener {
|
||||||
|
void onTxDetailsRequest(View view, TransactionInfo info);
|
||||||
|
|
||||||
|
Wallet getWallet();
|
||||||
|
|
||||||
|
void setToolbarButton(int type);
|
||||||
|
|
||||||
|
void setTitle(String title, String subtitle);
|
||||||
|
|
||||||
|
void setSubtitle(String subtitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(@NonNull Context context) {
|
||||||
|
super.onAttach(context);
|
||||||
|
if (context instanceof Listener) {
|
||||||
|
this.activityCallback = (Listener) context;
|
||||||
|
} else {
|
||||||
|
throw new ClassCastException(context.toString()
|
||||||
|
+ " must implement Listener");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
Timber.d("onResume()");
|
||||||
|
activityCallback.setSubtitle(getString(R.string.subbaddress_title));
|
||||||
|
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPause() {
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
}
|
@@ -16,15 +16,21 @@
|
|||||||
|
|
||||||
package com.m2049r.xmrwallet;
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
|
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.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.Html;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
|
import android.text.Spanned;
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.ImageView;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
@@ -32,11 +38,15 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
|
|
||||||
|
import com.google.android.material.transition.MaterialContainerTransform;
|
||||||
|
import com.google.android.material.transition.MaterialElevationScale;
|
||||||
|
import com.m2049r.xmrwallet.data.Subaddress;
|
||||||
import com.m2049r.xmrwallet.data.UserNotes;
|
import com.m2049r.xmrwallet.data.UserNotes;
|
||||||
import com.m2049r.xmrwallet.model.TransactionInfo;
|
import com.m2049r.xmrwallet.model.TransactionInfo;
|
||||||
import com.m2049r.xmrwallet.model.Transfer;
|
import com.m2049r.xmrwallet.model.Transfer;
|
||||||
import com.m2049r.xmrwallet.model.Wallet;
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
import com.m2049r.xmrwallet.util.ThemeHelper;
|
||||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||||
|
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
@@ -46,6 +56,8 @@ import java.util.HashSet;
|
|||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class TxFragment extends Fragment {
|
public class TxFragment extends Fragment {
|
||||||
|
|
||||||
static public final String ARG_INFO = "info";
|
static public final String ARG_INFO = "info";
|
||||||
@@ -77,17 +89,28 @@ public class TxFragment extends Fragment {
|
|||||||
private TextView tvTxXmrToKey;
|
private TextView tvTxXmrToKey;
|
||||||
private TextView tvDestinationBtc;
|
private TextView tvDestinationBtc;
|
||||||
private TextView tvTxAmountBtc;
|
private TextView tvTxAmountBtc;
|
||||||
|
private TextView tvXmrToSupport;
|
||||||
|
private TextView tvXmrToKeyLabel;
|
||||||
|
private ImageView tvXmrToLogo;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||||
Bundle savedInstanceState) {
|
|
||||||
|
|
||||||
View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
|
View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
|
||||||
|
|
||||||
|
final MaterialElevationScale exitTransition = new MaterialElevationScale(false);
|
||||||
|
exitTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
|
setExitTransition(exitTransition);
|
||||||
|
final MaterialElevationScale reenterTransition = new MaterialElevationScale(true);
|
||||||
|
reenterTransition.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
|
setReenterTransition(reenterTransition);
|
||||||
|
|
||||||
cvXmrTo = view.findViewById(R.id.cvXmrTo);
|
cvXmrTo = view.findViewById(R.id.cvXmrTo);
|
||||||
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
|
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
|
||||||
tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc);
|
tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc);
|
||||||
tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc);
|
tvTxAmountBtc = view.findViewById(R.id.tvTxAmountBtc);
|
||||||
|
tvXmrToSupport = view.findViewById(R.id.tvXmrToSupport);
|
||||||
|
tvXmrToKeyLabel = view.findViewById(R.id.tvXmrToKeyLabel);
|
||||||
|
tvXmrToLogo = view.findViewById(R.id.tvXmrToLogo);
|
||||||
|
|
||||||
tvAccount = view.findViewById(R.id.tvAccount);
|
tvAccount = view.findViewById(R.id.tvAccount);
|
||||||
tvAddress = view.findViewById(R.id.tvAddress);
|
tvAddress = view.findViewById(R.id.tvAddress);
|
||||||
@@ -104,17 +127,13 @@ public class TxFragment extends Fragment {
|
|||||||
|
|
||||||
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
|
||||||
|
|
||||||
tvTxXmrToKey.setOnClickListener(new View.OnClickListener() {
|
tvTxXmrToKey.setOnClickListener(v -> {
|
||||||
@Override
|
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
|
||||||
public void onClick(View v) {
|
Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
|
||||||
Helper.clipBoardCopy(getActivity(), getString(R.string.label_copy_xmrtokey), tvTxXmrToKey.getText().toString());
|
|
||||||
Toast.makeText(getActivity(), getString(R.string.message_copy_xmrtokey), Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Bundle args = getArguments();
|
info = getArguments().getParcelable(ARG_INFO);
|
||||||
TransactionInfo info = args.getParcelable(ARG_INFO);
|
show();
|
||||||
show(info);
|
|
||||||
return view;
|
return view;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,7 +202,7 @@ public class TxFragment extends Fragment {
|
|||||||
TransactionInfo info = null;
|
TransactionInfo info = null;
|
||||||
UserNotes userNotes = null;
|
UserNotes userNotes = null;
|
||||||
|
|
||||||
void loadNotes(TransactionInfo info) {
|
void loadNotes() {
|
||||||
if ((userNotes == null) || (info.notes == null)) {
|
if ((userNotes == null) || (info.notes == null)) {
|
||||||
info.notes = activityCallback.getTxNotes(info.hash);
|
info.notes = activityCallback.getTxNotes(info.hash);
|
||||||
}
|
}
|
||||||
@@ -196,19 +215,28 @@ public class TxFragment extends Fragment {
|
|||||||
tvTxFee.setTextColor(clr);
|
tvTxFee.setTextColor(clr);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void show(TransactionInfo info) {
|
private void showSubaddressLabel() {
|
||||||
|
final Subaddress subaddress = activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex);
|
||||||
|
final Context ctx = getContext();
|
||||||
|
Spanned label = Html.fromHtml(ctx.getString(R.string.tx_account_formatted,
|
||||||
|
info.accountIndex, info.addressIndex,
|
||||||
|
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoGreen) & 0xFFFFFF),
|
||||||
|
Integer.toHexString(ContextCompat.getColor(ctx, R.color.monerujoBackground) & 0xFFFFFF),
|
||||||
|
subaddress.getDisplayLabel()));
|
||||||
|
tvAccount.setText(label);
|
||||||
|
tvAccount.setOnClickListener(v -> activityCallback.showSubaddress(v, info.addressIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void show() {
|
||||||
if (info.txKey == null) {
|
if (info.txKey == null) {
|
||||||
info.txKey = activityCallback.getTxKey(info.hash);
|
info.txKey = activityCallback.getTxKey(info.hash);
|
||||||
}
|
}
|
||||||
if (info.address == null) {
|
if (info.address == null) {
|
||||||
info.address = activityCallback.getTxAddress(info.account, info.subaddress);
|
info.address = activityCallback.getTxAddress(info.accountIndex, info.addressIndex);
|
||||||
}
|
}
|
||||||
loadNotes(info);
|
loadNotes();
|
||||||
|
|
||||||
activityCallback.setSubtitle(getString(R.string.tx_title));
|
showSubaddressLabel();
|
||||||
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
|
||||||
|
|
||||||
tvAccount.setText(getString(R.string.tx_account_formatted, info.account, info.subaddress));
|
|
||||||
tvAddress.setText(info.address);
|
tvAddress.setText(info.address);
|
||||||
|
|
||||||
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
|
tvTxTimestamp.setText(TS_FORMATTER.format(new Date(info.timestamp * 1000)));
|
||||||
@@ -247,8 +275,8 @@ public class TxFragment extends Fragment {
|
|||||||
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_minus));
|
setTxColour(ContextCompat.getColor(getContext(), R.color.tx_minus));
|
||||||
}
|
}
|
||||||
Set<String> destinations = new HashSet<>();
|
Set<String> destinations = new HashSet<>();
|
||||||
StringBuffer sb = new StringBuffer();
|
StringBuilder sb = new StringBuilder();
|
||||||
StringBuffer dstSb = new StringBuffer();
|
StringBuilder dstSb = new StringBuilder();
|
||||||
if (info.transfers != null) {
|
if (info.transfers != null) {
|
||||||
boolean newline = false;
|
boolean newline = false;
|
||||||
for (Transfer transfer : info.transfers) {
|
for (Transfer transfer : info.transfers) {
|
||||||
@@ -272,23 +300,45 @@ public class TxFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
sb.append("-");
|
sb.append("-");
|
||||||
dstSb.append(info.direction ==
|
dstSb.append(info.direction == TransactionInfo.Direction.Direction_In ?
|
||||||
TransactionInfo.Direction.Direction_In ?
|
activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex).getAddress() :
|
||||||
activityCallback.getWalletSubaddress(info.account, info.subaddress) :
|
|
||||||
"-");
|
"-");
|
||||||
}
|
}
|
||||||
tvTxTransfers.setText(sb.toString());
|
tvTxTransfers.setText(sb.toString());
|
||||||
tvDestination.setText(dstSb.toString());
|
tvDestination.setText(dstSb.toString());
|
||||||
this.info = info;
|
|
||||||
showBtcInfo();
|
showBtcInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SetTextI18n")
|
||||||
void showBtcInfo() {
|
void showBtcInfo() {
|
||||||
if (userNotes.xmrtoKey != null) {
|
if (userNotes.xmrtoKey != null) {
|
||||||
cvXmrTo.setVisibility(View.VISIBLE);
|
cvXmrTo.setVisibility(View.VISIBLE);
|
||||||
tvTxXmrToKey.setText(userNotes.xmrtoKey);
|
String key = userNotes.xmrtoKey;
|
||||||
|
if ("xmrto".equals(userNotes.xmrtoTag)) { // legacy xmr.to service :(
|
||||||
|
key = "xmrto-" + key;
|
||||||
|
}
|
||||||
|
tvTxXmrToKey.setText(key);
|
||||||
tvDestinationBtc.setText(userNotes.xmrtoDestination);
|
tvDestinationBtc.setText(userNotes.xmrtoDestination);
|
||||||
tvTxAmountBtc.setText(userNotes.xmrtoAmount + " BTC");
|
tvTxAmountBtc.setText(userNotes.xmrtoAmount + " " + userNotes.xmrtoCurrency);
|
||||||
|
switch (userNotes.xmrtoTag) {
|
||||||
|
case "xmrto":
|
||||||
|
tvXmrToSupport.setVisibility(View.GONE);
|
||||||
|
tvXmrToKeyLabel.setVisibility(View.INVISIBLE);
|
||||||
|
tvXmrToLogo.setImageResource(R.drawable.ic_xmrto_logo);
|
||||||
|
break;
|
||||||
|
case "side": // defaults in layout - just add underline
|
||||||
|
tvXmrToSupport.setPaintFlags(tvXmrToSupport.getPaintFlags() | Paint.UNDERLINE_TEXT_FLAG);
|
||||||
|
tvXmrToSupport.setOnClickListener(v -> {
|
||||||
|
Uri uri = Uri.parse("https://sideshift.ai/orders/" + userNotes.xmrtoKey);
|
||||||
|
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||||
|
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);
|
||||||
}
|
}
|
||||||
@@ -298,6 +348,11 @@ public class TxFragment extends Fragment {
|
|||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setHasOptionsMenu(true);
|
setHasOptionsMenu(true);
|
||||||
|
final MaterialContainerTransform transform = new MaterialContainerTransform();
|
||||||
|
transform.setDrawingViewId(R.id.fragment_container);
|
||||||
|
transform.setDuration(getResources().getInteger(R.integer.tx_item_transition_duration));
|
||||||
|
transform.setAllContainerColors(ThemeHelper.getThemedColor(getContext(), android.R.attr.colorBackground));
|
||||||
|
setSharedElementEnterTransition(transform);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -309,7 +364,7 @@ public class TxFragment extends Fragment {
|
|||||||
Listener activityCallback;
|
Listener activityCallback;
|
||||||
|
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
String getWalletSubaddress(int accountIndex, int subaddressIndex);
|
Subaddress getWalletSubaddress(int accountIndex, int subaddressIndex);
|
||||||
|
|
||||||
String getTxKey(String hash);
|
String getTxKey(String hash);
|
||||||
|
|
||||||
@@ -323,6 +378,8 @@ public class TxFragment extends Fragment {
|
|||||||
|
|
||||||
void setSubtitle(String subtitle);
|
void setSubtitle(String subtitle);
|
||||||
|
|
||||||
|
void showSubaddress(View view, final int subaddressIndex);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -347,4 +404,13 @@ public class TxFragment extends Fragment {
|
|||||||
Helper.hideKeyboard(getActivity());
|
Helper.hideKeyboard(getActivity());
|
||||||
super.onPause();
|
super.onPause();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
Timber.d("onResume()");
|
||||||
|
activityCallback.setSubtitle(getString(R.string.tx_title));
|
||||||
|
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||||
|
showSubaddressLabel();
|
||||||
|
}
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -16,14 +16,13 @@
|
|||||||
|
|
||||||
package com.m2049r.xmrwallet;
|
package com.m2049r.xmrwallet;
|
||||||
|
|
||||||
|
|
||||||
import android.app.Application;
|
import android.app.Application;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.res.Configuration;
|
import android.content.res.Configuration;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.BuildConfig;
|
||||||
import com.m2049r.xmrwallet.model.NetworkType;
|
import com.m2049r.xmrwallet.model.NetworkType;
|
||||||
import com.m2049r.xmrwallet.util.DayNightMode;
|
|
||||||
import com.m2049r.xmrwallet.util.LocaleHelper;
|
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||||
import com.m2049r.xmrwallet.util.NightmodeHelper;
|
import com.m2049r.xmrwallet.util.NightmodeHelper;
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
89
app/src/main/java/com/m2049r/xmrwallet/data/Crypto.java
Normal file
89
app/src/main/java/com/m2049r/xmrwallet/data/Crypto.java
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
package com.m2049r.xmrwallet.data;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
|
import com.m2049r.xmrwallet.R;
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
|
import com.m2049r.xmrwallet.util.validator.BitcoinAddressType;
|
||||||
|
import com.m2049r.xmrwallet.util.validator.BitcoinAddressValidator;
|
||||||
|
import com.m2049r.xmrwallet.util.validator.EthAddressValidator;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
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),
|
||||||
|
BTC("BTC", true, "bitcoin:amount:label:message", R.id.ibBTC, R.drawable.ic_xmrto_btc, R.drawable.ic_xmrto_btc_off, address -> {
|
||||||
|
return BitcoinAddressValidator.validate(address, BitcoinAddressType.BTC);
|
||||||
|
}),
|
||||||
|
DASH("DASH", true, "dash:amount:label:message", R.id.ibDASH, R.drawable.ic_xmrto_dash, R.drawable.ic_xmrto_dash_off, address -> {
|
||||||
|
return BitcoinAddressValidator.validate(address, BitcoinAddressType.DASH);
|
||||||
|
}),
|
||||||
|
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
|
||||||
|
@NonNull
|
||||||
|
private final String symbol;
|
||||||
|
@Getter
|
||||||
|
private final boolean casefull;
|
||||||
|
@NonNull
|
||||||
|
private final String uriSpec;
|
||||||
|
@Getter
|
||||||
|
private final int buttonId;
|
||||||
|
@Getter
|
||||||
|
private final int iconEnabledId;
|
||||||
|
@Getter
|
||||||
|
private final int iconDisabledId;
|
||||||
|
@NonNull
|
||||||
|
private final Validator validator;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Crypto withScheme(@NonNull String scheme) {
|
||||||
|
for (Crypto crypto : values()) {
|
||||||
|
if (crypto.getUriScheme().equals(scheme)) return crypto;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public static Crypto withSymbol(@NonNull String symbol) {
|
||||||
|
final String upperSymbol = symbol.toUpperCase();
|
||||||
|
for (Crypto crypto : values()) {
|
||||||
|
if (crypto.symbol.equals(upperSymbol)) return crypto;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Validator {
|
||||||
|
boolean validate(String address);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO maybe cache these segments
|
||||||
|
String getUriScheme() {
|
||||||
|
return uriSpec.split(":")[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
String getUriAmount() {
|
||||||
|
return uriSpec.split(":")[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
String getUriLabel() {
|
||||||
|
return uriSpec.split(":")[2];
|
||||||
|
}
|
||||||
|
|
||||||
|
String getUriMessage() {
|
||||||
|
return uriSpec.split(":")[3];
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean validate(String address) {
|
||||||
|
return validator.validate(address);
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2020 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.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
// Nodes stolen from https://moneroworld.com/#nodes
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
public enum DefaultNodes {
|
||||||
|
MONERUJO("nodex.monerujo.io:18081"),
|
||||||
|
XMRTO("node.xmr.to:18081"),
|
||||||
|
SUPPORTXMR("node.supportxmr.com:18081"),
|
||||||
|
HASHVAULT("nodes.hashvault.pro:18081"),
|
||||||
|
MONEROWORLD("node.moneroworld.com:18089"),
|
||||||
|
XMRTW("opennode.xmr-tw.org:18089");
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
private final String uri;
|
||||||
|
}
|
@@ -26,6 +26,8 @@ import java.net.URLDecoder;
|
|||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class Node {
|
public class Node {
|
||||||
@@ -33,27 +35,43 @@ public class Node {
|
|||||||
static public final String STAGENET = "stagenet";
|
static public final String STAGENET = "stagenet";
|
||||||
static public final String TESTNET = "testnet";
|
static public final String TESTNET = "testnet";
|
||||||
|
|
||||||
|
@Getter
|
||||||
private String name = null;
|
private String name = null;
|
||||||
|
@Getter
|
||||||
final private NetworkType networkType;
|
final private NetworkType networkType;
|
||||||
InetAddress hostAddress;
|
InetAddress hostAddress;
|
||||||
|
@Getter
|
||||||
private String host;
|
private String host;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
int rpcPort = 0;
|
int rpcPort = 0;
|
||||||
private int levinPort = 0;
|
private int levinPort = 0;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
private String username = "";
|
private String username = "";
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
private String password = "";
|
private String password = "";
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
private boolean favourite = false;
|
private boolean favourite = false;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private boolean selected = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return hostAddress.hashCode();
|
return hostAddress.hashCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nodes are equal if they are the same host address & are on the same network
|
// Nodes are equal if they are the same host address:port & are on the same network
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object other) {
|
public boolean equals(Object other) {
|
||||||
if (!(other instanceof Node)) return false;
|
if (!(other instanceof Node)) return false;
|
||||||
final Node anotherNode = (Node) other;
|
final Node anotherNode = (Node) other;
|
||||||
return (hostAddress.equals(anotherNode.hostAddress) && (networkType == anotherNode.networkType));
|
return (hostAddress.equals(anotherNode.hostAddress)
|
||||||
|
&& (rpcPort == anotherNode.rpcPort)
|
||||||
|
&& (networkType == anotherNode.networkType));
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Node fromString(String nodeString) {
|
static public Node fromString(String nodeString) {
|
||||||
@@ -193,7 +211,6 @@ public class Node {
|
|||||||
this.levinPort = socketAddress.getPort();
|
this.levinPort = socketAddress.getPort();
|
||||||
this.username = "";
|
this.username = "";
|
||||||
this.password = "";
|
this.password = "";
|
||||||
//this.name = socketAddress.getHostName(); // triggers DNS so we don't do it by default
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getAddress() {
|
public String getAddress() {
|
||||||
@@ -204,14 +221,6 @@ public class Node {
|
|||||||
return hostAddress.getHostAddress();
|
return hostAddress.getHostAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getHost() {
|
|
||||||
return host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getRpcPort() {
|
|
||||||
return rpcPort;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setHost(String host) throws UnknownHostException {
|
public void setHost(String host) throws UnknownHostException {
|
||||||
if ((host == null) || (host.isEmpty()))
|
if ((host == null) || (host.isEmpty()))
|
||||||
throw new UnknownHostException("loopback not supported (yet?)");
|
throw new UnknownHostException("loopback not supported (yet?)");
|
||||||
@@ -219,18 +228,6 @@ public class Node {
|
|||||||
this.hostAddress = InetAddress.getByName(host);
|
this.hostAddress = InetAddress.getByName(host);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUsername(String user) {
|
|
||||||
username = user;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPassword(String pass) {
|
|
||||||
password = pass;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setRpcPort(int port) {
|
|
||||||
this.rpcPort = port;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setName() {
|
public void setName() {
|
||||||
if (name == null)
|
if (name == null)
|
||||||
this.name = hostAddress.getHostName();
|
this.name = hostAddress.getHostName();
|
||||||
@@ -243,30 +240,6 @@ public class Node {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkType getNetworkType() {
|
|
||||||
return networkType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPassword() {
|
|
||||||
return password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFavourite() {
|
|
||||||
return favourite;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setFavourite(boolean favourite) {
|
|
||||||
this.favourite = favourite;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void toggleFavourite() {
|
public void toggleFavourite() {
|
||||||
favourite = !favourite;
|
favourite = !favourite;
|
||||||
}
|
}
|
||||||
|
@@ -21,8 +21,8 @@ import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
|
|||||||
import com.burgstaller.okhttp.digest.CachingAuthenticator;
|
import com.burgstaller.okhttp.digest.CachingAuthenticator;
|
||||||
import com.burgstaller.okhttp.digest.Credentials;
|
import com.burgstaller.okhttp.digest.Credentials;
|
||||||
import com.burgstaller.okhttp.digest.DigestAuthenticator;
|
import com.burgstaller.okhttp.digest.DigestAuthenticator;
|
||||||
import com.m2049r.levin.scanner.Dispatcher;
|
|
||||||
import com.m2049r.levin.scanner.LevinPeer;
|
import com.m2049r.levin.scanner.LevinPeer;
|
||||||
|
import com.m2049r.xmrwallet.util.NodePinger;
|
||||||
import com.m2049r.xmrwallet.util.OkHttpHelper;
|
import com.m2049r.xmrwallet.util.OkHttpHelper;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@@ -36,6 +36,8 @@ import java.util.Comparator;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
import okhttp3.HttpUrl;
|
import okhttp3.HttpUrl;
|
||||||
import okhttp3.MediaType;
|
import okhttp3.MediaType;
|
||||||
import okhttp3.OkHttpClient;
|
import okhttp3.OkHttpClient;
|
||||||
@@ -46,14 +48,24 @@ import okhttp3.ResponseBody;
|
|||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
public class NodeInfo extends Node {
|
public class NodeInfo extends Node {
|
||||||
final static public int MIN_MAJOR_VERSION = 11;
|
final static public int MIN_MAJOR_VERSION = 14;
|
||||||
final static public String RPC_VERSION = "2.0";
|
final static public String RPC_VERSION = "2.0";
|
||||||
|
|
||||||
|
@Getter
|
||||||
private long height = 0;
|
private long height = 0;
|
||||||
|
@Getter
|
||||||
private long timestamp = 0;
|
private long timestamp = 0;
|
||||||
|
@Getter
|
||||||
private int majorVersion = 0;
|
private int majorVersion = 0;
|
||||||
|
@Getter
|
||||||
private double responseTime = Double.MAX_VALUE;
|
private double responseTime = Double.MAX_VALUE;
|
||||||
|
@Getter
|
||||||
private int responseCode = 0;
|
private int responseCode = 0;
|
||||||
|
@Getter
|
||||||
|
private boolean tested = false;
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private boolean selecting = false;
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
height = 0;
|
height = 0;
|
||||||
@@ -61,13 +73,13 @@ public class NodeInfo extends Node {
|
|||||||
responseTime = Double.MAX_VALUE;
|
responseTime = Double.MAX_VALUE;
|
||||||
responseCode = 0;
|
responseCode = 0;
|
||||||
timestamp = 0;
|
timestamp = 0;
|
||||||
|
tested = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public NodeInfo fromString(String nodeString) {
|
static public NodeInfo fromString(String nodeString) {
|
||||||
try {
|
try {
|
||||||
return new NodeInfo(nodeString);
|
return new NodeInfo(nodeString);
|
||||||
} catch (IllegalArgumentException ex) {
|
} catch (IllegalArgumentException ex) {
|
||||||
Timber.w(ex);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -113,26 +125,6 @@ public class NodeInfo extends Node {
|
|||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getHeight() {
|
|
||||||
return height;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getTimestamp() {
|
|
||||||
return timestamp;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMajorVersion() {
|
|
||||||
return majorVersion;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getResponseTime() {
|
|
||||||
return responseTime;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getResponseCode() {
|
|
||||||
return responseCode;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSuccessful() {
|
public boolean isSuccessful() {
|
||||||
return (responseCode >= 200) && (responseCode < 300);
|
return (responseCode >= 200) && (responseCode < 300);
|
||||||
}
|
}
|
||||||
@@ -145,23 +137,20 @@ public class NodeInfo extends Node {
|
|||||||
return isSuccessful() && (majorVersion >= MIN_MAJOR_VERSION) && (responseTime < Double.MAX_VALUE);
|
return isSuccessful() && (majorVersion >= MIN_MAJOR_VERSION) && (responseTime < Double.MAX_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Comparator<NodeInfo> BestNodeComparator = new Comparator<NodeInfo>() {
|
static public Comparator<NodeInfo> BestNodeComparator = (o1, o2) -> {
|
||||||
@Override
|
if (o1.isValid()) {
|
||||||
public int compare(NodeInfo o1, NodeInfo o2) {
|
if (o2.isValid()) { // both are valid
|
||||||
if (o1.isValid()) {
|
// higher node wins
|
||||||
if (o2.isValid()) { // both are valid
|
int heightDiff = (int) (o2.height - o1.height);
|
||||||
// higher node wins
|
if (heightDiff != 0)
|
||||||
int heightDiff = (int) (o2.height - o1.height);
|
return heightDiff;
|
||||||
if (Math.abs(heightDiff) > Dispatcher.HEIGHT_WINDOW)
|
// if they are equal, faster node wins
|
||||||
return heightDiff;
|
return (int) Math.signum(o1.responseTime - o2.responseTime);
|
||||||
// if they are (nearly) equal, faster node wins
|
|
||||||
return (int) Math.signum(o1.responseTime - o2.responseTime);
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
return 1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return 1;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -192,7 +181,7 @@ public class NodeInfo extends Node {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final int HTTP_TIMEOUT = OkHttpHelper.HTTP_TIMEOUT;
|
private static final int HTTP_TIMEOUT = OkHttpHelper.HTTP_TIMEOUT;
|
||||||
public static final double PING_GOOD = HTTP_TIMEOUT / 3; //ms
|
public static final double PING_GOOD = HTTP_TIMEOUT / 3.0; //ms
|
||||||
public static final double PING_MEDIUM = 2 * PING_GOOD; //ms
|
public static final double PING_MEDIUM = 2 * PING_GOOD; //ms
|
||||||
public static final double PING_BAD = HTTP_TIMEOUT;
|
public static final double PING_BAD = HTTP_TIMEOUT;
|
||||||
|
|
||||||
@@ -200,7 +189,15 @@ public class NodeInfo extends Node {
|
|||||||
return testRpcService(rpcPort);
|
return testRpcService(rpcPort);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean testRpcService(NodePinger.Listener listener) {
|
||||||
|
boolean result = testRpcService(rpcPort);
|
||||||
|
if (listener != null)
|
||||||
|
listener.publish(this);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean testRpcService(int port) {
|
private boolean testRpcService(int port) {
|
||||||
|
Timber.d("Testing %s", toNodeString());
|
||||||
clear();
|
clear();
|
||||||
try {
|
try {
|
||||||
OkHttpClient client = OkHttpHelper.getEagerClient();
|
OkHttpClient client = OkHttpHelper.getEagerClient();
|
||||||
@@ -230,8 +227,7 @@ public class NodeInfo extends Node {
|
|||||||
if (response.isSuccessful()) {
|
if (response.isSuccessful()) {
|
||||||
ResponseBody respBody = response.body(); // closed through Response object
|
ResponseBody respBody = response.body(); // closed through Response object
|
||||||
if ((respBody != null) && (respBody.contentLength() < 2000)) { // sanity check
|
if ((respBody != null) && (respBody.contentLength() < 2000)) { // sanity check
|
||||||
final JSONObject json = new JSONObject(
|
final JSONObject json = new JSONObject(respBody.string());
|
||||||
respBody.string());
|
|
||||||
String rpcVersion = json.getString("jsonrpc");
|
String rpcVersion = json.getString("jsonrpc");
|
||||||
if (!RPC_VERSION.equals(rpcVersion))
|
if (!RPC_VERSION.equals(rpcVersion))
|
||||||
return false;
|
return false;
|
||||||
@@ -247,8 +243,9 @@ public class NodeInfo extends Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException | JSONException ex) {
|
} catch (IOException | JSONException ex) {
|
||||||
// failure
|
Timber.d(ex);
|
||||||
Timber.d(ex.getMessage());
|
} finally {
|
||||||
|
tested = true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
59
app/src/main/java/com/m2049r/xmrwallet/data/Subaddress.java
Normal file
59
app/src/main/java/com/m2049r/xmrwallet/data/Subaddress.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (c) 2018 m2049r
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.m2049r.xmrwallet.data;
|
||||||
|
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@AllArgsConstructor
|
||||||
|
@ToString
|
||||||
|
@EqualsAndHashCode
|
||||||
|
public class Subaddress implements Comparable<Subaddress> {
|
||||||
|
@Getter
|
||||||
|
final private int accountIndex;
|
||||||
|
@Getter
|
||||||
|
final private int addressIndex;
|
||||||
|
@Getter
|
||||||
|
final private String address;
|
||||||
|
@Getter
|
||||||
|
private final String label;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int compareTo(Subaddress another) { // newer is <
|
||||||
|
final int compareAccountIndex = another.accountIndex - accountIndex;
|
||||||
|
if (compareAccountIndex == 0)
|
||||||
|
return another.addressIndex - addressIndex;
|
||||||
|
return compareAccountIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSquashedAddress() {
|
||||||
|
return address.substring(0, 8) + "…" + address.substring(address.length() - 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final Pattern DEFAULT_LABEL_FORMATTER = Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}$");
|
||||||
|
|
||||||
|
public String getDisplayLabel() {
|
||||||
|
if (label.isEmpty() || (DEFAULT_LABEL_FORMATTER.matcher(label).matches()))
|
||||||
|
return ("#" + addressIndex);
|
||||||
|
else
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
}
|
@@ -20,6 +20,7 @@ import android.os.Parcel;
|
|||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||||
|
import com.m2049r.xmrwallet.model.Wallet;
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
|
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
|
||||||
public class TxData implements Parcelable {
|
public class TxData implements Parcelable {
|
||||||
@@ -52,6 +53,10 @@ public class TxData implements Parcelable {
|
|||||||
return amount;
|
return amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getAmountAsDouble() {
|
||||||
|
return 1.0 * amount / 1000000000000L;
|
||||||
|
}
|
||||||
|
|
||||||
public int getMixin() {
|
public int getMixin() {
|
||||||
return mixin;
|
return mixin;
|
||||||
}
|
}
|
||||||
@@ -68,6 +73,10 @@ public class TxData implements Parcelable {
|
|||||||
this.amount = amount;
|
this.amount = amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAmount(double amount) {
|
||||||
|
this.amount = Wallet.getAmountFromDouble(amount);
|
||||||
|
}
|
||||||
|
|
||||||
public void setMixin(int mixin) {
|
public void setMixin(int mixin) {
|
||||||
this.mixin = mixin;
|
this.mixin = mixin;
|
||||||
}
|
}
|
||||||
|
@@ -18,11 +18,23 @@ package com.m2049r.xmrwallet.data;
|
|||||||
|
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
|
|
||||||
public class TxDataBtc extends TxData {
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
private String xmrtoUuid;
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
|
||||||
|
public class TxDataBtc extends TxData {
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private String btcSymbol; // the actual non-XMR thing we're sending
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
private String xmrtoOrderId; // shown in success screen
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
private String btcAddress;
|
private String btcAddress;
|
||||||
private String bip70;
|
@Getter
|
||||||
|
@Setter
|
||||||
private double btcAmount;
|
private double btcAmount;
|
||||||
|
|
||||||
public TxDataBtc() {
|
public TxDataBtc() {
|
||||||
@@ -33,44 +45,12 @@ public class TxDataBtc extends TxData {
|
|||||||
super(txDataBtc);
|
super(txDataBtc);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getXmrtoUuid() {
|
|
||||||
return xmrtoUuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setXmrtoUuid(String xmrtoUuid) {
|
|
||||||
this.xmrtoUuid = xmrtoUuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBtcAddress() {
|
|
||||||
return btcAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBtcAddress(String btcAddress) {
|
|
||||||
this.btcAddress = btcAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBip70() {
|
|
||||||
return bip70;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBip70(String bip70) {
|
|
||||||
this.bip70 = bip70;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getBtcAmount() {
|
|
||||||
return btcAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setBtcAmount(double btcAmount) {
|
|
||||||
this.btcAmount = btcAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void writeToParcel(Parcel out, int flags) {
|
public void writeToParcel(Parcel out, int flags) {
|
||||||
super.writeToParcel(out, flags);
|
super.writeToParcel(out, flags);
|
||||||
out.writeString(xmrtoUuid);
|
out.writeString(btcSymbol);
|
||||||
|
out.writeString(xmrtoOrderId);
|
||||||
out.writeString(btcAddress);
|
out.writeString(btcAddress);
|
||||||
out.writeString(bip70);
|
|
||||||
out.writeDouble(btcAmount);
|
out.writeDouble(btcAmount);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,23 +67,35 @@ public class TxDataBtc extends TxData {
|
|||||||
|
|
||||||
protected TxDataBtc(Parcel in) {
|
protected TxDataBtc(Parcel in) {
|
||||||
super(in);
|
super(in);
|
||||||
xmrtoUuid = in.readString();
|
btcSymbol = in.readString();
|
||||||
|
xmrtoOrderId = in.readString();
|
||||||
btcAddress = in.readString();
|
btcAddress = in.readString();
|
||||||
bip70 = in.readString();
|
|
||||||
btcAmount = in.readDouble();
|
btcAmount = in.readDouble();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(",xmrtoUuid:");
|
sb.append("xmrtoOrderId:");
|
||||||
sb.append(xmrtoUuid);
|
sb.append(xmrtoOrderId);
|
||||||
|
sb.append(",btcSymbol:");
|
||||||
|
sb.append(btcSymbol);
|
||||||
sb.append(",btcAddress:");
|
sb.append(",btcAddress:");
|
||||||
sb.append(btcAddress);
|
sb.append(btcAddress);
|
||||||
sb.append(",bip70:");
|
|
||||||
sb.append(bip70);
|
|
||||||
sb.append(",btcAmount:");
|
sb.append(",btcAmount:");
|
||||||
sb.append(btcAmount);
|
sb.append(btcAmount);
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean validateAddress(@NonNull String address) {
|
||||||
|
if ((btcSymbol == null) || (btcAddress == null)) return false;
|
||||||
|
final Crypto crypto = Crypto.withSymbol(btcSymbol);
|
||||||
|
if (crypto == null) return false;
|
||||||
|
if (crypto.isCasefull()) { // compare as-is
|
||||||
|
return address.equals(btcAddress);
|
||||||
|
} else { // normalize & compare (e.g. ETH with and without checksum capitals
|
||||||
|
return address.toLowerCase().equals(btcAddress.toLowerCase());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -16,18 +16,19 @@
|
|||||||
|
|
||||||
package com.m2049r.xmrwallet.data;
|
package com.m2049r.xmrwallet.data;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.xmrto.api.QueryOrderStatus;
|
import com.m2049r.xmrwallet.service.shift.sideshift.api.CreateOrder;
|
||||||
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class UserNotes {
|
public class UserNotes {
|
||||||
public String txNotes = "";
|
public String txNotes = "";
|
||||||
public String note = "";
|
public String note = "";
|
||||||
|
public String xmrtoTag = null;
|
||||||
public String xmrtoKey = null;
|
public String xmrtoKey = null;
|
||||||
public String xmrtoAmount = null; // could be a double - but we are not doing any calculations
|
public String xmrtoAmount = null; // could be a double - but we are not doing any calculations
|
||||||
|
public String xmrtoCurrency = null;
|
||||||
public String xmrtoDestination = null;
|
public String xmrtoDestination = null;
|
||||||
|
|
||||||
public UserNotes(final String txNotes) {
|
public UserNotes(final String txNotes) {
|
||||||
@@ -35,13 +36,15 @@ public class UserNotes {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.txNotes = txNotes;
|
this.txNotes = txNotes;
|
||||||
Pattern p = Pattern.compile("^\\{(xmrto-\\w{6}),([0-9.]*)BTC,(\\w*)\\} ?(.*)");
|
Pattern p = Pattern.compile("^\\{([a-z]+)-(\\w{6,}),([0-9.]*)([A-Z]+),(\\w*)\\} ?(.*)");
|
||||||
Matcher m = p.matcher(txNotes);
|
Matcher m = p.matcher(txNotes);
|
||||||
if (m.find()) {
|
if (m.find()) {
|
||||||
xmrtoKey = m.group(1);
|
xmrtoTag = m.group(1);
|
||||||
xmrtoAmount = m.group(2);
|
xmrtoKey = m.group(2);
|
||||||
xmrtoDestination = m.group(3);
|
xmrtoAmount = m.group(3);
|
||||||
note = m.group(4);
|
xmrtoCurrency = m.group(4);
|
||||||
|
xmrtoDestination = m.group(5);
|
||||||
|
note = m.group(6);
|
||||||
} else {
|
} else {
|
||||||
note = txNotes;
|
note = txNotes;
|
||||||
}
|
}
|
||||||
@@ -56,12 +59,15 @@ public class UserNotes {
|
|||||||
txNotes = buildTxNote();
|
txNotes = buildTxNote();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setXmrtoStatus(QueryOrderStatus xmrtoStatus) {
|
public void setXmrtoOrder(CreateOrder order) {
|
||||||
if (xmrtoStatus != null) {
|
if (order != null) {
|
||||||
xmrtoKey = xmrtoStatus.getUuid();
|
xmrtoTag = order.TAG;
|
||||||
xmrtoAmount = String.valueOf(xmrtoStatus.getBtcAmount());
|
xmrtoKey = order.getOrderId();
|
||||||
xmrtoDestination = xmrtoStatus.getBtcDestAddress();
|
xmrtoAmount = Helper.getDisplayAmount(order.getBtcAmount());
|
||||||
|
xmrtoCurrency = order.getBtcCurrency();
|
||||||
|
xmrtoDestination = order.getBtcAddress();
|
||||||
} else {
|
} else {
|
||||||
|
xmrtoTag = null;
|
||||||
xmrtoKey = null;
|
xmrtoKey = null;
|
||||||
xmrtoAmount = null;
|
xmrtoAmount = null;
|
||||||
xmrtoDestination = null;
|
xmrtoDestination = null;
|
||||||
@@ -70,15 +76,18 @@ public class UserNotes {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String buildTxNote() {
|
private String buildTxNote() {
|
||||||
StringBuffer sb = new StringBuffer();
|
StringBuilder sb = new StringBuilder();
|
||||||
if (xmrtoKey != null) {
|
if (xmrtoKey != null) {
|
||||||
if ((xmrtoAmount == null) || (xmrtoDestination == null))
|
if ((xmrtoAmount == null) || (xmrtoDestination == null))
|
||||||
throw new IllegalArgumentException("Broken notes");
|
throw new IllegalArgumentException("Broken notes");
|
||||||
sb.append("{");
|
sb.append("{");
|
||||||
|
sb.append(xmrtoTag);
|
||||||
|
sb.append("-");
|
||||||
sb.append(xmrtoKey);
|
sb.append(xmrtoKey);
|
||||||
sb.append(",");
|
sb.append(",");
|
||||||
sb.append(xmrtoAmount);
|
sb.append(xmrtoAmount);
|
||||||
sb.append("BTC,");
|
sb.append(xmrtoCurrency);
|
||||||
|
sb.append(",");
|
||||||
sb.append(xmrtoDestination);
|
sb.append(xmrtoDestination);
|
||||||
sb.append("}");
|
sb.append("}");
|
||||||
if ((note != null) && (!note.isEmpty()))
|
if ((note != null) && (!note.isEmpty()))
|
||||||
|
@@ -27,7 +27,6 @@ import android.widget.TextView;
|
|||||||
|
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import com.m2049r.xmrwallet.BuildConfig;
|
|
||||||
import com.m2049r.xmrwallet.R;
|
import com.m2049r.xmrwallet.R;
|
||||||
import com.m2049r.xmrwallet.util.Helper;
|
import com.m2049r.xmrwallet.util.Helper;
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user