Refactoring settings/onboarding screens

This commit is contained in:
pokkst 2023-12-09 02:30:55 -06:00
parent 3b77ae3673
commit bd67d6d4bd
No known key found for this signature in database
GPG key ID: EC4FAAA66859FAA4
9 changed files with 468 additions and 577 deletions

View file

@ -1,10 +1,8 @@
package net.mynero.wallet.fragment.onboarding
import android.app.Activity
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Patterns
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -18,13 +16,12 @@ import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.SwitchCompat
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import com.google.android.material.progressindicator.CircularProgressIndicator
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import net.mynero.wallet.MoneroApplication
import net.mynero.wallet.R
import net.mynero.wallet.data.Node
import net.mynero.wallet.fragment.dialog.AddNodeBottomSheetDialog
@ -32,9 +29,9 @@ import net.mynero.wallet.fragment.dialog.AddNodeBottomSheetDialog.AddNodeListene
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.model.EnumTorState
import net.mynero.wallet.service.PrefService
import net.mynero.wallet.service.ProxyService
import net.mynero.wallet.util.Constants
class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListener {
private var useOffset = true
@ -56,6 +53,7 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
private var xmrchanOnboardingImage: ImageView? = null
private var seedTypeButton: Button? = null
private var seedTypeDescTextView: TextView? = null
private var useBundledTor: CheckBox? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
@ -83,11 +81,38 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
xmrchanOnboardingImage = view.findViewById(R.id.xmrchan_onboarding_imageview)
seedTypeButton = view.findViewById(R.id.seed_type_button)
seedTypeDescTextView = view.findViewById(R.id.seed_type_desc_textview)
useBundledTor = view.findViewById(R.id.bundled_tor_checkbox)
seedOffsetCheckbox?.isChecked = useOffset
showXmrchanSwitch?.isChecked = true
val usingProxy = ProxyService.instance?.usingProxy == true
val usingBundledTor = ProxyService.instance?.useBundledTor == true
torSwitch?.isChecked = usingProxy
useBundledTor?.isChecked = usingBundledTor
useBundledTor?.isEnabled = usingProxy
walletProxyAddressEditText?.isEnabled = usingProxy && !usingBundledTor
walletProxyPortEditText?.isEnabled = usingProxy && !usingBundledTor
walletProxyPortEditText?.visibility = if (usingBundledTor) View.GONE else View.VISIBLE
walletProxyAddressEditText?.visibility = if (usingBundledTor) View.GONE else View.VISIBLE
val node = PrefService.instance?.node // should be using default here
selectNodeButton?.text = getString(R.string.node_button_text, node?.address)
bindListeners()
bindObservers()
}
private fun bindObservers() {
mViewModel?.passphrase?.observe(viewLifecycleOwner) { text ->
if (text.isEmpty()) {
walletPasswordConfirmEditText?.text = null
walletPasswordConfirmEditText?.visibility = View.GONE
} else {
walletPasswordConfirmEditText?.visibility = View.VISIBLE
}
}
mViewModel?.showMoreOptions?.observe(viewLifecycleOwner) { show: Boolean ->
if (show) {
moreOptionsChevronImageView?.setImageResource(R.drawable.ic_keyboard_arrow_up)
@ -97,9 +122,11 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
advancedOptionsLayout?.visibility = View.GONE
}
}
mViewModel?.enableButton?.observe(viewLifecycleOwner) { enable: Boolean ->
createWalletButton?.isEnabled = enable
}
mViewModel?.seedType?.observe(viewLifecycleOwner) { seedType: SeedType ->
seedTypeButton?.text = seedType.toString()
seedTypeDescTextView?.text = getText(seedType.descResId)
@ -116,59 +143,92 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
}
}
mViewModel?.showMonerochan?.observe(viewLifecycleOwner) {
if (it) {
xmrchanOnboardingImage?.visibility = View.VISIBLE
} else {
xmrchanOnboardingImage?.visibility = View.GONE
}
}
mViewModel?.useBundledTor?.observe(viewLifecycleOwner) { isChecked ->
walletProxyPortEditText?.visibility = if (isChecked) View.GONE else View.VISIBLE
walletProxyAddressEditText?.visibility = if (isChecked) View.GONE else View.VISIBLE
}
mViewModel?.useProxy?.observe(viewLifecycleOwner) { useProxy ->
useBundledTor?.isEnabled = useProxy
walletProxyAddressEditText?.isEnabled = useProxy
walletProxyPortEditText?.isEnabled = useProxy
}
val samouraiTorManager = ProxyService.instance?.samouraiTorManager
samouraiTorManager?.getTorStateLiveData()?.observeForever {
println("STATE CHANGE:: ${it.state.name}")
val indicatorCircle = view?.findViewById<CircularProgressIndicator>(R.id.onboarding_tor_loading_progressindicator)
val torIcon = view?.findViewById<ImageView>(R.id.onboarding_tor_icon)
samouraiTorManager?.getTorStateLiveData()?.observe(viewLifecycleOwner) { state ->
samouraiTorManager.getProxy()?.address()?.let { socketAddress ->
if(socketAddress.toString().isEmpty()) return@let
println("PROXY INIT")
val proxyString = socketAddress.toString().substring(1)
val address = proxyString.split(":")[0]
val port = proxyString.split(":")[1]
if(mViewModel?.useProxy?.value == true && mViewModel?.useBundledTor?.value == true) {
mViewModel?.setProxyAddress(address)
mViewModel?.setProxyPort(port)
torIcon?.visibility = View.VISIBLE
indicatorCircle?.visibility = View.INVISIBLE
}
}
indicatorCircle?.isIndeterminate = state.progressIndicator == 0
indicatorCircle?.progress = state.progressIndicator
when (state.state) {
EnumTorState.OFF -> {
torIcon?.visibility = View.INVISIBLE
indicatorCircle?.visibility = View.INVISIBLE
}
EnumTorState.STARTING -> {
torIcon?.visibility = View.INVISIBLE
indicatorCircle?.visibility = View.VISIBLE
}
EnumTorState.STOPPING -> {
torIcon?.visibility = View.INVISIBLE
indicatorCircle?.visibility = View.VISIBLE
}
else -> {}
}
}
}
private fun bindListeners() {
val useBundledTor = view?.findViewById<CheckBox>(R.id.bundled_tor_checkbox)
seedOffsetCheckbox?.isChecked = useOffset
// Disable onBack click
val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {}
}
val activity = activity
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, onBackPressedCallback)
moreOptionsDropdownTextView?.setOnClickListener { mViewModel?.onMoreOptionsClicked() }
moreOptionsChevronImageView?.setOnClickListener { mViewModel?.onMoreOptionsClicked() }
seedOffsetCheckbox?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
useOffset = b
}
createWalletButton?.setOnClickListener {
onBackPressedCallback.isEnabled = false
(getActivity()?.application as MoneroApplication).executor?.execute {
createOrImportWallet(
walletSeedEditText?.text.toString().trim { it <= ' ' },
walletRestoreHeightEditText?.text.toString().trim { it <= ' ' }
)
}
createOrImportWallet(
walletSeedEditText?.text.toString().trim { it <= ' ' },
walletRestoreHeightEditText?.text.toString().trim { it <= ' ' }
)
}
walletPasswordEditText?.addTextChangedListener(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) {
val text = editable.toString()
mViewModel?.setPassphrase(text)
if (text.isEmpty()) {
walletPasswordConfirmEditText?.text = null
walletPasswordConfirmEditText?.visibility = View.GONE
} else {
walletPasswordConfirmEditText?.visibility = View.VISIBLE
}
mViewModel?.setPassphrase(editable.toString())
}
})
@ -176,10 +236,10 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
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) {
val text = editable.toString()
mViewModel?.setConfirmedPassphrase(text)
mViewModel?.setConfirmedPassphrase(editable.toString())
}
})
walletSeedEditText?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
@ -192,19 +252,13 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
}
}
})
seedTypeButton?.setOnClickListener { toggleSeedType() }
torSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
if (b) {
useBundledTor?.visibility = View.VISIBLE
walletProxyAddressEditText?.visibility = View.VISIBLE
walletProxyPortEditText?.visibility = View.VISIBLE
} else {
useBundledTor?.visibility = View.GONE
walletProxyAddressEditText?.visibility = View.GONE
walletProxyPortEditText?.visibility = View.GONE
}
torSwitch?.setOnCheckedChangeListener { _, b: Boolean ->
mViewModel?.setUseProxy(b)
}
walletProxyPortEditText?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
@ -213,6 +267,7 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
mViewModel?.setProxyPort(text)
}
})
walletProxyAddressEditText?.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
@ -221,16 +276,11 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
mViewModel?.setProxyAddress(text)
}
})
showXmrchanSwitch?.isChecked = true
showXmrchanSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
if (b) {
xmrchanOnboardingImage?.visibility = View.VISIBLE
} else {
xmrchanOnboardingImage?.visibility = View.GONE
}
mViewModel?.setMonerochan(b)
}
val node = PrefService.instance?.node // should be using default here
selectNodeButton?.text = getString(R.string.node_button_text, node?.address)
selectNodeButton?.setOnClickListener {
activity?.supportFragmentManager?.let { fragmentManager ->
val dialog = NodeSelectionBottomSheetDialog()
@ -240,13 +290,7 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
}
useBundledTor?.setOnCheckedChangeListener { _, isChecked ->
walletProxyPortEditText?.visibility = if (isChecked) View.GONE else View.VISIBLE
walletProxyAddressEditText?.visibility = if (isChecked) View.GONE else View.VISIBLE
if(isChecked) {
ProxyService.instance?.samouraiTorManager?.start()
} else {
ProxyService.instance?.samouraiTorManager?.stop()
if(!isChecked) {
mViewModel?.setProxyAddress("")
mViewModel?.setProxyPort("")
}
@ -270,11 +314,10 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
walletSeed: String,
restoreHeightText: String
) {
val activity: Activity? = activity
if (activity != null) {
activity?.let { act ->
lifecycleScope.launch(Dispatchers.IO) {
mViewModel?.createOrImportWallet(
activity,
act,
walletSeed,
restoreHeightText,
useOffset
@ -288,10 +331,9 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
selectNodeButton?.text = getString(R.string.node_button_text, node?.address)
Toast.makeText(
activity,
getString(R.string.node_selected),
getString(R.string.node_selected, node?.name ?: node?.host),
Toast.LENGTH_SHORT
).show()
refreshProxy()
}
override fun onClickedEditNode(node: Node?) {}
@ -310,10 +352,4 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
dialog.show(fragmentManager, "node_selection_dialog")
}
}
private fun refreshProxy() {
val proxyAddress = walletProxyAddressEditText?.text.toString()
val proxyPort = walletProxyPortEditText?.text.toString()
ProxyService.instance?.updateProxy(proxyAddress, proxyPort)
}
}

View file

@ -1,7 +1,6 @@
package net.mynero.wallet.fragment.onboarding
import android.app.Activity
import android.util.Patterns
import android.widget.Toast
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
@ -9,7 +8,7 @@ import androidx.lifecycle.ViewModel
import net.mynero.wallet.MainActivity
import net.mynero.wallet.MoneroApplication
import net.mynero.wallet.R
import net.mynero.wallet.livedata.combineLatestIgnoreNull
import net.mynero.wallet.livedata.combineLiveDatas
import net.mynero.wallet.model.Wallet
import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.service.PrefService
@ -30,11 +29,19 @@ class OnboardingViewModel : ViewModel() {
private val _useBundledTor = MutableLiveData(false)
val useBundledTor: LiveData<Boolean> = _useBundledTor
private val _passphrase = MutableLiveData("")
val passphrase: LiveData<String> = _passphrase
private val _confirmedPassphrase = MutableLiveData("")
private val _showMonerochan = MutableLiveData(true)
val showMonerochan: LiveData<Boolean> = _showMonerochan
var showMoreOptions: LiveData<Boolean> = _showMoreOptions
var seedType: LiveData<SeedType> = _seedType
val enableButton = combineLatestIgnoreNull(
init {
_useProxy.value = ProxyService.instance?.usingProxy
_useBundledTor.value = ProxyService.instance?.useBundledTor
}
val enableButton = combineLiveDatas(
seedType,
_useProxy,
_proxyAddress,
@ -44,14 +51,14 @@ class OnboardingViewModel : ViewModel() {
_confirmedPassphrase,
_creatingWallet
) { seedType, useProxy, proxyAddress, proxyPort, useBundledTor, passphrase, confirmedPassphrase, creatingWallet ->
if(seedType == null || useProxy == null || proxyAddress == null || proxyPort == null || useBundledTor == null || passphrase == null || confirmedPassphrase == null || creatingWallet == null) return@combineLatestIgnoreNull false
if((passphrase.isNotEmpty() || confirmedPassphrase.isNotEmpty()) && passphrase != confirmedPassphrase) return@combineLatestIgnoreNull false
if(creatingWallet) return@combineLatestIgnoreNull false
if(seedType == SeedType.POLYSEED && passphrase.isEmpty()) return@combineLatestIgnoreNull false
if(useProxy && (proxyAddress.isEmpty() || proxyPort.isEmpty())) return@combineLatestIgnoreNull false
if(useBundledTor && (proxyAddress.isEmpty() || proxyPort.isEmpty())) return@combineLatestIgnoreNull false
if(seedType == null || useProxy == null || proxyAddress == null || proxyPort == null || useBundledTor == null || passphrase == null || confirmedPassphrase == null || creatingWallet == null) return@combineLiveDatas false
if((passphrase.isNotEmpty() || confirmedPassphrase.isNotEmpty()) && passphrase != confirmedPassphrase) return@combineLiveDatas false
if(creatingWallet) return@combineLiveDatas false
if(seedType == SeedType.POLYSEED && (passphrase.isEmpty() || confirmedPassphrase.isEmpty())) return@combineLiveDatas false
if(useProxy && (proxyAddress.isEmpty() || proxyPort.isEmpty())) return@combineLiveDatas false
if(useBundledTor && (proxyAddress.isEmpty() || proxyPort.isEmpty())) return@combineLiveDatas false
return@combineLatestIgnoreNull true
return@combineLiveDatas true
}
fun onMoreOptionsClicked() {
@ -72,7 +79,6 @@ class OnboardingViewModel : ViewModel() {
) {
val passphrase = _passphrase.value ?: return
val confirmedPassphrase = _confirmedPassphrase.value ?: return
val useProxy = _useProxy.value ?: return
val application = mainActivity.application as MoneroApplication
_creatingWallet.postValue(true)
@ -172,15 +178,6 @@ class OnboardingViewModel : ViewModel() {
val ok = walletStatus?.isOk
walletFile.delete() // cache is broken for some reason when recovering wallets. delete the file here. this happens in monerujo too.
if (ok == true) {
var editor = PrefService.instance?.edit()
?.putBoolean(Constants.PREF_USE_BUNDLED_TOR, _useBundledTor.value == true)
?.putBoolean(Constants.PREF_USES_PROXY, useProxy)
if(useProxy) {
editor = editor?.putString(Constants.PREF_PROXY, "${_proxyAddress.value}:${_proxyPort.value}")
}
editor?.apply()
(mainActivity as MainActivity).init(walletFile, passphrase)
mainActivity.runOnUiThread { mainActivity.onBackPressed() }
} else {
@ -228,18 +225,42 @@ class OnboardingViewModel : ViewModel() {
fun setProxyAddress(address: String) {
_proxyAddress.value = address
val port = _proxyPort.value ?: return
val proxyAddress = "$address:$port"
if(proxyAddress == ":") return
PrefService.instance?.edit()?.putString(Constants.PREF_PROXY, proxyAddress)?.apply()
}
fun setProxyPort(port: String) {
_proxyPort.value = port
val address = _proxyAddress.value ?: return
val proxyAddress = "$address:$port"
if(proxyAddress == ":") return
PrefService.instance?.edit()?.putString(Constants.PREF_PROXY, proxyAddress)?.apply()
}
fun setUseBundledTor(useBundled: Boolean) {
_useBundledTor.value = useBundled
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USE_BUNDLED_TOR, useBundled)?.apply()
val samouraiTorManager = ProxyService.instance?.samouraiTorManager
if(useBundled && useProxy.value == true) {
samouraiTorManager?.start()
} else {
samouraiTorManager?.stop()
}
}
fun setUseProxy(useProxy: Boolean) {
_useProxy.value = useProxy
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_PROXY, useProxy)?.apply()
val samouraiTorManager = ProxyService.instance?.samouraiTorManager
if(useProxy && useBundledTor.value == true) {
samouraiTorManager?.start()
} else {
samouraiTorManager?.stop()
}
}
fun setPassphrase(passphrase: String) {
@ -250,6 +271,11 @@ class OnboardingViewModel : ViewModel() {
_confirmedPassphrase.value = confirmedPassphrase
}
fun setMonerochan(b: Boolean) {
_showMonerochan.value = b
PrefService.instance?.edit()?.putBoolean(Constants.PREF_MONEROCHAN, b)?.apply()
}
enum class SeedType(val descResId: Int) {
LEGACY(R.string.seed_desc_legacy), POLYSEED(R.string.seed_desc_polyseed), UNKNOWN(0)

View file

@ -9,6 +9,8 @@ import android.widget.Button
import android.widget.CheckBox
import android.widget.CompoundButton
import android.widget.EditText
import android.widget.ImageView
import android.widget.Switch
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.widget.SwitchCompat
@ -17,6 +19,7 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import com.google.android.material.progressindicator.CircularProgressIndicator
import net.mynero.wallet.R
import net.mynero.wallet.data.Node
import net.mynero.wallet.data.Node.Companion.fromJson
@ -29,6 +32,7 @@ import net.mynero.wallet.fragment.dialog.NodeSelectionBottomSheetDialog.NodeSele
import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog
import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog.PasswordListener
import net.mynero.wallet.fragment.dialog.WalletKeysBottomSheetDialog
import net.mynero.wallet.model.EnumTorState
import net.mynero.wallet.service.BalanceService
import net.mynero.wallet.service.HistoryService
import net.mynero.wallet.service.PrefService
@ -44,6 +48,14 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
private var selectNodeButton: Button? = null
private var cachedProxyAddress: String = ""
private var cachedProxyPort: String = ""
private var streetModeSwitch: SwitchCompat? = null
private var monerochanSwitch: SwitchCompat? = null
private var donationSwitch: SwitchCompat? = null
private var useBundledTor: CheckBox? = null
private var displaySeedButton: Button? = null
private var displayUtxosButton: Button? = null
private var torSwitch: SwitchCompat? = null
private var proxySettingsLayout: ConstraintLayout? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@ -55,68 +67,76 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mViewModel = ViewModelProvider(this)[SettingsViewModel::class.java]
val displaySeedButton = view.findViewById<Button>(R.id.display_seed_button)
val displayUtxosButton = view.findViewById<Button>(R.id.display_utxos_button)
displaySeedButton = view.findViewById(R.id.display_seed_button)
displayUtxosButton = view.findViewById(R.id.display_utxos_button)
selectNodeButton = view.findViewById(R.id.select_node_button)
val streetModeSwitch = view.findViewById<SwitchCompat>(R.id.street_mode_switch)
val monerochanSwitch = view.findViewById<SwitchCompat>(R.id.monerochan_switch)
val donationSwitch = view.findViewById<SwitchCompat>(R.id.donate_per_tx_switch)
val torSwitch = view.findViewById<SwitchCompat>(R.id.tor_switch)
val proxySettingsLayout =
view.findViewById<ConstraintLayout>(R.id.wallet_proxy_settings_layout)
streetModeSwitch = view.findViewById(R.id.street_mode_switch)
monerochanSwitch = view.findViewById(R.id.monerochan_switch)
donationSwitch = view.findViewById(R.id.donate_per_tx_switch)
torSwitch = view.findViewById(R.id.tor_switch)
proxySettingsLayout = view.findViewById(R.id.wallet_proxy_settings_layout)
walletProxyAddressEditText = view.findViewById(R.id.wallet_proxy_address_edittext)
walletProxyPortEditText = view.findViewById(R.id.wallet_proxy_port_edittext)
val useBundledTor = view.findViewById<CheckBox>(R.id.bundled_tor_checkbox)
useBundledTor = view.findViewById(R.id.bundled_tor_checkbox)
useBundledTor.isChecked = ProxyService.instance?.useBundledTor == true
walletProxyPortEditText?.visibility = if (useBundledTor.isChecked) View.GONE else View.VISIBLE
walletProxyAddressEditText?.visibility = if (useBundledTor.isChecked) View.GONE else View.VISIBLE
useBundledTor?.isChecked = ProxyService.instance?.useBundledTor == true
walletProxyPortEditText?.visibility = if (useBundledTor?.isChecked == true) View.GONE else View.VISIBLE
walletProxyAddressEditText?.visibility = if (useBundledTor?.isChecked == true) View.GONE else View.VISIBLE
streetModeSwitch?.isChecked = PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false) == true
monerochanSwitch?.isChecked = PrefService.instance?.getBoolean(Constants.PREF_MONEROCHAN, true) == true
donationSwitch?.isChecked = PrefService.instance?.getBoolean(Constants.PREF_DONATE_PER_TX, false) == true
streetModeSwitch.isChecked =
PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false) == true
streetModeSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
PrefService.instance?.edit()?.putBoolean(Constants.PREF_STREET_MODE, b)?.apply()
BalanceService.instance?.refreshBalance()
}
monerochanSwitch.isChecked =
PrefService.instance?.getBoolean(Constants.PREF_MONEROCHAN, true) == true
monerochanSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
PrefService.instance?.edit()?.putBoolean(Constants.PREF_MONEROCHAN, b)?.apply()
HistoryService.instance?.refreshHistory()
}
donationSwitch.isChecked =
PrefService.instance?.getBoolean(Constants.PREF_DONATE_PER_TX, false) == true
donationSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
PrefService.instance?.edit()?.putBoolean(Constants.PREF_DONATE_PER_TX, b)?.apply()
}
val prefService = PrefService.instance ?: return
val usesProxy = ProxyService.instance?.usingProxy == true
cachedProxyAddress = ProxyService.instance?.proxyAddress ?: return
cachedProxyPort = ProxyService.instance?.proxyPort ?: return
if (ProxyService.instance?.hasProxySet() == true) {
initProxyStuff(cachedProxyAddress, cachedProxyPort)
}
torSwitch.isChecked = usesProxy
if (usesProxy) {
proxySettingsLayout.visibility = View.VISIBLE
} else {
proxySettingsLayout.visibility = View.GONE
}
torSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
prefService.edit()?.putBoolean(Constants.PREF_USES_PROXY, b)?.apply()
if (b) {
if (ProxyService.instance?.hasProxySet() == true) {
val proxyAddress = ProxyService.instance?.proxyAddress ?: return@setOnCheckedChangeListener
val proxyPort = ProxyService.instance?.proxyPort ?: return@setOnCheckedChangeListener
initProxyStuff(proxyAddress, proxyPort)
}
proxySettingsLayout.visibility = View.VISIBLE
} else {
proxySettingsLayout.visibility = View.GONE
torSwitch?.isChecked = usesProxy
proxySettingsLayout?.visibility = if (usesProxy) View.VISIBLE else View.GONE
val node = PrefService.instance?.node // shouldn't use default value here
selectNodeButton?.text = getString(R.string.node_button_text, node?.address)
bindListeners()
bindObservers()
}
private fun bindListeners() {
val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
refreshProxy()
findNavController().popBackStack()
}
refreshProxy()
}
displaySeedButton.setOnClickListener {
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, onBackPressedCallback)
donationSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
PrefService.instance?.edit()?.putBoolean(Constants.PREF_DONATE_PER_TX, b)?.apply()
}
streetModeSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
PrefService.instance?.edit()?.putBoolean(Constants.PREF_STREET_MODE, b)?.apply()
BalanceService.instance?.refreshBalance()
}
monerochanSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
PrefService.instance?.edit()?.putBoolean(Constants.PREF_MONEROCHAN, b)?.apply()
HistoryService.instance?.refreshHistory()
}
selectNodeButton?.setOnClickListener {
activity?.supportFragmentManager?.let { fragmentManager ->
val dialog = NodeSelectionBottomSheetDialog()
dialog.listener = this
dialog.show(fragmentManager, "node_selection_dialog")
}
}
useBundledTor?.setOnCheckedChangeListener { _, isChecked ->
mViewModel?.setUseBundledTor(isChecked)
}
displaySeedButton?.setOnClickListener {
val usesPassword =
PrefService.instance?.getBoolean(Constants.PREF_USES_PASSWORD, false) == true
if (usesPassword) {
@ -130,37 +150,66 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
displaySeedDialog("")
}
}
displayUtxosButton.setOnClickListener { navigate(R.id.nav_to_utxos) }
val node = PrefService.instance?.node // shouldn't use default value here
selectNodeButton?.text = getString(R.string.node_button_text, node?.address)
selectNodeButton?.setOnClickListener {
activity?.supportFragmentManager?.let { fragmentManager ->
val dialog = NodeSelectionBottomSheetDialog()
dialog.listener = this
dialog.show(fragmentManager, "node_selection_dialog")
displayUtxosButton?.setOnClickListener { navigate(R.id.nav_to_utxos) }
torSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
mViewModel?.setUseProxy(b)
}
}
private fun bindObservers() {
mViewModel?.useProxy?.observe(viewLifecycleOwner) { b ->
if (b) {
if (ProxyService.instance?.hasProxySet() == true) {
val proxyAddress = ProxyService.instance?.proxyAddress ?: return@observe
val proxyPort = ProxyService.instance?.proxyPort ?: return@observe
initProxyStuff(proxyAddress, proxyPort)
}
proxySettingsLayout?.visibility = View.VISIBLE
} else {
proxySettingsLayout?.visibility = View.GONE
}
refreshProxy()
}
useBundledTor?.setOnCheckedChangeListener { _, isChecked ->
mViewModel?.useBundledTor?.observe(viewLifecycleOwner) { isChecked ->
walletProxyPortEditText?.visibility = if (isChecked) View.GONE else View.VISIBLE
walletProxyAddressEditText?.visibility = if (isChecked) View.GONE else View.VISIBLE
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USE_BUNDLED_TOR, isChecked)?.apply()
if(isChecked) {
ProxyService.instance?.samouraiTorManager?.start()
} else {
ProxyService.instance?.samouraiTorManager?.stop()
}
}
val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
refreshProxy()
findNavController().popBackStack()
val samouraiTorManager = ProxyService.instance?.samouraiTorManager
val indicatorCircle = view?.findViewById<CircularProgressIndicator>(R.id.settings_tor_loading_progressindicator)
val torIcon = view?.findViewById<ImageView>(R.id.settings_tor_icon)
samouraiTorManager?.getTorStateLiveData()?.observe(viewLifecycleOwner) { state ->
samouraiTorManager.getProxy()?.address()?.let { socketAddress ->
if(socketAddress.toString().isEmpty()) return@let
if(mViewModel?.useProxy?.value == true && mViewModel?.useBundledTor?.value == true) {
torIcon?.visibility = View.VISIBLE
indicatorCircle?.visibility = View.INVISIBLE
}
}
indicatorCircle?.isIndeterminate = state.progressIndicator == 0
indicatorCircle?.progress = state.progressIndicator
when (state.state) {
EnumTorState.OFF -> {
torIcon?.visibility = View.INVISIBLE
indicatorCircle?.visibility = View.INVISIBLE
}
EnumTorState.STARTING -> {
torIcon?.visibility = View.INVISIBLE
indicatorCircle?.visibility = View.VISIBLE
}
EnumTorState.STOPPING -> {
torIcon?.visibility = View.INVISIBLE
indicatorCircle?.visibility = View.VISIBLE
}
else -> {}
}
}
activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, onBackPressedCallback)
}
private fun refreshProxy() {

View file

@ -1,12 +1,47 @@
package net.mynero.wallet.fragment.settings
import android.util.Patterns
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import net.mynero.wallet.MoneroApplication
import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.service.PrefService
import net.mynero.wallet.service.ProxyService
import net.mynero.wallet.util.Constants
class SettingsViewModel : ViewModel() {
private val _useProxy = MutableLiveData(false)
val useProxy: LiveData<Boolean> = _useProxy
private val _useBundledTor = MutableLiveData(false)
val useBundledTor: LiveData<Boolean> = _useBundledTor
init {
_useProxy.value = ProxyService.instance?.usingProxy
_useBundledTor.value = ProxyService.instance?.useBundledTor
}
fun setUseProxy(use: Boolean) {
_useProxy.value = use
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_PROXY, use)?.apply()
val samouraiTorManager = ProxyService.instance?.samouraiTorManager
if(use && useBundledTor.value == true) {
samouraiTorManager?.start()
} else {
samouraiTorManager?.stop()
}
}
fun setUseBundledTor(use: Boolean) {
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USE_BUNDLED_TOR, use)?.apply()
_useBundledTor.value = use
val samouraiTorManager = ProxyService.instance?.samouraiTorManager
if(use && useProxy.value == true) {
samouraiTorManager?.start()
} else {
samouraiTorManager?.stop()
}
}
}

View file

@ -0,0 +1,123 @@
package net.mynero.wallet.livedata
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
fun <T1, T2, T3, T4, T5, T6, T7, T8, S> combineLiveDatas(
source1: LiveData<T1>,
source2: LiveData<T2>,
source3: LiveData<T3>,
source4: LiveData<T4>,
source5: LiveData<T5>,
source6: LiveData<T6>,
source7: LiveData<T7>,
source8: LiveData<T8>,
func: (T1?, T2?, T3?, T4?, T5?, T6?, T7?, T8?) -> S?
): LiveData<S> {
val result = MediatorLiveData<S>()
result.addSource(source1) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source2) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source3) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source4) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source5) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source6) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source7) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source8) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
return result
}

View file

@ -1,414 +0,0 @@
package net.mynero.wallet.livedata
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
fun <T1, T2, S> combineLatest(
source1: LiveData<T1>,
source2: LiveData<T2>,
func: (T1?, T2?) -> S?
): LiveData<S> {
val result = MediatorLiveData<S>()
result.addSource(source1) {
result.value = func.invoke(source1.value, source2.value)
}
result.addSource(source2) {
result.value = func.invoke(source1.value, source2.value)
}
return result
}
fun <T1, T2, T3, S> combineLatest(
source1: LiveData<T1>,
source2: LiveData<T2>,
source3: LiveData<T3>,
func: (T1?, T2?, T3?) -> S?
): LiveData<S> {
val result = MediatorLiveData<S>()
result.addSource(source1) {
result.value = func.invoke(source1.value, source2.value, source3.value)
}
result.addSource(source2) {
result.value = func.invoke(source1.value, source2.value, source3.value)
}
result.addSource(source3) {
result.value = func.invoke(source1.value, source2.value, source3.value)
}
return result
}
fun <T1, T2, T3, T4, S> combineLatest(
source1: LiveData<T1>,
source2: LiveData<T2>,
source3: LiveData<T3>,
source4: LiveData<T4>,
func: (T1?, T2?, T3?, T4?) -> S?
): LiveData<S> {
val result = MediatorLiveData<S>()
result.addSource(source1) {
result.value = func.invoke(source1.value, source2.value, source3.value, source4.value)
}
result.addSource(source2) {
result.value = func.invoke(source1.value, source2.value, source3.value, source4.value)
}
result.addSource(source3) {
result.value = func.invoke(source1.value, source2.value, source3.value, source4.value)
}
result.addSource(source4) {
result.value = func.invoke(source1.value, source2.value, source3.value, source4.value)
}
return result
}
fun <T1, T2, T3, T4, T5, S> combineLatest(
source1: LiveData<T1>,
source2: LiveData<T2>,
source3: LiveData<T3>,
source4: LiveData<T4>,
source5: LiveData<T5>,
func: (T1?, T2?, T3?, T4?, T5?) -> S?
): LiveData<S> {
val result = MediatorLiveData<S>()
result.addSource(source1) {
result.value = func.invoke(source1.value, source2.value, source3.value, source4.value, source5.value)
}
result.addSource(source2) {
result.value = func.invoke(source1.value, source2.value, source3.value, source4.value, source5.value)
}
result.addSource(source3) {
result.value = func.invoke(source1.value, source2.value, source3.value, source4.value, source5.value)
}
result.addSource(source4) {
result.value = func.invoke(source1.value, source2.value, source3.value, source4.value, source5.value)
}
result.addSource(source5) {
result.value = func.invoke(source1.value, source2.value, source3.value, source4.value, source5.value)
}
return result
}
fun <T1, T2, S> combineLatestIgnoreNull(
source1: LiveData<T1>,
source2: LiveData<T2>,
func: (T1?, T2?) -> S?
): LiveData<S> {
val result = MediatorLiveData<S>()
result.addSource(source1) {
func.invoke(source1.value, source2.value)?.run { result.value = this }
}
result.addSource(source2) {
func.invoke(source1.value, source2.value)?.run { result.value = this }
}
return result
}
fun <T1, T2, T3, S> combineLatestIgnoreNull(
source1: LiveData<T1>,
source2: LiveData<T2>,
source3: LiveData<T3>,
func: (T1?, T2?, T3?) -> S?
): LiveData<S> {
val result = MediatorLiveData<S>()
result.addSource(source1) {
func(source1.value, source2.value, source3.value)?.run { result.value = this }
}
result.addSource(source2) {
func(source1.value, source2.value, source3.value)?.run { result.value = this }
}
result.addSource(source3) {
func(source1.value, source2.value, source3.value)?.run { result.value = this }
}
return result
}
fun <T1, T2, T3, T4, S> combineLatestIgnoreNull(
source1: LiveData<T1>,
source2: LiveData<T2>,
source3: LiveData<T3>,
source4: LiveData<T4>,
func: (T1?, T2?, T3?, T4?) -> S?
): LiveData<S> {
val result = MediatorLiveData<S>()
result.addSource(source1) {
func(source1.value, source2.value, source3.value, source4.value)?.run { result.value = this }
}
result.addSource(source2) {
func(source1.value, source2.value, source3.value, source4.value)?.run { result.value = this }
}
result.addSource(source3) {
func(source1.value, source2.value, source3.value, source4.value)?.run { result.value = this }
}
result.addSource(source4) {
func(source1.value, source2.value, source3.value, source4.value)?.run { result.value = this }
}
return result
}
fun <T1, T2, T3, T4, T5, S> combineLatestIgnoreNull(
source1: LiveData<T1>,
source2: LiveData<T2>,
source3: LiveData<T3>,
source4: LiveData<T4>,
source5: LiveData<T5>,
func: (T1?, T2?, T3?, T4?, T5?) -> S?
): LiveData<S> {
val result = MediatorLiveData<S>()
result.addSource(source1) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value
)?.run { result.value = this }
}
result.addSource(source2) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value
)?.run { result.value = this }
}
result.addSource(source3) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value
)?.run { result.value = this }
}
result.addSource(source4) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value
)?.run { result.value = this }
}
result.addSource(source5) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value
)?.run { result.value = this }
}
return result
}
fun <T1, T2, T3, T4, T5, T6, S> combineLatestIgnoreNull(
source1: LiveData<T1>,
source2: LiveData<T2>,
source3: LiveData<T3>,
source4: LiveData<T4>,
source5: LiveData<T5>,
source6: LiveData<T6>,
func: (T1?, T2?, T3?, T4?, T5?, T6?) -> S?
): LiveData<S> {
val result = MediatorLiveData<S>()
result.addSource(source1) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value
)?.run { result.value = this }
}
result.addSource(source2) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value
)?.run { result.value = this }
}
result.addSource(source3) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value
)?.run { result.value = this }
}
result.addSource(source4) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value
)?.run { result.value = this }
}
result.addSource(source5) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value
)?.run { result.value = this }
}
result.addSource(source6) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value
)?.run { result.value = this }
}
return result
}
fun <T1, T2, T3, T4, T5, T6, T7, T8, S> combineLatestIgnoreNull(
source1: LiveData<T1>,
source2: LiveData<T2>,
source3: LiveData<T3>,
source4: LiveData<T4>,
source5: LiveData<T5>,
source6: LiveData<T6>,
source7: LiveData<T7>,
source8: LiveData<T8>,
func: (T1?, T2?, T3?, T4?, T5?, T6?, T7?, T8?) -> S?
): LiveData<S> {
val result = MediatorLiveData<S>()
result.addSource(source1) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source2) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source3) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source4) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source5) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source6) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source7) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
result.addSource(source8) {
func(
source1.value,
source2.value,
source3.value,
source4.value,
source5.value,
source6.value,
source7.value,
source8.value
)?.run { result.value = this }
}
return result
}

