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 "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
|
||||
testImplementation "junit:junit:4.13.2"
|
||||
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
|
||||
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);
|
||||
wallet->startRefresh();
|
||||
}
|
||||
|
|
|
@ -45,6 +45,7 @@ class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, Password
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
ProxyService(this)
|
||||
val walletFile = File(applicationInfo.dataDir, Constants.WALLET_NAME)
|
||||
val walletKeysFile = File(applicationInfo.dataDir, Constants.WALLET_NAME + ".keys")
|
||||
if (walletKeysFile.exists()) {
|
||||
|
@ -87,7 +88,6 @@ class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, Password
|
|||
historyService = HistoryService(thread)
|
||||
blockchainService = BlockchainService(thread)
|
||||
daemonService = DaemonService(thread)
|
||||
proxyService = ProxyService(thread)
|
||||
utxoService = UTXOService(thread)
|
||||
thread.start()
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@ import android.widget.ProgressBar
|
|||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.PrefService
|
||||
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 timber.log.Timber
|
||||
|
||||
|
|
|
@ -18,8 +18,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 kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mynero.wallet.MoneroApplication
|
||||
import net.mynero.wallet.R
|
||||
import net.mynero.wallet.data.Node
|
||||
|
@ -35,26 +39,6 @@ import net.mynero.wallet.util.Constants
|
|||
class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListener {
|
||||
private var useOffset = true
|
||||
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 walletProxyPortEditText: EditText? = null
|
||||
private var walletPasswordEditText: EditText? = null
|
||||
|
@ -113,7 +97,7 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
|||
advancedOptionsLayout?.visibility = View.GONE
|
||||
}
|
||||
}
|
||||
mViewModel?.enableCreateButton?.observe(viewLifecycleOwner) { enable: Boolean ->
|
||||
mViewModel?.enableButton?.observe(viewLifecycleOwner) { enable: Boolean ->
|
||||
createWalletButton?.isEnabled = enable
|
||||
}
|
||||
mViewModel?.seedType?.observe(viewLifecycleOwner) { seedType: SeedType ->
|
||||
|
@ -131,9 +115,27 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
|||
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() {
|
||||
val useBundledTor = view?.findViewById<CheckBox>(R.id.bundled_tor_checkbox)
|
||||
|
||||
seedOffsetCheckbox?.isChecked = useOffset
|
||||
// Disable onBack click
|
||||
val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
|
||||
|
@ -147,12 +149,9 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
|||
useOffset = b
|
||||
}
|
||||
createWalletButton?.setOnClickListener {
|
||||
prepareDefaultNode()
|
||||
onBackPressedCallback.isEnabled = false
|
||||
(getActivity()?.application as MoneroApplication).executor?.execute {
|
||||
createOrImportWallet(
|
||||
walletPasswordEditText?.text.toString(),
|
||||
walletPasswordConfirmEditText?.text.toString(),
|
||||
walletSeedEditText?.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 afterTextChanged(editable: Editable) {
|
||||
val text = editable.toString()
|
||||
mViewModel?.setPassphrase(text)
|
||||
if (text.isEmpty()) {
|
||||
walletPasswordConfirmEditText?.text = null
|
||||
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 {
|
||||
override fun beforeTextChanged(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() }
|
||||
torSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
|
||||
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_TOR, b)?.apply()
|
||||
removeProxyTextListeners()
|
||||
if (b) {
|
||||
if (ProxyService.instance?.hasProxySet() == true) {
|
||||
val proxyAddress =
|
||||
ProxyService.instance?.proxyAddress ?: return@setOnCheckedChangeListener
|
||||
val proxyPort =
|
||||
ProxyService.instance?.proxyPort ?: return@setOnCheckedChangeListener
|
||||
initProxyStuff(proxyAddress, proxyPort)
|
||||
useBundledTor?.visibility = View.VISIBLE
|
||||
walletProxyAddressEditText?.visibility = View.VISIBLE
|
||||
walletProxyPortEditText?.visibility = View.VISIBLE
|
||||
} 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 ->
|
||||
PrefService.instance?.edit()?.putBoolean(Constants.PREF_MONEROCHAN, b)?.apply()
|
||||
if (b) {
|
||||
xmrchanOnboardingImage?.visibility = View.VISIBLE
|
||||
} else {
|
||||
|
@ -220,6 +238,21 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
|||
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() {
|
||||
|
@ -233,47 +266,21 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
|||
mViewModel?.setSeedType(newSeedType)
|
||||
}
|
||||
|
||||
private fun prepareDefaultNode() {
|
||||
PrefService.instance?.node
|
||||
}
|
||||
|
||||
private fun createOrImportWallet(
|
||||
walletPassword: String,
|
||||
confirmedPassword: String,
|
||||
walletSeed: String,
|
||||
restoreHeightText: String
|
||||
) {
|
||||
val activity: Activity? = activity
|
||||
if (activity != null) {
|
||||
lifecycleScope.launch(Dispatchers.IO) {
|
||||
mViewModel?.createOrImportWallet(
|
||||
activity,
|
||||
walletPassword,
|
||||
confirmedPassword,
|
||||
walletSeed,
|
||||
restoreHeightText,
|
||||
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() {
|
||||
|
@ -284,7 +291,7 @@ class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListe
|
|||
getString(R.string.node_selected),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
mViewModel?.updateProxy(activity?.application as MoneroApplication)
|
||||
refreshProxy()
|
||||
}
|
||||
|
||||
override fun onClickedEditNode(node: Node?) {}
|
||||
|
@ -303,4 +310,10 @@ 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)
|
||||
}
|
||||
}
|
|
@ -9,9 +9,11 @@ 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.model.Wallet
|
||||
import net.mynero.wallet.model.WalletManager
|
||||
import net.mynero.wallet.service.PrefService
|
||||
import net.mynero.wallet.service.ProxyService
|
||||
import net.mynero.wallet.util.Constants
|
||||
import net.mynero.wallet.util.RestoreHeight
|
||||
import java.io.File
|
||||
|
@ -19,62 +21,65 @@ import java.util.Calendar
|
|||
|
||||
class OnboardingViewModel : ViewModel() {
|
||||
private val _showMoreOptions = MutableLiveData(false)
|
||||
private val _enableCreateButton = MutableLiveData(true)
|
||||
private val _creatingWallet = MutableLiveData(false)
|
||||
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 enableCreateButton: LiveData<Boolean> = _enableCreateButton
|
||||
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() {
|
||||
val currentValue = showMoreOptions.value ?: false
|
||||
val newValue = !currentValue
|
||||
_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?) {
|
||||
_seedType.value = seedType
|
||||
}
|
||||
|
||||
fun setProxyAddress(address: String) {
|
||||
proxyAddress = address
|
||||
}
|
||||
|
||||
fun setProxyPort(port: String) {
|
||||
proxyPort = port
|
||||
}
|
||||
|
||||
fun createOrImportWallet(
|
||||
mainActivity: Activity,
|
||||
walletPassword: String,
|
||||
confirmedPassword: String,
|
||||
walletSeed: String,
|
||||
restoreHeightText: String,
|
||||
useOffset: Boolean
|
||||
) {
|
||||
val passphrase = _passphrase.value ?: return
|
||||
val confirmedPassphrase = _confirmedPassphrase.value ?: return
|
||||
val useProxy = _useProxy.value ?: return
|
||||
|
||||
val application = mainActivity.application as MoneroApplication
|
||||
application.executor?.execute {
|
||||
_enableCreateButton.postValue(false)
|
||||
val offset = if (useOffset) walletPassword else ""
|
||||
if (walletPassword.isNotEmpty()) {
|
||||
if (walletPassword != confirmedPassword) {
|
||||
_enableCreateButton.postValue(true)
|
||||
_creatingWallet.postValue(true)
|
||||
val offset = if (useOffset) confirmedPassphrase else ""
|
||||
if (passphrase.isNotEmpty()) {
|
||||
if (passphrase != confirmedPassphrase) {
|
||||
_creatingWallet.postValue(false)
|
||||
mainActivity.runOnUiThread {
|
||||
Toast.makeText(
|
||||
mainActivity,
|
||||
|
@ -82,7 +87,7 @@ class OnboardingViewModel : ViewModel() {
|
|||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
return@execute
|
||||
return
|
||||
}
|
||||
PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_PASSWORD, true)
|
||||
?.apply()
|
||||
|
@ -93,23 +98,23 @@ class OnboardingViewModel : ViewModel() {
|
|||
if (offset.isNotEmpty()) {
|
||||
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 (seedTypeValue == SeedType.POLYSEED) {
|
||||
wallet = if (offset.isEmpty()) {
|
||||
mainActivity.runOnUiThread {
|
||||
_enableCreateButton.postValue(true)
|
||||
_creatingWallet.postValue(false)
|
||||
Toast.makeText(
|
||||
mainActivity,
|
||||
application.getString(R.string.invalid_empty_passphrase),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
return@execute
|
||||
return
|
||||
} else {
|
||||
WalletManager.instance?.createWalletPolyseed(
|
||||
walletFile,
|
||||
walletPassword,
|
||||
passphrase,
|
||||
offset,
|
||||
Constants.MNEMONIC_LANGUAGE
|
||||
)
|
||||
|
@ -122,7 +127,7 @@ class OnboardingViewModel : ViewModel() {
|
|||
tmpWallet?.let {
|
||||
wallet = WalletManager.instance?.recoveryWallet(
|
||||
walletFile,
|
||||
walletPassword,
|
||||
passphrase,
|
||||
tmpWallet.getSeed("") ?: return@let,
|
||||
offset,
|
||||
restoreHeight
|
||||
|
@ -133,14 +138,14 @@ class OnboardingViewModel : ViewModel() {
|
|||
} else {
|
||||
if (getMnemonicType(walletSeed) == SeedType.UNKNOWN) {
|
||||
mainActivity.runOnUiThread {
|
||||
_enableCreateButton.postValue(true)
|
||||
_creatingWallet.postValue(false)
|
||||
Toast.makeText(
|
||||
mainActivity,
|
||||
application.getString(R.string.invalid_mnemonic_code),
|
||||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
return@execute
|
||||
return
|
||||
}
|
||||
if (restoreHeightText.isNotEmpty()) {
|
||||
restoreHeight = restoreHeightText.toLong()
|
||||
|
@ -148,14 +153,14 @@ class OnboardingViewModel : ViewModel() {
|
|||
if (seedTypeValue == SeedType.POLYSEED) {
|
||||
wallet = WalletManager.instance?.recoveryWalletPolyseed(
|
||||
walletFile,
|
||||
walletPassword,
|
||||
passphrase,
|
||||
walletSeed,
|
||||
offset
|
||||
)
|
||||
} else if (seedTypeValue == SeedType.LEGACY) {
|
||||
wallet = WalletManager.instance?.recoveryWallet(
|
||||
walletFile,
|
||||
walletPassword,
|
||||
passphrase,
|
||||
walletSeed,
|
||||
offset,
|
||||
restoreHeight
|
||||
|
@ -167,11 +172,20 @@ 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) {
|
||||
(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() }
|
||||
} else {
|
||||
mainActivity.runOnUiThread {
|
||||
_enableCreateButton.postValue(true)
|
||||
_creatingWallet.postValue(false)
|
||||
Toast.makeText(
|
||||
mainActivity,
|
||||
application.getString(
|
||||
|
@ -183,7 +197,6 @@ class OnboardingViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val newRestoreHeight: Long
|
||||
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) {
|
||||
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.ViewGroup
|
||||
import android.widget.Button
|
||||
import android.widget.CheckBox
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.activity.OnBackPressedCallback
|
||||
import androidx.appcompat.widget.SwitchCompat
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import net.mynero.wallet.R
|
||||
import net.mynero.wallet.data.Node
|
||||
import net.mynero.wallet.data.Node.Companion.fromJson
|
||||
|
@ -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.PasswordListener
|
||||
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.BlockchainService
|
||||
import net.mynero.wallet.service.DaemonService
|
||||
import net.mynero.wallet.service.HistoryService
|
||||
import net.mynero.wallet.service.PrefService
|
||||
import net.mynero.wallet.service.ProxyService
|
||||
import net.mynero.wallet.util.Constants
|
||||
import org.json.JSONArray
|
||||
import timber.log.Timber
|
||||
|
||||
class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListener, AddNodeListener,
|
||||
EditNodeListener {
|
||||
|
@ -75,6 +66,12 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
|
|||
view.findViewById<ConstraintLayout>(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.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 =
|
||||
PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false) == true
|
||||
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()
|
||||
}
|
||||
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
|
||||
cachedProxyPort = ProxyService.instance?.proxyPort ?: return
|
||||
if (ProxyService.instance?.hasProxySet() == true) {
|
||||
|
@ -106,7 +103,7 @@ class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListen
|
|||
proxySettingsLayout.visibility = View.GONE
|
||||
}
|
||||
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 (ProxyService.instance?.hasProxySet() == true) {
|
||||
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) {
|
||||
override fun handleOnBackPressed() {
|
||||
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
|
||||
}
|
||||
|
||||
external fun startRefresh()
|
||||
fun startRefresh() {
|
||||
startRefreshJ()
|
||||
}
|
||||
private external fun startRefreshJ()
|
||||
external fun pauseRefresh()
|
||||
external fun refresh(): Boolean
|
||||
external fun refreshAsync()
|
||||
|
|
|
@ -51,7 +51,7 @@ class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet)
|
|||
|
||||
override fun run() {
|
||||
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 isLocalIp =
|
||||
currentNode?.address?.startsWith("10.") == true ||
|
||||
|
|
|
@ -26,7 +26,7 @@ class PrefService(application: MoneroApplication) : ServiceBase(null) {
|
|||
|
||||
val node: Node?
|
||||
get() {
|
||||
val usesProxy = getBoolean(Constants.PREF_USES_TOR, false)
|
||||
val usesProxy = ProxyService.instance?.usingProxy == true
|
||||
var defaultNode = DefaultNodes.SAMOURAI
|
||||
if (usesProxy) {
|
||||
val proxyPort = ProxyService.instance?.proxyPort
|
||||
|
|
|
@ -1,29 +1,48 @@
|
|||
package net.mynero.wallet.service
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Patterns
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import net.mynero.wallet.data.Node
|
||||
import net.mynero.wallet.MainActivity
|
||||
import net.mynero.wallet.livedata.SingleLiveEvent
|
||||
import net.mynero.wallet.model.WalletManager
|
||||
import net.mynero.wallet.util.Constants
|
||||
|
||||
class ProxyService(thread: MoneroHandlerThread) : ServiceBase(thread) {
|
||||
class ProxyService(activity: MainActivity) : ServiceBase(null) {
|
||||
val proxyChangeEvents: SingleLiveEvent<String> = SingleLiveEvent()
|
||||
var samouraiTorManager: SamouraiTorManager? = null
|
||||
|
||||
init {
|
||||
samouraiTorManager = SamouraiTorManager(activity.application, TorKmpManager(activity.application))
|
||||
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) {
|
||||
var finalProxyAddress = proxyAddress
|
||||
var finalProxyPort = proxyPort
|
||||
val usesProxy = PrefService.instance?.getBoolean(Constants.PREF_USES_TOR, false) == true
|
||||
val curretNode = PrefService.instance?.node
|
||||
val isNodeLocalIp =
|
||||
curretNode?.address?.startsWith("10.") == true || curretNode?.address?.startsWith("192.168.") == true || curretNode?.address == "localhost" || curretNode?.address == "127.0.0.1"
|
||||
curretNode?.trusted?.let { WalletManager.instance?.wallet?.setTrustedDaemon(it) }
|
||||
if (!usesProxy || isNodeLocalIp) {
|
||||
if (!usingProxy || isNodeLocalIp) {
|
||||
// User is not using proxy, or is using local node currently, so we will disable proxy here.
|
||||
proxyChangeEvents.postValue("")
|
||||
return
|
||||
|
@ -44,6 +63,12 @@ class ProxyService(thread: MoneroHandlerThread) : ServiceBase(thread) {
|
|||
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?
|
||||
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 MNEMONIC_LANGUAGE = "English"
|
||||
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_NODE_2 = "pref_node_2"
|
||||
const val PREF_CUSTOM_NODES = "pref_custom_nodes"
|
||||
|
@ -13,6 +13,8 @@ object Constants {
|
|||
const val PREF_MONEROCHAN = "pref_monerochan"
|
||||
const val PREF_DONATE_PER_TX = "pref_donate_per_tx"
|
||||
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_ARG_AMOUNT = "tx_amount"
|
||||
const val URI_ARG_AMOUNT2 = "amount"
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<?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>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- Color when the row is selected -->
|
||||
<item android:drawable="@drawable/edittext_bg_enabled" android:state_enabled="true" />
|
||||
<!-- Standard background color -->
|
||||
<item android:drawable="@drawable/edittext_bg_disabled" />
|
||||
</selector>
|
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"
|
||||
app:layout_constraintEnd_toEndOf="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
|
||||
android:id="@+id/wallet_proxy_address_edittext"
|
||||
android:layout_width="0dp"
|
||||
|
@ -250,10 +260,11 @@
|
|||
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"
|
||||
app:layout_constraintTop_toBottomOf="@id/tor_onboarding_switch" />
|
||||
app:layout_constraintTop_toBottomOf="@id/bundled_tor_checkbox" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/wallet_proxy_port_edittext"
|
||||
|
@ -264,6 +275,7 @@
|
|||
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"
|
||||
|
@ -277,6 +289,7 @@
|
|||
android:layout_marginTop="32dp"
|
||||
android:background="@drawable/button_bg"
|
||||
android:text="@string/create_wallet"
|
||||
android:enabled="false"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/more_options_layout" />
|
||||
|
|
|
@ -237,11 +237,24 @@
|
|||
app:layout_constraintTop_toBottomOf="@id/tor_switch"
|
||||
app:layout_constraintStart_toStartOf="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
|
||||
android:id="@+id/wallet_proxy_address_edittext"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginStart="24dp"
|
||||
android:background="@drawable/edittext_bg"
|
||||
|
@ -250,7 +263,7 @@
|
|||
app:layout_constraintBottom_toTopOf="@id/wallet_proxy_port_edittext"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toBottomOf="@id/bundled_tor_checkbox" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/wallet_proxy_port_edittext"
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
<color name="oled_colorError">@color/oled_favouriteColor</color>
|
||||
<color name="oled_colorOnError">#ffffff</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_txBackgroundColor">#060606</color>
|
||||
<color name="oled_dialogBackgroundColor">#0E0E0E</color>
|
||||
|
|
|
@ -30,6 +30,7 @@
|
|||
<color name="oled_colorOnError">@color/oled_colorBackground</color>
|
||||
<color name="edittext_bg_color">#CCCCCC</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_txBackgroundColor">#FBFBFB</color>
|
||||
<color name="oled_dialogBackgroundColor">#E8E8E8</color>
|
||||
|
|
|
@ -136,6 +136,7 @@
|
|||
<string name="block_height">Block Height</string>
|
||||
<string name="use_password_as_seed_offset">Use passphrase as seed offset</string>
|
||||
<string name="trusted_daemon">Trusted daemon</string>
|
||||
<string name="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="previous_addresses">Previous addresses</string>
|
||||
<string name="donate_label">Donate to Mysu</string>
|
||||
|
|
|
@ -17,6 +17,7 @@ allprojects {
|
|||
repositories {
|
||||
mavenCentral()
|
||||
google()
|
||||
maven { url 'https://jitpack.io' }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue