From c1f4b46b3b26d8e2950c5b43d03dbb6102c29f13 Mon Sep 17 00:00:00 2001 From: pokkst Date: Wed, 6 Dec 2023 10:49:36 -0600 Subject: [PATCH] Refactoring (WIP): Convert to Kotlin --- app/build.gradle | 24 +- app/src/main/cpp/monerujo.cpp | 27 - .../java/net/mynero/wallet/MainActivity.java | 143 --- .../java/net/mynero/wallet/MainActivity.kt | 115 +++ .../net/mynero/wallet/MoneroApplication.java | 24 - .../net/mynero/wallet/MoneroApplication.kt | 19 + .../wallet/adapter/CoinsInfoAdapter.java | 18 +- .../wallet/adapter/SubaddressAdapter.java | 6 +- .../adapter/TransactionInfoAdapter.java | 2 - .../net/mynero/wallet/data/DefaultNodes.java | 82 -- .../net/mynero/wallet/data/DefaultNodes.kt | 40 + .../java/net/mynero/wallet/data/Node.java | 382 -------- .../main/java/net/mynero/wallet/data/Node.kt | 295 +++++++ .../net/mynero/wallet/data/Subaddress.java | 78 -- .../java/net/mynero/wallet/data/Subaddress.kt | 38 + .../java/net/mynero/wallet/data/TxData.java | 157 ---- .../java/net/mynero/wallet/data/TxData.kt | 104 +++ .../net/mynero/wallet/data/UserNotes.java | 80 -- .../java/net/mynero/wallet/data/UserNotes.kt | 74 ++ .../dialog/SendBottomSheetDialog.java | 8 +- .../onboarding/OnboardingViewModel.java | 2 +- .../fragment/receive/ReceiveFragment.java | 10 +- .../wallet/fragment/send/SendFragment.java | 2 +- .../wallet/fragment/utxos/UtxosFragment.java | 8 +- .../mynero/wallet/livedata/SingleLiveEvent.kt | 65 +- .../net/mynero/wallet/model/BalanceInfo.java | 48 - .../net/mynero/wallet/model/BalanceInfo.kt | 32 + .../java/net/mynero/wallet/model/Coins.java | 52 -- .../java/net/mynero/wallet/model/Coins.kt | 39 + .../net/mynero/wallet/model/CoinsInfo.java | 131 --- .../java/net/mynero/wallet/model/CoinsInfo.kt | 105 +++ .../{NetworkType.java => NetworkType.kt} | 39 +- .../wallet/model/PendingTransaction.java | 98 --- .../mynero/wallet/model/PendingTransaction.kt | 60 ++ .../mynero/wallet/model/TransactionHistory.kt | 79 +- .../wallet/model/TransactionOutput.java | 22 - .../mynero/wallet/model/TransactionOutput.kt | 3 + .../net/mynero/wallet/model/Transfer.java | 56 -- .../java/net/mynero/wallet/model/Transfer.kt | 55 ++ .../java/net/mynero/wallet/model/Wallet.kt | 828 ++++++++---------- ...{WalletListener.java => WalletListener.kt} | 20 +- .../mynero/wallet/model/WalletManager.java | 366 -------- .../net/mynero/wallet/model/WalletManager.kt | 370 ++++++++ .../mynero/wallet/service/BalanceService.java | 4 +- .../wallet/service/MoneroHandlerThread.java | 8 +- .../mynero/wallet/service/UTXOService.java | 20 +- .../net/mynero/wallet/util/Constants.java | 29 - .../java/net/mynero/wallet/util/Constants.kt | 29 + .../util/{DateHelper.java => DateHelper.kt} | 22 +- .../net/mynero/wallet/util/DayNightMode.kt | 36 +- .../java/net/mynero/wallet/util/Helper.kt | 417 +++++---- .../mynero/wallet/util/KeyStoreHelper.java | 270 ------ .../wallet/util/MoneroThreadPoolExecutor.kt | 70 +- .../net/mynero/wallet/util/NightmodeHelper.kt | 25 +- .../net/mynero/wallet/util/OnionHelper.kt | 16 +- .../net/mynero/wallet/util/RestoreHeight.kt | 356 ++++---- .../net/mynero/wallet/util/ThemeHelper.kt | 66 +- .../java/net/mynero/wallet/util/UriData.kt | 102 +-- build.gradle | 4 +- gradle.properties | 3 + gradle/wrapper/gradle-wrapper.properties | 2 +- 61 files changed, 2428 insertions(+), 3257 deletions(-) delete mode 100644 app/src/main/java/net/mynero/wallet/MainActivity.java create mode 100644 app/src/main/java/net/mynero/wallet/MainActivity.kt delete mode 100644 app/src/main/java/net/mynero/wallet/MoneroApplication.java create mode 100644 app/src/main/java/net/mynero/wallet/MoneroApplication.kt delete mode 100644 app/src/main/java/net/mynero/wallet/data/DefaultNodes.java create mode 100644 app/src/main/java/net/mynero/wallet/data/DefaultNodes.kt delete mode 100644 app/src/main/java/net/mynero/wallet/data/Node.java create mode 100644 app/src/main/java/net/mynero/wallet/data/Node.kt delete mode 100644 app/src/main/java/net/mynero/wallet/data/Subaddress.java create mode 100644 app/src/main/java/net/mynero/wallet/data/Subaddress.kt delete mode 100644 app/src/main/java/net/mynero/wallet/data/TxData.java create mode 100644 app/src/main/java/net/mynero/wallet/data/TxData.kt delete mode 100644 app/src/main/java/net/mynero/wallet/data/UserNotes.java create mode 100644 app/src/main/java/net/mynero/wallet/data/UserNotes.kt delete mode 100644 app/src/main/java/net/mynero/wallet/model/BalanceInfo.java create mode 100644 app/src/main/java/net/mynero/wallet/model/BalanceInfo.kt delete mode 100644 app/src/main/java/net/mynero/wallet/model/Coins.java create mode 100644 app/src/main/java/net/mynero/wallet/model/Coins.kt delete mode 100644 app/src/main/java/net/mynero/wallet/model/CoinsInfo.java create mode 100644 app/src/main/java/net/mynero/wallet/model/CoinsInfo.kt rename app/src/main/java/net/mynero/wallet/model/{NetworkType.java => NetworkType.kt} (51%) delete mode 100644 app/src/main/java/net/mynero/wallet/model/PendingTransaction.java create mode 100644 app/src/main/java/net/mynero/wallet/model/PendingTransaction.kt delete mode 100644 app/src/main/java/net/mynero/wallet/model/TransactionOutput.java create mode 100644 app/src/main/java/net/mynero/wallet/model/TransactionOutput.kt delete mode 100644 app/src/main/java/net/mynero/wallet/model/Transfer.java create mode 100644 app/src/main/java/net/mynero/wallet/model/Transfer.kt rename app/src/main/java/net/mynero/wallet/model/{WalletListener.java => WalletListener.kt} (82%) delete mode 100644 app/src/main/java/net/mynero/wallet/model/WalletManager.java create mode 100644 app/src/main/java/net/mynero/wallet/model/WalletManager.kt delete mode 100644 app/src/main/java/net/mynero/wallet/util/Constants.java create mode 100644 app/src/main/java/net/mynero/wallet/util/Constants.kt rename app/src/main/java/net/mynero/wallet/util/{DateHelper.java => DateHelper.kt} (59%) delete mode 100644 app/src/main/java/net/mynero/wallet/util/KeyStoreHelper.java diff --git a/app/build.gradle b/app/build.gradle index dac84dd..43933bc 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,6 @@ apply plugin: 'com.android.application' apply plugin: "androidx.navigation.safeargs" +apply plugin: 'kotlin-android' android { compileSdkVersion 34 @@ -96,8 +97,8 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_9 - targetCompatibility JavaVersion.VERSION_1_9 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } namespace 'net.mynero.wallet' buildFeatures { @@ -112,16 +113,16 @@ static def getId(name) { } 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.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.swiperefreshlayout:swiperefreshlayout:1.1.0' 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.journeyapps:zxing-android-embedded:4.3.0' implementation "com.jakewharton.timber:timber:5.0.1" @@ -129,10 +130,13 @@ dependencies { implementation fileTree(dir: 'libs/classes', include: ['*.jar']) implementation 'org.slf4j:slf4j-nop:1.7.36' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' - implementation 'androidx.navigation:navigation-fragment:2.5.3' - implementation 'androidx.navigation:navigation-ui:2.5.3' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.2' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2' + implementation 'androidx.navigation:navigation-fragment:2.7.5' + 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 testImplementation "junit:junit:4.13.2" diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp index 5b29960..f9cee9d 100644 --- a/app/src/main/cpp/monerujo.cpp +++ b/app/src/main/cpp/monerujo.cpp @@ -1004,33 +1004,6 @@ Java_net_mynero_wallet_model_Wallet_getDeviceTypeJ(JNIEnv *env, jobject instance return static_cast(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 Java_net_mynero_wallet_model_Wallet_getDisplayAmount(JNIEnv *env, jclass clazz, jlong amount) { diff --git a/app/src/main/java/net/mynero/wallet/MainActivity.java b/app/src/main/java/net/mynero/wallet/MainActivity.java deleted file mode 100644 index 700a255..0000000 --- a/app/src/main/java/net/mynero/wallet/MainActivity.java +++ /dev/null @@ -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()); - } -} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/MainActivity.kt b/app/src/main/java/net/mynero/wallet/MainActivity.kt new file mode 100644 index 0000000..947d324 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/MainActivity.kt @@ -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() + 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() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/MoneroApplication.java b/app/src/main/java/net/mynero/wallet/MoneroApplication.java deleted file mode 100644 index 4686c7c..0000000 --- a/app/src/main/java/net/mynero/wallet/MoneroApplication.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/net/mynero/wallet/MoneroApplication.kt b/app/src/main/java/net/mynero/wallet/MoneroApplication.kt new file mode 100644 index 0000000..62d2cc9 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/MoneroApplication.kt @@ -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) + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/adapter/CoinsInfoAdapter.java b/app/src/main/java/net/mynero/wallet/adapter/CoinsInfoAdapter.java index fc12a54..2dde97a 100644 --- a/app/src/main/java/net/mynero/wallet/adapter/CoinsInfoAdapter.java +++ b/app/src/main/java/net/mynero/wallet/adapter/CoinsInfoAdapter.java @@ -60,7 +60,7 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter selectedUtxos) { this.editing = editing; 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 amountTextView = itemView.findViewById(R.id.utxo_amount_textview); @@ -143,12 +143,12 @@ public class CoinsInfoAdapter extends RecyclerView.Adapter 0) { boolean streetMode = PrefService.getInstance().getBoolean(Constants.PREF_STREET_MODE, false); if(streetMode) { diff --git a/app/src/main/java/net/mynero/wallet/adapter/TransactionInfoAdapter.java b/app/src/main/java/net/mynero/wallet/adapter/TransactionInfoAdapter.java index c8001e7..a4f02e5 100644 --- a/app/src/main/java/net/mynero/wallet/adapter/TransactionInfoAdapter.java +++ b/app/src/main/java/net/mynero/wallet/adapter/TransactionInfoAdapter.java @@ -30,9 +30,7 @@ import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.progressindicator.CircularProgressIndicator; import net.mynero.wallet.R; -import net.mynero.wallet.data.UserNotes; import net.mynero.wallet.model.TransactionInfo; -import net.mynero.wallet.model.Wallet; import net.mynero.wallet.service.PrefService; import net.mynero.wallet.util.Constants; import net.mynero.wallet.util.Helper; diff --git a/app/src/main/java/net/mynero/wallet/data/DefaultNodes.java b/app/src/main/java/net/mynero/wallet/data/DefaultNodes.java deleted file mode 100644 index aeacb78..0000000 --- a/app/src/main/java/net/mynero/wallet/data/DefaultNodes.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/net/mynero/wallet/data/DefaultNodes.kt b/app/src/main/java/net/mynero/wallet/data/DefaultNodes.kt new file mode 100644 index 0000000..4a5a7b7 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/data/DefaultNodes.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/data/Node.java b/app/src/main/java/net/mynero/wallet/data/Node.java deleted file mode 100644 index 43f7070..0000000 --- a/app/src/main/java/net/mynero/wallet/data/Node.java +++ /dev/null @@ -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; - } -} diff --git a/app/src/main/java/net/mynero/wallet/data/Node.kt b/app/src/main/java/net/mynero/wallet/data/Node.kt new file mode 100644 index 0000000..d592f69 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/data/Node.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/data/Subaddress.java b/app/src/main/java/net/mynero/wallet/data/Subaddress.java deleted file mode 100644 index 9a3920b..0000000 --- a/app/src/main/java/net/mynero/wallet/data/Subaddress.java +++ /dev/null @@ -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 { - 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; - } -} diff --git a/app/src/main/java/net/mynero/wallet/data/Subaddress.kt b/app/src/main/java/net/mynero/wallet/data/Subaddress.kt new file mode 100644 index 0000000..f00b7f4 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/data/Subaddress.kt @@ -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 { + @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}$") + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/data/TxData.java b/app/src/main/java/net/mynero/wallet/data/TxData.java deleted file mode 100644 index 5953917..0000000 --- a/app/src/main/java/net/mynero/wallet/data/TxData.java +++ /dev/null @@ -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 CREATOR = new Parcelable.Creator() { - 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 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 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 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(); - } -} diff --git a/app/src/main/java/net/mynero/wallet/data/TxData.kt b/app/src/main/java/net/mynero/wallet/data/TxData.kt new file mode 100644 index 0000000..84443ec --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/data/TxData.kt @@ -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? = 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?) { + 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 { + override fun createFromParcel(parcel: Parcel): TxData { + return TxData(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/data/UserNotes.java b/app/src/main/java/net/mynero/wallet/data/UserNotes.java deleted file mode 100644 index 69e8973..0000000 --- a/app/src/main/java/net/mynero/wallet/data/UserNotes.java +++ /dev/null @@ -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(); - } -} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/data/UserNotes.kt b/app/src/main/java/net/mynero/wallet/data/UserNotes.kt new file mode 100644 index 0000000..4ed985e --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/data/UserNotes.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/SendBottomSheetDialog.java b/app/src/main/java/net/mynero/wallet/fragment/dialog/SendBottomSheetDialog.java index c46470a..8b9cfa1 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/dialog/SendBottomSheetDialog.java +++ b/app/src/main/java/net/mynero/wallet/fragment/dialog/SendBottomSheetDialog.java @@ -106,7 +106,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { donateTextView = view.findViewById(R.id.donate_label_textview); donateTextView.setOnClickListener(view1 -> addressEditText.setText(Constants.DONATE_ADDRESS)); if (uriData != null) { - addressEditText.setText(uriData.getAddress()); + addressEditText.setText(uriData.address); if (uriData.hasAmount()) { amountEditText.setText(uriData.getAmount()); } @@ -116,8 +116,8 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { long selectedValue = 0; for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) { - if (selectedUtxos.contains(coinsInfo.getKeyImage())) { - selectedValue += coinsInfo.getAmount(); + if (selectedUtxos.contains(coinsInfo.keyImage)) { + selectedValue += coinsInfo.amount; } } @@ -338,7 +338,7 @@ public class SendBottomSheetDialog extends BottomSheetDialogFragment { private void pasteAddress(String address) { UriData uriData = UriData.parse(address); if (uriData != null) { - addressEditText.setText(uriData.getAddress()); + addressEditText.setText(uriData.address); if (uriData.hasAmount()) { amountEditText.setText(uriData.getAmount()); } diff --git a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingViewModel.java b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingViewModel.java index be6f74f..c3ea781 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingViewModel.java +++ b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingViewModel.java @@ -132,7 +132,7 @@ public class OnboardingViewModel extends ViewModel { } else { mainActivity.runOnUiThread(() -> { _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(); }); } }); diff --git a/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveFragment.java b/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveFragment.java index 479e9e5..eb8f8d4 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveFragment.java +++ b/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveFragment.java @@ -91,7 +91,7 @@ public class ReceiveFragment extends Fragment { private void editAddressLabel(Subaddress subaddress) { EditAddressLabelBottomSheetDialog dialog = new EditAddressLabelBottomSheetDialog(); - dialog.addressIndex = subaddress.getAddressIndex(); + dialog.addressIndex = subaddress.addressIndex; dialog.listener = () -> mViewModel.init(); dialog.show(getParentFragmentManager(), "edit_address_dialog"); } @@ -99,11 +99,11 @@ public class ReceiveFragment extends Fragment { private void setAddress(Subaddress subaddress) { final String label = subaddress.getDisplayLabel(); final String address = getContext().getString(R.string.subbaddress_info_subtitle, - subaddress.getAddressIndex(), subaddress.getSquashedAddress()); + subaddress.addressIndex, subaddress.getSquashedAddress()); addressLabelTextView.setText(label.isEmpty() ? address : label); - addressTextView.setText(subaddress.getAddress()); - addressImageView.setImageBitmap(generate(subaddress.getAddress(), 256, 256)); - copyAddressImageButton.setOnClickListener(view1 -> Helper.clipBoardCopy(getContext(), "address", subaddress.getAddress())); + addressTextView.setText(subaddress.address); + addressImageView.setImageBitmap(generate(subaddress.address, 256, 256)); + copyAddressImageButton.setOnClickListener(view1 -> Helper.clipBoardCopy(getContext(), "address", subaddress.address)); addressLabelTextView.setOnLongClickListener(v -> { editAddressLabel(subaddress); return true; diff --git a/app/src/main/java/net/mynero/wallet/fragment/send/SendFragment.java b/app/src/main/java/net/mynero/wallet/fragment/send/SendFragment.java index fb5e370..de99318 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/send/SendFragment.java +++ b/app/src/main/java/net/mynero/wallet/fragment/send/SendFragment.java @@ -411,7 +411,7 @@ public class SendFragment extends Fragment { mViewModel.setShowAddOutputButton(false); } EditText addressField = entryView.findViewById(R.id.address_edittext); - addressField.setText(uriData.getAddress()); + addressField.setText(uriData.address); if (uriData.hasAmount()) { setAmount(entryView, uriData.getAmount()); } diff --git a/app/src/main/java/net/mynero/wallet/fragment/utxos/UtxosFragment.java b/app/src/main/java/net/mynero/wallet/fragment/utxos/UtxosFragment.java index efe06f5..e34ca9e 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/utxos/UtxosFragment.java +++ b/app/src/main/java/net/mynero/wallet/fragment/utxos/UtxosFragment.java @@ -72,7 +72,7 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf sendUtxosButton.setOnClickListener(view1 -> { ArrayList selectedKeyImages = new ArrayList<>(); for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) { - selectedKeyImages.add(coinsInfo.getKeyImage()); + selectedKeyImages.add(coinsInfo.keyImage); } SendBottomSheetDialog sendDialog = new SendBottomSheetDialog(); sendDialog.listener = this; @@ -82,12 +82,12 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf churnUtxosButton.setOnClickListener(view1 -> { ArrayList selectedKeyImages = new ArrayList<>(); for(CoinsInfo coinsInfo : adapter.getSelectedUtxos().values()) { - selectedKeyImages.add(coinsInfo.getKeyImage()); + selectedKeyImages.add(coinsInfo.keyImage); } SendBottomSheetDialog sendDialog = new SendBottomSheetDialog(); sendDialog.listener = this; sendDialog.isChurning = true; - sendDialog.uriData = UriData.parse(AddressService.getInstance().currentSubaddress().getAddress()); + sendDialog.uriData = UriData.parse(AddressService.getInstance().currentSubaddress().address); sendDialog.selectedUtxos = selectedKeyImages; sendDialog.show(getActivity().getSupportFragmentManager(), null); }); @@ -103,7 +103,7 @@ public class UtxosFragment extends Fragment implements CoinsInfoAdapter.CoinsInf HashMap filteredUtxos = new HashMap<>(); for (CoinsInfo coinsInfo : utxos) { if (!coinsInfo.isSpent()) { - filteredUtxos.put(coinsInfo.getPubKey(), coinsInfo); + filteredUtxos.put(coinsInfo.pubKey, coinsInfo); } } if (filteredUtxos.isEmpty()) { diff --git a/app/src/main/java/net/mynero/wallet/livedata/SingleLiveEvent.kt b/app/src/main/java/net/mynero/wallet/livedata/SingleLiveEvent.kt index c0c48a5..1b84b76 100644 --- a/app/src/main/java/net/mynero/wallet/livedata/SingleLiveEvent.kt +++ b/app/src/main/java/net/mynero/wallet/livedata/SingleLiveEvent.kt @@ -13,65 +13,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package net.mynero.wallet.livedata -package net.mynero.wallet.livedata; - -import android.util.Log; - -import androidx.annotation.MainThread; -import androidx.annotation.Nullable; -import androidx.lifecycle.LifecycleOwner; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.Observer; - -import java.util.concurrent.atomic.AtomicBoolean; +import android.util.Log +import androidx.annotation.MainThread +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 * navigation and Snackbar messages. - *

+ * + * * 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 * explicit call to setValue() or call(). - *

+ * + * * Note that only one observer is going to be notified of changes. */ -public class SingleLiveEvent extends MutableLiveData { - - private static final String TAG = "SingleLiveEvent"; - - private final AtomicBoolean mPending = new AtomicBoolean(false); - +class SingleLiveEvent : MutableLiveData() { + private val mPending = AtomicBoolean(false) @MainThread - @Override - public void observe(LifecycleOwner owner, final Observer observer) { - + override fun observe(owner: LifecycleOwner, observer: Observer) { 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 - super.observe(owner, new Observer() { - @Override - public void onChanged(@Nullable T t) { - if (mPending.compareAndSet(true, false)) { - observer.onChanged(t); - } + super.observe(owner) { value -> + if (mPending.compareAndSet(true, false)) { + observer.onChanged(value) } - }); + } } @MainThread - public void setValue(@Nullable T t) { - mPending.set(true); - super.setValue(t); + override fun setValue(t: T?) { + mPending.set(true) + super.setValue(t) } /** * Used for cases where T is Void, to make calls cleaner. */ @MainThread - public void call() { - setValue(null); + fun call() { + value = null } -} + + companion object { + private const val TAG = "SingleLiveEvent" + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/BalanceInfo.java b/app/src/main/java/net/mynero/wallet/model/BalanceInfo.java deleted file mode 100644 index a815c7b..0000000 --- a/app/src/main/java/net/mynero/wallet/model/BalanceInfo.java +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/BalanceInfo.kt b/app/src/main/java/net/mynero/wallet/model/BalanceInfo.kt new file mode 100644 index 0000000..7d99be8 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/model/BalanceInfo.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/Coins.java b/app/src/main/java/net/mynero/wallet/model/Coins.java deleted file mode 100644 index a9854ca..0000000 --- a/app/src/main/java/net/mynero/wallet/model/Coins.java +++ /dev/null @@ -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 coins = new ArrayList<>(); - - public Coins(long handle) { - this.handle = handle; - } - - public native int getCount(); // over all accounts/subaddresses - - public List getAll() { - return coins; - } - - public void refresh() { - List transactionInfos = refreshJ(); - Timber.d("refresh size=%d", transactionInfos.size()); - coins = transactionInfos; - } - - public native void setFrozen(String publicKey, boolean frozen); - - private native List refreshJ(); -} diff --git a/app/src/main/java/net/mynero/wallet/model/Coins.kt b/app/src/main/java/net/mynero/wallet/model/Coins.kt new file mode 100644 index 0000000..3ec8d78 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/model/Coins.kt @@ -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 = 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 + external fun getCount(): Int + + companion object { + init { + System.loadLibrary("monerujo") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/CoinsInfo.java b/app/src/main/java/net/mynero/wallet/model/CoinsInfo.java deleted file mode 100644 index b6756cb..0000000 --- a/app/src/main/java/net/mynero/wallet/model/CoinsInfo.java +++ /dev/null @@ -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 { - public static final Creator CREATOR = new Creator() { - @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); - } - } -} diff --git a/app/src/main/java/net/mynero/wallet/model/CoinsInfo.kt b/app/src/main/java/net/mynero/wallet/model/CoinsInfo.kt new file mode 100644 index 0000000..c85ffec --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/model/CoinsInfo.kt @@ -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 { + @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 = object : Creator { + override fun createFromParcel(`in`: Parcel): CoinsInfo? { + return CoinsInfo(`in`) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + + init { + System.loadLibrary("monerujo") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/NetworkType.java b/app/src/main/java/net/mynero/wallet/model/NetworkType.kt similarity index 51% rename from app/src/main/java/net/mynero/wallet/model/NetworkType.java rename to app/src/main/java/net/mynero/wallet/model/NetworkType.kt index 882dc64..78b9e89 100644 --- a/app/src/main/java/net/mynero/wallet/model/NetworkType.java +++ b/app/src/main/java/net/mynero/wallet/model/NetworkType.kt @@ -13,33 +13,20 @@ * See the License for the specific language governing permissions and * 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 { - NetworkType_Mainnet(0), - NetworkType_Testnet(1), - NetworkType_Stagenet(2); - - private final int value; - - NetworkType(int value) { - this.value = value; - } - - public static NetworkType fromInteger(int n) { - switch (n) { - case 0: - return NetworkType_Mainnet; - case 1: - return NetworkType_Testnet; - case 2: - return NetworkType_Stagenet; + companion object { + @JvmStatic + fun fromInteger(n: Int): NetworkType? { + when (n) { + 0 -> return NetworkType_Mainnet + 1 -> return NetworkType_Testnet + 2 -> return NetworkType_Stagenet + } + return null } - return null; } - - public int getValue() { - return value; - } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/PendingTransaction.java b/app/src/main/java/net/mynero/wallet/model/PendingTransaction.java deleted file mode 100644 index 8e871bb..0000000 --- a/app/src/main/java/net/mynero/wallet/model/PendingTransaction.java +++ /dev/null @@ -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; - } - - - } - -} diff --git a/app/src/main/java/net/mynero/wallet/model/PendingTransaction.kt b/app/src/main/java/net/mynero/wallet/model/PendingTransaction.kt new file mode 100644 index 0000000..a3a3a80 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/model/PendingTransaction.kt @@ -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") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/TransactionHistory.kt b/app/src/main/java/net/mynero/wallet/model/TransactionHistory.kt index b680722..776c381 100644 --- a/app/src/main/java/net/mynero/wallet/model/TransactionHistory.kt +++ b/app/src/main/java/net/mynero/wallet/model/TransactionHistory.kt @@ -13,69 +13,52 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package net.mynero.wallet.model -package net.mynero.wallet.model; +import timber.log.Timber -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; +class TransactionHistory(private val handle: Long, var accountIndex: Int) { + var all: List = ArrayList() + private set -import timber.log.Timber; - -public class TransactionHistory { - static { - System.loadLibrary("monerujo"); - } - - private final long handle; - - int accountIndex; - private List transactions = new ArrayList<>(); - - public TransactionHistory(long handle, int accountIndex) { - this.handle = handle; - this.accountIndex = accountIndex; - } - - public void setAccountFor(Wallet wallet) { + fun setAccountFor(wallet: Wallet) { if (accountIndex != wallet.getAccountIndex()) { - this.accountIndex = wallet.getAccountIndex(); - refreshWithNotes(wallet); + accountIndex = wallet.getAccountIndex() + refreshWithNotes(wallet) } } - private void loadNotes(Wallet wallet) { - for (TransactionInfo info : transactions) { - info.notes = wallet.getUserNote(info.hash); + private fun loadNotes(wallet: Wallet) { + for (info in all) { + info.notes = wallet.getUserNote(info.hash) } } - //private native long getTransactionByIndexJ(int i); + external fun getCount(): Int - //private native long getTransactionByIdJ(String id); - - public native int getCount(); // over all accounts/subaddresses - - public List getAll() { - return transactions; + fun refreshWithNotes(wallet: Wallet) { + refresh() + loadNotes(wallet) } - void refreshWithNotes(Wallet wallet) { - refresh(); - loadNotes(wallet); - } - - private void refresh() { - List transactionInfos = refreshJ(); - Timber.d("refresh size=%d", transactionInfos.size()); - for (Iterator iterator = transactionInfos.iterator(); iterator.hasNext(); ) { - TransactionInfo info = iterator.next(); + private fun refresh() { + val transactionInfos = refreshJ() + Timber.d("refresh size=%d", transactionInfos.size) + val iterator = transactionInfos.iterator() + while (iterator.hasNext()) { + val info = iterator.next() if (info.accountIndex != accountIndex) { - iterator.remove(); + iterator.remove() } } - transactions = transactionInfos; + all = transactionInfos } - private native List refreshJ(); -} + private external fun refreshJ(): MutableList + + companion object { + init { + System.loadLibrary("monerujo") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/TransactionOutput.java b/app/src/main/java/net/mynero/wallet/model/TransactionOutput.java deleted file mode 100644 index 0480dc7..0000000 --- a/app/src/main/java/net/mynero/wallet/model/TransactionOutput.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/TransactionOutput.kt b/app/src/main/java/net/mynero/wallet/model/TransactionOutput.kt new file mode 100644 index 0000000..56a9eae --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/model/TransactionOutput.kt @@ -0,0 +1,3 @@ +package net.mynero.wallet.model + +class TransactionOutput(@JvmField val destination: String, @JvmField val amount: Long) \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/Transfer.java b/app/src/main/java/net/mynero/wallet/model/Transfer.java deleted file mode 100644 index e4523de..0000000 --- a/app/src/main/java/net/mynero/wallet/model/Transfer.java +++ /dev/null @@ -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 CREATOR = new Parcelable.Creator() { - 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; - } - -} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/Transfer.kt b/app/src/main/java/net/mynero/wallet/model/Transfer.kt new file mode 100644 index 0000000..c515df0 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/model/Transfer.kt @@ -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 { + override fun createFromParcel(parcel: Parcel): Transfer { + return Transfer(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/Wallet.kt b/app/src/main/java/net/mynero/wallet/model/Wallet.kt index 6ec41d6..2b35f70 100644 --- a/app/src/main/java/net/mynero/wallet/model/Wallet.kt +++ b/app/src/main/java/net/mynero/wallet/model/Wallet.kt @@ -13,566 +13,464 @@ * See the License for the specific language governing permissions and * 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; -import android.util.Pair; +class Wallet { + 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; -import androidx.annotation.Nullable; - -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"); + internal constructor(handle: Long) { + this.handle = handle } - boolean synced = false; - private int accountIndex = 0; - private long handle = 0; - private long listenerHandle = 0; - private PendingTransaction pendingTransaction = null; - private TransactionHistory history = null; - private Coins coins = null; - - Wallet(long handle) { - this.handle = handle; + internal constructor(handle: Long, accountIndex: Int) { + this.handle = handle + this.accountIndex = accountIndex } - Wallet(long handle, int accountIndex) { - this.handle = handle; - this.accountIndex = accountIndex; + fun getAccountIndex(): Int { + return accountIndex } - public static native String getDisplayAmount(long amount); - - public static native long getAmountFromString(String amount); - - 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()); + fun setAccountIndex(accountIndex: Int) { + Timber.d("setAccountIndex(%d)", accountIndex) + this.accountIndex = accountIndex + history?.setAccountFor(this) } - 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() { - return accountIndex; + private external fun statusWithErrorString(): Status + @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) { - Timber.d("setAccountIndex(%d)", accountIndex); - this.accountIndex = accountIndex; - getHistory().setAccountFor(this); + private fun getSubaddress(addressIndex: Int): String { + return getAddressJ(accountIndex, addressIndex) } - public String getName() { - return new File(getPath()).getName(); + fun getSubaddress(accountIndex: Int, addressIndex: Int): String { + return getAddressJ(accountIndex, addressIndex) } - public native String getSeed(String offset); - - public native String getLegacySeed(String offset); - - public native boolean isPolyseedSupported(String offset); - - public native String getSeedLanguage(); - - public native void setSeedLanguage(String language); - - public Status getStatus() { - return statusWithErrorString(); + private external fun getAddressJ(accountIndex: Int, addressIndex: Int): String + private fun getSubaddressObject(accountIndex: Int, subAddressIndex: Int): Subaddress { + return Subaddress( + accountIndex, subAddressIndex, + getSubaddress(subAddressIndex), getSubaddressLabel(subAddressIndex) + ) } - public Status getFullStatus() { - Wallet.Status walletStatus = statusWithErrorString(); - walletStatus.setConnectionStatus(getConnectionStatus()); - return walletStatus; - } - - private native Status statusWithErrorString(); - - 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; + fun getSubaddressObject(subAddressIndex: Int): Subaddress { + val subaddress = getSubaddressObject(accountIndex, subAddressIndex) + var amount: Long = 0 + history?.let { history -> + for (info in history.all) { + 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(); - - public NetworkType getNetworkType() { - return NetworkType.fromInteger(nettype()); + //TODO virtual void setTrustedDaemon(bool arg) = 0; + //TODO virtual bool trustedDaemon() const = 0; + @Synchronized + external fun store(path: String?): Boolean + fun close(): Boolean { + disposePendingTransaction() + return instance?.close(this) == true } - public native int nettype(); - -// 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(); + external fun getFilename(): String // virtual std::string keysFilename() const = 0; - public boolean init(long upper_transaction_size_limit) { - String daemon_address = WalletManager.getInstance().getDaemonAddress(); - String daemon_username = WalletManager.getInstance().getDaemonUsername(); - String daemon_password = WalletManager.getInstance().getDaemonPassword(); - String proxy_address = WalletManager.getInstance().getProxy(); - Log.d("Wallet.java", "init("); - if (daemon_address != null) { - Log.d("Wallet.java", daemon_address.toString()); + fun init(upperTransactionSizeLimit: Long): Boolean { + var daemonAddress = instance?.getDaemonAddress() + var daemonUsername = instance?.daemonUsername + var daemonPassword = instance?.daemonPassword + var proxyAddress = instance?.proxy + Timber.d("init(") + if (daemonAddress != null) { + Timber.d(daemonAddress.toString()) } else { - Log.d("Wallet.java", "daemon_address == null"); - daemon_address = ""; + Timber.d("daemon_address == null") + daemonAddress = "" } - Log.d("Wallet.java", "upper_transaction_size_limit = 0 (probably)"); - if (daemon_username != null) { - Log.d("Wallet.java", daemon_username.toString()); + Timber.d("upper_transaction_size_limit = 0 (probably)") + if (daemonUsername != null) { + Timber.d(daemonUsername) } else { - Log.d("Wallet.java", "daemon_username == null"); - daemon_username = ""; + Timber.d("daemon_username == null") + daemonUsername = "" } - if (daemon_password != null) { - Log.d("Wallet.java", daemon_password.toString()); + if (daemonPassword != null) { + Timber.d(daemonPassword) } else { - Log.d("Wallet.java", "daemon_password == null"); - daemon_password = ""; + Timber.d("daemon_password == null") + daemonPassword = "" } - if (proxy_address != null) { - Log.d("Wallet.java", proxy_address.toString()); + if (proxyAddress != null) { + Timber.d(proxyAddress) } else { - Log.d("Wallet.java", "proxy_address == null"); - proxy_address = ""; + Timber.d("proxy_address == null") + proxyAddress = "" } - Log.d("Wallet.java", ");"); - return initJ(daemon_address, upper_transaction_size_limit, - daemon_username, daemon_password, - proxy_address); + Timber.d(");") + return initJ( + daemonAddress, upperTransactionSizeLimit, + 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, - String daemon_username, String daemon_password, String proxy); + external fun getRestoreHeight(): Long + 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() { - int s = getConnectionStatusJ(); - return Wallet.ConnectionStatus.values()[s]; + external fun setProxy(address: String?): Boolean + val balance: Long + 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(); - - public native boolean setProxy(String address); - - public long getBalance() { - return getBalance(accountIndex); + external fun startRefresh() + external fun pauseRefresh() + external fun refresh(): Boolean + external fun refreshAsync() + private external fun rescanBlockchainAsyncJ() + fun rescanBlockchainAsync() { + isSynchronized = false + rescanBlockchainAsyncJ() } - public native long getBalance(int accountIndex); - - public native long getBalanceAll(); - - 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() { + //TODO virtual void setAutoRefreshInterval(int millis) = 0; + //TODO virtual int autoRefreshInterval() const = 0; + private fun disposePendingTransaction() { if (pendingTransaction != null) { - disposeTransaction(pendingTransaction); - pendingTransaction = null; + disposeTransaction(pendingTransaction) + pendingTransaction = null } } - public long estimateTransactionFee(List> destinations, PendingTransaction.Priority priority) { - int _priority = priority.getValue(); - return estimateTransactionFee(destinations, _priority); + fun estimateTransactionFee( + destinations: List>, + priority: PendingTransaction.Priority + ): Long { + val _priority = priority.value + return estimateTransactionFee(destinations, _priority) } - private native long estimateTransactionFee(List> destinations, int priority); + private external fun estimateTransactionFee( + destinations: List>, + priority: Int + ): Long - public PendingTransaction createSweepTransaction(String dst_addr, PendingTransaction.Priority priority, ArrayList key_images) { - disposePendingTransaction(); - int _priority = priority.getValue(); - long txHandle = createSweepTransaction(dst_addr, "", 0, _priority, accountIndex, key_images); - pendingTransaction = new PendingTransaction(txHandle); - return pendingTransaction; + fun createSweepTransaction( + dstAddr: String, + priority: PendingTransaction.Priority, + keyImages: ArrayList + ): PendingTransaction? { + disposePendingTransaction() + val _priority = priority.value + val txHandle = createSweepTransaction(dstAddr, "", 0, _priority, accountIndex, keyImages) + pendingTransaction = PendingTransaction(txHandle) + return pendingTransaction } - public PendingTransaction createTransactionMultDest(List outputs, PendingTransaction.Priority priority, ArrayList key_images) { - disposePendingTransaction(); - int _priority = priority.getValue(); - ArrayList destinations = new ArrayList<>(); - long[] amounts = new long[outputs.size()]; - for(int i = 0; i < outputs.size(); i++) { - TransactionOutput output = outputs.get(i); - destinations.add(output.getDestination()); - amounts[i] = output.getAmount(); + fun createTransactionMultDest( + outputs: List, + priority: PendingTransaction.Priority, + keyImages: ArrayList + ): PendingTransaction? { + disposePendingTransaction() + val _priority = priority.value + val destinations = ArrayList() + 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, - accountIndex, key_images); - pendingTransaction = new PendingTransaction(txHandle); - return pendingTransaction; + val txHandle = createTransactionMultDestJ( + destinations, "", amounts, 0, _priority, + accountIndex, keyImages + ) + pendingTransaction = PendingTransaction(txHandle) + return pendingTransaction } - private native long createTransactionMultDestJ(ArrayList dst_addrs, String payment_id, - long[] amount, int mixin_count, - int priority, int accountIndex, ArrayList key_images); + private external fun createTransactionMultDestJ( + dstAddrs: ArrayList, paymentId: String, + amount: LongArray, mixinCount: Int, + priority: Int, accountIndex: Int, keyImages: ArrayList + ): Long - private native long createTransactionJ(String dst_addr, String payment_id, - long amount, int mixin_count, - int priority, int accountIndex, ArrayList key_images); + private external fun createTransactionJ( + dstAddr: String, paymentId: String, + amount: Long, mixinCount: Int, + priority: Int, accountIndex: Int, keyImages: ArrayList + ): Long - private native long createSweepTransaction(String dst_addr, String payment_id, - int mixin_count, - int priority, int accountIndex, ArrayList key_images); + private external fun createSweepTransaction( + dstAddr: String, paymentId: String, + mixinCount: Int, + priority: Int, accountIndex: Int, keyImages: ArrayList + ): Long - public PendingTransaction createSweepUnmixableTransaction() { - disposePendingTransaction(); - long txHandle = createSweepUnmixableTransactionJ(); - pendingTransaction = new PendingTransaction(txHandle); - return pendingTransaction; + fun createSweepUnmixableTransaction(): PendingTransaction? { + disposePendingTransaction() + val txHandle = createSweepUnmixableTransactionJ() + pendingTransaction = PendingTransaction(txHandle) + 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); - - public TransactionHistory getHistory() { - if (history == null) { - history = new TransactionHistory(getHistoryJ(), accountIndex); - } - return history; + //virtual bool exportKeyImages(const std::string &filename) = 0; + //virtual bool importKeyImages(const std::string &filename) = 0; + //virtual TransactionHistory * history() const = 0; + fun refreshHistory() { + history?.refreshWithNotes(this) } -//virtual UnsignedTransaction * loadUnsignedTx(const std::string &unsigned_filename) = 0; -//virtual bool submitTransaction(const std::string &fileName) = 0; - - 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(); + fun refreshCoins() { + if (isSynchronized) { + coins?.refresh() } } - private native long setListenerJ(WalletListener listener); - - public void setListener(WalletListener listener) { - this.listenerHandle = setListenerJ(listener); + private external fun setListenerJ(listener: WalletListener): Long + fun setListener(listener: WalletListener) { + 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; -//virtual void setListener(WalletListener *) = 0; - - public native void setDefaultMixin(int mixin); - - 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 &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(); + private fun getAccountLabel(accountIndex: Int): String { + var label = getSubaddressLabel(accountIndex, 0) + if (label == NEW_ACCOUNT_NAME) { + val address = getAddress(accountIndex) + val len = address.length 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) { - return getSubaddressLabel(accountIndex, addressIndex); + fun getSubaddressLabel(addressIndex: Int): String { + return getSubaddressLabel(accountIndex, addressIndex) } - public native String getSubaddressLabel(int accountIndex, int addressIndex); - - public void setAccountLabel(int accountIndex, String label) { - setSubaddressLabel(accountIndex, 0, label); + private external fun getSubaddressLabel(accountIndex: Int, addressIndex: Int): String + private fun setAccountLabel(accountIndex: Int, label: String?) { + setSubaddressLabel(accountIndex, 0, label) } - public void setSubaddressLabel(int addressIndex, String label) { - setSubaddressLabel(accountIndex, addressIndex, label); - refreshHistory(); + fun setSubaddressLabel(addressIndex: Int, label: String?) { + setSubaddressLabel(accountIndex, addressIndex, label) + 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() { - return getNumSubaddresses(accountIndex); + private fun getNewSubaddress(accountIndex: Int): String { + 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); - - public String getNewSubaddress() { - return getNewSubaddress(accountIndex); + external fun addSubaddress(accountIndex: Int, label: String?) + private fun getLastSubaddress(accountIndex: Int): String { + return getSubaddress(accountIndex, getNumSubaddresses(accountIndex) - 1) } - public String getNewSubaddress(int accountIndex) { - String timeStamp = new SimpleDateFormat("yyyy-MM-dd-HH:mm:ss", Locale.US).format(new Date()); - addSubaddress(accountIndex, timeStamp); - String subaddress = getLastSubaddress(accountIndex); - 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; + val deviceType: Device + get() { + val device = getDeviceTypeJ() + return Device.values()[device + 1] // mapping is monero+1=android } - public int getAccountLookahead() { - return accountLookahead; + private external fun getDeviceTypeJ(): Int + + 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() { - return subaddressLookahead; + val isOk: Boolean + get() = (status == StatusEnum.Status_Ok + && (connectionStatus == null || connectionStatus == ConnectionStatus.ConnectionStatus_Connected)) + + override fun toString(): String { + return "Wallet.Status: $status/$errorString/$connectionStatus" } } - public enum StatusEnum { - Status_Ok, - Status_Error, - Status_Critical + companion object { + const val SWEEP_ALL = Long.MAX_VALUE + private const val NEW_ACCOUNT_NAME = "Untitled account" // src/wallet/wallet2.cpp:941 + + 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; - } - } - -} +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/WalletListener.java b/app/src/main/java/net/mynero/wallet/model/WalletListener.kt similarity index 82% rename from app/src/main/java/net/mynero/wallet/model/WalletListener.java rename to app/src/main/java/net/mynero/wallet/model/WalletListener.kt index 42eb9c1..1a9d936 100644 --- a/app/src/main/java/net/mynero/wallet/model/WalletListener.java +++ b/app/src/main/java/net/mynero/wallet/model/WalletListener.kt @@ -13,17 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package net.mynero.wallet.model -package net.mynero.wallet.model; - -public interface WalletListener { +interface WalletListener { /** * moneySpent - called when money spent * * @param txId - transaction id * @param amount - tvAmount */ - void moneySpent(String txId, long amount); + fun moneySpent(txId: String?, amount: Long) /** * moneyReceived - called when money received @@ -31,7 +30,7 @@ public interface WalletListener { * @param txId - transaction id * @param amount - tvAmount */ - void moneyReceived(String txId, long amount); + fun moneyReceived(txId: String?, amount: Long) /** * unconfirmedMoneyReceived - called when payment arrived in tx pool @@ -39,23 +38,22 @@ public interface WalletListener { * @param txId - transaction id * @param amount - tvAmount */ - void unconfirmedMoneyReceived(String txId, long amount); + fun unconfirmedMoneyReceived(txId: String?, amount: Long) /** * newBlock - called when new block received * * @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; */ - void updated(); + fun updated() /** * refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously */ - void refreshed(); - -} + fun refreshed() +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/WalletManager.java b/app/src/main/java/net/mynero/wallet/model/WalletManager.java deleted file mode 100644 index c58d0ea..0000000 --- a/app/src/main/java/net/mynero/wallet/model/WalletManager.java +++ /dev/null @@ -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 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 findWallets(File path) { - List 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 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 { - 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()); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/model/WalletManager.kt b/app/src/main/java/net/mynero/wallet/model/WalletManager.kt new file mode 100644 index 0000000..7f9664a --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/model/WalletManager.kt @@ -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 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 { + val wallets: MutableList = 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 { + 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? + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/service/BalanceService.java b/app/src/main/java/net/mynero/wallet/service/BalanceService.java index ab859b4..8bce1b6 100644 --- a/app/src/main/java/net/mynero/wallet/service/BalanceService.java +++ b/app/src/main/java/net/mynero/wallet/service/BalanceService.java @@ -33,7 +33,7 @@ public class BalanceService extends ServiceBase { long unlocked = 0; for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) { if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && coinsInfo.isUnlocked() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) { - unlocked += coinsInfo.getAmount(); + unlocked += coinsInfo.amount; } } return unlocked; @@ -43,7 +43,7 @@ public class BalanceService extends ServiceBase { long total = 0; for(CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) { if(!coinsInfo.isSpent() && !coinsInfo.isFrozen() && !UTXOService.getInstance().isCoinFrozen(coinsInfo)) { - total += coinsInfo.getAmount(); + total += coinsInfo.amount; } } return total; diff --git a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java index dcb322a..f2ef98f 100644 --- a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java +++ b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.java @@ -100,7 +100,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener { @Override public void refreshed() { - Wallet.ConnectionStatus status = wallet.getFullStatus().getConnectionStatus(); + Wallet.ConnectionStatus status = wallet.getFullStatus().connectionStatus; if (status == Wallet.ConnectionStatus.ConnectionStatus_Disconnected || status == null) { if (triesLeft > 0) { wallet.startRefresh(); @@ -162,7 +162,7 @@ public class MoneroHandlerThread extends Thread implements WalletListener { private List maybeAddDonationOutputs(long amount, List outputs, List 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 - String paymentId = Wallet.getPaymentIdFromAddress(mainDestination.getDestination(), WalletManager.getInstance().getNetworkType().getValue()); + String paymentId = Wallet.getPaymentIdFromAddress(mainDestination.destination, WalletManager.getInstance().networkType.value); ArrayList newOutputs = new ArrayList<>(outputs); 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) @@ -205,8 +205,8 @@ public class MoneroHandlerThread extends Thread implements WalletListener { if (!sendAll) { long amountSelected = 0; for (CoinsInfo coinsInfo : UTXOService.getInstance().getUtxos()) { - if (selectedUtxos.contains(coinsInfo.getKeyImage())) { - amountSelected += coinsInfo.getAmount(); + if (selectedUtxos.contains(coinsInfo.keyImage)) { + amountSelected += coinsInfo.amount; } } diff --git a/app/src/main/java/net/mynero/wallet/service/UTXOService.java b/app/src/main/java/net/mynero/wallet/service/UTXOService.java index 84d0ed3..364f7ad 100644 --- a/app/src/main/java/net/mynero/wallet/service/UTXOService.java +++ b/app/src/main/java/net/mynero/wallet/service/UTXOService.java @@ -57,10 +57,10 @@ public class UTXOService extends ServiceBase { public void toggleFrozen(HashMap selectedCoins) { ArrayList frozenCoinsCopy = new ArrayList<>(frozenCoins); for(CoinsInfo coin : selectedCoins.values()) { - if(frozenCoinsCopy.contains(coin.getPubKey())) { - frozenCoinsCopy.remove(coin.getPubKey()); + if(frozenCoinsCopy.contains(coin.pubKey)) { + frozenCoinsCopy.remove(coin.pubKey); } else { - frozenCoinsCopy.add(coin.getPubKey()); + frozenCoinsCopy.add(coin.pubKey); } } this.frozenCoins = frozenCoinsCopy; @@ -70,7 +70,7 @@ public class UTXOService extends ServiceBase { } public boolean isCoinFrozen(CoinsInfo coinsInfo) { - return frozenCoins.contains(coinsInfo.getPubKey()); + return frozenCoins.contains(coinsInfo.pubKey); } private void loadFrozenCoins() throws JSONException { @@ -104,18 +104,18 @@ public class UTXOService extends ServiceBase { Collections.sort(utxos); //loop through each utxo 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 send all, add all utxos and set amount to send all - selectedUtxos.add(coinsInfo.getKeyImage()); + selectedUtxos.add(coinsInfo.keyImage); amountSelected = Wallet.SWEEP_ALL; } else { //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())) { - selectedUtxos.add(coinsInfo.getKeyImage()); + if (amountSelected <= amountWithBasicFee && !seenTxs.contains(coinsInfo.hash)) { + selectedUtxos.add(coinsInfo.keyImage); // we don't want to spend multiple utxos from the same transaction, so we prevent that from happening here. - seenTxs.add(coinsInfo.getHash()); - amountSelected += coinsInfo.getAmount(); + seenTxs.add(coinsInfo.hash); + amountSelected += coinsInfo.amount; } } } diff --git a/app/src/main/java/net/mynero/wallet/util/Constants.java b/app/src/main/java/net/mynero/wallet/util/Constants.java deleted file mode 100644 index 323fc5d..0000000 --- a/app/src/main/java/net/mynero/wallet/util/Constants.java +++ /dev/null @@ -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"; -} diff --git a/app/src/main/java/net/mynero/wallet/util/Constants.kt b/app/src/main/java/net/mynero/wallet/util/Constants.kt new file mode 100644 index 0000000..4b988c7 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/util/Constants.kt @@ -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" +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/util/DateHelper.java b/app/src/main/java/net/mynero/wallet/util/DateHelper.kt similarity index 59% rename from app/src/main/java/net/mynero/wallet/util/DateHelper.java rename to app/src/main/java/net/mynero/wallet/util/DateHelper.kt index dd8d0d0..1f6ae29 100644 --- a/app/src/main/java/net/mynero/wallet/util/DateHelper.java +++ b/app/src/main/java/net/mynero/wallet/util/DateHelper.kt @@ -13,17 +13,17 @@ * See the License for the specific language governing permissions and * 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; -import java.text.SimpleDateFormat; -import java.util.Date; - -public class DateHelper { - public static final SimpleDateFormat DATETIME_FORMATTER = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - - public static Date parse(String dateString) throws ParseException { - return DATETIME_FORMATTER.parse(dateString.replaceAll("Z$", "+0000")); +object DateHelper { + @JvmField + val DATETIME_FORMATTER = SimpleDateFormat("yyyy-MM-dd HH:mm:ss") + @Throws(ParseException::class) + fun parse(dateString: String): Date { + return DATETIME_FORMATTER.parse(dateString.replace("Z$".toRegex(), "+0000")) } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/util/DayNightMode.kt b/app/src/main/java/net/mynero/wallet/util/DayNightMode.kt index 121a620..fcbf6f5 100644 --- a/app/src/main/java/net/mynero/wallet/util/DayNightMode.kt +++ b/app/src/main/java/net/mynero/wallet/util/DayNightMode.kt @@ -13,33 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package net.mynero.wallet.util -package net.mynero.wallet.util; +import androidx.appcompat.app.AppCompatDelegate -import androidx.appcompat.app.AppCompatDelegate; - -public enum DayNightMode { +enum class DayNightMode(val nightMode: Int) { // order must match R.array.daynight_themes - AUTO(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM), - DAY(AppCompatDelegate.MODE_NIGHT_NO), - NIGHT(AppCompatDelegate.MODE_NIGHT_YES), + AUTO(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM), DAY(AppCompatDelegate.MODE_NIGHT_NO), NIGHT( + AppCompatDelegate.MODE_NIGHT_YES + ), UNKNOWN(AppCompatDelegate.MODE_NIGHT_UNSPECIFIED); - final private int nightMode; - - DayNightMode(int nightMode) { - this.nightMode = nightMode; - } - - static public DayNightMode getValue(int nightMode) { - for (DayNightMode mode : DayNightMode.values()) { - if (mode.nightMode == nightMode) - return mode; + companion object { + fun getValue(nightMode: Int): DayNightMode { + for (mode in values()) { + if (mode.nightMode == nightMode) return mode + } + return UNKNOWN } - return UNKNOWN; } - - public int getNightMode() { - return nightMode; - } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/util/Helper.kt b/app/src/main/java/net/mynero/wallet/util/Helper.kt index c230c0b..91c019a 100644 --- a/app/src/main/java/net/mynero/wallet/util/Helper.kt +++ b/app/src/main/java/net/mynero/wallet/util/Helper.kt @@ -13,299 +13,294 @@ * See the License for the specific language governing permissions and * 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; -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.Drawable; -import android.graphics.drawable.VectorDrawable; -import android.view.View; -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 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); +object Helper { + const val NOCRAZYPASS_FLAGFILE = ".nocrazypass" + const val XMR_DECIMALS = 12 + val ONE_XMR = Math.round(Math.pow(10.0, XMR_DECIMALS.toDouble())) + const val SHOW_EXCHANGERATES = true + const val PERMISSIONS_REQUEST_CAMERA = 7 + const val HTTP_TIMEOUT = 5000 + private const val WALLET_DIR = "wallets" + private const val MONERO_DIR = "monero" + private val HexArray = "0123456789ABCDEF".toCharArray() + var ALLOW_SHIFT = false + @JvmField + var DISPLAY_DIGITS_INFO = 5 + private var ShakeAnimation: Animation? = null + fun getWalletRoot(context: Context): File { + return getStorage(context, WALLET_DIR) } - static public File getStorage(Context context, String folderName) { - File dir = new File(context.getFilesDir(), folderName); + fun getStorage(context: Context, folderName: String?): File { + val dir = File(context.filesDir, folderName) if (!dir.exists()) { - Timber.i("Creating %s", dir.getAbsolutePath()); - dir.mkdirs(); // try to make it + Timber.i("Creating %s", dir.absolutePath) + dir.mkdirs() // try to make it } - if (!dir.isDirectory()) { - String msg = "Directory " + dir.getAbsolutePath() + " does not exist."; - Timber.e(msg); - throw new IllegalStateException(msg); + if (!dir.isDirectory) { + val msg = "Directory " + dir.absolutePath + " does not exist." + Timber.e(msg) + throw IllegalStateException(msg) } - return dir; + return dir } - static public boolean getCameraPermission(Activity context) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + fun getCameraPermission(context: Activity): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (context.checkSelfPermission(Manifest.permission.CAMERA) - == PackageManager.PERMISSION_DENIED) { - Timber.w("Permission denied for CAMERA - requesting it"); - String[] permissions = {Manifest.permission.CAMERA}; - context.requestPermissions(permissions, PERMISSIONS_REQUEST_CAMERA); - return false; + == PackageManager.PERMISSION_DENIED + ) { + Timber.w("Permission denied for CAMERA - requesting it") + val permissions = arrayOf(Manifest.permission.CAMERA) + context.requestPermissions( + permissions, + PERMISSIONS_REQUEST_CAMERA + ) + false } else { - return true; + true } } else { - return true; + true } } - static public boolean getCameraPermission(Activity context, ActivityResultLauncher launcher) { - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { + @JvmStatic + fun getCameraPermission(context: Activity, launcher: ActivityResultLauncher): Boolean { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (context.checkSelfPermission(Manifest.permission.CAMERA) - == PackageManager.PERMISSION_DENIED) { - Timber.w("Permission denied for CAMERA - requesting it"); - launcher.launch(Manifest.permission.CAMERA); - return false; + == PackageManager.PERMISSION_DENIED + ) { + Timber.w("Permission denied for CAMERA - requesting it") + launcher.launch(Manifest.permission.CAMERA) + false } else { - return true; + true } } else { - return true; + true } } - static public File getWalletFile(Context context, String aWalletName) { - File walletDir = getWalletRoot(context); - File f = new File(walletDir, aWalletName); - Timber.d("wallet=%s size= %d", f.getAbsolutePath(), f.length()); - return f; + fun getWalletFile(context: Context, aWalletName: String?): File { + val walletDir = getWalletRoot(context) + val f = File(walletDir, aWalletName) + Timber.d("wallet=%s size= %d", f.absolutePath, f.length()) + return f } - static public void showKeyboard(Activity act) { - InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); - final View focus = act.getCurrentFocus(); - if (focus != null) - imm.showSoftInput(focus, InputMethodManager.SHOW_IMPLICIT); + fun showKeyboard(act: Activity) { + val imm = act.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + val focus = act.currentFocus + if (focus != null) imm.showSoftInput(focus, InputMethodManager.SHOW_IMPLICIT) } - static public void hideKeyboard(Activity act) { - if (act == null) return; - if (act.getCurrentFocus() == null) { - act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN); + fun hideKeyboard(act: Activity?) { + if (act == null) return + if (act.currentFocus == null) { + act.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN) } else { - InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE); - imm.hideSoftInputFromWindow((null == act.getCurrentFocus()) ? null : act.getCurrentFocus().getWindowToken(), - InputMethodManager.HIDE_NOT_ALWAYS); + val imm = act.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + act.currentFocus?.let { + imm.hideSoftInputFromWindow( + it.windowToken, + InputMethodManager.HIDE_NOT_ALWAYS + ) + } } } - static public void showKeyboard(Dialog dialog) { - dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE); + fun showKeyboard(dialog: Dialog) { + dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) } - static public void hideKeyboardAlways(Activity act) { - act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + fun hideKeyboardAlways(act: Activity) { + act.window.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) } - static public BigDecimal getDecimalAmount(long amount) { - return new BigDecimal(amount).scaleByPowerOfTen(-XMR_DECIMALS); + fun getDecimalAmount(amount: Long): BigDecimal { + return BigDecimal(amount).scaleByPowerOfTen(-XMR_DECIMALS) } - static public String getDisplayAmount(long amount) { - return getDisplayAmount(amount, XMR_DECIMALS); + @JvmStatic + 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 - if (amount == 0) return "0.00"; - BigDecimal d = getDecimalAmount(amount) - .setScale(maxDecimals, BigDecimal.ROUND_HALF_UP) - .stripTrailingZeros(); - if (d.scale() < 2) - d = d.setScale(2, BigDecimal.ROUND_UNNECESSARY); - return d.toPlainString(); + if (amount == 0L) return "0.00" + var d = getDecimalAmount(amount) + .setScale(maxDecimals, BigDecimal.ROUND_HALF_UP) + .stripTrailingZeros() + if (d.scale() < 2) d = d.setScale(2, BigDecimal.ROUND_UNNECESSARY) + 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 - String displayB; - if (isCrypto) { - if ((amount >= 0) || (amount == 0)) { - displayB = String.format(Locale.US, "%,.5f", amount); + val displayB: String? + displayB = if (isCrypto) { + if (amount >= 0 || amount == 0.0) { + String.format(Locale.US, "%,.5f", amount) } else { - displayB = null; + null } } 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 - BigDecimal d = new BigDecimal(amount) - .setScale(XMR_DECIMALS, BigDecimal.ROUND_HALF_UP) - .stripTrailingZeros(); - if (d.scale() < 1) - d = d.setScale(1, BigDecimal.ROUND_UNNECESSARY); - return d.toPlainString(); + var d = BigDecimal(amount) + .setScale(XMR_DECIMALS, BigDecimal.ROUND_HALF_UP) + .stripTrailingZeros() + if (d.scale() < 1) d = d.setScale(1, BigDecimal.ROUND_UNNECESSARY) + return d.toPlainString() } - static public Bitmap getBitmap(Context context, int drawableId) { - Drawable drawable = ContextCompat.getDrawable(context, drawableId); - if (drawable instanceof BitmapDrawable) { - return BitmapFactory.decodeResource(context.getResources(), drawableId); - } else if (drawable instanceof VectorDrawable) { - return getBitmap((VectorDrawable) drawable); + fun getBitmap(context: Context, drawableId: Int): Bitmap { + val drawable = ContextCompat.getDrawable(context, drawableId) + return if (drawable is BitmapDrawable) { + BitmapFactory.decodeResource(context.resources, drawableId) + } else if (drawable is VectorDrawable) { + getBitmap(drawable) } else { - throw new IllegalArgumentException("unsupported drawable type"); + throw IllegalArgumentException("unsupported drawable type") } } - static private Bitmap getBitmap(VectorDrawable vectorDrawable) { - Bitmap bitmap = Bitmap.createBitmap(vectorDrawable.getIntrinsicWidth(), - vectorDrawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888); - Canvas canvas = new Canvas(bitmap); - vectorDrawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); - vectorDrawable.draw(canvas); - return bitmap; + private fun getBitmap(vectorDrawable: VectorDrawable): Bitmap { + val bitmap = Bitmap.createBitmap( + vectorDrawable.intrinsicWidth, + vectorDrawable.intrinsicHeight, Bitmap.Config.ARGB_8888 + ) + val canvas = Canvas(bitmap) + vectorDrawable.setBounds(0, 0, canvas.width, canvas.height) + vectorDrawable.draw(canvas) + return bitmap } - static public String getUrl(String httpsUrl) { - HttpsURLConnection urlConnection = null; + fun getUrl(httpsUrl: String?): String? { + var urlConnection: HttpsURLConnection? = null try { - URL url = new URL(httpsUrl); - urlConnection = (HttpsURLConnection) url.openConnection(); - urlConnection.setConnectTimeout(HTTP_TIMEOUT); - urlConnection.setReadTimeout(HTTP_TIMEOUT); - InputStreamReader in = new InputStreamReader(urlConnection.getInputStream()); - StringBuffer sb = new StringBuffer(); - final int BUFFER_SIZE = 512; - char[] buffer = new char[BUFFER_SIZE]; - int length = in.read(buffer, 0, BUFFER_SIZE); + val url = URL(httpsUrl) + urlConnection = url.openConnection() as HttpsURLConnection + urlConnection.connectTimeout = HTTP_TIMEOUT + urlConnection.readTimeout = HTTP_TIMEOUT + val `in` = InputStreamReader(urlConnection.inputStream) + val sb = StringBuffer() + val BUFFER_SIZE = 512 + val buffer = CharArray(BUFFER_SIZE) + var length = `in`.read(buffer, 0, BUFFER_SIZE) while (length >= 0) { - sb.append(buffer, 0, length); - length = in.read(buffer, 0, BUFFER_SIZE); + sb.append(buffer, 0, length) + length = `in`.read(buffer, 0, BUFFER_SIZE) } - return sb.toString(); - } catch (SocketTimeoutException ex) { - Timber.w("C %s", ex.getLocalizedMessage()); - } catch (MalformedURLException ex) { - Timber.e("A %s", ex.getLocalizedMessage()); - } catch (IOException ex) { - Timber.e("B %s", ex.getLocalizedMessage()); + return sb.toString() + } catch (ex: SocketTimeoutException) { + Timber.w("C %s", ex.localizedMessage) + } catch (ex: MalformedURLException) { + Timber.e("A %s", ex.localizedMessage) + } catch (ex: IOException) { + Timber.e("B %s", ex.localizedMessage) } 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) { - ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); - ClipData clip = ClipData.newPlainText(label, text); - clipboardManager.setPrimaryClip(clip); - Toast.makeText(context, context.getText(R.string.copied_to_clipboard), Toast.LENGTH_SHORT).show(); + val clipboardManager = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText(label, text) + clipboardManager.setPrimaryClip(clip) + Toast.makeText( + context, + context.getText(R.string.copied_to_clipboard), + Toast.LENGTH_SHORT + ).show() } } - static public String getClipBoardText(Context context) { - final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + @JvmStatic + fun getClipBoardText(context: Context): String? { + val clipboardManager = + context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager try { if (clipboardManager.hasPrimaryClip() - && clipboardManager.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) { - final ClipData.Item item = clipboardManager.getPrimaryClip().getItemAt(0); - return item.getText().toString(); + && clipboardManager.primaryClipDescription?.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) == true + ) { + 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 - return null; + return null } - return null; + return null } - static public Animation getShakeAnimation(Context context) { + fun getShakeAnimation(context: Context?): Animation? { if (ShakeAnimation == null) { - synchronized (Helper.class) { + synchronized(Helper::class.java) { if (ShakeAnimation == null) { - ShakeAnimation = AnimationUtils.loadAnimation(context, R.anim.shake); + ShakeAnimation = AnimationUtils.loadAnimation(context, R.anim.shake) } } } - 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; + return ShakeAnimation } // TODO make the log levels refer to the WalletManagerFactory::LogLevel enum ? - static public void initLogger(Context context, int level) { - String home = getStorage(context, MONERO_DIR).getAbsolutePath(); - WalletManager.initLogger(home + "/monerujo", "monerujo.log"); - if (level >= WalletManager.LOGLEVEL_SILENT) - WalletManager.setLogLevel(level); + fun initLogger(context: Context, level: Int) { + val home = getStorage(context, MONERO_DIR).absolutePath + initLogger("$home/monerujo", "monerujo.log") + if (level >= WalletManager.LOGLEVEL_SILENT) setLogLevel(level) } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/util/KeyStoreHelper.java b/app/src/main/java/net/mynero/wallet/util/KeyStoreHelper.java deleted file mode 100644 index dba791b..0000000 --- a/app/src/main/java/net/mynero/wallet/util/KeyStoreHelper.java +++ /dev/null @@ -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); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/util/MoneroThreadPoolExecutor.kt b/app/src/main/java/net/mynero/wallet/util/MoneroThreadPoolExecutor.kt index be37c71..2708ead 100644 --- a/app/src/main/java/net/mynero/wallet/util/MoneroThreadPoolExecutor.kt +++ b/app/src/main/java/net/mynero/wallet/util/MoneroThreadPoolExecutor.kt @@ -13,41 +13,43 @@ * See the License for the specific language governing permissions and * 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; - -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; - - -public class MoneroThreadPoolExecutor { - public static final Executor MONERO_THREAD_POOL_EXECUTOR; - private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); - private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)); - 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); +object MoneroThreadPoolExecutor { + @JvmField + var MONERO_THREAD_POOL_EXECUTOR: Executor? = null + private val CPU_COUNT = Runtime.getRuntime().availableProcessors() + private val CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4)) + private val MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1 + private const val KEEP_ALIVE_SECONDS = 30 + private val sThreadFactory: ThreadFactory = object : ThreadFactory { + private val mCount = AtomicInteger(1) + override fun newThread(r: Runnable): Thread { + return Thread( + null, + r, + "MoneroTask #" + mCount.getAndIncrement(), + MoneroHandlerThread.THREAD_STACK_SIZE + ) } - }; - private static final BlockingQueue 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 = 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/util/NightmodeHelper.kt b/app/src/main/java/net/mynero/wallet/util/NightmodeHelper.kt index 61db511..d22d74b 100644 --- a/app/src/main/java/net/mynero/wallet/util/NightmodeHelper.kt +++ b/app/src/main/java/net/mynero/wallet/util/NightmodeHelper.kt @@ -13,22 +13,19 @@ * See the License for the specific language governing permissions and * 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; - -import androidx.appcompat.app.AppCompatDelegate; - -import net.mynero.wallet.service.PrefService; - -public class NightmodeHelper { - public static void getAndSetPreferredNightmode() { - setNightMode(DayNightMode.NIGHT); - } +object NightmodeHelper { + val preferredNightmode: Unit + get() { + setNightMode(DayNightMode.NIGHT) + } @SuppressLint("WrongConstant") - public static void setNightMode(DayNightMode mode) { - AppCompatDelegate.setDefaultNightMode(mode.getNightMode()); + fun setNightMode(mode: DayNightMode) { + AppCompatDelegate.setDefaultNightMode(mode.nightMode) } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/util/OnionHelper.kt b/app/src/main/java/net/mynero/wallet/util/OnionHelper.kt index b3c4282..28b3bb1 100644 --- a/app/src/main/java/net/mynero/wallet/util/OnionHelper.kt +++ b/app/src/main/java/net/mynero/wallet/util/OnionHelper.kt @@ -13,16 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +package net.mynero.wallet.util -package net.mynero.wallet.util; - -public class OnionHelper { - - public static boolean isOnionHost(String hostname) { - return hostname.endsWith(".onion"); +object OnionHelper { + fun isOnionHost(hostname: String): Boolean { + return hostname.endsWith(".onion") } - public static boolean isI2PHost(String hostname) { - return hostname.endsWith(".i2p"); + fun isI2PHost(hostname: String): Boolean { + return hostname.endsWith(".i2p") } -} +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/util/RestoreHeight.kt b/app/src/main/java/net/mynero/wallet/util/RestoreHeight.kt index bfeb4ad..06c4f19 100644 --- a/app/src/main/java/net/mynero/wallet/util/RestoreHeight.kt +++ b/app/src/main/java/net/mynero/wallet/util/RestoreHeight.kt @@ -13,198 +13,198 @@ * See the License for the specific language governing permissions and * 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; -import java.text.SimpleDateFormat; -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; +class RestoreHeight internal constructor() { + private val blockheight: MutableMap = HashMap() -public class RestoreHeight { - static final int DIFFICULTY_TARGET = 120; // seconds - - static private RestoreHeight Singleton = null; - private final Map blockheight = new HashMap<>(); - - RestoreHeight() { - blockheight.put("2014-05-01", 18844L); - blockheight.put("2014-06-01", 65406L); - blockheight.put("2014-07-01", 108882L); - blockheight.put("2014-08-01", 153594L); - blockheight.put("2014-09-01", 198072L); - blockheight.put("2014-10-01", 241088L); - blockheight.put("2014-11-01", 285305L); - blockheight.put("2014-12-01", 328069L); - blockheight.put("2015-01-01", 372369L); - blockheight.put("2015-02-01", 416505L); - blockheight.put("2015-03-01", 456631L); - blockheight.put("2015-04-01", 501084L); - blockheight.put("2015-05-01", 543973L); - blockheight.put("2015-06-01", 588326L); - blockheight.put("2015-07-01", 631187L); - blockheight.put("2015-08-01", 675484L); - blockheight.put("2015-09-01", 719725L); - blockheight.put("2015-10-01", 762463L); - blockheight.put("2015-11-01", 806528L); - blockheight.put("2015-12-01", 849041L); - blockheight.put("2016-01-01", 892866L); - blockheight.put("2016-02-01", 936736L); - blockheight.put("2016-03-01", 977691L); - blockheight.put("2016-04-01", 1015848L); - blockheight.put("2016-05-01", 1037417L); - blockheight.put("2016-06-01", 1059651L); - blockheight.put("2016-07-01", 1081269L); - blockheight.put("2016-08-01", 1103630L); - blockheight.put("2016-09-01", 1125983L); - blockheight.put("2016-10-01", 1147617L); - blockheight.put("2016-11-01", 1169779L); - blockheight.put("2016-12-01", 1191402L); - blockheight.put("2017-01-01", 1213861L); - blockheight.put("2017-02-01", 1236197L); - blockheight.put("2017-03-01", 1256358L); - blockheight.put("2017-04-01", 1278622L); - blockheight.put("2017-05-01", 1300239L); - blockheight.put("2017-06-01", 1322564L); - blockheight.put("2017-07-01", 1344225L); - blockheight.put("2017-08-01", 1366664L); - blockheight.put("2017-09-01", 1389113L); - blockheight.put("2017-10-01", 1410738L); - blockheight.put("2017-11-01", 1433039L); - blockheight.put("2017-12-01", 1454639L); - blockheight.put("2018-01-01", 1477201L); - blockheight.put("2018-02-01", 1499599L); - blockheight.put("2018-03-01", 1519796L); - blockheight.put("2018-04-01", 1542067L); - blockheight.put("2018-05-01", 1562861L); - blockheight.put("2018-06-01", 1585135L); - blockheight.put("2018-07-01", 1606715L); - blockheight.put("2018-08-01", 1629017L); - blockheight.put("2018-09-01", 1651347L); - blockheight.put("2018-10-01", 1673031L); - blockheight.put("2018-11-01", 1695128L); - blockheight.put("2018-12-01", 1716687L); - blockheight.put("2019-01-01", 1738923L); - blockheight.put("2019-02-01", 1761435L); - blockheight.put("2019-03-01", 1781681L); - blockheight.put("2019-04-01", 1803081L); - blockheight.put("2019-05-01", 1824671L); - blockheight.put("2019-06-01", 1847005L); - blockheight.put("2019-07-01", 1868590L); - blockheight.put("2019-08-01", 1890878L); - blockheight.put("2019-09-01", 1913201L); - blockheight.put("2019-10-01", 1934732L); - blockheight.put("2019-11-01", 1957051L); - blockheight.put("2019-12-01", 1978433L); - blockheight.put("2020-01-01", 2001315L); - blockheight.put("2020-02-01", 2023656L); - blockheight.put("2020-03-01", 2044552L); - blockheight.put("2020-04-01", 2066806L); - blockheight.put("2020-05-01", 2088411L); - blockheight.put("2020-06-01", 2110702L); - blockheight.put("2020-07-01", 2132318L); - blockheight.put("2020-08-01", 2154590L); - blockheight.put("2020-09-01", 2176790L); - blockheight.put("2020-10-01", 2198370L); - blockheight.put("2020-11-01", 2220670L); - blockheight.put("2020-12-01", 2242241L); - blockheight.put("2021-01-01", 2264584L); - blockheight.put("2021-02-01", 2286892L); - blockheight.put("2021-03-01", 2307079L); - blockheight.put("2021-04-01", 2329385L); - blockheight.put("2021-05-01", 2351004L); - blockheight.put("2021-06-01", 2373306L); - blockheight.put("2021-07-01", 2394882L); - blockheight.put("2021-08-01", 2417162L); - blockheight.put("2021-09-01", 2439490L); - 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); + init { + blockheight["2014-05-01"] = 18844L + blockheight["2014-06-01"] = 65406L + blockheight["2014-07-01"] = 108882L + blockheight["2014-08-01"] = 153594L + blockheight["2014-09-01"] = 198072L + blockheight["2014-10-01"] = 241088L + blockheight["2014-11-01"] = 285305L + blockheight["2014-12-01"] = 328069L + blockheight["2015-01-01"] = 372369L + blockheight["2015-02-01"] = 416505L + blockheight["2015-03-01"] = 456631L + blockheight["2015-04-01"] = 501084L + blockheight["2015-05-01"] = 543973L + blockheight["2015-06-01"] = 588326L + blockheight["2015-07-01"] = 631187L + blockheight["2015-08-01"] = 675484L + blockheight["2015-09-01"] = 719725L + blockheight["2015-10-01"] = 762463L + blockheight["2015-11-01"] = 806528L + blockheight["2015-12-01"] = 849041L + blockheight["2016-01-01"] = 892866L + blockheight["2016-02-01"] = 936736L + blockheight["2016-03-01"] = 977691L + blockheight["2016-04-01"] = 1015848L + blockheight["2016-05-01"] = 1037417L + blockheight["2016-06-01"] = 1059651L + blockheight["2016-07-01"] = 1081269L + blockheight["2016-08-01"] = 1103630L + blockheight["2016-09-01"] = 1125983L + blockheight["2016-10-01"] = 1147617L + blockheight["2016-11-01"] = 1169779L + blockheight["2016-12-01"] = 1191402L + blockheight["2017-01-01"] = 1213861L + blockheight["2017-02-01"] = 1236197L + blockheight["2017-03-01"] = 1256358L + blockheight["2017-04-01"] = 1278622L + blockheight["2017-05-01"] = 1300239L + blockheight["2017-06-01"] = 1322564L + blockheight["2017-07-01"] = 1344225L + blockheight["2017-08-01"] = 1366664L + blockheight["2017-09-01"] = 1389113L + blockheight["2017-10-01"] = 1410738L + blockheight["2017-11-01"] = 1433039L + blockheight["2017-12-01"] = 1454639L + blockheight["2018-01-01"] = 1477201L + blockheight["2018-02-01"] = 1499599L + blockheight["2018-03-01"] = 1519796L + blockheight["2018-04-01"] = 1542067L + blockheight["2018-05-01"] = 1562861L + blockheight["2018-06-01"] = 1585135L + blockheight["2018-07-01"] = 1606715L + blockheight["2018-08-01"] = 1629017L + blockheight["2018-09-01"] = 1651347L + blockheight["2018-10-01"] = 1673031L + blockheight["2018-11-01"] = 1695128L + blockheight["2018-12-01"] = 1716687L + blockheight["2019-01-01"] = 1738923L + blockheight["2019-02-01"] = 1761435L + blockheight["2019-03-01"] = 1781681L + blockheight["2019-04-01"] = 1803081L + blockheight["2019-05-01"] = 1824671L + blockheight["2019-06-01"] = 1847005L + blockheight["2019-07-01"] = 1868590L + blockheight["2019-08-01"] = 1890878L + blockheight["2019-09-01"] = 1913201L + blockheight["2019-10-01"] = 1934732L + blockheight["2019-11-01"] = 1957051L + blockheight["2019-12-01"] = 1978433L + blockheight["2020-01-01"] = 2001315L + blockheight["2020-02-01"] = 2023656L + blockheight["2020-03-01"] = 2044552L + blockheight["2020-04-01"] = 2066806L + blockheight["2020-05-01"] = 2088411L + blockheight["2020-06-01"] = 2110702L + blockheight["2020-07-01"] = 2132318L + blockheight["2020-08-01"] = 2154590L + blockheight["2020-09-01"] = 2176790L + blockheight["2020-10-01"] = 2198370L + blockheight["2020-11-01"] = 2220670L + blockheight["2020-12-01"] = 2242241L + blockheight["2021-01-01"] = 2264584L + blockheight["2021-02-01"] = 2286892L + blockheight["2021-03-01"] = 2307079L + blockheight["2021-04-01"] = 2329385L + blockheight["2021-05-01"] = 2351004L + blockheight["2021-06-01"] = 2373306L + blockheight["2021-07-01"] = 2394882L + blockheight["2021-08-01"] = 2417162L + blockheight["2021-09-01"] = 2439490L + blockheight["2021-10-01"] = 2461020L + blockheight["2021-11-01"] = 2483377L + blockheight["2021-12-01"] = 2504932L + blockheight["2022-01-01"] = 2527316L + blockheight["2022-02-01"] = 2549605L + blockheight["2022-03-01"] = 2569711L } - static public RestoreHeight getInstance() { - if (Singleton == null) { - synchronized (RestoreHeight.class) { - if (Singleton == null) { - Singleton = new RestoreHeight(); - } - } - } - 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); + fun getHeight(date: String?): Long { + val parser = SimpleDateFormat("yyyy-MM-dd") + parser.timeZone = TimeZone.getTimeZone("UTC") + parser.isLenient = false + return try { + getHeight(parser.parse(date)) + } catch (ex: ParseException) { + throw IllegalArgumentException(ex) } } - public long getHeight(final Date date) { - Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); - cal.set(Calendar.DST_OFFSET, 0); - cal.setTime(date); - cal.add(Calendar.DAY_OF_MONTH, -4); // give it some leeway - if (cal.get(Calendar.YEAR) < 2014) - return 0; - if ((cal.get(Calendar.YEAR) == 2014) && (cal.get(Calendar.MONTH) <= 3)) - // before May 2014 - return 0; - - Calendar query = (Calendar) cal.clone(); - - SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd"); - formatter.setTimeZone(TimeZone.getTimeZone("UTC")); - - String queryDate = formatter.format(date); - - cal.set(Calendar.DAY_OF_MONTH, 1); - long prevTime = cal.getTimeInMillis(); - String prevDate = formatter.format(prevTime); + fun getHeight(date: Date?): Long { + val cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")) + cal[Calendar.DST_OFFSET] = 0 + cal.time = date + cal.add(Calendar.DAY_OF_MONTH, -4) // give it some leeway + if (cal[Calendar.YEAR] < 2014) return 0 + if (cal[Calendar.YEAR] == 2014 && cal[Calendar.MONTH] <= 3) // before May 2014 + return 0 + val query = cal.clone() as Calendar + val formatter = SimpleDateFormat("yyyy-MM-dd") + formatter.timeZone = TimeZone.getTimeZone("UTC") + val queryDate = formatter.format(date) + cal[Calendar.DAY_OF_MONTH] = 1 + var prevTime = cal.timeInMillis + var prevDate = formatter.format(prevTime) // lookup blockheight at first of the month - Long prevBc = blockheight.get(prevDate); + var prevBc = blockheight[prevDate] if (prevBc == null) { // if too recent, go back in time and find latest one we have while (prevBc == null) { - cal.add(Calendar.MONTH, -1); - if (cal.get(Calendar.YEAR) < 2014) { - throw new IllegalStateException("endless loop looking for blockheight"); - } - prevTime = cal.getTimeInMillis(); - prevDate = formatter.format(prevTime); - prevBc = blockheight.get(prevDate); + cal.add(Calendar.MONTH, -1) + check(cal[Calendar.YEAR] >= 2014) { "endless loop looking for blockheight" } + prevTime = cal.timeInMillis + prevDate = formatter.format(prevTime) + prevBc = blockheight[prevDate] } } - long height = prevBc; + var height: Long = prevBc // 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 - cal.add(Calendar.MONTH, 1); - long nextTime = cal.getTimeInMillis(); - String nextDate = formatter.format(nextTime); - Long nextBc = blockheight.get(nextDate); - if (nextBc != null) { // we have a range - interpolate the blockheight we are looking for - long diff = nextBc - prevBc; - long diffDays = TimeUnit.DAYS.convert(nextTime - prevTime, TimeUnit.MILLISECONDS); - long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime, - TimeUnit.MILLISECONDS); - height = Math.round(prevBc + diff * (1.0 * days / diffDays)); - } else { - long days = TimeUnit.DAYS.convert(query.getTimeInMillis() - prevTime, - TimeUnit.MILLISECONDS); - height = Math.round(prevBc + 1.0 * days * (24f * 60 * 60 / DIFFICULTY_TARGET)); - } - return height; + cal.add(Calendar.MONTH, 1) + val nextTime = cal.timeInMillis + val nextDate = formatter.format(nextTime) + val nextBc = blockheight[nextDate] + height = + if (nextBc != null) { // we have a range - interpolate the blockheight we are looking for + val diff = nextBc - prevBc + val diffDays = TimeUnit.DAYS.convert( + nextTime - prevTime, + TimeUnit.MILLISECONDS + ) + val days = TimeUnit.DAYS.convert( + query.timeInMillis - prevTime, + TimeUnit.MILLISECONDS + ) + 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 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/util/ThemeHelper.kt b/app/src/main/java/net/mynero/wallet/util/ThemeHelper.kt index 72c173d..4d0a580 100644 --- a/app/src/main/java/net/mynero/wallet/util/ThemeHelper.kt +++ b/app/src/main/java/net/mynero/wallet/util/ThemeHelper.kt @@ -13,46 +13,46 @@ * See the License for the specific language governing permissions and * 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; -import android.content.Context; -import android.graphics.Color; -import android.preference.PreferenceManager; -import android.util.TypedValue; - -import androidx.annotation.ColorInt; -import androidx.annotation.NonNull; - -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; +object ThemeHelper { + fun getThemedResourceId(ctx: Context, attrId: Int): Int { + val typedValue = TypedValue() + return if (ctx.theme.resolveAttribute( + attrId, + typedValue, + true + ) + ) typedValue.resourceId else 0 } + @JvmStatic @ColorInt - static public int getThemedColor(Context ctx, int attrId) { - final TypedValue typedValue = new TypedValue(); - if (ctx.getTheme().resolveAttribute(attrId, typedValue, true)) - return typedValue.data; - else - return Color.BLACK; + fun getThemedColor(ctx: Context, attrId: Int): Int { + val typedValue = TypedValue() + return if (ctx.theme.resolveAttribute( + attrId, + typedValue, + true + ) + ) typedValue.data else Color.BLACK } - public static void setTheme(@NonNull Activity activity, @NonNull String theme) { - activity.setTheme(R.style.MyMaterialThemeOled); + fun setTheme(activity: Activity, theme: String) { + activity.setTheme(R.style.MyMaterialThemeOled) } - public static void setPreferred(Activity activity) { - final String theme = PreferenceManager.getDefaultSharedPreferences(activity) - .getString(activity.getString(R.string.preferred_theme), "Classic"); - setTheme(activity, theme); + fun setPreferred(activity: Activity) { + val theme = PreferenceManager.getDefaultSharedPreferences(activity) + .getString(activity.getString(R.string.preferred_theme), "Classic") + theme?.let { setTheme(activity, it) } } - -} +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/util/UriData.kt b/app/src/main/java/net/mynero/wallet/util/UriData.kt index 9c08cc6..f1b9575 100644 --- a/app/src/main/java/net/mynero/wallet/util/UriData.kt +++ b/app/src/main/java/net/mynero/wallet/util/UriData.kt @@ -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.WalletManager; +import net.mynero.wallet.model.Wallet.Companion.getPaymentIdFromAddress +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) { -public class UriData { - private final String address; - private final HashMap params; - - public UriData(String address, HashMap params) { - this.address = address; - this.params = params; + fun hasPaymentId(): Boolean { + return instance?.wallet?.nettype()?.let { getPaymentIdFromAddress(address, it) }?.isNotEmpty() == true } - public static UriData parse(String uri) { - HashMap params = new HashMap<>(); - String[] uriParts = uri.replace(Constants.URI_PREFIX, "").split("\\?"); - String finalAddress = uriParts[0]; - String queryParams = ""; - if (uriParts.length > 1) { - queryParams = uriParts[1]; - String[] queryParts = queryParams.split("&"); - for (String param : queryParts) { - String[] paramParts = param.split("="); - if(paramParts.length == 2) { - String variable = paramParts[0]; - String value = paramParts[1]; - params.put(variable, value); + val amount: String? + get() = params[Constants.URI_ARG_AMOUNT] + ?: params[Constants.URI_ARG_AMOUNT2] + + fun hasAmount(): Boolean { + return params.containsKey(Constants.URI_ARG_AMOUNT) || params.containsKey(Constants.URI_ARG_AMOUNT2) + } + + companion object { + @JvmStatic + fun parse(uri: String): UriData? { + if(uri.isEmpty()) return null + val params = HashMap() + 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 + } } } - } - boolean valid = Wallet.isAddressValid(finalAddress); - if (valid) { - return new UriData(finalAddress, params); - } else { - return null; + val valid = isAddressValid(finalAddress) + return if (valid) { + UriData(finalAddress, params) + } else { + null + } } } - - public HashMap 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); - } -} +} \ No newline at end of file diff --git a/build.gradle b/build.gradle index 3673219..811b131 100644 --- a/build.gradle +++ b/build.gradle @@ -1,12 +1,14 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.9.21' repositories { mavenCentral() google() } 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 "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" } } diff --git a/gradle.properties b/gradle.properties index bfa2b69..17726b9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -18,3 +18,6 @@ org.gradle.jvmargs=-Xmx2048m android.useAndroidX=true android.enableJetifier=true +android.defaults.buildfeatures.buildconfig=true +android.nonTransitiveRClass=false +android.nonFinalResIds=false diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index c5f98ef..e9bc078 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Feb 10 17:24:42 CST 2023 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 zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME