mirror of
https://codeberg.org/r4v3r23/mysu.git
synced 2024-11-25 07:12:26 +00:00
WIP: Embedded Tor
This commit is contained in:
parent
dc178f10d9
commit
3b77ae3673
26 changed files with 1236 additions and 249 deletions
|
@ -138,6 +138,13 @@ dependencies {
|
||||||
implementation 'androidx.core:core-ktx:1.12.0'
|
implementation 'androidx.core:core-ktx:1.12.0'
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
|
||||||
|
// Tor
|
||||||
|
def vTor = '4.8.6-0'
|
||||||
|
def vKmpTor = '1.4.4'
|
||||||
|
|
||||||
|
implementation 'io.samourai.code.wallet:android-tor-binary:0.4.7.12'
|
||||||
|
implementation "io.matthewnelson.kotlin-components:kmp-tor:$vTor-$vKmpTor"
|
||||||
|
|
||||||
//noinspection GradleDependency
|
//noinspection GradleDependency
|
||||||
testImplementation "junit:junit:4.13.2"
|
testImplementation "junit:junit:4.13.2"
|
||||||
testImplementation "org.mockito:mockito-all:1.10.19"
|
testImplementation "org.mockito:mockito-all:1.10.19"
|
||||||
|
|
|
@ -1080,7 +1080,7 @@ Java_net_mynero_wallet_model_Wallet_getMaximumAllowedAmount(JNIEnv *env, jclass
|
||||||
}
|
}
|
||||||
|
|
||||||
JNIEXPORT void JNICALL
|
JNIEXPORT void JNICALL
|
||||||
Java_net_mynero_wallet_model_Wallet_startRefresh(JNIEnv *env, jobject instance) {
|
Java_net_mynero_wallet_model_Wallet_startRefreshJ(JNIEnv *env, jobject instance) {
|
||||||
Monero::Wallet *wallet = getHandle<Monero::Wallet>(env, instance);
|
Monero::Wallet *wallet = getHandle<Monero::Wallet>(env, instance);
|
||||||
wallet->startRefresh();
|
wallet->startRefresh();
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,6 +45,7 @@ class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, Password
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_main)
|
setContentView(R.layout.activity_main)
|
||||||
|
ProxyService(this)
|
||||||
val walletFile = File(applicationInfo.dataDir, Constants.WALLET_NAME)
|
val walletFile = File(applicationInfo.dataDir, Constants.WALLET_NAME)
|
||||||
val walletKeysFile = File(applicationInfo.dataDir, Constants.WALLET_NAME + ".keys")
|
val walletKeysFile = File(applicationInfo.dataDir, Constants.WALLET_NAME + ".keys")
|
||||||
if (walletKeysFile.exists()) {
|
if (walletKeysFile.exists()) {
|
||||||
|
@ -87,7 +88,6 @@ class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, Password
|
||||||
historyService = HistoryService(thread)
|
historyService = HistoryService(thread)
|
||||||
blockchainService = BlockchainService(thread)
|
blockchainService = BlockchainService(thread)
|
||||||
daemonService = DaemonService(thread)
|
daemonService = DaemonService(thread)
|
||||||
proxyService = ProxyService(thread)
|
|
||||||
utxoService = UTXOService(thread)
|
utxoService = UTXOService(thread)
|
||||||
thread.start()
|
thread.start()
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ import android.widget.ProgressBar
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.NavDirections
|
import androidx.navigation.NavDirections
|
||||||
|
@ -30,6 +31,8 @@ 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.service.ProxyService
|
||||||
|
import net.mynero.wallet.service.SamouraiTorManager
|
||||||
|
import net.mynero.wallet.service.TorKmpManager
|
||||||
import net.mynero.wallet.util.Constants
|
import net.mynero.wallet.util.Constants
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
|
|
@ -18,8 +18,12 @@ 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
|
||||||
|
import androidx.core.widget.addTextChangedListener
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.lifecycle.ViewModelProvider
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import net.mynero.wallet.MoneroApplication
|
import net.mynero.wallet.MoneroApplication
|
||||||
import net.mynero.wallet.R
|
import net.mynero.wallet.R
|
||||||
import net.mynero.wallet.data.Node
|
import net.mynero.wallet.data.Node
|
||||||
|
@ -35,26 +39,6 @@ import net.mynero.wallet.util.Constants
|
||||||
class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListener {
|
class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListener {
|
||||||
private var useOffset = true
|
private var useOffset = true
|
||||||
private var mViewModel: OnboardingViewModel? = null
|
private var mViewModel: OnboardingViewModel? = 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 walletPasswordEditText: EditText? = null
|
private var walletPasswordEditText: EditText? = null
|
||||||
|
@ -113,7 +97,7 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
||||||
advancedOptionsLayout?.visibility = View.GONE
|
advancedOptionsLayout?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mViewModel?.enableCreateButton?.observe(viewLifecycleOwner) { enable: Boolean ->
|
mViewModel?.enableButton?.observe(viewLifecycleOwner) { enable: Boolean ->
|
||||||
createWalletButton?.isEnabled = enable
|
createWalletButton?.isEnabled = enable
|
||||||
}
|
}
|
||||||
mViewModel?.seedType?.observe(viewLifecycleOwner) { seedType: SeedType ->
|
mViewModel?.seedType?.observe(viewLifecycleOwner) { seedType: SeedType ->
|
||||||
|
@ -131,9 +115,27 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
||||||
walletSeedEditText?.hint = getString(R.string.recovery_phrase_optional_polyseed)
|
walletSeedEditText?.hint = getString(R.string.recovery_phrase_optional_polyseed)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val samouraiTorManager = ProxyService.instance?.samouraiTorManager
|
||||||
|
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]
|
||||||
|
if(mViewModel?.useProxy?.value == true && mViewModel?.useBundledTor?.value == true) {
|
||||||
|
mViewModel?.setProxyAddress(address)
|
||||||
|
mViewModel?.setProxyPort(port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun bindListeners() {
|
private fun bindListeners() {
|
||||||
|
val useBundledTor = view?.findViewById<CheckBox>(R.id.bundled_tor_checkbox)
|
||||||
|
|
||||||
seedOffsetCheckbox?.isChecked = useOffset
|
seedOffsetCheckbox?.isChecked = useOffset
|
||||||
// Disable onBack click
|
// Disable onBack click
|
||||||
val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
|
val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||||
|
@ -147,12 +149,9 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
||||||
useOffset = b
|
useOffset = b
|
||||||
}
|
}
|
||||||
createWalletButton?.setOnClickListener {
|
createWalletButton?.setOnClickListener {
|
||||||
prepareDefaultNode()
|
|
||||||
onBackPressedCallback.isEnabled = false
|
onBackPressedCallback.isEnabled = false
|
||||||
(getActivity()?.application as MoneroApplication).executor?.execute {
|
(getActivity()?.application as MoneroApplication).executor?.execute {
|
||||||
createOrImportWallet(
|
createOrImportWallet(
|
||||||
walletPasswordEditText?.text.toString(),
|
|
||||||
walletPasswordConfirmEditText?.text.toString(),
|
|
||||||
walletSeedEditText?.text.toString().trim { it <= ' ' },
|
walletSeedEditText?.text.toString().trim { it <= ' ' },
|
||||||
walletRestoreHeightEditText?.text.toString().trim { it <= ' ' }
|
walletRestoreHeightEditText?.text.toString().trim { it <= ' ' }
|
||||||
)
|
)
|
||||||
|
@ -163,6 +162,7 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
||||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
override fun afterTextChanged(editable: Editable) {
|
override fun afterTextChanged(editable: Editable) {
|
||||||
val text = editable.toString()
|
val text = editable.toString()
|
||||||
|
mViewModel?.setPassphrase(text)
|
||||||
if (text.isEmpty()) {
|
if (text.isEmpty()) {
|
||||||
walletPasswordConfirmEditText?.text = null
|
walletPasswordConfirmEditText?.text = null
|
||||||
walletPasswordConfirmEditText?.visibility = View.GONE
|
walletPasswordConfirmEditText?.visibility = View.GONE
|
||||||
|
@ -171,6 +171,15 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
walletPasswordConfirmEditText?.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?.setConfirmedPassphrase(text)
|
||||||
|
}
|
||||||
|
})
|
||||||
walletSeedEditText?.addTextChangedListener(object : TextWatcher {
|
walletSeedEditText?.addTextChangedListener(object : TextWatcher {
|
||||||
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
|
||||||
|
@ -185,26 +194,35 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
||||||
})
|
})
|
||||||
seedTypeButton?.setOnClickListener { toggleSeedType() }
|
seedTypeButton?.setOnClickListener { toggleSeedType() }
|
||||||
torSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
|
torSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
|
||||||
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_TOR, b)?.apply()
|
|
||||||
removeProxyTextListeners()
|
|
||||||
if (b) {
|
if (b) {
|
||||||
if (ProxyService.instance?.hasProxySet() == true) {
|
useBundledTor?.visibility = View.VISIBLE
|
||||||
val proxyAddress =
|
walletProxyAddressEditText?.visibility = View.VISIBLE
|
||||||
ProxyService.instance?.proxyAddress ?: return@setOnCheckedChangeListener
|
walletProxyPortEditText?.visibility = View.VISIBLE
|
||||||
val proxyPort =
|
|
||||||
ProxyService.instance?.proxyPort ?: return@setOnCheckedChangeListener
|
|
||||||
initProxyStuff(proxyAddress, proxyPort)
|
|
||||||
} else {
|
} else {
|
||||||
initProxyStuff("127.0.0.1", "9050")
|
useBundledTor?.visibility = View.GONE
|
||||||
|
walletProxyAddressEditText?.visibility = View.GONE
|
||||||
|
walletProxyPortEditText?.visibility = View.GONE
|
||||||
}
|
}
|
||||||
addProxyTextListeners()
|
mViewModel?.setUseProxy(b)
|
||||||
}
|
}
|
||||||
mViewModel?.updateProxy(getActivity()?.application as MoneroApplication)
|
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) {}
|
||||||
|
override fun afterTextChanged(editable: Editable) {
|
||||||
|
val text = editable.toString()
|
||||||
|
mViewModel?.setProxyPort(text)
|
||||||
}
|
}
|
||||||
showXmrchanSwitch?.isChecked =
|
})
|
||||||
PrefService.instance?.getBoolean(Constants.PREF_MONEROCHAN, true) == true
|
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) {}
|
||||||
|
override fun afterTextChanged(editable: Editable) {
|
||||||
|
val text = editable.toString()
|
||||||
|
mViewModel?.setProxyAddress(text)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
showXmrchanSwitch?.isChecked = true
|
||||||
showXmrchanSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
|
showXmrchanSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
|
||||||
PrefService.instance?.edit()?.putBoolean(Constants.PREF_MONEROCHAN, b)?.apply()
|
|
||||||
if (b) {
|
if (b) {
|
||||||
xmrchanOnboardingImage?.visibility = View.VISIBLE
|
xmrchanOnboardingImage?.visibility = View.VISIBLE
|
||||||
} else {
|
} else {
|
||||||
|
@ -220,6 +238,21 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
||||||
dialog.show(fragmentManager, "node_selection_dialog")
|
dialog.show(fragmentManager, "node_selection_dialog")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
mViewModel?.setProxyAddress("")
|
||||||
|
mViewModel?.setProxyPort("")
|
||||||
|
}
|
||||||
|
|
||||||
|
mViewModel?.setUseBundledTor(isChecked)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleSeedType() {
|
private fun toggleSeedType() {
|
||||||
|
@ -233,47 +266,21 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
||||||
mViewModel?.setSeedType(newSeedType)
|
mViewModel?.setSeedType(newSeedType)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun prepareDefaultNode() {
|
|
||||||
PrefService.instance?.node
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createOrImportWallet(
|
private fun createOrImportWallet(
|
||||||
walletPassword: String,
|
|
||||||
confirmedPassword: String,
|
|
||||||
walletSeed: String,
|
walletSeed: String,
|
||||||
restoreHeightText: String
|
restoreHeightText: String
|
||||||
) {
|
) {
|
||||||
val activity: Activity? = activity
|
val activity: Activity? = activity
|
||||||
if (activity != null) {
|
if (activity != null) {
|
||||||
|
lifecycleScope.launch(Dispatchers.IO) {
|
||||||
mViewModel?.createOrImportWallet(
|
mViewModel?.createOrImportWallet(
|
||||||
activity,
|
activity,
|
||||||
walletPassword,
|
|
||||||
confirmedPassword,
|
|
||||||
walletSeed,
|
walletSeed,
|
||||||
restoreHeightText,
|
restoreHeightText,
|
||||||
useOffset
|
useOffset
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun removeProxyTextListeners() {
|
|
||||||
walletProxyAddressEditText?.removeTextChangedListener(proxyAddressListener)
|
|
||||||
walletProxyPortEditText?.removeTextChangedListener(proxyPortListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addProxyTextListeners() {
|
|
||||||
walletProxyAddressEditText?.addTextChangedListener(proxyAddressListener)
|
|
||||||
walletProxyPortEditText?.addTextChangedListener(proxyPortListener)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initProxyStuff(proxyAddress: String, proxyPort: String) {
|
|
||||||
val validIpAddress = Patterns.IP_ADDRESS.matcher(proxyAddress).matches()
|
|
||||||
if (validIpAddress) {
|
|
||||||
mViewModel?.setProxyAddress(proxyAddress)
|
|
||||||
mViewModel?.setProxyPort(proxyPort)
|
|
||||||
walletProxyAddressEditText?.setText(proxyAddress)
|
|
||||||
walletProxyPortEditText?.setText(proxyPort)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onNodeSelected() {
|
override fun onNodeSelected() {
|
||||||
|
@ -284,7 +291,7 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
||||||
getString(R.string.node_selected),
|
getString(R.string.node_selected),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
mViewModel?.updateProxy(activity?.application as MoneroApplication)
|
refreshProxy()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClickedEditNode(node: Node?) {}
|
override fun onClickedEditNode(node: Node?) {}
|
||||||
|
@ -303,4 +310,10 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
||||||
dialog.show(fragmentManager, "node_selection_dialog")
|
dialog.show(fragmentManager, "node_selection_dialog")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun refreshProxy() {
|
||||||
|
val proxyAddress = walletProxyAddressEditText?.text.toString()
|
||||||
|
val proxyPort = walletProxyPortEditText?.text.toString()
|
||||||
|
ProxyService.instance?.updateProxy(proxyAddress, proxyPort)
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -9,9 +9,11 @@ import androidx.lifecycle.ViewModel
|
||||||
import net.mynero.wallet.MainActivity
|
import net.mynero.wallet.MainActivity
|
||||||
import net.mynero.wallet.MoneroApplication
|
import net.mynero.wallet.MoneroApplication
|
||||||
import net.mynero.wallet.R
|
import net.mynero.wallet.R
|
||||||
|
import net.mynero.wallet.livedata.combineLatestIgnoreNull
|
||||||
import net.mynero.wallet.model.Wallet
|
import net.mynero.wallet.model.Wallet
|
||||||
import net.mynero.wallet.model.WalletManager
|
import net.mynero.wallet.model.WalletManager
|
||||||
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 net.mynero.wallet.util.RestoreHeight
|
import net.mynero.wallet.util.RestoreHeight
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
@ -19,62 +21,65 @@ import java.util.Calendar
|
||||||
|
|
||||||
class OnboardingViewModel : ViewModel() {
|
class OnboardingViewModel : ViewModel() {
|
||||||
private val _showMoreOptions = MutableLiveData(false)
|
private val _showMoreOptions = MutableLiveData(false)
|
||||||
private val _enableCreateButton = MutableLiveData(true)
|
private val _creatingWallet = MutableLiveData(false)
|
||||||
private val _seedType = MutableLiveData(SeedType.POLYSEED)
|
private val _seedType = MutableLiveData(SeedType.POLYSEED)
|
||||||
|
private val _useProxy = MutableLiveData(false)
|
||||||
|
val useProxy: LiveData<Boolean> = _useProxy
|
||||||
|
private val _proxyAddress = MutableLiveData("")
|
||||||
|
private val _proxyPort = MutableLiveData("")
|
||||||
|
private val _useBundledTor = MutableLiveData(false)
|
||||||
|
val useBundledTor: LiveData<Boolean> = _useBundledTor
|
||||||
|
private val _passphrase = MutableLiveData("")
|
||||||
|
private val _confirmedPassphrase = MutableLiveData("")
|
||||||
var showMoreOptions: LiveData<Boolean> = _showMoreOptions
|
var showMoreOptions: LiveData<Boolean> = _showMoreOptions
|
||||||
var enableCreateButton: LiveData<Boolean> = _enableCreateButton
|
|
||||||
var seedType: LiveData<SeedType> = _seedType
|
var seedType: LiveData<SeedType> = _seedType
|
||||||
private var proxyAddress = ""
|
|
||||||
private var proxyPort = ""
|
val enableButton = combineLatestIgnoreNull(
|
||||||
|
seedType,
|
||||||
|
_useProxy,
|
||||||
|
_proxyAddress,
|
||||||
|
_proxyPort,
|
||||||
|
_useBundledTor,
|
||||||
|
_passphrase,
|
||||||
|
_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
|
||||||
|
|
||||||
|
return@combineLatestIgnoreNull true
|
||||||
|
}
|
||||||
|
|
||||||
fun onMoreOptionsClicked() {
|
fun onMoreOptionsClicked() {
|
||||||
val currentValue = showMoreOptions.value ?: false
|
val currentValue = showMoreOptions.value ?: false
|
||||||
val newValue = !currentValue
|
val newValue = !currentValue
|
||||||
_showMoreOptions.value = newValue
|
_showMoreOptions.value = newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateProxy(application: MoneroApplication) {
|
|
||||||
application.executor?.execute {
|
|
||||||
val usesProxy = PrefService.instance?.getBoolean(Constants.PREF_USES_TOR, false) == true
|
|
||||||
if (!usesProxy) {
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setSeedType(seedType: SeedType?) {
|
fun setSeedType(seedType: SeedType?) {
|
||||||
_seedType.value = seedType
|
_seedType.value = seedType
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setProxyAddress(address: String) {
|
|
||||||
proxyAddress = address
|
|
||||||
}
|
|
||||||
|
|
||||||
fun setProxyPort(port: String) {
|
|
||||||
proxyPort = port
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createOrImportWallet(
|
fun createOrImportWallet(
|
||||||
mainActivity: Activity,
|
mainActivity: Activity,
|
||||||
walletPassword: String,
|
|
||||||
confirmedPassword: String,
|
|
||||||
walletSeed: String,
|
walletSeed: String,
|
||||||
restoreHeightText: String,
|
restoreHeightText: String,
|
||||||
useOffset: Boolean
|
useOffset: Boolean
|
||||||
) {
|
) {
|
||||||
|
val passphrase = _passphrase.value ?: return
|
||||||
|
val confirmedPassphrase = _confirmedPassphrase.value ?: return
|
||||||
|
val useProxy = _useProxy.value ?: return
|
||||||
|
|
||||||
val application = mainActivity.application as MoneroApplication
|
val application = mainActivity.application as MoneroApplication
|
||||||
application.executor?.execute {
|
_creatingWallet.postValue(true)
|
||||||
_enableCreateButton.postValue(false)
|
val offset = if (useOffset) confirmedPassphrase else ""
|
||||||
val offset = if (useOffset) walletPassword else ""
|
if (passphrase.isNotEmpty()) {
|
||||||
if (walletPassword.isNotEmpty()) {
|
if (passphrase != confirmedPassphrase) {
|
||||||
if (walletPassword != confirmedPassword) {
|
_creatingWallet.postValue(false)
|
||||||
_enableCreateButton.postValue(true)
|
|
||||||
mainActivity.runOnUiThread {
|
mainActivity.runOnUiThread {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
mainActivity,
|
mainActivity,
|
||||||
|
@ -82,7 +87,7 @@ class OnboardingViewModel : ViewModel() {
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
return@execute
|
return
|
||||||
}
|
}
|
||||||
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_PASSWORD, true)
|
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_PASSWORD, true)
|
||||||
?.apply()
|
?.apply()
|
||||||
|
@ -93,23 +98,23 @@ class OnboardingViewModel : ViewModel() {
|
||||||
if (offset.isNotEmpty()) {
|
if (offset.isNotEmpty()) {
|
||||||
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_OFFSET, true)?.apply()
|
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_OFFSET, true)?.apply()
|
||||||
}
|
}
|
||||||
val seedTypeValue = seedType.value ?: return@execute
|
val seedTypeValue = seedType.value ?: return
|
||||||
if (walletSeed.isEmpty()) {
|
if (walletSeed.isEmpty()) {
|
||||||
if (seedTypeValue == SeedType.POLYSEED) {
|
if (seedTypeValue == SeedType.POLYSEED) {
|
||||||
wallet = if (offset.isEmpty()) {
|
wallet = if (offset.isEmpty()) {
|
||||||
mainActivity.runOnUiThread {
|
mainActivity.runOnUiThread {
|
||||||
_enableCreateButton.postValue(true)
|
_creatingWallet.postValue(false)
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
mainActivity,
|
mainActivity,
|
||||||
application.getString(R.string.invalid_empty_passphrase),
|
application.getString(R.string.invalid_empty_passphrase),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
return@execute
|
return
|
||||||
} else {
|
} else {
|
||||||
WalletManager.instance?.createWalletPolyseed(
|
WalletManager.instance?.createWalletPolyseed(
|
||||||
walletFile,
|
walletFile,
|
||||||
walletPassword,
|
passphrase,
|
||||||
offset,
|
offset,
|
||||||
Constants.MNEMONIC_LANGUAGE
|
Constants.MNEMONIC_LANGUAGE
|
||||||
)
|
)
|
||||||
|
@ -122,7 +127,7 @@ class OnboardingViewModel : ViewModel() {
|
||||||
tmpWallet?.let {
|
tmpWallet?.let {
|
||||||
wallet = WalletManager.instance?.recoveryWallet(
|
wallet = WalletManager.instance?.recoveryWallet(
|
||||||
walletFile,
|
walletFile,
|
||||||
walletPassword,
|
passphrase,
|
||||||
tmpWallet.getSeed("") ?: return@let,
|
tmpWallet.getSeed("") ?: return@let,
|
||||||
offset,
|
offset,
|
||||||
restoreHeight
|
restoreHeight
|
||||||
|
@ -133,14 +138,14 @@ class OnboardingViewModel : ViewModel() {
|
||||||
} else {
|
} else {
|
||||||
if (getMnemonicType(walletSeed) == SeedType.UNKNOWN) {
|
if (getMnemonicType(walletSeed) == SeedType.UNKNOWN) {
|
||||||
mainActivity.runOnUiThread {
|
mainActivity.runOnUiThread {
|
||||||
_enableCreateButton.postValue(true)
|
_creatingWallet.postValue(false)
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
mainActivity,
|
mainActivity,
|
||||||
application.getString(R.string.invalid_mnemonic_code),
|
application.getString(R.string.invalid_mnemonic_code),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
return@execute
|
return
|
||||||
}
|
}
|
||||||
if (restoreHeightText.isNotEmpty()) {
|
if (restoreHeightText.isNotEmpty()) {
|
||||||
restoreHeight = restoreHeightText.toLong()
|
restoreHeight = restoreHeightText.toLong()
|
||||||
|
@ -148,14 +153,14 @@ class OnboardingViewModel : ViewModel() {
|
||||||
if (seedTypeValue == SeedType.POLYSEED) {
|
if (seedTypeValue == SeedType.POLYSEED) {
|
||||||
wallet = WalletManager.instance?.recoveryWalletPolyseed(
|
wallet = WalletManager.instance?.recoveryWalletPolyseed(
|
||||||
walletFile,
|
walletFile,
|
||||||
walletPassword,
|
passphrase,
|
||||||
walletSeed,
|
walletSeed,
|
||||||
offset
|
offset
|
||||||
)
|
)
|
||||||
} else if (seedTypeValue == SeedType.LEGACY) {
|
} else if (seedTypeValue == SeedType.LEGACY) {
|
||||||
wallet = WalletManager.instance?.recoveryWallet(
|
wallet = WalletManager.instance?.recoveryWallet(
|
||||||
walletFile,
|
walletFile,
|
||||||
walletPassword,
|
passphrase,
|
||||||
walletSeed,
|
walletSeed,
|
||||||
offset,
|
offset,
|
||||||
restoreHeight
|
restoreHeight
|
||||||
|
@ -167,11 +172,20 @@ class OnboardingViewModel : ViewModel() {
|
||||||
val ok = walletStatus?.isOk
|
val ok = walletStatus?.isOk
|
||||||
walletFile.delete() // cache is broken for some reason when recovering wallets. delete the file here. this happens in monerujo too.
|
walletFile.delete() // cache is broken for some reason when recovering wallets. delete the file here. this happens in monerujo too.
|
||||||
if (ok == true) {
|
if (ok == true) {
|
||||||
(mainActivity as MainActivity).init(walletFile, walletPassword)
|
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() }
|
mainActivity.runOnUiThread { mainActivity.onBackPressed() }
|
||||||
} else {
|
} else {
|
||||||
mainActivity.runOnUiThread {
|
mainActivity.runOnUiThread {
|
||||||
_enableCreateButton.postValue(true)
|
_creatingWallet.postValue(false)
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
mainActivity,
|
mainActivity,
|
||||||
application.getString(
|
application.getString(
|
||||||
|
@ -183,7 +197,6 @@ class OnboardingViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private val newRestoreHeight: Long
|
private val newRestoreHeight: Long
|
||||||
get() {
|
get() {
|
||||||
|
@ -213,6 +226,30 @@ class OnboardingViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setProxyAddress(address: String) {
|
||||||
|
_proxyAddress.value = address
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setProxyPort(port: String) {
|
||||||
|
_proxyPort.value = port
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setUseBundledTor(useBundled: Boolean) {
|
||||||
|
_useBundledTor.value = useBundled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setUseProxy(useProxy: Boolean) {
|
||||||
|
_useProxy.value = useProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPassphrase(passphrase: String) {
|
||||||
|
_passphrase.value = passphrase
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setConfirmedPassphrase(confirmedPassphrase: String) {
|
||||||
|
_confirmedPassphrase.value = confirmedPassphrase
|
||||||
|
}
|
||||||
|
|
||||||
enum class SeedType(val descResId: Int) {
|
enum class SeedType(val descResId: Int) {
|
||||||
LEGACY(R.string.seed_desc_legacy), POLYSEED(R.string.seed_desc_polyseed), UNKNOWN(0)
|
LEGACY(R.string.seed_desc_legacy), POLYSEED(R.string.seed_desc_polyseed), UNKNOWN(0)
|
||||||
|
|
||||||
|
|
|
@ -6,21 +6,17 @@ 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.CompoundButton
|
import android.widget.CompoundButton
|
||||||
import android.widget.EditText
|
import android.widget.EditText
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
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
|
||||||
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 androidx.navigation.fragment.findNavController
|
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
|
||||||
|
@ -33,17 +29,12 @@ import net.mynero.wallet.fragment.dialog.NodeSelectionBottomSheetDialog.NodeSele
|
||||||
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.WalletKeysBottomSheetDialog
|
import net.mynero.wallet.fragment.dialog.WalletKeysBottomSheetDialog
|
||||||
import net.mynero.wallet.model.Wallet.ConnectionStatus
|
|
||||||
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.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.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 {
|
||||||
|
@ -75,6 +66,12 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
|
||||||
view.findViewById<ConstraintLayout>(R.id.wallet_proxy_settings_layout)
|
view.findViewById<ConstraintLayout>(R.id.wallet_proxy_settings_layout)
|
||||||
walletProxyAddressEditText = view.findViewById(R.id.wallet_proxy_address_edittext)
|
walletProxyAddressEditText = view.findViewById(R.id.wallet_proxy_address_edittext)
|
||||||
walletProxyPortEditText = view.findViewById(R.id.wallet_proxy_port_edittext)
|
walletProxyPortEditText = view.findViewById(R.id.wallet_proxy_port_edittext)
|
||||||
|
val useBundledTor = view.findViewById<CheckBox>(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
|
||||||
|
|
||||||
streetModeSwitch.isChecked =
|
streetModeSwitch.isChecked =
|
||||||
PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false) == true
|
PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false) == true
|
||||||
streetModeSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
|
streetModeSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
|
||||||
|
@ -93,7 +90,7 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
|
||||||
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 ?: return
|
val prefService = PrefService.instance ?: return
|
||||||
val usesProxy = prefService.getBoolean(Constants.PREF_USES_TOR, false)
|
val usesProxy = ProxyService.instance?.usingProxy == true
|
||||||
cachedProxyAddress = ProxyService.instance?.proxyAddress ?: return
|
cachedProxyAddress = ProxyService.instance?.proxyAddress ?: return
|
||||||
cachedProxyPort = ProxyService.instance?.proxyPort ?: return
|
cachedProxyPort = ProxyService.instance?.proxyPort ?: return
|
||||||
if (ProxyService.instance?.hasProxySet() == true) {
|
if (ProxyService.instance?.hasProxySet() == true) {
|
||||||
|
@ -106,7 +103,7 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
|
||||||
proxySettingsLayout.visibility = View.GONE
|
proxySettingsLayout.visibility = View.GONE
|
||||||
}
|
}
|
||||||
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_PROXY, b)?.apply()
|
||||||
if (b) {
|
if (b) {
|
||||||
if (ProxyService.instance?.hasProxySet() == true) {
|
if (ProxyService.instance?.hasProxySet() == true) {
|
||||||
val proxyAddress = ProxyService.instance?.proxyAddress ?: return@setOnCheckedChangeListener
|
val proxyAddress = ProxyService.instance?.proxyAddress ?: return@setOnCheckedChangeListener
|
||||||
|
@ -144,6 +141,18 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useBundledTor?.setOnCheckedChangeListener { _, 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) {
|
val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
refreshProxy()
|
refreshProxy()
|
||||||
|
|
414
app/src/main/java/net/mynero/wallet/livedata/LiveData.kt
Normal file
414
app/src/main/java/net/mynero/wallet/livedata/LiveData.kt
Normal file
|
@ -0,0 +1,414 @@
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,8 @@
|
||||||
|
package net.mynero.wallet.model
|
||||||
|
|
||||||
|
enum class EnumTorState {
|
||||||
|
STARTING,
|
||||||
|
ON,
|
||||||
|
STOPPING,
|
||||||
|
OFF
|
||||||
|
}
|
14
app/src/main/java/net/mynero/wallet/model/TorState.kt
Normal file
14
app/src/main/java/net/mynero/wallet/model/TorState.kt
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package net.mynero.wallet.model
|
||||||
|
|
||||||
|
class TorState {
|
||||||
|
var state : EnumTorState = EnumTorState.OFF
|
||||||
|
get() = field
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
var progressIndicator : Int = 0
|
||||||
|
get() = field
|
||||||
|
set(value) {
|
||||||
|
field = value
|
||||||
|
}
|
||||||
|
}
|
|
@ -249,7 +249,10 @@ class Wallet {
|
||||||
isSynchronized = true
|
isSynchronized = true
|
||||||
}
|
}
|
||||||
|
|
||||||
external fun startRefresh()
|
fun startRefresh() {
|
||||||
|
startRefreshJ()
|
||||||
|
}
|
||||||
|
private external fun startRefreshJ()
|
||||||
external fun pauseRefresh()
|
external fun pauseRefresh()
|
||||||
external fun refresh(): Boolean
|
external fun refresh(): Boolean
|
||||||
external fun refreshAsync()
|
external fun refreshAsync()
|
||||||
|
|
|
@ -51,7 +51,7 @@ class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet)
|
||||||
|
|
||||||
override fun run() {
|
override fun run() {
|
||||||
val prefService = PrefService.instance ?: return
|
val prefService = PrefService.instance ?: return
|
||||||
val usesTor = prefService.getBoolean(Constants.PREF_USES_TOR, false)
|
val usesTor = ProxyService.instance?.usingProxy == true
|
||||||
val currentNode = prefService.node
|
val currentNode = prefService.node
|
||||||
val isLocalIp =
|
val isLocalIp =
|
||||||
currentNode?.address?.startsWith("10.") == true ||
|
currentNode?.address?.startsWith("10.") == true ||
|
||||||
|
|
|
@ -26,7 +26,7 @@ class PrefService(application: MoneroApplication) : ServiceBase(null) {
|
||||||
|
|
||||||
val node: Node?
|
val node: Node?
|
||||||
get() {
|
get() {
|
||||||
val usesProxy = getBoolean(Constants.PREF_USES_TOR, false)
|
val usesProxy = ProxyService.instance?.usingProxy == true
|
||||||
var defaultNode = DefaultNodes.SAMOURAI
|
var defaultNode = DefaultNodes.SAMOURAI
|
||||||
if (usesProxy) {
|
if (usesProxy) {
|
||||||
val proxyPort = ProxyService.instance?.proxyPort
|
val proxyPort = ProxyService.instance?.proxyPort
|
||||||
|
|
|
@ -1,29 +1,48 @@
|
||||||
package net.mynero.wallet.service
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
import android.util.Patterns
|
import android.util.Patterns
|
||||||
import androidx.lifecycle.LiveData
|
import net.mynero.wallet.MainActivity
|
||||||
import androidx.lifecycle.MutableLiveData
|
|
||||||
import net.mynero.wallet.data.Node
|
|
||||||
import net.mynero.wallet.livedata.SingleLiveEvent
|
import net.mynero.wallet.livedata.SingleLiveEvent
|
||||||
import net.mynero.wallet.model.WalletManager
|
import net.mynero.wallet.model.WalletManager
|
||||||
import net.mynero.wallet.util.Constants
|
import net.mynero.wallet.util.Constants
|
||||||
|
|
||||||
class ProxyService(thread: MoneroHandlerThread) : ServiceBase(thread) {
|
class ProxyService(activity: MainActivity) : ServiceBase(null) {
|
||||||
val proxyChangeEvents: SingleLiveEvent<String> = SingleLiveEvent()
|
val proxyChangeEvents: SingleLiveEvent<String> = SingleLiveEvent()
|
||||||
|
var samouraiTorManager: SamouraiTorManager? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
samouraiTorManager = SamouraiTorManager(activity.application, TorKmpManager(activity.application))
|
||||||
instance = this
|
instance = this
|
||||||
|
|
||||||
|
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]
|
||||||
|
if(usingProxy && useBundledTor)
|
||||||
|
updateProxy(address, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(useBundledTor) {
|
||||||
|
samouraiTorManager?.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateProxy(proxyAddress: String, proxyPort: String) {
|
fun updateProxy(proxyAddress: String, proxyPort: String) {
|
||||||
var finalProxyAddress = proxyAddress
|
var finalProxyAddress = proxyAddress
|
||||||
var finalProxyPort = proxyPort
|
var finalProxyPort = proxyPort
|
||||||
val usesProxy = PrefService.instance?.getBoolean(Constants.PREF_USES_TOR, false) == true
|
|
||||||
val curretNode = PrefService.instance?.node
|
val curretNode = PrefService.instance?.node
|
||||||
val isNodeLocalIp =
|
val isNodeLocalIp =
|
||||||
curretNode?.address?.startsWith("10.") == true || curretNode?.address?.startsWith("192.168.") == true || curretNode?.address == "localhost" || curretNode?.address == "127.0.0.1"
|
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) }
|
curretNode?.trusted?.let { WalletManager.instance?.wallet?.setTrustedDaemon(it) }
|
||||||
if (!usesProxy || isNodeLocalIp) {
|
if (!usingProxy || isNodeLocalIp) {
|
||||||
// User is not using proxy, or is using local node currently, so we will disable proxy here.
|
// User is not using proxy, or is using local node currently, so we will disable proxy here.
|
||||||
proxyChangeEvents.postValue("")
|
proxyChangeEvents.postValue("")
|
||||||
return
|
return
|
||||||
|
@ -44,6 +63,12 @@ class ProxyService(thread: MoneroHandlerThread) : ServiceBase(thread) {
|
||||||
return proxyString?.contains(":") == true
|
return proxyString?.contains(":") == true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val useBundledTor: Boolean
|
||||||
|
get() = PrefService.instance?.getBoolean(Constants.PREF_USE_BUNDLED_TOR, false) == true
|
||||||
|
|
||||||
|
val usingProxy: Boolean
|
||||||
|
get() = PrefService.instance?.getBoolean(Constants.PREF_USES_PROXY, false) == true
|
||||||
|
|
||||||
val proxy: String?
|
val proxy: String?
|
||||||
get() = PrefService.instance?.getString(Constants.PREF_PROXY, "")
|
get() = PrefService.instance?.getString(Constants.PREF_PROXY, "")
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import net.mynero.wallet.model.TorState
|
||||||
|
import org.json.JSONException
|
||||||
|
import org.json.JSONObject
|
||||||
|
import java.net.Proxy
|
||||||
|
|
||||||
|
class SamouraiTorManager(val appContext: Application?, val torKmpManager: TorKmpManager?) {
|
||||||
|
fun getTorStateLiveData(): MutableLiveData<TorState> {
|
||||||
|
return torKmpManager!!.torStateLiveData
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getTorState(): TorState {
|
||||||
|
return torKmpManager!!.torState
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isConnected(): Boolean {
|
||||||
|
return torKmpManager?.isConnected() ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isStarting(): Boolean {
|
||||||
|
return torKmpManager?.isStarting() ?: false
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
torKmpManager?.torOperationManager?.stopQuietly()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun start() {
|
||||||
|
torKmpManager?.torOperationManager?.startQuietly()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getProxy(): Proxy? {
|
||||||
|
return torKmpManager?.proxy
|
||||||
|
}
|
||||||
|
|
||||||
|
fun newIdentity() {
|
||||||
|
torKmpManager?.newIdentity(appContext!!)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val TAG = "SamouraiTorManager"
|
||||||
|
}
|
||||||
|
}
|
357
app/src/main/java/net/mynero/wallet/service/TorKmpManager.kt
Normal file
357
app/src/main/java/net/mynero/wallet/service/TorKmpManager.kt
Normal file
|
@ -0,0 +1,357 @@
|
||||||
|
package net.mynero.wallet.service
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.util.Log
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import net.mynero.wallet.model.TorState
|
||||||
|
import io.matthewnelson.kmp.tor.KmpTorLoaderAndroid
|
||||||
|
import io.matthewnelson.kmp.tor.TorConfigProviderAndroid
|
||||||
|
import io.matthewnelson.kmp.tor.common.address.*
|
||||||
|
import io.matthewnelson.kmp.tor.controller.common.config.TorConfig
|
||||||
|
import io.matthewnelson.kmp.tor.controller.common.config.TorConfig.Option.*
|
||||||
|
import io.matthewnelson.kmp.tor.controller.common.config.TorConfig.Setting.*
|
||||||
|
import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlInfoGet
|
||||||
|
import io.matthewnelson.kmp.tor.controller.common.control.usecase.TorControlSignal
|
||||||
|
import io.matthewnelson.kmp.tor.controller.common.events.TorEvent
|
||||||
|
import io.matthewnelson.kmp.tor.manager.TorManager
|
||||||
|
import io.matthewnelson.kmp.tor.manager.TorServiceConfig
|
||||||
|
import io.matthewnelson.kmp.tor.manager.common.TorControlManager
|
||||||
|
import io.matthewnelson.kmp.tor.manager.common.TorOperationManager
|
||||||
|
import io.matthewnelson.kmp.tor.manager.common.event.TorManagerEvent
|
||||||
|
import io.matthewnelson.kmp.tor.manager.common.state.isOff
|
||||||
|
import io.matthewnelson.kmp.tor.manager.common.state.isOn
|
||||||
|
import io.matthewnelson.kmp.tor.manager.common.state.isStarting
|
||||||
|
import io.matthewnelson.kmp.tor.manager.common.state.isStopping
|
||||||
|
import kotlinx.coroutines.*
|
||||||
|
import net.mynero.wallet.model.EnumTorState
|
||||||
|
import java.lang.Exception
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Proxy
|
||||||
|
|
||||||
|
class TorKmpManager(application : Application) {
|
||||||
|
|
||||||
|
private val TAG = "TorListener"
|
||||||
|
|
||||||
|
private val providerAndroid by lazy {
|
||||||
|
object : TorConfigProviderAndroid(context = application) {
|
||||||
|
override fun provide(): TorConfig {
|
||||||
|
return TorConfig.Builder {
|
||||||
|
// Set multiple ports for all of the things
|
||||||
|
val dns = Ports.Dns()
|
||||||
|
put(dns.set(AorDorPort.Value(PortProxy(9252))))
|
||||||
|
put(dns.set(AorDorPort.Value(PortProxy(9253))))
|
||||||
|
|
||||||
|
val socks = Ports.Socks()
|
||||||
|
put(socks.set(AorDorPort.Value(PortProxy(9254))))
|
||||||
|
put(socks.set(AorDorPort.Value(PortProxy(9255))))
|
||||||
|
|
||||||
|
val http = Ports.HttpTunnel()
|
||||||
|
put(http.set(AorDorPort.Value(PortProxy(9258))))
|
||||||
|
put(http.set(AorDorPort.Value(PortProxy(9259))))
|
||||||
|
|
||||||
|
val trans = Ports.Trans()
|
||||||
|
put(trans.set(AorDorPort.Value(PortProxy(9262))))
|
||||||
|
put(trans.set(AorDorPort.Value(PortProxy(9263))))
|
||||||
|
|
||||||
|
// If a port (9263) is already taken (by ^^^^ trans port above)
|
||||||
|
// this will take its place and "overwrite" the trans port entry
|
||||||
|
// because port 9263 is taken.
|
||||||
|
put(socks.set(AorDorPort.Value(PortProxy(9263))))
|
||||||
|
|
||||||
|
// Set Flags
|
||||||
|
socks.setFlags(setOf(
|
||||||
|
Ports.Socks.Flag.OnionTrafficOnly
|
||||||
|
)).setIsolationFlags(setOf(
|
||||||
|
Ports.IsolationFlag.IsolateClientAddr,
|
||||||
|
)).set(AorDorPort.Value(PortProxy(9264)))
|
||||||
|
put(socks)
|
||||||
|
|
||||||
|
// reset our socks object to defaults
|
||||||
|
socks.setDefault()
|
||||||
|
|
||||||
|
// Not necessary, as if ControlPort is missing it will be
|
||||||
|
// automatically added for you; but for demonstration purposes...
|
||||||
|
// put(Ports.Control().set(AorDorPort.Auto))
|
||||||
|
|
||||||
|
// Use a UnixSocket instead of TCP for the ControlPort.
|
||||||
|
//
|
||||||
|
// A unix domain socket will always be preferred on Android
|
||||||
|
// if neither Ports.Control or UnixSockets.Control are provided.
|
||||||
|
put(UnixSockets.Control().set(FileSystemFile(
|
||||||
|
workDir.builder {
|
||||||
|
|
||||||
|
// Put the file in the "data" directory
|
||||||
|
// so that we avoid any directory permission
|
||||||
|
// issues.
|
||||||
|
//
|
||||||
|
// Note that DataDirectory is automatically added
|
||||||
|
// for you if it is not present in your provided
|
||||||
|
// config. If you set a custom Path for it, you
|
||||||
|
// should use it here.
|
||||||
|
addSegment(DataDirectory.DEFAULT_NAME)
|
||||||
|
|
||||||
|
addSegment(UnixSockets.Control.DEFAULT_NAME)
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
|
||||||
|
// Use a UnixSocket instead of TCP for the SocksPort.
|
||||||
|
put(UnixSockets.Socks().set(FileSystemFile(
|
||||||
|
workDir.builder {
|
||||||
|
|
||||||
|
// Put the file in the "data" directory
|
||||||
|
// so that we avoid any directory permission
|
||||||
|
// issues.
|
||||||
|
//
|
||||||
|
// Note that DataDirectory is automatically added
|
||||||
|
// for you if it is not present in your provided
|
||||||
|
// config. If you set a custom Path for it, you
|
||||||
|
// should use it here.
|
||||||
|
addSegment(DataDirectory.DEFAULT_NAME)
|
||||||
|
|
||||||
|
addSegment(UnixSockets.Socks.DEFAULT_NAME)
|
||||||
|
}
|
||||||
|
)))
|
||||||
|
|
||||||
|
// For Android, disabling & reducing connection padding is
|
||||||
|
// advisable to minimize mobile data usage.
|
||||||
|
put(ConnectionPadding().set(AorTorF.False))
|
||||||
|
put(ConnectionPaddingReduced().set(TorF.True))
|
||||||
|
|
||||||
|
// Tor default is 24h. Reducing to 10 min helps mitigate
|
||||||
|
// unnecessary mobile data usage.
|
||||||
|
put(DormantClientTimeout().set(Time.Minutes(10)))
|
||||||
|
|
||||||
|
// Tor defaults this setting to false which would mean if
|
||||||
|
// Tor goes dormant, the next time it is started it will still
|
||||||
|
// be in the dormant state and will not bootstrap until being
|
||||||
|
// set to "active". This ensures that if it is a fresh start,
|
||||||
|
// dormancy will be cancelled automatically.
|
||||||
|
put(DormantCanceledByStartup().set(TorF.True))
|
||||||
|
|
||||||
|
// If planning to use v3 Client Authentication in a persistent
|
||||||
|
// manner (where private keys are saved to disk via the "Persist"
|
||||||
|
// flag), this is needed to be set.
|
||||||
|
put(ClientOnionAuthDir().set(FileSystemDir(
|
||||||
|
workDir.builder { addSegment(ClientOnionAuthDir.DEFAULT_NAME) }
|
||||||
|
)))
|
||||||
|
|
||||||
|
val hsPath = workDir.builder {
|
||||||
|
addSegment(HiddenService.DEFAULT_PARENT_DIR_NAME)
|
||||||
|
addSegment("test_service")
|
||||||
|
}
|
||||||
|
// Add Hidden services
|
||||||
|
put(HiddenService()
|
||||||
|
.setPorts(ports = setOf(
|
||||||
|
// Use a unix domain socket to communicate via IPC instead of over TCP
|
||||||
|
HiddenService.UnixSocket(virtualPort = Port(80), targetUnixSocket = hsPath.builder {
|
||||||
|
addSegment(HiddenService.UnixSocket.DEFAULT_UNIX_SOCKET_NAME)
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
.setMaxStreams(maxStreams = HiddenService.MaxStreams(value = 2))
|
||||||
|
.setMaxStreamsCloseCircuit(value = TorF.True)
|
||||||
|
.set(FileSystemDir(path = hsPath))
|
||||||
|
)
|
||||||
|
|
||||||
|
put(HiddenService()
|
||||||
|
.setPorts(ports = setOf(
|
||||||
|
HiddenService.Ports(virtualPort = Port(80), targetPort = Port(1030)), // http
|
||||||
|
HiddenService.Ports(virtualPort = Port(443), targetPort = Port(1030)) // https
|
||||||
|
))
|
||||||
|
.set(FileSystemDir(path =
|
||||||
|
workDir.builder {
|
||||||
|
addSegment(HiddenService.DEFAULT_PARENT_DIR_NAME)
|
||||||
|
addSegment("test_service_2")
|
||||||
|
}
|
||||||
|
))
|
||||||
|
)
|
||||||
|
}.build()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val loaderAndroid by lazy {
|
||||||
|
KmpTorLoaderAndroid(provider = providerAndroid)
|
||||||
|
}
|
||||||
|
|
||||||
|
private val manager: TorManager by lazy {
|
||||||
|
TorManager.newInstance(application = application, loader = loaderAndroid, requiredEvents = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
// only expose necessary interfaces
|
||||||
|
val torOperationManager: TorOperationManager get() = manager
|
||||||
|
val torControlManager: TorControlManager get() = manager
|
||||||
|
|
||||||
|
private val listener = TorListener()
|
||||||
|
|
||||||
|
val events: LiveData<String> get() = listener.eventLines
|
||||||
|
|
||||||
|
private val appScope by lazy {
|
||||||
|
CoroutineScope(Dispatchers.Main.immediate + SupervisorJob())
|
||||||
|
}
|
||||||
|
|
||||||
|
val torStateLiveData: MutableLiveData<TorState> = MutableLiveData()
|
||||||
|
get() = field
|
||||||
|
var torState: TorState = TorState()
|
||||||
|
get() = field
|
||||||
|
|
||||||
|
var proxy: Proxy? = null
|
||||||
|
get() = field
|
||||||
|
|
||||||
|
init {
|
||||||
|
manager.debug(true)
|
||||||
|
manager.addListener(listener)
|
||||||
|
listener.addLine(TorServiceConfig.getMetaData(application).toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isConnected(): Boolean {
|
||||||
|
return manager.state.isOn()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun isStarting(): Boolean {
|
||||||
|
return manager.state.isStarting();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
fun newIdentity(appContext: Application) {
|
||||||
|
appScope.launch(Dispatchers.IO) {
|
||||||
|
var result = manager.signal(TorControlSignal.Signal.NewNym)
|
||||||
|
appScope.launch(Dispatchers.Main) {
|
||||||
|
if (result.isSuccess) {
|
||||||
|
val msg = "You have changed Tor identity"
|
||||||
|
listener.addLine(msg)
|
||||||
|
Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).show()
|
||||||
|
} else if (result.isFailure) {
|
||||||
|
val msg = "Tor identity change failed"
|
||||||
|
listener.addLine(msg)
|
||||||
|
Toast.makeText(appContext, msg, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class TorListener: TorManagerEvent.Listener() {
|
||||||
|
private val _eventLines: MutableLiveData<String> = MutableLiveData("")
|
||||||
|
val eventLines: LiveData<String> = _eventLines
|
||||||
|
private val events: MutableList<String> = ArrayList(50)
|
||||||
|
fun addLine(line: String) {
|
||||||
|
synchronized(this) {
|
||||||
|
if (events.size > 49) {
|
||||||
|
events.removeAt(0)
|
||||||
|
}
|
||||||
|
events.add(line)
|
||||||
|
//Log.i(TAG, line)
|
||||||
|
try {
|
||||||
|
_eventLines.value = events.joinToString("\n")
|
||||||
|
} catch (e: Exception) {
|
||||||
|
_eventLines.postValue(events.joinToString("\n"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEvent(event: TorManagerEvent) {
|
||||||
|
|
||||||
|
if (event is TorManagerEvent.State) {
|
||||||
|
val stateEvent: TorManagerEvent.State = event
|
||||||
|
val state = stateEvent.torState
|
||||||
|
torState.progressIndicator = state.bootstrap
|
||||||
|
val liveTorState = TorState()
|
||||||
|
liveTorState.progressIndicator = state.bootstrap
|
||||||
|
|
||||||
|
if (state.isOn()) {
|
||||||
|
torState.state = EnumTorState.ON
|
||||||
|
liveTorState.state = EnumTorState.ON
|
||||||
|
} else if (state.isStarting()) {
|
||||||
|
torState.state = EnumTorState.STARTING
|
||||||
|
liveTorState.state = EnumTorState.STARTING
|
||||||
|
} else if (state.isOff()) {
|
||||||
|
torState.state = EnumTorState.OFF
|
||||||
|
liveTorState.state = EnumTorState.OFF
|
||||||
|
} else if (state.isStopping()) {
|
||||||
|
torState.state = EnumTorState.STOPPING
|
||||||
|
liveTorState.state = EnumTorState.STOPPING
|
||||||
|
}
|
||||||
|
torStateLiveData.postValue(liveTorState)
|
||||||
|
}
|
||||||
|
addLine(event.toString())
|
||||||
|
super.onEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEvent(event: TorEvent.Type.SingleLineEvent, output: String) {
|
||||||
|
addLine("$event - $output")
|
||||||
|
|
||||||
|
super.onEvent(event, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onEvent(event: TorEvent.Type.MultiLineEvent, output: List<String>) {
|
||||||
|
addLine("multi-line event: $event. See Logs.")
|
||||||
|
|
||||||
|
// these events are many many many lines and should be moved
|
||||||
|
// off the main thread if ever needed to be dealt with.
|
||||||
|
val enabled = false
|
||||||
|
if (enabled) {
|
||||||
|
appScope.launch(Dispatchers.IO) {
|
||||||
|
Log.d(TAG, "-------------- multi-line event START: $event --------------")
|
||||||
|
for (line in output) {
|
||||||
|
Log.d(TAG, line)
|
||||||
|
}
|
||||||
|
Log.d(TAG, "--------------- multi-line event END: $event ---------------")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
super.onEvent(event, output)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun managerEventError(t: Throwable) {
|
||||||
|
t.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun managerEventAddressInfo(info: TorManagerEvent.AddressInfo) {
|
||||||
|
if (info.isNull) {
|
||||||
|
// Tear down HttpClient
|
||||||
|
} else {
|
||||||
|
info.socksInfoToProxyAddressOrNull()?.firstOrNull()?.let { proxyAddress ->
|
||||||
|
@Suppress("UNUSED_VARIABLE")
|
||||||
|
val socket = InetSocketAddress(proxyAddress.address.value, proxyAddress.port.value)
|
||||||
|
proxy = Proxy(Proxy.Type.SOCKS, socket)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun managerEventStartUpCompleteForTorInstance() {
|
||||||
|
// Do one-time things after we're bootstrapped
|
||||||
|
|
||||||
|
appScope.launch {
|
||||||
|
torControlManager.onionAddNew(
|
||||||
|
type = OnionAddress.PrivateKey.Type.ED25519_V3,
|
||||||
|
hsPorts = setOf(HiddenService.Ports(virtualPort = Port(443))),
|
||||||
|
flags = null,
|
||||||
|
maxStreams = null,
|
||||||
|
).onSuccess { hsEntry ->
|
||||||
|
addLine(
|
||||||
|
"New HiddenService: " +
|
||||||
|
"\n - Address: https://${hsEntry.address.canonicalHostname()}" +
|
||||||
|
"\n - PrivateKey: ${hsEntry.privateKey}"
|
||||||
|
)
|
||||||
|
|
||||||
|
torControlManager.onionDel(hsEntry.address).onSuccess {
|
||||||
|
addLine("Aaaaaaaaand it's gone...")
|
||||||
|
}.onFailure { t ->
|
||||||
|
t.printStackTrace()
|
||||||
|
}
|
||||||
|
}.onFailure { t ->
|
||||||
|
t.printStackTrace()
|
||||||
|
}
|
||||||
|
|
||||||
|
delay(20_000L)
|
||||||
|
|
||||||
|
torControlManager.infoGet(TorControlInfoGet.KeyWord.Uptime()).onSuccess { uptime ->
|
||||||
|
addLine("Uptime - $uptime")
|
||||||
|
}.onFailure { t ->
|
||||||
|
t.printStackTrace()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,7 +4,7 @@ object Constants {
|
||||||
const val WALLET_NAME = "xmr_wallet"
|
const val WALLET_NAME = "xmr_wallet"
|
||||||
const val MNEMONIC_LANGUAGE = "English"
|
const val MNEMONIC_LANGUAGE = "English"
|
||||||
const val PREF_USES_PASSWORD = "pref_uses_password"
|
const val PREF_USES_PASSWORD = "pref_uses_password"
|
||||||
const val PREF_USES_TOR = "pref_uses_tor"
|
const val PREF_USES_PROXY = "pref_uses_tor"
|
||||||
const val PREF_PROXY = "pref_proxy"
|
const val PREF_PROXY = "pref_proxy"
|
||||||
const val PREF_NODE_2 = "pref_node_2"
|
const val PREF_NODE_2 = "pref_node_2"
|
||||||
const val PREF_CUSTOM_NODES = "pref_custom_nodes"
|
const val PREF_CUSTOM_NODES = "pref_custom_nodes"
|
||||||
|
@ -13,6 +13,8 @@ object Constants {
|
||||||
const val PREF_MONEROCHAN = "pref_monerochan"
|
const val PREF_MONEROCHAN = "pref_monerochan"
|
||||||
const val PREF_DONATE_PER_TX = "pref_donate_per_tx"
|
const val PREF_DONATE_PER_TX = "pref_donate_per_tx"
|
||||||
const val PREF_FROZEN_COINS = "pref_frozen_coins"
|
const val PREF_FROZEN_COINS = "pref_frozen_coins"
|
||||||
|
const val PREF_USE_BUNDLED_TOR = "pref_use_bundled_tor"
|
||||||
|
|
||||||
const val URI_PREFIX = "monero:"
|
const val URI_PREFIX = "monero:"
|
||||||
const val URI_ARG_AMOUNT = "tx_amount"
|
const val URI_ARG_AMOUNT = "tx_amount"
|
||||||
const val URI_ARG_AMOUNT2 = "amount"
|
const val URI_ARG_AMOUNT2 = "amount"
|
||||||
|
|
|
@ -1,11 +1,7 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
android:shape="rectangle">
|
<!-- Color when the row is selected -->
|
||||||
<padding
|
<item android:drawable="@drawable/edittext_bg_enabled" android:state_enabled="true" />
|
||||||
android:bottom="8dp"
|
<!-- Standard background color -->
|
||||||
android:left="12dp"
|
<item android:drawable="@drawable/edittext_bg_disabled" />
|
||||||
android:right="12dp"
|
</selector>
|
||||||
android:top="8dp" />
|
|
||||||
<solid android:color="@color/edittext_bg_color" />
|
|
||||||
<corners android:radius="8dp" />
|
|
||||||
</shape>
|
|
11
app/src/main/res/drawable/edittext_bg_disabled.xml
Normal file
11
app/src/main/res/drawable/edittext_bg_disabled.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<padding
|
||||||
|
android:bottom="8dp"
|
||||||
|
android:left="12dp"
|
||||||
|
android:right="12dp"
|
||||||
|
android:top="8dp" />
|
||||||
|
<solid android:color="@color/edittext_bg_color_disabled" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
11
app/src/main/res/drawable/edittext_bg_enabled.xml
Normal file
11
app/src/main/res/drawable/edittext_bg_enabled.xml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<padding
|
||||||
|
android:bottom="8dp"
|
||||||
|
android:left="12dp"
|
||||||
|
android:right="12dp"
|
||||||
|
android:top="8dp" />
|
||||||
|
<solid android:color="@color/edittext_bg_color" />
|
||||||
|
<corners android:radius="8dp" />
|
||||||
|
</shape>
|
|
@ -242,7 +242,17 @@
|
||||||
android:minWidth="48dp"
|
android:minWidth="48dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/bundled_tor_checkbox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
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"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/tor_onboarding_switch"/>
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/wallet_proxy_address_edittext"
|
android:id="@+id/wallet_proxy_address_edittext"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
|
@ -250,10 +260,11 @@
|
||||||
android:background="@drawable/edittext_bg"
|
android:background="@drawable/edittext_bg"
|
||||||
android:hint="@string/wallet_proxy_address_hint"
|
android:hint="@string/wallet_proxy_address_hint"
|
||||||
android:minHeight="48dp"
|
android:minHeight="48dp"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toTopOf="@id/wallet_proxy_port_edittext"
|
app:layout_constraintBottom_toTopOf="@id/wallet_proxy_port_edittext"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/tor_onboarding_switch" />
|
app:layout_constraintTop_toBottomOf="@id/bundled_tor_checkbox" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/wallet_proxy_port_edittext"
|
android:id="@+id/wallet_proxy_port_edittext"
|
||||||
|
@ -264,6 +275,7 @@
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:inputType="number"
|
android:inputType="number"
|
||||||
android:minHeight="48dp"
|
android:minHeight="48dp"
|
||||||
|
android:visibility="gone"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
@ -277,6 +289,7 @@
|
||||||
android:layout_marginTop="32dp"
|
android:layout_marginTop="32dp"
|
||||||
android:background="@drawable/button_bg"
|
android:background="@drawable/button_bg"
|
||||||
android:text="@string/create_wallet"
|
android:text="@string/create_wallet"
|
||||||
|
android:enabled="false"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/more_options_layout" />
|
app:layout_constraintTop_toBottomOf="@id/more_options_layout" />
|
||||||
|
|
|
@ -237,11 +237,24 @@
|
||||||
app:layout_constraintTop_toBottomOf="@id/tor_switch"
|
app:layout_constraintTop_toBottomOf="@id/tor_switch"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent">
|
app:layout_constraintEnd_toEndOf="parent">
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/bundled_tor_checkbox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:minHeight="48dp"
|
||||||
|
android:text="@string/use_bundled_tor"
|
||||||
|
android:visibility="visible"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/wallet_proxy_address_edittext"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"/>
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/wallet_proxy_address_edittext"
|
android:id="@+id/wallet_proxy_address_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_marginEnd="24dp"
|
android:layout_marginEnd="24dp"
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:background="@drawable/edittext_bg"
|
android:background="@drawable/edittext_bg"
|
||||||
|
@ -250,7 +263,7 @@
|
||||||
app:layout_constraintBottom_toTopOf="@id/wallet_proxy_port_edittext"
|
app:layout_constraintBottom_toTopOf="@id/wallet_proxy_port_edittext"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toBottomOf="@id/bundled_tor_checkbox" />
|
||||||
|
|
||||||
<EditText
|
<EditText
|
||||||
android:id="@+id/wallet_proxy_port_edittext"
|
android:id="@+id/wallet_proxy_port_edittext"
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
<color name="oled_colorError">@color/oled_favouriteColor</color>
|
<color name="oled_colorError">@color/oled_favouriteColor</color>
|
||||||
<color name="oled_colorOnError">#ffffff</color>
|
<color name="oled_colorOnError">#ffffff</color>
|
||||||
<color name="edittext_bg_color">#202020</color>
|
<color name="edittext_bg_color">#202020</color>
|
||||||
|
<color name="edittext_bg_color_disabled">#0E0E0E</color>
|
||||||
<color name="oled_locked_utxo">#956E43</color>
|
<color name="oled_locked_utxo">#956E43</color>
|
||||||
<color name="oled_txBackgroundColor">#060606</color>
|
<color name="oled_txBackgroundColor">#060606</color>
|
||||||
<color name="oled_dialogBackgroundColor">#0E0E0E</color>
|
<color name="oled_dialogBackgroundColor">#0E0E0E</color>
|
||||||
|
|
|
@ -30,6 +30,7 @@
|
||||||
<color name="oled_colorOnError">@color/oled_colorBackground</color>
|
<color name="oled_colorOnError">@color/oled_colorBackground</color>
|
||||||
<color name="edittext_bg_color">#CCCCCC</color>
|
<color name="edittext_bg_color">#CCCCCC</color>
|
||||||
<color name="button_disabled_bg_color">#454545</color>
|
<color name="button_disabled_bg_color">#454545</color>
|
||||||
|
<color name="edittext_bg_color_disabled">#CCCCCC</color>
|
||||||
<color name="oled_locked_utxo">#B5895A</color>
|
<color name="oled_locked_utxo">#B5895A</color>
|
||||||
<color name="oled_txBackgroundColor">#FBFBFB</color>
|
<color name="oled_txBackgroundColor">#FBFBFB</color>
|
||||||
<color name="oled_dialogBackgroundColor">#E8E8E8</color>
|
<color name="oled_dialogBackgroundColor">#E8E8E8</color>
|
||||||
|
|
|
@ -136,6 +136,7 @@
|
||||||
<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="trusted_daemon">Trusted daemon</string>
|
||||||
|
<string name="use_bundled_tor">Let Mysu start and manage a Tor 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>
|
||||||
|
|
|
@ -17,6 +17,7 @@ allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
google()
|
google()
|
||||||
|
maven { url 'https://jitpack.io' }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue