1
mirror of https://github.com/m2049r/xmrwallet synced 2025-09-03 08:23:04 +02:00

Compare commits

...

53 Commits

Author SHA1 Message Date
m2049r
1d027c1694 fix translation 2021-04-18 19:39:00 +02:00
Vlad
45dc21fbf7 Update Romanian translation (#739)
Co-authored-by: m2049r <m2049r@monerujo.io>
2021-04-18 19:19:42 +02:00
netrik182
d4f4de234a pt-br translation of new strings (#735) 2021-04-18 19:17:29 +02:00
m2049r
394d5471e3 show total incoming amount (#745) 2021-04-18 19:16:52 +02:00
m2049r
beb098adb3 build version 2021-04-18 17:38:28 +02:00
m2049r
fda370d35b bump version 2021-04-18 16:57:58 +02:00
m2049r
99681e1bbb fix permissions Q needs to read our old wallet files (#744) 2021-04-18 16:55:14 +02:00
m2049r
21f44380b1 remove persmissions 2021-04-18 13:29:16 +02:00
m2049r
f4c1af1bb8 Some refactoring (#742)
* configure enabling of shift & exchange
* refactor qr logo code
* fix rename wallet
2021-04-17 21:03:32 +02:00
m2049r
c002b81ebd use default mixin 2021-04-15 19:10:56 +02:00
m2049r
d2f07ba3b6 migrate to scoped storage and API 30 (#741) 2021-04-15 19:10:01 +02:00
m2049r
24fc27b09e show new incoming tx in subaddress details (#734) 2021-03-23 19:05:30 +01:00
m2049r
cf4ff856d5 Subaddresses as first class citizens (#733) 2021-03-22 00:31:01 +01:00
m2049r
b01de1ad6e bump version 2021-03-20 09:14:51 +01:00
anhdres
5fb1bcb552 Update strings.xml (#732)
several gender mistakes ("monedero" we're using now is masculine)
ledger translations
onboarding
sideshift integration
2021-03-19 23:38:03 +01:00
anhdres
b937ba38b6 Update help.xml (#731)
translated new text and help_ok string
2021-03-19 23:36:32 +01:00
m2049r
849718fdc7 bump version 2021-03-15 09:38:01 +01:00
m2049r
a6372f701d Merge pull request #730 from m2049r/feature_sr_transitions_s
Send & Receive UI transitions
2021-03-14 21:24:18 +01:00
m2049r
12f135bb14 refactoring 2021-03-14 21:22:49 +01:00
m2049r
16870fcbb9 Receive & send transition 2021-03-14 21:22:37 +01:00
m2049r
2fbd152fb3 bump version 2021-03-13 13:16:26 +01:00
m2049r
a7b178e024 Tweaking UI & some bugfixing on the way (#729) 2021-03-13 13:12:46 +01:00
m2049r
c5a035437b bump version 2021-03-12 20:16:05 +01:00
m2049r
75c550fd19 add copyright notice 2021-03-12 20:02:30 +01:00
m2049r
1b680344b1 call fail() if password cancelled (#726) 2021-03-12 19:57:13 +01:00
m2049r
3329636d32 adjust areContentsTheSame criteria 2021-03-12 19:56:42 +01:00
m2049r
77e9bf7c43 fix Node equality 2021-03-12 19:56:42 +01:00
m2049r
40f5c9365e Merge pull request #725 from m2049r/fix_recyclerdiff
Fix transactions not updating when switching accounts
2021-03-12 01:00:21 +01:00
m2049r
850ba7efef clean whitespace & style 2021-03-12 00:57:22 +01:00
m2049r
548369ca0b fix updating when transfers between accounts 2021-03-12 00:49:12 +01:00
yorha-0x
afc0d5b7bc Update recyclerviews with diffutil (#711)
Remove modified imports

Co-authored-by: m2049r <m2049r@monerujo.io>
2021-03-11 20:57:19 +01:00
m2049r
02e92db59a bump version 2021-03-10 19:41:39 +01:00
m2049r
2b34454214 Fix restoreheights prior to 2014 (#723)
* fix pre-2014 dates
* update heights
2021-03-10 19:29:10 +01:00
Lafudoci
12706119d3 Update zh-rTW translation (#719) 2021-03-10 19:27:49 +01:00
m2049r
f7a762f32b show passphrase strength with icons (#722) 2021-03-10 19:13:30 +01:00
m2049r
86d5d2a2cb Update BUILDING-external-libs.md 2021-02-21 23:01:48 +01:00
m2049r
e56369b91a fix tests (#717) 2021-02-19 17:25:11 +01:00
m2049r
b04aa24269 bump version 2021-02-19 16:35:02 +01:00
m2049r
dda86bd5de fix URI generation (#716) 2021-02-19 16:32:15 +01:00
m2049r
a19ad7fd52 change build to docker (#715) 2021-02-19 16:31:35 +01:00
m2049r
0443fd808c bump version (consolidating with play release) 2021-02-17 00:16:24 +01:00
m2049r
faf57c96fc add more output currencies (#714) 2021-02-15 21:38:26 +01:00
m2049r
57ddddfce2 replace xmr.to with SideShift.ai (#710)
replace xmr.to with SideShift.ai
random bugfixes
upgrade gradle & dependencies
2021-02-13 00:01:19 +01:00
netrik182
ab6069058b new pt-br strings after review (#708) 2021-02-01 23:02:36 +01:00
m2049r
d94d6e6925 downgrade to APK 29 & bump version 2020-11-22 19:39:30 +01:00
m2049r
4a819cc159 bump version 2020-11-21 22:18:36 +01:00
m2049r
f40c3d6c6d catch cases where the qr code produces a null monero address (#702) 2020-11-21 22:08:23 +01:00
m2049r
82b25df7ad better node selection (#701) 2020-11-21 21:54:21 +01:00
m2049r
08f815e830 don't show connection error if we havent tested yet (#700) 2020-11-21 13:22:54 +01:00
m2049r
4e59be2dff New handling of nodes (#699)
* rework node handling

* update block heights
2020-11-19 16:36:01 +01:00
m2049r
45c5883e11 fix potential NPE in updateAccountsList() 2020-10-20 23:47:41 +02:00
m2049r
067a23e6a5 fix crash if back before new address is generated 2020-10-20 23:40:55 +02:00
m2049r
d21fd41c44 update dependencies & fix MethodNotFound exception 2020-10-20 23:31:29 +02:00
267 changed files with 9921 additions and 7849 deletions

1
.gitignore vendored
View File

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

View File

@@ -13,7 +13,7 @@ set(EXTERNAL_LIBS_DIR ${CMAKE_SOURCE_DIR}/../external-libs)
add_library(sodium STATIC IMPORTED)
set_target_properties(sodium PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/libsodium/lib/${ANDROID_ABI}/libsodium.a)
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libsodium.a)
############
# OpenSSL
@@ -21,11 +21,11 @@ set_target_properties(sodium PROPERTIES IMPORTED_LOCATION
add_library(crypto STATIC IMPORTED)
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)
set_target_properties(ssl PROPERTIES IMPORTED_LOCATION
${EXTERNAL_LIBS_DIR}/openssl/lib/${ANDROID_ABI}/libssl.a)
${EXTERNAL_LIBS_DIR}/${ANDROID_ABI}/libssl.a)
############
# Boost
@@ -33,39 +33,39 @@ set_target_properties(ssl PROPERTIES IMPORTED_LOCATION
add_library(boost_chrono STATIC IMPORTED)
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)
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)
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)
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)
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)
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)
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)
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)
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
@@ -73,99 +73,103 @@ set_target_properties(boost_wserialization PROPERTIES IMPORTED_LOCATION
add_library(wallet_api STATIC IMPORTED)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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)
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
@@ -173,10 +177,16 @@ set_target_properties(rpc_base PROPERTIES IMPORTED_LOCATION
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})
if(${ANDROID_ABI} STREQUAL "x86_64")
set(EXTRA_LIBS "wallet-crypto")
else()
set(EXTRA_LIBS "")
endif()
target_link_libraries( monerujo
wallet_api
@@ -203,6 +213,7 @@ target_link_libraries( monerujo
randomx
hardforks
rpc_base
${EXTRA_LIBS}
boost_chrono
boost_date_time

View File

@@ -1,14 +1,14 @@
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
buildToolsVersion '29.0.2'
compileSdkVersion 30
buildToolsVersion '29.0.3'
defaultConfig {
applicationId "com.m2049r.xmrwallet"
minSdkVersion 21
targetSdkVersion 28
versionCode 503
versionName "1.15.3 'Dark Fork'"
targetSdkVersion 30
versionCode 1001
versionName "2.0.1 'Puginarug'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
@@ -56,6 +56,9 @@ android {
debug {
applicationIdSuffix ".debug"
}
applicationVariants.all { variant ->
variant.buildConfigField "String", "ID_A", "\"" + getId("ID_A") + "\""
}
}
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 {
implementation 'androidx.core:core:1.3.2'
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.recyclerview:recyclerview:1.1.0'
implementation 'androidx.cardview:cardview:1.0.0'
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 "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
implementation "com.burgstaller:okhttp-digest:2.0"
implementation "com.jakewharton.timber:timber:$rootProject.ext.timberVersion"
implementation "com.squareup.okhttp3:okhttp:4.9.0"
implementation "com.burgstaller:okhttp-digest:2.1"
implementation "com.jakewharton.timber:timber:4.7.1"
implementation 'com.nulab-inc:zxcvbn:1.3.0'
@@ -131,12 +140,14 @@ dependencies {
implementation 'org.slf4j:slf4j-nop:1.7.30'
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
// https://mvnrepository.com/artifact/com.github.aelstad/keccakj
implementation 'com.github.aelstad:keccakj:1.1.0'
testImplementation "junit:junit:$rootProject.ext.junitVersion"
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 'net.jodah:concurrentunit:0.4.4'
compileOnly 'org.projectlombok:lombok:1.18.16'
annotationProcessor 'org.projectlombok:lombok:1.18.16'
}

View File

@@ -4,15 +4,14 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
<uses-permission android:name="android.permission.NFC" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:preserveLegacyExternalStorage="true"
android:name=".XmrWalletApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"

View File

@@ -185,7 +185,6 @@ public class Dispatcher implements PeerRetriever.OnGetPeers {
public void seedPeers(Collection<NodeInfo> seedNodes) {
for (NodeInfo node : seedNodes) {
node.clear();
if (node.isFavourite()) {
rpcNodes.add(node);
if (listener != null) listener.onGet(node);

View File

@@ -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;
import java.net.InetAddress;

View File

@@ -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;
import android.app.PendingIntent;
@@ -15,10 +31,11 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.PowerManager;
import android.widget.Toast;
import androidx.annotation.CallSuper;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import android.widget.Toast;
import com.m2049r.xmrwallet.data.BarcodeData;
import com.m2049r.xmrwallet.dialog.ProgressDialog;
@@ -30,7 +47,8 @@ import java.io.IOException;
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;
@@ -197,7 +215,7 @@ public class BaseActivity extends SecureActivity implements GenerateReviewFragme
if (uri == null) {
Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show();
} else {
BarcodeData bc = BarcodeData.fromQrCode(uri.toString());
BarcodeData bc = BarcodeData.fromString(uri.toString());
if (bc == null)
Toast.makeText(this, getString(R.string.nfc_tag_read_undef), Toast.LENGTH_LONG).show();
else

View File

@@ -50,9 +50,8 @@ import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.RestoreHeight;
import com.m2049r.xmrwallet.util.ledger.Monero;
import com.m2049r.xmrwallet.widget.PasswordEntryView;
import com.m2049r.xmrwallet.widget.Toolbar;
import com.nulabinc.zxcvbn.Strength;
import com.nulabinc.zxcvbn.Zxcvbn;
import java.io.File;
import java.text.ParseException;
@@ -70,7 +69,7 @@ public class GenerateFragment extends Fragment {
static final String TYPE_VIEWONLY = "view";
private TextInputLayout etWalletName;
private TextInputLayout etWalletPassword;
private PasswordEntryView etWalletPassword;
private LinearLayout llFingerprintAuth;
private TextInputLayout etWalletAddress;
private TextInputLayout etWalletMnemonic;
@@ -131,21 +130,6 @@ public class GenerateFragment extends Fragment {
});
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() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
@@ -354,45 +338,10 @@ public class GenerateFragment extends Fragment {
});
etWalletName.requestFocus();
initZxcvbn();
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() {
String name = etWalletName.getEditText().getText().toString();
boolean ok = true;
@@ -429,7 +378,7 @@ public class GenerateFragment extends Fragment {
}
private long getHeight() {
long height = 0;
long height = -1;
String restoreHeight = etWalletRestoreHeight.getEditText().getText().toString().trim();
if (restoreHeight.isEmpty()) return -1;
@@ -440,7 +389,7 @@ public class GenerateFragment extends Fragment {
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
} catch (ParseException ex) {
}
if ((height <= 0) && (restoreHeight.length() == 8))
if ((height < 0) && (restoreHeight.length() == 8))
try {
// is it a date without dashes?
SimpleDateFormat parser = new SimpleDateFormat("yyyyMMdd");
@@ -448,7 +397,7 @@ public class GenerateFragment extends Fragment {
height = RestoreHeight.getInstance().getHeight(parser.parse(restoreHeight));
} catch (ParseException ex) {
}
if (height <= 0)
if (height < 0)
try {
// or is it a height?
height = Long.parseLong(restoreHeight);
@@ -654,24 +603,7 @@ public class GenerateFragment extends Fragment {
final TextInputLayout etSeed = promptsView.findViewById(R.id.etSeed);
final TextInputLayout etPassphrase = promptsView.findViewById(R.id.etPassphrase);
etSeed.getEditText().addTextChangedListener(new TextWatcher() {
@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) {
}
});
clearErrorOnTextEntry(etSeed);
alertDialogBuilder
.setCancelable(false)

View File

@@ -56,6 +56,7 @@ import com.m2049r.xmrwallet.util.FingerprintHelper;
import com.m2049r.xmrwallet.util.Helper;
import com.m2049r.xmrwallet.util.KeyStoreHelper;
import com.m2049r.xmrwallet.util.MoneroThreadPoolExecutor;
import com.m2049r.xmrwallet.widget.PasswordEntryView;
import com.m2049r.xmrwallet.widget.Toolbar;
import java.text.NumberFormat;
@@ -306,7 +307,6 @@ public class GenerateReviewFragment extends Fragment {
void dismissProgressDialog();
}
public interface AcceptListener {
void onAccept(String name, String password);
}
@@ -473,7 +473,7 @@ public class GenerateReviewFragment extends Fragment {
AlertDialog.Builder alertDialogBuilder = new MaterialAlertDialogBuilder(getActivity());
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));
final TextInputLayout etPasswordB = promptsView.findViewById(R.id.etWalletPasswordB);
@@ -509,9 +509,6 @@ public class GenerateReviewFragment extends Fragment {
etPasswordA.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (etPasswordA.getError() != null) {
etPasswordA.setError(null);
}
if (etPasswordB.getError() != null) {
etPasswordB.setError(null);
}
@@ -531,9 +528,6 @@ public class GenerateReviewFragment extends Fragment {
etPasswordB.getEditText().addTextChangedListener(new TextWatcher() {
@Override
public void afterTextChanged(Editable s) {
if (etPasswordA.getError() != null) {
etPasswordA.setError(null);
}
if (etPasswordB.getError() != null) {
etPasswordB.setError(null);
}
@@ -564,29 +558,20 @@ public class GenerateReviewFragment extends Fragment {
});
openDialog = alertDialogBuilder.create();
openDialog.setOnShowListener(new DialogInterface.OnShowListener() {
@Override
public void onShow(final DialogInterface dialog) {
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String newPasswordA = etPasswordA.getEditText().getText().toString();
String newPasswordB = etPasswordB.getEditText().getText().toString();
// disallow empty passwords
if (newPasswordA.isEmpty()) {
etPasswordA.setError(getString(R.string.generate_empty_passwordB));
} 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;
}
}
});
}
openDialog.setOnShowListener(dialog -> {
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
button.setOnClickListener(view -> {
String newPasswordA = etPasswordA.getEditText().getText().toString();
String newPasswordB = etPasswordB.getEditText().getText().toString();
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"
@@ -597,9 +582,7 @@ public class GenerateReviewFragment extends Fragment {
String newPasswordA = etPasswordA.getEditText().getText().toString();
String newPasswordB = etPasswordB.getEditText().getText().toString();
// disallow empty passwords
if (newPasswordA.isEmpty()) {
etPasswordA.setError(getString(R.string.generate_empty_passwordB));
} else if (!newPasswordA.equals(newPasswordB)) {
if (!newPasswordA.equals(newPasswordB)) {
etPasswordB.setError(getString(R.string.generate_bad_passwordB));
} else {
new AsyncChangePassword().execute(newPasswordA, Boolean.toString(swFingerprintAllowed.isChecked()));

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -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.
// * I don't know why this is the case but I don't have the time to figure out.
Handler handler = new Handler();
handler.postDelayed(new
Runnable() {
@Override
public void run() {
mScannerView.resumeCameraPreview(ScannerFragment.this);
}
}, 2000);
handler.postDelayed(() -> mScannerView.resumeCameraPreview(ScannerFragment.this), 2000);
}
@Override

View File

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

View File

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

View File

@@ -16,15 +16,21 @@
package com.m2049r.xmrwallet;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.graphics.Paint;
import android.net.Uri;
import android.os.Bundle;
import android.text.Html;
import android.text.InputType;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@@ -32,11 +38,15 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
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.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Transfer;
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.text.SimpleDateFormat;
@@ -46,6 +56,8 @@ import java.util.HashSet;
import java.util.Set;
import java.util.TimeZone;
import timber.log.Timber;
public class TxFragment extends Fragment {
static public final String ARG_INFO = "info";
@@ -77,17 +89,28 @@ public class TxFragment extends Fragment {
private TextView tvTxXmrToKey;
private TextView tvDestinationBtc;
private TextView tvTxAmountBtc;
private TextView tvXmrToSupport;
private TextView tvXmrToKeyLabel;
private ImageView tvXmrToLogo;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_tx_info, container, false);
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);
tvTxXmrToKey = view.findViewById(R.id.tvTxXmrToKey);
tvDestinationBtc = view.findViewById(R.id.tvDestinationBtc);
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);
tvAddress = view.findViewById(R.id.tvAddress);
@@ -104,17 +127,13 @@ public class TxFragment extends Fragment {
etTxNotes.setRawInputType(InputType.TYPE_CLASS_TEXT);
tvTxXmrToKey.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
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();
}
tvTxXmrToKey.setOnClickListener(v -> {
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();
TransactionInfo info = args.getParcelable(ARG_INFO);
show(info);
info = getArguments().getParcelable(ARG_INFO);
show();
return view;
}
@@ -183,7 +202,7 @@ public class TxFragment extends Fragment {
TransactionInfo info = null;
UserNotes userNotes = null;
void loadNotes(TransactionInfo info) {
void loadNotes() {
if ((userNotes == null) || (info.notes == null)) {
info.notes = activityCallback.getTxNotes(info.hash);
}
@@ -196,19 +215,28 @@ public class TxFragment extends Fragment {
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) {
info.txKey = activityCallback.getTxKey(info.hash);
}
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));
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
tvAccount.setText(getString(R.string.tx_account_formatted, info.account, info.subaddress));
showSubaddressLabel();
tvAddress.setText(info.address);
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));
}
Set<String> destinations = new HashSet<>();
StringBuffer sb = new StringBuffer();
StringBuffer dstSb = new StringBuffer();
StringBuilder sb = new StringBuilder();
StringBuilder dstSb = new StringBuilder();
if (info.transfers != null) {
boolean newline = false;
for (Transfer transfer : info.transfers) {
@@ -272,23 +300,45 @@ public class TxFragment extends Fragment {
}
} else {
sb.append("-");
dstSb.append(info.direction ==
TransactionInfo.Direction.Direction_In ?
activityCallback.getWalletSubaddress(info.account, info.subaddress) :
dstSb.append(info.direction == TransactionInfo.Direction.Direction_In ?
activityCallback.getWalletSubaddress(info.accountIndex, info.addressIndex).getAddress() :
"-");
}
tvTxTransfers.setText(sb.toString());
tvDestination.setText(dstSb.toString());
this.info = info;
showBtcInfo();
}
@SuppressLint("SetTextI18n")
void showBtcInfo() {
if (userNotes.xmrtoKey != null) {
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);
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 {
cvXmrTo.setVisibility(View.GONE);
}
@@ -298,6 +348,11 @@ public class TxFragment extends Fragment {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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
@@ -309,7 +364,7 @@ public class TxFragment extends Fragment {
Listener activityCallback;
public interface Listener {
String getWalletSubaddress(int accountIndex, int subaddressIndex);
Subaddress getWalletSubaddress(int accountIndex, int subaddressIndex);
String getTxKey(String hash);
@@ -323,6 +378,8 @@ public class TxFragment extends Fragment {
void setSubtitle(String subtitle);
void showSubaddress(View view, final int subaddressIndex);
}
@Override
@@ -347,4 +404,13 @@ public class TxFragment extends Fragment {
Helper.hideKeyboard(getActivity());
super.onPause();
}
@Override
public void onResume() {
super.onResume();
Timber.d("onResume()");
activityCallback.setSubtitle(getString(R.string.tx_title));
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
showSubaddressLabel();
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -16,14 +16,13 @@
package com.m2049r.xmrwallet;
import android.app.Application;
import android.content.Context;
import android.content.res.Configuration;
import android.os.Build;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.model.NetworkType;
import com.m2049r.xmrwallet.util.DayNightMode;
import com.m2049r.xmrwallet.util.LocaleHelper;
import com.m2049r.xmrwallet.util.NightmodeHelper;

File diff suppressed because it is too large Load Diff

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

View File

@@ -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;
}

View File

@@ -26,6 +26,8 @@ import java.net.URLDecoder;
import java.net.URLEncoder;
import java.net.UnknownHostException;
import lombok.Getter;
import lombok.Setter;
import timber.log.Timber;
public class Node {
@@ -33,27 +35,43 @@ public class Node {
static public final String STAGENET = "stagenet";
static public final String TESTNET = "testnet";
@Getter
private String name = null;
@Getter
final private NetworkType networkType;
InetAddress hostAddress;
@Getter
private String host;
@Getter
@Setter
int rpcPort = 0;
private int levinPort = 0;
@Getter
@Setter
private String username = "";
@Getter
@Setter
private String password = "";
@Getter
@Setter
private boolean favourite = false;
@Getter
@Setter
private boolean selected = false;
@Override
public int 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
public boolean equals(Object other) {
if (!(other instanceof Node)) return false;
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) {
@@ -193,7 +211,6 @@ public class Node {
this.levinPort = socketAddress.getPort();
this.username = "";
this.password = "";
//this.name = socketAddress.getHostName(); // triggers DNS so we don't do it by default
}
public String getAddress() {
@@ -204,14 +221,6 @@ public class Node {
return hostAddress.getHostAddress();
}
public String getHost() {
return host;
}
public int getRpcPort() {
return rpcPort;
}
public void setHost(String host) throws UnknownHostException {
if ((host == null) || (host.isEmpty()))
throw new UnknownHostException("loopback not supported (yet?)");
@@ -219,18 +228,6 @@ public class Node {
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() {
if (name == null)
this.name = hostAddress.getHostName();
@@ -243,30 +240,6 @@ public class Node {
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() {
favourite = !favourite;
}

View File

@@ -21,8 +21,8 @@ import com.burgstaller.okhttp.CachingAuthenticatorDecorator;
import com.burgstaller.okhttp.digest.CachingAuthenticator;
import com.burgstaller.okhttp.digest.Credentials;
import com.burgstaller.okhttp.digest.DigestAuthenticator;
import com.m2049r.levin.scanner.Dispatcher;
import com.m2049r.levin.scanner.LevinPeer;
import com.m2049r.xmrwallet.util.NodePinger;
import com.m2049r.xmrwallet.util.OkHttpHelper;
import org.json.JSONException;
@@ -36,6 +36,8 @@ import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import lombok.Getter;
import lombok.Setter;
import okhttp3.HttpUrl;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
@@ -46,14 +48,24 @@ import okhttp3.ResponseBody;
import timber.log.Timber;
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";
@Getter
private long height = 0;
@Getter
private long timestamp = 0;
@Getter
private int majorVersion = 0;
@Getter
private double responseTime = Double.MAX_VALUE;
@Getter
private int responseCode = 0;
@Getter
private boolean tested = false;
@Getter
@Setter
private boolean selecting = false;
public void clear() {
height = 0;
@@ -61,13 +73,13 @@ public class NodeInfo extends Node {
responseTime = Double.MAX_VALUE;
responseCode = 0;
timestamp = 0;
tested = false;
}
static public NodeInfo fromString(String nodeString) {
try {
return new NodeInfo(nodeString);
} catch (IllegalArgumentException ex) {
Timber.w(ex);
return null;
}
}
@@ -113,26 +125,6 @@ public class NodeInfo extends Node {
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() {
return (responseCode >= 200) && (responseCode < 300);
}
@@ -145,23 +137,20 @@ public class NodeInfo extends Node {
return isSuccessful() && (majorVersion >= MIN_MAJOR_VERSION) && (responseTime < Double.MAX_VALUE);
}
static public Comparator<NodeInfo> BestNodeComparator = new Comparator<NodeInfo>() {
@Override
public int compare(NodeInfo o1, NodeInfo o2) {
if (o1.isValid()) {
if (o2.isValid()) { // both are valid
// higher node wins
int heightDiff = (int) (o2.height - o1.height);
if (Math.abs(heightDiff) > Dispatcher.HEIGHT_WINDOW)
return heightDiff;
// if they are (nearly) equal, faster node wins
return (int) Math.signum(o1.responseTime - o2.responseTime);
} else {
return -1;
}
static public Comparator<NodeInfo> BestNodeComparator = (o1, o2) -> {
if (o1.isValid()) {
if (o2.isValid()) { // both are valid
// higher node wins
int heightDiff = (int) (o2.height - o1.height);
if (heightDiff != 0)
return heightDiff;
// if they are equal, faster node wins
return (int) Math.signum(o1.responseTime - o2.responseTime);
} 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;
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_BAD = HTTP_TIMEOUT;
@@ -200,7 +189,15 @@ public class NodeInfo extends Node {
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) {
Timber.d("Testing %s", toNodeString());
clear();
try {
OkHttpClient client = OkHttpHelper.getEagerClient();
@@ -230,8 +227,7 @@ public class NodeInfo extends Node {
if (response.isSuccessful()) {
ResponseBody respBody = response.body(); // closed through Response object
if ((respBody != null) && (respBody.contentLength() < 2000)) { // sanity check
final JSONObject json = new JSONObject(
respBody.string());
final JSONObject json = new JSONObject(respBody.string());
String rpcVersion = json.getString("jsonrpc");
if (!RPC_VERSION.equals(rpcVersion))
return false;
@@ -247,8 +243,9 @@ public class NodeInfo extends Node {
}
}
} catch (IOException | JSONException ex) {
// failure
Timber.d(ex.getMessage());
Timber.d(ex);
} finally {
tested = true;
}
return false;
}

View File

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

View File

@@ -20,6 +20,7 @@ import android.os.Parcel;
import android.os.Parcelable;
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
public class TxData implements Parcelable {
@@ -52,6 +53,10 @@ public class TxData implements Parcelable {
return amount;
}
public double getAmountAsDouble() {
return 1.0 * amount / 1000000000000L;
}
public int getMixin() {
return mixin;
}
@@ -68,6 +73,10 @@ public class TxData implements Parcelable {
this.amount = amount;
}
public void setAmount(double amount) {
this.amount = Wallet.getAmountFromDouble(amount);
}
public void setMixin(int mixin) {
this.mixin = mixin;
}

View File

@@ -18,11 +18,23 @@ package com.m2049r.xmrwallet.data;
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 bip70;
@Getter
@Setter
private double btcAmount;
public TxDataBtc() {
@@ -33,44 +45,12 @@ public class TxDataBtc extends TxData {
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
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeString(xmrtoUuid);
out.writeString(btcSymbol);
out.writeString(xmrtoOrderId);
out.writeString(btcAddress);
out.writeString(bip70);
out.writeDouble(btcAmount);
}
@@ -87,23 +67,35 @@ public class TxDataBtc extends TxData {
protected TxDataBtc(Parcel in) {
super(in);
xmrtoUuid = in.readString();
btcSymbol = in.readString();
xmrtoOrderId = in.readString();
btcAddress = in.readString();
bip70 = in.readString();
btcAmount = in.readDouble();
}
@NonNull
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(",xmrtoUuid:");
sb.append(xmrtoUuid);
sb.append("xmrtoOrderId:");
sb.append(xmrtoOrderId);
sb.append(",btcSymbol:");
sb.append(btcSymbol);
sb.append(",btcAddress:");
sb.append(btcAddress);
sb.append(",bip70:");
sb.append(bip70);
sb.append(",btcAmount:");
sb.append(btcAmount);
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());
}
}
}

View File

@@ -16,18 +16,19 @@
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.Pattern;
import timber.log.Timber;
public class UserNotes {
public String txNotes = "";
public String note = "";
public String xmrtoTag = null;
public String xmrtoKey = null;
public String xmrtoAmount = null; // could be a double - but we are not doing any calculations
public String xmrtoCurrency = null;
public String xmrtoDestination = null;
public UserNotes(final String txNotes) {
@@ -35,13 +36,15 @@ public class UserNotes {
return;
}
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);
if (m.find()) {
xmrtoKey = m.group(1);
xmrtoAmount = m.group(2);
xmrtoDestination = m.group(3);
note = m.group(4);
xmrtoTag = m.group(1);
xmrtoKey = m.group(2);
xmrtoAmount = m.group(3);
xmrtoCurrency = m.group(4);
xmrtoDestination = m.group(5);
note = m.group(6);
} else {
note = txNotes;
}
@@ -56,12 +59,15 @@ public class UserNotes {
txNotes = buildTxNote();
}
public void setXmrtoStatus(QueryOrderStatus xmrtoStatus) {
if (xmrtoStatus != null) {
xmrtoKey = xmrtoStatus.getUuid();
xmrtoAmount = String.valueOf(xmrtoStatus.getBtcAmount());
xmrtoDestination = xmrtoStatus.getBtcDestAddress();
public void setXmrtoOrder(CreateOrder order) {
if (order != null) {
xmrtoTag = order.TAG;
xmrtoKey = order.getOrderId();
xmrtoAmount = Helper.getDisplayAmount(order.getBtcAmount());
xmrtoCurrency = order.getBtcCurrency();
xmrtoDestination = order.getBtcAddress();
} else {
xmrtoTag = null;
xmrtoKey = null;
xmrtoAmount = null;
xmrtoDestination = null;
@@ -70,15 +76,18 @@ public class UserNotes {
}
private String buildTxNote() {
StringBuffer sb = new StringBuffer();
StringBuilder sb = new StringBuilder();
if (xmrtoKey != null) {
if ((xmrtoAmount == null) || (xmrtoDestination == null))
throw new IllegalArgumentException("Broken notes");
sb.append("{");
sb.append(xmrtoTag);
sb.append("-");
sb.append(xmrtoKey);
sb.append(",");
sb.append(xmrtoAmount);
sb.append("BTC,");
sb.append(xmrtoCurrency);
sb.append(",");
sb.append(xmrtoDestination);
sb.append("}");
if ((note != null) && (!note.isEmpty()))

View File

@@ -27,7 +27,6 @@ import android.widget.TextView;
import androidx.appcompat.app.AlertDialog;
import com.m2049r.xmrwallet.BuildConfig;
import com.m2049r.xmrwallet.R;
import com.m2049r.xmrwallet.util.Helper;

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