0.5.3: Better synchronization/status

This commit is contained in:
pokkst 2023-12-07 17:41:38 -06:00
parent cab3a0d79f
commit 0a9d774f18
No known key found for this signature in database
GPG key ID: EC4FAAA66859FAA4
21 changed files with 349 additions and 197 deletions

View file

@ -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 {

View file

@ -920,11 +920,25 @@ Java_net_mynero_wallet_model_Wallet_getConnectionStatusJ(JNIEnv *env, jobject in
Monero::Wallet *wallet = getHandle<Monero::Wallet>(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<Monero::Wallet>(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<Monero::Wallet>(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<Monero::Wallet>(env, instance);

View file

@ -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()
}

View file

@ -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 {

View file

@ -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<EditText>(R.id.node_name_edittext)
val usernameEditText = view.findViewById<EditText>(R.id.username_edittext)
val passwordEditText = view.findViewById<EditText>(R.id.password_edittext)
val trustedDaemonCheckbox = view.findViewById<CheckBox>(R.id.trusted_node_checkbox)
val pastePasswordImageButton =
view.findViewById<ImageButton>(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()

View file

@ -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<EditText>(R.id.node_name_edittext)
val usernameEditText = view.findViewById<EditText>(R.id.username_edittext)
val passwordEditText = view.findViewById<EditText>(R.id.password_edittext)
val trustedDaemonCheckBox = view.findViewById<CheckBox>(R.id.trusted_node_checkbox)
val pastePasswordImageButton =
view.findViewById<ImageButton>(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) {

View file

@ -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()
}

View file

@ -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)

View file

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

View file

@ -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<TextView>(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()
}
}

View file

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

View file

@ -57,6 +57,12 @@ class SingleLiveEvent<T> : MutableLiveData<T>() {
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.
*/

View file

@ -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)

View file

@ -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<Node> = SingleLiveEvent()
init {
instance = this
}
fun setDaemon(daemon: Node) {
daemonChangeEvents.postValue(daemon)
}
companion object {
var instance: DaemonService? = null
private set
}
}

View file

@ -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) {

View file

@ -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()) {

View file

@ -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<String> = 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
}
}

View file

@ -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" />
<EditText
android:id="@+id/node_port_edittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:background="@drawable/edittext_bg"
android:hint="@string/node_port_hint"
@ -83,6 +83,19 @@
app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton"
app:layout_constraintStart_toEndOf="@id/address_edittext" />
<CheckBox
android:id="@+id/trusted_node_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginBottom="8dp"
android:text="@string/trusted_daemon"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/username_edittext"
app:layout_constraintTop_toBottomOf="@id/address_edittext"/>
<Button
android:id="@+id/add_node_button"
android:layout_width="match_parent"
@ -101,6 +114,7 @@
android:background="@drawable/edittext_bg"
android:hint="@string/node_username_hint"
android:inputType="text"
app:layout_constraintTop_toBottomOf="@id/trusted_node_checkbox"
app:layout_constraintBottom_toTopOf="@id/password_edittext"
app:layout_constraintEnd_toStartOf="@id/paste_username_imagebutton"
app:layout_constraintStart_toStartOf="parent" />
@ -123,11 +137,10 @@
android:id="@+id/password_edittext"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="@drawable/edittext_bg"
android:hint="@string/node_password_hint"
android:inputType="textPassword"
android:visibility="gone"
android:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/add_node_button"
app:layout_constraintEnd_toStartOf="@id/paste_password_imagebutton"
app:layout_constraintStart_toStartOf="parent" />
@ -136,12 +149,10 @@
android:id="@+id/paste_password_imagebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@android:color/transparent"
android:minWidth="48dp"
android:minHeight="48dp"
android:visibility="gone"
android:visibility="visible"
android:src="@drawable/ic_content_paste_24dp"
app:layout_constraintBottom_toBottomOf="@id/password_edittext"
app:layout_constraintEnd_toEndOf="parent"

View file

@ -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" />
<EditText
android:id="@+id/node_port_edittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:background="@drawable/edittext_bg"
android:hint="@string/node_port_hint"
@ -83,15 +83,29 @@
app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton"
app:layout_constraintStart_toEndOf="@id/address_edittext" />
<CheckBox
android:id="@+id/trusted_node_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="48dp"
android:layout_marginBottom="8dp"
android:text="@string/trusted_daemon"
android:visibility="visible"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/username_edittext"
app:layout_constraintTop_toBottomOf="@id/address_edittext"/>
<EditText
android:id="@+id/username_edittext"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
android:background="@drawable/edittext_bg"
android:hint="@string/node_username_hint"
android:inputType="text"
app:layout_constraintTop_toBottomOf="@id/address_edittext"
app:layout_constraintTop_toBottomOf="@id/trusted_node_checkbox"
app:layout_constraintBottom_toTopOf="@id/password_edittext"
app:layout_constraintEnd_toStartOf="@id/paste_username_imagebutton"
app:layout_constraintStart_toStartOf="parent" />
@ -113,12 +127,10 @@
android:id="@+id/password_edittext"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginTop="16dp"
android:background="@drawable/edittext_bg"
android:hint="@string/node_password_hint"
android:inputType="textPassword"
android:visibility="gone"
android:visibility="visible"
app:layout_constraintTop_toBottomOf="@id/username_edittext"
app:layout_constraintBottom_toTopOf="@id/delete_node_button"
app:layout_constraintEnd_toStartOf="@id/paste_password_imagebutton"
@ -128,13 +140,11 @@
android:id="@+id/paste_password_imagebutton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@android:color/transparent"
android:minWidth="24dp"
android:minHeight="24dp"
android:minWidth="48dp"
android:minHeight="48dp"
android:padding="8dp"
android:visibility="gone"
android:visibility="visible"
android:src="@drawable/ic_content_paste_24dp"
app:layout_constraintBottom_toBottomOf="@id/password_edittext"
app:layout_constraintEnd_toEndOf="parent"
@ -146,7 +156,7 @@
android:id="@+id/delete_node_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="1dp"
android:background="@drawable/button_bg_left"
android:text="@string/delete"
@ -157,7 +167,7 @@
android:id="@+id/done_editing_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:layout_marginTop="16dp"
android:layout_marginStart="1dp"
android:background="@drawable/button_bg_right"
android:text="@string/done"

View file

@ -16,24 +16,13 @@
android:text="@string/settings"
android:layout_marginTop="24dp"
android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/status_textview"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/status_textview"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/disconnected"
android:textSize="12sp"
android:layout_marginEnd="24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/settings_textview"
app:layout_constraintBottom_toBottomOf="@id/settings_textview"
app:layout_constraintTop_toTopOf="@id/settings_textview" />
<TextView
android:id="@+id/wallet_settings_textview"
android:layout_width="match_parent"

View file

@ -105,7 +105,8 @@
<string name="transaction_conf_1_desc2_confirmed">time, and was mined in block</string>
<string name="date">Date</string>
<string name="transaction_on_date_label">on</string>
<string name="node_selected">Node has been selected</string>
<string name="node_selecting">Selecting node…</string>
<string name="node_selected">Using node: %1$s</string>
<string name="fee_priority">Fee priority:</string>
<string name="low">Low</string>
<string name="medium">Medium</string>
@ -134,6 +135,7 @@
<string name="wallet_restore_height_label">Restore height</string>
<string name="block_height">Block Height</string>
<string name="use_password_as_seed_offset">Use passphrase as seed offset</string>
<string name="trusted_daemon">Trusted daemon</string>
<string name="subbaddress_info_subtitle" translatable="false">#%1$d: %2$s</string>
<string name="previous_addresses">Previous addresses</string>
<string name="donate_label">Donate to Mysu</string>