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" applicationId "net.mynero.wallet"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 34 targetSdkVersion 34
versionCode 50200 versionCode 50300
versionName "0.5.2 'Fluorine Fermi'" versionName "0.5.3 'Fluorine Fermi'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild { externalNativeBuild {
cmake { 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); Monero::Wallet *wallet = getHandle<Monero::Wallet>(env, instance);
return wallet->connected(); 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; //TODO virtual bool trustedDaemon() const = 0;
JNIEXPORT jboolean JNICALL 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) { jstring address) {
const char *_address = env->GetStringUTFChars(address, nullptr); const char *_address = env->GetStringUTFChars(address, nullptr);
Monero::Wallet *wallet = getHandle<Monero::Wallet>(env, instance); Monero::Wallet *wallet = getHandle<Monero::Wallet>(env, instance);

View file

@ -4,7 +4,11 @@ import android.os.Bundle
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.NavHostFragment 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
import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog.PasswordListener import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog.PasswordListener
import net.mynero.wallet.fragment.dialog.SendBottomSheetDialog 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.AddressService
import net.mynero.wallet.service.BalanceService import net.mynero.wallet.service.BalanceService
import net.mynero.wallet.service.BlockchainService import net.mynero.wallet.service.BlockchainService
import net.mynero.wallet.service.DaemonService
import net.mynero.wallet.service.HistoryService import net.mynero.wallet.service.HistoryService
import net.mynero.wallet.service.MoneroHandlerThread import net.mynero.wallet.service.MoneroHandlerThread
import net.mynero.wallet.service.PrefService import net.mynero.wallet.service.PrefService
import net.mynero.wallet.service.ProxyService
import net.mynero.wallet.service.TxService import net.mynero.wallet.service.TxService
import net.mynero.wallet.service.UTXOService import net.mynero.wallet.service.UTXOService
import net.mynero.wallet.util.Constants import net.mynero.wallet.util.Constants
import net.mynero.wallet.util.UriData import net.mynero.wallet.util.UriData
import timber.log.Timber
import java.io.File import java.io.File
class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, PasswordListener { class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, PasswordListener {
@ -29,6 +36,8 @@ class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, Password
private var balanceService: BalanceService? = null private var balanceService: BalanceService? = null
private var addressService: AddressService? = null private var addressService: AddressService? = null
private var historyService: HistoryService? = null private var historyService: HistoryService? = null
private var proxyService: ProxyService? = null
private var daemonService: DaemonService? = null
private var blockchainService: BlockchainService? = null private var blockchainService: BlockchainService? = null
private var utxoService: UTXOService? = null private var utxoService: UTXOService? = null
private var proceedToSend = false private var proceedToSend = false
@ -77,6 +86,8 @@ class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, Password
addressService = AddressService(thread) addressService = AddressService(thread)
historyService = HistoryService(thread) historyService = HistoryService(thread)
blockchainService = BlockchainService(thread) blockchainService = BlockchainService(thread)
daemonService = DaemonService(thread)
proxyService = ProxyService(thread)
utxoService = UTXOService(thread) utxoService = UTXOService(thread)
thread.start() thread.start()
} }

View file

@ -39,7 +39,8 @@ class Node {
private set private set
var password = "" var password = ""
private set private set
private var favourite = false var trusted = false
private set
internal constructor(nodeString: String?) { internal constructor(nodeString: String?) {
require(!nodeString.isNullOrEmpty()) { "daemon is empty" } require(!nodeString.isNullOrEmpty()) { "daemon is empty" }
@ -142,6 +143,9 @@ class Node {
} }
require(networkType == WalletManager.instance?.networkType) { "wrong net: $networkType" } require(networkType == WalletManager.instance?.networkType) { "wrong net: $networkType" }
} }
if (jsonObject.has("trusted")) {
this.trusted = jsonObject.getBoolean("trusted")
}
} }
constructor() { constructor() {
@ -197,6 +201,7 @@ class Node {
null -> TODO() null -> TODO()
} }
if (name?.isNotEmpty() == true) jsonObject.put("name", name) if (name?.isNotEmpty() == true) jsonObject.put("name", name)
jsonObject.put("trusted", trusted)
} catch (e: JSONException) { } catch (e: JSONException) {
throw RuntimeException(e) throw RuntimeException(e)
} }
@ -235,7 +240,7 @@ class Node {
levinPort = anotherNode.levinPort levinPort = anotherNode.levinPort
username = anotherNode.username username = anotherNode.username
password = anotherNode.password password = anotherNode.password
favourite = anotherNode.favourite trusted = anotherNode.trusted
} }
companion object { companion object {

View file

@ -7,6 +7,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText import android.widget.EditText
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.Toast import android.widget.Toast
@ -38,6 +39,8 @@ class AddNodeBottomSheetDialog : BottomSheetDialogFragment() {
val nodeNameEditText = view.findViewById<EditText>(R.id.node_name_edittext) val nodeNameEditText = view.findViewById<EditText>(R.id.node_name_edittext)
val usernameEditText = view.findViewById<EditText>(R.id.username_edittext) val usernameEditText = view.findViewById<EditText>(R.id.username_edittext)
val passwordEditText = view.findViewById<EditText>(R.id.password_edittext) val passwordEditText = view.findViewById<EditText>(R.id.password_edittext)
val trustedDaemonCheckbox = view.findViewById<CheckBox>(R.id.trusted_node_checkbox)
val pastePasswordImageButton = val pastePasswordImageButton =
view.findViewById<ImageButton>(R.id.paste_password_imagebutton) view.findViewById<ImageButton>(R.id.paste_password_imagebutton)
usernameEditText.addTextChangedListener(object : TextWatcher { usernameEditText.addTextChangedListener(object : TextWatcher {
@ -63,6 +66,8 @@ class AddNodeBottomSheetDialog : BottomSheetDialogFragment() {
val name = nodeNameEditText.text.toString() val name = nodeNameEditText.text.toString()
val user = usernameEditText.text.toString() val user = usernameEditText.text.toString()
val pass = passwordEditText.text.toString() val pass = passwordEditText.text.toString()
val trusted = trustedDaemonCheckbox.isChecked
if (name.isEmpty()) { if (name.isEmpty()) {
Toast.makeText(context, "Enter node name", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Enter node name", Toast.LENGTH_SHORT).show()
return@setOnClickListener return@setOnClickListener
@ -85,6 +90,7 @@ class AddNodeBottomSheetDialog : BottomSheetDialogFragment() {
jsonObject.put("rpcPort", portString.toInt()) jsonObject.put("rpcPort", portString.toInt())
jsonObject.put("network", "mainnet") jsonObject.put("network", "mainnet")
jsonObject.put("name", name) jsonObject.put("name", name)
jsonObject.put("trusted", trusted)
addNodeToSaved(jsonObject) addNodeToSaved(jsonObject)
if (listener != null) { if (listener != null) {
listener?.onNodeAdded() listener?.onNodeAdded()

View file

@ -7,6 +7,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.CheckBox
import android.widget.EditText import android.widget.EditText
import android.widget.ImageButton import android.widget.ImageButton
import android.widget.Toast import android.widget.Toast
@ -39,6 +40,7 @@ class EditNodeBottomSheetDialog : BottomSheetDialogFragment() {
val nodeNameEditText = view.findViewById<EditText>(R.id.node_name_edittext) val nodeNameEditText = view.findViewById<EditText>(R.id.node_name_edittext)
val usernameEditText = view.findViewById<EditText>(R.id.username_edittext) val usernameEditText = view.findViewById<EditText>(R.id.username_edittext)
val passwordEditText = view.findViewById<EditText>(R.id.password_edittext) val passwordEditText = view.findViewById<EditText>(R.id.password_edittext)
val trustedDaemonCheckBox = view.findViewById<CheckBox>(R.id.trusted_node_checkbox)
val pastePasswordImageButton = val pastePasswordImageButton =
view.findViewById<ImageButton>(R.id.paste_password_imagebutton) view.findViewById<ImageButton>(R.id.paste_password_imagebutton)
if (node == null) return if (node == null) return
@ -46,6 +48,7 @@ class EditNodeBottomSheetDialog : BottomSheetDialogFragment() {
portEditText.setText("${node?.rpcPort}") portEditText.setText("${node?.rpcPort}")
nodeNameEditText.setText(node?.name) nodeNameEditText.setText(node?.name)
usernameEditText.setText(node?.username) usernameEditText.setText(node?.username)
trustedDaemonCheckBox.isChecked = node?.trusted ?: false
if (node?.password?.isNotEmpty() == true) { if (node?.password?.isNotEmpty() == true) {
passwordEditText.setText(node?.password) passwordEditText.setText(node?.password)
passwordEditText.visibility = View.VISIBLE passwordEditText.visibility = View.VISIBLE
@ -78,6 +81,7 @@ class EditNodeBottomSheetDialog : BottomSheetDialogFragment() {
val nodeName = nodeNameEditText.text.toString() val nodeName = nodeNameEditText.text.toString()
val user = usernameEditText.text.toString() val user = usernameEditText.text.toString()
val pass = passwordEditText.text.toString() val pass = passwordEditText.text.toString()
val trusted = trustedDaemonCheckBox.isChecked
if (nodeName.isEmpty()) { if (nodeName.isEmpty()) {
Toast.makeText(context, "Enter node name", Toast.LENGTH_SHORT).show() Toast.makeText(context, "Enter node name", Toast.LENGTH_SHORT).show()
return@setOnClickListener return@setOnClickListener
@ -101,6 +105,7 @@ class EditNodeBottomSheetDialog : BottomSheetDialogFragment() {
jsonObject.put("rpcPort", portString.toInt()) jsonObject.put("rpcPort", portString.toInt())
jsonObject.put("network", "mainnet") jsonObject.put("network", "mainnet")
jsonObject.put("name", nodeName) jsonObject.put("name", nodeName)
jsonObject.put("trusted", trusted)
listener?.onNodeEdited(node, fromJson(jsonObject)) listener?.onNodeEdited(node, fromJson(jsonObject))
dismiss() dismiss()
} catch (e: JSONException) { } catch (e: JSONException) {

View file

@ -7,9 +7,12 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button import android.widget.Button
import android.widget.Toast import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.mynero.wallet.R import net.mynero.wallet.R
import net.mynero.wallet.adapter.NodeSelectionAdapter import net.mynero.wallet.adapter.NodeSelectionAdapter
import net.mynero.wallet.adapter.NodeSelectionAdapter.NodeSelectionAdapterListener 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.fromJson
import net.mynero.wallet.data.Node.Companion.fromString import net.mynero.wallet.data.Node.Companion.fromString
import net.mynero.wallet.model.WalletManager import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.service.DaemonService
import net.mynero.wallet.service.PrefService import net.mynero.wallet.service.PrefService
import net.mynero.wallet.util.Constants import net.mynero.wallet.util.Constants
import org.json.JSONArray import org.json.JSONArray
@ -90,18 +94,12 @@ class NodeSelectionBottomSheetDialog : BottomSheetDialogFragment(), NodeSelectio
} }
override fun onSelectNode(node: Node?) { 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()) PrefService.instance?.edit()?.putString(Constants.PREF_NODE_2, node?.toJson().toString())
?.apply() ?.apply()
WalletManager.instance?.setDaemon(node) node?.let { DaemonService.instance?.setDaemon(it) }
activity?.runOnUiThread {
adapter?.updateSelectedNode() adapter?.updateSelectedNode()
}
listener?.onNodeSelected() listener?.onNodeSelected()
} }

View file

@ -8,12 +8,16 @@ import android.widget.Button
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavDirections import androidx.navigation.NavDirections
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.mynero.wallet.MainActivity import net.mynero.wallet.MainActivity
import net.mynero.wallet.R import net.mynero.wallet.R
import net.mynero.wallet.adapter.TransactionInfoAdapter 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.model.WalletManager
import net.mynero.wallet.service.BalanceService import net.mynero.wallet.service.BalanceService
import net.mynero.wallet.service.BlockchainService import net.mynero.wallet.service.BlockchainService
import net.mynero.wallet.service.DaemonService
import net.mynero.wallet.service.HistoryService import net.mynero.wallet.service.HistoryService
import net.mynero.wallet.service.PrefService import net.mynero.wallet.service.PrefService
import net.mynero.wallet.service.ProxyService
import net.mynero.wallet.util.Constants import net.mynero.wallet.util.Constants
import timber.log.Timber
class HomeFragment : Fragment(), TxInfoAdapterListener { class HomeFragment : Fragment(), TxInfoAdapterListener {
private var startHeight: Long = 0 private var startHeight: Long = 0
@ -66,6 +73,26 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
val historyService = HistoryService.instance val historyService = HistoryService.instance
val blockchainService = BlockchainService.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 -> balanceService?.balanceInfo?.observe(viewLifecycleOwner) { balanceInfo ->
if (balanceInfo != null) { if (balanceInfo != null) {
unlockedBalanceTextView.text = balanceInfo.unlockedDisplay unlockedBalanceTextView.text = balanceInfo.unlockedDisplay
@ -101,7 +128,8 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
} }
} else { } else {
progressBar.visibility = View.INVISIBLE progressBar.visibility = View.INVISIBLE
progressBarText.visibility = View.GONE progressBarText.visibility = View.VISIBLE
progressBarText.text = "Synchronized"
} }
} }
val adapter = TransactionInfoAdapter(this) val adapter = TransactionInfoAdapter(this)

View file

@ -14,6 +14,7 @@ import android.widget.CompoundButton
import android.widget.EditText import android.widget.EditText
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.SwitchCompat import androidx.appcompat.widget.SwitchCompat
import androidx.constraintlayout.widget.ConstraintLayout 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.dialog.NodeSelectionBottomSheetDialog.NodeSelectionDialogListener
import net.mynero.wallet.fragment.onboarding.OnboardingViewModel.SeedType import net.mynero.wallet.fragment.onboarding.OnboardingViewModel.SeedType
import net.mynero.wallet.service.PrefService import net.mynero.wallet.service.PrefService
import net.mynero.wallet.service.ProxyService
import net.mynero.wallet.util.Constants import net.mynero.wallet.util.Constants
class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListener { class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListener {
@ -186,11 +188,11 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_TOR, b)?.apply() PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_TOR, b)?.apply()
removeProxyTextListeners() removeProxyTextListeners()
if (b) { if (b) {
if (PrefService.instance?.hasProxySet() == true) { if (ProxyService.instance?.hasProxySet() == true) {
val proxyAddress = val proxyAddress =
PrefService.instance?.proxyAddress ?: return@setOnCheckedChangeListener ProxyService.instance?.proxyAddress ?: return@setOnCheckedChangeListener
val proxyPort = val proxyPort =
PrefService.instance?.proxyPort ?: return@setOnCheckedChangeListener ProxyService.instance?.proxyPort ?: return@setOnCheckedChangeListener
initProxyStuff(proxyAddress, proxyPort) initProxyStuff(proxyAddress, proxyPort)
} else { } else {
initProxyStuff("127.0.0.1", "9050") initProxyStuff("127.0.0.1", "9050")
@ -277,6 +279,11 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
override fun onNodeSelected() { override fun onNodeSelected() {
val node = PrefService.instance?.node val node = PrefService.instance?.node
selectNodeButton?.text = getString(R.string.node_button_text, node?.address) 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) mViewModel?.updateProxy(activity?.application as MoneroApplication)
} }

View file

@ -1,8 +1,6 @@
package net.mynero.wallet.fragment.settings package net.mynero.wallet.fragment.settings
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Patterns import android.util.Patterns
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -12,12 +10,17 @@ import android.widget.CompoundButton
import android.widget.EditText import android.widget.EditText
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.SwitchCompat import androidx.appcompat.widget.SwitchCompat
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.NavHostFragment 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.R
import net.mynero.wallet.data.Node import net.mynero.wallet.data.Node
import net.mynero.wallet.data.Node.Companion.fromJson 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.model.WalletManager
import net.mynero.wallet.service.BalanceService import net.mynero.wallet.service.BalanceService
import net.mynero.wallet.service.BlockchainService import net.mynero.wallet.service.BlockchainService
import net.mynero.wallet.service.DaemonService
import net.mynero.wallet.service.HistoryService import net.mynero.wallet.service.HistoryService
import net.mynero.wallet.service.PrefService import net.mynero.wallet.service.PrefService
import net.mynero.wallet.service.ProxyService
import net.mynero.wallet.util.Constants import net.mynero.wallet.util.Constants
import org.json.JSONArray import org.json.JSONArray
import timber.log.Timber
class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListener, AddNodeListener, class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListener, AddNodeListener,
EditNodeListener { EditNodeListener {
private var mViewModel: SettingsViewModel? = null 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 walletProxyAddressEditText: EditText? = null
private var walletProxyPortEditText: EditText? = null private var walletProxyPortEditText: EditText? = null
private var selectNodeButton: Button? = null private var selectNodeButton: Button? = null
private var cachedProxyAddress: String = ""
private var cachedProxyPort: String = ""
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
@ -103,12 +92,12 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
donationSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean -> donationSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
PrefService.instance?.edit()?.putBoolean(Constants.PREF_DONATE_PER_TX, b)?.apply() PrefService.instance?.edit()?.putBoolean(Constants.PREF_DONATE_PER_TX, b)?.apply()
} }
val prefService = PrefService.instance val prefService = PrefService.instance ?: return
val usesProxy = prefService?.getBoolean(Constants.PREF_USES_TOR, false) == true val usesProxy = prefService.getBoolean(Constants.PREF_USES_TOR, false)
if (prefService?.hasProxySet() == true) { cachedProxyAddress = ProxyService.instance?.proxyAddress ?: return
val proxyAddress = prefService.proxyAddress cachedProxyPort = ProxyService.instance?.proxyPort ?: return
val proxyPort = prefService.proxyPort if (ProxyService.instance?.hasProxySet() == true) {
initProxyStuff(proxyAddress, proxyPort) initProxyStuff(cachedProxyAddress, cachedProxyPort)
} }
torSwitch.isChecked = usesProxy torSwitch.isChecked = usesProxy
if (usesProxy) { if (usesProxy) {
@ -116,22 +105,19 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
} else { } else {
proxySettingsLayout.visibility = View.GONE proxySettingsLayout.visibility = View.GONE
} }
addProxyTextListeners()
torSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean -> 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 (b) {
if (prefService?.hasProxySet() == true) { if (ProxyService.instance?.hasProxySet() == true) {
removeProxyTextListeners() val proxyAddress = ProxyService.instance?.proxyAddress ?: return@setOnCheckedChangeListener
val proxyAddress = prefService.proxyAddress val proxyPort = ProxyService.instance?.proxyPort ?: return@setOnCheckedChangeListener
val proxyPort = prefService.proxyPort
initProxyStuff(proxyAddress, proxyPort) initProxyStuff(proxyAddress, proxyPort)
addProxyTextListeners()
} }
proxySettingsLayout.visibility = View.VISIBLE proxySettingsLayout.visibility = View.VISIBLE
} else { } else {
proxySettingsLayout.visibility = View.GONE proxySettingsLayout.visibility = View.GONE
} }
mViewModel?.updateProxy(activity?.application as MoneroApplication) refreshProxy()
} }
displaySeedButton.setOnClickListener { displaySeedButton.setOnClickListener {
val usesPassword = val usesPassword =
@ -148,16 +134,6 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
} }
} }
displayUtxosButton.setOnClickListener { navigate(R.id.nav_to_utxos) } 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 val node = PrefService.instance?.node // shouldn't use default value here
selectNodeButton?.text = getString(R.string.node_button_text, node?.address) selectNodeButton?.text = getString(R.string.node_button_text, node?.address)
selectNodeButton?.setOnClickListener { selectNodeButton?.setOnClickListener {
@ -167,6 +143,25 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
dialog.show(fragmentManager, "node_selection_dialog") 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) { private fun displaySeedDialog(password: String) {
@ -188,30 +183,22 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
private fun initProxyStuff(proxyAddress: String, proxyPort: String) { private fun initProxyStuff(proxyAddress: String, proxyPort: String) {
val validIpAddress = Patterns.IP_ADDRESS.matcher(proxyAddress).matches() val validIpAddress = Patterns.IP_ADDRESS.matcher(proxyAddress).matches()
if (validIpAddress) { if (validIpAddress) {
mViewModel?.setProxyAddress(proxyAddress)
mViewModel?.setProxyPort(proxyPort)
walletProxyAddressEditText?.setText(proxyAddress) walletProxyAddressEditText?.setText(proxyAddress)
walletProxyPortEditText?.setText(proxyPort) walletProxyPortEditText?.setText(proxyPort)
} }
} }
private fun removeProxyTextListeners() {
walletProxyAddressEditText?.removeTextChangedListener(proxyAddressListener)
walletProxyPortEditText?.removeTextChangedListener(proxyPortListener)
}
private fun addProxyTextListeners() {
walletProxyAddressEditText?.addTextChangedListener(proxyAddressListener)
walletProxyPortEditText?.addTextChangedListener(proxyPortListener)
}
override fun onNodeSelected() { override fun onNodeSelected() {
val node = PrefService.instance?.node val node = PrefService.instance?.node
selectNodeButton?.text = getString(R.string.node_button_text, node?.address) selectNodeButton?.text = getString(R.string.node_button_text, node?.address)
mViewModel?.updateProxy(activity?.application as MoneroApplication) refreshProxy()
(activity?.application as MoneroApplication).executor?.execute {
WalletManager.instance?.wallet?.init(0) activity?.runOnUiThread {
WalletManager.instance?.wallet?.startRefresh() 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 import net.mynero.wallet.util.Constants
class SettingsViewModel : ViewModel() { 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) 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. * Used for cases where T is Void, to make calls cleaner.
*/ */

View file

@ -208,8 +208,20 @@ class Wallet {
} }
private external fun getConnectionStatusJ(): Int 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 val balance: Long
get() = getBalance(accountIndex) 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 package net.mynero.wallet.service
import android.util.Log
import net.mynero.wallet.model.PendingTransaction import net.mynero.wallet.model.PendingTransaction
import net.mynero.wallet.model.TransactionOutput import net.mynero.wallet.model.TransactionOutput
import net.mynero.wallet.model.Wallet 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.WalletListener
import net.mynero.wallet.model.WalletManager import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.util.Constants import net.mynero.wallet.util.Constants
import timber.log.Timber
import java.security.SecureRandom import java.security.SecureRandom
/** /**
@ -57,11 +59,12 @@ class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet)
currentNode?.address == "localhost" || currentNode?.address == "localhost" ||
currentNode?.address == "127.0.0.1" currentNode?.address == "127.0.0.1"
if (usesTor && !isLocalIp) { if (usesTor && !isLocalIp) {
val proxy = prefService.proxy val proxy = ProxyService.instance?.proxy
proxy?.let { WalletManager.instance?.setProxy(it) } proxy?.let { WalletManager.instance?.setProxy(it) }
wallet.setProxy(proxy) wallet.setProxy(proxy)
} }
WalletManager.instance?.setDaemon(currentNode) WalletManager.instance?.setDaemon(currentNode)
currentNode?.trusted?.let { wallet.setTrustedDaemon(it) }
wallet.init(0) wallet.init(0)
wallet.setListener(this) wallet.setListener(this)
wallet.startRefresh() wallet.startRefresh()
@ -81,20 +84,36 @@ class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet)
override fun refreshed() { override fun refreshed() {
val status = wallet.fullStatus.connectionStatus val status = wallet.fullStatus.connectionStatus
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) { if (status === ConnectionStatus.ConnectionStatus_Disconnected || status == null) {
if (triesLeft > 0) { tryRestartOrFail()
wallet.startRefresh()
triesLeft--
} else { } else {
listener?.onConnectionFail() val heightDiff = daemonHeight - chainHeight
} if(heightDiff >= 2) {
tryRestartOrFail()
} else { } else {
BlockchainService.instance?.daemonHeight = wallet.getDaemonBlockChainHeight() Timber.d("refreshed() Synchronized")
wallet.setSynchronized() wallet.setSynchronized()
wallet.store() wallet.store()
refresh(true) refresh(true)
} }
status?.let { BlockchainService.instance?.setConnectionStatus(it) }
}
}
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) { 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) val usesProxy = getBoolean(Constants.PREF_USES_TOR, false)
var defaultNode = DefaultNodes.SAMOURAI var defaultNode = DefaultNodes.SAMOURAI
if (usesProxy) { if (usesProxy) {
val proxyPort = proxyPort val proxyPort = ProxyService.instance?.proxyPort
if (proxyPort.isNotEmpty()) { if (proxyPort?.isNotEmpty() == true) {
val port = proxyPort.toInt() val port = proxyPort.toInt()
defaultNode = if (port == 4447) { defaultNode = if (port == 4447) {
DefaultNodes.MYNERO_I2P DefaultNodes.MYNERO_I2P
@ -60,35 +60,6 @@ class PrefService(application: MoneroApplication) : ServiceBase(null) {
return 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? { fun getString(key: String?, defaultValue: String): String? {
val value = preferences?.getString(key, "") val value = preferences?.getString(key, "")
if (value?.isEmpty() == true && defaultValue.isNotEmpty()) { 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:id="@+id/address_edittext"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="16dp" android:layout_marginBottom="8dp"
android:background="@drawable/edittext_bg" android:background="@drawable/edittext_bg"
android:hint="@string/node_address_hint" android:hint="@string/node_address_hint"
android:inputType="text" android:inputType="text"
android:digits="QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm1234567890.:-[]" 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_constraintEnd_toStartOf="@id/node_port_edittext"
app:layout_constraintTop_toBottomOf="@id/node_name_edittext"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
<EditText <EditText
android:id="@+id/node_port_edittext" android:id="@+id/node_port_edittext"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp" android:layout_marginStart="8dp"
android:background="@drawable/edittext_bg" android:background="@drawable/edittext_bg"
android:hint="@string/node_port_hint" android:hint="@string/node_port_hint"
@ -83,6 +83,19 @@
app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton" app:layout_constraintEnd_toStartOf="@id/paste_address_imagebutton"
app:layout_constraintStart_toEndOf="@id/address_edittext" /> 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 <Button
android:id="@+id/add_node_button" android:id="@+id/add_node_button"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -101,6 +114,7 @@
android:background="@drawable/edittext_bg" android:background="@drawable/edittext_bg"
android:hint="@string/node_username_hint" android:hint="@string/node_username_hint"
android:inputType="text" android:inputType="text"
app:layout_constraintTop_toBottomOf="@id/trusted_node_checkbox"
app:layout_constraintBottom_toTopOf="@id/password_edittext" app:layout_constraintBottom_toTopOf="@id/password_edittext"
app:layout_constraintEnd_toStartOf="@id/paste_username_imagebutton" app:layout_constraintEnd_toStartOf="@id/paste_username_imagebutton"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
@ -123,11 +137,10 @@
android:id="@+id/password_edittext" android:id="@+id/password_edittext"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:background="@drawable/edittext_bg" android:background="@drawable/edittext_bg"
android:hint="@string/node_password_hint" android:hint="@string/node_password_hint"
android:inputType="textPassword" android:inputType="textPassword"
android:visibility="gone" android:visibility="visible"
app:layout_constraintBottom_toTopOf="@id/add_node_button" app:layout_constraintBottom_toTopOf="@id/add_node_button"
app:layout_constraintEnd_toStartOf="@id/paste_password_imagebutton" app:layout_constraintEnd_toStartOf="@id/paste_password_imagebutton"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintStart_toStartOf="parent" />
@ -136,12 +149,10 @@
android:id="@+id/paste_password_imagebutton" android:id="@+id/paste_password_imagebutton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:background="@android:color/transparent" android:background="@android:color/transparent"
android:minWidth="48dp" android:minWidth="48dp"
android:minHeight="48dp" android:minHeight="48dp"
android:visibility="gone" android:visibility="visible"
android:src="@drawable/ic_content_paste_24dp" android:src="@drawable/ic_content_paste_24dp"
app:layout_constraintBottom_toBottomOf="@id/password_edittext" app:layout_constraintBottom_toBottomOf="@id/password_edittext"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"

View file

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

View file

@ -16,24 +16,13 @@
android:text="@string/settings" android:text="@string/settings"
android:layout_marginTop="24dp" android:layout_marginTop="24dp"
android:layout_marginStart="24dp" android:layout_marginStart="24dp"
android:layout_marginEnd="24dp"
android:textSize="32sp" android:textSize="32sp"
android:textStyle="bold" android:textStyle="bold"
app:layout_constraintEnd_toStartOf="@id/status_textview" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="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 <TextView
android:id="@+id/wallet_settings_textview" android:id="@+id/wallet_settings_textview"
android:layout_width="match_parent" 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="transaction_conf_1_desc2_confirmed">time, and was mined in block</string>
<string name="date">Date</string> <string name="date">Date</string>
<string name="transaction_on_date_label">on</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="fee_priority">Fee priority:</string>
<string name="low">Low</string> <string name="low">Low</string>
<string name="medium">Medium</string> <string name="medium">Medium</string>
@ -134,6 +135,7 @@
<string name="wallet_restore_height_label">Restore height</string> <string name="wallet_restore_height_label">Restore height</string>
<string name="block_height">Block Height</string> <string name="block_height">Block Height</string>
<string name="use_password_as_seed_offset">Use passphrase as seed offset</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="subbaddress_info_subtitle" translatable="false">#%1$d: %2$s</string>
<string name="previous_addresses">Previous addresses</string> <string name="previous_addresses">Previous addresses</string>
<string name="donate_label">Donate to Mysu</string> <string name="donate_label">Donate to Mysu</string>