mirror of
https://codeberg.org/anoncontributorxmr/mysu.git
synced 2024-11-21 15:02:25 +00:00
Refactoring (WIP): Convert to Kotlin
This commit is contained in:
parent
de35fbc457
commit
c1f4b46b3b
61 changed files with 2428 additions and 3257 deletions
|
@ -1,5 +1,6 @@
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
apply plugin: "androidx.navigation.safeargs"
|
apply plugin: "androidx.navigation.safeargs"
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 34
|
compileSdkVersion 34
|
||||||
|
@ -96,8 +97,8 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_9
|
sourceCompatibility JavaVersion.VERSION_17
|
||||||
targetCompatibility JavaVersion.VERSION_1_9
|
targetCompatibility JavaVersion.VERSION_17
|
||||||
}
|
}
|
||||||
namespace 'net.mynero.wallet'
|
namespace 'net.mynero.wallet'
|
||||||
buildFeatures {
|
buildFeatures {
|
||||||
|
@ -112,16 +113,16 @@ static def getId(name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.core:core:1.10.0'
|
implementation 'androidx.core:core-ktx:1.12.0'
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
implementation 'androidx.recyclerview:recyclerview:1.3.2'
|
||||||
implementation 'androidx.cardview:cardview:1.0.0'
|
implementation 'androidx.cardview:cardview:1.0.0'
|
||||||
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||||
implementation 'androidx.preference:preference:1.2.0'
|
implementation 'androidx.preference:preference-ktx:1.2.1'
|
||||||
|
|
||||||
implementation 'com.google.android.material:material:1.8.0'
|
implementation 'com.google.android.material:material:1.10.0'
|
||||||
implementation 'com.ncorti:slidetoact:0.9.0'
|
implementation 'com.ncorti:slidetoact:0.9.0'
|
||||||
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
|
implementation 'com.journeyapps:zxing-android-embedded:4.3.0'
|
||||||
implementation "com.jakewharton.timber:timber:5.0.1"
|
implementation "com.jakewharton.timber:timber:5.0.1"
|
||||||
|
@ -129,10 +130,13 @@ dependencies {
|
||||||
implementation fileTree(dir: 'libs/classes', include: ['*.jar'])
|
implementation fileTree(dir: 'libs/classes', include: ['*.jar'])
|
||||||
|
|
||||||
implementation 'org.slf4j:slf4j-nop:1.7.36'
|
implementation 'org.slf4j:slf4j-nop:1.7.36'
|
||||||
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1'
|
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2'
|
||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2'
|
||||||
implementation 'androidx.navigation:navigation-fragment:2.5.3'
|
implementation 'androidx.navigation:navigation-fragment:2.7.5'
|
||||||
implementation 'androidx.navigation:navigation-ui:2.5.3'
|
implementation 'androidx.navigation:navigation-ui:2.7.5'
|
||||||
|
|
||||||
|
implementation 'androidx.core:core-ktx:1.12.0'
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
testImplementation "junit:junit:4.13.2"
|
testImplementation "junit:junit:4.13.2"
|
||||||
|
|
|
@ -1004,33 +1004,6 @@ Java_net_mynero_wallet_model_Wallet_getDeviceTypeJ(JNIEnv *env, jobject instance
|
||||||
return static_cast<jint>(device_type);
|
return static_cast<jint>(device_type);
|
||||||
}
|
}
|
||||||
|
|
||||||
//void cn_slow_hash(const void *data, size_t length, char *hash); // from crypto/hash-ops.h
|
|
||||||
JNIEXPORT jbyteArray JNICALL
|
|
||||||
Java_net_mynero_wallet_util_KeyStoreHelper_slowHash(JNIEnv *env, jclass clazz,
|
|
||||||
jbyteArray data, jint brokenVariant) {
|
|
||||||
char hash[HASH_SIZE];
|
|
||||||
jsize size = env->GetArrayLength(data);
|
|
||||||
if ((brokenVariant > 0) && (size < 200 /*sizeof(union hash_state)*/)) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
jbyte *buffer = env->GetByteArrayElements(data, nullptr);
|
|
||||||
switch (brokenVariant) {
|
|
||||||
case 1:
|
|
||||||
slow_hash_broken(buffer, hash, 1);
|
|
||||||
break;
|
|
||||||
case 2:
|
|
||||||
slow_hash_broken(buffer, hash, 0);
|
|
||||||
break;
|
|
||||||
default: // not broken
|
|
||||||
slow_hash(buffer, (size_t) size, hash);
|
|
||||||
}
|
|
||||||
env->ReleaseByteArrayElements(data, buffer, JNI_ABORT); // do not update java byte[]
|
|
||||||
jbyteArray result = env->NewByteArray(HASH_SIZE);
|
|
||||||
env->SetByteArrayRegion(result, 0, HASH_SIZE, (jbyte *) hash);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
JNIEXPORT jstring JNICALL
|
JNIEXPORT jstring JNICALL
|
||||||
Java_net_mynero_wallet_model_Wallet_getDisplayAmount(JNIEnv *env, jclass clazz,
|
Java_net_mynero_wallet_model_Wallet_getDisplayAmount(JNIEnv *env, jclass clazz,
|
||||||
jlong amount) {
|
jlong amount) {
|
||||||
|
|
|
@ -1,143 +0,0 @@
|
||||||
package net.mynero.wallet;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.PersistableBundle;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
|
||||||
import androidx.fragment.app.FragmentActivity;
|
|
||||||
import androidx.fragment.app.FragmentManager;
|
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
|
||||||
|
|
||||||
import net.mynero.wallet.data.DefaultNodes;
|
|
||||||
import net.mynero.wallet.data.Node;
|
|
||||||
import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog;
|
|
||||||
import net.mynero.wallet.fragment.dialog.SendBottomSheetDialog;
|
|
||||||
import net.mynero.wallet.livedata.SingleLiveEvent;
|
|
||||||
import net.mynero.wallet.model.Wallet;
|
|
||||||
import net.mynero.wallet.model.WalletManager;
|
|
||||||
import net.mynero.wallet.service.AddressService;
|
|
||||||
import net.mynero.wallet.service.BalanceService;
|
|
||||||
import net.mynero.wallet.service.BlockchainService;
|
|
||||||
import net.mynero.wallet.service.HistoryService;
|
|
||||||
import net.mynero.wallet.service.MoneroHandlerThread;
|
|
||||||
import net.mynero.wallet.service.PrefService;
|
|
||||||
import net.mynero.wallet.service.TxService;
|
|
||||||
import net.mynero.wallet.service.UTXOService;
|
|
||||||
import net.mynero.wallet.util.Constants;
|
|
||||||
import net.mynero.wallet.util.UriData;
|
|
||||||
|
|
||||||
import org.json.JSONArray;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
public class MainActivity extends AppCompatActivity implements MoneroHandlerThread.Listener, PasswordBottomSheetDialog.PasswordListener {
|
|
||||||
public final SingleLiveEvent restartEvents = new SingleLiveEvent();
|
|
||||||
private MoneroHandlerThread thread = null;
|
|
||||||
private BalanceService balanceService = null;
|
|
||||||
private AddressService addressService = null;
|
|
||||||
private HistoryService historyService = null;
|
|
||||||
private BlockchainService blockchainService = null;
|
|
||||||
private UTXOService utxoService = null;
|
|
||||||
|
|
||||||
private boolean proceedToSend = false;
|
|
||||||
private UriData uriData = null;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
setContentView(R.layout.activity_main);
|
|
||||||
File walletFile = new File(getApplicationInfo().dataDir, Constants.WALLET_NAME);
|
|
||||||
File walletKeysFile = new File(getApplicationInfo().dataDir, Constants.WALLET_NAME + ".keys");
|
|
||||||
if (walletKeysFile.exists()) {
|
|
||||||
boolean promptPassword = PrefService.getInstance().getBoolean(Constants.PREF_USES_PASSWORD, false);
|
|
||||||
if (!promptPassword) {
|
|
||||||
init(walletFile, "");
|
|
||||||
} else {
|
|
||||||
PasswordBottomSheetDialog passwordDialog = new PasswordBottomSheetDialog();
|
|
||||||
passwordDialog.listener = this;
|
|
||||||
passwordDialog.show(getSupportFragmentManager(), "password_dialog");
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent intent = getIntent();
|
|
||||||
Uri uri = intent.getData();
|
|
||||||
if (uri != null) {
|
|
||||||
uriData = UriData.parse(uri.toString());
|
|
||||||
if (uriData != null) {
|
|
||||||
proceedToSend = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
navigate(R.id.onboarding_fragment);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPostCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
|
|
||||||
super.onPostCreate(savedInstanceState, persistentState);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void navigate(int destination) {
|
|
||||||
FragmentActivity activity = this;
|
|
||||||
FragmentManager fm = activity.getSupportFragmentManager();
|
|
||||||
NavHostFragment navHostFragment =
|
|
||||||
(NavHostFragment) fm.findFragmentById(R.id.nav_host_fragment);
|
|
||||||
if (navHostFragment != null) {
|
|
||||||
navHostFragment.getNavController().navigate(destination);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public MoneroHandlerThread getThread() {
|
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void init(File walletFile, String password) {
|
|
||||||
Wallet wallet = WalletManager.getInstance().openWallet(walletFile.getAbsolutePath(), password);
|
|
||||||
thread = new MoneroHandlerThread("WalletService", this, wallet);
|
|
||||||
new TxService(thread);
|
|
||||||
this.balanceService = new BalanceService(thread);
|
|
||||||
this.addressService = new AddressService(thread);
|
|
||||||
this.historyService = new HistoryService(thread);
|
|
||||||
this.blockchainService = new BlockchainService(thread);
|
|
||||||
this.utxoService = new UTXOService(thread);
|
|
||||||
thread.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onRefresh(boolean walletSynced) {
|
|
||||||
if(walletSynced)
|
|
||||||
this.utxoService.refreshUtxos();
|
|
||||||
|
|
||||||
this.historyService.refreshHistory();
|
|
||||||
this.balanceService.refreshBalance();
|
|
||||||
this.blockchainService.refreshBlockchain();
|
|
||||||
this.addressService.refreshAddresses();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onConnectionFail() {
|
|
||||||
runOnUiThread(() -> Toast.makeText(getApplication(), R.string.connection_failed, Toast.LENGTH_SHORT).show());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPasswordSuccess(String password) {
|
|
||||||
File walletFile = new File(getApplicationInfo().dataDir, Constants.WALLET_NAME);
|
|
||||||
init(walletFile, password);
|
|
||||||
restartEvents.call();
|
|
||||||
|
|
||||||
if (proceedToSend) {
|
|
||||||
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
|
|
||||||
sendDialog.uriData = uriData;
|
|
||||||
sendDialog.show(getSupportFragmentManager(), null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPasswordFail() {
|
|
||||||
runOnUiThread(() -> Toast.makeText(getApplication(), R.string.bad_password, Toast.LENGTH_SHORT).show());
|
|
||||||
}
|
|
||||||
}
|
|
115
app/src/main/java/net/mynero/wallet/MainActivity.kt
Normal file
115
app/src/main/java/net/mynero/wallet/MainActivity.kt
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
package net.mynero.wallet
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.PersistableBundle
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
|
import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog
|
||||||
|
import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog.PasswordListener
|
||||||
|
import net.mynero.wallet.fragment.dialog.SendBottomSheetDialog
|
||||||
|
import net.mynero.wallet.livedata.SingleLiveEvent
|
||||||
|
import net.mynero.wallet.model.WalletManager.Companion.instance
|
||||||
|
import net.mynero.wallet.service.AddressService
|
||||||
|
import net.mynero.wallet.service.BalanceService
|
||||||
|
import net.mynero.wallet.service.BlockchainService
|
||||||
|
import net.mynero.wallet.service.HistoryService
|
||||||
|
import net.mynero.wallet.service.MoneroHandlerThread
|
||||||
|
import net.mynero.wallet.service.PrefService
|
||||||
|
import net.mynero.wallet.service.TxService
|
||||||
|
import net.mynero.wallet.service.UTXOService
|
||||||
|
import net.mynero.wallet.util.Constants
|
||||||
|
import net.mynero.wallet.util.UriData
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, PasswordListener {
|
||||||
|
@JvmField
|
||||||
|
val restartEvents: SingleLiveEvent<*> = SingleLiveEvent<Any?>()
|
||||||
|
var thread: MoneroHandlerThread? = null
|
||||||
|
private set
|
||||||
|
private var balanceService: BalanceService? = null
|
||||||
|
private var addressService: AddressService? = null
|
||||||
|
private var historyService: HistoryService? = null
|
||||||
|
private var blockchainService: BlockchainService? = null
|
||||||
|
private var utxoService: UTXOService? = null
|
||||||
|
private var proceedToSend = false
|
||||||
|
private var uriData: UriData? = null
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_main)
|
||||||
|
val walletFile = File(applicationInfo.dataDir, Constants.WALLET_NAME)
|
||||||
|
val walletKeysFile = File(applicationInfo.dataDir, Constants.WALLET_NAME + ".keys")
|
||||||
|
if (walletKeysFile.exists()) {
|
||||||
|
val promptPassword =
|
||||||
|
PrefService.getInstance().getBoolean(Constants.PREF_USES_PASSWORD, false)
|
||||||
|
if (!promptPassword) {
|
||||||
|
init(walletFile, "")
|
||||||
|
} else {
|
||||||
|
val passwordDialog = PasswordBottomSheetDialog()
|
||||||
|
passwordDialog.listener = this
|
||||||
|
passwordDialog.show(supportFragmentManager, "password_dialog")
|
||||||
|
}
|
||||||
|
val intent = intent
|
||||||
|
val uri = intent.data
|
||||||
|
if (uri != null) {
|
||||||
|
uriData = UriData.parse(uri.toString())
|
||||||
|
if (uriData != null) {
|
||||||
|
proceedToSend = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
navigate(R.id.onboarding_fragment)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun navigate(destination: Int) {
|
||||||
|
val activity: FragmentActivity = this
|
||||||
|
val fm = activity.supportFragmentManager
|
||||||
|
val navHostFragment = fm.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
|
||||||
|
navHostFragment?.navController?.navigate(destination)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(walletFile: File, password: String?) {
|
||||||
|
val wallet = password?.let { instance?.openWallet(walletFile.absolutePath, it) }
|
||||||
|
thread = MoneroHandlerThread("WalletService", this, wallet)
|
||||||
|
TxService(thread)
|
||||||
|
balanceService = BalanceService(thread)
|
||||||
|
addressService = AddressService(thread)
|
||||||
|
historyService = HistoryService(thread)
|
||||||
|
blockchainService = BlockchainService(thread)
|
||||||
|
utxoService = UTXOService(thread)
|
||||||
|
thread?.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onRefresh(walletSynced: Boolean) {
|
||||||
|
if (walletSynced) utxoService?.refreshUtxos()
|
||||||
|
historyService?.refreshHistory()
|
||||||
|
balanceService?.refreshBalance()
|
||||||
|
blockchainService?.refreshBlockchain()
|
||||||
|
addressService?.refreshAddresses()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConnectionFail() {
|
||||||
|
runOnUiThread {
|
||||||
|
Toast.makeText(application, R.string.connection_failed, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPasswordSuccess(password: String) {
|
||||||
|
val walletFile = File(applicationInfo.dataDir, Constants.WALLET_NAME)
|
||||||
|
init(walletFile, password)
|
||||||
|
restartEvents.call()
|
||||||
|
if (proceedToSend) {
|
||||||
|
val sendDialog = SendBottomSheetDialog()
|
||||||
|
sendDialog.uriData = uriData
|
||||||
|
sendDialog.show(supportFragmentManager, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPasswordFail() {
|
||||||
|
runOnUiThread {
|
||||||
|
Toast.makeText(application, R.string.bad_password, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,24 +0,0 @@
|
||||||
package net.mynero.wallet;
|
|
||||||
|
|
||||||
import android.app.Application;
|
|
||||||
|
|
||||||
import net.mynero.wallet.service.PrefService;
|
|
||||||
import net.mynero.wallet.util.NightmodeHelper;
|
|
||||||
|
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
public class MoneroApplication extends Application {
|
|
||||||
private ExecutorService executor = null;
|
|
||||||
@Override
|
|
||||||
public void onCreate() {
|
|
||||||
super.onCreate();
|
|
||||||
new PrefService(this);
|
|
||||||
NightmodeHelper.getAndSetPreferredNightmode();
|
|
||||||
executor = Executors.newFixedThreadPool(16);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ExecutorService getExecutor() {
|
|
||||||
return executor;
|
|
||||||
}
|
|
||||||
}
|
|
19
app/src/main/java/net/mynero/wallet/MoneroApplication.kt
Normal file
19
app/src/main/java/net/mynero/wallet/MoneroApplication.kt
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
package net.mynero.wallet
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import net.mynero.wallet.service.PrefService
|
||||||
|
import net.mynero.wallet.util.NightmodeHelper
|
||||||
|
import java.util.concurrent.ExecutorService
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class MoneroApplication : Application() {
|
||||||
|
var executor: ExecutorService? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
override fun onCreate() {
|
||||||
|
super.onCreate()
|
||||||
|
PrefService(this)
|
||||||
|
NightmodeHelper.preferredNightmode
|
||||||
|
executor = Executors.newFixedThreadPool(16)
|
||||||
|
}
|
||||||
|
}
|
|
@ -60,7 +60,7 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter<CoinsInfoAdapter.View
|
||||||
}
|
}
|
||||||
|
|
||||||
public void deselectUtxo(CoinsInfo coinsInfo) {
|
public void deselectUtxo(CoinsInfo coinsInfo) {
|
||||||
this.selectedUtxos.remove(coinsInfo.getPubKey());
|
this.selectedUtxos.remove(coinsInfo.pubKey);
|
||||||
|
|
||||||
if(this.selectedUtxos.size() == 0) {
|
if(this.selectedUtxos.size() == 0) {
|
||||||
editing = false;
|
editing = false;
|
||||||
|
@ -71,12 +71,12 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter<CoinsInfoAdapter.View
|
||||||
|
|
||||||
public void selectUtxo(CoinsInfo coinsInfo) {
|
public void selectUtxo(CoinsInfo coinsInfo) {
|
||||||
editing = true;
|
editing = true;
|
||||||
this.selectedUtxos.put(coinsInfo.getPubKey(), coinsInfo);
|
this.selectedUtxos.put(coinsInfo.pubKey, coinsInfo);
|
||||||
notifyDataSetChanged();
|
notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean contains(CoinsInfo coinsInfo) {
|
public boolean contains(CoinsInfo coinsInfo) {
|
||||||
return selectedUtxos.containsKey(coinsInfo.getPubKey());
|
return selectedUtxos.containsKey(coinsInfo.pubKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
@ -135,7 +135,7 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter<CoinsInfoAdapter.View
|
||||||
public void bind(boolean editing, CoinsInfo coinsInfo, HashMap<String, CoinsInfo> selectedUtxos) {
|
public void bind(boolean editing, CoinsInfo coinsInfo, HashMap<String, CoinsInfo> selectedUtxos) {
|
||||||
this.editing = editing;
|
this.editing = editing;
|
||||||
this.coinsInfo = coinsInfo;
|
this.coinsInfo = coinsInfo;
|
||||||
boolean selected = selectedUtxos.containsKey(coinsInfo.getPubKey());
|
boolean selected = selectedUtxos.containsKey(coinsInfo.pubKey);
|
||||||
|
|
||||||
TextView pubKeyTextView = itemView.findViewById(R.id.utxo_pub_key_textview);
|
TextView pubKeyTextView = itemView.findViewById(R.id.utxo_pub_key_textview);
|
||||||
TextView amountTextView = itemView.findViewById(R.id.utxo_amount_textview);
|
TextView amountTextView = itemView.findViewById(R.id.utxo_amount_textview);
|
||||||
|
@ -143,12 +143,12 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter<CoinsInfoAdapter.View
|
||||||
TextView globalIdxTextView = itemView.findViewById(R.id.utxo_global_index_textview);
|
TextView globalIdxTextView = itemView.findViewById(R.id.utxo_global_index_textview);
|
||||||
TextView outpointTextView = itemView.findViewById(R.id.utxo_outpoint_textview);
|
TextView outpointTextView = itemView.findViewById(R.id.utxo_outpoint_textview);
|
||||||
boolean streetModeEnabled = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
|
boolean streetModeEnabled = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
|
||||||
String balanceString = streetModeEnabled ? Constants.STREET_MODE_BALANCE : Wallet.getDisplayAmount(coinsInfo.getAmount());
|
String balanceString = streetModeEnabled ? Constants.STREET_MODE_BALANCE : Wallet.getDisplayAmount(coinsInfo.amount);
|
||||||
amountTextView.setText(itemView.getResources().getString(R.string.tx_amount_no_prefix, balanceString));
|
amountTextView.setText(itemView.getResources().getString(R.string.tx_amount_no_prefix, balanceString));
|
||||||
pubKeyTextView.setText(coinsInfo.getPubKey());
|
pubKeyTextView.setText(coinsInfo.pubKey);
|
||||||
addressTextView.setText(coinsInfo.getAddress());
|
addressTextView.setText(coinsInfo.address);
|
||||||
globalIdxTextView.setText(itemView.getResources().getString(R.string.global_index_text, coinsInfo.getGlobalOutputIndex()));
|
globalIdxTextView.setText(itemView.getResources().getString(R.string.global_index_text, coinsInfo.globalOutputIndex));
|
||||||
outpointTextView.setText(itemView.getResources().getString(R.string.outpoint_text, coinsInfo.getHash() + ":" + coinsInfo.getLocalOutputIndex()));
|
outpointTextView.setText(itemView.getResources().getString(R.string.outpoint_text, coinsInfo.hash + ":" + coinsInfo.localOutputIndex));
|
||||||
if (selected) {
|
if (selected) {
|
||||||
itemView.setBackgroundTintList(ContextCompat.getColorStateList(itemView.getContext(), R.color.oled_colorSecondary));
|
itemView.setBackgroundTintList(ContextCompat.getColorStateList(itemView.getContext(), R.color.oled_colorSecondary));
|
||||||
} else if(coinsInfo.isFrozen() || UTXOService.instance.isCoinFrozen(coinsInfo)) {
|
} else if(coinsInfo.isFrozen() || UTXOService.instance.isCoinFrozen(coinsInfo)) {
|
||||||
|
|
|
@ -99,14 +99,14 @@ public class SubaddressAdapter extends RecyclerView.Adapter<SubaddressAdapter.Vi
|
||||||
TextView addressLabelTextView = itemView.findViewById(R.id.address_label_textview);
|
TextView addressLabelTextView = itemView.findViewById(R.id.address_label_textview);
|
||||||
TextView addressAmountTextView = itemView.findViewById(R.id.address_amount_textview);
|
TextView addressAmountTextView = itemView.findViewById(R.id.address_amount_textview);
|
||||||
|
|
||||||
addressTextView.setText(subaddress.getAddress());
|
addressTextView.setText(subaddress.address);
|
||||||
|
|
||||||
final String label = subaddress.getDisplayLabel();
|
final String label = subaddress.getDisplayLabel();
|
||||||
final String address = itemView.getContext().getString(R.string.subbaddress_info_subtitle,
|
final String address = itemView.getContext().getString(R.string.subbaddress_info_subtitle,
|
||||||
subaddress.getAddressIndex(), subaddress.getSquashedAddress());
|
subaddress.addressIndex, subaddress.getSquashedAddress());
|
||||||
addressLabelTextView.setText(label.isEmpty() ? address : label);
|
addressLabelTextView.setText(label.isEmpty() ? address : label);
|
||||||
|
|
||||||
final long amount = subaddress.getAmount();
|
final long amount = subaddress.amount;
|
||||||
if (amount > 0) {
|
if (amount > 0) {
|
||||||
boolean streetMode = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
|
boolean streetMode = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
|
||||||
if(streetMode) {
|
if(streetMode) {
|
||||||
|
|
|
@ -30,9 +30,7 @@ import androidx.recyclerview.widget.RecyclerView;
|
||||||
import com.google.android.material.progressindicator.CircularProgressIndicator;
|
import com.google.android.material.progressindicator.CircularProgressIndicator;
|
||||||
|
|
||||||
import net.mynero.wallet.R;
|
import net.mynero.wallet.R;
|
||||||
import net.mynero.wallet.data.UserNotes;
|
|
||||||
import net.mynero.wallet.model.TransactionInfo;
|
import net.mynero.wallet.model.TransactionInfo;
|
||||||
import net.mynero.wallet.model.Wallet;
|
|
||||||
import net.mynero.wallet.service.PrefService;
|
import net.mynero.wallet.service.PrefService;
|
||||||
import net.mynero.wallet.util.Constants;
|
import net.mynero.wallet.util.Constants;
|
||||||
import net.mynero.wallet.util.Helper;
|
import net.mynero.wallet.util.Helper;
|
||||||
|
|
|
@ -1,82 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 net.mynero.wallet.data;
|
|
||||||
|
|
||||||
// Nodes stolen from https://moneroworld.com/#nodes
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public enum DefaultNodes {
|
|
||||||
SAMOURAI("163.172.56.213", 18089, "mainnet", "SamouraiWallet"),
|
|
||||||
MONERUJO("nodex.monerujo.io", 18081, "mainnet", "monerujo"),
|
|
||||||
SUPPORTXMR("node.supportxmr.com", 18081, "mainnet", "SupportXMR"),
|
|
||||||
HASHVAULT("nodes.hashvault.pro", 18081, "mainnet", "Hashvault"),
|
|
||||||
MONEROWORLD("node.moneroworld.com", 18089, "mainnet", "MoneroWorld"),
|
|
||||||
XMRTW("opennode.xmr-tw.org", 18089, "mainnet", "XMRTW"),
|
|
||||||
MYNERO_I2P("ynk3hrwte23asonojqeskoulek2g2cd6tqg4neghnenfyljrvhga.b32.i2p", 0, "mainnet", "node.mysu.i2p"),
|
|
||||||
MYNERO_ONION("tiopyrxseconw73thwlv2pf5hebfcqxj5zdolym7z6pbq6gl4z7xz4ad.onion", 18081, "mainnet", "node.mysu.onion"),
|
|
||||||
SAMOURAI_ONION("446unwib5vc7pfbzflosy6m6vtyuhddnalr3hutyavwe4esfuu5g6ryd.onion", 18089, "mainnet", "SamouraiWallet.onion"),
|
|
||||||
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");
|
|
||||||
|
|
||||||
private final String address;
|
|
||||||
private final int port;
|
|
||||||
private final String network;
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
DefaultNodes(String address, int port, String network, String name) {
|
|
||||||
this.address = address;
|
|
||||||
this.port = port;
|
|
||||||
this.network = network;
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getNodeString() {
|
|
||||||
return address + ":" + port + "/" + network + "/" + name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONObject getJson() {
|
|
||||||
JSONObject jsonObject = new JSONObject();
|
|
||||||
try {
|
|
||||||
jsonObject.put("host", address);
|
|
||||||
jsonObject.put("rpcPort", port);
|
|
||||||
jsonObject.put("network", network);
|
|
||||||
if (!name.isEmpty())
|
|
||||||
jsonObject.put("name", name);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
}
|
|
40
app/src/main/java/net/mynero/wallet/data/DefaultNodes.kt
Normal file
40
app/src/main/java/net/mynero/wallet/data/DefaultNodes.kt
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
/*
|
||||||
|
* 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 net.mynero.wallet.data
|
||||||
|
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
// Nodes stolen from https://moneroworld.com/#nodes
|
||||||
|
enum class DefaultNodes(val address: String, private val port: Int, private val network: String, private val nodeName: String) {
|
||||||
|
SAMOURAI("163.172.56.213", 18089, "mainnet", "SamouraiWallet"), MONERUJO("nodex.monerujo.io", 18081, "mainnet", "monerujo"), SUPPORTXMR("node.supportxmr.com", 18081, "mainnet", "SupportXMR"), HASHVAULT("nodes.hashvault.pro", 18081, "mainnet", "Hashvault"), MONEROWORLD("node.moneroworld.com", 18089, "mainnet", "MoneroWorld"), XMRTW("opennode.xmr-tw.org", 18089, "mainnet", "XMRTW"), MYNERO_I2P("ynk3hrwte23asonojqeskoulek2g2cd6tqg4neghnenfyljrvhga.b32.i2p", 0, "mainnet", "node.mysu.i2p"), MYNERO_ONION("tiopyrxseconw73thwlv2pf5hebfcqxj5zdolym7z6pbq6gl4z7xz4ad.onion", 18081, "mainnet", "node.mysu.onion"), SAMOURAI_ONION("446unwib5vc7pfbzflosy6m6vtyuhddnalr3hutyavwe4esfuu5g6ryd.onion", 18089, "mainnet", "SamouraiWallet.onion"), 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");
|
||||||
|
|
||||||
|
val nodeString: String
|
||||||
|
get() = "$address:$port/$network/$nodeName"
|
||||||
|
val json: JSONObject
|
||||||
|
get() {
|
||||||
|
val jsonObject = JSONObject()
|
||||||
|
try {
|
||||||
|
jsonObject.put("host", address)
|
||||||
|
jsonObject.put("rpcPort", port)
|
||||||
|
jsonObject.put("network", network)
|
||||||
|
if (nodeName.isNotEmpty()) jsonObject.put("name", nodeName)
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
return jsonObject
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,382 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 net.mynero.wallet.data;
|
|
||||||
|
|
||||||
import net.mynero.wallet.model.NetworkType;
|
|
||||||
import net.mynero.wallet.model.WalletManager;
|
|
||||||
import net.mynero.wallet.util.OnionHelper;
|
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.UnsupportedEncodingException;
|
|
||||||
import java.net.InetSocketAddress;
|
|
||||||
import java.net.URLDecoder;
|
|
||||||
import java.net.URLEncoder;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class Node {
|
|
||||||
static public final String MAINNET = "mainnet";
|
|
||||||
static public final String STAGENET = "stagenet";
|
|
||||||
static public final String TESTNET = "testnet";
|
|
||||||
static private int DEFAULT_LEVIN_PORT = 0;
|
|
||||||
static private int DEFAULT_RPC_PORT = 0;
|
|
||||||
private NetworkType networkType;
|
|
||||||
private final boolean selected = false;
|
|
||||||
int rpcPort = 0;
|
|
||||||
private String name = null;
|
|
||||||
private String host;
|
|
||||||
private int levinPort = 0;
|
|
||||||
private String username = "";
|
|
||||||
private String password = "";
|
|
||||||
private boolean favourite = false;
|
|
||||||
|
|
||||||
|
|
||||||
public NetworkType getNetworkType() {
|
|
||||||
return networkType;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUsername() {
|
|
||||||
return this.username;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPassword() {
|
|
||||||
return this.password;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return this.name;
|
|
||||||
}
|
|
||||||
|
|
||||||
Node(String nodeString) {
|
|
||||||
if ((nodeString == null) || nodeString.isEmpty())
|
|
||||||
throw new IllegalArgumentException("daemon is empty");
|
|
||||||
String daemonAddress;
|
|
||||||
String[] a = nodeString.split("@");
|
|
||||||
if (a.length == 1) { // no credentials
|
|
||||||
daemonAddress = a[0];
|
|
||||||
username = "";
|
|
||||||
password = "";
|
|
||||||
} else if (a.length == 2) { // credentials
|
|
||||||
String[] userPassword = a[0].split(":");
|
|
||||||
if (userPassword.length != 2)
|
|
||||||
throw new IllegalArgumentException("User:Password invalid");
|
|
||||||
username = userPassword[0];
|
|
||||||
if (!username.isEmpty()) {
|
|
||||||
password = userPassword[1];
|
|
||||||
} else {
|
|
||||||
password = "";
|
|
||||||
}
|
|
||||||
daemonAddress = a[1];
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Too many @");
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] daParts = daemonAddress.split("/");
|
|
||||||
if ((daParts.length > 3) || (daParts.length < 1))
|
|
||||||
throw new IllegalArgumentException("Too many '/' or too few");
|
|
||||||
|
|
||||||
daemonAddress = daParts[0];
|
|
||||||
String[] da = daemonAddress.split(":");
|
|
||||||
if ((da.length > 2) || (da.length < 1))
|
|
||||||
throw new IllegalArgumentException("Too many ':' or too few");
|
|
||||||
String host = da[0];
|
|
||||||
|
|
||||||
if (daParts.length == 1) {
|
|
||||||
networkType = NetworkType.NetworkType_Mainnet;
|
|
||||||
} else {
|
|
||||||
switch (daParts[1]) {
|
|
||||||
case MAINNET:
|
|
||||||
networkType = NetworkType.NetworkType_Mainnet;
|
|
||||||
break;
|
|
||||||
case STAGENET:
|
|
||||||
networkType = NetworkType.NetworkType_Stagenet;
|
|
||||||
break;
|
|
||||||
case TESTNET:
|
|
||||||
networkType = NetworkType.NetworkType_Testnet;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("invalid net: " + daParts[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (networkType != WalletManager.getInstance().getNetworkType())
|
|
||||||
throw new IllegalArgumentException("wrong net: " + networkType);
|
|
||||||
|
|
||||||
String name = host;
|
|
||||||
if (daParts.length == 3) {
|
|
||||||
try {
|
|
||||||
name = URLDecoder.decode(daParts[2], "UTF-8");
|
|
||||||
} catch (UnsupportedEncodingException ex) {
|
|
||||||
Timber.w(ex); // if we can't encode it, we don't use it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.name = name;
|
|
||||||
|
|
||||||
int port;
|
|
||||||
if (da.length == 2) {
|
|
||||||
try {
|
|
||||||
port = Integer.parseInt(da[1]);
|
|
||||||
} catch (NumberFormatException ex) {
|
|
||||||
throw new IllegalArgumentException("Port not numeric");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
port = getDefaultRpcPort();
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
setHost(host);
|
|
||||||
} catch (UnknownHostException ex) {
|
|
||||||
throw new IllegalArgumentException("cannot resolve host " + host);
|
|
||||||
}
|
|
||||||
this.rpcPort = port;
|
|
||||||
this.levinPort = getDefaultLevinPort();
|
|
||||||
}
|
|
||||||
|
|
||||||
Node(JSONObject jsonObject) throws JSONException, UnknownHostException {
|
|
||||||
if (jsonObject == null)
|
|
||||||
throw new IllegalArgumentException("daemon is empty");
|
|
||||||
if(jsonObject.has("username")) {
|
|
||||||
username = jsonObject.getString("username");
|
|
||||||
}
|
|
||||||
if(jsonObject.has("password")) {
|
|
||||||
password = jsonObject.getString("password");
|
|
||||||
}
|
|
||||||
if(jsonObject.has("host")) {
|
|
||||||
setHost(jsonObject.getString("host"));
|
|
||||||
}
|
|
||||||
if(jsonObject.has("rpcPort")) {
|
|
||||||
this.rpcPort = jsonObject.getInt("rpcPort");
|
|
||||||
} else {
|
|
||||||
this.rpcPort = getDefaultRpcPort();
|
|
||||||
}
|
|
||||||
if(jsonObject.has("name")) {
|
|
||||||
this.name = jsonObject.getString("name");
|
|
||||||
}
|
|
||||||
|
|
||||||
if(jsonObject.has("network")) {
|
|
||||||
switch (jsonObject.getString("network")) {
|
|
||||||
case MAINNET:
|
|
||||||
networkType = NetworkType.NetworkType_Mainnet;
|
|
||||||
break;
|
|
||||||
case STAGENET:
|
|
||||||
networkType = NetworkType.NetworkType_Stagenet;
|
|
||||||
break;
|
|
||||||
case TESTNET:
|
|
||||||
networkType = NetworkType.NetworkType_Testnet;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalArgumentException("invalid net: " + jsonObject.getString("network"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (networkType != WalletManager.getInstance().getNetworkType())
|
|
||||||
throw new IllegalArgumentException("wrong net: " + networkType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Node() {
|
|
||||||
this.networkType = WalletManager.getInstance().getNetworkType();
|
|
||||||
}
|
|
||||||
|
|
||||||
// constructor used for created nodes from retrieved peer lists
|
|
||||||
public Node(InetSocketAddress socketAddress) {
|
|
||||||
this();
|
|
||||||
this.host = socketAddress.getHostString();
|
|
||||||
this.rpcPort = 0; // unknown
|
|
||||||
this.levinPort = socketAddress.getPort();
|
|
||||||
this.username = "";
|
|
||||||
this.password = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public Node(Node anotherNode) {
|
|
||||||
networkType = anotherNode.networkType;
|
|
||||||
overwriteWith(anotherNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
static public Node fromString(String nodeString) {
|
|
||||||
try {
|
|
||||||
return new Node(nodeString);
|
|
||||||
} catch (IllegalArgumentException ex) {
|
|
||||||
Timber.w(ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static public Node fromJson(JSONObject jsonObject) {
|
|
||||||
try {
|
|
||||||
return new Node(jsonObject);
|
|
||||||
} catch (IllegalArgumentException | UnknownHostException | JSONException ex) {
|
|
||||||
Timber.w(ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// every node knows its network, but they are all the same
|
|
||||||
static public int getDefaultLevinPort() {
|
|
||||||
if (DEFAULT_LEVIN_PORT > 0) return DEFAULT_LEVIN_PORT;
|
|
||||||
switch (WalletManager.getInstance().getNetworkType()) {
|
|
||||||
case NetworkType_Mainnet:
|
|
||||||
DEFAULT_LEVIN_PORT = 18080;
|
|
||||||
break;
|
|
||||||
case NetworkType_Testnet:
|
|
||||||
DEFAULT_LEVIN_PORT = 28080;
|
|
||||||
break;
|
|
||||||
case NetworkType_Stagenet:
|
|
||||||
DEFAULT_LEVIN_PORT = 38080;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType());
|
|
||||||
}
|
|
||||||
return DEFAULT_LEVIN_PORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
// every node knows its network, but they are all the same
|
|
||||||
static public int getDefaultRpcPort() {
|
|
||||||
if (DEFAULT_RPC_PORT > 0) return DEFAULT_RPC_PORT;
|
|
||||||
switch (WalletManager.getInstance().getNetworkType()) {
|
|
||||||
case NetworkType_Mainnet:
|
|
||||||
DEFAULT_RPC_PORT = 18081;
|
|
||||||
break;
|
|
||||||
case NetworkType_Testnet:
|
|
||||||
DEFAULT_RPC_PORT = 28081;
|
|
||||||
break;
|
|
||||||
case NetworkType_Stagenet:
|
|
||||||
DEFAULT_RPC_PORT = 38081;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("unsupported net " + WalletManager.getInstance().getNetworkType());
|
|
||||||
}
|
|
||||||
return DEFAULT_RPC_PORT;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return host.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 (host.equals(anotherNode.host)
|
|
||||||
&& (getAddress().equals(anotherNode.getAddress()))
|
|
||||||
&& (rpcPort == anotherNode.rpcPort)
|
|
||||||
&& (networkType == anotherNode.networkType))
|
|
||||||
&& (username.equals(anotherNode.getUsername()))
|
|
||||||
&& (password.equals(anotherNode.getPassword()));
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOnion() {
|
|
||||||
return OnionHelper.isOnionHost(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isI2P() {
|
|
||||||
return OnionHelper.isI2PHost(host);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String toNodeString() {
|
|
||||||
return toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public JSONObject toJson() {
|
|
||||||
JSONObject jsonObject = new JSONObject();
|
|
||||||
try {
|
|
||||||
if (!username.isEmpty() && !password.isEmpty()) {
|
|
||||||
jsonObject.put("username", username);
|
|
||||||
jsonObject.put("password", password);
|
|
||||||
}
|
|
||||||
jsonObject.put("host", host);
|
|
||||||
jsonObject.put("rpcPort", rpcPort);
|
|
||||||
switch (networkType) {
|
|
||||||
case NetworkType_Mainnet:
|
|
||||||
jsonObject.put("network", MAINNET);
|
|
||||||
break;
|
|
||||||
case NetworkType_Stagenet:
|
|
||||||
jsonObject.put("network", STAGENET);
|
|
||||||
break;
|
|
||||||
case NetworkType_Testnet:
|
|
||||||
jsonObject.put("network", TESTNET);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (!name.isEmpty())
|
|
||||||
jsonObject.put("name", name);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return jsonObject;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuilder sb = new StringBuilder();
|
|
||||||
if (!username.isEmpty() && !password.isEmpty()) {
|
|
||||||
sb.append(username).append(":").append(password).append("@");
|
|
||||||
}
|
|
||||||
sb.append(host).append(":").append(rpcPort);
|
|
||||||
sb.append("/");
|
|
||||||
switch (networkType) {
|
|
||||||
case NetworkType_Mainnet:
|
|
||||||
sb.append(MAINNET);
|
|
||||||
break;
|
|
||||||
case NetworkType_Stagenet:
|
|
||||||
sb.append(STAGENET);
|
|
||||||
break;
|
|
||||||
case NetworkType_Testnet:
|
|
||||||
sb.append(TESTNET);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (name != null)
|
|
||||||
try {
|
|
||||||
sb.append("/").append(URLEncoder.encode(name, "UTF-8"));
|
|
||||||
} catch (UnsupportedEncodingException ex) {
|
|
||||||
Timber.w(ex); // if we can't encode it, we don't store it
|
|
||||||
}
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return getHost() + ":" + getRpcPort();
|
|
||||||
}
|
|
||||||
|
|
||||||
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?)");
|
|
||||||
this.host = host;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void overwriteWith(Node anotherNode) {
|
|
||||||
if (networkType != anotherNode.networkType)
|
|
||||||
throw new IllegalStateException("network types do not match");
|
|
||||||
name = anotherNode.name;
|
|
||||||
host = anotherNode.host;
|
|
||||||
rpcPort = anotherNode.rpcPort;
|
|
||||||
levinPort = anotherNode.levinPort;
|
|
||||||
username = anotherNode.username;
|
|
||||||
password = anotherNode.password;
|
|
||||||
favourite = anotherNode.favourite;
|
|
||||||
}
|
|
||||||
}
|
|
295
app/src/main/java/net/mynero/wallet/data/Node.kt
Normal file
295
app/src/main/java/net/mynero/wallet/data/Node.kt
Normal file
|
@ -0,0 +1,295 @@
|
||||||
|
/*
|
||||||
|
* 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 net.mynero.wallet.data
|
||||||
|
|
||||||
|
import net.mynero.wallet.model.NetworkType
|
||||||
|
import net.mynero.wallet.model.WalletManager
|
||||||
|
import net.mynero.wallet.util.OnionHelper
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.UnsupportedEncodingException
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.URLDecoder
|
||||||
|
import java.net.URLEncoder
|
||||||
|
import java.net.UnknownHostException
|
||||||
|
|
||||||
|
class Node {
|
||||||
|
var networkType: NetworkType? = null
|
||||||
|
private set
|
||||||
|
var rpcPort = 0
|
||||||
|
var name: String? = null
|
||||||
|
private set
|
||||||
|
var host: String? = null
|
||||||
|
private var levinPort = 0
|
||||||
|
var username = ""
|
||||||
|
private set
|
||||||
|
var password = ""
|
||||||
|
private set
|
||||||
|
private var favourite = false
|
||||||
|
|
||||||
|
internal constructor(nodeString: String?) {
|
||||||
|
require(!nodeString.isNullOrEmpty()) { "daemon is empty" }
|
||||||
|
var daemonAddress: String
|
||||||
|
val a = nodeString.split("@".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
when (a.size) {
|
||||||
|
1 -> { // no credentials
|
||||||
|
daemonAddress = a[0]
|
||||||
|
this.username = ""
|
||||||
|
this.password = ""
|
||||||
|
}
|
||||||
|
2 -> { // credentials
|
||||||
|
val userPassword = a[0].split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
require(userPassword.size == 2) { "User:Password invalid" }
|
||||||
|
this.username = userPassword[0]
|
||||||
|
this.password = if (this.username.isNotEmpty()) {
|
||||||
|
userPassword[1]
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
daemonAddress = a[1]
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw IllegalArgumentException("Too many @")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val daParts = daemonAddress.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
require(!(daParts.size > 3 || daParts.isEmpty())) { "Too many '/' or too few" }
|
||||||
|
daemonAddress = daParts[0]
|
||||||
|
val da = daemonAddress.split(":".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
require(!(da.size > 2 || da.isEmpty())) { "Too many ':' or too few" }
|
||||||
|
val host = da[0]
|
||||||
|
this.networkType = if (daParts.size == 1) {
|
||||||
|
NetworkType.NetworkType_Mainnet
|
||||||
|
} else {
|
||||||
|
when (daParts[1]) {
|
||||||
|
MAINNET -> NetworkType.NetworkType_Mainnet
|
||||||
|
STAGENET -> NetworkType.NetworkType_Stagenet
|
||||||
|
TESTNET -> NetworkType.NetworkType_Testnet
|
||||||
|
else -> throw IllegalArgumentException("invalid net: " + daParts[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
require(networkType == WalletManager.instance?.networkType) { "wrong net: $networkType" }
|
||||||
|
var name: String? = host
|
||||||
|
if (daParts.size == 3) {
|
||||||
|
try {
|
||||||
|
name = URLDecoder.decode(daParts[2], "UTF-8")
|
||||||
|
} catch (ex: UnsupportedEncodingException) {
|
||||||
|
Timber.w(ex) // if we can't encode it, we don't use it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.name = name
|
||||||
|
val port: Int = if (da.size == 2) {
|
||||||
|
try {
|
||||||
|
da[1].toInt()
|
||||||
|
} catch (ex: NumberFormatException) {
|
||||||
|
throw IllegalArgumentException("Port not numeric")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.defaultRpcPort
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
this.host = host
|
||||||
|
} catch (ex: UnknownHostException) {
|
||||||
|
throw IllegalArgumentException("cannot resolve host $host")
|
||||||
|
}
|
||||||
|
this.rpcPort = port
|
||||||
|
this.levinPort = this.defaultLevinPort
|
||||||
|
}
|
||||||
|
|
||||||
|
internal constructor(jsonObject: JSONObject?) {
|
||||||
|
requireNotNull(jsonObject) { "daemon is empty" }
|
||||||
|
if (jsonObject.has("username")) {
|
||||||
|
username = jsonObject.getString("username")
|
||||||
|
}
|
||||||
|
if (jsonObject.has("password")) {
|
||||||
|
password = jsonObject.getString("password")
|
||||||
|
}
|
||||||
|
if (jsonObject.has("host")) {
|
||||||
|
this.host = jsonObject.getString("host")
|
||||||
|
}
|
||||||
|
this.rpcPort = if (jsonObject.has("rpcPort")) {
|
||||||
|
jsonObject.getInt("rpcPort")
|
||||||
|
} else {
|
||||||
|
defaultRpcPort
|
||||||
|
}
|
||||||
|
if (jsonObject.has("name")) {
|
||||||
|
this.name = jsonObject.getString("name")
|
||||||
|
}
|
||||||
|
if (jsonObject.has("network")) {
|
||||||
|
networkType = when (jsonObject.getString("network")) {
|
||||||
|
MAINNET -> NetworkType.NetworkType_Mainnet
|
||||||
|
STAGENET -> NetworkType.NetworkType_Stagenet
|
||||||
|
TESTNET -> NetworkType.NetworkType_Testnet
|
||||||
|
else -> throw IllegalArgumentException("invalid net: " + jsonObject.getString("network"))
|
||||||
|
}
|
||||||
|
require(networkType == WalletManager.instance?.networkType) { "wrong net: $networkType" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
networkType = WalletManager.instance?.networkType
|
||||||
|
}
|
||||||
|
|
||||||
|
// constructor used for created nodes from retrieved peer lists
|
||||||
|
constructor(socketAddress: InetSocketAddress) : this() {
|
||||||
|
host = socketAddress.hostString
|
||||||
|
rpcPort = 0 // unknown
|
||||||
|
levinPort = socketAddress.port
|
||||||
|
username = ""
|
||||||
|
password = ""
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(anotherNode: Node) {
|
||||||
|
networkType = anotherNode.networkType
|
||||||
|
overwriteWith(anotherNode)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return host.hashCode()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nodes are equal if they are the same host address:port & are on the same network
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (other !is Node) return false
|
||||||
|
return host == other.host && address == other.address && rpcPort == other.rpcPort && networkType == other.networkType && username == other.username && password == other.password
|
||||||
|
}
|
||||||
|
|
||||||
|
val isOnion: Boolean
|
||||||
|
get() = host?.let { OnionHelper.isOnionHost(it) } == true
|
||||||
|
val isI2P: Boolean
|
||||||
|
get() = host?.let { OnionHelper.isI2PHost(it) } == true
|
||||||
|
|
||||||
|
fun toNodeString(): String {
|
||||||
|
return toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toJson(): JSONObject {
|
||||||
|
val jsonObject = JSONObject()
|
||||||
|
try {
|
||||||
|
if (username.isNotEmpty() && password.isNotEmpty()) {
|
||||||
|
jsonObject.put("username", username)
|
||||||
|
jsonObject.put("password", password)
|
||||||
|
}
|
||||||
|
jsonObject.put("host", host)
|
||||||
|
jsonObject.put("rpcPort", rpcPort)
|
||||||
|
when (networkType) {
|
||||||
|
NetworkType.NetworkType_Mainnet -> jsonObject.put("network", MAINNET)
|
||||||
|
NetworkType.NetworkType_Stagenet -> jsonObject.put("network", STAGENET)
|
||||||
|
NetworkType.NetworkType_Testnet -> jsonObject.put("network", TESTNET)
|
||||||
|
null -> TODO()
|
||||||
|
}
|
||||||
|
if (name?.isNotEmpty() == true) jsonObject.put("name", name)
|
||||||
|
} catch (e: JSONException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
return jsonObject
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
if (username.isNotEmpty() && password.isNotEmpty()) {
|
||||||
|
sb.append(username).append(":").append(password).append("@")
|
||||||
|
}
|
||||||
|
sb.append(host).append(":").append(rpcPort)
|
||||||
|
sb.append("/")
|
||||||
|
when (networkType) {
|
||||||
|
NetworkType.NetworkType_Mainnet -> sb.append(MAINNET)
|
||||||
|
NetworkType.NetworkType_Stagenet -> sb.append(STAGENET)
|
||||||
|
NetworkType.NetworkType_Testnet -> sb.append(TESTNET)
|
||||||
|
null -> TODO()
|
||||||
|
}
|
||||||
|
if (name != null) try {
|
||||||
|
sb.append("/").append(URLEncoder.encode(name, "UTF-8"))
|
||||||
|
} catch (ex: UnsupportedEncodingException) {
|
||||||
|
Timber.w(ex) // if we can't encode it, we don't store it
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
val address: String
|
||||||
|
get() = "$host:$rpcPort"
|
||||||
|
|
||||||
|
private fun overwriteWith(anotherNode: Node) {
|
||||||
|
check(networkType == anotherNode.networkType) { "network types do not match" }
|
||||||
|
name = anotherNode.name
|
||||||
|
host = anotherNode.host
|
||||||
|
rpcPort = anotherNode.rpcPort
|
||||||
|
levinPort = anotherNode.levinPort
|
||||||
|
username = anotherNode.username
|
||||||
|
password = anotherNode.password
|
||||||
|
favourite = anotherNode.favourite
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val MAINNET = "mainnet"
|
||||||
|
const val STAGENET = "stagenet"
|
||||||
|
const val TESTNET = "testnet"
|
||||||
|
private var DEFAULT_LEVIN_PORT = 0
|
||||||
|
private var DEFAULT_RPC_PORT = 0
|
||||||
|
@JvmStatic
|
||||||
|
fun fromString(nodeString: String?): Node? {
|
||||||
|
return try {
|
||||||
|
Node(nodeString)
|
||||||
|
} catch (ex: IllegalArgumentException) {
|
||||||
|
Timber.w(ex)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun fromJson(jsonObject: JSONObject?): Node? {
|
||||||
|
return try {
|
||||||
|
Node(jsonObject)
|
||||||
|
} catch (ex: IllegalArgumentException) {
|
||||||
|
Timber.w(ex)
|
||||||
|
null
|
||||||
|
} catch (ex: UnknownHostException) {
|
||||||
|
Timber.w(ex)
|
||||||
|
null
|
||||||
|
} catch (ex: JSONException) {
|
||||||
|
Timber.w(ex)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val defaultRpcPort: Int
|
||||||
|
// every node knows its network, but they are all the same
|
||||||
|
get() {
|
||||||
|
if (DEFAULT_RPC_PORT > 0) return DEFAULT_RPC_PORT
|
||||||
|
DEFAULT_RPC_PORT = when (WalletManager.instance?.networkType) {
|
||||||
|
NetworkType.NetworkType_Mainnet -> 18081
|
||||||
|
NetworkType.NetworkType_Testnet -> 28081
|
||||||
|
NetworkType.NetworkType_Stagenet -> 38081
|
||||||
|
else -> throw IllegalStateException("unsupported net " + WalletManager.instance?.networkType)
|
||||||
|
}
|
||||||
|
return DEFAULT_RPC_PORT
|
||||||
|
}
|
||||||
|
|
||||||
|
private val defaultLevinPort: Int
|
||||||
|
// every node knows its network, but they are all the same
|
||||||
|
get() {
|
||||||
|
if (DEFAULT_LEVIN_PORT > 0) return DEFAULT_LEVIN_PORT
|
||||||
|
DEFAULT_LEVIN_PORT = when (WalletManager.instance?.networkType) {
|
||||||
|
NetworkType.NetworkType_Mainnet -> 18080
|
||||||
|
NetworkType.NetworkType_Testnet -> 28080
|
||||||
|
NetworkType.NetworkType_Stagenet -> 38080
|
||||||
|
else -> throw IllegalStateException("unsupported net " + WalletManager.instance?.networkType)
|
||||||
|
}
|
||||||
|
return DEFAULT_LEVIN_PORT
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,78 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 net.mynero.wallet.data;
|
|
||||||
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
public class Subaddress implements Comparable<Subaddress> {
|
|
||||||
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}$");
|
|
||||||
final private int accountIndex;
|
|
||||||
final private int addressIndex;
|
|
||||||
final private String address;
|
|
||||||
private final String label;
|
|
||||||
private long amount;
|
|
||||||
|
|
||||||
public Subaddress(int accountIndex, int addressIndex, String address, String label) {
|
|
||||||
this.accountIndex = accountIndex;
|
|
||||||
this.addressIndex = addressIndex;
|
|
||||||
this.address = address;
|
|
||||||
this.label = label;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(Subaddress another) { // newer is <
|
|
||||||
final int compareAccountIndex = another.accountIndex - accountIndex;
|
|
||||||
if (compareAccountIndex == 0)
|
|
||||||
return another.addressIndex - addressIndex;
|
|
||||||
return compareAccountIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSquashedAddress() {
|
|
||||||
return address.substring(0, 8) + "…" + address.substring(address.length() - 8);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDisplayLabel() {
|
|
||||||
if (label.isEmpty() || (DEFAULT_LABEL_FORMATTER.matcher(label).matches()))
|
|
||||||
return ("#" + addressIndex);
|
|
||||||
else
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAmount() {
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAccountIndex() {
|
|
||||||
return accountIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getAddressIndex() {
|
|
||||||
return addressIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLabel() {
|
|
||||||
return label;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAmount(long amount) {
|
|
||||||
this.amount = amount;
|
|
||||||
}
|
|
||||||
}
|
|
38
app/src/main/java/net/mynero/wallet/data/Subaddress.kt
Normal file
38
app/src/main/java/net/mynero/wallet/data/Subaddress.kt
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
/*
|
||||||
|
* 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 net.mynero.wallet.data
|
||||||
|
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
class Subaddress(private val accountIndex: Int, @JvmField val addressIndex: Int, @JvmField val address: String, val label: String) : Comparable<Subaddress> {
|
||||||
|
@JvmField
|
||||||
|
var amount: Long = 0
|
||||||
|
|
||||||
|
override fun compareTo(other: Subaddress): Int { // newer is <
|
||||||
|
val compareAccountIndex = other.accountIndex - accountIndex
|
||||||
|
return if (compareAccountIndex == 0) other.addressIndex - addressIndex else compareAccountIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
val squashedAddress: String
|
||||||
|
get() = address.substring(0, 8) + "…" + address.substring(address.length - 8)
|
||||||
|
val displayLabel: String
|
||||||
|
get() = if (label.isEmpty() || DEFAULT_LABEL_FORMATTER.matcher(label).matches()) "#$addressIndex" else label
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val DEFAULT_LABEL_FORMATTER: Pattern = Pattern.compile("^[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{2}:[0-9]{2}:[0-9]{2}$")
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,157 +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 net.mynero.wallet.data;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import net.mynero.wallet.model.PendingTransaction;
|
|
||||||
import net.mynero.wallet.model.Wallet;
|
|
||||||
import net.mynero.wallet.util.Helper;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
|
|
||||||
public class TxData implements Parcelable {
|
|
||||||
|
|
||||||
// this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
|
|
||||||
public static final Parcelable.Creator<TxData> CREATOR = new Parcelable.Creator<TxData>() {
|
|
||||||
public TxData createFromParcel(Parcel in) {
|
|
||||||
return new TxData(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
public TxData[] newArray(int size) {
|
|
||||||
return new TxData[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
private String dstAddr;
|
|
||||||
private long amount;
|
|
||||||
private int mixin;
|
|
||||||
private PendingTransaction.Priority priority;
|
|
||||||
private UserNotes userNotes;
|
|
||||||
private ArrayList<String> preferredInputs;
|
|
||||||
|
|
||||||
public TxData() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public TxData(TxData txData) {
|
|
||||||
this.dstAddr = txData.dstAddr;
|
|
||||||
this.amount = txData.amount;
|
|
||||||
this.mixin = txData.mixin;
|
|
||||||
this.priority = txData.priority;
|
|
||||||
this.preferredInputs = txData.preferredInputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TxData(String dstAddr,
|
|
||||||
long amount,
|
|
||||||
int mixin,
|
|
||||||
PendingTransaction.Priority priority,
|
|
||||||
ArrayList<String> preferredInputs) {
|
|
||||||
this.dstAddr = dstAddr;
|
|
||||||
this.amount = amount;
|
|
||||||
this.mixin = mixin;
|
|
||||||
this.priority = priority;
|
|
||||||
this.preferredInputs = preferredInputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected TxData(Parcel in) {
|
|
||||||
dstAddr = in.readString();
|
|
||||||
amount = in.readLong();
|
|
||||||
mixin = in.readInt();
|
|
||||||
priority = PendingTransaction.Priority.fromInteger(in.readInt());
|
|
||||||
in.readStringList(preferredInputs);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDestinationAddress() {
|
|
||||||
return dstAddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setDestinationAddress(String dstAddr) {
|
|
||||||
this.dstAddr = dstAddr;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAmount() {
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAmount(long amount) {
|
|
||||||
this.amount = amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAmount(double amount) {
|
|
||||||
this.amount = Wallet.getAmountFromDouble(amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getAmountAsDouble() {
|
|
||||||
return 1.0 * amount / Helper.ONE_XMR;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ArrayList<String> getPreferredInputs() {
|
|
||||||
return preferredInputs;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getMixin() {
|
|
||||||
return mixin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setMixin(int mixin) {
|
|
||||||
this.mixin = mixin;
|
|
||||||
}
|
|
||||||
|
|
||||||
public PendingTransaction.Priority getPriority() {
|
|
||||||
return priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPriority(PendingTransaction.Priority priority) {
|
|
||||||
this.priority = priority;
|
|
||||||
}
|
|
||||||
|
|
||||||
public UserNotes getUserNotes() {
|
|
||||||
return userNotes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setUserNotes(UserNotes userNotes) {
|
|
||||||
this.userNotes = userNotes;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel out, int flags) {
|
|
||||||
out.writeString(dstAddr);
|
|
||||||
out.writeLong(amount);
|
|
||||||
out.writeInt(mixin);
|
|
||||||
out.writeInt(priority.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
StringBuffer sb = new StringBuffer();
|
|
||||||
sb.append("dstAddr:");
|
|
||||||
sb.append(dstAddr);
|
|
||||||
sb.append(",amount:");
|
|
||||||
sb.append(amount);
|
|
||||||
sb.append(",mixin:");
|
|
||||||
sb.append(mixin);
|
|
||||||
sb.append(",priority:");
|
|
||||||
sb.append(priority);
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
104
app/src/main/java/net/mynero/wallet/data/TxData.kt
Normal file
104
app/src/main/java/net/mynero/wallet/data/TxData.kt
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
/*
|
||||||
|
* 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 net.mynero.wallet.data
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.os.Parcelable.Creator
|
||||||
|
import net.mynero.wallet.model.PendingTransaction
|
||||||
|
import net.mynero.wallet.model.Wallet
|
||||||
|
import net.mynero.wallet.util.Helper
|
||||||
|
|
||||||
|
// https://stackoverflow.com/questions/2139134/how-to-send-an-object-from-one-android-activity-to-another-using-intents
|
||||||
|
class TxData : Parcelable {
|
||||||
|
var destinationAddress: String? = null
|
||||||
|
var amount: Long = 0
|
||||||
|
var mixin = 0
|
||||||
|
var priority: PendingTransaction.Priority? = null
|
||||||
|
var userNotes: UserNotes? = null
|
||||||
|
var preferredInputs: ArrayList<String>? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
constructor()
|
||||||
|
constructor(txData: TxData) {
|
||||||
|
destinationAddress = txData.destinationAddress
|
||||||
|
amount = txData.amount
|
||||||
|
mixin = txData.mixin
|
||||||
|
priority = txData.priority
|
||||||
|
preferredInputs = txData.preferredInputs
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(dstAddr: String?,
|
||||||
|
amount: Long,
|
||||||
|
mixin: Int,
|
||||||
|
priority: PendingTransaction.Priority?,
|
||||||
|
preferredInputs: ArrayList<String>?) {
|
||||||
|
destinationAddress = dstAddr
|
||||||
|
this.amount = amount
|
||||||
|
this.mixin = mixin
|
||||||
|
this.priority = priority
|
||||||
|
this.preferredInputs = preferredInputs
|
||||||
|
}
|
||||||
|
|
||||||
|
protected constructor(`in`: Parcel) {
|
||||||
|
destinationAddress = `in`.readString()
|
||||||
|
amount = `in`.readLong()
|
||||||
|
mixin = `in`.readInt()
|
||||||
|
priority = PendingTransaction.Priority.fromInteger(`in`.readInt())
|
||||||
|
`in`.readStringList(preferredInputs!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setAmount(amount: Double) {
|
||||||
|
this.amount = Wallet.getAmountFromDouble(amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
val amountAsDouble: Double
|
||||||
|
get() = 1.0 * amount / Helper.ONE_XMR
|
||||||
|
|
||||||
|
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||||
|
out.writeString(destinationAddress)
|
||||||
|
out.writeLong(amount)
|
||||||
|
out.writeInt(mixin)
|
||||||
|
out.writeInt(priority!!.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
val sb = StringBuffer()
|
||||||
|
sb.append("dstAddr:")
|
||||||
|
sb.append(destinationAddress)
|
||||||
|
sb.append(",amount:")
|
||||||
|
sb.append(amount)
|
||||||
|
sb.append(",mixin:")
|
||||||
|
sb.append(mixin)
|
||||||
|
sb.append(",priority:")
|
||||||
|
sb.append(priority)
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Creator<TxData> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): TxData {
|
||||||
|
return TxData(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<TxData?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,80 +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 net.mynero.wallet.data;
|
|
||||||
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
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) {
|
|
||||||
if (txNotes == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.txNotes = txNotes;
|
|
||||||
Pattern p = Pattern.compile("^\\{([a-z]+)-(\\w{6,}),([0-9.]*)([A-Z]+),(\\w*)\\} ?(.*)");
|
|
||||||
Matcher m = p.matcher(txNotes);
|
|
||||||
if (m.find()) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setNote(String newNote) {
|
|
||||||
if (newNote != null) {
|
|
||||||
note = newNote;
|
|
||||||
} else {
|
|
||||||
note = "";
|
|
||||||
}
|
|
||||||
txNotes = buildTxNote();
|
|
||||||
}
|
|
||||||
|
|
||||||
private String buildTxNote() {
|
|
||||||
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(xmrtoCurrency);
|
|
||||||
sb.append(",");
|
|
||||||
sb.append(xmrtoDestination);
|
|
||||||
sb.append("}");
|
|
||||||
if ((note != null) && (!note.isEmpty()))
|
|
||||||
sb.append(" ");
|
|
||||||
}
|
|
||||||
sb.append(note);
|
|
||||||
return sb.toString();
|
|
||||||
}
|
|
||||||
}
|
|
74
app/src/main/java/net/mynero/wallet/data/UserNotes.kt
Normal file
74
app/src/main/java/net/mynero/wallet/data/UserNotes.kt
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* 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 net.mynero.wallet.data
|
||||||
|
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
class UserNotes(txNotes: String?) {
|
||||||
|
var txNotes: String? = ""
|
||||||
|
@JvmField
|
||||||
|
var note: String? = ""
|
||||||
|
@JvmField
|
||||||
|
var xmrtoTag: String? = null
|
||||||
|
@JvmField
|
||||||
|
var xmrtoKey: String? = null
|
||||||
|
@JvmField
|
||||||
|
var xmrtoAmount: String? = null // could be a double - but we are not doing any calculations
|
||||||
|
var xmrtoCurrency: String? = null
|
||||||
|
@JvmField
|
||||||
|
var xmrtoDestination: String? = null
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.txNotes = txNotes
|
||||||
|
val p = Pattern.compile("^\\{([a-z]+)-(\\w{6,}),([0-9.]*)([A-Z]+),(\\w*)\\} ?(.*)")
|
||||||
|
val m = p.matcher(txNotes)
|
||||||
|
if (m.find()) {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setNote(newNote: String?) {
|
||||||
|
note = newNote ?: ""
|
||||||
|
txNotes = buildTxNote()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun buildTxNote(): String {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
if (xmrtoKey != null) {
|
||||||
|
require(!(xmrtoAmount == null || xmrtoDestination == null)) { "Broken notes" }
|
||||||
|
sb.append("{")
|
||||||
|
sb.append(xmrtoTag)
|
||||||
|
sb.append("-")
|
||||||
|
sb.append(xmrtoKey)
|
||||||
|
sb.append(",")
|
||||||
|
sb.append(xmrtoAmount)
|
||||||
|
sb.append(xmrtoCurrency)
|
||||||
|
sb.append(",")
|
||||||
|
sb.append(xmrtoDestination)
|
||||||
|
sb.append("}")
|
||||||
|
if (note != null && !note!!.isEmpty()) sb.append(" ")
|
||||||
|
}
|
||||||
|
sb.append(note)
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
}
|
|
@ -106,7 +106,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
|
||||||
donateTextView = view.findViewById(R.id.donate_label_textview);
|
donateTextView = view.findViewById(R.id.donate_label_textview);
|
||||||
donateTextView.setOnClickListener(view1 -> addressEditText.setText(Constants.DONATE_ADDRESS));
|
donateTextView.setOnClickListener(view1 -> addressEditText.setText(Constants.DONATE_ADDRESS));
|
||||||
if (uriData != null) {
|
if (uriData != null) {
|
||||||
addressEditText.setText(uriData.getAddress());
|
addressEditText.setText(uriData.address);
|
||||||
if (uriData.hasAmount()) {
|
if (uriData.hasAmount()) {
|
||||||
amountEditText.setText(uriData.getAmount());
|
amountEditText.setText(uriData.getAmount());
|
||||||
}
|
}
|
||||||
|
@ -116,8 +116,8 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
|
||||||
long selectedValue = 0;
|
long selectedValue = 0;
|
||||||
|
|
||||||
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
||||||
if (selectedUtxos.contains(coinsInfo.getKeyImage())) {
|
if (selectedUtxos.contains(coinsInfo.keyImage)) {
|
||||||
selectedValue += coinsInfo.getAmount();
|
selectedValue += coinsInfo.amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,7 +338,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment {
|
||||||
private void pasteAddress(String address) {
|
private void pasteAddress(String address) {
|
||||||
UriData uriData = UriData.parse(address);
|
UriData uriData = UriData.parse(address);
|
||||||
if (uriData != null) {
|
if (uriData != null) {
|
||||||
addressEditText.setText(uriData.getAddress());
|
addressEditText.setText(uriData.address);
|
||||||
if (uriData.hasAmount()) {
|
if (uriData.hasAmount()) {
|
||||||
amountEditText.setText(uriData.getAmount());
|
amountEditText.setText(uriData.getAmount());
|
||||||
}
|
}
|
||||||
|
|
|
@ -132,7 +132,7 @@ public class OnboardingViewModel extends ViewModel {
|
||||||
} else {
|
} else {
|
||||||
mainActivity.runOnUiThread(() -> {
|
mainActivity.runOnUiThread(() -> {
|
||||||
_enableCreateButton.postValue(true);
|
_enableCreateButton.postValue(true);
|
||||||
Toast.makeText(mainActivity, application.getString(R.string.create_wallet_failed, walletStatus.getErrorString()), Toast.LENGTH_SHORT).show();
|
Toast.makeText(mainActivity, application.getString(R.string.create_wallet_failed, walletStatus.errorString), Toast.LENGTH_SHORT).show();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -91,7 +91,7 @@ public class ReceiveFragment extends Fragment {
|
||||||
|
|
||||||
private void editAddressLabel(Subaddress subaddress) {
|
private void editAddressLabel(Subaddress subaddress) {
|
||||||
EditAddressLabelBottomSheetDialog dialog = new EditAddressLabelBottomSheetDialog();
|
EditAddressLabelBottomSheetDialog dialog = new EditAddressLabelBottomSheetDialog();
|
||||||
dialog.addressIndex = subaddress.getAddressIndex();
|
dialog.addressIndex = subaddress.addressIndex;
|
||||||
dialog.listener = () -> mViewModel.init();
|
dialog.listener = () -> mViewModel.init();
|
||||||
dialog.show(getParentFragmentManager(), "edit_address_dialog");
|
dialog.show(getParentFragmentManager(), "edit_address_dialog");
|
||||||
}
|
}
|
||||||
|
@ -99,11 +99,11 @@ public class ReceiveFragment extends Fragment {
|
||||||
private void setAddress(Subaddress subaddress) {
|
private void setAddress(Subaddress subaddress) {
|
||||||
final String label = subaddress.getDisplayLabel();
|
final String label = subaddress.getDisplayLabel();
|
||||||
final String address = getContext().getString(R.string.subbaddress_info_subtitle,
|
final String address = getContext().getString(R.string.subbaddress_info_subtitle,
|
||||||
subaddress.getAddressIndex(), subaddress.getSquashedAddress());
|
subaddress.addressIndex, subaddress.getSquashedAddress());
|
||||||
addressLabelTextView.setText(label.isEmpty() ? address : label);
|
addressLabelTextView.setText(label.isEmpty() ? address : label);
|
||||||
addressTextView.setText(subaddress.getAddress());
|
addressTextView.setText(subaddress.address);
|
||||||
addressImageView.setImageBitmap(generate(subaddress.getAddress(), 256, 256));
|
addressImageView.setImageBitmap(generate(subaddress.address, 256, 256));
|
||||||
copyAddressImageButton.setOnClickListener(view1 -> Helper.clipBoardCopy(getContext(), "address", subaddress.getAddress()));
|
copyAddressImageButton.setOnClickListener(view1 -> Helper.clipBoardCopy(getContext(), "address", subaddress.address));
|
||||||
addressLabelTextView.setOnLongClickListener(v -> {
|
addressLabelTextView.setOnLongClickListener(v -> {
|
||||||
editAddressLabel(subaddress);
|
editAddressLabel(subaddress);
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -411,7 +411,7 @@ public class SendFragment extends Fragment {
|
||||||
mViewModel.setShowAddOutputButton(false);
|
mViewModel.setShowAddOutputButton(false);
|
||||||
}
|
}
|
||||||
EditText addressField = entryView.findViewById(R.id.address_edittext);
|
EditText addressField = entryView.findViewById(R.id.address_edittext);
|
||||||
addressField.setText(uriData.getAddress());
|
addressField.setText(uriData.address);
|
||||||
if (uriData.hasAmount()) {
|
if (uriData.hasAmount()) {
|
||||||
setAmount(entryView, uriData.getAmount());
|
setAmount(entryView, uriData.getAmount());
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
|
||||||
sendUtxosButton.setOnClickListener(view1 -> {
|
sendUtxosButton.setOnClickListener(view1 -> {
|
||||||
ArrayList<String> selectedKeyImages = new ArrayList<>();
|
ArrayList<String> selectedKeyImages = new ArrayList<>();
|
||||||
for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) {
|
for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) {
|
||||||
selectedKeyImages.add(coinsInfo.getKeyImage());
|
selectedKeyImages.add(coinsInfo.keyImage);
|
||||||
}
|
}
|
||||||
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
|
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
|
||||||
sendDialog.listener = this;
|
sendDialog.listener = this;
|
||||||
|
@ -82,12 +82,12 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
|
||||||
churnUtxosButton.setOnClickListener(view1 -> {
|
churnUtxosButton.setOnClickListener(view1 -> {
|
||||||
ArrayList<String> selectedKeyImages = new ArrayList<>();
|
ArrayList<String> selectedKeyImages = new ArrayList<>();
|
||||||
for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) {
|
for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) {
|
||||||
selectedKeyImages.add(coinsInfo.getKeyImage());
|
selectedKeyImages.add(coinsInfo.keyImage);
|
||||||
}
|
}
|
||||||
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
|
SendBottomSheetDialog sendDialog = new SendBottomSheetDialog();
|
||||||
sendDialog.listener = this;
|
sendDialog.listener = this;
|
||||||
sendDialog.isChurning = true;
|
sendDialog.isChurning = true;
|
||||||
sendDialog.uriData = UriData.parse(AddressService.getInstance().currentSubaddress().getAddress());
|
sendDialog.uriData = UriData.parse(AddressService.getInstance().currentSubaddress().address);
|
||||||
sendDialog.selectedUtxos = selectedKeyImages;
|
sendDialog.selectedUtxos = selectedKeyImages;
|
||||||
sendDialog.show(getActivity().getSupportFragmentManager(), null);
|
sendDialog.show(getActivity().getSupportFragmentManager(), null);
|
||||||
});
|
});
|
||||||
|
@ -103,7 +103,7 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf
|
||||||
HashMap<String, CoinsInfo> filteredUtxos = new HashMap<>();
|
HashMap<String, CoinsInfo> filteredUtxos = new HashMap<>();
|
||||||
for (CoinsInfo coinsInfo : utxos) {
|
for (CoinsInfo coinsInfo : utxos) {
|
||||||
if (!coinsInfo.isSpent()) {
|
if (!coinsInfo.isSpent()) {
|
||||||
filteredUtxos.put(coinsInfo.getPubKey(), coinsInfo);
|
filteredUtxos.put(coinsInfo.pubKey, coinsInfo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (filteredUtxos.isEmpty()) {
|
if (filteredUtxos.isEmpty()) {
|
||||||
|
|
|
@ -13,65 +13,58 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.livedata
|
||||||
|
|
||||||
package net.mynero.wallet.livedata;
|
import android.util.Log
|
||||||
|
import androidx.annotation.MainThread
|
||||||
import android.util.Log;
|
import androidx.lifecycle.LifecycleOwner
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.annotation.MainThread;
|
import androidx.lifecycle.Observer
|
||||||
import androidx.annotation.Nullable;
|
import java.util.concurrent.atomic.AtomicBoolean
|
||||||
import androidx.lifecycle.LifecycleOwner;
|
|
||||||
import androidx.lifecycle.MutableLiveData;
|
|
||||||
import androidx.lifecycle.Observer;
|
|
||||||
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
|
* A lifecycle-aware observable that sends only new updates after subscription, used for events like
|
||||||
* navigation and Snackbar messages.
|
* navigation and Snackbar messages.
|
||||||
* <p>
|
*
|
||||||
|
*
|
||||||
* This avoids a common problem with events: on configuration change (like rotation) an update
|
* This avoids a common problem with events: on configuration change (like rotation) an update
|
||||||
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
|
* can be emitted if the observer is active. This LiveData only calls the observable if there's an
|
||||||
* explicit call to setValue() or call().
|
* explicit call to setValue() or call().
|
||||||
* <p>
|
*
|
||||||
|
*
|
||||||
* Note that only one observer is going to be notified of changes.
|
* Note that only one observer is going to be notified of changes.
|
||||||
*/
|
*/
|
||||||
public class SingleLiveEvent<T> extends MutableLiveData<T> {
|
class SingleLiveEvent<T> : MutableLiveData<T>() {
|
||||||
|
private val mPending = AtomicBoolean(false)
|
||||||
private static final String TAG = "SingleLiveEvent";
|
|
||||||
|
|
||||||
private final AtomicBoolean mPending = new AtomicBoolean(false);
|
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
@Override
|
override fun observe(owner: LifecycleOwner, observer: Observer<in T>) {
|
||||||
public void observe(LifecycleOwner owner, final Observer<? super T> observer) {
|
|
||||||
|
|
||||||
if (hasActiveObservers()) {
|
if (hasActiveObservers()) {
|
||||||
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.");
|
Log.w(TAG, "Multiple observers registered but only one will be notified of changes.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Observe the internal MutableLiveData
|
// Observe the internal MutableLiveData
|
||||||
super.observe(owner, new Observer<T>() {
|
super.observe(owner) { value ->
|
||||||
@Override
|
if (mPending.compareAndSet(true, false)) {
|
||||||
public void onChanged(@Nullable T t) {
|
observer.onChanged(value)
|
||||||
if (mPending.compareAndSet(true, false)) {
|
|
||||||
observer.onChanged(t);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@MainThread
|
@MainThread
|
||||||
public void setValue(@Nullable T t) {
|
override fun setValue(t: T?) {
|
||||||
mPending.set(true);
|
mPending.set(true)
|
||||||
super.setValue(t);
|
super.setValue(t)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used for cases where T is Void, to make calls cleaner.
|
* Used for cases where T is Void, to make calls cleaner.
|
||||||
*/
|
*/
|
||||||
@MainThread
|
@MainThread
|
||||||
public void call() {
|
fun call() {
|
||||||
setValue(null);
|
value = null
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "SingleLiveEvent"
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,48 +0,0 @@
|
||||||
package net.mynero.wallet.model;
|
|
||||||
|
|
||||||
import net.mynero.wallet.service.PrefService;
|
|
||||||
import net.mynero.wallet.util.Constants;
|
|
||||||
|
|
||||||
public class BalanceInfo {
|
|
||||||
private final long rawUnlocked;
|
|
||||||
private final long rawLocked;
|
|
||||||
|
|
||||||
public BalanceInfo(long rawUnlocked, long rawLocked) {
|
|
||||||
this.rawUnlocked = rawUnlocked;
|
|
||||||
this.rawLocked = rawLocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getRawLocked() {
|
|
||||||
return rawLocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getRawUnlocked() {
|
|
||||||
return rawUnlocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUnlockedBalanceZero() {
|
|
||||||
return rawUnlocked == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isLockedBalanceZero() {
|
|
||||||
return rawLocked == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getUnlockedDisplay() {
|
|
||||||
boolean streetModeEnabled = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
|
|
||||||
if(streetModeEnabled) {
|
|
||||||
return Constants.STREET_MODE_BALANCE;
|
|
||||||
} else {
|
|
||||||
return Wallet.getDisplayAmount(rawUnlocked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getLockedDisplay() {
|
|
||||||
boolean streetModeEnabled = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false);
|
|
||||||
if(streetModeEnabled) {
|
|
||||||
return Constants.STREET_MODE_BALANCE;
|
|
||||||
} else {
|
|
||||||
return Wallet.getDisplayAmount(rawLocked);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
32
app/src/main/java/net/mynero/wallet/model/BalanceInfo.kt
Normal file
32
app/src/main/java/net/mynero/wallet/model/BalanceInfo.kt
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
package net.mynero.wallet.model
|
||||||
|
|
||||||
|
import net.mynero.wallet.service.PrefService
|
||||||
|
import net.mynero.wallet.util.Constants
|
||||||
|
|
||||||
|
class BalanceInfo(val rawUnlocked: Long, val rawLocked: Long) {
|
||||||
|
|
||||||
|
val isUnlockedBalanceZero: Boolean
|
||||||
|
get() = rawUnlocked == 0L
|
||||||
|
val isLockedBalanceZero: Boolean
|
||||||
|
get() = rawLocked == 0L
|
||||||
|
val unlockedDisplay: String
|
||||||
|
get() {
|
||||||
|
val streetModeEnabled =
|
||||||
|
PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false)
|
||||||
|
return if (streetModeEnabled) {
|
||||||
|
Constants.STREET_MODE_BALANCE
|
||||||
|
} else {
|
||||||
|
Wallet.getDisplayAmount(rawUnlocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val lockedDisplay: String
|
||||||
|
get() {
|
||||||
|
val streetModeEnabled =
|
||||||
|
PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false)
|
||||||
|
return if (streetModeEnabled) {
|
||||||
|
Constants.STREET_MODE_BALANCE
|
||||||
|
} else {
|
||||||
|
Wallet.getDisplayAmount(rawLocked)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,52 +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 net.mynero.wallet.model;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class Coins {
|
|
||||||
static {
|
|
||||||
System.loadLibrary("monerujo");
|
|
||||||
}
|
|
||||||
|
|
||||||
private final long handle;
|
|
||||||
|
|
||||||
private List<CoinsInfo> coins = new ArrayList<>();
|
|
||||||
|
|
||||||
public Coins(long handle) {
|
|
||||||
this.handle = handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public native int getCount(); // over all accounts/subaddresses
|
|
||||||
|
|
||||||
public List<CoinsInfo> getAll() {
|
|
||||||
return coins;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refresh() {
|
|
||||||
List<CoinsInfo> transactionInfos = refreshJ();
|
|
||||||
Timber.d("refresh size=%d", transactionInfos.size());
|
|
||||||
coins = transactionInfos;
|
|
||||||
}
|
|
||||||
|
|
||||||
public native void setFrozen(String publicKey, boolean frozen);
|
|
||||||
|
|
||||||
private native List<CoinsInfo> refreshJ();
|
|
||||||
}
|
|
39
app/src/main/java/net/mynero/wallet/model/Coins.kt
Normal file
39
app/src/main/java/net/mynero/wallet/model/Coins.kt
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* 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 net.mynero.wallet.model
|
||||||
|
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
class Coins(private val handle: Long) {
|
||||||
|
var all: List<CoinsInfo> = ArrayList()
|
||||||
|
private set
|
||||||
|
|
||||||
|
fun refresh() {
|
||||||
|
val transactionInfos = refreshJ()
|
||||||
|
Timber.d("refresh size=%d", transactionInfos.size)
|
||||||
|
all = transactionInfos
|
||||||
|
}
|
||||||
|
|
||||||
|
external fun setFrozen(publicKey: String?, frozen: Boolean)
|
||||||
|
private external fun refreshJ(): List<CoinsInfo>
|
||||||
|
external fun getCount(): Int
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
init {
|
||||||
|
System.loadLibrary("monerujo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,131 +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 net.mynero.wallet.model;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
public class CoinsInfo implements Parcelable, Comparable<CoinsInfo> {
|
|
||||||
public static final Creator<CoinsInfo> CREATOR = new Creator<CoinsInfo>() {
|
|
||||||
@Override
|
|
||||||
public CoinsInfo createFromParcel(Parcel in) {
|
|
||||||
return new CoinsInfo(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CoinsInfo[] newArray(int size) {
|
|
||||||
return new CoinsInfo[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static {
|
|
||||||
System.loadLibrary("monerujo");
|
|
||||||
}
|
|
||||||
|
|
||||||
long globalOutputIndex;
|
|
||||||
boolean spent;
|
|
||||||
String keyImage;
|
|
||||||
long amount;
|
|
||||||
String hash;
|
|
||||||
String pubKey;
|
|
||||||
boolean unlocked;
|
|
||||||
long localOutputIndex;
|
|
||||||
boolean frozen;
|
|
||||||
String address;
|
|
||||||
|
|
||||||
public CoinsInfo(long globalOutputIndex, boolean spent, String keyImage, long amount, String hash, String pubKey, boolean unlocked, long localOutputIndex, boolean frozen, String address) {
|
|
||||||
this.globalOutputIndex = globalOutputIndex;
|
|
||||||
this.spent = spent;
|
|
||||||
this.keyImage = keyImage;
|
|
||||||
this.amount = amount;
|
|
||||||
this.hash = hash;
|
|
||||||
this.pubKey = pubKey;
|
|
||||||
this.unlocked = unlocked;
|
|
||||||
this.localOutputIndex = localOutputIndex;
|
|
||||||
this.frozen = frozen;
|
|
||||||
this.address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected CoinsInfo(Parcel in) {
|
|
||||||
globalOutputIndex = in.readLong();
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getGlobalOutputIndex() {
|
|
||||||
return globalOutputIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isSpent() {
|
|
||||||
return spent;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getKeyImage() {
|
|
||||||
return keyImage;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getHash() {
|
|
||||||
return hash;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAmount() {
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPubKey() {
|
|
||||||
return pubKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isUnlocked() {
|
|
||||||
return unlocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getLocalOutputIndex() {
|
|
||||||
return localOutputIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isFrozen() {
|
|
||||||
return frozen;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(@NonNull Parcel parcel, int i) {
|
|
||||||
parcel.writeLong(globalOutputIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(CoinsInfo another) {
|
|
||||||
long b1 = this.amount;
|
|
||||||
long b2 = another.amount;
|
|
||||||
if (b1 > b2) {
|
|
||||||
return -1;
|
|
||||||
} else if (b1 < b2) {
|
|
||||||
return 1;
|
|
||||||
} else {
|
|
||||||
return this.hash.compareTo(another.hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
105
app/src/main/java/net/mynero/wallet/model/CoinsInfo.kt
Normal file
105
app/src/main/java/net/mynero/wallet/model/CoinsInfo.kt
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
/*
|
||||||
|
* 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 net.mynero.wallet.model
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.os.Parcelable.Creator
|
||||||
|
|
||||||
|
class CoinsInfo : Parcelable, Comparable<CoinsInfo> {
|
||||||
|
@JvmField
|
||||||
|
var globalOutputIndex: Long
|
||||||
|
var isSpent = false
|
||||||
|
@JvmField
|
||||||
|
var keyImage: String? = null
|
||||||
|
@JvmField
|
||||||
|
var amount: Long = 0
|
||||||
|
@JvmField
|
||||||
|
var hash: String? = null
|
||||||
|
@JvmField
|
||||||
|
var pubKey: String? = null
|
||||||
|
var isUnlocked = false
|
||||||
|
@JvmField
|
||||||
|
var localOutputIndex: Long = 0
|
||||||
|
var isFrozen = false
|
||||||
|
@JvmField
|
||||||
|
var address: String? = null
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
globalOutputIndex: Long,
|
||||||
|
spent: Boolean,
|
||||||
|
keyImage: String?,
|
||||||
|
amount: Long,
|
||||||
|
hash: String?,
|
||||||
|
pubKey: String?,
|
||||||
|
unlocked: Boolean,
|
||||||
|
localOutputIndex: Long,
|
||||||
|
frozen: Boolean,
|
||||||
|
address: String?
|
||||||
|
) {
|
||||||
|
this.globalOutputIndex = globalOutputIndex
|
||||||
|
isSpent = spent
|
||||||
|
this.keyImage = keyImage
|
||||||
|
this.amount = amount
|
||||||
|
this.hash = hash
|
||||||
|
this.pubKey = pubKey
|
||||||
|
isUnlocked = unlocked
|
||||||
|
this.localOutputIndex = localOutputIndex
|
||||||
|
isFrozen = frozen
|
||||||
|
this.address = address
|
||||||
|
}
|
||||||
|
|
||||||
|
protected constructor(`in`: Parcel) {
|
||||||
|
globalOutputIndex = `in`.readLong()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, i: Int) {
|
||||||
|
parcel.writeLong(globalOutputIndex)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compareTo(another: CoinsInfo): Int {
|
||||||
|
val b1 = amount
|
||||||
|
val b2 = another.amount
|
||||||
|
return if (b1 > b2) {
|
||||||
|
-1
|
||||||
|
} else if (b1 < b2) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
hash!!.compareTo(another.hash!!)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmField
|
||||||
|
val CREATOR: Creator<CoinsInfo?> = object : Creator<CoinsInfo?> {
|
||||||
|
override fun createFromParcel(`in`: Parcel): CoinsInfo? {
|
||||||
|
return CoinsInfo(`in`)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<CoinsInfo?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
System.loadLibrary("monerujo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,33 +13,20 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.model
|
||||||
|
|
||||||
package net.mynero.wallet.model;
|
enum class NetworkType(@JvmField val value: Int) {
|
||||||
|
NetworkType_Mainnet(0), NetworkType_Testnet(1), NetworkType_Stagenet(2);
|
||||||
|
|
||||||
public enum NetworkType {
|
companion object {
|
||||||
NetworkType_Mainnet(0),
|
@JvmStatic
|
||||||
NetworkType_Testnet(1),
|
fun fromInteger(n: Int): NetworkType? {
|
||||||
NetworkType_Stagenet(2);
|
when (n) {
|
||||||
|
0 -> return NetworkType_Mainnet
|
||||||
private final int value;
|
1 -> return NetworkType_Testnet
|
||||||
|
2 -> return NetworkType_Stagenet
|
||||||
NetworkType(int value) {
|
}
|
||||||
this.value = value;
|
return null
|
||||||
}
|
|
||||||
|
|
||||||
public static NetworkType fromInteger(int n) {
|
|
||||||
switch (n) {
|
|
||||||
case 0:
|
|
||||||
return NetworkType_Mainnet;
|
|
||||||
case 1:
|
|
||||||
return NetworkType_Testnet;
|
|
||||||
case 2:
|
|
||||||
return NetworkType_Stagenet;
|
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public int getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,98 +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 net.mynero.wallet.model;
|
|
||||||
|
|
||||||
public class PendingTransaction {
|
|
||||||
static {
|
|
||||||
System.loadLibrary("monerujo");
|
|
||||||
}
|
|
||||||
|
|
||||||
public long handle;
|
|
||||||
|
|
||||||
PendingTransaction(long handle) {
|
|
||||||
this.handle = handle;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Status getStatus() {
|
|
||||||
return Status.values()[getStatusJ()];
|
|
||||||
}
|
|
||||||
|
|
||||||
public native int getStatusJ();
|
|
||||||
|
|
||||||
public native String getErrorString();
|
|
||||||
|
|
||||||
// commit transaction or save to file if filename is provided.
|
|
||||||
public native boolean commit(String filename, boolean overwrite);
|
|
||||||
|
|
||||||
public native long getAmount();
|
|
||||||
|
|
||||||
public native long getDust();
|
|
||||||
|
|
||||||
public native long getFee();
|
|
||||||
|
|
||||||
public String getFirstTxId() {
|
|
||||||
String id = getFirstTxIdJ();
|
|
||||||
if (id == null)
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
|
|
||||||
public native String getFirstTxIdJ();
|
|
||||||
|
|
||||||
public native long getTxCount();
|
|
||||||
|
|
||||||
public enum Status {
|
|
||||||
Status_Ok,
|
|
||||||
Status_Error,
|
|
||||||
Status_Critical
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum Priority {
|
|
||||||
Priority_Default(0),
|
|
||||||
Priority_Low(1),
|
|
||||||
Priority_Medium(2),
|
|
||||||
Priority_High(3),
|
|
||||||
Priority_Last(4);
|
|
||||||
|
|
||||||
private final int value;
|
|
||||||
|
|
||||||
Priority(int value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Priority fromInteger(int n) {
|
|
||||||
switch (n) {
|
|
||||||
case 0:
|
|
||||||
return Priority_Default;
|
|
||||||
case 1:
|
|
||||||
return Priority_Low;
|
|
||||||
case 2:
|
|
||||||
return Priority_Medium;
|
|
||||||
case 3:
|
|
||||||
return Priority_High;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getValue() {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
/*
|
||||||
|
* 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 net.mynero.wallet.model
|
||||||
|
|
||||||
|
class PendingTransaction internal constructor(var handle: Long) {
|
||||||
|
val status: Status
|
||||||
|
get() = Status.values()[getStatusJ()]
|
||||||
|
external fun getStatusJ(): Int
|
||||||
|
external fun getErrorString(): String?
|
||||||
|
|
||||||
|
// commit transaction or save to file if filename is provided.
|
||||||
|
external fun commit(filename: String?, overwrite: Boolean): Boolean
|
||||||
|
|
||||||
|
external fun getAmount(): Long
|
||||||
|
external fun getDust(): Long
|
||||||
|
external fun getFee(): Long
|
||||||
|
val firstTxId: String
|
||||||
|
get() = getFirstTxIdJ() ?: throw IndexOutOfBoundsException()
|
||||||
|
external fun getFirstTxIdJ(): String?
|
||||||
|
external fun getTxCount(): Long
|
||||||
|
|
||||||
|
enum class Status {
|
||||||
|
Status_Ok, Status_Error, Status_Critical
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class Priority(@JvmField val value: Int) {
|
||||||
|
Priority_Default(0), Priority_Low(1), Priority_Medium(2), Priority_High(3), Priority_Last(4);
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromInteger(n: Int): Priority? {
|
||||||
|
when (n) {
|
||||||
|
0 -> return Priority_Default
|
||||||
|
1 -> return Priority_Low
|
||||||
|
2 -> return Priority_Medium
|
||||||
|
3 -> return Priority_High
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
init {
|
||||||
|
System.loadLibrary("monerujo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,69 +13,52 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.model
|
||||||
|
|
||||||
package net.mynero.wallet.model;
|
import timber.log.Timber
|
||||||
|
|
||||||
import java.util.ArrayList;
|
class TransactionHistory(private val handle: Long, var accountIndex: Int) {
|
||||||
import java.util.Iterator;
|
var all: List<TransactionInfo> = ArrayList()
|
||||||
import java.util.List;
|
private set
|
||||||
|
|
||||||
import timber.log.Timber;
|
fun setAccountFor(wallet: Wallet) {
|
||||||
|
|
||||||
public class TransactionHistory {
|
|
||||||
static {
|
|
||||||
System.loadLibrary("monerujo");
|
|
||||||
}
|
|
||||||
|
|
||||||
private final long handle;
|
|
||||||
|
|
||||||
int accountIndex;
|
|
||||||
private List<TransactionInfo> transactions = new ArrayList<>();
|
|
||||||
|
|
||||||
public TransactionHistory(long handle, int accountIndex) {
|
|
||||||
this.handle = handle;
|
|
||||||
this.accountIndex = accountIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setAccountFor(Wallet wallet) {
|
|
||||||
if (accountIndex != wallet.getAccountIndex()) {
|
if (accountIndex != wallet.getAccountIndex()) {
|
||||||
this.accountIndex = wallet.getAccountIndex();
|
accountIndex = wallet.getAccountIndex()
|
||||||
refreshWithNotes(wallet);
|
refreshWithNotes(wallet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadNotes(Wallet wallet) {
|
private fun loadNotes(wallet: Wallet) {
|
||||||
for (TransactionInfo info : transactions) {
|
for (info in all) {
|
||||||
info.notes = wallet.getUserNote(info.hash);
|
info.notes = wallet.getUserNote(info.hash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//private native long getTransactionByIndexJ(int i);
|
external fun getCount(): Int
|
||||||
|
|
||||||
//private native long getTransactionByIdJ(String id);
|
fun refreshWithNotes(wallet: Wallet) {
|
||||||
|
refresh()
|
||||||
public native int getCount(); // over all accounts/subaddresses
|
loadNotes(wallet)
|
||||||
|
|
||||||
public List<TransactionInfo> getAll() {
|
|
||||||
return transactions;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void refreshWithNotes(Wallet wallet) {
|
private fun refresh() {
|
||||||
refresh();
|
val transactionInfos = refreshJ()
|
||||||
loadNotes(wallet);
|
Timber.d("refresh size=%d", transactionInfos.size)
|
||||||
}
|
val iterator = transactionInfos.iterator()
|
||||||
|
while (iterator.hasNext()) {
|
||||||
private void refresh() {
|
val info = iterator.next()
|
||||||
List<TransactionInfo> transactionInfos = refreshJ();
|
|
||||||
Timber.d("refresh size=%d", transactionInfos.size());
|
|
||||||
for (Iterator<TransactionInfo> iterator = transactionInfos.iterator(); iterator.hasNext(); ) {
|
|
||||||
TransactionInfo info = iterator.next();
|
|
||||||
if (info.accountIndex != accountIndex) {
|
if (info.accountIndex != accountIndex) {
|
||||||
iterator.remove();
|
iterator.remove()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
transactions = transactionInfos;
|
all = transactionInfos
|
||||||
}
|
}
|
||||||
|
|
||||||
private native List<TransactionInfo> refreshJ();
|
private external fun refreshJ(): MutableList<TransactionInfo>
|
||||||
}
|
|
||||||
|
companion object {
|
||||||
|
init {
|
||||||
|
System.loadLibrary("monerujo")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,22 +0,0 @@
|
||||||
package net.mynero.wallet.model;
|
|
||||||
|
|
||||||
import net.mynero.wallet.service.PrefService;
|
|
||||||
import net.mynero.wallet.util.Constants;
|
|
||||||
|
|
||||||
public class TransactionOutput {
|
|
||||||
private final String destination;
|
|
||||||
private final long amount;
|
|
||||||
|
|
||||||
public TransactionOutput(String destination, long amount) {
|
|
||||||
this.destination = destination;
|
|
||||||
this.amount = amount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDestination() {
|
|
||||||
return destination;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getAmount() {
|
|
||||||
return amount;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
package net.mynero.wallet.model
|
||||||
|
|
||||||
|
class TransactionOutput(@JvmField val destination: String, @JvmField val amount: Long)
|
|
@ -1,56 +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 net.mynero.wallet.model;
|
|
||||||
|
|
||||||
import android.os.Parcel;
|
|
||||||
import android.os.Parcelable;
|
|
||||||
|
|
||||||
public class Transfer implements Parcelable {
|
|
||||||
public static final Parcelable.Creator<Transfer> CREATOR = new Parcelable.Creator<Transfer>() {
|
|
||||||
public Transfer createFromParcel(Parcel in) {
|
|
||||||
return new Transfer(in);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Transfer[] newArray(int size) {
|
|
||||||
return new Transfer[size];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
public long amount;
|
|
||||||
public String address;
|
|
||||||
|
|
||||||
public Transfer(long amount, String address) {
|
|
||||||
this.amount = amount;
|
|
||||||
this.address = address;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Transfer(Parcel in) {
|
|
||||||
amount = in.readLong();
|
|
||||||
address = in.readString();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void writeToParcel(Parcel out, int flags) {
|
|
||||||
out.writeLong(amount);
|
|
||||||
out.writeString(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int describeContents() {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
55
app/src/main/java/net/mynero/wallet/model/Transfer.kt
Normal file
55
app/src/main/java/net/mynero/wallet/model/Transfer.kt
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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 net.mynero.wallet.model
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.os.Parcelable.Creator
|
||||||
|
|
||||||
|
class Transfer : Parcelable {
|
||||||
|
var amount: Long
|
||||||
|
@JvmField
|
||||||
|
var address: String?
|
||||||
|
|
||||||
|
constructor(amount: Long, address: String?) {
|
||||||
|
this.amount = amount
|
||||||
|
this.address = address
|
||||||
|
}
|
||||||
|
|
||||||
|
private constructor(`in`: Parcel) {
|
||||||
|
amount = `in`.readLong()
|
||||||
|
address = `in`.readString()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||||
|
out.writeLong(amount)
|
||||||
|
out.writeString(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Creator<Transfer> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): Transfer {
|
||||||
|
return Transfer(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<Transfer?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,566 +13,464 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.model
|
||||||
|
|
||||||
package net.mynero.wallet.model;
|
import android.util.Pair
|
||||||
|
import net.mynero.wallet.data.Subaddress
|
||||||
|
import net.mynero.wallet.model.NetworkType.Companion.fromInteger
|
||||||
|
import net.mynero.wallet.model.WalletManager.Companion.instance
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
import android.util.Log;
|
class Wallet {
|
||||||
import android.util.Pair;
|
var isSynchronized = false
|
||||||
|
private var accountIndex = 0
|
||||||
|
private var handle: Long = 0
|
||||||
|
private var listenerHandle: Long = 0
|
||||||
|
private var pendingTransaction: PendingTransaction? = null
|
||||||
|
var history: TransactionHistory? = null
|
||||||
|
get() {
|
||||||
|
if (field == null) {
|
||||||
|
field = TransactionHistory(getHistoryJ(), accountIndex)
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
var coins: Coins? = null
|
||||||
|
get() {
|
||||||
|
if (field == null) {
|
||||||
|
field = Coins(getCoinsJ())
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
internal constructor(handle: Long) {
|
||||||
import androidx.annotation.Nullable;
|
this.handle = handle
|
||||||
|
|
||||||
import net.mynero.wallet.data.Subaddress;
|
|
||||||
import net.mynero.wallet.data.TxData;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class Wallet {
|
|
||||||
final static public long SWEEP_ALL = Long.MAX_VALUE;
|
|
||||||
private static final String NEW_ACCOUNT_NAME = "Untitled account"; // src/wallet/wallet2.cpp:941
|
|
||||||
|
|
||||||
static {
|
|
||||||
System.loadLibrary("monerujo");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean synced = false;
|
internal constructor(handle: Long, accountIndex: Int) {
|
||||||
private int accountIndex = 0;
|
this.handle = handle
|
||||||
private long handle = 0;
|
this.accountIndex = accountIndex
|
||||||
private long listenerHandle = 0;
|
|
||||||
private PendingTransaction pendingTransaction = null;
|
|
||||||
private TransactionHistory history = null;
|
|
||||||
private Coins coins = null;
|
|
||||||
|
|
||||||
Wallet(long handle) {
|
|
||||||
this.handle = handle;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Wallet(long handle, int accountIndex) {
|
fun getAccountIndex(): Int {
|
||||||
this.handle = handle;
|
return accountIndex
|
||||||
this.accountIndex = accountIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static native String getDisplayAmount(long amount);
|
fun setAccountIndex(accountIndex: Int) {
|
||||||
|
Timber.d("setAccountIndex(%d)", accountIndex)
|
||||||
public static native long getAmountFromString(String amount);
|
this.accountIndex = accountIndex
|
||||||
|
history?.setAccountFor(this)
|
||||||
public static native long getAmountFromDouble(double amount);
|
|
||||||
|
|
||||||
public static native String generatePaymentId();
|
|
||||||
|
|
||||||
public static native boolean isPaymentIdValid(String payment_id);
|
|
||||||
|
|
||||||
public static boolean isAddressValid(String address) {
|
|
||||||
return isAddressValid(address, WalletManager.getInstance().getNetworkType().getValue());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static native boolean isAddressValid(String address, int networkType);
|
val name: String
|
||||||
|
get() = getPath()?.let { File(it).name }.toString()
|
||||||
|
|
||||||
public static native String getPaymentIdFromAddress(String address, int networkType);
|
external fun getSeed(offset: String?): String?
|
||||||
|
external fun getLegacySeed(offset: String?): String?
|
||||||
|
external fun isPolyseedSupported(offset: String?): Boolean
|
||||||
|
external fun getSeedLanguage(): String?
|
||||||
|
|
||||||
public static native long getMaximumAllowedAmount();
|
val status: Status
|
||||||
|
get() = statusWithErrorString()
|
||||||
|
val fullStatus: Status
|
||||||
|
get() {
|
||||||
|
val walletStatus = statusWithErrorString()
|
||||||
|
walletStatus.connectionStatus = connectionStatus
|
||||||
|
return walletStatus
|
||||||
|
}
|
||||||
|
|
||||||
public int getAccountIndex() {
|
private external fun statusWithErrorString(): Status
|
||||||
return accountIndex;
|
@Synchronized
|
||||||
|
external fun setPassword(password: String?): Boolean
|
||||||
|
val address: String
|
||||||
|
get() = getAddress(accountIndex)
|
||||||
|
|
||||||
|
//TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
|
||||||
|
//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0;
|
||||||
|
fun getAddress(accountIndex: Int): String {
|
||||||
|
return getAddressJ(accountIndex, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setAccountIndex(int accountIndex) {
|
private fun getSubaddress(addressIndex: Int): String {
|
||||||
Timber.d("setAccountIndex(%d)", accountIndex);
|
return getAddressJ(accountIndex, addressIndex)
|
||||||
this.accountIndex = accountIndex;
|
|
||||||
getHistory().setAccountFor(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
fun getSubaddress(accountIndex: Int, addressIndex: Int): String {
|
||||||
return new File(getPath()).getName();
|
return getAddressJ(accountIndex, addressIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
public native String getSeed(String offset);
|
private external fun getAddressJ(accountIndex: Int, addressIndex: Int): String
|
||||||
|
private fun getSubaddressObject(accountIndex: Int, subAddressIndex: Int): Subaddress {
|
||||||
public native String getLegacySeed(String offset);
|
return Subaddress(
|
||||||
|
accountIndex, subAddressIndex,
|
||||||
public native boolean isPolyseedSupported(String offset);
|
getSubaddress(subAddressIndex), getSubaddressLabel(subAddressIndex)
|
||||||
|
)
|
||||||
public native String getSeedLanguage();
|
|
||||||
|
|
||||||
public native void setSeedLanguage(String language);
|
|
||||||
|
|
||||||
public Status getStatus() {
|
|
||||||
return statusWithErrorString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Status getFullStatus() {
|
fun getSubaddressObject(subAddressIndex: Int): Subaddress {
|
||||||
Wallet.Status walletStatus = statusWithErrorString();
|
val subaddress = getSubaddressObject(accountIndex, subAddressIndex)
|
||||||
walletStatus.setConnectionStatus(getConnectionStatus());
|
var amount: Long = 0
|
||||||
return walletStatus;
|
history?.let { history ->
|
||||||
}
|
for (info in history.all) {
|
||||||
|
if (info.addressIndex == subAddressIndex && info.direction == TransactionInfo.Direction.Direction_In) {
|
||||||
private native Status statusWithErrorString();
|
amount += info.amount
|
||||||
|
}
|
||||||
public native synchronized boolean setPassword(String password);
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return getAddress(accountIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO virtual void hardForkInfo(uint8_t &version, uint64_t &earliest_height) const = 0;
|
|
||||||
//TODO virtual bool useForkRules(uint8_t version, int64_t early_blocks) const = 0;
|
|
||||||
|
|
||||||
public String getAddress(int accountIndex) {
|
|
||||||
return getAddressJ(accountIndex, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSubaddress(int addressIndex) {
|
|
||||||
return getAddressJ(accountIndex, addressIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getSubaddress(int accountIndex, int addressIndex) {
|
|
||||||
return getAddressJ(accountIndex, addressIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
private native String getAddressJ(int accountIndex, int addressIndex);
|
|
||||||
|
|
||||||
public Subaddress getSubaddressObject(int accountIndex, int subAddressIndex) {
|
|
||||||
return new Subaddress(accountIndex, subAddressIndex,
|
|
||||||
getSubaddress(subAddressIndex), getSubaddressLabel(subAddressIndex));
|
|
||||||
}
|
|
||||||
|
|
||||||
public Subaddress getSubaddressObject(int subAddressIndex) {
|
|
||||||
Subaddress subaddress = getSubaddressObject(accountIndex, subAddressIndex);
|
|
||||||
long amount = 0;
|
|
||||||
for (TransactionInfo info : getHistory().getAll()) {
|
|
||||||
if ((info.addressIndex == subAddressIndex)
|
|
||||||
&& (info.direction == TransactionInfo.Direction.Direction_In)) {
|
|
||||||
amount += info.amount;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
subaddress.setAmount(amount);
|
|
||||||
return subaddress;
|
subaddress.amount = amount
|
||||||
|
return subaddress
|
||||||
|
}
|
||||||
|
external fun getPath(): String?
|
||||||
|
val networkType: NetworkType?
|
||||||
|
get() = fromInteger(nettype())
|
||||||
|
|
||||||
|
external fun nettype(): Int
|
||||||
|
|
||||||
|
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
|
||||||
|
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
|
||||||
|
external fun getIntegratedAddress(paymentId: String?): String?
|
||||||
|
external fun getSecretViewKey(): String
|
||||||
|
external fun getSecretSpendKey(): String
|
||||||
|
|
||||||
|
fun store(): Boolean {
|
||||||
|
return store("")
|
||||||
}
|
}
|
||||||
|
|
||||||
public native String getPath();
|
//TODO virtual void setTrustedDaemon(bool arg) = 0;
|
||||||
|
//TODO virtual bool trustedDaemon() const = 0;
|
||||||
public NetworkType getNetworkType() {
|
@Synchronized
|
||||||
return NetworkType.fromInteger(nettype());
|
external fun store(path: String?): Boolean
|
||||||
|
fun close(): Boolean {
|
||||||
|
disposePendingTransaction()
|
||||||
|
return instance?.close(this) == true
|
||||||
}
|
}
|
||||||
|
|
||||||
public native int nettype();
|
external fun getFilename(): String
|
||||||
|
|
||||||
// virtual bool createWatchOnly(const std::string &path, const std::string &password, const std::string &language) const = 0;
|
|
||||||
// virtual void setRefreshFromBlockHeight(uint64_t refresh_from_block_height) = 0;
|
|
||||||
|
|
||||||
public native String getIntegratedAddress(String payment_id);
|
|
||||||
|
|
||||||
public native String getSecretViewKey();
|
|
||||||
|
|
||||||
// virtual void setRecoveringFromSeed(bool recoveringFromSeed) = 0;
|
|
||||||
// virtual bool connectToDaemon() = 0;
|
|
||||||
|
|
||||||
public native String getSecretSpendKey();
|
|
||||||
|
|
||||||
public boolean store() {
|
|
||||||
return store("");
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO virtual void setTrustedDaemon(bool arg) = 0;
|
|
||||||
//TODO virtual bool trustedDaemon() const = 0;
|
|
||||||
|
|
||||||
public native synchronized boolean store(String path);
|
|
||||||
|
|
||||||
public boolean close() {
|
|
||||||
disposePendingTransaction();
|
|
||||||
return WalletManager.getInstance().close(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public native String getFilename();
|
|
||||||
|
|
||||||
// virtual std::string keysFilename() const = 0;
|
// virtual std::string keysFilename() const = 0;
|
||||||
public boolean init(long upper_transaction_size_limit) {
|
fun init(upperTransactionSizeLimit: Long): Boolean {
|
||||||
String daemon_address = WalletManager.getInstance().getDaemonAddress();
|
var daemonAddress = instance?.getDaemonAddress()
|
||||||
String daemon_username = WalletManager.getInstance().getDaemonUsername();
|
var daemonUsername = instance?.daemonUsername
|
||||||
String daemon_password = WalletManager.getInstance().getDaemonPassword();
|
var daemonPassword = instance?.daemonPassword
|
||||||
String proxy_address = WalletManager.getInstance().getProxy();
|
var proxyAddress = instance?.proxy
|
||||||
Log.d("Wallet.java", "init(");
|
Timber.d("init(")
|
||||||
if (daemon_address != null) {
|
if (daemonAddress != null) {
|
||||||
Log.d("Wallet.java", daemon_address.toString());
|
Timber.d(daemonAddress.toString())
|
||||||
} else {
|
} else {
|
||||||
Log.d("Wallet.java", "daemon_address == null");
|
Timber.d("daemon_address == null")
|
||||||
daemon_address = "";
|
daemonAddress = ""
|
||||||
}
|
}
|
||||||
Log.d("Wallet.java", "upper_transaction_size_limit = 0 (probably)");
|
Timber.d("upper_transaction_size_limit = 0 (probably)")
|
||||||
if (daemon_username != null) {
|
if (daemonUsername != null) {
|
||||||
Log.d("Wallet.java", daemon_username.toString());
|
Timber.d(daemonUsername)
|
||||||
} else {
|
} else {
|
||||||
Log.d("Wallet.java", "daemon_username == null");
|
Timber.d("daemon_username == null")
|
||||||
daemon_username = "";
|
daemonUsername = ""
|
||||||
}
|
}
|
||||||
if (daemon_password != null) {
|
if (daemonPassword != null) {
|
||||||
Log.d("Wallet.java", daemon_password.toString());
|
Timber.d(daemonPassword)
|
||||||
} else {
|
} else {
|
||||||
Log.d("Wallet.java", "daemon_password == null");
|
Timber.d("daemon_password == null")
|
||||||
daemon_password = "";
|
daemonPassword = ""
|
||||||
}
|
}
|
||||||
if (proxy_address != null) {
|
if (proxyAddress != null) {
|
||||||
Log.d("Wallet.java", proxy_address.toString());
|
Timber.d(proxyAddress)
|
||||||
} else {
|
} else {
|
||||||
Log.d("Wallet.java", "proxy_address == null");
|
Timber.d("proxy_address == null")
|
||||||
proxy_address = "";
|
proxyAddress = ""
|
||||||
}
|
}
|
||||||
Log.d("Wallet.java", ");");
|
Timber.d(");")
|
||||||
return initJ(daemon_address, upper_transaction_size_limit,
|
return initJ(
|
||||||
daemon_username, daemon_password,
|
daemonAddress, upperTransactionSizeLimit,
|
||||||
proxy_address);
|
daemonUsername, daemonPassword,
|
||||||
|
proxyAddress
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private external fun initJ(
|
||||||
|
daemonAddress: String, upperTransactionSizeLimit: Long,
|
||||||
|
daemonUsername: String, daemonPassword: String, proxyAddress: String
|
||||||
|
): Boolean
|
||||||
|
|
||||||
private native boolean initJ(String daemon_address, long upper_transaction_size_limit,
|
external fun getRestoreHeight(): Long
|
||||||
String daemon_username, String daemon_password, String proxy);
|
external fun setRestoreHeight(height: Long)
|
||||||
|
|
||||||
public native long getRestoreHeight();
|
private val connectionStatus: ConnectionStatus
|
||||||
|
get() {
|
||||||
|
val s = getConnectionStatusJ()
|
||||||
|
return ConnectionStatus.values()[s]
|
||||||
|
}
|
||||||
|
|
||||||
public native void setRestoreHeight(long height);
|
private external fun getConnectionStatusJ(): Int
|
||||||
|
|
||||||
public ConnectionStatus getConnectionStatus() {
|
external fun setProxy(address: String?): Boolean
|
||||||
int s = getConnectionStatusJ();
|
val balance: Long
|
||||||
return Wallet.ConnectionStatus.values()[s];
|
get() = getBalance(accountIndex)
|
||||||
|
|
||||||
|
private external fun getBalance(accountIndex: Int): Long
|
||||||
|
external fun getBalanceAll(): Long
|
||||||
|
val unlockedBalance: Long
|
||||||
|
get() = getUnlockedBalance(accountIndex)
|
||||||
|
external fun getUnlockedBalanceAll(): Long
|
||||||
|
|
||||||
|
external fun getUnlockedBalance(accountIndex: Int): Long
|
||||||
|
external fun isWatchOnly(): Boolean
|
||||||
|
external fun getBlockChainHeight(): Long
|
||||||
|
external fun getApproximateBlockChainHeight(): Long
|
||||||
|
external fun getDaemonBlockChainHeight(): Long
|
||||||
|
external fun getDaemonBlockChainTargetHeight(): Long
|
||||||
|
|
||||||
|
fun setSynchronized() {
|
||||||
|
isSynchronized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private native int getConnectionStatusJ();
|
external fun startRefresh()
|
||||||
|
external fun pauseRefresh()
|
||||||
public native boolean setProxy(String address);
|
external fun refresh(): Boolean
|
||||||
|
external fun refreshAsync()
|
||||||
public long getBalance() {
|
private external fun rescanBlockchainAsyncJ()
|
||||||
return getBalance(accountIndex);
|
fun rescanBlockchainAsync() {
|
||||||
|
isSynchronized = false
|
||||||
|
rescanBlockchainAsyncJ()
|
||||||
}
|
}
|
||||||
|
|
||||||
public native long getBalance(int accountIndex);
|
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
|
||||||
|
//TODO virtual int autoRefreshInterval() const = 0;
|
||||||
public native long getBalanceAll();
|
private fun disposePendingTransaction() {
|
||||||
|
|
||||||
public long getUnlockedBalance() {
|
|
||||||
return getUnlockedBalance(accountIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
public native long getUnlockedBalanceAll();
|
|
||||||
|
|
||||||
public native long getUnlockedBalance(int accountIndex);
|
|
||||||
|
|
||||||
public native boolean isWatchOnly();
|
|
||||||
|
|
||||||
public native long getBlockChainHeight();
|
|
||||||
|
|
||||||
public native long getApproximateBlockChainHeight();
|
|
||||||
|
|
||||||
public native long getDaemonBlockChainHeight();
|
|
||||||
|
|
||||||
public native long getDaemonBlockChainTargetHeight();
|
|
||||||
|
|
||||||
public boolean isSynchronized() {
|
|
||||||
return synced;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSynchronized() {
|
|
||||||
this.synced = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public native void startRefresh();
|
|
||||||
|
|
||||||
public native void pauseRefresh();
|
|
||||||
|
|
||||||
public native boolean refresh();
|
|
||||||
|
|
||||||
public native void refreshAsync();
|
|
||||||
|
|
||||||
public native void rescanBlockchainAsyncJ();
|
|
||||||
|
|
||||||
public void rescanBlockchainAsync() {
|
|
||||||
synced = false;
|
|
||||||
rescanBlockchainAsyncJ();
|
|
||||||
}
|
|
||||||
|
|
||||||
public PendingTransaction getPendingTransaction() {
|
|
||||||
return pendingTransaction;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO virtual void setAutoRefreshInterval(int millis) = 0;
|
|
||||||
//TODO virtual int autoRefreshInterval() const = 0;
|
|
||||||
|
|
||||||
public void disposePendingTransaction() {
|
|
||||||
if (pendingTransaction != null) {
|
if (pendingTransaction != null) {
|
||||||
disposeTransaction(pendingTransaction);
|
disposeTransaction(pendingTransaction)
|
||||||
pendingTransaction = null;
|
pendingTransaction = null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long estimateTransactionFee(List<Pair<String, Long>> destinations, PendingTransaction.Priority priority) {
|
fun estimateTransactionFee(
|
||||||
int _priority = priority.getValue();
|
destinations: List<Pair<String, Long>>,
|
||||||
return estimateTransactionFee(destinations, _priority);
|
priority: PendingTransaction.Priority
|
||||||
|
): Long {
|
||||||
|
val _priority = priority.value
|
||||||
|
return estimateTransactionFee(destinations, _priority)
|
||||||
}
|
}
|
||||||
|
|
||||||
private native long estimateTransactionFee(List<Pair<String, Long>> destinations, int priority);
|
private external fun estimateTransactionFee(
|
||||||
|
destinations: List<Pair<String, Long>>,
|
||||||
|
priority: Int
|
||||||
|
): Long
|
||||||
|
|
||||||
public PendingTransaction createSweepTransaction(String dst_addr, PendingTransaction.Priority priority, ArrayList<String> key_images) {
|
fun createSweepTransaction(
|
||||||
disposePendingTransaction();
|
dstAddr: String,
|
||||||
int _priority = priority.getValue();
|
priority: PendingTransaction.Priority,
|
||||||
long txHandle = createSweepTransaction(dst_addr, "", 0, _priority, accountIndex, key_images);
|
keyImages: ArrayList<String>
|
||||||
pendingTransaction = new PendingTransaction(txHandle);
|
): PendingTransaction? {
|
||||||
return pendingTransaction;
|
disposePendingTransaction()
|
||||||
|
val _priority = priority.value
|
||||||
|
val txHandle = createSweepTransaction(dstAddr, "", 0, _priority, accountIndex, keyImages)
|
||||||
|
pendingTransaction = PendingTransaction(txHandle)
|
||||||
|
return pendingTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
public PendingTransaction createTransactionMultDest(List<TransactionOutput> outputs, PendingTransaction.Priority priority, ArrayList<String> key_images) {
|
fun createTransactionMultDest(
|
||||||
disposePendingTransaction();
|
outputs: List<TransactionOutput>,
|
||||||
int _priority = priority.getValue();
|
priority: PendingTransaction.Priority,
|
||||||
ArrayList<String> destinations = new ArrayList<>();
|
keyImages: ArrayList<String>
|
||||||
long[] amounts = new long[outputs.size()];
|
): PendingTransaction? {
|
||||||
for(int i = 0; i < outputs.size(); i++) {
|
disposePendingTransaction()
|
||||||
TransactionOutput output = outputs.get(i);
|
val _priority = priority.value
|
||||||
destinations.add(output.getDestination());
|
val destinations = ArrayList<String>()
|
||||||
amounts[i] = output.getAmount();
|
val amounts = LongArray(outputs.size)
|
||||||
|
for (i in outputs.indices) {
|
||||||
|
val output = outputs[i]
|
||||||
|
destinations.add(output.destination)
|
||||||
|
amounts[i] = output.amount
|
||||||
}
|
}
|
||||||
long txHandle = createTransactionMultDestJ(destinations, "", amounts, 0, _priority,
|
val txHandle = createTransactionMultDestJ(
|
||||||
accountIndex, key_images);
|
destinations, "", amounts, 0, _priority,
|
||||||
pendingTransaction = new PendingTransaction(txHandle);
|
accountIndex, keyImages
|
||||||
return pendingTransaction;
|
)
|
||||||
|
pendingTransaction = PendingTransaction(txHandle)
|
||||||
|
return pendingTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
private native long createTransactionMultDestJ(ArrayList<String> dst_addrs, String payment_id,
|
private external fun createTransactionMultDestJ(
|
||||||
long[] amount, int mixin_count,
|
dstAddrs: ArrayList<String>, paymentId: String,
|
||||||
int priority, int accountIndex, ArrayList<String> key_images);
|
amount: LongArray, mixinCount: Int,
|
||||||
|
priority: Int, accountIndex: Int, keyImages: ArrayList<String>
|
||||||
|
): Long
|
||||||
|
|
||||||
private native long createTransactionJ(String dst_addr, String payment_id,
|
private external fun createTransactionJ(
|
||||||
long amount, int mixin_count,
|
dstAddr: String, paymentId: String,
|
||||||
int priority, int accountIndex, ArrayList<String> key_images);
|
amount: Long, mixinCount: Int,
|
||||||
|
priority: Int, accountIndex: Int, keyImages: ArrayList<String>
|
||||||
|
): Long
|
||||||
|
|
||||||
private native long createSweepTransaction(String dst_addr, String payment_id,
|
private external fun createSweepTransaction(
|
||||||
int mixin_count,
|
dstAddr: String, paymentId: String,
|
||||||
int priority, int accountIndex, ArrayList<String> key_images);
|
mixinCount: Int,
|
||||||
|
priority: Int, accountIndex: Int, keyImages: ArrayList<String>
|
||||||
|
): Long
|
||||||
|
|
||||||
public PendingTransaction createSweepUnmixableTransaction() {
|
fun createSweepUnmixableTransaction(): PendingTransaction? {
|
||||||
disposePendingTransaction();
|
disposePendingTransaction()
|
||||||
long txHandle = createSweepUnmixableTransactionJ();
|
val txHandle = createSweepUnmixableTransactionJ()
|
||||||
pendingTransaction = new PendingTransaction(txHandle);
|
pendingTransaction = PendingTransaction(txHandle)
|
||||||
return pendingTransaction;
|
return pendingTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
private native long createSweepUnmixableTransactionJ();
|
private external fun createSweepUnmixableTransactionJ(): Long
|
||||||
|
private external fun disposeTransaction(pendingTransaction: PendingTransaction?)
|
||||||
|
private external fun getHistoryJ(): Long
|
||||||
|
private external fun getCoinsJ(): Long
|
||||||
|
|
||||||
public native void disposeTransaction(PendingTransaction pendingTransaction);
|
//virtual bool exportKeyImages(const std::string &filename) = 0;
|
||||||
|
//virtual bool importKeyImages(const std::string &filename) = 0;
|
||||||
public TransactionHistory getHistory() {
|
//virtual TransactionHistory * history() const = 0;
|
||||||
if (history == null) {
|
fun refreshHistory() {
|
||||||
history = new TransactionHistory(getHistoryJ(), accountIndex);
|
history?.refreshWithNotes(this)
|
||||||
}
|
|
||||||
return history;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0;
|
fun refreshCoins() {
|
||||||
//virtual bool submitTransaction(const std::string &fileName) = 0;
|
if (isSynchronized) {
|
||||||
|
coins?.refresh()
|
||||||
private native long getHistoryJ();
|
|
||||||
|
|
||||||
public Coins getCoins() {
|
|
||||||
if (coins == null) {
|
|
||||||
coins = new Coins(getCoinsJ());
|
|
||||||
}
|
|
||||||
return coins;
|
|
||||||
}
|
|
||||||
|
|
||||||
private native long getCoinsJ();
|
|
||||||
|
|
||||||
//virtual bool exportKeyImages(const std::string &filename) = 0;
|
|
||||||
//virtual bool importKeyImages(const std::string &filename) = 0;
|
|
||||||
|
|
||||||
|
|
||||||
//virtual TransactionHistory * history() const = 0;
|
|
||||||
|
|
||||||
public void refreshHistory() {
|
|
||||||
getHistory().refreshWithNotes(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void refreshCoins() {
|
|
||||||
if (this.isSynchronized()) {
|
|
||||||
getCoins().refresh();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private native long setListenerJ(WalletListener listener);
|
private external fun setListenerJ(listener: WalletListener): Long
|
||||||
|
fun setListener(listener: WalletListener) {
|
||||||
public void setListener(WalletListener listener) {
|
listenerHandle = setListenerJ(listener)
|
||||||
this.listenerHandle = setListenerJ(listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public native int getDefaultMixin();
|
external fun getDefaultMixin(): Int
|
||||||
|
external fun setDefaultMixin(mixin: Int)
|
||||||
|
external fun setUserNote(txid: String?, note: String?): Boolean
|
||||||
|
external fun getUserNote(txid: String?): String?
|
||||||
|
external fun getTxKey(txid: String?): String?
|
||||||
|
@JvmOverloads
|
||||||
|
external fun addAccount(label: String? = NEW_ACCOUNT_NAME)
|
||||||
|
var accountLabel: String?
|
||||||
|
get() = getAccountLabel(accountIndex)
|
||||||
|
//virtual std::string signMessage(const std::string &message) = 0;
|
||||||
|
set(label) {
|
||||||
|
setAccountLabel(accountIndex, label)
|
||||||
|
}
|
||||||
|
|
||||||
//virtual AddressBook * addressBook() const = 0;
|
private fun getAccountLabel(accountIndex: Int): String {
|
||||||
//virtual void setListener(WalletListener *) = 0;
|
var label = getSubaddressLabel(accountIndex, 0)
|
||||||
|
if (label == NEW_ACCOUNT_NAME) {
|
||||||
public native void setDefaultMixin(int mixin);
|
val address = getAddress(accountIndex)
|
||||||
|
val len = address.length
|
||||||
public native boolean setUserNote(String txid, String note);
|
|
||||||
|
|
||||||
public native String getUserNote(String txid);
|
|
||||||
|
|
||||||
public native String getTxKey(String txid);
|
|
||||||
|
|
||||||
public void addAccount() {
|
|
||||||
addAccount(NEW_ACCOUNT_NAME);
|
|
||||||
}
|
|
||||||
|
|
||||||
public native void addAccount(String label);
|
|
||||||
|
|
||||||
public String getAccountLabel() {
|
|
||||||
return getAccountLabel(accountIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
//virtual std::string signMessage(const std::string &message) = 0;
|
|
||||||
//virtual bool verifySignedMessage(const std::string &message, const std::string &addres, const std::string &signature) const = 0;
|
|
||||||
|
|
||||||
//virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &tvAmount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0;
|
|
||||||
//virtual bool rescanSpent() = 0;
|
|
||||||
|
|
||||||
public void setAccountLabel(String label) {
|
|
||||||
setAccountLabel(accountIndex, label);
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAccountLabel(int accountIndex) {
|
|
||||||
String label = getSubaddressLabel(accountIndex, 0);
|
|
||||||
if (label.equals(NEW_ACCOUNT_NAME)) {
|
|
||||||
String address = getAddress(accountIndex);
|
|
||||||
int len = address.length();
|
|
||||||
label = address.substring(0, 6) +
|
label = address.substring(0, 6) +
|
||||||
"\u2026" + address.substring(len - 6, len);
|
"\u2026" + address.substring(len - 6, len)
|
||||||
}
|
}
|
||||||
return label;
|
return label
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getSubaddressLabel(int addressIndex) {
|
fun getSubaddressLabel(addressIndex: Int): String {
|
||||||
return getSubaddressLabel(accountIndex, addressIndex);
|
return getSubaddressLabel(accountIndex, addressIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
public native String getSubaddressLabel(int accountIndex, int addressIndex);
|
private external fun getSubaddressLabel(accountIndex: Int, addressIndex: Int): String
|
||||||
|
private fun setAccountLabel(accountIndex: Int, label: String?) {
|
||||||
public void setAccountLabel(int accountIndex, String label) {
|
setSubaddressLabel(accountIndex, 0, label)
|
||||||
setSubaddressLabel(accountIndex, 0, label);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSubaddressLabel(int addressIndex, String label) {
|
fun setSubaddressLabel(addressIndex: Int, label: String?) {
|
||||||
setSubaddressLabel(accountIndex, addressIndex, label);
|
setSubaddressLabel(accountIndex, addressIndex, label)
|
||||||
refreshHistory();
|
refreshHistory()
|
||||||
}
|
}
|
||||||
|
|
||||||
public native void setSubaddressLabel(int accountIndex, int addressIndex, String label);
|
private external fun setSubaddressLabel(accountIndex: Int, addressIndex: Int, label: String?)
|
||||||
|
external fun getNumAccounts(): Int
|
||||||
|
val numSubaddresses: Int
|
||||||
|
get() = getNumSubaddresses(accountIndex)
|
||||||
|
|
||||||
public native int getNumAccounts();
|
private external fun getNumSubaddresses(accountIndex: Int): Int
|
||||||
|
val newSubaddress: String
|
||||||
|
get() = getNewSubaddress(accountIndex)
|
||||||
|
|
||||||
public int getNumSubaddresses() {
|
private fun getNewSubaddress(accountIndex: Int): String {
|
||||||
return getNumSubaddresses(accountIndex);
|
val timeStamp = SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).format(Date())
|
||||||
|
addSubaddress(accountIndex, timeStamp)
|
||||||
|
val subaddress = getLastSubaddress(accountIndex)
|
||||||
|
Timber.d("%d: %s", getNumSubaddresses(accountIndex) - 1, subaddress)
|
||||||
|
return subaddress
|
||||||
}
|
}
|
||||||
|
|
||||||
public native int getNumSubaddresses(int accountIndex);
|
external fun addSubaddress(accountIndex: Int, label: String?)
|
||||||
|
private fun getLastSubaddress(accountIndex: Int): String {
|
||||||
public String getNewSubaddress() {
|
return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1)
|
||||||
return getNewSubaddress(accountIndex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getNewSubaddress(int accountIndex) {
|
val deviceType: Device
|
||||||
String timeStamp = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).format(new Date());
|
get() {
|
||||||
addSubaddress(accountIndex, timeStamp);
|
val device = getDeviceTypeJ()
|
||||||
String subaddress = getLastSubaddress(accountIndex);
|
return Device.values()[device + 1] // mapping is monero+1=android
|
||||||
Timber.d("%d: %s", getNumSubaddresses(accountIndex) - 1, subaddress);
|
|
||||||
return subaddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
public native void addSubaddress(int accountIndex, String label);
|
|
||||||
|
|
||||||
public String getLastSubaddress(int accountIndex) {
|
|
||||||
return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Wallet.Device getDeviceType() {
|
|
||||||
int device = getDeviceTypeJ();
|
|
||||||
return Wallet.Device.values()[device + 1]; // mapping is monero+1=android
|
|
||||||
}
|
|
||||||
|
|
||||||
private native int getDeviceTypeJ();
|
|
||||||
|
|
||||||
public enum Device {
|
|
||||||
Device_Undefined(0, 0),
|
|
||||||
Device_Software(50, 200),
|
|
||||||
Device_Ledger(5, 20);
|
|
||||||
private final int accountLookahead;
|
|
||||||
private final int subaddressLookahead;
|
|
||||||
|
|
||||||
Device(int accountLookahead, int subaddressLookahead) {
|
|
||||||
this.accountLookahead = accountLookahead;
|
|
||||||
this.subaddressLookahead = subaddressLookahead;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getAccountLookahead() {
|
private external fun getDeviceTypeJ(): Int
|
||||||
return accountLookahead;
|
|
||||||
|
enum class Device(val accountLookahead: Int, val subaddressLookahead: Int) {
|
||||||
|
Device_Undefined(0, 0), Device_Software(50, 200), Device_Ledger(5, 20)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class StatusEnum {
|
||||||
|
Status_Ok, Status_Error, Status_Critical
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ConnectionStatus {
|
||||||
|
ConnectionStatus_Disconnected, ConnectionStatus_Connected, ConnectionStatus_WrongVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
class Status internal constructor(status: Int, @JvmField val errorString: String) {
|
||||||
|
val status: StatusEnum
|
||||||
|
@JvmField
|
||||||
|
var connectionStatus: ConnectionStatus? = null // optional
|
||||||
|
|
||||||
|
init {
|
||||||
|
this.status = StatusEnum.values()[status]
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getSubaddressLookahead() {
|
val isOk: Boolean
|
||||||
return subaddressLookahead;
|
get() = (status == StatusEnum.Status_Ok
|
||||||
|
&& (connectionStatus == null || connectionStatus == ConnectionStatus.ConnectionStatus_Connected))
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return "Wallet.Status: $status/$errorString/$connectionStatus"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum StatusEnum {
|
companion object {
|
||||||
Status_Ok,
|
const val SWEEP_ALL = Long.MAX_VALUE
|
||||||
Status_Error,
|
private const val NEW_ACCOUNT_NAME = "Untitled account" // src/wallet/wallet2.cpp:941
|
||||||
Status_Critical
|
|
||||||
|
init {
|
||||||
|
System.loadLibrary("monerujo")
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
external fun getDisplayAmount(amount: Long): String
|
||||||
|
@JvmStatic
|
||||||
|
external fun getAmountFromString(amount: String?): Long
|
||||||
|
@JvmStatic
|
||||||
|
external fun getAmountFromDouble(amount: Double): Long
|
||||||
|
@JvmStatic
|
||||||
|
external fun generatePaymentId(): String
|
||||||
|
@JvmStatic
|
||||||
|
external fun isPaymentIdValid(payment_id: String): Boolean
|
||||||
|
@JvmStatic
|
||||||
|
fun isAddressValid(address: String): Boolean {
|
||||||
|
return instance?.networkType?.value?.let { isAddressValid(address, it) } == true
|
||||||
|
}
|
||||||
|
@JvmStatic
|
||||||
|
external fun isAddressValid(address: String?, networkType: Int): Boolean
|
||||||
|
@JvmStatic
|
||||||
|
external fun getPaymentIdFromAddress(address: String?, networkType: Int): String?
|
||||||
|
@JvmStatic
|
||||||
|
external fun getMaximumAllowedAmount(): Long
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public enum ConnectionStatus {
|
|
||||||
ConnectionStatus_Disconnected,
|
|
||||||
ConnectionStatus_Connected,
|
|
||||||
ConnectionStatus_WrongVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
static public class Status {
|
|
||||||
final private StatusEnum status;
|
|
||||||
final private String errorString;
|
|
||||||
@Nullable
|
|
||||||
private ConnectionStatus connectionStatus; // optional
|
|
||||||
|
|
||||||
Status(int status, String errorString) {
|
|
||||||
this.status = StatusEnum.values()[status];
|
|
||||||
this.errorString = errorString;
|
|
||||||
}
|
|
||||||
|
|
||||||
public StatusEnum getStatus() {
|
|
||||||
return status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getErrorString() {
|
|
||||||
return errorString;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public ConnectionStatus getConnectionStatus() {
|
|
||||||
return connectionStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConnectionStatus(@Nullable ConnectionStatus connectionStatus) {
|
|
||||||
this.connectionStatus = connectionStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isOk() {
|
|
||||||
return (getStatus() == StatusEnum.Status_Ok)
|
|
||||||
&& ((getConnectionStatus() == null) ||
|
|
||||||
(getConnectionStatus() == ConnectionStatus.ConnectionStatus_Connected));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@NonNull
|
|
||||||
public String toString() {
|
|
||||||
return "Wallet.Status: " + status + "/" + errorString + "/" + connectionStatus;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -13,17 +13,16 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.model
|
||||||
|
|
||||||
package net.mynero.wallet.model;
|
interface WalletListener {
|
||||||
|
|
||||||
public interface WalletListener {
|
|
||||||
/**
|
/**
|
||||||
* moneySpent - called when money spent
|
* moneySpent - called when money spent
|
||||||
*
|
*
|
||||||
* @param txId - transaction id
|
* @param txId - transaction id
|
||||||
* @param amount - tvAmount
|
* @param amount - tvAmount
|
||||||
*/
|
*/
|
||||||
void moneySpent(String txId, long amount);
|
fun moneySpent(txId: String?, amount: Long)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* moneyReceived - called when money received
|
* moneyReceived - called when money received
|
||||||
|
@ -31,7 +30,7 @@ public interface WalletListener {
|
||||||
* @param txId - transaction id
|
* @param txId - transaction id
|
||||||
* @param amount - tvAmount
|
* @param amount - tvAmount
|
||||||
*/
|
*/
|
||||||
void moneyReceived(String txId, long amount);
|
fun moneyReceived(txId: String?, amount: Long)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* unconfirmedMoneyReceived - called when payment arrived in tx pool
|
* unconfirmedMoneyReceived - called when payment arrived in tx pool
|
||||||
|
@ -39,23 +38,22 @@ public interface WalletListener {
|
||||||
* @param txId - transaction id
|
* @param txId - transaction id
|
||||||
* @param amount - tvAmount
|
* @param amount - tvAmount
|
||||||
*/
|
*/
|
||||||
void unconfirmedMoneyReceived(String txId, long amount);
|
fun unconfirmedMoneyReceived(txId: String?, amount: Long)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* newBlock - called when new block received
|
* newBlock - called when new block received
|
||||||
*
|
*
|
||||||
* @param height - block height
|
* @param height - block height
|
||||||
*/
|
*/
|
||||||
void newBlock(long height);
|
fun newBlock(height: Long)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* updated - generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet;
|
* updated - generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet;
|
||||||
*/
|
*/
|
||||||
void updated();
|
fun updated()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously
|
* refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously
|
||||||
*/
|
*/
|
||||||
void refreshed();
|
fun refreshed()
|
||||||
|
}
|
||||||
}
|
|
|
@ -1,366 +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 net.mynero.wallet.model;
|
|
||||||
|
|
||||||
import net.mynero.wallet.data.Node;
|
|
||||||
import net.mynero.wallet.util.RestoreHeight;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FilenameFilter;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class WalletManager {
|
|
||||||
|
|
||||||
//TODO: maybe put these in an enum like in monero core - but why?
|
|
||||||
static public int LOGLEVEL_SILENT = -1;
|
|
||||||
static public int LOGLEVEL_WARN = 0;
|
|
||||||
static public int LOGLEVEL_INFO = 1;
|
|
||||||
static public int LOGLEVEL_DEBUG = 2;
|
|
||||||
static public int LOGLEVEL_TRACE = 3;
|
|
||||||
static public int LOGLEVEL_MAX = 4;
|
|
||||||
// no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it)
|
|
||||||
private static WalletManager Instance = null;
|
|
||||||
|
|
||||||
static {
|
|
||||||
System.loadLibrary("monerujo");
|
|
||||||
}
|
|
||||||
|
|
||||||
private final NetworkType networkType = NetworkType.NetworkType_Mainnet;
|
|
||||||
private Wallet managedWallet = null;
|
|
||||||
private String daemonAddress = null;
|
|
||||||
private String daemonUsername = "";
|
|
||||||
private String daemonPassword = "";
|
|
||||||
private String proxy = "";
|
|
||||||
|
|
||||||
public static synchronized WalletManager getInstance() {
|
|
||||||
if (WalletManager.Instance == null) {
|
|
||||||
WalletManager.Instance = new WalletManager();
|
|
||||||
}
|
|
||||||
|
|
||||||
return WalletManager.Instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
static public String addressPrefix(NetworkType networkType) {
|
|
||||||
switch (networkType) {
|
|
||||||
case NetworkType_Testnet:
|
|
||||||
return "9A-";
|
|
||||||
case NetworkType_Mainnet:
|
|
||||||
return "4-";
|
|
||||||
case NetworkType_Stagenet:
|
|
||||||
return "5-";
|
|
||||||
default:
|
|
||||||
throw new IllegalStateException("Unsupported Network: " + networkType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static public native void initLogger(String argv0, String defaultLogBaseName);
|
|
||||||
|
|
||||||
static public native void setLogLevel(int level);
|
|
||||||
|
|
||||||
static public native void logDebug(String category, String message);
|
|
||||||
|
|
||||||
static public native void logInfo(String category, String message);
|
|
||||||
|
|
||||||
static public native void logWarning(String category, String message);
|
|
||||||
|
|
||||||
static public native void logError(String category, String message);
|
|
||||||
|
|
||||||
static public native String moneroVersion();
|
|
||||||
|
|
||||||
public String addressPrefix() {
|
|
||||||
return addressPrefix(getNetworkType());
|
|
||||||
}
|
|
||||||
|
|
||||||
public Wallet getWallet() {
|
|
||||||
return managedWallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void manageWallet(Wallet wallet) {
|
|
||||||
Timber.d("Managing %s", wallet.getName());
|
|
||||||
managedWallet = wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void unmanageWallet(Wallet wallet) {
|
|
||||||
if (wallet == null) {
|
|
||||||
throw new IllegalArgumentException("Cannot unmanage null!");
|
|
||||||
}
|
|
||||||
if (getWallet() == null) {
|
|
||||||
throw new IllegalStateException("No wallet under management!");
|
|
||||||
}
|
|
||||||
if (getWallet() != wallet) {
|
|
||||||
throw new IllegalStateException(wallet.getName() + " not under management!");
|
|
||||||
}
|
|
||||||
Timber.d("Unmanaging %s", managedWallet.getName());
|
|
||||||
managedWallet = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Wallet createWallet(File aFile, String password, String language, long height) {
|
|
||||||
long walletHandle = createWalletJ(aFile.getAbsolutePath(), password, language, getNetworkType().getValue());
|
|
||||||
Wallet wallet = new Wallet(walletHandle);
|
|
||||||
manageWallet(wallet);
|
|
||||||
if (wallet.getStatus().isOk()) {
|
|
||||||
// (Re-)Estimate restore height based on what we know
|
|
||||||
final long oldHeight = wallet.getRestoreHeight();
|
|
||||||
// Go back 4 days if we don't have a precise restore height
|
|
||||||
Calendar restoreDate = Calendar.getInstance();
|
|
||||||
restoreDate.add(Calendar.DAY_OF_MONTH, 0);
|
|
||||||
final long restoreHeight =
|
|
||||||
(height > -1) ? height : RestoreHeight.getInstance().getHeight(restoreDate.getTime());
|
|
||||||
wallet.setRestoreHeight(restoreHeight);
|
|
||||||
Timber.d("Changed Restore Height from %d to %d", oldHeight, wallet.getRestoreHeight());
|
|
||||||
wallet.setPassword(password); // this rewrites the keys file (which contains the restore height)
|
|
||||||
} else
|
|
||||||
Timber.e(wallet.getStatus().toString());
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
//public native List<String> findWallets(String path); // this does not work - some error in boost
|
|
||||||
|
|
||||||
private native long createWalletJ(String path, String password, String language, int networkType);
|
|
||||||
|
|
||||||
public Wallet createWalletPolyseed(File aFile, String password, String passphrase, String language) {
|
|
||||||
long walletHandle = createWalletPolyseedJ(aFile.getAbsolutePath(), password, passphrase, language, getNetworkType().getValue());
|
|
||||||
Wallet wallet = new Wallet(walletHandle);
|
|
||||||
manageWallet(wallet);
|
|
||||||
if (wallet.getStatus().isOk()) {
|
|
||||||
wallet.setPassword(password); // this rewrites the keys file (which contains the restore height)
|
|
||||||
} else
|
|
||||||
Timber.e(wallet.getStatus().toString());
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
private native long createWalletPolyseedJ(String path, String password, String passphrase, String language, int networkType);
|
|
||||||
|
|
||||||
public Wallet openAccount(String path, int accountIndex, String password) {
|
|
||||||
long walletHandle = openWalletJ(path, password, getNetworkType().getValue());
|
|
||||||
Wallet wallet = new Wallet(walletHandle, accountIndex);
|
|
||||||
manageWallet(wallet);
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
|
|
||||||
|
|
||||||
public Wallet openWallet(String path, String password) {
|
|
||||||
long walletHandle = openWalletJ(path, password, getNetworkType().getValue());
|
|
||||||
Wallet wallet = new Wallet(walletHandle);
|
|
||||||
manageWallet(wallet);
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
private native long openWalletJ(String path, String password, int networkType);
|
|
||||||
|
|
||||||
public Wallet recoveryWallet(File aFile, String password,
|
|
||||||
String mnemonic, String offset,
|
|
||||||
long restoreHeight) {
|
|
||||||
long walletHandle = recoveryWalletJ(aFile.getAbsolutePath(), password,
|
|
||||||
mnemonic, offset,
|
|
||||||
getNetworkType().getValue(), restoreHeight);
|
|
||||||
Wallet wallet = new Wallet(walletHandle);
|
|
||||||
manageWallet(wallet);
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
private native long recoveryWalletJ(String path, String password,
|
|
||||||
String mnemonic, String offset,
|
|
||||||
int networkType, long restoreHeight);
|
|
||||||
|
|
||||||
public Wallet recoveryWalletPolyseed(File aFile, String password,
|
|
||||||
String mnemonic, String offset) {
|
|
||||||
long walletHandle = recoveryWalletPolyseedJ(aFile.getAbsolutePath(), password,
|
|
||||||
mnemonic, offset,
|
|
||||||
getNetworkType().getValue());
|
|
||||||
Wallet wallet = new Wallet(walletHandle);
|
|
||||||
manageWallet(wallet);
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
private native long recoveryWalletPolyseedJ(String path, String password,
|
|
||||||
String mnemonic, String offset,
|
|
||||||
int networkType);
|
|
||||||
|
|
||||||
public Wallet createWalletWithKeys(File aFile, String password, String language, long restoreHeight,
|
|
||||||
String addressString, String viewKeyString, String spendKeyString) {
|
|
||||||
long walletHandle = createWalletFromKeysJ(aFile.getAbsolutePath(), password,
|
|
||||||
language, getNetworkType().getValue(), restoreHeight,
|
|
||||||
addressString, viewKeyString, spendKeyString);
|
|
||||||
Wallet wallet = new Wallet(walletHandle);
|
|
||||||
manageWallet(wallet);
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
private native long createWalletFromKeysJ(String path, String password,
|
|
||||||
String language,
|
|
||||||
int networkType,
|
|
||||||
long restoreHeight,
|
|
||||||
String addressString,
|
|
||||||
String viewKeyString,
|
|
||||||
String spendKeyString);
|
|
||||||
|
|
||||||
public Wallet createWalletFromDevice(File aFile, String password, long restoreHeight,
|
|
||||||
String deviceName) {
|
|
||||||
long walletHandle = createWalletFromDeviceJ(aFile.getAbsolutePath(), password,
|
|
||||||
getNetworkType().getValue(), deviceName, restoreHeight,
|
|
||||||
"5:20");
|
|
||||||
Wallet wallet = new Wallet(walletHandle);
|
|
||||||
manageWallet(wallet);
|
|
||||||
return wallet;
|
|
||||||
}
|
|
||||||
|
|
||||||
private native long createWalletFromDeviceJ(String path, String password,
|
|
||||||
int networkType,
|
|
||||||
String deviceName,
|
|
||||||
long restoreHeight,
|
|
||||||
String subaddressLookahead);
|
|
||||||
|
|
||||||
public native boolean closeJ(Wallet wallet);
|
|
||||||
|
|
||||||
public boolean close(Wallet wallet) {
|
|
||||||
unmanageWallet(wallet);
|
|
||||||
boolean closed = closeJ(wallet);
|
|
||||||
if (!closed) {
|
|
||||||
// in case we could not close it
|
|
||||||
// we manage it again
|
|
||||||
manageWallet(wallet);
|
|
||||||
}
|
|
||||||
return closed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean walletExists(File aFile) {
|
|
||||||
return walletExists(aFile.getAbsolutePath());
|
|
||||||
}
|
|
||||||
|
|
||||||
public native boolean walletExists(String path);
|
|
||||||
|
|
||||||
public native boolean verifyWalletPassword(String keys_file_name, String password, boolean watch_only);
|
|
||||||
|
|
||||||
public boolean verifyWalletPasswordOnly(String keys_file_name, String password) {
|
|
||||||
return queryWalletDeviceJ(keys_file_name, password) >= 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Wallet.Device queryWalletDevice(String keys_file_name, String password) {
|
|
||||||
int device = queryWalletDeviceJ(keys_file_name, password);
|
|
||||||
return Wallet.Device.values()[device + 1]; // mapping is monero+1=android
|
|
||||||
}
|
|
||||||
|
|
||||||
private native int queryWalletDeviceJ(String keys_file_name, String password);
|
|
||||||
|
|
||||||
public List<WalletInfo> findWallets(File path) {
|
|
||||||
List<WalletInfo> wallets = new ArrayList<>();
|
|
||||||
Timber.d("Scanning: %s", path.getAbsolutePath());
|
|
||||||
File[] found = path.listFiles(new FilenameFilter() {
|
|
||||||
public boolean accept(File dir, String filename) {
|
|
||||||
return filename.endsWith(".keys");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
for (int i = 0; i < found.length; i++) {
|
|
||||||
String filename = found[i].getName();
|
|
||||||
File f = new File(found[i].getParent(), filename.substring(0, filename.length() - 5)); // 5 is length of ".keys"+1
|
|
||||||
wallets.add(new WalletInfo(f));
|
|
||||||
}
|
|
||||||
return wallets;
|
|
||||||
}
|
|
||||||
|
|
||||||
public NetworkType getNetworkType() {
|
|
||||||
return networkType;
|
|
||||||
}
|
|
||||||
|
|
||||||
// this should not be called on the main thread as it connects to the node (and takes a long time)
|
|
||||||
public void setDaemon(Node node) {
|
|
||||||
if (node != null) {
|
|
||||||
this.daemonAddress = node.getAddress();
|
|
||||||
if (networkType != node.getNetworkType())
|
|
||||||
throw new IllegalArgumentException("network type does not match");
|
|
||||||
this.daemonUsername = node.getUsername();
|
|
||||||
this.daemonPassword = node.getPassword();
|
|
||||||
setDaemonAddressJ(daemonAddress);
|
|
||||||
} else {
|
|
||||||
this.daemonAddress = null;
|
|
||||||
this.daemonUsername = "";
|
|
||||||
this.daemonPassword = "";
|
|
||||||
//setDaemonAddressJ(""); // don't disconnect as monero code blocks for many seconds!
|
|
||||||
//TODO: need to do something about that later
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDaemonAddress() {
|
|
||||||
if (daemonAddress == null) {
|
|
||||||
throw new IllegalStateException("use setDaemon() to initialise daemon and net first!");
|
|
||||||
}
|
|
||||||
return this.daemonAddress;
|
|
||||||
}
|
|
||||||
|
|
||||||
private native void setDaemonAddressJ(String address);
|
|
||||||
|
|
||||||
public String getDaemonUsername() {
|
|
||||||
return daemonUsername;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDaemonPassword() {
|
|
||||||
return daemonPassword;
|
|
||||||
}
|
|
||||||
|
|
||||||
public native int getDaemonVersion();
|
|
||||||
|
|
||||||
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
|
|
||||||
|
|
||||||
public native long getBlockchainHeight();
|
|
||||||
|
|
||||||
public native long getBlockchainTargetHeight();
|
|
||||||
|
|
||||||
public native long getNetworkDifficulty();
|
|
||||||
|
|
||||||
public native double getMiningHashRate();
|
|
||||||
|
|
||||||
public native long getBlockTarget();
|
|
||||||
|
|
||||||
public native boolean isMining();
|
|
||||||
|
|
||||||
public native boolean startMining(String address, boolean background_mining, boolean ignore_battery);
|
|
||||||
|
|
||||||
public native boolean stopMining();
|
|
||||||
|
|
||||||
public native String resolveOpenAlias(String address, boolean dnssec_valid);
|
|
||||||
|
|
||||||
public String getProxy() {
|
|
||||||
return proxy;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean setProxy(String address) {
|
|
||||||
this.proxy = address;
|
|
||||||
return setProxyJ(address);
|
|
||||||
}
|
|
||||||
|
|
||||||
public native boolean setProxyJ(String address);
|
|
||||||
|
|
||||||
public class WalletInfo implements Comparable<WalletInfo> {
|
|
||||||
final private File path;
|
|
||||||
final private String name;
|
|
||||||
|
|
||||||
public WalletInfo(File wallet) {
|
|
||||||
path = wallet.getParentFile();
|
|
||||||
name = wallet.getName();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int compareTo(WalletInfo another) {
|
|
||||||
return name.toLowerCase().compareTo(another.name.toLowerCase());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
370
app/src/main/java/net/mynero/wallet/model/WalletManager.kt
Normal file
370
app/src/main/java/net/mynero/wallet/model/WalletManager.kt
Normal file
|
@ -0,0 +1,370 @@
|
||||||
|
/*
|
||||||
|
* 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 net.mynero.wallet.model
|
||||||
|
|
||||||
|
import net.mynero.wallet.data.Node
|
||||||
|
import net.mynero.wallet.util.RestoreHeight
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Locale
|
||||||
|
|
||||||
|
class WalletManager {
|
||||||
|
@JvmField
|
||||||
|
val networkType = NetworkType.NetworkType_Mainnet
|
||||||
|
var wallet: Wallet? = null
|
||||||
|
private set
|
||||||
|
private var daemonAddress: String? = null
|
||||||
|
var daemonUsername = ""
|
||||||
|
private set
|
||||||
|
var daemonPassword = ""
|
||||||
|
private set
|
||||||
|
var proxy = ""
|
||||||
|
private set
|
||||||
|
|
||||||
|
private fun manageWallet(wallet: Wallet) {
|
||||||
|
Timber.d("Managing %s", wallet.name)
|
||||||
|
this.wallet = wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun unmanageWallet(wallet: Wallet?) {
|
||||||
|
requireNotNull(wallet) { "Cannot unmanage null!" }
|
||||||
|
checkNotNull(this.wallet) { "No wallet under management!" }
|
||||||
|
check(this.wallet === wallet) { wallet.name + " not under management!" }
|
||||||
|
Timber.d("Unmanaging %s", wallet.name)
|
||||||
|
this.wallet = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createWallet(aFile: File, password: String, language: String, height: Long): Wallet {
|
||||||
|
val walletHandle = createWalletJ(aFile.absolutePath, password, language, networkType.value)
|
||||||
|
val wallet = Wallet(walletHandle)
|
||||||
|
manageWallet(wallet)
|
||||||
|
if (wallet.status.isOk) {
|
||||||
|
// (Re-)Estimate restore height based on what we know
|
||||||
|
val oldHeight = wallet.getRestoreHeight()
|
||||||
|
// Go back 4 days if we don't have a precise restore height
|
||||||
|
val restoreDate = Calendar.getInstance()
|
||||||
|
restoreDate.add(Calendar.DAY_OF_MONTH, 0)
|
||||||
|
val restoreHeight =
|
||||||
|
if (height > -1) height else RestoreHeight.instance?.getHeight(restoreDate.time)
|
||||||
|
if (restoreHeight != null) {
|
||||||
|
wallet.setRestoreHeight(restoreHeight)
|
||||||
|
}
|
||||||
|
Timber.d("Changed Restore Height from %d to %d", oldHeight, wallet.getRestoreHeight())
|
||||||
|
wallet.setPassword(password) // this rewrites the keys file (which contains the restore height)
|
||||||
|
} else Timber.e(wallet.status.toString())
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
//public native List<String> findWallets(String path); // this does not work - some error in boost
|
||||||
|
private external fun createWalletJ(
|
||||||
|
path: String,
|
||||||
|
password: String,
|
||||||
|
language: String,
|
||||||
|
networkType: Int
|
||||||
|
): Long
|
||||||
|
|
||||||
|
fun createWalletPolyseed(
|
||||||
|
aFile: File,
|
||||||
|
password: String,
|
||||||
|
passphrase: String,
|
||||||
|
language: String
|
||||||
|
): Wallet {
|
||||||
|
val walletHandle = createWalletPolyseedJ(
|
||||||
|
aFile.absolutePath,
|
||||||
|
password,
|
||||||
|
passphrase,
|
||||||
|
language,
|
||||||
|
networkType.value
|
||||||
|
)
|
||||||
|
val wallet = Wallet(walletHandle)
|
||||||
|
manageWallet(wallet)
|
||||||
|
if (wallet.status.isOk) {
|
||||||
|
wallet.setPassword(password) // this rewrites the keys file (which contains the restore height)
|
||||||
|
} else Timber.e(wallet.status.toString())
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun createWalletPolyseedJ(
|
||||||
|
path: String,
|
||||||
|
password: String,
|
||||||
|
passphrase: String,
|
||||||
|
language: String,
|
||||||
|
networkType: Int
|
||||||
|
): Long
|
||||||
|
|
||||||
|
fun openAccount(path: String, accountIndex: Int, password: String): Wallet {
|
||||||
|
val walletHandle = openWalletJ(path, password, networkType.value)
|
||||||
|
val wallet = Wallet(walletHandle, accountIndex)
|
||||||
|
manageWallet(wallet)
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
//TODO virtual bool checkPayment(const std::string &address, const std::string &txid, const std::string &txkey, const std::string &daemon_address, uint64_t &received, uint64_t &height, std::string &error) const = 0;
|
||||||
|
fun openWallet(path: String, password: String): Wallet {
|
||||||
|
val walletHandle = openWalletJ(path, password, networkType.value)
|
||||||
|
val wallet = Wallet(walletHandle)
|
||||||
|
manageWallet(wallet)
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun openWalletJ(path: String, password: String, networkType: Int): Long
|
||||||
|
fun recoveryWallet(
|
||||||
|
aFile: File, password: String,
|
||||||
|
mnemonic: String, offset: String,
|
||||||
|
restoreHeight: Long
|
||||||
|
): Wallet {
|
||||||
|
val walletHandle = recoveryWalletJ(
|
||||||
|
aFile.absolutePath, password,
|
||||||
|
mnemonic, offset,
|
||||||
|
networkType.value, restoreHeight
|
||||||
|
)
|
||||||
|
val wallet = Wallet(walletHandle)
|
||||||
|
manageWallet(wallet)
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun recoveryWalletJ(
|
||||||
|
path: String, password: String,
|
||||||
|
mnemonic: String, offset: String,
|
||||||
|
networkType: Int, restoreHeight: Long
|
||||||
|
): Long
|
||||||
|
|
||||||
|
fun recoveryWalletPolyseed(
|
||||||
|
aFile: File, password: String,
|
||||||
|
mnemonic: String, offset: String
|
||||||
|
): Wallet {
|
||||||
|
val walletHandle = recoveryWalletPolyseedJ(
|
||||||
|
aFile.absolutePath, password,
|
||||||
|
mnemonic, offset,
|
||||||
|
networkType.value
|
||||||
|
)
|
||||||
|
val wallet = Wallet(walletHandle)
|
||||||
|
manageWallet(wallet)
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun recoveryWalletPolyseedJ(
|
||||||
|
path: String, password: String,
|
||||||
|
mnemonic: String, offset: String,
|
||||||
|
networkType: Int
|
||||||
|
): Long
|
||||||
|
|
||||||
|
fun createWalletWithKeys(
|
||||||
|
aFile: File, password: String, language: String, restoreHeight: Long,
|
||||||
|
addressString: String, viewKeyString: String, spendKeyString: String
|
||||||
|
): Wallet {
|
||||||
|
val walletHandle = createWalletFromKeysJ(
|
||||||
|
aFile.absolutePath, password,
|
||||||
|
language, networkType.value, restoreHeight,
|
||||||
|
addressString, viewKeyString, spendKeyString
|
||||||
|
)
|
||||||
|
val wallet = Wallet(walletHandle)
|
||||||
|
manageWallet(wallet)
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun createWalletFromKeysJ(
|
||||||
|
path: String, password: String,
|
||||||
|
language: String,
|
||||||
|
networkType: Int,
|
||||||
|
restoreHeight: Long,
|
||||||
|
addressString: String,
|
||||||
|
viewKeyString: String,
|
||||||
|
spendKeyString: String
|
||||||
|
): Long
|
||||||
|
|
||||||
|
fun createWalletFromDevice(
|
||||||
|
aFile: File, password: String, restoreHeight: Long,
|
||||||
|
deviceName: String
|
||||||
|
): Wallet {
|
||||||
|
val walletHandle = createWalletFromDeviceJ(
|
||||||
|
aFile.absolutePath, password,
|
||||||
|
networkType.value, deviceName, restoreHeight,
|
||||||
|
"5:20"
|
||||||
|
)
|
||||||
|
val wallet = Wallet(walletHandle)
|
||||||
|
manageWallet(wallet)
|
||||||
|
return wallet
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun createWalletFromDeviceJ(
|
||||||
|
path: String, password: String,
|
||||||
|
networkType: Int,
|
||||||
|
deviceName: String,
|
||||||
|
restoreHeight: Long,
|
||||||
|
subaddressLookahead: String
|
||||||
|
): Long
|
||||||
|
|
||||||
|
external fun closeJ(wallet: Wallet?): Boolean
|
||||||
|
fun close(wallet: Wallet): Boolean {
|
||||||
|
unmanageWallet(wallet)
|
||||||
|
val closed = closeJ(wallet)
|
||||||
|
if (!closed) {
|
||||||
|
// in case we could not close it
|
||||||
|
// we manage it again
|
||||||
|
manageWallet(wallet)
|
||||||
|
}
|
||||||
|
return closed
|
||||||
|
}
|
||||||
|
|
||||||
|
fun walletExists(aFile: File): Boolean {
|
||||||
|
return walletExists(aFile.absolutePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
external fun walletExists(path: String?): Boolean
|
||||||
|
external fun verifyWalletPassword(
|
||||||
|
keys_file_name: String?,
|
||||||
|
password: String?,
|
||||||
|
watch_only: Boolean
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
fun verifyWalletPasswordOnly(keys_file_name: String, password: String): Boolean {
|
||||||
|
return queryWalletDeviceJ(keys_file_name, password) >= 0
|
||||||
|
}
|
||||||
|
|
||||||
|
fun queryWalletDevice(keys_file_name: String, password: String): Wallet.Device {
|
||||||
|
val device = queryWalletDeviceJ(keys_file_name, password)
|
||||||
|
return Wallet.Device.values()[device + 1] // mapping is monero+1=android
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun queryWalletDeviceJ(keys_file_name: String, password: String): Int
|
||||||
|
fun findWallets(path: File): List<WalletInfo> {
|
||||||
|
val wallets: MutableList<WalletInfo> = ArrayList()
|
||||||
|
Timber.d("Scanning: %s", path.absolutePath)
|
||||||
|
val found = path.listFiles { dir, filename -> filename.endsWith(".keys") } ?: return emptyList()
|
||||||
|
for (i in found.indices) {
|
||||||
|
val filename = found[i].name
|
||||||
|
val f = File(
|
||||||
|
found[i].parent,
|
||||||
|
filename.substring(0, filename.length - 5)
|
||||||
|
) // 5 is length of ".keys"+1
|
||||||
|
wallets.add(WalletInfo(f))
|
||||||
|
}
|
||||||
|
return wallets
|
||||||
|
}
|
||||||
|
|
||||||
|
// this should not be called on the main thread as it connects to the node (and takes a long time)
|
||||||
|
fun setDaemon(node: Node?) {
|
||||||
|
if (node != null) {
|
||||||
|
daemonAddress = node.address
|
||||||
|
require(networkType === node.networkType) { "network type does not match" }
|
||||||
|
daemonUsername = node.username
|
||||||
|
daemonPassword = node.password
|
||||||
|
daemonAddress?.let { addr -> setDaemonAddressJ(addr) }
|
||||||
|
} else {
|
||||||
|
daemonAddress = null
|
||||||
|
daemonUsername = ""
|
||||||
|
daemonPassword = ""
|
||||||
|
//setDaemonAddressJ(""); // don't disconnect as monero code blocks for many seconds!
|
||||||
|
//TODO: need to do something about that later
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getDaemonAddress(): String? {
|
||||||
|
checkNotNull(daemonAddress) { "use setDaemon() to initialise daemon and net first!" }
|
||||||
|
return daemonAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun setDaemonAddressJ(address: String)
|
||||||
|
external fun getDaemonVersion(): Int
|
||||||
|
external fun getBlockchainHeight(): Long
|
||||||
|
external fun getBlockchainTargetHeight(): Long
|
||||||
|
external fun getNetworkDifficulty(): Long
|
||||||
|
external fun getMiningHashRate(): Double
|
||||||
|
external fun getBlockTarget(): Long
|
||||||
|
external fun isMining(): Boolean
|
||||||
|
|
||||||
|
external fun startMining(
|
||||||
|
address: String?,
|
||||||
|
background_mining: Boolean,
|
||||||
|
ignore_battery: Boolean
|
||||||
|
): Boolean
|
||||||
|
|
||||||
|
external fun stopMining(): Boolean
|
||||||
|
external fun resolveOpenAlias(address: String?, dnssec_valid: Boolean): String?
|
||||||
|
fun setProxy(address: String): Boolean {
|
||||||
|
proxy = address
|
||||||
|
return setProxyJ(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
private external fun setProxyJ(address: String?): Boolean
|
||||||
|
inner class WalletInfo(wallet: File) : Comparable<WalletInfo> {
|
||||||
|
private val path: File
|
||||||
|
private val name: String
|
||||||
|
|
||||||
|
init {
|
||||||
|
path = wallet.parentFile
|
||||||
|
name = wallet.name
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun compareTo(other: WalletInfo): Int {
|
||||||
|
return name.lowercase(Locale.getDefault())
|
||||||
|
.compareTo(other.name.lowercase(Locale.getDefault()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
//TODO: maybe put these in an enum like in monero core - but why?
|
||||||
|
@JvmField
|
||||||
|
var LOGLEVEL_SILENT = -1
|
||||||
|
var LOGLEVEL_WARN = 0
|
||||||
|
var LOGLEVEL_INFO = 1
|
||||||
|
var LOGLEVEL_DEBUG = 2
|
||||||
|
var LOGLEVEL_TRACE = 3
|
||||||
|
var LOGLEVEL_MAX = 4
|
||||||
|
|
||||||
|
// no need to keep a reference to the REAL WalletManager (we get it every tvTime we need it)
|
||||||
|
@get:Synchronized
|
||||||
|
@JvmStatic
|
||||||
|
var instance: WalletManager? = null
|
||||||
|
get() {
|
||||||
|
if (field == null) {
|
||||||
|
field = WalletManager()
|
||||||
|
}
|
||||||
|
return field
|
||||||
|
}
|
||||||
|
private set
|
||||||
|
|
||||||
|
init {
|
||||||
|
System.loadLibrary("monerujo")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addressPrefix(networkType: NetworkType): String {
|
||||||
|
return when (networkType) {
|
||||||
|
NetworkType.NetworkType_Testnet -> "9A-"
|
||||||
|
NetworkType.NetworkType_Mainnet -> "4-"
|
||||||
|
NetworkType.NetworkType_Stagenet -> "5-"
|
||||||
|
else -> throw IllegalStateException("Unsupported Network: $networkType")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
external fun initLogger(argv0: String?, defaultLogBaseName: String?)
|
||||||
|
@JvmStatic
|
||||||
|
external fun setLogLevel(level: Int)
|
||||||
|
@JvmStatic
|
||||||
|
external fun logDebug(category: String?, message: String?)
|
||||||
|
@JvmStatic
|
||||||
|
external fun logInfo(category: String?, message: String?)
|
||||||
|
@JvmStatic
|
||||||
|
external fun logWarning(category: String?, message: String?)
|
||||||
|
@JvmStatic
|
||||||
|
external fun logError(category: String?, message: String?)
|
||||||
|
@JvmStatic
|
||||||
|
external fun moneroVersion(): String?
|
||||||
|
}
|
||||||
|
}
|
|
@ -33,7 +33,7 @@ public class BalanceService extends ServiceBase {
|
||||||
long unlocked = 0;
|
long unlocked = 0;
|
||||||
for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
||||||
if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && coinsInfo.isUnlocked() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) {
|
if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && coinsInfo.isUnlocked() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) {
|
||||||
unlocked += coinsInfo.getAmount();
|
unlocked += coinsInfo.amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return unlocked;
|
return unlocked;
|
||||||
|
@ -43,7 +43,7 @@ public class BalanceService extends ServiceBase {
|
||||||
long total = 0;
|
long total = 0;
|
||||||
for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
||||||
if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) {
|
if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) {
|
||||||
total += coinsInfo.getAmount();
|
total += coinsInfo.amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
|
|
|
@ -100,7 +100,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void refreshed() {
|
public void refreshed() {
|
||||||
Wallet.ConnectionStatus status = wallet.getFullStatus().getConnectionStatus();
|
Wallet.ConnectionStatus status = wallet.getFullStatus().connectionStatus;
|
||||||
if (status == Wallet.ConnectionStatus.ConnectionStatus_Disconnected || status == null) {
|
if (status == Wallet.ConnectionStatus.ConnectionStatus_Disconnected || status == null) {
|
||||||
if (triesLeft > 0) {
|
if (triesLeft > 0) {
|
||||||
wallet.startRefresh();
|
wallet.startRefresh();
|
||||||
|
@ -162,7 +162,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
|
||||||
|
|
||||||
private List<TransactionOutput> maybeAddDonationOutputs(long amount, List<TransactionOutput> outputs, List<String> preferredInputs) throws Exception {
|
private List<TransactionOutput> maybeAddDonationOutputs(long amount, List<TransactionOutput> outputs, List<String> preferredInputs) throws Exception {
|
||||||
TransactionOutput mainDestination = outputs.get(0); // at this point, for now, we should only have one item in the list. TODO: add multi-dest/pay-to-many feature in the UI
|
TransactionOutput mainDestination = outputs.get(0); // at this point, for now, we should only have one item in the list. TODO: add multi-dest/pay-to-many feature in the UI
|
||||||
String paymentId = Wallet.getPaymentIdFromAddress(mainDestination.getDestination(), WalletManager.getInstance().getNetworkType().getValue());
|
String paymentId = Wallet.getPaymentIdFromAddress(mainDestination.destination, WalletManager.getInstance().networkType.value);
|
||||||
ArrayList<TransactionOutput> newOutputs = new ArrayList<>(outputs);
|
ArrayList<TransactionOutput> newOutputs = new ArrayList<>(outputs);
|
||||||
boolean donatePerTx = PrefService.getInstance().getBoolean(Constants.PREF_DONATE_PER_TX, false);
|
boolean donatePerTx = PrefService.getInstance().getBoolean(Constants.PREF_DONATE_PER_TX, false);
|
||||||
if(donatePerTx && paymentId.isEmpty()) { // only attach donation when no payment id is needed (i.e. integrated address)
|
if(donatePerTx && paymentId.isEmpty()) { // only attach donation when no payment id is needed (i.e. integrated address)
|
||||||
|
@ -205,8 +205,8 @@ public class MoneroHandlerThread extends Thread implements WalletListener {
|
||||||
if (!sendAll) {
|
if (!sendAll) {
|
||||||
long amountSelected = 0;
|
long amountSelected = 0;
|
||||||
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) {
|
||||||
if (selectedUtxos.contains(coinsInfo.getKeyImage())) {
|
if (selectedUtxos.contains(coinsInfo.keyImage)) {
|
||||||
amountSelected += coinsInfo.getAmount();
|
amountSelected += coinsInfo.amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,10 +57,10 @@ public class UTXOService extends ServiceBase {
|
||||||
public void toggleFrozen(HashMap<String, CoinsInfo> selectedCoins) {
|
public void toggleFrozen(HashMap<String, CoinsInfo> selectedCoins) {
|
||||||
ArrayList<String> frozenCoinsCopy = new ArrayList<>(frozenCoins);
|
ArrayList<String> frozenCoinsCopy = new ArrayList<>(frozenCoins);
|
||||||
for(CoinsInfo coin : selectedCoins.values()) {
|
for(CoinsInfo coin : selectedCoins.values()) {
|
||||||
if(frozenCoinsCopy.contains(coin.getPubKey())) {
|
if(frozenCoinsCopy.contains(coin.pubKey)) {
|
||||||
frozenCoinsCopy.remove(coin.getPubKey());
|
frozenCoinsCopy.remove(coin.pubKey);
|
||||||
} else {
|
} else {
|
||||||
frozenCoinsCopy.add(coin.getPubKey());
|
frozenCoinsCopy.add(coin.pubKey);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
this.frozenCoins = frozenCoinsCopy;
|
this.frozenCoins = frozenCoinsCopy;
|
||||||
|
@ -70,7 +70,7 @@ public class UTXOService extends ServiceBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isCoinFrozen(CoinsInfo coinsInfo) {
|
public boolean isCoinFrozen(CoinsInfo coinsInfo) {
|
||||||
return frozenCoins.contains(coinsInfo.getPubKey());
|
return frozenCoins.contains(coinsInfo.pubKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadFrozenCoins() throws JSONException {
|
private void loadFrozenCoins() throws JSONException {
|
||||||
|
@ -104,18 +104,18 @@ public class UTXOService extends ServiceBase {
|
||||||
Collections.sort(utxos);
|
Collections.sort(utxos);
|
||||||
//loop through each utxo
|
//loop through each utxo
|
||||||
for (CoinsInfo coinsInfo : utxos) {
|
for (CoinsInfo coinsInfo : utxos) {
|
||||||
if (!coinsInfo.isSpent() && coinsInfo.isUnlocked() && !coinsInfo.isFrozen() && !frozenCoins.contains(coinsInfo.getPubKey())) { //filter out spent, locked, and frozen outputs
|
if (!coinsInfo.isSpent() && coinsInfo.isUnlocked() && !coinsInfo.isFrozen() && !frozenCoins.contains(coinsInfo.pubKey)) { //filter out spent, locked, and frozen outputs
|
||||||
if (sendAll) {
|
if (sendAll) {
|
||||||
// if send all, add all utxos and set amount to send all
|
// if send all, add all utxos and set amount to send all
|
||||||
selectedUtxos.add(coinsInfo.getKeyImage());
|
selectedUtxos.add(coinsInfo.keyImage);
|
||||||
amountSelected = Wallet.SWEEP_ALL;
|
amountSelected = Wallet.SWEEP_ALL;
|
||||||
} else {
|
} else {
|
||||||
//if amount selected is still less than amount needed, and the utxos tx hash hasn't already been seen, add utxo
|
//if amount selected is still less than amount needed, and the utxos tx hash hasn't already been seen, add utxo
|
||||||
if (amountSelected <= amountWithBasicFee && !seenTxs.contains(coinsInfo.getHash())) {
|
if (amountSelected <= amountWithBasicFee && !seenTxs.contains(coinsInfo.hash)) {
|
||||||
selectedUtxos.add(coinsInfo.getKeyImage());
|
selectedUtxos.add(coinsInfo.keyImage);
|
||||||
// we don't want to spend multiple utxos from the same transaction, so we prevent that from happening here.
|
// we don't want to spend multiple utxos from the same transaction, so we prevent that from happening here.
|
||||||
seenTxs.add(coinsInfo.getHash());
|
seenTxs.add(coinsInfo.hash);
|
||||||
amountSelected += coinsInfo.getAmount();
|
amountSelected += coinsInfo.amount;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
package net.mynero.wallet.util;
|
|
||||||
|
|
||||||
public class Constants {
|
|
||||||
public static final String WALLET_NAME = "xmr_wallet";
|
|
||||||
public static final String MNEMONIC_LANGUAGE = "English";
|
|
||||||
public static final String PREF_USES_PASSWORD = "pref_uses_password";
|
|
||||||
public static final String PREF_USES_TOR = "pref_uses_tor";
|
|
||||||
public static final String PREF_PROXY = "pref_proxy";
|
|
||||||
public static final String PREF_NODE_2 = "pref_node_2";
|
|
||||||
public static final String PREF_CUSTOM_NODES = "pref_custom_nodes";
|
|
||||||
public static final String PREF_USES_OFFSET = "pref_uses_offset";
|
|
||||||
public static final String PREF_STREET_MODE = "pref_street_mode";
|
|
||||||
public static final String PREF_MONEROCHAN = "pref_monerochan";
|
|
||||||
public static final String PREF_DONATE_PER_TX = "pref_donate_per_tx";
|
|
||||||
public static final String PREF_FROZEN_COINS = "pref_frozen_coins";
|
|
||||||
|
|
||||||
public static final String URI_PREFIX = "monero:";
|
|
||||||
public static final String URI_ARG_AMOUNT = "tx_amount";
|
|
||||||
public static final String URI_ARG_AMOUNT2 = "amount";
|
|
||||||
public static final String NAV_ARG_TXINFO = "nav_arg_txinfo";
|
|
||||||
|
|
||||||
public static final String STREET_MODE_BALANCE = "#.############";
|
|
||||||
public static final String[] DONATION_ADDRESSES = new String[] {
|
|
||||||
"87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC", // primary Mysu Donation address
|
|
||||||
"89QoPxs4cQSGbJrJddwzV3Ca7s2gVYHE1Xd1hGZafuVJVyNKt2LCQhxUdBF57PemxQiX3dmGUZLRRAzfeYyh9pq3GiWsDVo", // second Mysu Donation address
|
|
||||||
"855acibBehTQJdC1f41BHWGq1FiQ5ztiAU7LiJgDUmmyJfDtRpJoo6Mc1en73duUScdeUYjLirACnZpv2C6pPbcZKgumdCS" // third Mysu Donation address
|
|
||||||
};
|
|
||||||
public static final String DONATE_ADDRESS = "87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC";
|
|
||||||
}
|
|
29
app/src/main/java/net/mynero/wallet/util/Constants.kt
Normal file
29
app/src/main/java/net/mynero/wallet/util/Constants.kt
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package net.mynero.wallet.util
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
const val WALLET_NAME = "xmr_wallet"
|
||||||
|
const val MNEMONIC_LANGUAGE = "English"
|
||||||
|
const val PREF_USES_PASSWORD = "pref_uses_password"
|
||||||
|
const val PREF_USES_TOR = "pref_uses_tor"
|
||||||
|
const val PREF_PROXY = "pref_proxy"
|
||||||
|
const val PREF_NODE_2 = "pref_node_2"
|
||||||
|
const val PREF_CUSTOM_NODES = "pref_custom_nodes"
|
||||||
|
const val PREF_USES_OFFSET = "pref_uses_offset"
|
||||||
|
const val PREF_STREET_MODE = "pref_street_mode"
|
||||||
|
const val PREF_MONEROCHAN = "pref_monerochan"
|
||||||
|
const val PREF_DONATE_PER_TX = "pref_donate_per_tx"
|
||||||
|
const val PREF_FROZEN_COINS = "pref_frozen_coins"
|
||||||
|
const val URI_PREFIX = "monero:"
|
||||||
|
const val URI_ARG_AMOUNT = "tx_amount"
|
||||||
|
const val URI_ARG_AMOUNT2 = "amount"
|
||||||
|
const val NAV_ARG_TXINFO = "nav_arg_txinfo"
|
||||||
|
const val STREET_MODE_BALANCE = "#.############"
|
||||||
|
@JvmField
|
||||||
|
val DONATION_ADDRESSES = arrayOf(
|
||||||
|
"87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC", // primary Mysu Donation address
|
||||||
|
"89QoPxs4cQSGbJrJddwzV3Ca7s2gVYHE1Xd1hGZafuVJVyNKt2LCQhxUdBF57PemxQiX3dmGUZLRRAzfeYyh9pq3GiWsDVo", // second Mysu Donation address
|
||||||
|
"855acibBehTQJdC1f41BHWGq1FiQ5ztiAU7LiJgDUmmyJfDtRpJoo6Mc1en73duUScdeUYjLirACnZpv2C6pPbcZKgumdCS" // third Mysu Donation address
|
||||||
|
)
|
||||||
|
const val DONATE_ADDRESS =
|
||||||
|
"87BqQYkugEzh6Tuyotm2uc3DzJzKM6MuZaC161e6u1TsQxxPmXVPHpdNRyK47JY4d1hhbe25YVz4e9vTXCLDxvHkRXEAeBC"
|
||||||
|
}
|
|
@ -13,17 +13,17 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.util
|
||||||
|
|
||||||
package net.mynero.wallet.util;
|
import java.text.ParseException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Date
|
||||||
|
|
||||||
import java.text.ParseException;
|
object DateHelper {
|
||||||
import java.text.SimpleDateFormat;
|
@JvmField
|
||||||
import java.util.Date;
|
val DATETIME_FORMATTER = SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
|
||||||
|
@Throws(ParseException::class)
|
||||||
public class DateHelper {
|
fun parse(dateString: String): Date {
|
||||||
public static final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
|
return DATETIME_FORMATTER.parse(dateString.replace("Z$".toRegex(), "+0000"))
|
||||||
|
|
||||||
public static Date parse(String dateString) throws ParseException {
|
|
||||||
return DATETIME_FORMATTER.parse(dateString.replaceAll("Z$", "+0000"));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,33 +13,23 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.util
|
||||||
|
|
||||||
package net.mynero.wallet.util;
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
enum class DayNightMode(val nightMode: Int) {
|
||||||
|
|
||||||
public enum DayNightMode {
|
|
||||||
// order must match R.array.daynight_themes
|
// order must match R.array.daynight_themes
|
||||||
AUTO(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM),
|
AUTO(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM), DAY(AppCompatDelegate.MODE_NIGHT_NO), NIGHT(
|
||||||
DAY(AppCompatDelegate.MODE_NIGHT_NO),
|
AppCompatDelegate.MODE_NIGHT_YES
|
||||||
NIGHT(AppCompatDelegate.MODE_NIGHT_YES),
|
),
|
||||||
UNKNOWN(AppCompatDelegate.MODE_NIGHT_UNSPECIFIED);
|
UNKNOWN(AppCompatDelegate.MODE_NIGHT_UNSPECIFIED);
|
||||||
|
|
||||||
final private int nightMode;
|
companion object {
|
||||||
|
fun getValue(nightMode: Int): DayNightMode {
|
||||||
DayNightMode(int nightMode) {
|
for (mode in values()) {
|
||||||
this.nightMode = nightMode;
|
if (mode.nightMode == nightMode) return mode
|
||||||
}
|
}
|
||||||
|
return UNKNOWN
|
||||||
static public DayNightMode getValue(int nightMode) {
|
|
||||||
for (DayNightMode mode : DayNightMode.values()) {
|
|
||||||
if (mode.nightMode == nightMode)
|
|
||||||
return mode;
|
|
||||||
}
|
}
|
||||||
return UNKNOWN;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public int getNightMode() {
|
|
||||||
return nightMode;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,299 +13,294 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.util
|
||||||
|
|
||||||
package net.mynero.wallet.util;
|
import android.Manifest
|
||||||
|
import android.app.Activity
|
||||||
|
import android.app.Dialog
|
||||||
|
import android.content.ClipData
|
||||||
|
import android.content.ClipDescription
|
||||||
|
import android.content.ClipboardManager
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.Canvas
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.graphics.drawable.VectorDrawable
|
||||||
|
import android.os.Build
|
||||||
|
import android.view.WindowManager
|
||||||
|
import android.view.animation.Animation
|
||||||
|
import android.view.animation.AnimationUtils
|
||||||
|
import android.view.inputmethod.InputMethodManager
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import net.mynero.wallet.R
|
||||||
|
import net.mynero.wallet.model.WalletManager
|
||||||
|
import net.mynero.wallet.model.WalletManager.Companion.initLogger
|
||||||
|
import net.mynero.wallet.model.WalletManager.Companion.setLogLevel
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.File
|
||||||
|
import java.io.IOException
|
||||||
|
import java.io.InputStreamReader
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import java.math.BigInteger
|
||||||
|
import java.net.MalformedURLException
|
||||||
|
import java.net.SocketTimeoutException
|
||||||
|
import java.net.URL
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
import android.Manifest;
|
object Helper {
|
||||||
import android.app.Activity;
|
const val NOCRAZYPASS_FLAGFILE = ".nocrazypass"
|
||||||
import android.app.Dialog;
|
const val XMR_DECIMALS = 12
|
||||||
import android.content.ClipData;
|
val ONE_XMR = Math.round(Math.pow(10.0, XMR_DECIMALS.toDouble()))
|
||||||
import android.content.ClipDescription;
|
const val SHOW_EXCHANGERATES = true
|
||||||
import android.content.ClipboardManager;
|
const val PERMISSIONS_REQUEST_CAMERA = 7
|
||||||
import android.content.Context;
|
const val HTTP_TIMEOUT = 5000
|
||||||
import android.content.pm.PackageManager;
|
private const val WALLET_DIR = "wallets"
|
||||||
import android.graphics.Bitmap;
|
private const val MONERO_DIR = "monero"
|
||||||
import android.graphics.BitmapFactory;
|
private val HexArray = "0123456789ABCDEF".toCharArray()
|
||||||
import android.graphics.Canvas;
|
var ALLOW_SHIFT = false
|
||||||
import android.graphics.drawable.BitmapDrawable;
|
@JvmField
|
||||||
import android.graphics.drawable.Drawable;
|
var DISPLAY_DIGITS_INFO = 5
|
||||||
import android.graphics.drawable.VectorDrawable;
|
private var ShakeAnimation: Animation? = null
|
||||||
import android.view.View;
|
fun getWalletRoot(context: Context): File {
|
||||||
import android.view.WindowManager;
|
return getStorage(context, WALLET_DIR)
|
||||||
import android.view.animation.Animation;
|
|
||||||
import android.view.animation.AnimationUtils;
|
|
||||||
import android.view.inputmethod.InputMethodManager;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
|
||||||
import androidx.core.content.ContextCompat;
|
|
||||||
|
|
||||||
import net.mynero.wallet.R;
|
|
||||||
import net.mynero.wallet.model.WalletManager;
|
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.SocketTimeoutException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
import javax.net.ssl.HttpsURLConnection;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class Helper {
|
|
||||||
static public final String NOCRAZYPASS_FLAGFILE = ".nocrazypass";
|
|
||||||
|
|
||||||
static public final int XMR_DECIMALS = 12;
|
|
||||||
static public final long ONE_XMR = Math.round(Math.pow(10, Helper.XMR_DECIMALS));
|
|
||||||
|
|
||||||
static public final boolean SHOW_EXCHANGERATES = true;
|
|
||||||
static public final int PERMISSIONS_REQUEST_CAMERA = 7;
|
|
||||||
static final int HTTP_TIMEOUT = 5000;
|
|
||||||
static private final String WALLET_DIR = "wallets";
|
|
||||||
static private final String MONERO_DIR = "monero";
|
|
||||||
private final static char[] HexArray = "0123456789ABCDEF".toCharArray();
|
|
||||||
static public boolean ALLOW_SHIFT = false;
|
|
||||||
static public int DISPLAY_DIGITS_INFO = 5;
|
|
||||||
static private Animation ShakeAnimation;
|
|
||||||
|
|
||||||
static public File getWalletRoot(Context context) {
|
|
||||||
return getStorage(context, WALLET_DIR);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static public File getStorage(Context context, String folderName) {
|
fun getStorage(context: Context, folderName: String?): File {
|
||||||
File dir = new File(context.getFilesDir(), folderName);
|
val dir = File(context.filesDir, folderName)
|
||||||
if (!dir.exists()) {
|
if (!dir.exists()) {
|
||||||
Timber.i("Creating %s", dir.getAbsolutePath());
|
Timber.i("Creating %s", dir.absolutePath)
|
||||||
dir.mkdirs(); // try to make it
|
dir.mkdirs() // try to make it
|
||||||
}
|
}
|
||||||
if (!dir.isDirectory()) {
|
if (!dir.isDirectory) {
|
||||||
String msg = "Directory " + dir.getAbsolutePath() + " does not exist.";
|
val msg = "Directory " + dir.absolutePath + " does not exist."
|
||||||
Timber.e(msg);
|
Timber.e(msg)
|
||||||
throw new IllegalStateException(msg);
|
throw IllegalStateException(msg)
|
||||||
}
|
}
|
||||||
return dir;
|
return dir
|
||||||
}
|
}
|
||||||
|
|
||||||
static public boolean getCameraPermission(Activity context) {
|
fun getCameraPermission(context: Activity): Boolean {
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (context.checkSelfPermission(Manifest.permission.CAMERA)
|
if (context.checkSelfPermission(Manifest.permission.CAMERA)
|
||||||
== PackageManager.PERMISSION_DENIED) {
|
== PackageManager.PERMISSION_DENIED
|
||||||
Timber.w("Permission denied for CAMERA - requesting it");
|
) {
|
||||||
String[] permissions = {Manifest.permission.CAMERA};
|
Timber.w("Permission denied for CAMERA - requesting it")
|
||||||
context.requestPermissions(permissions, PERMISSIONS_REQUEST_CAMERA);
|
val permissions = arrayOf(Manifest.permission.CAMERA)
|
||||||
return false;
|
context.requestPermissions(
|
||||||
|
permissions,
|
||||||
|
PERMISSIONS_REQUEST_CAMERA
|
||||||
|
)
|
||||||
|
false
|
||||||
} else {
|
} else {
|
||||||
return true;
|
true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return true;
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static public boolean getCameraPermission(Activity context, ActivityResultLauncher<String> launcher) {
|
@JvmStatic
|
||||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
fun getCameraPermission(context: Activity, launcher: ActivityResultLauncher<String?>): Boolean {
|
||||||
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
if (context.checkSelfPermission(Manifest.permission.CAMERA)
|
if (context.checkSelfPermission(Manifest.permission.CAMERA)
|
||||||
== PackageManager.PERMISSION_DENIED) {
|
== PackageManager.PERMISSION_DENIED
|
||||||
Timber.w("Permission denied for CAMERA - requesting it");
|
) {
|
||||||
launcher.launch(Manifest.permission.CAMERA);
|
Timber.w("Permission denied for CAMERA - requesting it")
|
||||||
return false;
|
launcher.launch(Manifest.permission.CAMERA)
|
||||||
|
false
|
||||||
} else {
|
} else {
|
||||||
return true;
|
true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return true;
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static public File getWalletFile(Context context, String aWalletName) {
|
fun getWalletFile(context: Context, aWalletName: String?): File {
|
||||||
File walletDir = getWalletRoot(context);
|
val walletDir = getWalletRoot(context)
|
||||||
File f = new File(walletDir, aWalletName);
|
val f = File(walletDir, aWalletName)
|
||||||
Timber.d("wallet=%s size= %d", f.getAbsolutePath(), f.length());
|
Timber.d("wallet=%s size= %d", f.absolutePath, f.length())
|
||||||
return f;
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
static public void showKeyboard(Activity act) {
|
fun showKeyboard(act: Activity) {
|
||||||
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
|
val imm = act.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
final View focus = act.getCurrentFocus();
|
val focus = act.currentFocus
|
||||||
if (focus != null)
|
if (focus != null) imm.showSoftInput(focus, InputMethodManager.SHOW_IMPLICIT)
|
||||||
imm.showSoftInput(focus, InputMethodManager.SHOW_IMPLICIT);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static public void hideKeyboard(Activity act) {
|
fun hideKeyboard(act: Activity?) {
|
||||||
if (act == null) return;
|
if (act == null) return
|
||||||
if (act.getCurrentFocus() == null) {
|
if (act.currentFocus == null) {
|
||||||
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
|
act.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN)
|
||||||
} else {
|
} else {
|
||||||
InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
|
val imm = act.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||||
imm.hideSoftInputFromWindow((null == act.getCurrentFocus()) ? null : act.getCurrentFocus().getWindowToken(),
|
act.currentFocus?.let {
|
||||||
InputMethodManager.HIDE_NOT_ALWAYS);
|
imm.hideSoftInputFromWindow(
|
||||||
|
it.windowToken,
|
||||||
|
InputMethodManager.HIDE_NOT_ALWAYS
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static public void showKeyboard(Dialog dialog) {
|
fun showKeyboard(dialog: Dialog) {
|
||||||
dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
|
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||||
}
|
}
|
||||||
|
|
||||||
static public void hideKeyboardAlways(Activity act) {
|
fun hideKeyboardAlways(act: Activity) {
|
||||||
act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
|
act.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN)
|
||||||
}
|
}
|
||||||
|
|
||||||
static public BigDecimal getDecimalAmount(long amount) {
|
fun getDecimalAmount(amount: Long): BigDecimal {
|
||||||
return new BigDecimal(amount).scaleByPowerOfTen(-XMR_DECIMALS);
|
return BigDecimal(amount).scaleByPowerOfTen(-XMR_DECIMALS)
|
||||||
}
|
}
|
||||||
|
|
||||||
static public String getDisplayAmount(long amount) {
|
@JvmStatic
|
||||||
return getDisplayAmount(amount, XMR_DECIMALS);
|
fun getDisplayAmount(amount: Long): String {
|
||||||
|
return getDisplayAmount(amount, XMR_DECIMALS)
|
||||||
}
|
}
|
||||||
|
|
||||||
static public String getDisplayAmount(long amount, int maxDecimals) {
|
@JvmStatic
|
||||||
|
fun getDisplayAmount(amount: Long, maxDecimals: Int): String {
|
||||||
// a Java bug does not strip zeros properly if the value is 0
|
// a Java bug does not strip zeros properly if the value is 0
|
||||||
if (amount == 0) return "0.00";
|
if (amount == 0L) return "0.00"
|
||||||
BigDecimal d = getDecimalAmount(amount)
|
var d = getDecimalAmount(amount)
|
||||||
.setScale(maxDecimals, BigDecimal.ROUND_HALF_UP)
|
.setScale(maxDecimals, BigDecimal.ROUND_HALF_UP)
|
||||||
.stripTrailingZeros();
|
.stripTrailingZeros()
|
||||||
if (d.scale() < 2)
|
if (d.scale() < 2) d = d.setScale(2, BigDecimal.ROUND_UNNECESSARY)
|
||||||
d = d.setScale(2, BigDecimal.ROUND_UNNECESSARY);
|
return d.toPlainString()
|
||||||
return d.toPlainString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static public String getFormattedAmount(double amount, boolean isCrypto) {
|
fun getFormattedAmount(amount: Double, isCrypto: Boolean): String? {
|
||||||
// at this point selection is XMR in case of error
|
// at this point selection is XMR in case of error
|
||||||
String displayB;
|
val displayB: String?
|
||||||
if (isCrypto) {
|
displayB = if (isCrypto) {
|
||||||
if ((amount >= 0) || (amount == 0)) {
|
if (amount >= 0 || amount == 0.0) {
|
||||||
displayB = String.format(Locale.US, "%,.5f", amount);
|
String.format(Locale.US, "%,.5f", amount)
|
||||||
} else {
|
} else {
|
||||||
displayB = null;
|
null
|
||||||
}
|
}
|
||||||
} else { // not crypto
|
} else { // not crypto
|
||||||
displayB = String.format(Locale.US, "%,.2f", amount);
|
String.format(Locale.US, "%,.2f", amount)
|
||||||
}
|
}
|
||||||
return displayB;
|
return displayB
|
||||||
}
|
}
|
||||||
|
|
||||||
static public String getDisplayAmount(double amount) {
|
@JvmStatic
|
||||||
|
fun getDisplayAmount(amount: Double): String {
|
||||||
// a Java bug does not strip zeros properly if the value is 0
|
// a Java bug does not strip zeros properly if the value is 0
|
||||||
BigDecimal d = new BigDecimal(amount)
|
var d = BigDecimal(amount)
|
||||||
.setScale(XMR_DECIMALS, BigDecimal.ROUND_HALF_UP)
|
.setScale(XMR_DECIMALS, BigDecimal.ROUND_HALF_UP)
|
||||||
.stripTrailingZeros();
|
.stripTrailingZeros()
|
||||||
if (d.scale() < 1)
|
if (d.scale() < 1) d = d.setScale(1, BigDecimal.ROUND_UNNECESSARY)
|
||||||
d = d.setScale(1, BigDecimal.ROUND_UNNECESSARY);
|
return d.toPlainString()
|
||||||
return d.toPlainString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Bitmap getBitmap(Context context, int drawableId) {
|
fun getBitmap(context: Context, drawableId: Int): Bitmap {
|
||||||
Drawable drawable = ContextCompat.getDrawable(context, drawableId);
|
val drawable = ContextCompat.getDrawable(context, drawableId)
|
||||||
if (drawable instanceof BitmapDrawable) {
|
return if (drawable is BitmapDrawable) {
|
||||||
return BitmapFactory.decodeResource(context.getResources(), drawableId);
|
BitmapFactory.decodeResource(context.resources, drawableId)
|
||||||
} else if (drawable instanceof VectorDrawable) {
|
} else if (drawable is VectorDrawable) {
|
||||||
return getBitmap((VectorDrawable) drawable);
|
getBitmap(drawable)
|
||||||
} else {
|
} else {
|
||||||
throw new IllegalArgumentException("unsupported drawable type");
|
throw IllegalArgumentException("unsupported drawable type")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static private Bitmap getBitmap(VectorDrawable vectorDrawable) {
|
private fun getBitmap(vectorDrawable: VectorDrawable): Bitmap {
|
||||||
Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(),
|
val bitmap = Bitmap.createBitmap(
|
||||||
vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
|
vectorDrawable.intrinsicWidth,
|
||||||
Canvas canvas = new Canvas(bitmap);
|
vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888
|
||||||
vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
|
)
|
||||||
vectorDrawable.draw(canvas);
|
val canvas = Canvas(bitmap)
|
||||||
return bitmap;
|
vectorDrawable.setBounds(0, 0, canvas.width, canvas.height)
|
||||||
|
vectorDrawable.draw(canvas)
|
||||||
|
return bitmap
|
||||||
}
|
}
|
||||||
|
|
||||||
static public String getUrl(String httpsUrl) {
|
fun getUrl(httpsUrl: String?): String? {
|
||||||
HttpsURLConnection urlConnection = null;
|
var urlConnection: HttpsURLConnection? = null
|
||||||
try {
|
try {
|
||||||
URL url = new URL(httpsUrl);
|
val url = URL(httpsUrl)
|
||||||
urlConnection = (HttpsURLConnection) url.openConnection();
|
urlConnection = url.openConnection() as HttpsURLConnection
|
||||||
urlConnection.setConnectTimeout(HTTP_TIMEOUT);
|
urlConnection.connectTimeout = HTTP_TIMEOUT
|
||||||
urlConnection.setReadTimeout(HTTP_TIMEOUT);
|
urlConnection.readTimeout = HTTP_TIMEOUT
|
||||||
InputStreamReader in = new InputStreamReader(urlConnection.getInputStream());
|
val `in` = InputStreamReader(urlConnection.inputStream)
|
||||||
StringBuffer sb = new StringBuffer();
|
val sb = StringBuffer()
|
||||||
final int BUFFER_SIZE = 512;
|
val BUFFER_SIZE = 512
|
||||||
char[] buffer = new char[BUFFER_SIZE];
|
val buffer = CharArray(BUFFER_SIZE)
|
||||||
int length = in.read(buffer, 0, BUFFER_SIZE);
|
var length = `in`.read(buffer, 0, BUFFER_SIZE)
|
||||||
while (length >= 0) {
|
while (length >= 0) {
|
||||||
sb.append(buffer, 0, length);
|
sb.append(buffer, 0, length)
|
||||||
length = in.read(buffer, 0, BUFFER_SIZE);
|
length = `in`.read(buffer, 0, BUFFER_SIZE)
|
||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString()
|
||||||
} catch (SocketTimeoutException ex) {
|
} catch (ex: SocketTimeoutException) {
|
||||||
Timber.w("C %s", ex.getLocalizedMessage());
|
Timber.w("C %s", ex.localizedMessage)
|
||||||
} catch (MalformedURLException ex) {
|
} catch (ex: MalformedURLException) {
|
||||||
Timber.e("A %s", ex.getLocalizedMessage());
|
Timber.e("A %s", ex.localizedMessage)
|
||||||
} catch (IOException ex) {
|
} catch (ex: IOException) {
|
||||||
Timber.e("B %s", ex.getLocalizedMessage());
|
Timber.e("B %s", ex.localizedMessage)
|
||||||
} finally {
|
} finally {
|
||||||
if (urlConnection != null) {
|
urlConnection?.disconnect()
|
||||||
urlConnection.disconnect();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
static public void clipBoardCopy(Context context, String label, String text) {
|
@JvmStatic
|
||||||
|
fun clipBoardCopy(context: Context?, label: String?, text: String?) {
|
||||||
if (context != null) {
|
if (context != null) {
|
||||||
ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
val clipboardManager =
|
||||||
ClipData clip = ClipData.newPlainText(label, text);
|
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
clipboardManager.setPrimaryClip(clip);
|
val clip = ClipData.newPlainText(label, text)
|
||||||
Toast.makeText(context, context.getText(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show();
|
clipboardManager.setPrimaryClip(clip)
|
||||||
|
Toast.makeText(
|
||||||
|
context,
|
||||||
|
context.getText(R.string.copied_to_clipboard),
|
||||||
|
Toast.LENGTH_SHORT
|
||||||
|
).show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static public String getClipBoardText(Context context) {
|
@JvmStatic
|
||||||
final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
fun getClipBoardText(context: Context): String? {
|
||||||
|
val clipboardManager =
|
||||||
|
context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
|
||||||
try {
|
try {
|
||||||
if (clipboardManager.hasPrimaryClip()
|
if (clipboardManager.hasPrimaryClip()
|
||||||
&& clipboardManager.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
|
&& clipboardManager.primaryClipDescription?.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) == true
|
||||||
final ClipData.Item item = clipboardManager.getPrimaryClip().getItemAt(0);
|
) {
|
||||||
return item.getText().toString();
|
val item = clipboardManager.primaryClip?.getItemAt(0)
|
||||||
|
return item?.text.toString()
|
||||||
}
|
}
|
||||||
} catch (NullPointerException ex) {
|
} catch (ex: NullPointerException) {
|
||||||
// if we have don't find a text in the clipboard
|
// if we have don't find a text in the clipboard
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
return null;
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
static public Animation getShakeAnimation(Context context) {
|
fun getShakeAnimation(context: Context?): Animation? {
|
||||||
if (ShakeAnimation == null) {
|
if (ShakeAnimation == null) {
|
||||||
synchronized (Helper.class) {
|
synchronized(Helper::class.java) {
|
||||||
if (ShakeAnimation == null) {
|
if (ShakeAnimation == null) {
|
||||||
ShakeAnimation = AnimationUtils.loadAnimation(context, R.anim.shake);
|
ShakeAnimation = AnimationUtils.loadAnimation(context, R.anim.shake)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ShakeAnimation;
|
return ShakeAnimation
|
||||||
}
|
|
||||||
|
|
||||||
public static String bytesToHex(byte[] data) {
|
|
||||||
if ((data != null) && (data.length > 0))
|
|
||||||
return String.format("%0" + (data.length * 2) + "X", new BigInteger(1, data));
|
|
||||||
else return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static byte[] hexToBytes(String hex) {
|
|
||||||
final int len = hex.length();
|
|
||||||
final byte[] data = new byte[len / 2];
|
|
||||||
for (int i = 0; i < len; i += 2) {
|
|
||||||
data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4)
|
|
||||||
+ Character.digit(hex.charAt(i + 1), 16));
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
|
// TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ?
|
||||||
static public void initLogger(Context context, int level) {
|
fun initLogger(context: Context, level: Int) {
|
||||||
String home = getStorage(context, MONERO_DIR).getAbsolutePath();
|
val home = getStorage(context, MONERO_DIR).absolutePath
|
||||||
WalletManager.initLogger(home + "/monerujo", "monerujo.log");
|
initLogger("$home/monerujo", "monerujo.log")
|
||||||
if (level >= WalletManager.LOGLEVEL_SILENT)
|
if (level >= WalletManager.LOGLEVEL_SILENT) setLogLevel(level)
|
||||||
WalletManager.setLogLevel(level);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,270 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2018 m2049r
|
|
||||||
* Copyright 2013 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* 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 net.mynero.wallet.util;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.Context;
|
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.security.KeyPairGeneratorSpec;
|
|
||||||
import android.security.keystore.KeyGenParameterSpec;
|
|
||||||
import android.security.keystore.KeyProperties;
|
|
||||||
import android.util.Base64;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.math.BigInteger;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.security.InvalidAlgorithmParameterException;
|
|
||||||
import java.security.InvalidKeyException;
|
|
||||||
import java.security.KeyPair;
|
|
||||||
import java.security.KeyPairGenerator;
|
|
||||||
import java.security.KeyStore;
|
|
||||||
import java.security.KeyStoreException;
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.security.NoSuchProviderException;
|
|
||||||
import java.security.PrivateKey;
|
|
||||||
import java.security.PublicKey;
|
|
||||||
import java.security.Signature;
|
|
||||||
import java.security.SignatureException;
|
|
||||||
import java.security.UnrecoverableEntryException;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.GregorianCalendar;
|
|
||||||
|
|
||||||
import javax.crypto.BadPaddingException;
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.IllegalBlockSizeException;
|
|
||||||
import javax.crypto.NoSuchPaddingException;
|
|
||||||
import javax.security.auth.x500.X500Principal;
|
|
||||||
|
|
||||||
import timber.log.Timber;
|
|
||||||
|
|
||||||
public class KeyStoreHelper {
|
|
||||||
|
|
||||||
static {
|
|
||||||
System.loadLibrary("monerujo");
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean saveWalletUserPass(@NonNull Context context, String wallet, String password) {
|
|
||||||
String walletKeyAlias = SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet;
|
|
||||||
byte[] data = password.getBytes(StandardCharsets.UTF_8);
|
|
||||||
try {
|
|
||||||
KeyStoreHelper.createKeys(context, walletKeyAlias);
|
|
||||||
byte[] encrypted = KeyStoreHelper.encrypt(walletKeyAlias, data);
|
|
||||||
SharedPreferences.Editor e = context.getSharedPreferences(SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE).edit();
|
|
||||||
if (encrypted == null) {
|
|
||||||
e.remove(wallet).apply();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
e.putString(wallet, Base64.encodeToString(encrypted, Base64.DEFAULT)).apply();
|
|
||||||
return true;
|
|
||||||
} catch (NoSuchProviderException | NoSuchAlgorithmException |
|
|
||||||
InvalidAlgorithmParameterException | KeyStoreException ex) {
|
|
||||||
Timber.w(ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a public and private key and stores it using the Android Key
|
|
||||||
* Store, so that only this application will be able to access the keys.
|
|
||||||
*/
|
|
||||||
private static void createKeys(Context context, String alias) throws NoSuchProviderException,
|
|
||||||
NoSuchAlgorithmException, InvalidAlgorithmParameterException, KeyStoreException {
|
|
||||||
KeyStore keyStore = KeyStore.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
|
||||||
try {
|
|
||||||
keyStore.load(null);
|
|
||||||
} catch (IOException | CertificateException ex) {
|
|
||||||
throw new IllegalStateException("Could not load KeySotre", ex);
|
|
||||||
}
|
|
||||||
if (!keyStore.containsAlias(alias)) {
|
|
||||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
|
|
||||||
createKeysJBMR2(context, alias);
|
|
||||||
} else {
|
|
||||||
createKeysM(alias);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean deleteKeys(String alias) throws KeyStoreException {
|
|
||||||
KeyStore keyStore = KeyStore.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
|
||||||
try {
|
|
||||||
keyStore.load(null);
|
|
||||||
keyStore.deleteEntry(alias);
|
|
||||||
return true;
|
|
||||||
} catch (IOException | NoSuchAlgorithmException | CertificateException ex) {
|
|
||||||
Timber.w(ex);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static boolean keyExists(String wallet) throws BrokenPasswordStoreException {
|
|
||||||
try {
|
|
||||||
KeyStore keyStore = KeyStore.getInstance(KeyStoreHelper.SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
|
||||||
keyStore.load(null);
|
|
||||||
return keyStore.containsAlias(KeyStoreHelper.SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet);
|
|
||||||
} catch (IOException | NoSuchAlgorithmException | CertificateException | KeyStoreException ex) {
|
|
||||||
throw new BrokenPasswordStoreException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
|
|
||||||
private static void createKeysJBMR2(Context context, String alias) throws NoSuchProviderException,
|
|
||||||
NoSuchAlgorithmException, InvalidAlgorithmParameterException {
|
|
||||||
|
|
||||||
Calendar start = new GregorianCalendar();
|
|
||||||
Calendar end = new GregorianCalendar();
|
|
||||||
end.add(Calendar.YEAR, 300);
|
|
||||||
|
|
||||||
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(context)
|
|
||||||
.setAlias(alias)
|
|
||||||
.setSubject(new X500Principal("CN=" + alias))
|
|
||||||
.setSerialNumber(BigInteger.valueOf(Math.abs(alias.hashCode())))
|
|
||||||
.setStartDate(start.getTime()).setEndDate(end.getTime())
|
|
||||||
.build();
|
|
||||||
// defaults to 2048 bit modulus
|
|
||||||
KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance(
|
|
||||||
SecurityConstants.TYPE_RSA,
|
|
||||||
SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
|
||||||
kpGenerator.initialize(spec);
|
|
||||||
KeyPair kp = kpGenerator.generateKeyPair();
|
|
||||||
Timber.d("preM Keys created");
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.M)
|
|
||||||
private static void createKeysM(String alias) throws NoSuchProviderException,
|
|
||||||
NoSuchAlgorithmException, InvalidAlgorithmParameterException {
|
|
||||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(
|
|
||||||
KeyProperties.KEY_ALGORITHM_RSA, SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
|
||||||
keyPairGenerator.initialize(
|
|
||||||
new KeyGenParameterSpec.Builder(
|
|
||||||
alias, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
|
|
||||||
.setDigests(KeyProperties.DIGEST_SHA256)
|
|
||||||
.setSignaturePaddings(KeyProperties.SIGNATURE_PADDING_RSA_PKCS1)
|
|
||||||
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
|
|
||||||
.build());
|
|
||||||
KeyPair keyPair = keyPairGenerator.generateKeyPair();
|
|
||||||
Timber.d("M Keys created");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PrivateKey getPrivateKey(String alias) {
|
|
||||||
try {
|
|
||||||
KeyStore ks = KeyStore
|
|
||||||
.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
|
||||||
ks.load(null);
|
|
||||||
//KeyStore.Entry entry = ks.getEntry(alias, null);
|
|
||||||
PrivateKey privateKey = (PrivateKey) ks.getKey(alias, null);
|
|
||||||
|
|
||||||
if (privateKey == null) {
|
|
||||||
Timber.w("No key found under alias: %s", alias);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return privateKey;
|
|
||||||
} catch (IOException | NoSuchAlgorithmException | CertificateException
|
|
||||||
| UnrecoverableEntryException | KeyStoreException ex) {
|
|
||||||
throw new IllegalStateException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static PublicKey getPublicKey(String alias) {
|
|
||||||
try {
|
|
||||||
KeyStore ks = KeyStore
|
|
||||||
.getInstance(SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
|
||||||
ks.load(null);
|
|
||||||
|
|
||||||
PublicKey publicKey = ks.getCertificate(alias).getPublicKey();
|
|
||||||
|
|
||||||
if (publicKey == null) {
|
|
||||||
Timber.w("No public key");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return publicKey;
|
|
||||||
} catch (IOException | NoSuchAlgorithmException | CertificateException
|
|
||||||
| KeyStoreException ex) {
|
|
||||||
throw new IllegalStateException(ex);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] encrypt(String alias, byte[] data) {
|
|
||||||
try {
|
|
||||||
PublicKey publicKey = getPublicKey(alias);
|
|
||||||
Cipher cipher = Cipher.getInstance(SecurityConstants.CIPHER_RSA_ECB_PKCS1);
|
|
||||||
|
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
|
|
||||||
return cipher.doFinal(data);
|
|
||||||
} catch (InvalidKeyException | IllegalBlockSizeException | BadPaddingException
|
|
||||||
| NoSuchAlgorithmException | NoSuchPaddingException ex) {
|
|
||||||
Timber.e(ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static byte[] decrypt(String alias, byte[] data) {
|
|
||||||
try {
|
|
||||||
PrivateKey privateKey = getPrivateKey(alias);
|
|
||||||
if (privateKey == null) return null;
|
|
||||||
Cipher cipher = Cipher.getInstance(SecurityConstants.CIPHER_RSA_ECB_PKCS1);
|
|
||||||
|
|
||||||
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
|
||||||
return cipher.doFinal(data);
|
|
||||||
} catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidKeyException |
|
|
||||||
IllegalBlockSizeException | BadPaddingException ex) {
|
|
||||||
Timber.e(ex);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Signs the data using the key pair stored in the Android Key Store. This
|
|
||||||
* signature can be used with the data later to verify it was signed by this
|
|
||||||
* application.
|
|
||||||
*
|
|
||||||
* @return The data signature generated
|
|
||||||
*/
|
|
||||||
private static byte[] signData(String alias, byte[] data) throws NoSuchAlgorithmException,
|
|
||||||
InvalidKeyException, SignatureException {
|
|
||||||
PrivateKey privateKey = getPrivateKey(alias);
|
|
||||||
if (privateKey == null) return null;
|
|
||||||
Signature s = Signature.getInstance(SecurityConstants.SIGNATURE_SHA256withRSA);
|
|
||||||
s.initSign(privateKey);
|
|
||||||
s.update(data);
|
|
||||||
return s.sign();
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface SecurityConstants {
|
|
||||||
String KEYSTORE_PROVIDER_ANDROID_KEYSTORE = "AndroidKeyStore";
|
|
||||||
String TYPE_RSA = "RSA";
|
|
||||||
String SIGNATURE_SHA256withRSA = "SHA256withRSA";
|
|
||||||
String CIPHER_RSA_ECB_PKCS1 = "RSA/ECB/PKCS1Padding";
|
|
||||||
String WALLET_PASS_PREFS_NAME = "wallet";
|
|
||||||
String WALLET_PASS_KEY_PREFIX = "walletKey-";
|
|
||||||
}
|
|
||||||
|
|
||||||
static public class BrokenPasswordStoreException extends Exception {
|
|
||||||
BrokenPasswordStoreException() {
|
|
||||||
super();
|
|
||||||
}
|
|
||||||
|
|
||||||
BrokenPasswordStoreException(Throwable cause) {
|
|
||||||
super(cause);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -13,41 +13,43 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.util
|
||||||
|
|
||||||
package net.mynero.wallet.util;
|
import net.mynero.wallet.service.MoneroHandlerThread
|
||||||
|
import java.util.concurrent.BlockingQueue
|
||||||
|
import java.util.concurrent.Executor
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
|
import java.util.concurrent.ThreadFactory
|
||||||
|
import java.util.concurrent.ThreadPoolExecutor
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
import net.mynero.wallet.service.MoneroHandlerThread;
|
object MoneroThreadPoolExecutor {
|
||||||
|
@JvmField
|
||||||
import java.util.concurrent.BlockingQueue;
|
var MONERO_THREAD_POOL_EXECUTOR: Executor? = null
|
||||||
import java.util.concurrent.Executor;
|
private val CPU_COUNT = Runtime.getRuntime().availableProcessors()
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
private val CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4))
|
||||||
import java.util.concurrent.ThreadFactory;
|
private val MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1
|
||||||
import java.util.concurrent.ThreadPoolExecutor;
|
private const val KEEP_ALIVE_SECONDS = 30
|
||||||
import java.util.concurrent.TimeUnit;
|
private val sThreadFactory: ThreadFactory = object : ThreadFactory {
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
private val mCount = AtomicInteger(1)
|
||||||
|
override fun newThread(r: Runnable): Thread {
|
||||||
|
return Thread(
|
||||||
public class MoneroThreadPoolExecutor {
|
null,
|
||||||
public static final Executor MONERO_THREAD_POOL_EXECUTOR;
|
r,
|
||||||
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
|
"MoneroTask #" + mCount.getAndIncrement(),
|
||||||
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
|
MoneroHandlerThread.THREAD_STACK_SIZE
|
||||||
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
|
)
|
||||||
private static final int KEEP_ALIVE_SECONDS = 30;
|
|
||||||
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
|
|
||||||
private final AtomicInteger mCount = new AtomicInteger(1);
|
|
||||||
|
|
||||||
public Thread newThread(Runnable r) {
|
|
||||||
return new Thread(null, r, "MoneroTask #" + mCount.getAndIncrement(), MoneroHandlerThread.THREAD_STACK_SIZE);
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
private static final BlockingQueue<Runnable> sPoolWorkQueue =
|
|
||||||
new LinkedBlockingQueue<>(128);
|
|
||||||
|
|
||||||
static {
|
|
||||||
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
|
|
||||||
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
|
|
||||||
sPoolWorkQueue, sThreadFactory);
|
|
||||||
threadPoolExecutor.allowCoreThreadTimeOut(true);
|
|
||||||
MONERO_THREAD_POOL_EXECUTOR = threadPoolExecutor;
|
|
||||||
}
|
}
|
||||||
}
|
private val sPoolWorkQueue: BlockingQueue<Runnable> = LinkedBlockingQueue(128)
|
||||||
|
|
||||||
|
init {
|
||||||
|
val threadPoolExecutor = ThreadPoolExecutor(
|
||||||
|
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS.toLong(), TimeUnit.SECONDS,
|
||||||
|
sPoolWorkQueue, sThreadFactory
|
||||||
|
)
|
||||||
|
threadPoolExecutor.allowCoreThreadTimeOut(true)
|
||||||
|
MONERO_THREAD_POOL_EXECUTOR = threadPoolExecutor
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,22 +13,19 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.util
|
||||||
|
|
||||||
package net.mynero.wallet.util;
|
import android.annotation.SuppressLint
|
||||||
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
object NightmodeHelper {
|
||||||
|
val preferredNightmode: Unit
|
||||||
import androidx.appcompat.app.AppCompatDelegate;
|
get() {
|
||||||
|
setNightMode(DayNightMode.NIGHT)
|
||||||
import net.mynero.wallet.service.PrefService;
|
}
|
||||||
|
|
||||||
public class NightmodeHelper {
|
|
||||||
public static void getAndSetPreferredNightmode() {
|
|
||||||
setNightMode(DayNightMode.NIGHT);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("WrongConstant")
|
@SuppressLint("WrongConstant")
|
||||||
public static void setNightMode(DayNightMode mode) {
|
fun setNightMode(mode: DayNightMode) {
|
||||||
AppCompatDelegate.setDefaultNightMode(mode.getNightMode());
|
AppCompatDelegate.setDefaultNightMode(mode.nightMode)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,16 +13,14 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.util
|
||||||
|
|
||||||
package net.mynero.wallet.util;
|
object OnionHelper {
|
||||||
|
fun isOnionHost(hostname: String): Boolean {
|
||||||
public class OnionHelper {
|
return hostname.endsWith(".onion")
|
||||||
|
|
||||||
public static boolean isOnionHost(String hostname) {
|
|
||||||
return hostname.endsWith(".onion");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isI2PHost(String hostname) {
|
fun isI2PHost(hostname: String): Boolean {
|
||||||
return hostname.endsWith(".i2p");
|
return hostname.endsWith(".i2p")
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -13,198 +13,198 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.util
|
||||||
|
|
||||||
package net.mynero.wallet.util;
|
import java.text.ParseException
|
||||||
|
import java.text.SimpleDateFormat
|
||||||
|
import java.util.Calendar
|
||||||
|
import java.util.Date
|
||||||
|
import java.util.TimeZone
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
import java.text.ParseException;
|
class RestoreHeight internal constructor() {
|
||||||
import java.text.SimpleDateFormat;
|
private val blockheight: MutableMap<String, Long> = HashMap()
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.TimeZone;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
public class RestoreHeight {
|
init {
|
||||||
static final int DIFFICULTY_TARGET = 120; // seconds
|
blockheight["2014-05-01"] = 18844L
|
||||||
|
blockheight["2014-06-01"] = 65406L
|
||||||
static private RestoreHeight Singleton = null;
|
blockheight["2014-07-01"] = 108882L
|
||||||
private final Map<String, Long> blockheight = new HashMap<>();
|
blockheight["2014-08-01"] = 153594L
|
||||||
|
blockheight["2014-09-01"] = 198072L
|
||||||
RestoreHeight() {
|
blockheight["2014-10-01"] = 241088L
|
||||||
blockheight.put("2014-05-01", 18844L);
|
blockheight["2014-11-01"] = 285305L
|
||||||
blockheight.put("2014-06-01", 65406L);
|
blockheight["2014-12-01"] = 328069L
|
||||||
blockheight.put("2014-07-01", 108882L);
|
blockheight["2015-01-01"] = 372369L
|
||||||
blockheight.put("2014-08-01", 153594L);
|
blockheight["2015-02-01"] = 416505L
|
||||||
blockheight.put("2014-09-01", 198072L);
|
blockheight["2015-03-01"] = 456631L
|
||||||
blockheight.put("2014-10-01", 241088L);
|
blockheight["2015-04-01"] = 501084L
|
||||||
blockheight.put("2014-11-01", 285305L);
|
blockheight["2015-05-01"] = 543973L
|
||||||
blockheight.put("2014-12-01", 328069L);
|
blockheight["2015-06-01"] = 588326L
|
||||||
blockheight.put("2015-01-01", 372369L);
|
blockheight["2015-07-01"] = 631187L
|
||||||
blockheight.put("2015-02-01", 416505L);
|
blockheight["2015-08-01"] = 675484L
|
||||||
blockheight.put("2015-03-01", 456631L);
|
blockheight["2015-09-01"] = 719725L
|
||||||
blockheight.put("2015-04-01", 501084L);
|
blockheight["2015-10-01"] = 762463L
|
||||||
blockheight.put("2015-05-01", 543973L);
|
blockheight["2015-11-01"] = 806528L
|
||||||
blockheight.put("2015-06-01", 588326L);
|
blockheight["2015-12-01"] = 849041L
|
||||||
blockheight.put("2015-07-01", 631187L);
|
blockheight["2016-01-01"] = 892866L
|
||||||
blockheight.put("2015-08-01", 675484L);
|
blockheight["2016-02-01"] = 936736L
|
||||||
blockheight.put("2015-09-01", 719725L);
|
blockheight["2016-03-01"] = 977691L
|
||||||
blockheight.put("2015-10-01", 762463L);
|
blockheight["2016-04-01"] = 1015848L
|
||||||
blockheight.put("2015-11-01", 806528L);
|
blockheight["2016-05-01"] = 1037417L
|
||||||
blockheight.put("2015-12-01", 849041L);
|
blockheight["2016-06-01"] = 1059651L
|
||||||
blockheight.put("2016-01-01", 892866L);
|
blockheight["2016-07-01"] = 1081269L
|
||||||
blockheight.put("2016-02-01", 936736L);
|
blockheight["2016-08-01"] = 1103630L
|
||||||
blockheight.put("2016-03-01", 977691L);
|
blockheight["2016-09-01"] = 1125983L
|
||||||
blockheight.put("2016-04-01", 1015848L);
|
blockheight["2016-10-01"] = 1147617L
|
||||||
blockheight.put("2016-05-01", 1037417L);
|
blockheight["2016-11-01"] = 1169779L
|
||||||
blockheight.put("2016-06-01", 1059651L);
|
blockheight["2016-12-01"] = 1191402L
|
||||||
blockheight.put("2016-07-01", 1081269L);
|
blockheight["2017-01-01"] = 1213861L
|
||||||
blockheight.put("2016-08-01", 1103630L);
|
blockheight["2017-02-01"] = 1236197L
|
||||||
blockheight.put("2016-09-01", 1125983L);
|
blockheight["2017-03-01"] = 1256358L
|
||||||
blockheight.put("2016-10-01", 1147617L);
|
blockheight["2017-04-01"] = 1278622L
|
||||||
blockheight.put("2016-11-01", 1169779L);
|
blockheight["2017-05-01"] = 1300239L
|
||||||
blockheight.put("2016-12-01", 1191402L);
|
blockheight["2017-06-01"] = 1322564L
|
||||||
blockheight.put("2017-01-01", 1213861L);
|
blockheight["2017-07-01"] = 1344225L
|
||||||
blockheight.put("2017-02-01", 1236197L);
|
blockheight["2017-08-01"] = 1366664L
|
||||||
blockheight.put("2017-03-01", 1256358L);
|
blockheight["2017-09-01"] = 1389113L
|
||||||
blockheight.put("2017-04-01", 1278622L);
|
blockheight["2017-10-01"] = 1410738L
|
||||||
blockheight.put("2017-05-01", 1300239L);
|
blockheight["2017-11-01"] = 1433039L
|
||||||
blockheight.put("2017-06-01", 1322564L);
|
blockheight["2017-12-01"] = 1454639L
|
||||||
blockheight.put("2017-07-01", 1344225L);
|
blockheight["2018-01-01"] = 1477201L
|
||||||
blockheight.put("2017-08-01", 1366664L);
|
blockheight["2018-02-01"] = 1499599L
|
||||||
blockheight.put("2017-09-01", 1389113L);
|
blockheight["2018-03-01"] = 1519796L
|
||||||
blockheight.put("2017-10-01", 1410738L);
|
blockheight["2018-04-01"] = 1542067L
|
||||||
blockheight.put("2017-11-01", 1433039L);
|
blockheight["2018-05-01"] = 1562861L
|
||||||
blockheight.put("2017-12-01", 1454639L);
|
blockheight["2018-06-01"] = 1585135L
|
||||||
blockheight.put("2018-01-01", 1477201L);
|
blockheight["2018-07-01"] = 1606715L
|
||||||
blockheight.put("2018-02-01", 1499599L);
|
blockheight["2018-08-01"] = 1629017L
|
||||||
blockheight.put("2018-03-01", 1519796L);
|
blockheight["2018-09-01"] = 1651347L
|
||||||
blockheight.put("2018-04-01", 1542067L);
|
blockheight["2018-10-01"] = 1673031L
|
||||||
blockheight.put("2018-05-01", 1562861L);
|
blockheight["2018-11-01"] = 1695128L
|
||||||
blockheight.put("2018-06-01", 1585135L);
|
blockheight["2018-12-01"] = 1716687L
|
||||||
blockheight.put("2018-07-01", 1606715L);
|
blockheight["2019-01-01"] = 1738923L
|
||||||
blockheight.put("2018-08-01", 1629017L);
|
blockheight["2019-02-01"] = 1761435L
|
||||||
blockheight.put("2018-09-01", 1651347L);
|
blockheight["2019-03-01"] = 1781681L
|
||||||
blockheight.put("2018-10-01", 1673031L);
|
blockheight["2019-04-01"] = 1803081L
|
||||||
blockheight.put("2018-11-01", 1695128L);
|
blockheight["2019-05-01"] = 1824671L
|
||||||
blockheight.put("2018-12-01", 1716687L);
|
blockheight["2019-06-01"] = 1847005L
|
||||||
blockheight.put("2019-01-01", 1738923L);
|
blockheight["2019-07-01"] = 1868590L
|
||||||
blockheight.put("2019-02-01", 1761435L);
|
blockheight["2019-08-01"] = 1890878L
|
||||||
blockheight.put("2019-03-01", 1781681L);
|
blockheight["2019-09-01"] = 1913201L
|
||||||
blockheight.put("2019-04-01", 1803081L);
|
blockheight["2019-10-01"] = 1934732L
|
||||||
blockheight.put("2019-05-01", 1824671L);
|
blockheight["2019-11-01"] = 1957051L
|
||||||
blockheight.put("2019-06-01", 1847005L);
|
blockheight["2019-12-01"] = 1978433L
|
||||||
blockheight.put("2019-07-01", 1868590L);
|
blockheight["2020-01-01"] = 2001315L
|
||||||
blockheight.put("2019-08-01", 1890878L);
|
blockheight["2020-02-01"] = 2023656L
|
||||||
blockheight.put("2019-09-01", 1913201L);
|
blockheight["2020-03-01"] = 2044552L
|
||||||
blockheight.put("2019-10-01", 1934732L);
|
blockheight["2020-04-01"] = 2066806L
|
||||||
blockheight.put("2019-11-01", 1957051L);
|
blockheight["2020-05-01"] = 2088411L
|
||||||
blockheight.put("2019-12-01", 1978433L);
|
blockheight["2020-06-01"] = 2110702L
|
||||||
blockheight.put("2020-01-01", 2001315L);
|
blockheight["2020-07-01"] = 2132318L
|
||||||
blockheight.put("2020-02-01", 2023656L);
|
blockheight["2020-08-01"] = 2154590L
|
||||||
blockheight.put("2020-03-01", 2044552L);
|
blockheight["2020-09-01"] = 2176790L
|
||||||
blockheight.put("2020-04-01", 2066806L);
|
blockheight["2020-10-01"] = 2198370L
|
||||||
blockheight.put("2020-05-01", 2088411L);
|
blockheight["2020-11-01"] = 2220670L
|
||||||
blockheight.put("2020-06-01", 2110702L);
|
blockheight["2020-12-01"] = 2242241L
|
||||||
blockheight.put("2020-07-01", 2132318L);
|
blockheight["2021-01-01"] = 2264584L
|
||||||
blockheight.put("2020-08-01", 2154590L);
|
blockheight["2021-02-01"] = 2286892L
|
||||||
blockheight.put("2020-09-01", 2176790L);
|
blockheight["2021-03-01"] = 2307079L
|
||||||
blockheight.put("2020-10-01", 2198370L);
|
blockheight["2021-04-01"] = 2329385L
|
||||||
blockheight.put("2020-11-01", 2220670L);
|
blockheight["2021-05-01"] = 2351004L
|
||||||
blockheight.put("2020-12-01", 2242241L);
|
blockheight["2021-06-01"] = 2373306L
|
||||||
blockheight.put("2021-01-01", 2264584L);
|
blockheight["2021-07-01"] = 2394882L
|
||||||
blockheight.put("2021-02-01", 2286892L);
|
blockheight["2021-08-01"] = 2417162L
|
||||||
blockheight.put("2021-03-01", 2307079L);
|
blockheight["2021-09-01"] = 2439490L
|
||||||
blockheight.put("2021-04-01", 2329385L);
|
blockheight["2021-10-01"] = 2461020L
|
||||||
blockheight.put("2021-05-01", 2351004L);
|
blockheight["2021-11-01"] = 2483377L
|
||||||
blockheight.put("2021-06-01", 2373306L);
|
blockheight["2021-12-01"] = 2504932L
|
||||||
blockheight.put("2021-07-01", 2394882L);
|
blockheight["2022-01-01"] = 2527316L
|
||||||
blockheight.put("2021-08-01", 2417162L);
|
blockheight["2022-02-01"] = 2549605L
|
||||||
blockheight.put("2021-09-01", 2439490L);
|
blockheight["2022-03-01"] = 2569711L
|
||||||
blockheight.put("2021-10-01", 2461020L);
|
|
||||||
blockheight.put("2021-11-01", 2483377L);
|
|
||||||
blockheight.put("2021-12-01", 2504932L);
|
|
||||||
blockheight.put("2022-01-01", 2527316L);
|
|
||||||
blockheight.put("2022-02-01", 2549605L);
|
|
||||||
blockheight.put("2022-03-01", 2569711L);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static public RestoreHeight getInstance() {
|
fun getHeight(date: String?): Long {
|
||||||
if (Singleton == null) {
|
val parser = SimpleDateFormat("yyyy-MM-dd")
|
||||||
synchronized (RestoreHeight.class) {
|
parser.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
if (Singleton == null) {
|
parser.isLenient = false
|
||||||
Singleton = new RestoreHeight();
|
return try {
|
||||||
}
|
getHeight(parser.parse(date))
|
||||||
}
|
} catch (ex: ParseException) {
|
||||||
}
|
throw IllegalArgumentException(ex)
|
||||||
return Singleton;
|
|
||||||
}
|
|
||||||
|
|
||||||
public long getHeight(String date) {
|
|
||||||
SimpleDateFormat parser = new SimpleDateFormat("yyyy-MM-dd");
|
|
||||||
parser.setTimeZone(TimeZone.getTimeZone("UTC"));
|
|
||||||
parser.setLenient(false);
|
|
||||||
try {
|
|
||||||
return getHeight(parser.parse(date));
|
|
||||||
} catch (ParseException ex) {
|
|
||||||
throw new IllegalArgumentException(ex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getHeight(final Date date) {
|
fun getHeight(date: Date?): Long {
|
||||||
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
|
val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
|
||||||
cal.set(Calendar.DST_OFFSET, 0);
|
cal[Calendar.DST_OFFSET] = 0
|
||||||
cal.setTime(date);
|
cal.time = date
|
||||||
cal.add(Calendar.DAY_OF_MONTH, -4); // give it some leeway
|
cal.add(Calendar.DAY_OF_MONTH, -4) // give it some leeway
|
||||||
if (cal.get(Calendar.YEAR) < 2014)
|
if (cal[Calendar.YEAR] < 2014) return 0
|
||||||
return 0;
|
if (cal[Calendar.YEAR] == 2014 && cal[Calendar.MONTH] <= 3) // before May 2014
|
||||||
if ((cal.get(Calendar.YEAR) == 2014) && (cal.get(Calendar.MONTH) <= 3))
|
return 0
|
||||||
// before May 2014
|
val query = cal.clone() as Calendar
|
||||||
return 0;
|
val formatter = SimpleDateFormat("yyyy-MM-dd")
|
||||||
|
formatter.timeZone = TimeZone.getTimeZone("UTC")
|
||||||
Calendar query = (Calendar) cal.clone();
|
val queryDate = formatter.format(date)
|
||||||
|
cal[Calendar.DAY_OF_MONTH] = 1
|
||||||
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
|
var prevTime = cal.timeInMillis
|
||||||
formatter.setTimeZone(TimeZone.getTimeZone("UTC"));
|
var prevDate = formatter.format(prevTime)
|
||||||
|
|
||||||
String queryDate = formatter.format(date);
|
|
||||||
|
|
||||||
cal.set(Calendar.DAY_OF_MONTH, 1);
|
|
||||||
long prevTime = cal.getTimeInMillis();
|
|
||||||
String prevDate = formatter.format(prevTime);
|
|
||||||
// lookup blockheight at first of the month
|
// lookup blockheight at first of the month
|
||||||
Long prevBc = blockheight.get(prevDate);
|
var prevBc = blockheight[prevDate]
|
||||||
if (prevBc == null) {
|
if (prevBc == null) {
|
||||||
// if too recent, go back in time and find latest one we have
|
// if too recent, go back in time and find latest one we have
|
||||||
while (prevBc == null) {
|
while (prevBc == null) {
|
||||||
cal.add(Calendar.MONTH, -1);
|
cal.add(Calendar.MONTH, -1)
|
||||||
if (cal.get(Calendar.YEAR) < 2014) {
|
check(cal[Calendar.YEAR] >= 2014) { "endless loop looking for blockheight" }
|
||||||
throw new IllegalStateException("endless loop looking for blockheight");
|
prevTime = cal.timeInMillis
|
||||||
}
|
prevDate = formatter.format(prevTime)
|
||||||
prevTime = cal.getTimeInMillis();
|
prevBc = blockheight[prevDate]
|
||||||
prevDate = formatter.format(prevTime);
|
|
||||||
prevBc = blockheight.get(prevDate);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
long height = prevBc;
|
var height: Long = prevBc
|
||||||
// now we have a blockheight & a date ON or BEFORE the restore date requested
|
// now we have a blockheight & a date ON or BEFORE the restore date requested
|
||||||
if (queryDate.equals(prevDate)) return height;
|
if (queryDate == prevDate) return height
|
||||||
// see if we have a blockheight after this date
|
// see if we have a blockheight after this date
|
||||||
cal.add(Calendar.MONTH, 1);
|
cal.add(Calendar.MONTH, 1)
|
||||||
long nextTime = cal.getTimeInMillis();
|
val nextTime = cal.timeInMillis
|
||||||
String nextDate = formatter.format(nextTime);
|
val nextDate = formatter.format(nextTime)
|
||||||
Long nextBc = blockheight.get(nextDate);
|
val nextBc = blockheight[nextDate]
|
||||||
if (nextBc != null) { // we have a range - interpolate the blockheight we are looking for
|
height =
|
||||||
long diff = nextBc - prevBc;
|
if (nextBc != null) { // we have a range - interpolate the blockheight we are looking for
|
||||||
long diffDays = TimeUnit.DAYS.convert(nextTime - prevTime, TimeUnit.MILLISECONDS);
|
val diff = nextBc - prevBc
|
||||||
long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime,
|
val diffDays = TimeUnit.DAYS.convert(
|
||||||
TimeUnit.MILLISECONDS);
|
nextTime - prevTime,
|
||||||
height = Math.round(prevBc + diff * (1.0 * days / diffDays));
|
TimeUnit.MILLISECONDS
|
||||||
} else {
|
)
|
||||||
long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime,
|
val days = TimeUnit.DAYS.convert(
|
||||||
TimeUnit.MILLISECONDS);
|
query.timeInMillis - prevTime,
|
||||||
height = Math.round(prevBc + 1.0 * days * (24f * 60 * 60 / DIFFICULTY_TARGET));
|
TimeUnit.MILLISECONDS
|
||||||
}
|
)
|
||||||
return height;
|
Math.round(prevBc + diff * (1.0 * days / diffDays))
|
||||||
|
} else {
|
||||||
|
val days = TimeUnit.DAYS.convert(
|
||||||
|
query.timeInMillis - prevTime,
|
||||||
|
TimeUnit.MILLISECONDS
|
||||||
|
)
|
||||||
|
Math.round(prevBc + 1.0 * days * (24f * 60 * 60 / DIFFICULTY_TARGET))
|
||||||
|
}
|
||||||
|
return height
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
companion object {
|
||||||
|
const val DIFFICULTY_TARGET = 120 // seconds
|
||||||
|
private var Singleton: RestoreHeight? = null
|
||||||
|
@JvmStatic
|
||||||
|
val instance: RestoreHeight?
|
||||||
|
get() {
|
||||||
|
if (Singleton == null) {
|
||||||
|
synchronized(RestoreHeight::class.java) {
|
||||||
|
if (Singleton == null) {
|
||||||
|
Singleton = RestoreHeight()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Singleton
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,46 +13,46 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
package net.mynero.wallet.util
|
||||||
|
|
||||||
package net.mynero.wallet.util;
|
import android.app.Activity
|
||||||
|
import android.content.Context
|
||||||
|
import android.graphics.Color
|
||||||
|
import android.preference.PreferenceManager
|
||||||
|
import android.util.TypedValue
|
||||||
|
import androidx.annotation.ColorInt
|
||||||
|
import net.mynero.wallet.R
|
||||||
|
|
||||||
import android.app.Activity;
|
object ThemeHelper {
|
||||||
import android.content.Context;
|
fun getThemedResourceId(ctx: Context, attrId: Int): Int {
|
||||||
import android.graphics.Color;
|
val typedValue = TypedValue()
|
||||||
import android.preference.PreferenceManager;
|
return if (ctx.theme.resolveAttribute(
|
||||||
import android.util.TypedValue;
|
attrId,
|
||||||
|
typedValue,
|
||||||
import androidx.annotation.ColorInt;
|
true
|
||||||
import androidx.annotation.NonNull;
|
)
|
||||||
|
) typedValue.resourceId else 0
|
||||||
import net.mynero.wallet.R;
|
|
||||||
|
|
||||||
public class ThemeHelper {
|
|
||||||
static public int getThemedResourceId(Context ctx, int attrId) {
|
|
||||||
final TypedValue typedValue = new TypedValue();
|
|
||||||
if (ctx.getTheme().resolveAttribute(attrId, typedValue, true))
|
|
||||||
return typedValue.resourceId;
|
|
||||||
else
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
@ColorInt
|
@ColorInt
|
||||||
static public int getThemedColor(Context ctx, int attrId) {
|
fun getThemedColor(ctx: Context, attrId: Int): Int {
|
||||||
final TypedValue typedValue = new TypedValue();
|
val typedValue = TypedValue()
|
||||||
if (ctx.getTheme().resolveAttribute(attrId, typedValue, true))
|
return if (ctx.theme.resolveAttribute(
|
||||||
return typedValue.data;
|
attrId,
|
||||||
else
|
typedValue,
|
||||||
return Color.BLACK;
|
true
|
||||||
|
)
|
||||||
|
) typedValue.data else Color.BLACK
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setTheme(@NonNull Activity activity, @NonNull String theme) {
|
fun setTheme(activity: Activity, theme: String) {
|
||||||
activity.setTheme(R.style.MyMaterialThemeOled);
|
activity.setTheme(R.style.MyMaterialThemeOled)
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void setPreferred(Activity activity) {
|
fun setPreferred(activity: Activity) {
|
||||||
final String theme = PreferenceManager.getDefaultSharedPreferences(activity)
|
val theme = PreferenceManager.getDefaultSharedPreferences(activity)
|
||||||
.getString(activity.getString(R.string.preferred_theme), "Classic");
|
.getString(activity.getString(R.string.preferred_theme), "Classic")
|
||||||
setTheme(activity, theme);
|
theme?.let { setTheme(activity, it) }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
|
@ -1,65 +1,55 @@
|
||||||
package net.mynero.wallet.util;
|
package net.mynero.wallet.util
|
||||||
|
|
||||||
import net.mynero.wallet.model.Wallet;
|
import net.mynero.wallet.model.Wallet.Companion.getPaymentIdFromAddress
|
||||||
import net.mynero.wallet.model.WalletManager;
|
import net.mynero.wallet.model.Wallet.Companion.isAddressValid
|
||||||
|
import net.mynero.wallet.model.WalletManager.Companion.instance
|
||||||
|
|
||||||
import java.util.HashMap;
|
class UriData(@JvmField val address: String, val params: HashMap<String, String>) {
|
||||||
|
|
||||||
public class UriData {
|
fun hasPaymentId(): Boolean {
|
||||||
private final String address;
|
return instance?.wallet?.nettype()?.let { getPaymentIdFromAddress(address, it) }?.isNotEmpty() == true
|
||||||
private final HashMap<String, String> params;
|
|
||||||
|
|
||||||
public UriData(String address, HashMap<String, String> params) {
|
|
||||||
this.address = address;
|
|
||||||
this.params = params;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static UriData parse(String uri) {
|
val amount: String?
|
||||||
HashMap<String, String> params = new HashMap<>();
|
get() = params[Constants.URI_ARG_AMOUNT]
|
||||||
String[] uriParts = uri.replace(Constants.URI_PREFIX, "").split("\\?");
|
?: params[Constants.URI_ARG_AMOUNT2]
|
||||||
String finalAddress = uriParts[0];
|
|
||||||
String queryParams = "";
|
fun hasAmount(): Boolean {
|
||||||
if (uriParts.length > 1) {
|
return params.containsKey(Constants.URI_ARG_AMOUNT) || params.containsKey(Constants.URI_ARG_AMOUNT2)
|
||||||
queryParams = uriParts[1];
|
}
|
||||||
String[] queryParts = queryParams.split("&");
|
|
||||||
for (String param : queryParts) {
|
companion object {
|
||||||
String[] paramParts = param.split("=");
|
@JvmStatic
|
||||||
if(paramParts.length == 2) {
|
fun parse(uri: String): UriData? {
|
||||||
String variable = paramParts[0];
|
if(uri.isEmpty()) return null
|
||||||
String value = paramParts[1];
|
val params = HashMap<String, String>()
|
||||||
params.put(variable, value);
|
val uriParts = uri.replace(Constants.URI_PREFIX, "").split("\\?".toRegex())
|
||||||
|
.dropLastWhile { it.isEmpty() }
|
||||||
|
.toTypedArray()
|
||||||
|
|
||||||
|
if(uriParts.isEmpty()) return null
|
||||||
|
val finalAddress = uriParts[0]
|
||||||
|
var queryParams = ""
|
||||||
|
if (uriParts.size > 1) {
|
||||||
|
queryParams = uriParts[1]
|
||||||
|
val queryParts =
|
||||||
|
queryParams.split("&".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
for (param in queryParts) {
|
||||||
|
val paramParts =
|
||||||
|
param.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
|
||||||
|
if (paramParts.size == 2) {
|
||||||
|
val variable = paramParts[0]
|
||||||
|
val value = paramParts[1]
|
||||||
|
params[variable] = value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
val valid = isAddressValid(finalAddress)
|
||||||
boolean valid = Wallet.isAddressValid(finalAddress);
|
return if (valid) {
|
||||||
if (valid) {
|
UriData(finalAddress, params)
|
||||||
return new UriData(finalAddress, params);
|
} else {
|
||||||
} else {
|
null
|
||||||
return null;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
public HashMap<String, String> getParams() {
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAddress() {
|
|
||||||
return address;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasPaymentId() {
|
|
||||||
return !Wallet.getPaymentIdFromAddress(this.address, WalletManager.getInstance().getWallet().nettype()).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getAmount() {
|
|
||||||
String txAmount = params.get(Constants.URI_ARG_AMOUNT);
|
|
||||||
if (txAmount == null) {
|
|
||||||
return params.get(Constants.URI_ARG_AMOUNT2);
|
|
||||||
}
|
|
||||||
return txAmount;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean hasAmount() {
|
|
||||||
return params.containsKey(Constants.URI_ARG_AMOUNT) || params.containsKey(Constants.URI_ARG_AMOUNT2);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,12 +1,14 @@
|
||||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||||
buildscript {
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.9.21'
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.4.1'
|
classpath 'com.android.tools.build:gradle:8.1.3'
|
||||||
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.2"
|
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.2"
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,3 +18,6 @@ org.gradle.jvmargs=-Xmx2048m
|
||||||
|
|
||||||
android.useAndroidX=true
|
android.useAndroidX=true
|
||||||
android.enableJetifier=true
|
android.enableJetifier=true
|
||||||
|
android.defaults.buildfeatures.buildconfig=true
|
||||||
|
android.nonTransitiveRClass=false
|
||||||
|
android.nonFinalResIds=false
|
||||||
|
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
@ -1,6 +1,6 @@
|
||||||
#Fri Feb 10 17:24:42 CST 2023
|
#Fri Feb 10 17:24:42 CST 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
|
Loading…
Reference in a new issue