mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-04 00:53:36 +02:00
Compare commits
29 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
c64fb1a769 | ||
![]() |
2d2a7d2db0 | ||
![]() |
ac9e24ee9f | ||
![]() |
059438a09e | ||
![]() |
d22fdfe2dc | ||
![]() |
48577e46aa | ||
![]() |
451371cd92 | ||
![]() |
cd6f646b63 | ||
![]() |
aa530caa28 | ||
![]() |
5df43f1274 | ||
![]() |
3b0cb3ffdb | ||
![]() |
300551dbe4 | ||
![]() |
942519adb7 | ||
![]() |
0652a8ba14 | ||
![]() |
04d0bd2ffb | ||
![]() |
8473e66c69 | ||
![]() |
2218ff615c | ||
![]() |
54b55b9f8f | ||
![]() |
5abf84f62b | ||
![]() |
6ff75d221f | ||
![]() |
c90f107c3c | ||
![]() |
a3db18032e | ||
![]() |
9db2c10679 | ||
![]() |
eab85c7f0a | ||
![]() |
6bf3229e77 | ||
![]() |
9f4f626acb | ||
![]() |
434dab55ba | ||
![]() |
59cc6b1864 | ||
![]() |
a6e9d0e77c |
@@ -3,7 +3,7 @@ jobs:
|
||||
build:
|
||||
working_directory: ~/code
|
||||
docker:
|
||||
- image: cimg/android:2022.03-ndk
|
||||
- image: cimg/android:2023.12-ndk
|
||||
environment:
|
||||
JVM_OPTS: -Xmx3200m
|
||||
steps:
|
||||
|
10
.gitignore
vendored
10
.gitignore
vendored
@@ -8,11 +8,9 @@
|
||||
.DS_Store
|
||||
/app/build
|
||||
/app/release
|
||||
/app/alpha
|
||||
/app/prod
|
||||
/app/alphaMainnet
|
||||
/app/prodMainnet
|
||||
/app/alphaStagenet
|
||||
/app/prodStagenet
|
||||
/app/alpha*
|
||||
/app/prod*
|
||||
/app/.cxx
|
||||
/monerujo.id
|
||||
/external-libs/VERSION
|
||||
/external-libs/include/wallet2_api.h
|
||||
|
@@ -4,12 +4,12 @@ android {
|
||||
ndkVersion '17.2.4988734'
|
||||
defaultConfig {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
buildToolsVersion = '34.0.0'
|
||||
compileSdk 33
|
||||
buildToolsVersion = '35.0.0'
|
||||
compileSdk 35
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 33
|
||||
versionCode 3308
|
||||
versionName "3.3.8 'Pocket Change'"
|
||||
targetSdkVersion 35
|
||||
versionCode 4006
|
||||
versionName "4.0.6 'Sidekick'"
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
@@ -24,7 +24,7 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
flavorDimensions 'type', 'net'
|
||||
flavorDimensions = ['type', 'net']
|
||||
productFlavors {
|
||||
mainnet {
|
||||
dimension 'net'
|
||||
@@ -112,6 +112,17 @@ android {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
namespace 'com.m2049r.xmrwallet'
|
||||
buildFeatures {
|
||||
buildConfig true
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
all {
|
||||
jvmArgs '--add-opens=java.base/java.lang=ALL-UNNAMED'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static def getId(name) {
|
||||
@@ -121,41 +132,40 @@ static def getId(name) {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0"))
|
||||
implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.22"))
|
||||
|
||||
implementation 'androidx.core:core:1.10.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.core:core:1.13.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.1'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
implementation "com.squareup.okhttp3:okhttp:4.9.3"
|
||||
implementation "io.github.rburgst:okhttp-digest:2.6"
|
||||
implementation "com.squareup.okhttp3:okhttp:4.12.0"
|
||||
implementation "io.github.rburgst:okhttp-digest:3.1.0"
|
||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||
|
||||
implementation 'info.guardianproject.netcipher:netcipher:2.1.0'
|
||||
//implementation 'info.guardianproject.netcipher:netcipher-okhttp3:2.1.0'
|
||||
implementation fileTree(dir: 'libs/classes', include: ['*.jar'])
|
||||
implementation 'com.nulab-inc:zxcvbn:1.5.2'
|
||||
implementation 'com.nulab-inc:zxcvbn:1.8.2'
|
||||
|
||||
implementation 'dnsjava:dnsjava:2.1.9'
|
||||
implementation 'org.jitsi:dnssecjava:1.2.0'
|
||||
implementation 'org.slf4j:slf4j-nop:1.7.36'
|
||||
implementation 'dnsjava:dnsjava:3.5.3'
|
||||
implementation 'org.slf4j:slf4j-nop:2.0.11'
|
||||
implementation 'com.github.brnunes:swipeablerecyclerview:1.0.2'
|
||||
|
||||
//noinspection GradleDependency
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
testImplementation "org.mockito:mockito-all:1.10.19"
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.9.3"
|
||||
testImplementation 'org.json:json:20211205'
|
||||
testImplementation "com.squareup.okhttp3:mockwebserver:4.12.0"
|
||||
testImplementation 'org.json:json:20231013'
|
||||
testImplementation 'net.jodah:concurrentunit:0.4.6'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok:1.18.22'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.22'
|
||||
compileOnly 'org.projectlombok:lombok:1.18.30'
|
||||
annotationProcessor 'org.projectlombok:lombok:1.18.30'
|
||||
}
|
||||
|
@@ -5,6 +5,11 @@
|
||||
android:name="android.hardware.camera"
|
||||
android:required="false" />
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
|
||||
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
@@ -12,6 +17,7 @@
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.USE_BIOMETRIC" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE" />
|
||||
|
||||
<queries>
|
||||
<intent>
|
||||
@@ -98,7 +104,12 @@
|
||||
android:name=".service.WalletService"
|
||||
android:description="@string/service_description"
|
||||
android:exported="false"
|
||||
android:label="Monero Wallet Service" />
|
||||
android:foregroundServiceType="specialUse"
|
||||
android:label="Monero Wallet Service">
|
||||
<property
|
||||
android:name="android.app.PROPERTY_SPECIAL_USE_FGS_SUBTYPE"
|
||||
android:value="Keeps app in sync with the blockchain" />
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 m2049r
|
||||
* Copyright (c) 2017-2024 m2049r
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -18,8 +18,6 @@
|
||||
#include "monerujo.h"
|
||||
#include "wallet2_api.h"
|
||||
|
||||
//TODO explicit casting jlong, jint, jboolean to avoid warnings
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
@@ -34,7 +32,6 @@ extern "C"
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR , LOG_TAG,__VA_ARGS__)
|
||||
|
||||
static JavaVM *cachedJVM;
|
||||
static jclass class_String;
|
||||
static jclass class_ArrayList;
|
||||
static jclass class_WalletListener;
|
||||
static jclass class_CoinsInfo;
|
||||
@@ -42,17 +39,11 @@ static jclass class_TransactionInfo;
|
||||
static jclass class_Transfer;
|
||||
static jclass class_Ledger;
|
||||
static jclass class_WalletStatus;
|
||||
static jclass class_BluetoothService;
|
||||
static jclass class_SidekickService;
|
||||
|
||||
std::mutex _listenerMutex;
|
||||
|
||||
//void jstringToString(JNIEnv *env, std::string &str, jstring jstr) {
|
||||
// if (!jstr) return;
|
||||
// const int len = env->GetStringUTFLength(jstr);
|
||||
// const char *chars = env->GetStringUTFChars(jstr, nullptr);
|
||||
// str.assign(chars, len);
|
||||
// env->ReleaseStringUTFChars(jstr, chars);
|
||||
//}
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
|
||||
cachedJVM = jvm;
|
||||
LOGI("JNI_OnLoad");
|
||||
@@ -62,8 +53,6 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
|
||||
}
|
||||
//LOGI("JNI_OnLoad ok");
|
||||
|
||||
class_String = static_cast<jclass>(jenv->NewGlobalRef(
|
||||
jenv->FindClass("java/lang/String")));
|
||||
class_ArrayList = static_cast<jclass>(jenv->NewGlobalRef(
|
||||
jenv->FindClass("java/util/ArrayList")));
|
||||
class_CoinsInfo = static_cast<jclass>(jenv->NewGlobalRef(
|
||||
@@ -78,6 +67,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
|
||||
jenv->FindClass("com/m2049r/xmrwallet/ledger/Ledger")));
|
||||
class_WalletStatus = static_cast<jclass>(jenv->NewGlobalRef(
|
||||
jenv->FindClass("com/m2049r/xmrwallet/model/Wallet$Status")));
|
||||
class_BluetoothService = static_cast<jclass>(jenv->NewGlobalRef(
|
||||
jenv->FindClass("com/m2049r/xmrwallet/service/BluetoothService")));
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
#ifdef __cplusplus
|
||||
@@ -1686,6 +1677,79 @@ int LedgerFind(char *buffer, size_t len) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
//
|
||||
// SidekickWallet Stuff
|
||||
//
|
||||
|
||||
/**
|
||||
* @brief BtExchange - exchange data with Monerujo Device
|
||||
* @param request - buffer for data to send
|
||||
* @param request_len - length of data to send
|
||||
* @param response - buffer for received data
|
||||
* @param max_resp_len - size of receive buffer
|
||||
*
|
||||
* @return length of received data in response or -1 if error, -2 if response buffer too small
|
||||
*/
|
||||
int BtExchange(
|
||||
unsigned char *request,
|
||||
unsigned int request_len,
|
||||
unsigned char *response,
|
||||
unsigned int max_resp_len) {
|
||||
JNIEnv *jenv;
|
||||
int envStat = attachJVM(&jenv);
|
||||
if (envStat == JNI_ERR) return -16;
|
||||
|
||||
jmethodID exchangeMethod = jenv->GetStaticMethodID(class_BluetoothService, "Exchange",
|
||||
"([B)[B");
|
||||
|
||||
auto reqLen = static_cast<jsize>(request_len);
|
||||
jbyteArray reqData = jenv->NewByteArray(reqLen);
|
||||
jenv->SetByteArrayRegion(reqData, 0, reqLen, (jbyte *) request);
|
||||
LOGD("BtExchange cmd: 0x%02x with %u bytes", request[0], reqLen);
|
||||
auto dataRecv = (jbyteArray)
|
||||
jenv->CallStaticObjectMethod(class_BluetoothService, exchangeMethod, reqData);
|
||||
jenv->DeleteLocalRef(reqData);
|
||||
if (dataRecv == nullptr) {
|
||||
detachJVM(jenv, envStat);
|
||||
LOGD("BtExchange: error reading");
|
||||
return -1;
|
||||
}
|
||||
jsize respLen = jenv->GetArrayLength(dataRecv);
|
||||
LOGD("BtExchange response is %u bytes", respLen);
|
||||
if (respLen <= max_resp_len) {
|
||||
jenv->GetByteArrayRegion(dataRecv, 0, respLen, (jbyte *) response);
|
||||
jenv->DeleteLocalRef(dataRecv);
|
||||
detachJVM(jenv, envStat);
|
||||
return static_cast<int>(respLen);;
|
||||
} else {
|
||||
jenv->DeleteLocalRef(dataRecv);
|
||||
detachJVM(jenv, envStat);
|
||||
LOGE("BtExchange response buffer too small: %u < %u", respLen, max_resp_len);
|
||||
return -2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ConfirmTransfers
|
||||
* @param transfers - string of "fee (':' address ':' amount)+"
|
||||
*
|
||||
* @return true on accept, false on reject
|
||||
*/
|
||||
bool ConfirmTransfers(const char *transfers) {
|
||||
JNIEnv *jenv;
|
||||
int envStat = attachJVM(&jenv);
|
||||
if (envStat == JNI_ERR) return -16;
|
||||
|
||||
jmethodID confirmMethod = jenv->GetStaticMethodID(class_SidekickService, "ConfirmTransfers",
|
||||
"(Ljava/lang/String;)Z");
|
||||
|
||||
jstring _transfers = jenv->NewStringUTF(transfers);
|
||||
auto confirmed =
|
||||
jenv->CallStaticBooleanMethod(class_SidekickService, confirmMethod, _transfers);
|
||||
jenv->DeleteLocalRef(_transfers);
|
||||
return confirmed;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/**
|
||||
* Copyright (c) 2017 m2049r
|
||||
* Copyright (c) 2017-2024 m2049r
|
||||
* <p>
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
#include <string>
|
||||
|
||||
/*
|
||||
#include <android/log.h>
|
||||
|
||||
@@ -28,6 +30,10 @@
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
|
||||
*/
|
||||
|
||||
void ThrowException(JNIEnv *jenv, const char* type, const char* msg) {
|
||||
jenv->ThrowNew(jenv->FindClass(type), msg);
|
||||
}
|
||||
|
||||
jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
|
||||
jclass c = env->GetObjectClass(obj);
|
||||
return env->GetFieldID(c, fieldName, "J"); // of type long
|
||||
@@ -35,8 +41,16 @@ jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handl
|
||||
|
||||
template<typename T>
|
||||
T *getHandle(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
|
||||
return reinterpret_cast<T *>(env->GetLongField(obj, getHandleField(env, obj, fieldName)));
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void destroyNativeObject(JNIEnv *env, T nativeObjectHandle, jobject obj, const char *fieldName = "handle") {
|
||||
jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName));
|
||||
return reinterpret_cast<T *>(handle);
|
||||
if (handle != 0) {
|
||||
ThrowException(env, "java/lang/IllegalStateException", "invalid handle (destroy)");
|
||||
}
|
||||
delete reinterpret_cast<T *>(nativeObjectHandle);
|
||||
}
|
||||
|
||||
void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) {
|
||||
@@ -54,7 +68,7 @@ extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
extern const char* const MONERO_VERSION; // the actual monero core version
|
||||
extern const char *const MONERO_VERSION; // the actual monero core version
|
||||
|
||||
// from monero-core crypto/hash-ops.h - avoid #including monero code here
|
||||
enum {
|
||||
@@ -62,18 +76,40 @@ enum {
|
||||
HASH_DATA_AREA = 136
|
||||
};
|
||||
|
||||
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed, uint64_t height);
|
||||
void cn_slow_hash(const void *data, size_t length, char *hash, int variant, int prehashed,
|
||||
uint64_t height);
|
||||
|
||||
inline void slow_hash(const void *data, const size_t length, char *hash) {
|
||||
cn_slow_hash(data, length, hash, 0 /*variant*/, 0 /*prehashed*/, 0 /*height*/);
|
||||
}
|
||||
|
||||
inline void slow_hash_broken(const void *data, char *hash, int variant) {
|
||||
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/, 0 /*height*/);
|
||||
cn_slow_hash(data, 200 /*sizeof(union hash_state)*/, hash, variant, 1 /*prehashed*/,
|
||||
0 /*height*/);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
namespace Monerujo {
|
||||
class SidekickWallet {
|
||||
public:
|
||||
enum Status {
|
||||
Status_Ok,
|
||||
Status_Error,
|
||||
Status_Critical
|
||||
};
|
||||
|
||||
SidekickWallet(uint8_t networkType, std::string a, std::string b);
|
||||
|
||||
~SidekickWallet();
|
||||
|
||||
std::string call(int commandId, const std::string &request);
|
||||
|
||||
void reset();
|
||||
|
||||
Status status() const;
|
||||
|
||||
};
|
||||
}
|
||||
#endif //XMRWALLET_WALLET_LIB_H
|
||||
|
BIN
app/src/main/ic_launcher-playstore.png
Normal file
BIN
app/src/main/ic_launcher-playstore.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
@@ -16,12 +16,26 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
import android.net.NetworkCapabilities;
|
||||
import android.net.NetworkInfo;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.PowerManager;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.CallSuper;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
||||
import com.m2049r.xmrwallet.data.BarcodeData;
|
||||
import com.m2049r.xmrwallet.dialog.ProgressDialog;
|
||||
@@ -35,18 +49,13 @@ public class BaseActivity extends SecureActivity
|
||||
|
||||
ProgressDialog progressDialog = null;
|
||||
|
||||
private class SimpleProgressDialog extends ProgressDialog {
|
||||
private static class SimpleProgressDialog extends ProgressDialog {
|
||||
|
||||
SimpleProgressDialog(Context context, int msgId) {
|
||||
super(context);
|
||||
setCancelable(false);
|
||||
setMessage(context.getString(msgId));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// prevent back button
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -59,13 +68,15 @@ public class BaseActivity extends SecureActivity
|
||||
progressDialog = new SimpleProgressDialog(BaseActivity.this, msgId);
|
||||
if (delayMillis > 0) {
|
||||
Handler handler = new Handler();
|
||||
handler.postDelayed(new Runnable() {
|
||||
public void run() {
|
||||
if (progressDialog != null) progressDialog.show();
|
||||
handler.postDelayed(() -> {
|
||||
if (progressDialog != null) {
|
||||
progressDialog.show();
|
||||
disableBackPressed();
|
||||
}
|
||||
}, delayMillis);
|
||||
} else {
|
||||
progressDialog.show();
|
||||
disableBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,6 +86,7 @@ public class BaseActivity extends SecureActivity
|
||||
progressDialog = new LedgerProgressDialog(BaseActivity.this, mode);
|
||||
Ledger.setListener((Ledger.Listener) progressDialog);
|
||||
progressDialog.show();
|
||||
disableBackPressed();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -87,6 +99,28 @@ public class BaseActivity extends SecureActivity
|
||||
progressDialog.dismiss();
|
||||
}
|
||||
progressDialog = null;
|
||||
enableBackPressed();
|
||||
}
|
||||
|
||||
OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
// no going back
|
||||
}
|
||||
};
|
||||
|
||||
public void disableBackPressed() {
|
||||
backPressedCallback.setEnabled(true);
|
||||
}
|
||||
|
||||
public void enableBackPressed() {
|
||||
backPressedCallback.setEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
|
||||
}
|
||||
|
||||
static final int RELEASE_WAKE_LOCK_DELAY = 5000; // millisconds
|
||||
@@ -136,4 +170,87 @@ public class BaseActivity extends SecureActivity
|
||||
barcodeData = null;
|
||||
return popped;
|
||||
}
|
||||
|
||||
public boolean isNetworkAvailable() {
|
||||
ConnectivityManager connectivityManager = (ConnectivityManager) getApplication().getSystemService(Context.CONNECTIVITY_SERVICE);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
Network network = connectivityManager.getActiveNetwork();
|
||||
if (network == null) return false;
|
||||
NetworkCapabilities networkCapabilities = connectivityManager.getNetworkCapabilities(network);
|
||||
return networkCapabilities != null && (networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) || networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_BLUETOOTH));
|
||||
} else {
|
||||
NetworkInfo networkInfo = connectivityManager.getActiveNetworkInfo();
|
||||
return networkInfo != null && networkInfo.isConnected();
|
||||
}
|
||||
}
|
||||
|
||||
static private final int REQUEST_CODE_BLUETOOTH_PERMISSIONS = 32423;
|
||||
|
||||
void btPermissionGranted() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
if (requestCode == REQUEST_CODE_BLUETOOTH_PERMISSIONS) {
|
||||
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED && grantResults[1] == PackageManager.PERMISSION_GRANTED) {
|
||||
btPermissionGranted();
|
||||
} // else onResume() takes care of trying again
|
||||
}
|
||||
}
|
||||
|
||||
private void showBtPermissionsDialog() {
|
||||
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
|
||||
alertDialogBuilder.setMessage(R.string.bluetooth_permissions);
|
||||
alertDialogBuilder.setPositiveButton(R.string.bluetooth_permissions_ok,
|
||||
(dialog, which) -> requestBtPermissions());
|
||||
alertDialogBuilder.setCancelable(false);
|
||||
AlertDialog alertDialog = alertDialogBuilder.create();
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
private void showAppInfoDialog() {
|
||||
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
|
||||
alertDialogBuilder.setMessage(R.string.bluetooth_permissions);
|
||||
alertDialogBuilder.setPositiveButton(R.string.bluetooth_permissions_settings, (dialog, which) -> {
|
||||
Intent i = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
||||
i.addCategory(Intent.CATEGORY_DEFAULT);
|
||||
i.setData(Uri.parse("package:" + getPackageName()));
|
||||
startActivity(i);
|
||||
});
|
||||
alertDialogBuilder.setNegativeButton(R.string.bluetooth_permissions_cancel, (dialog, which) -> {
|
||||
finish();
|
||||
});
|
||||
alertDialogBuilder.setCancelable(false);
|
||||
AlertDialog alertDialog = alertDialogBuilder.create();
|
||||
alertDialog.show();
|
||||
}
|
||||
|
||||
private void requestBtPermissions() {
|
||||
ActivityCompat.requestPermissions(this, new String[]{android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_SCAN}, REQUEST_CODE_BLUETOOTH_PERMISSIONS);
|
||||
}
|
||||
|
||||
private boolean firstCheck = true;
|
||||
|
||||
void checkBtPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||
if ((ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_CONNECT) != PackageManager.PERMISSION_GRANTED) ||
|
||||
(ActivityCompat.checkSelfPermission(this, android.Manifest.permission.BLUETOOTH_SCAN) != PackageManager.PERMISSION_GRANTED)) {
|
||||
if (shouldShowRequestPermissionRationale(android.Manifest.permission.BLUETOOTH_SCAN) || shouldShowRequestPermissionRationale(Manifest.permission.BLUETOOTH_CONNECT)) {
|
||||
showBtPermissionsDialog();
|
||||
} else {
|
||||
if (firstCheck) {
|
||||
requestBtPermissions();
|
||||
} else {
|
||||
showAppInfoDialog();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
btPermissionGranted();
|
||||
}
|
||||
firstCheck = false;
|
||||
} else {
|
||||
btPermissionGranted();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
326
app/src/main/java/com/m2049r/xmrwallet/BluetoothFragment.java
Normal file
326
app/src/main/java/com/m2049r/xmrwallet/BluetoothFragment.java
Normal file
File diff suppressed because it is too large
Load Diff
@@ -18,7 +18,6 @@ package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
@@ -67,8 +66,20 @@ public class GenerateFragment extends Fragment {
|
||||
static final String TYPE_KEY = "key";
|
||||
static final String TYPE_SEED = "seed";
|
||||
static final String TYPE_LEDGER = "ledger";
|
||||
static final String TYPE_SIDEKICK = "sidekick";
|
||||
static final String TYPE_VIEWONLY = "view";
|
||||
|
||||
static Wallet.Device getDeviceType(String type) {
|
||||
switch (type) {
|
||||
case TYPE_SIDEKICK:
|
||||
return Wallet.Device.Sidekick;
|
||||
case TYPE_LEDGER:
|
||||
return Wallet.Device.Ledger;
|
||||
default:
|
||||
return Wallet.Device.Software;
|
||||
}
|
||||
}
|
||||
|
||||
private TextInputLayout etWalletName;
|
||||
private PasswordEntryView etWalletPassword;
|
||||
private LinearLayout llFingerprintAuth;
|
||||
@@ -195,6 +206,7 @@ public class GenerateFragment extends Fragment {
|
||||
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_UNSPECIFIED);
|
||||
break;
|
||||
case TYPE_LEDGER:
|
||||
case TYPE_SIDEKICK:
|
||||
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
etWalletPassword.getEditText().setOnEditorActionListener((v, actionId, event) -> {
|
||||
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER) && (event.getAction() == KeyEvent.ACTION_DOWN))
|
||||
@@ -308,7 +320,7 @@ public class GenerateFragment extends Fragment {
|
||||
private boolean checkName() {
|
||||
String name = etWalletName.getEditText().getText().toString();
|
||||
boolean ok = true;
|
||||
if (name.length() == 0) {
|
||||
if (name.isEmpty()) {
|
||||
etWalletName.setError(getString(R.string.generate_wallet_name));
|
||||
ok = false;
|
||||
} else if (name.charAt(0) == '.') {
|
||||
@@ -450,11 +462,12 @@ public class GenerateFragment extends Fragment {
|
||||
activityCallback.onGenerate(name, crazyPass, seed, offset, height);
|
||||
break;
|
||||
case TYPE_LEDGER:
|
||||
case TYPE_SIDEKICK:
|
||||
bGenerate.setEnabled(false);
|
||||
if (fingerprintAuthAllowed) {
|
||||
KeyStoreHelper.saveWalletUserPass(requireActivity(), name, password);
|
||||
}
|
||||
activityCallback.onGenerateLedger(name, crazyPass, height);
|
||||
activityCallback.onGenerateDevice(getDeviceType(type), name, crazyPass, height);
|
||||
break;
|
||||
case TYPE_KEY:
|
||||
case TYPE_VIEWONLY:
|
||||
@@ -498,6 +511,8 @@ public class GenerateFragment extends Fragment {
|
||||
return getString(R.string.generate_wallet_type_seed);
|
||||
case TYPE_LEDGER:
|
||||
return getString(R.string.generate_wallet_type_ledger);
|
||||
case TYPE_SIDEKICK:
|
||||
return getString(R.string.generate_wallet_type_sidekick);
|
||||
case TYPE_VIEWONLY:
|
||||
return getString(R.string.generate_wallet_type_view);
|
||||
default:
|
||||
@@ -515,7 +530,7 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height);
|
||||
|
||||
void onGenerateLedger(String name, String password, long height);
|
||||
void onGenerateDevice(Wallet.Device device, String name, String password, long height);
|
||||
|
||||
void setTitle(String title);
|
||||
|
||||
@@ -555,6 +570,9 @@ public class GenerateFragment extends Fragment {
|
||||
case TYPE_LEDGER:
|
||||
inflater.inflate(R.menu.create_wallet_ledger, menu);
|
||||
break;
|
||||
case TYPE_SIDEKICK:
|
||||
inflater.inflate(R.menu.create_wallet_sidekick, menu);
|
||||
break;
|
||||
case TYPE_VIEWONLY:
|
||||
inflater.inflate(R.menu.create_wallet_view, menu);
|
||||
break;
|
||||
@@ -581,13 +599,11 @@ public class GenerateFragment extends Fragment {
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getString(R.string.label_ok), null)
|
||||
.setNegativeButton(getString(R.string.label_cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
etWalletMnemonic.getEditText().getText().clear();
|
||||
dialog.cancel();
|
||||
ledgerDialog = null;
|
||||
}
|
||||
(dialog, id) -> {
|
||||
Helper.hideKeyboardAlways(activity);
|
||||
etWalletMnemonic.getEditText().getText().clear();
|
||||
dialog.cancel();
|
||||
ledgerDialog = null;
|
||||
});
|
||||
|
||||
ledgerDialog = alertDialogBuilder.create();
|
||||
|
@@ -40,6 +40,7 @@ import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
@@ -98,6 +99,13 @@ public class GenerateReviewFragment extends Fragment {
|
||||
private String walletPath;
|
||||
private String walletName;
|
||||
|
||||
private OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(false) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
// nothing
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -160,6 +168,9 @@ public class GenerateReviewFragment extends Fragment {
|
||||
walletPath = args.getString(REQUEST_PATH);
|
||||
localPassword = args.getString(REQUEST_PASSWORD);
|
||||
showDetails();
|
||||
if (type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT)) {
|
||||
backPressedCallback.setEnabled(true);
|
||||
}
|
||||
return view;
|
||||
}
|
||||
|
||||
@@ -243,7 +254,7 @@ public class GenerateReviewFragment extends Fragment {
|
||||
showProgress();
|
||||
if ((walletPath != null)
|
||||
&& (WalletManager.getInstance().queryWalletDevice(walletPath + ".keys", getPassword())
|
||||
== Wallet.Device.Device_Ledger)
|
||||
== Wallet.Device.Ledger)
|
||||
&& (progressCallback != null)) {
|
||||
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_RESTORE);
|
||||
dialogOpened = true;
|
||||
@@ -275,10 +286,11 @@ public class GenerateReviewFragment extends Fragment {
|
||||
height = wallet.getRestoreHeight();
|
||||
seed = wallet.getSeed(getSeedOffset());
|
||||
switch (wallet.getDeviceType()) {
|
||||
case Device_Ledger:
|
||||
case Ledger:
|
||||
viewKey = Ledger.Key();
|
||||
break;
|
||||
case Device_Software:
|
||||
case Software:
|
||||
case Sidekick:
|
||||
viewKey = wallet.getSecretViewKey();
|
||||
break;
|
||||
default:
|
||||
@@ -420,14 +432,11 @@ public class GenerateReviewFragment extends Fragment {
|
||||
pbProgress.setVisibility(View.INVISIBLE);
|
||||
}
|
||||
|
||||
boolean backOk() {
|
||||
return !type.equals(GenerateReviewFragment.VIEW_TYPE_ACCEPT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
37
app/src/main/java/com/m2049r/xmrwallet/LockFragment.java
Normal file
37
app/src/main/java/com/m2049r/xmrwallet/LockFragment.java
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2024 m2049r
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.FrameLayout;
|
||||
|
||||
import androidx.fragment.app.Fragment;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class LockFragment extends Fragment {
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
|
||||
Timber.d("onCreateView");
|
||||
final FrameLayout frame = new FrameLayout(requireContext());
|
||||
frame.setLayoutParams(new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
|
||||
return frame;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,7 @@ import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -45,6 +46,7 @@ import com.google.android.material.progressindicator.CircularProgressIndicator;
|
||||
import com.m2049r.xmrwallet.data.NodeInfo;
|
||||
import com.m2049r.xmrwallet.dialog.HelpFragment;
|
||||
import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
@@ -60,6 +62,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import lombok.Getter;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInteractionListener,
|
||||
@@ -114,7 +117,9 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
|
||||
Set<NodeInfo> getOrPopulateFavourites();
|
||||
|
||||
boolean hasLedger();
|
||||
boolean hasDevice(Wallet.Device type);
|
||||
|
||||
boolean isNetworkAvailable();
|
||||
|
||||
void runOnNetCipher(Runnable runnable);
|
||||
}
|
||||
@@ -145,9 +150,15 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
activityCallback.setToolbarButton(Toolbar.BUTTON_SETTINGS);
|
||||
activityCallback.showNet();
|
||||
showNetwork();
|
||||
//activityCallback.runOnNetCipher(this::pingSelectedNode);
|
||||
}
|
||||
|
||||
private OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
animateFAB();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -164,6 +175,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
fabSeed = view.findViewById(R.id.fabSeed);
|
||||
fabImport = view.findViewById(R.id.fabImport);
|
||||
fabLedger = view.findViewById(R.id.fabLedger);
|
||||
fabSidekick = view.findViewById(R.id.fabSidekick);
|
||||
|
||||
fabNewL = view.findViewById(R.id.fabNewL);
|
||||
fabViewL = view.findViewById(R.id.fabViewL);
|
||||
@@ -171,6 +183,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
fabSeedL = view.findViewById(R.id.fabSeedL);
|
||||
fabImportL = view.findViewById(R.id.fabImportL);
|
||||
fabLedgerL = view.findViewById(R.id.fabLedgerL);
|
||||
fabSidekickL = view.findViewById(R.id.fabSidekickL);
|
||||
|
||||
fab_pulse = AnimationUtils.loadAnimation(getContext(), R.anim.fab_pulse);
|
||||
fab_open_screen = AnimationUtils.loadAnimation(getContext(), R.anim.fab_open_screen);
|
||||
@@ -186,6 +199,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
fabSeed.setOnClickListener(this);
|
||||
fabImport.setOnClickListener(this);
|
||||
fabLedger.setOnClickListener(this);
|
||||
fabSidekick.setOnClickListener(this);
|
||||
fabScreen.setOnClickListener(this);
|
||||
|
||||
RecyclerView recyclerView = view.findViewById(R.id.list);
|
||||
@@ -194,6 +208,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
recyclerView.setAdapter(adapter);
|
||||
|
||||
ViewGroup llNotice = view.findViewById(R.id.llNotice);
|
||||
|
||||
Notice.showAll(llNotice, ".*_login");
|
||||
|
||||
view.findViewById(R.id.llNode).setOnClickListener(v -> startNodePrefs());
|
||||
@@ -287,6 +302,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -295,25 +311,30 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
private boolean isFabOpen = false;
|
||||
private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabImport, fabLedger;
|
||||
@Getter
|
||||
private boolean fabOpen = false;
|
||||
private FloatingActionButton fab, fabNew, fabView, fabKey, fabSeed, fabImport, fabLedger, fabSidekick;
|
||||
private RelativeLayout fabScreen;
|
||||
private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabImportL, fabLedgerL;
|
||||
private RelativeLayout fabNewL, fabViewL, fabKeyL, fabSeedL, fabImportL, fabLedgerL, fabSidekickL;
|
||||
private Animation fab_open, fab_close, rotate_forward, rotate_backward, fab_open_screen, fab_close_screen;
|
||||
private Animation fab_pulse;
|
||||
|
||||
public boolean isFabOpen() {
|
||||
return isFabOpen;
|
||||
private void setFabOpen(boolean value) {
|
||||
fabOpen = value;
|
||||
onBackPressedCallback.setEnabled(value);
|
||||
}
|
||||
|
||||
public void animateFAB() {
|
||||
if (isFabOpen) { // close the fab
|
||||
if (isFabOpen()) { // close the fab
|
||||
fabScreen.setClickable(false);
|
||||
fabScreen.startAnimation(fab_close_screen);
|
||||
fab.startAnimation(rotate_backward);
|
||||
if (fabLedgerL.getVisibility() == View.VISIBLE) {
|
||||
fabLedgerL.startAnimation(fab_close);
|
||||
fabLedger.setClickable(false);
|
||||
} else if (fabSidekickL.getVisibility() == View.VISIBLE) {
|
||||
fabSidekickL.startAnimation(fab_close);
|
||||
fabSidekick.setClickable(false);
|
||||
} else {
|
||||
fabNewL.startAnimation(fab_close);
|
||||
fabNew.setClickable(false);
|
||||
@@ -326,22 +347,30 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
fabImportL.startAnimation(fab_close);
|
||||
fabImport.setClickable(false);
|
||||
}
|
||||
isFabOpen = false;
|
||||
setFabOpen(false);
|
||||
} else { // open the fab
|
||||
fabScreen.setClickable(true);
|
||||
fabScreen.startAnimation(fab_open_screen);
|
||||
fab.startAnimation(rotate_forward);
|
||||
if (activityCallback.hasLedger()) {
|
||||
fabLedgerL.setVisibility(View.VISIBLE);
|
||||
if ((activityCallback.hasDevice(Wallet.Device.Ledger)
|
||||
|| activityCallback.hasDevice(Wallet.Device.Sidekick))) {
|
||||
fabNewL.setVisibility(View.GONE);
|
||||
fabViewL.setVisibility(View.GONE);
|
||||
fabKeyL.setVisibility(View.GONE);
|
||||
fabSeedL.setVisibility(View.GONE);
|
||||
fabImportL.setVisibility(View.GONE);
|
||||
|
||||
fabLedgerL.startAnimation(fab_open);
|
||||
fabLedger.setClickable(true);
|
||||
if (activityCallback.hasDevice(Wallet.Device.Ledger)) {
|
||||
fabLedgerL.setVisibility(View.VISIBLE);
|
||||
fabLedgerL.startAnimation(fab_open);
|
||||
fabLedger.setClickable(true);
|
||||
} else { // Sidekick
|
||||
fabSidekickL.setVisibility(View.VISIBLE);
|
||||
fabSidekickL.startAnimation(fab_open);
|
||||
fabSidekick.setClickable(true);
|
||||
}
|
||||
} else {
|
||||
fabSidekickL.setVisibility(View.GONE);
|
||||
fabLedgerL.setVisibility(View.GONE);
|
||||
fabNewL.setVisibility(View.VISIBLE);
|
||||
fabViewL.setVisibility(View.VISIBLE);
|
||||
@@ -360,7 +389,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
fabImportL.startAnimation(fab_open);
|
||||
fabImport.setClickable(true);
|
||||
}
|
||||
isFabOpen = true;
|
||||
setFabOpen(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -372,7 +401,7 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
animateFAB();
|
||||
} else if (id == R.id.fabNew) {
|
||||
fabScreen.setVisibility(View.INVISIBLE);
|
||||
isFabOpen = false;
|
||||
setFabOpen(false);
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_NEW);
|
||||
} else if (id == R.id.fabView) {
|
||||
animateFAB();
|
||||
@@ -390,6 +419,10 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
Timber.d("FAB_LEDGER");
|
||||
animateFAB();
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_LEDGER);
|
||||
} else if (id == R.id.fabSidekick) {
|
||||
Timber.d("FAB_SIDEKICK");
|
||||
animateFAB();
|
||||
activityCallback.onAddWallet(GenerateFragment.TYPE_SIDEKICK);
|
||||
} else if (id == R.id.fabScreen) {
|
||||
animateFAB();
|
||||
}
|
||||
|
@@ -31,6 +31,7 @@ import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -145,6 +146,13 @@ public class NodeFragment extends Fragment
|
||||
}
|
||||
}
|
||||
|
||||
private OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
Toast.makeText(requireActivity(), getString(R.string.node_refresh_wait), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
@@ -187,6 +195,7 @@ public class NodeFragment extends Fragment
|
||||
|
||||
private boolean refresh(int type) {
|
||||
if (asyncFindNodes != null) return false; // ignore refresh request as one is ongoing
|
||||
onBackPressedCallback.setEnabled(true);
|
||||
asyncFindNodes = new AsyncFindNodes();
|
||||
updateRefreshElements();
|
||||
asyncFindNodes.execute(type);
|
||||
@@ -197,6 +206,7 @@ public class NodeFragment extends Fragment
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(this, onBackPressedCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -342,6 +352,7 @@ public class NodeFragment extends Fragment
|
||||
|
||||
private void complete() {
|
||||
asyncFindNodes = null;
|
||||
onBackPressedCallback.setEnabled(false);
|
||||
if (!isAdded()) return;
|
||||
//if (isCancelled()) return;
|
||||
tvPull.setText(getString(R.string.node_pull_hint));
|
||||
@@ -575,6 +586,7 @@ public class NodeFragment extends Fragment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void restoreDefaultNodes() {
|
||||
|
@@ -1,21 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
public interface OnBackPressedListener {
|
||||
boolean onBackPressed();
|
||||
}
|
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* Copyright (c) 2018 m2049r
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothClass;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.Context;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||
|
||||
import com.m2049r.xmrwallet.data.BluetoothInfo;
|
||||
import com.m2049r.xmrwallet.layout.BluetoothInfoAdapter;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class SidekickConnectFragment extends Fragment
|
||||
implements BluetoothInfoAdapter.OnInteractionListener {
|
||||
|
||||
private BluetoothAdapter bluetoothAdapter;
|
||||
|
||||
private SwipeRefreshLayout pullToRefresh;
|
||||
|
||||
private BluetoothInfoAdapter infoAdapter;
|
||||
|
||||
private Listener activityCallback;
|
||||
|
||||
public interface Listener {
|
||||
void setToolbarButton(int type);
|
||||
|
||||
void setSubtitle(String title);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAttach(@NonNull Context context) {
|
||||
super.onAttach(context);
|
||||
if (context instanceof Listener) {
|
||||
this.activityCallback = (Listener) context;
|
||||
} else {
|
||||
throw new ClassCastException(context + " must implement Listener");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
Timber.d("onPause()");
|
||||
if (bluetoothAdapter.isDiscovering()) {
|
||||
bluetoothAdapter.cancelDiscovery();
|
||||
}
|
||||
// the the activity we are connected? why? it can ask the bluetoothservice...
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
Timber.d("onResume()");
|
||||
activityCallback.setSubtitle(getString(R.string.label_bluetooth));
|
||||
activityCallback.setToolbarButton(Toolbar.BUTTON_BACK);
|
||||
final BluetoothFragment btFragment = (BluetoothFragment) getChildFragmentManager().findFragmentById(R.id.bt_fragment);
|
||||
assert btFragment != null;
|
||||
btFragment.start();
|
||||
}
|
||||
|
||||
@Override
|
||||
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||
Bundle savedInstanceState) {
|
||||
Timber.d("onCreateView");
|
||||
View view = inflater.inflate(R.layout.fragment_sidekick_connect, container, false);
|
||||
|
||||
RecyclerView recyclerView = view.findViewById(R.id.list);
|
||||
infoAdapter = new BluetoothInfoAdapter(this);
|
||||
recyclerView.setAdapter(infoAdapter);
|
||||
|
||||
pullToRefresh = view.findViewById(R.id.pullToRefresh);
|
||||
pullToRefresh.setOnRefreshListener(() -> {
|
||||
populateList();
|
||||
pullToRefresh.setRefreshing(false);
|
||||
});
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
private void populateList() {
|
||||
List<BluetoothInfo> items = new ArrayList<>();
|
||||
for (BluetoothDevice device : bluetoothAdapter.getBondedDevices()) {
|
||||
final int deviceCLass = device.getBluetoothClass().getDeviceClass();
|
||||
switch (deviceCLass) {
|
||||
case BluetoothClass.Device.PHONE_SMART:
|
||||
//TODO verify these are correct
|
||||
case BluetoothClass.Device.COMPUTER_HANDHELD_PC_PDA:
|
||||
case BluetoothClass.Device.COMPUTER_PALM_SIZE_PC_PDA:
|
||||
items.add(new BluetoothInfo(device));
|
||||
}
|
||||
}
|
||||
infoAdapter.setItems(items);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||
Helper.hideKeyboard(getActivity());
|
||||
|
||||
// Get the local Bluetooth adapter
|
||||
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
populateList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setHasOptionsMenu(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreateOptionsMenu(@NonNull Menu menu, MenuInflater inflater) {
|
||||
inflater.inflate(R.menu.sidekick_connect_menu, menu);
|
||||
super.onCreateOptionsMenu(menu, inflater);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
// Make sure we're not doing discovery anymore
|
||||
if (bluetoothAdapter != null) {
|
||||
bluetoothAdapter.cancelDiscovery();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInteraction(final View view, final BluetoothInfo item) {
|
||||
Timber.d("onInteraction %s", item);
|
||||
bluetoothAdapter.cancelDiscovery();
|
||||
|
||||
final BluetoothFragment btFragment = (BluetoothFragment) getChildFragmentManager().findFragmentById(R.id.bt_fragment);
|
||||
assert btFragment != null;
|
||||
btFragment.connectDevice(item.getAddress());
|
||||
}
|
||||
|
||||
public void allowClick() {
|
||||
infoAdapter.allowClick(true);
|
||||
}
|
||||
}
|
@@ -195,7 +195,7 @@ public class SubaddressFragment extends Fragment implements SubaddressInfoAdapte
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
if ((wallet.getDeviceType() == Wallet.Device.Device_Ledger) && (progressCallback != null)) {
|
||||
if ((wallet.getDeviceType() == Wallet.Device.Ledger) && (progressCallback != null)) {
|
||||
progressCallback.showLedgerProgressDialog(LedgerProgressDialog.TYPE_SUBADDRESS);
|
||||
dialogOpened = true;
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -22,8 +22,8 @@ import android.content.res.Configuration;
|
||||
import android.os.Build;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.model.NetworkType;
|
||||
import com.m2049r.xmrwallet.util.LocaleHelper;
|
||||
import com.m2049r.xmrwallet.util.NetCipherHelper;
|
||||
@@ -36,7 +36,6 @@ public class XmrWalletApplication extends Application {
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
FragmentManager.enableNewStateManager(false);
|
||||
if (BuildConfig.DEBUG) {
|
||||
Timber.plant(new Timber.DebugTree());
|
||||
}
|
||||
|
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2018 m2049r
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.m2049r.xmrwallet.data;
|
||||
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
|
||||
import java.util.Comparator;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
|
||||
@Data
|
||||
public class BluetoothInfo {
|
||||
@Getter
|
||||
final private String name;
|
||||
@Getter
|
||||
final private String address;
|
||||
@Getter
|
||||
private boolean bonded;
|
||||
|
||||
public BluetoothInfo(BluetoothDevice device) {
|
||||
name = device.getName().trim();
|
||||
address = device.getAddress();
|
||||
bonded = device.getBondState() == BluetoothDevice.BOND_BONDED;
|
||||
}
|
||||
|
||||
static public Comparator<BluetoothInfo> NameComparator = (o1, o2) -> o1.name.compareTo(o2.name);
|
||||
}
|
@@ -29,10 +29,12 @@ public enum DefaultNodes {
|
||||
HASHVAULT("nodes.hashvault.pro:18081"),
|
||||
MONEROWORLD("node.moneroworld.com:18089"),
|
||||
XMRTW("opennode.xmr-tw.org:18089"),
|
||||
ds_jetzt("monero.ds-jetzt.de:18089"),
|
||||
MONERUJO_ONION("monerujods7mbghwe6cobdr6ujih6c22zu5rl7zshmizz2udf7v7fsad.onion:18081/mainnet/monerujo.onion"),
|
||||
Criminales78("56wl7y2ebhamkkiza4b7il4mrzwtyvpdym7bm2bkg3jrei2je646k3qd.onion:18089/mainnet/Criminales78.onion"),
|
||||
xmrfail("mxcd4577fldb3ppzy7obmmhnu3tf57gbcbd4qhwr2kxyjj2qi3dnbfqd.onion:18081/mainnet/xmrfail.onion"),
|
||||
boldsuck("6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081/mainnet/boldsuck.onion");
|
||||
boldsuck("6dsdenp6vjkvqzy4wzsnzn6wixkdzihx3khiumyzieauxuxslmcaeiad.onion:18081/mainnet/boldsuck.onion"),
|
||||
ds_jetzt_onion("qvlr4w7yhnjrdg3txa72jwtpnjn4ezsrivzvocbnvpfbdo342fahhoad.onion:18089/mainnet/ds-jetzt.onion");
|
||||
|
||||
@Getter
|
||||
private final String uri;
|
||||
|
@@ -29,6 +29,7 @@ import android.view.ViewGroup;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
|
||||
import androidx.activity.OnBackPressedCallback;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.fragment.app.Fragment;
|
||||
@@ -37,7 +38,6 @@ import androidx.fragment.app.FragmentStatePagerAdapter;
|
||||
import androidx.viewpager.widget.ViewPager;
|
||||
|
||||
import com.google.android.material.transition.MaterialContainerTransform;
|
||||
import com.m2049r.xmrwallet.OnBackPressedListener;
|
||||
import com.m2049r.xmrwallet.OnUriScannedListener;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.WalletActivity;
|
||||
@@ -63,7 +63,7 @@ public class SendFragment extends Fragment
|
||||
SendAmountWizardFragment.Listener,
|
||||
SendConfirmWizardFragment.Listener,
|
||||
SendSuccessWizardFragment.Listener,
|
||||
OnBackPressedListener, OnUriScannedListener {
|
||||
OnUriScannedListener {
|
||||
|
||||
final static public int MIXIN = 0;
|
||||
|
||||
@@ -248,16 +248,18 @@ public class SendFragment extends Fragment
|
||||
private SpendViewPager spendViewPager;
|
||||
private SpendPagerAdapter pagerAdapter;
|
||||
|
||||
@Override
|
||||
public boolean onBackPressed() {
|
||||
if (isComitted()) return true; // no going back
|
||||
if (spendViewPager.getCurrentItem() == 0) {
|
||||
return false;
|
||||
} else {
|
||||
spendViewPager.previous();
|
||||
return true;
|
||||
OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
|
||||
@Override
|
||||
public void handleOnBackPressed() {
|
||||
if (isComitted()) return; // no going back
|
||||
if (spendViewPager.getCurrentItem() == 0) {
|
||||
setEnabled(false);
|
||||
requireActivity().getOnBackPressedDispatcher().onBackPressed();
|
||||
} else {
|
||||
spendViewPager.previous();
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public boolean onUriScanned(BarcodeData barcodeData) {
|
||||
@@ -546,8 +548,9 @@ public class SendFragment extends Fragment
|
||||
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));
|
||||
transform.setAllContainerColors(ThemeHelper.getThemedColor(requireContext(), android.R.attr.colorBackground));
|
||||
setSharedElementEnterTransition(transform);
|
||||
requireActivity().getOnBackPressedDispatcher().addCallback(this, backPressedCallback);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* 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.layout;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.recyclerview.widget.DiffUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.data.BluetoothInfo;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class BluetoothInfoAdapter extends RecyclerView.Adapter<BluetoothInfoAdapter.ViewHolder> {
|
||||
|
||||
public interface OnInteractionListener {
|
||||
void onInteraction(View view, BluetoothInfo item);
|
||||
}
|
||||
|
||||
private final List<BluetoothInfo> items = new ArrayList<>();
|
||||
private final OnInteractionListener listener;
|
||||
|
||||
public BluetoothInfoAdapter(OnInteractionListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
private static class BluetoothInfoDiff extends DiffCallback<BluetoothInfo> {
|
||||
|
||||
public BluetoothInfoDiff(List<BluetoothInfo> oldList, List<BluetoothInfo> newList) {
|
||||
super(oldList, newList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
return mOldList.get(oldItemPosition).getAddress().equals(mNewList.get(newItemPosition).getAddress());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) {
|
||||
final BluetoothInfo oldItem = mOldList.get(oldItemPosition);
|
||||
final BluetoothInfo newItem = mNewList.get(newItemPosition);
|
||||
return oldItem.equals(newItem);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull
|
||||
ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
|
||||
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_bluetooth, parent, false);
|
||||
return new ViewHolder(view);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(final @NonNull ViewHolder holder, int position) {
|
||||
holder.bind(position);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return items.size();
|
||||
}
|
||||
|
||||
public void add(BluetoothInfo item) {
|
||||
if (item == null) return;
|
||||
List<BluetoothInfo> newItems = new ArrayList<>(items);
|
||||
if (!items.contains(item))
|
||||
newItems.add(item);
|
||||
setItems(newItems); // in case the nodeinfo has changed
|
||||
}
|
||||
|
||||
public void setItems(Collection<BluetoothInfo> newItemsCollection) {
|
||||
List<BluetoothInfo> newItems;
|
||||
if (newItemsCollection != null) {
|
||||
newItems = new ArrayList<>(newItemsCollection);
|
||||
Collections.sort(newItems, BluetoothInfo.NameComparator);
|
||||
} else {
|
||||
newItems = new ArrayList<>();
|
||||
}
|
||||
final BluetoothInfoAdapter.BluetoothInfoDiff diffCallback = new BluetoothInfoAdapter.BluetoothInfoDiff(items, newItems);
|
||||
final DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(diffCallback);
|
||||
items.clear();
|
||||
items.addAll(newItems);
|
||||
diffResult.dispatchUpdatesTo(this);
|
||||
}
|
||||
|
||||
private boolean itemsClickable = true;
|
||||
|
||||
public void allowClick(boolean clickable) {
|
||||
itemsClickable = clickable;
|
||||
notifyDataSetChanged();
|
||||
}
|
||||
|
||||
public class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
|
||||
final TextView tvName;
|
||||
final TextView tvAddress;
|
||||
BluetoothInfo item;
|
||||
|
||||
ViewHolder(View itemView) {
|
||||
super(itemView);
|
||||
tvName = itemView.findViewById(R.id.tvName);
|
||||
tvAddress = itemView.findViewById(R.id.tvAddress);
|
||||
itemView.setOnClickListener(this);
|
||||
}
|
||||
|
||||
void bind(int position) {
|
||||
item = items.get(position);
|
||||
tvName.setText(item.getName());
|
||||
tvAddress.setText(item.getAddress());
|
||||
itemView.setClickable(itemsClickable);
|
||||
itemView.setEnabled(itemsClickable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (listener != null) {
|
||||
int position = getBindingAdapterPosition(); // gets item position
|
||||
if (position != RecyclerView.NO_POSITION) { // Check if an item was deleted, but the user clicked it before the UI removed it
|
||||
final BluetoothInfo node = items.get(position);
|
||||
allowClick(false);
|
||||
listener.onInteraction(view, node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -86,7 +86,7 @@ public class TransactionInfoAdapter extends RecyclerView.Adapter<TransactionInfo
|
||||
}
|
||||
|
||||
public boolean needsTransactionUpdateOnNewBlock() {
|
||||
return (infoItems.size() > 0) && !infoItems.get(0).isConfirmed();
|
||||
return (!infoItems.isEmpty()) && !infoItems.get(0).isConfirmed();
|
||||
}
|
||||
|
||||
private static class TransactionInfoDiff extends DiffCallback<TransactionInfo> {
|
||||
|
@@ -0,0 +1,4 @@
|
||||
package com.m2049r.xmrwallet.ledger;
|
||||
|
||||
public interface Hardware {
|
||||
}
|
@@ -35,12 +35,11 @@ import java.nio.charset.StandardCharsets;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class Ledger {
|
||||
public class Ledger implements Hardware {
|
||||
static final public boolean ENABLED = true;
|
||||
// 5:20 is same as wallet2.cpp::restore()
|
||||
static public final int LOOKAHEAD_ACCOUNTS = 5;
|
||||
static public final int LOOKAHEAD_SUBADDRESSES = 20;
|
||||
static public final String SUBADDRESS_LOOKAHEAD = LOOKAHEAD_ACCOUNTS + ":" + LOOKAHEAD_SUBADDRESSES;
|
||||
|
||||
private static final byte PROTOCOL_VERSION = 0x03;
|
||||
public static final int SW_OK = 0x9000;
|
||||
|
@@ -46,11 +46,6 @@ public class LedgerProgressDialog extends ProgressDialog implements Ledger.Liste
|
||||
setMessage(context.getString(R.string.progress_ledger_progress));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
// prevent back button
|
||||
}
|
||||
|
||||
private int firstSubaddress = Integer.MAX_VALUE;
|
||||
|
||||
private boolean validate = false;
|
||||
|
@@ -111,7 +111,11 @@ public class Wallet {
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum Device {
|
||||
Device_Undefined(0, 0), Device_Software(50, 200), Device_Ledger(5, 20);
|
||||
Undefined(0, 0),
|
||||
Software(50, 200),
|
||||
Ledger(5, 20),
|
||||
Trezor(5, 20),
|
||||
Sidekick(5, 20);
|
||||
private final int accountLookahead;
|
||||
private final int subaddressLookahead;
|
||||
}
|
||||
|
@@ -18,7 +18,6 @@ package com.m2049r.xmrwallet.model;
|
||||
|
||||
import com.m2049r.xmrwallet.XmrWalletApplication;
|
||||
import com.m2049r.xmrwallet.data.Node;
|
||||
import com.m2049r.xmrwallet.ledger.Ledger;
|
||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||
|
||||
import java.io.File;
|
||||
@@ -161,10 +160,12 @@ public class WalletManager {
|
||||
String spendKeyString);
|
||||
|
||||
public Wallet createWalletFromDevice(File aFile, String password, long restoreHeight,
|
||||
String deviceName) {
|
||||
Wallet.Device device) {
|
||||
final String lookahead = device.getAccountLookahead() + ":" + device.getSubaddressLookahead();
|
||||
Timber.d("Creating from %s with %s lookahead", device, lookahead);
|
||||
long walletHandle = createWalletFromDeviceJ(aFile.getAbsolutePath(), password,
|
||||
getNetworkType().getValue(), deviceName, restoreHeight,
|
||||
Ledger.SUBADDRESS_LOOKAHEAD);
|
||||
getNetworkType().getValue(), device.name(), restoreHeight,
|
||||
lookahead);
|
||||
Wallet wallet = new Wallet(walletHandle);
|
||||
manageWallet(wallet);
|
||||
return wallet;
|
||||
|
@@ -19,6 +19,8 @@ package com.m2049r.xmrwallet.onboarding;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
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