View file

@ -17,10 +17,8 @@ class ProxyService(activity: MainActivity) : ServiceBase(null) {
activity.runOnUiThread {
samouraiTorManager?.getTorStateLiveData()?.observeForever {
println("STATE CHANGE:: ${it.state.name}")
samouraiTorManager?.getProxy()?.address()?.let { socketAddress ->
if(socketAddress.toString().isEmpty()) return@let
println("PROXY INIT")
val proxyString = socketAddress.toString().substring(1)
val address = proxyString.split(":")[0]
val port = proxyString.split(":")[1]

View file

@ -23,17 +23,38 @@
android:paddingTop="24dp">
<TextView
android:id="@+id/create_wallet_textview"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="@string/create_wallet"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintBottom_toTopOf="@id/wallet_password_edittext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/onboarding_tor_loading_progressindicator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/onboarding_tor_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/tor"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="@id/onboarding_tor_loading_progressindicator"
app:layout_constraintStart_toStartOf="@id/onboarding_tor_loading_progressindicator"
app:layout_constraintTop_toTopOf="@id/onboarding_tor_loading_progressindicator"
app:layout_constraintBottom_toBottomOf="@id/onboarding_tor_loading_progressindicator"/>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/onboarding_tor_loading_progressindicator"
android:layout_width="32dp"
android:layout_height="32dp"
android:indeterminate="true"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/create_wallet_textview"
app:layout_constraintBottom_toBottomOf="@id/create_wallet_textview"/>
<EditText
android:id="@+id/wallet_password_edittext"
android:layout_width="0dp"
@ -248,7 +269,6 @@
android:layout_height="wrap_content"
android:minHeight="48dp"
android:text="@string/use_bundled_tor"
android:visibility="gone"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/wallet_proxy_address_edittext"
@ -260,7 +280,6 @@
android:background="@drawable/edittext_bg"
android:hint="@string/wallet_proxy_address_hint"
android:minHeight="48dp"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/wallet_proxy_port_edittext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
@ -275,7 +294,6 @@
android:layout_marginTop="8dp"
android:inputType="number"
android:minHeight="48dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View file

@ -19,9 +19,29 @@
android:layout_marginEnd="24dp"
android:textSize="32sp"
android:textStyle="bold"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/settings_tor_loading_progressindicator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/settings_tor_icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:src="@drawable/tor"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="@id/settings_tor_loading_progressindicator"
app:layout_constraintStart_toStartOf="@id/settings_tor_loading_progressindicator"
app:layout_constraintTop_toTopOf="@id/settings_tor_loading_progressindicator"
app:layout_constraintBottom_toBottomOf="@id/settings_tor_loading_progressindicator"/>
<com.google.android.material.progressindicator.CircularProgressIndicator
android:id="@+id/settings_tor_loading_progressindicator"
android:layout_width="32dp"
android:layout_height="32dp"
android:indeterminate="true"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@id/settings_textview"
app:layout_constraintBottom_toBottomOf="@id/settings_textview"/>
<TextView
android:id="@+id/wallet_settings_textview"