From 0a9d774f184c44c98b95e01d6bf6bc53bced9eb5 Mon Sep 17 00:00:00 2001 From: pokkst Date: Thu, 7 Dec 2023 17:41:38 -0600 Subject: [PATCH] 0.5.3: Better synchronization/status --- app/build.gradle | 4 +- app/src/main/cpp/monerujo.cpp | 18 ++- .../java/net/mynero/wallet/MainActivity.kt | 11 ++ .../main/java/net/mynero/wallet/data/Node.kt | 9 +- .../dialog/AddNodeBottomSheetDialog.kt | 6 + .../dialog/EditNodeBottomSheetDialog.kt | 5 + .../dialog/NodeSelectionBottomSheetDialog.kt | 18 ++- .../wallet/fragment/home/HomeFragment.kt | 30 ++++- .../fragment/onboarding/OnboardingFragment.kt | 13 +- .../fragment/settings/SettingsFragment.kt | 113 ++++++++---------- .../fragment/settings/SettingsViewModel.kt | 31 ----- .../mynero/wallet/livedata/SingleLiveEvent.kt | 6 + .../java/net/mynero/wallet/model/Wallet.kt | 14 ++- .../mynero/wallet/service/DaemonService.kt | 27 +++++ .../wallet/service/MoneroHandlerThread.kt | 47 +++++--- .../net/mynero/wallet/service/PrefService.kt | 33 +---- .../net/mynero/wallet/service/ProxyService.kt | 75 ++++++++++++ .../layout/add_node_bottom_sheet_dialog.xml | 27 +++-- .../layout/edit_node_bottom_sheet_dialog.xml | 40 ++++--- app/src/main/res/layout/fragment_settings.xml | 15 +-- app/src/main/res/values/strings.xml | 4 +- 21 files changed, 349 insertions(+), 197 deletions(-) create mode 100644 app/src/main/java/net/mynero/wallet/service/DaemonService.kt create mode 100644 app/src/main/java/net/mynero/wallet/service/ProxyService.kt diff --git a/app/build.gradle b/app/build.gradle index 44274a6..5344364 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,8 +10,8 @@ android { applicationId "net.mynero.wallet" minSdkVersion 21 targetSdkVersion 34 - versionCode 50200 - versionName "0.5.2 'Fluorine Fermi'" + versionCode 50300 + versionName "0.5.3 'Fluorine Fermi'" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" externalNativeBuild { cmake { diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp index f9cee9d..e71e5a5 100644 --- a/app/src/main/cpp/monerujo.cpp +++ b/app/src/main/cpp/monerujo.cpp @@ -920,11 +920,25 @@ Java_net_mynero_wallet_model_Wallet_getConnectionStatusJ(JNIEnv *env, jobject in Monero::Wallet *wallet = getHandle(env, instance); return wallet->connected(); } -//TODO virtual void setTrustedDaemon(bool arg) = 0; +JNIEXPORT void JNICALL +Java_net_mynero_wallet_model_Wallet_setTrustedDaemonJ(JNIEnv *env, jobject instance, jboolean trusted) { + Monero::Wallet *wallet = getHandle(env, instance); + if (trusted) { + wallet->setTrustedDaemon(true); + } else { + wallet->setTrustedDaemon(false); + } +} +JNIEXPORT jboolean JNICALL +Java_net_mynero_wallet_model_Wallet_isTrustedDaemonJ(JNIEnv *env, jobject instance) { + Monero::Wallet *wallet = getHandle(env, instance); + bool td = wallet->trustedDaemon(); + return td; +} //TODO virtual bool trustedDaemon() const = 0; JNIEXPORT jboolean JNICALL -Java_net_mynero_wallet_model_Wallet_setProxy(JNIEnv *env, jobject instance, +Java_net_mynero_wallet_model_Wallet_setProxyJ(JNIEnv *env, jobject instance, jstring address) { const char *_address = env->GetStringUTFChars(address, nullptr); Monero::Wallet *wallet = getHandle(env, instance); diff --git a/app/src/main/java/net/mynero/wallet/MainActivity.kt b/app/src/main/java/net/mynero/wallet/MainActivity.kt index d78c1a7..1b0b9a6 100644 --- a/app/src/main/java/net/mynero/wallet/MainActivity.kt +++ b/app/src/main/java/net/mynero/wallet/MainActivity.kt @@ -4,7 +4,11 @@ import android.os.Bundle import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.Observer +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.NavHostFragment +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog.PasswordListener import net.mynero.wallet.fragment.dialog.SendBottomSheetDialog @@ -13,13 +17,16 @@ 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.DaemonService import net.mynero.wallet.service.HistoryService import net.mynero.wallet.service.MoneroHandlerThread import net.mynero.wallet.service.PrefService +import net.mynero.wallet.service.ProxyService 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 timber.log.Timber import java.io.File class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, PasswordListener { @@ -29,6 +36,8 @@ class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, Password private var balanceService: BalanceService? = null private var addressService: AddressService? = null private var historyService: HistoryService? = null + private var proxyService: ProxyService? = null + private var daemonService: DaemonService? = null private var blockchainService: BlockchainService? = null private var utxoService: UTXOService? = null private var proceedToSend = false @@ -77,6 +86,8 @@ class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, Password addressService = AddressService(thread) historyService = HistoryService(thread) blockchainService = BlockchainService(thread) + daemonService = DaemonService(thread) + proxyService = ProxyService(thread) utxoService = UTXOService(thread) thread.start() } diff --git a/app/src/main/java/net/mynero/wallet/data/Node.kt b/app/src/main/java/net/mynero/wallet/data/Node.kt index e22a93f..74a96df 100644 --- a/app/src/main/java/net/mynero/wallet/data/Node.kt +++ b/app/src/main/java/net/mynero/wallet/data/Node.kt @@ -39,7 +39,8 @@ class Node { private set var password = "" private set - private var favourite = false + var trusted = false + private set internal constructor(nodeString: String?) { require(!nodeString.isNullOrEmpty()) { "daemon is empty" } @@ -142,6 +143,9 @@ class Node { } require(networkType == WalletManager.instance?.networkType) { "wrong net: $networkType" } } + if (jsonObject.has("trusted")) { + this.trusted = jsonObject.getBoolean("trusted") + } } constructor() { @@ -197,6 +201,7 @@ class Node { null -> TODO() } if (name?.isNotEmpty() == true) jsonObject.put("name", name) + jsonObject.put("trusted", trusted) } catch (e: JSONException) { throw RuntimeException(e) } @@ -235,7 +240,7 @@ class Node { levinPort = anotherNode.levinPort username = anotherNode.username password = anotherNode.password - favourite = anotherNode.favourite + trusted = anotherNode.trusted } companion object { diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.kt b/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.kt index 3e21b73..fea1511 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.kt +++ b/app/src/main/java/net/mynero/wallet/fragment/dialog/AddNodeBottomSheetDialog.kt @@ -7,6 +7,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button +import android.widget.CheckBox import android.widget.EditText import android.widget.ImageButton import android.widget.Toast @@ -38,6 +39,8 @@ class AddNodeBottomSheetDialog : BottomSheetDialogFragment() { val nodeNameEditText = view.findViewById(R.id.node_name_edittext) val usernameEditText = view.findViewById(R.id.username_edittext) val passwordEditText = view.findViewById(R.id.password_edittext) + val trustedDaemonCheckbox = view.findViewById(R.id.trusted_node_checkbox) + val pastePasswordImageButton = view.findViewById(R.id.paste_password_imagebutton) usernameEditText.addTextChangedListener(object : TextWatcher { @@ -63,6 +66,8 @@ class AddNodeBottomSheetDialog : BottomSheetDialogFragment() { val name = nodeNameEditText.text.toString() val user = usernameEditText.text.toString() val pass = passwordEditText.text.toString() + val trusted = trustedDaemonCheckbox.isChecked + if (name.isEmpty()) { Toast.makeText(context, "Enter node name", Toast.LENGTH_SHORT).show() return@setOnClickListener @@ -85,6 +90,7 @@ class AddNodeBottomSheetDialog : BottomSheetDialogFragment() { jsonObject.put("rpcPort", portString.toInt()) jsonObject.put("network", "mainnet") jsonObject.put("name", name) + jsonObject.put("trusted", trusted) addNodeToSaved(jsonObject) if (listener != null) { listener?.onNodeAdded() diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.kt b/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.kt index 6d4b549..7b01b36 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.kt +++ b/app/src/main/java/net/mynero/wallet/fragment/dialog/EditNodeBottomSheetDialog.kt @@ -7,6 +7,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button +import android.widget.CheckBox import android.widget.EditText import android.widget.ImageButton import android.widget.Toast @@ -39,6 +40,7 @@ class EditNodeBottomSheetDialog : BottomSheetDialogFragment() { val nodeNameEditText = view.findViewById(R.id.node_name_edittext) val usernameEditText = view.findViewById(R.id.username_edittext) val passwordEditText = view.findViewById(R.id.password_edittext) + val trustedDaemonCheckBox = view.findViewById(R.id.trusted_node_checkbox) val pastePasswordImageButton = view.findViewById(R.id.paste_password_imagebutton) if (node == null) return @@ -46,6 +48,7 @@ class EditNodeBottomSheetDialog : BottomSheetDialogFragment() { portEditText.setText("${node?.rpcPort}") nodeNameEditText.setText(node?.name) usernameEditText.setText(node?.username) + trustedDaemonCheckBox.isChecked = node?.trusted ?: false if (node?.password?.isNotEmpty() == true) { passwordEditText.setText(node?.password) passwordEditText.visibility = View.VISIBLE @@ -78,6 +81,7 @@ class EditNodeBottomSheetDialog : BottomSheetDialogFragment() { val nodeName = nodeNameEditText.text.toString() val user = usernameEditText.text.toString() val pass = passwordEditText.text.toString() + val trusted = trustedDaemonCheckBox.isChecked if (nodeName.isEmpty()) { Toast.makeText(context, "Enter node name", Toast.LENGTH_SHORT).show() return@setOnClickListener @@ -101,6 +105,7 @@ class EditNodeBottomSheetDialog : BottomSheetDialogFragment() { jsonObject.put("rpcPort", portString.toInt()) jsonObject.put("network", "mainnet") jsonObject.put("name", nodeName) + jsonObject.put("trusted", trusted) listener?.onNodeEdited(node, fromJson(jsonObject)) dismiss() } catch (e: JSONException) { diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/NodeSelectionBottomSheetDialog.kt b/app/src/main/java/net/mynero/wallet/fragment/dialog/NodeSelectionBottomSheetDialog.kt index b217d30..16f224b 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/dialog/NodeSelectionBottomSheetDialog.kt +++ b/app/src/main/java/net/mynero/wallet/fragment/dialog/NodeSelectionBottomSheetDialog.kt @@ -7,9 +7,12 @@ import android.view.View import android.view.ViewGroup import android.widget.Button import android.widget.Toast +import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import net.mynero.wallet.R import net.mynero.wallet.adapter.NodeSelectionAdapter import net.mynero.wallet.adapter.NodeSelectionAdapter.NodeSelectionAdapterListener @@ -18,6 +21,7 @@ import net.mynero.wallet.data.Node import net.mynero.wallet.data.Node.Companion.fromJson import net.mynero.wallet.data.Node.Companion.fromString import net.mynero.wallet.model.WalletManager +import net.mynero.wallet.service.DaemonService import net.mynero.wallet.service.PrefService import net.mynero.wallet.util.Constants import org.json.JSONArray @@ -90,18 +94,12 @@ class NodeSelectionBottomSheetDialog : BottomSheetDialogFragment(), NodeSelectio } override fun onSelectNode(node: Node?) { - val activity: Activity? = activity - activity?.runOnUiThread { - Toast.makeText( - activity, - getString(R.string.node_selected), - Toast.LENGTH_SHORT - ).show() - } PrefService.instance?.edit()?.putString(Constants.PREF_NODE_2, node?.toJson().toString()) ?.apply() - WalletManager.instance?.setDaemon(node) - adapter?.updateSelectedNode() + node?.let { DaemonService.instance?.setDaemon(it) } + activity?.runOnUiThread { + adapter?.updateSelectedNode() + } listener?.onNodeSelected() } diff --git a/app/src/main/java/net/mynero/wallet/fragment/home/HomeFragment.kt b/app/src/main/java/net/mynero/wallet/fragment/home/HomeFragment.kt index 1ad4eb8..ece5412 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/home/HomeFragment.kt +++ b/app/src/main/java/net/mynero/wallet/fragment/home/HomeFragment.kt @@ -8,12 +8,16 @@ import android.widget.Button import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView +import android.widget.Toast import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.navigation.NavDirections import androidx.navigation.fragment.NavHostFragment import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import net.mynero.wallet.MainActivity import net.mynero.wallet.R import net.mynero.wallet.adapter.TransactionInfoAdapter @@ -22,9 +26,12 @@ import net.mynero.wallet.model.TransactionInfo import net.mynero.wallet.model.WalletManager import net.mynero.wallet.service.BalanceService import net.mynero.wallet.service.BlockchainService +import net.mynero.wallet.service.DaemonService import net.mynero.wallet.service.HistoryService import net.mynero.wallet.service.PrefService +import net.mynero.wallet.service.ProxyService import net.mynero.wallet.util.Constants +import timber.log.Timber class HomeFragment : Fragment(), TxInfoAdapterListener { private var startHeight: Long = 0 @@ -66,6 +73,26 @@ class HomeFragment : Fragment(), TxInfoAdapterListener { val historyService = HistoryService.instance val blockchainService = BlockchainService.instance + ProxyService.instance?.proxyChangeEvents?.observe(viewLifecycleOwner) { proxy -> + lifecycleScope.launch(Dispatchers.IO) { + Timber.d("Updating proxy:: $proxy") + WalletManager.instance?.setProxy(proxy) + WalletManager.instance?.wallet?.setProxy(proxy) + WalletManager.instance?.wallet?.init(0) + WalletManager.instance?.wallet?.startRefresh() + } + } + + DaemonService.instance?.daemonChangeEvents?.observe(viewLifecycleOwner) { daemon -> + lifecycleScope.launch(Dispatchers.IO) { + Timber.d("Updating daemon:: $daemon") + WalletManager.instance?.setDaemon(daemon) + WalletManager.instance?.wallet?.setTrustedDaemon(daemon.trusted) + WalletManager.instance?.wallet?.init(0) + WalletManager.instance?.wallet?.startRefresh() + } + } + balanceService?.balanceInfo?.observe(viewLifecycleOwner) { balanceInfo -> if (balanceInfo != null) { unlockedBalanceTextView.text = balanceInfo.unlockedDisplay @@ -101,7 +128,8 @@ class HomeFragment : Fragment(), TxInfoAdapterListener { } } else { progressBar.visibility = View.INVISIBLE - progressBarText.visibility = View.GONE + progressBarText.visibility = View.VISIBLE + progressBarText.text = "Synchronized" } } val adapter = TransactionInfoAdapter(this) diff --git a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.kt b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.kt index 3700cdc..15dbd46 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.kt +++ b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.kt @@ -14,6 +14,7 @@ import android.widget.CompoundButton import android.widget.EditText import android.widget.ImageView import android.widget.TextView +import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.appcompat.widget.SwitchCompat import androidx.constraintlayout.widget.ConstraintLayout @@ -28,6 +29,7 @@ import net.mynero.wallet.fragment.dialog.NodeSelectionBottomSheetDialog import net.mynero.wallet.fragment.dialog.NodeSelectionBottomSheetDialog.NodeSelectionDialogListener import net.mynero.wallet.fragment.onboarding.OnboardingViewModel.SeedType import net.mynero.wallet.service.PrefService +import net.mynero.wallet.service.ProxyService import net.mynero.wallet.util.Constants class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListener { @@ -186,11 +188,11 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_TOR, b)?.apply() removeProxyTextListeners() if (b) { - if (PrefService.instance?.hasProxySet() == true) { + if (ProxyService.instance?.hasProxySet() == true) { val proxyAddress = - PrefService.instance?.proxyAddress ?: return@setOnCheckedChangeListener + ProxyService.instance?.proxyAddress ?: return@setOnCheckedChangeListener val proxyPort = - PrefService.instance?.proxyPort ?: return@setOnCheckedChangeListener + ProxyService.instance?.proxyPort ?: return@setOnCheckedChangeListener initProxyStuff(proxyAddress, proxyPort) } else { initProxyStuff("127.0.0.1", "9050") @@ -277,6 +279,11 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe override fun onNodeSelected() { val node = PrefService.instance?.node selectNodeButton?.text = getString(R.string.node_button_text, node?.address) + Toast.makeText( + activity, + getString(R.string.node_selected), + Toast.LENGTH_SHORT + ).show() mViewModel?.updateProxy(activity?.application as MoneroApplication) } diff --git a/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsFragment.kt b/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsFragment.kt index a84fa9a..6a9e5ed 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsFragment.kt +++ b/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsFragment.kt @@ -1,8 +1,6 @@ package net.mynero.wallet.fragment.settings import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher import android.util.Patterns import android.view.LayoutInflater import android.view.View @@ -12,12 +10,17 @@ import android.widget.CompoundButton import android.widget.EditText import android.widget.TextView import android.widget.Toast +import androidx.activity.OnBackPressedCallback import androidx.appcompat.widget.SwitchCompat import androidx.constraintlayout.widget.ConstraintLayout import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.NavHostFragment -import net.mynero.wallet.MoneroApplication +import androidx.navigation.fragment.findNavController +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch import net.mynero.wallet.R import net.mynero.wallet.data.Node import net.mynero.wallet.data.Node.Companion.fromJson @@ -34,37 +37,23 @@ import net.mynero.wallet.model.Wallet.ConnectionStatus import net.mynero.wallet.model.WalletManager import net.mynero.wallet.service.BalanceService import net.mynero.wallet.service.BlockchainService +import net.mynero.wallet.service.DaemonService import net.mynero.wallet.service.HistoryService import net.mynero.wallet.service.PrefService +import net.mynero.wallet.service.ProxyService import net.mynero.wallet.util.Constants import org.json.JSONArray +import timber.log.Timber class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListener, AddNodeListener, EditNodeListener { private var mViewModel: SettingsViewModel? = null - private var proxyAddressListener: TextWatcher = object : TextWatcher { - override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} - override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} - override fun afterTextChanged(editable: Editable) { - if (mViewModel != null) { - mViewModel?.setProxyAddress(editable.toString()) - mViewModel?.updateProxy(activity?.application as MoneroApplication) - } - } - } - private var proxyPortListener: TextWatcher = object : TextWatcher { - override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} - override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {} - override fun afterTextChanged(editable: Editable) { - if (mViewModel != null) { - mViewModel?.setProxyPort(editable.toString()) - mViewModel?.updateProxy(activity?.application as MoneroApplication) - } - } - } private var walletProxyAddressEditText: EditText? = null private var walletProxyPortEditText: EditText? = null private var selectNodeButton: Button? = null + private var cachedProxyAddress: String = "" + private var cachedProxyPort: String = "" + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -103,12 +92,12 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen donationSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean -> PrefService.instance?.edit()?.putBoolean(Constants.PREF_DONATE_PER_TX, b)?.apply() } - val prefService = PrefService.instance - val usesProxy = prefService?.getBoolean(Constants.PREF_USES_TOR, false) == true - if (prefService?.hasProxySet() == true) { - val proxyAddress = prefService.proxyAddress - val proxyPort = prefService.proxyPort - initProxyStuff(proxyAddress, proxyPort) + val prefService = PrefService.instance ?: return + val usesProxy = prefService.getBoolean(Constants.PREF_USES_TOR, false) + cachedProxyAddress = ProxyService.instance?.proxyAddress ?: return + cachedProxyPort = ProxyService.instance?.proxyPort ?: return + if (ProxyService.instance?.hasProxySet() == true) { + initProxyStuff(cachedProxyAddress, cachedProxyPort) } torSwitch.isChecked = usesProxy if (usesProxy) { @@ -116,22 +105,19 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen } else { proxySettingsLayout.visibility = View.GONE } - addProxyTextListeners() torSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean -> - prefService?.edit()?.putBoolean(Constants.PREF_USES_TOR, b)?.apply() + prefService.edit()?.putBoolean(Constants.PREF_USES_TOR, b)?.apply() if (b) { - if (prefService?.hasProxySet() == true) { - removeProxyTextListeners() - val proxyAddress = prefService.proxyAddress - val proxyPort = prefService.proxyPort + if (ProxyService.instance?.hasProxySet() == true) { + val proxyAddress = ProxyService.instance?.proxyAddress ?: return@setOnCheckedChangeListener + val proxyPort = ProxyService.instance?.proxyPort ?: return@setOnCheckedChangeListener initProxyStuff(proxyAddress, proxyPort) - addProxyTextListeners() } proxySettingsLayout.visibility = View.VISIBLE } else { proxySettingsLayout.visibility = View.GONE } - mViewModel?.updateProxy(activity?.application as MoneroApplication) + refreshProxy() } displaySeedButton.setOnClickListener { val usesPassword = @@ -148,16 +134,6 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen } } displayUtxosButton.setOnClickListener { navigate(R.id.nav_to_utxos) } - val statusTextView = view.findViewById(R.id.status_textview) - BlockchainService.instance?.connectionStatus?.observe(viewLifecycleOwner) { connectionStatus: ConnectionStatus -> - if (connectionStatus === ConnectionStatus.ConnectionStatus_Connected) { - statusTextView.text = resources.getText(R.string.connected) - } else if (connectionStatus === ConnectionStatus.ConnectionStatus_Disconnected) { - statusTextView.text = resources.getText(R.string.disconnected) - } else if (connectionStatus === ConnectionStatus.ConnectionStatus_WrongVersion) { - statusTextView.text = resources.getText(R.string.version_mismatch) - } - } val node = PrefService.instance?.node // shouldn't use default value here selectNodeButton?.text = getString(R.string.node_button_text, node?.address) selectNodeButton?.setOnClickListener { @@ -167,6 +143,25 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen dialog.show(fragmentManager, "node_selection_dialog") } } + + val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) { + override fun handleOnBackPressed() { + refreshProxy() + findNavController().popBackStack() + } + } + + activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, onBackPressedCallback) + } + + private fun refreshProxy() { + val proxyAddress = walletProxyAddressEditText?.text.toString() + val proxyPort = walletProxyPortEditText?.text.toString() + if((proxyAddress != cachedProxyAddress) || (proxyPort != cachedProxyPort)) { + ProxyService.instance?.updateProxy(proxyAddress, proxyPort) + cachedProxyAddress = proxyAddress + cachedProxyPort = proxyPort + } } private fun displaySeedDialog(password: String) { @@ -188,30 +183,22 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen private fun initProxyStuff(proxyAddress: String, proxyPort: String) { val validIpAddress = Patterns.IP_ADDRESS.matcher(proxyAddress).matches() if (validIpAddress) { - mViewModel?.setProxyAddress(proxyAddress) - mViewModel?.setProxyPort(proxyPort) walletProxyAddressEditText?.setText(proxyAddress) walletProxyPortEditText?.setText(proxyPort) } } - private fun removeProxyTextListeners() { - walletProxyAddressEditText?.removeTextChangedListener(proxyAddressListener) - walletProxyPortEditText?.removeTextChangedListener(proxyPortListener) - } - - private fun addProxyTextListeners() { - walletProxyAddressEditText?.addTextChangedListener(proxyAddressListener) - walletProxyPortEditText?.addTextChangedListener(proxyPortListener) - } - override fun onNodeSelected() { val node = PrefService.instance?.node selectNodeButton?.text = getString(R.string.node_button_text, node?.address) - mViewModel?.updateProxy(activity?.application as MoneroApplication) - (activity?.application as MoneroApplication).executor?.execute { - WalletManager.instance?.wallet?.init(0) - WalletManager.instance?.wallet?.startRefresh() + refreshProxy() + + activity?.runOnUiThread { + Toast.makeText( + activity, + activity?.getString(R.string.node_selected, node?.name ?: node?.host), + Toast.LENGTH_SHORT + ).show() } } diff --git a/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsViewModel.kt b/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsViewModel.kt index 4e99ef4..5a3a4f6 100644 --- a/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsViewModel.kt +++ b/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsViewModel.kt @@ -8,36 +8,5 @@ import net.mynero.wallet.service.PrefService import net.mynero.wallet.util.Constants class SettingsViewModel : ViewModel() { - private var proxyAddress = "" - private var proxyPort = "" - fun updateProxy(application: MoneroApplication) { - application.executor?.execute { - val usesProxy = PrefService.instance?.getBoolean(Constants.PREF_USES_TOR, false) == true - val curretNode = PrefService.instance?.node - val isNodeLocalIp = - curretNode?.address?.startsWith("10.") == true || curretNode?.address?.startsWith("192.168.") == true || curretNode?.address == "localhost" || curretNode?.address == "127.0.0.1" - if (!usesProxy || isNodeLocalIp) { - WalletManager.instance?.setProxy("") - WalletManager.instance?.wallet?.setProxy("") - return@execute - } - if (proxyAddress.isEmpty()) proxyAddress = "127.0.0.1" - if (proxyPort.isEmpty()) proxyPort = "9050" - val validIpAddress = Patterns.IP_ADDRESS.matcher(proxyAddress).matches() - if (validIpAddress) { - val proxy = "$proxyAddress:$proxyPort" - PrefService.instance?.edit()?.putString(Constants.PREF_PROXY, proxy)?.apply() - WalletManager.instance?.setProxy(proxy) - WalletManager.instance?.wallet?.setProxy(proxy) - } - } - } - fun setProxyAddress(address: String) { - proxyAddress = address - } - - fun setProxyPort(port: String) { - proxyPort = port - } } \ No newline at end of file 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 c9f1464..4c5db02 100644 --- a/app/src/main/java/net/mynero/wallet/livedata/SingleLiveEvent.kt +++ b/app/src/main/java/net/mynero/wallet/livedata/SingleLiveEvent.kt @@ -57,6 +57,12 @@ class SingleLiveEvent : MutableLiveData() { super.setValue(t) } + @MainThread + override fun postValue(value: T) { + mPending.set(true) + super.postValue(value) + } + /** * Used for cases where T is Void, to make calls cleaner. */ 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 3568de4..505ca27 100644 --- a/app/src/main/java/net/mynero/wallet/model/Wallet.kt +++ b/app/src/main/java/net/mynero/wallet/model/Wallet.kt @@ -208,8 +208,20 @@ class Wallet { } private external fun getConnectionStatusJ(): Int + fun setTrustedDaemon(trusted: Boolean) { + setTrustedDaemonJ(trusted) + } + private external fun setTrustedDaemonJ(trusted: Boolean) - external fun setProxy(address: String?): Boolean + fun isTrustedDaemon(): Boolean { + return isTrustedDaemonJ(); + } + private external fun isTrustedDaemonJ(): Boolean + + fun setProxy(address: String?): Boolean { + return setProxyJ(address) + } + private external fun setProxyJ(address: String?): Boolean val balance: Long get() = getBalance(accountIndex) diff --git a/app/src/main/java/net/mynero/wallet/service/DaemonService.kt b/app/src/main/java/net/mynero/wallet/service/DaemonService.kt new file mode 100644 index 0000000..4584263 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/service/DaemonService.kt @@ -0,0 +1,27 @@ +package net.mynero.wallet.service + +import android.util.Patterns +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import net.mynero.wallet.data.Node +import net.mynero.wallet.livedata.SingleLiveEvent +import net.mynero.wallet.model.WalletManager +import net.mynero.wallet.util.Constants + +class DaemonService(thread: MoneroHandlerThread) : ServiceBase(thread) { + val daemonChangeEvents: SingleLiveEvent = SingleLiveEvent() + + + init { + instance = this + } + + fun setDaemon(daemon: Node) { + daemonChangeEvents.postValue(daemon) + } + + companion object { + var instance: DaemonService? = null + private set + } +} \ No newline at end of file diff --git a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.kt b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.kt index ed2bcfa..8aa9359 100644 --- a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.kt +++ b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.kt @@ -16,6 +16,7 @@ */ package net.mynero.wallet.service +import android.util.Log import net.mynero.wallet.model.PendingTransaction import net.mynero.wallet.model.TransactionOutput import net.mynero.wallet.model.Wallet @@ -25,6 +26,7 @@ import net.mynero.wallet.model.Wallet.ConnectionStatus import net.mynero.wallet.model.WalletListener import net.mynero.wallet.model.WalletManager import net.mynero.wallet.util.Constants +import timber.log.Timber import java.security.SecureRandom /** @@ -57,11 +59,12 @@ class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet) currentNode?.address == "localhost" || currentNode?.address == "127.0.0.1" if (usesTor && !isLocalIp) { - val proxy = prefService.proxy + val proxy = ProxyService.instance?.proxy proxy?.let { WalletManager.instance?.setProxy(it) } wallet.setProxy(proxy) } WalletManager.instance?.setDaemon(currentNode) + currentNode?.trusted?.let { wallet.setTrustedDaemon(it) } wallet.init(0) wallet.setListener(this) wallet.startRefresh() @@ -81,20 +84,36 @@ class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet) override fun refreshed() { val status = wallet.fullStatus.connectionStatus - if (status === ConnectionStatus.ConnectionStatus_Disconnected || status == null) { - if (triesLeft > 0) { - wallet.startRefresh() - triesLeft-- - } else { - listener?.onConnectionFail() - } - } else { - BlockchainService.instance?.daemonHeight = wallet.getDaemonBlockChainHeight() - wallet.setSynchronized() - wallet.store() - refresh(true) - } + val daemonHeight = wallet.getDaemonBlockChainHeight() + val chainHeight = wallet.getBlockChainHeight() + BlockchainService.instance?.daemonHeight = daemonHeight status?.let { BlockchainService.instance?.setConnectionStatus(it) } + if (status === ConnectionStatus.ConnectionStatus_Disconnected || status == null) { + tryRestartOrFail() + } else { + val heightDiff = daemonHeight - chainHeight + if(heightDiff >= 2) { + tryRestartOrFail() + } else { + Timber.d("refreshed() Synchronized") + wallet.setSynchronized() + wallet.store() + refresh(true) + } + + } + } + + private fun tryRestartOrFail() { + Timber.d("refreshed() Disconnected") + if (triesLeft > 0) { + Timber.d("refreshed() Starting refresh") + wallet.startRefresh() + triesLeft-- + } else { + Timber.d("refreshed() On connection fail") + listener?.onConnectionFail() + } } private fun refresh(walletSynced: Boolean) { diff --git a/app/src/main/java/net/mynero/wallet/service/PrefService.kt b/app/src/main/java/net/mynero/wallet/service/PrefService.kt index 90bfdea..3332b49 100644 --- a/app/src/main/java/net/mynero/wallet/service/PrefService.kt +++ b/app/src/main/java/net/mynero/wallet/service/PrefService.kt @@ -29,8 +29,8 @@ class PrefService(application: MoneroApplication) : ServiceBase(null) { val usesProxy = getBoolean(Constants.PREF_USES_TOR, false) var defaultNode = DefaultNodes.SAMOURAI if (usesProxy) { - val proxyPort = proxyPort - if (proxyPort.isNotEmpty()) { + val proxyPort = ProxyService.instance?.proxyPort + if (proxyPort?.isNotEmpty() == true) { val port = proxyPort.toInt() defaultNode = if (port == 4447) { DefaultNodes.MYNERO_I2P @@ -60,35 +60,6 @@ class PrefService(application: MoneroApplication) : ServiceBase(null) { return null } - val proxy: String? - get() = instance?.getString(Constants.PREF_PROXY, "") - - fun hasProxySet(): Boolean { - val proxyString = proxy - return proxyString?.contains(":") == true - } - - val proxyAddress: String - get() { - if (hasProxySet()) { - val proxyString = proxy - return proxyString?.split(":".toRegex())?.dropLastWhile { it.isEmpty() } - ?.toTypedArray() - ?.get(0) ?: "" - } - return "" - } - val proxyPort: String - get() { - if (hasProxySet()) { - val proxyString = proxy - return proxyString?.split(":".toRegex())?.dropLastWhile { it.isEmpty() } - ?.toTypedArray() - ?.get(1) ?: "" - } - return "" - } - fun getString(key: String?, defaultValue: String): String? { val value = preferences?.getString(key, "") if (value?.isEmpty() == true && defaultValue.isNotEmpty()) { diff --git a/app/src/main/java/net/mynero/wallet/service/ProxyService.kt b/app/src/main/java/net/mynero/wallet/service/ProxyService.kt new file mode 100644 index 0000000..8385829 --- /dev/null +++ b/app/src/main/java/net/mynero/wallet/service/ProxyService.kt @@ -0,0 +1,75 @@ +package net.mynero.wallet.service + +import android.util.Patterns +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import net.mynero.wallet.data.Node +import net.mynero.wallet.livedata.SingleLiveEvent +import net.mynero.wallet.model.WalletManager +import net.mynero.wallet.util.Constants + +class ProxyService(thread: MoneroHandlerThread) : ServiceBase(thread) { + val proxyChangeEvents: SingleLiveEvent = SingleLiveEvent() + + init { + instance = this + } + + fun updateProxy(proxyAddress: String, proxyPort: String) { + var finalProxyAddress = proxyAddress + var finalProxyPort = proxyPort + val usesProxy = PrefService.instance?.getBoolean(Constants.PREF_USES_TOR, false) == true + val curretNode = PrefService.instance?.node + val isNodeLocalIp = + curretNode?.address?.startsWith("10.") == true || curretNode?.address?.startsWith("192.168.") == true || curretNode?.address == "localhost" || curretNode?.address == "127.0.0.1" + curretNode?.trusted?.let { WalletManager.instance?.wallet?.setTrustedDaemon(it) } + if (!usesProxy || isNodeLocalIp) { + // User is not using proxy, or is using local node currently, so we will disable proxy here. + proxyChangeEvents.postValue("") + return + } + // We are using proxy at this point, but user set them to empty. We will fallback to Tor defaults here. + if (proxyAddress.isEmpty()) finalProxyAddress = "127.0.0.1" + if (proxyPort.isEmpty()) finalProxyPort = "9050" + val validIpAddress = Patterns.IP_ADDRESS.matcher(proxyAddress).matches() + if (validIpAddress) { + val proxy = "$finalProxyAddress:$finalProxyPort" + PrefService.instance?.edit()?.putString(Constants.PREF_PROXY, proxy)?.apply() + proxyChangeEvents.postValue(proxy) + } + } + + fun hasProxySet(): Boolean { + val proxyString = proxy + return proxyString?.contains(":") == true + } + + val proxy: String? + get() = PrefService.instance?.getString(Constants.PREF_PROXY, "") + + val proxyAddress: String + get() { + if (hasProxySet()) { + val proxyString = proxy + return proxyString?.split(":".toRegex())?.dropLastWhile { it.isEmpty() } + ?.toTypedArray() + ?.get(0) ?: "" + } + return "" + } + val proxyPort: String + get() { + if (hasProxySet()) { + val proxyString = proxy + return proxyString?.split(":".toRegex())?.dropLastWhile { it.isEmpty() } + ?.toTypedArray() + ?.get(1) ?: "" + } + return "" + } + + companion object { + var instance: ProxyService? = null + private set + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml b/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml index 85c853d..25c1896 100644 --- a/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml +++ b/app/src/main/res/layout/add_node_bottom_sheet_dialog.xml @@ -59,20 +59,20 @@ android:id="@+id/address_edittext" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginBottom="16dp" + android:layout_marginBottom="8dp" android:background="@drawable/edittext_bg" android:hint="@string/node_address_hint" android:inputType="text" android:digits="QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.:-[]" - app:layout_constraintBottom_toTopOf="@id/username_edittext" + app:layout_constraintBottom_toTopOf="@id/trusted_node_checkbox" app:layout_constraintEnd_toStartOf="@id/node_port_edittext" + app:layout_constraintTop_toBottomOf="@id/node_name_edittext" app:layout_constraintStart_toStartOf="parent" /> + +