diff --git a/app/build.gradle b/app/build.gradle
index 63878c4..70cb168 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,8 +9,8 @@ android {
minSdkVersion 22
targetSdkVersion 34
compileSdk 34
- versionCode 50900
- versionName "0.5.9 'Fluorine Fermi'"
+ versionCode 51000
+ versionName "0.5.10 'Fluorine Fermi'"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro
deleted file mode 100644
index cb4574a..0000000
--- a/app/proguard-rules.pro
+++ /dev/null
@@ -1,25 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in C:\Users\Test\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 79874fa..84c7400 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -19,14 +19,16 @@
android:supportsRtl="true"
android:theme="@style/MyMaterialThemeOled"
android:usesCleartextTraffic="true">
+
+
-
+
@@ -36,6 +38,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
(R.id.settings_imageview)
+ val sendButton = findViewById(R.id.send_button)
+ val receiveButton = findViewById(R.id.receive_button)
- private fun bindListeners(view: View) {
- val settingsImageView = view.findViewById(R.id.settings_imageview)
- val sendButton = view.findViewById(R.id.send_button)
- val receiveButton = view.findViewById(R.id.receive_button)
- settingsImageView.setOnClickListener { navigate(HomeFragmentDirections.navToSettings()) }
- sendButton.setOnClickListener { navigate(HomeFragmentDirections.navToSend()) }
- receiveButton.setOnClickListener { navigate(HomeFragmentDirections.navToReceive()) }
- }
-
- private fun bindObservers(view: View) {
val txHistoryRecyclerView =
- view.findViewById(R.id.transaction_history_recyclerview)
- val unlockedBalanceTextView = view.findViewById(R.id.balance_unlocked_textview)
- val lockedBalanceTextView = view.findViewById(R.id.balance_locked_textview)
+ findViewById(R.id.transaction_history_recyclerview)
+ val unlockedBalanceTextView = findViewById(R.id.balance_unlocked_textview)
+ val lockedBalanceTextView = findViewById(R.id.balance_locked_textview)
val balanceService = BalanceService.instance
val historyService = HistoryService.instance
val blockchainService = BlockchainService.instance
- ProxyService.instance?.proxyChangeEvents?.observe(viewLifecycleOwner) { proxy ->
+
+ settingsImageView.setOnClickListener {
+ startActivity(Intent(this, SettingsActivity::class.java))
+ }
+ sendButton.setOnClickListener {
+ startActivity(Intent(this, SendActivity::class.java))
+ }
+ receiveButton.setOnClickListener {
+ startActivity(Intent(this, ReceiveActivity::class.java))
+ }
+
+ ProxyService.instance?.proxyChangeEvents?.observe(this) { proxy ->
lifecycleScope.launch(Dispatchers.IO) {
Log.d("HomeFragment", "Updating proxy, restarting wallet. proxy=$proxy")
val finalProxy =
@@ -84,7 +67,7 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
}
}
- DaemonService.instance?.daemonChangeEvents?.observe(viewLifecycleOwner) { daemon ->
+ DaemonService.instance?.daemonChangeEvents?.observe(this) { daemon ->
lifecycleScope.launch(Dispatchers.IO) {
Log.d("HomeFragment", "Updating daemon, restarting wallet. daemon=$daemon")
WalletManager.instance?.setDaemon(daemon)
@@ -94,7 +77,7 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
}
}
- balanceService?.balanceInfo?.observe(viewLifecycleOwner) { balanceInfo ->
+ balanceService?.balanceInfo?.observe(this) { balanceInfo ->
if (balanceInfo != null) {
unlockedBalanceTextView.text = balanceInfo.unlockedDisplay
if (balanceInfo.lockedDisplay == Constants.STREET_MODE_BALANCE || balanceInfo.isLockedBalanceZero) {
@@ -108,9 +91,9 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
}
}
}
- val progressBar = view.findViewById(R.id.sync_progress_bar)
- val progressBarText = view.findViewById(R.id.sync_progress_text)
- blockchainService?.height?.observe(viewLifecycleOwner) { height: Long ->
+ val progressBar = findViewById(R.id.sync_progress_bar)
+ val progressBarText = findViewById(R.id.sync_progress_text)
+ blockchainService?.height?.observe(this) { height: Long ->
val wallet = WalletManager.instance?.wallet
if (wallet?.isSynchronized == false) {
if (startHeight == 0L && height != 1L) {
@@ -133,10 +116,17 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
progressBarText.text = "Synchronized at $height"
}
}
- val adapter = TransactionInfoAdapter(this)
- txHistoryRecyclerView.layoutManager = LinearLayoutManager(activity)
+ val activity = this
+ val adapter = TransactionInfoAdapter(object : TransactionInfoAdapter.TxInfoAdapterListener {
+ override fun onClickTransaction(txInfo: TransactionInfo?) {
+ val intent = Intent(activity, TransactionActivity::class.java)
+ intent.putExtra(Constants.NAV_ARG_TXINFO, txInfo)
+ startActivity(intent)
+ }
+ })
+ txHistoryRecyclerView.layoutManager = LinearLayoutManager(this)
txHistoryRecyclerView.adapter = adapter
- historyService?.history?.observe(viewLifecycleOwner) { history: List ->
+ historyService?.history?.observe(this) { history: List ->
if (history.isEmpty()) {
// DISPLAYING EMPTY WALLET HISTORY
val wallet = WalletManager.instance?.wallet
@@ -149,7 +139,7 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
R.drawable.xmrchan_loading2 // img for loading
}
txHistoryRecyclerView.visibility = View.GONE
- displayEmptyHistory(true, view, textResId, botImgResId)
+ displayEmptyHistory(true, this, textResId, botImgResId)
} else {
// POPULATED WALLET HISTORY
val sortedHistory = history.sortedByDescending { it.timestamp }
@@ -161,7 +151,7 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
txHistoryRecyclerView.visibility = View.VISIBLE
displayEmptyHistory(
false,
- view,
+ this,
R.string.no_history_nget_some_monero_in_here,
R.drawable.xmrchan_loading2
)
@@ -169,7 +159,7 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
}
val samouraiTorManager = ProxyService.instance?.samouraiTorManager
- samouraiTorManager?.getTorStateLiveData()?.observe(viewLifecycleOwner) {
+ samouraiTorManager?.getTorStateLiveData()?.observe(this) {
samouraiTorManager.getProxy()?.address()?.let { socketAddress ->
if (socketAddress.toString().isEmpty()) return@let
if (ProxyService.instance?.usingProxy == true && ProxyService.instance?.useBundledTor == true) {
@@ -182,6 +172,7 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
}
}
}
+
}
private fun refreshProxy(proxyAddress: String, proxyPort: String) {
@@ -194,23 +185,9 @@ class HomeFragment : Fragment(), TxInfoAdapterListener {
}
}
- override fun onClickTransaction(txInfo: TransactionInfo?) {
- val directions: NavDirections = HomeFragmentDirections.navToTransaction(txInfo)
- navigate(directions)
- }
-
- private fun navigate(destination: NavDirections) {
- val activity = activity
- if (activity != null) {
- val fm = activity.supportFragmentManager
- val navHostFragment = fm.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
- navHostFragment?.navController?.navigate(destination)
- }
- }
-
private fun displayEmptyHistory(
display: Boolean,
- view: View,
+ view: HomeActivity,
textResId: Int,
botImgResId: Int
) {
diff --git a/app/src/main/java/net/mynero/wallet/MainActivity.kt b/app/src/main/java/net/mynero/wallet/MainActivity.kt
deleted file mode 100644
index 7b832b5..0000000
--- a/app/src/main/java/net/mynero/wallet/MainActivity.kt
+++ /dev/null
@@ -1,119 +0,0 @@
-package net.mynero.wallet
-
-import android.os.Bundle
-import android.widget.Toast
-import androidx.appcompat.app.AppCompatActivity
-import androidx.fragment.app.FragmentActivity
-import androidx.navigation.fragment.NavHostFragment
-import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog
-import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog.PasswordListener
-import net.mynero.wallet.fragment.dialog.SendBottomSheetDialog
-import net.mynero.wallet.livedata.SingleLiveEvent
-import net.mynero.wallet.model.WalletManager
-import net.mynero.wallet.service.AddressService
-import net.mynero.wallet.service.BalanceService
-import net.mynero.wallet.service.BlockchainService
-import net.mynero.wallet.service.DaemonService
-import net.mynero.wallet.service.HistoryService
-import net.mynero.wallet.service.MoneroHandlerThread
-import net.mynero.wallet.service.PrefService
-import net.mynero.wallet.service.ProxyService
-import net.mynero.wallet.service.TxService
-import net.mynero.wallet.service.UTXOService
-import net.mynero.wallet.util.Constants
-import net.mynero.wallet.util.UriData
-import java.io.File
-
-class MainActivity : AppCompatActivity(), MoneroHandlerThread.Listener, PasswordListener {
- val restartEvents: SingleLiveEvent<*> = SingleLiveEvent()
- var thread: MoneroHandlerThread? = null
- private set
- private var balanceService: BalanceService? = null
- private var addressService: AddressService? = null
- private var historyService: HistoryService? = null
- private var daemonService: DaemonService? = null
- private var blockchainService: BlockchainService? = null
- private var utxoService: UTXOService? = null
- private var proceedToSend = false
- private var uriData: UriData? = null
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
- setContentView(R.layout.activity_main)
- val walletFile = File(applicationInfo.dataDir, Constants.WALLET_NAME)
- val walletKeysFile = File(applicationInfo.dataDir, Constants.WALLET_NAME + ".keys")
- if (walletKeysFile.exists()) {
- val promptPassword =
- PrefService.instance?.getBoolean(Constants.PREF_USES_PASSWORD, false) == true
- if (!promptPassword) {
- init(walletFile, "")
- } else {
- val passwordDialog = PasswordBottomSheetDialog()
- passwordDialog.listener = this
- passwordDialog.show(supportFragmentManager, "password_dialog")
- }
- val intent = intent
- val uri = intent.data
- if (uri != null) {
- uriData = UriData.parse(uri.toString())
- if (uriData != null) {
- proceedToSend = true
- }
- }
- } else {
- navigate(R.id.onboarding_fragment)
- }
- }
-
- private fun navigate(destination: Int) {
- val activity: FragmentActivity = this
- val fm = activity.supportFragmentManager
- val navHostFragment = fm.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
- navHostFragment?.navController?.navigate(destination)
- }
-
- fun init(walletFile: File, password: String?) {
- val wallet = WalletManager.instance?.openWallet(walletFile.absolutePath, password ?: "")
- thread = wallet?.let { MoneroHandlerThread("WalletService", this, it) }
- thread?.let { thread ->
- TxService(thread)
- balanceService = BalanceService(thread)
- addressService = AddressService(thread)
- historyService = HistoryService(thread)
- blockchainService = BlockchainService(thread)
- daemonService = DaemonService(thread)
- utxoService = UTXOService(thread)
- thread.start()
- }
- }
-
- override fun onRefresh(walletSynced: Boolean) {
- if (walletSynced) utxoService?.refreshUtxos()
- historyService?.refreshHistory()
- balanceService?.refreshBalance()
- blockchainService?.refreshBlockchain()
- addressService?.refreshAddresses()
- }
-
- override fun onConnectionFail() {
- runOnUiThread {
- Toast.makeText(application, R.string.connection_failed, Toast.LENGTH_SHORT).show()
- }
- }
-
- override fun onPasswordSuccess(password: String) {
- val walletFile = File(applicationInfo.dataDir, Constants.WALLET_NAME)
- init(walletFile, password)
- restartEvents.call()
- if (proceedToSend) {
- val sendDialog = SendBottomSheetDialog()
- sendDialog.uriData = uriData
- sendDialog.show(supportFragmentManager, null)
- }
- }
-
- override fun onPasswordFail() {
- runOnUiThread {
- Toast.makeText(application, R.string.bad_password, Toast.LENGTH_SHORT).show()
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/OnboardingActivity.kt b/app/src/main/java/net/mynero/wallet/OnboardingActivity.kt
new file mode 100644
index 0000000..4ca125c
--- /dev/null
+++ b/app/src/main/java/net/mynero/wallet/OnboardingActivity.kt
@@ -0,0 +1,608 @@
+package net.mynero.wallet
+
+import android.app.Activity
+import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.View
+import android.widget.Button
+import android.widget.CheckBox
+import android.widget.CompoundButton
+import android.widget.EditText
+import android.widget.ImageView
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.OnBackPressedCallback
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.SwitchCompat
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.progressindicator.CircularProgressIndicator
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+import net.mynero.wallet.data.Node
+import net.mynero.wallet.fragment.dialog.AddNodeBottomSheetDialog
+import net.mynero.wallet.fragment.dialog.NodeSelectionBottomSheetDialog
+import net.mynero.wallet.livedata.combineLiveDatas
+import net.mynero.wallet.model.EnumTorState
+import net.mynero.wallet.model.Wallet
+import net.mynero.wallet.model.WalletManager
+import net.mynero.wallet.service.MoneroHandlerThread
+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
+import java.util.Calendar
+
+class OnboardingActivity : AppCompatActivity() {
+
+ private lateinit var mViewModel: OnboardingViewModel
+ private lateinit var walletProxyAddressEditText: EditText
+ private lateinit var walletProxyPortEditText: EditText
+ private lateinit var walletPasswordEditText: EditText
+ private lateinit var walletPasswordConfirmEditText: EditText
+ private lateinit var walletSeedEditText: EditText
+ private lateinit var walletRestoreHeightEditText: EditText
+ private lateinit var createWalletButton: Button
+ private lateinit var moreOptionsDropdownTextView: TextView
+ private lateinit var torSwitch: SwitchCompat
+ private lateinit var advancedOptionsLayout: ConstraintLayout
+ private lateinit var moreOptionsChevronImageView: ImageView
+ private lateinit var seedOffsetCheckbox: CheckBox
+ private lateinit var selectNodeButton: Button
+ private lateinit var showXmrchanSwitch: SwitchCompat
+ private lateinit var xmrchanOnboardingImage: ImageView
+ private lateinit var seedTypeButton: Button
+ private lateinit var seedTypeDescTextView: TextView
+ private lateinit var useBundledTor: CheckBox
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_onboarding)
+
+ mViewModel = ViewModelProvider(this)[OnboardingViewModel::class.java]
+ selectNodeButton = findViewById(R.id.select_node_button)
+ walletPasswordEditText = findViewById(R.id.wallet_password_edittext)
+ walletPasswordConfirmEditText = findViewById(R.id.wallet_password_confirm_edittext)
+ walletSeedEditText = findViewById(R.id.wallet_seed_edittext)
+ walletRestoreHeightEditText = findViewById(R.id.wallet_restore_height_edittext)
+ createWalletButton = findViewById(R.id.create_wallet_button)
+ moreOptionsDropdownTextView = findViewById(R.id.advanced_settings_dropdown_textview)
+ moreOptionsChevronImageView = findViewById(R.id.advanced_settings_chevron_imageview)
+ torSwitch = findViewById(R.id.tor_onboarding_switch)
+ seedOffsetCheckbox = findViewById(R.id.seed_offset_checkbox)
+ walletProxyAddressEditText = findViewById(R.id.wallet_proxy_address_edittext)
+ walletProxyPortEditText = findViewById(R.id.wallet_proxy_port_edittext)
+ advancedOptionsLayout = findViewById(R.id.more_options_layout)
+ showXmrchanSwitch = findViewById(R.id.show_xmrchan_switch)
+ xmrchanOnboardingImage = findViewById(R.id.xmrchan_onboarding_imageview)
+ seedTypeButton = findViewById(R.id.seed_type_button)
+ seedTypeDescTextView = findViewById(R.id.seed_type_desc_textview)
+ useBundledTor = findViewById(R.id.bundled_tor_checkbox)
+
+ seedOffsetCheckbox.isChecked = mViewModel.useOffset
+ val usingProxy = ProxyService.instance?.usingProxy == true
+ val usingBundledTor = ProxyService.instance?.useBundledTor == true
+
+ torSwitch.isChecked = usingProxy
+ useBundledTor.isChecked = usingBundledTor
+ useBundledTor.isEnabled = usingProxy
+ walletProxyAddressEditText.isEnabled = usingProxy && !usingBundledTor
+ walletProxyPortEditText.isEnabled = usingProxy && !usingBundledTor
+
+ val node = PrefService.instance?.node // should be using default here
+ selectNodeButton.text = getString(R.string.node_button_text, node?.address)
+
+ bindListeners()
+ bindObservers()
+ }
+
+ private fun bindObservers() {
+ mViewModel.passphrase.observe(this) { text ->
+ if (text.isEmpty()) {
+ walletPasswordConfirmEditText.text = null
+ walletPasswordConfirmEditText.visibility = View.GONE
+ } else {
+ walletPasswordConfirmEditText.visibility = View.VISIBLE
+ }
+ }
+
+ mViewModel.showMoreOptions.observe(this) { show: Boolean ->
+ if (show) {
+ moreOptionsChevronImageView.setImageResource(R.drawable.ic_keyboard_arrow_up)
+ advancedOptionsLayout.visibility = View.VISIBLE
+ } else {
+ moreOptionsChevronImageView.setImageResource(R.drawable.ic_keyboard_arrow_down)
+ advancedOptionsLayout.visibility = View.GONE
+ }
+ }
+
+ mViewModel.enableButton.observe(this) { enable: Boolean ->
+ createWalletButton.isEnabled = enable
+ }
+
+ mViewModel.seedType.observe(this) { seedType: OnboardingViewModel.SeedType ->
+ seedTypeButton.text = seedType.toString()
+ seedTypeDescTextView.text = getText(seedType.descResId)
+ if (seedType == OnboardingViewModel.SeedType.LEGACY) {
+ seedOffsetCheckbox.visibility = View.VISIBLE
+ walletRestoreHeightEditText.visibility = View.VISIBLE
+ walletPasswordEditText.hint = getString(R.string.password_optional)
+ walletSeedEditText.hint = getString(R.string.recovery_phrase_optional_legacy)
+ } else {
+ seedOffsetCheckbox.visibility = View.GONE
+ walletRestoreHeightEditText.visibility = View.GONE
+ walletPasswordEditText.hint = getString(R.string.password_non_optional)
+ walletSeedEditText.hint = getString(R.string.recovery_phrase_optional_polyseed)
+ }
+ }
+
+ mViewModel.showMonerochan.observe(this) {
+ if (it) {
+ xmrchanOnboardingImage.visibility = View.VISIBLE
+ } else {
+ xmrchanOnboardingImage.visibility = View.GONE
+ }
+ }
+
+ mViewModel.useBundledTor.observe(this) { isChecked ->
+ walletProxyPortEditText.isEnabled = !isChecked && mViewModel.useProxy.value == true
+ walletProxyAddressEditText.isEnabled = !isChecked && mViewModel.useProxy.value == true
+ }
+
+ mViewModel.useProxy.observe(this) { useProxy ->
+ useBundledTor.isEnabled = useProxy
+ walletProxyAddressEditText.isEnabled = useProxy && mViewModel.useBundledTor.value == false
+ walletProxyPortEditText.isEnabled = useProxy && mViewModel.useBundledTor.value == false
+ }
+
+ val samouraiTorManager = ProxyService.instance?.samouraiTorManager
+ val indicatorCircle = findViewById(R.id.onboarding_tor_loading_progressindicator)
+ val torIcon = findViewById(R.id.onboarding_tor_icon)
+
+ samouraiTorManager?.getTorStateLiveData()?.observe(this) { state ->
+ samouraiTorManager.getProxy()?.address()?.let { socketAddress ->
+ if (socketAddress.toString().isEmpty()) return@let
+ if (mViewModel.useProxy.value == true && mViewModel.useBundledTor.value == true) {
+ torIcon?.visibility = View.VISIBLE
+ indicatorCircle?.visibility = View.INVISIBLE
+ val proxyString = socketAddress.toString().substring(1)
+ val address = proxyString.split(":")[0]
+ val port = proxyString.split(":")[1]
+ updateProxy(address, port)
+ }
+ }
+
+ indicatorCircle.isIndeterminate = state.progressIndicator == 0
+ indicatorCircle.progress = state.progressIndicator
+
+ when (state.state) {
+ EnumTorState.OFF -> {
+ torIcon.visibility = View.INVISIBLE
+ indicatorCircle.visibility = View.INVISIBLE
+ }
+
+ EnumTorState.STARTING, EnumTorState.STOPPING -> {
+ torIcon.visibility = View.INVISIBLE
+ indicatorCircle.visibility = View.VISIBLE
+ }
+
+ else -> {}
+ }
+ }
+ }
+
+ private fun updateProxy(address: String, port: String) {
+ walletProxyPortEditText.setText(port)
+ walletProxyAddressEditText.setText(address)
+ }
+
+ private fun bindListeners() {
+ // Disable onBack click
+ val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {}
+ }
+ onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+
+ moreOptionsDropdownTextView.setOnClickListener { mViewModel.onMoreOptionsClicked() }
+
+ moreOptionsChevronImageView.setOnClickListener { mViewModel.onMoreOptionsClicked() }
+
+ seedOffsetCheckbox.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
+ mViewModel.useOffset = b
+ }
+
+ createWalletButton.setOnClickListener {
+ onBackPressedCallback.isEnabled = false
+ createOrImportWallet(
+ walletSeedEditText.text.toString().trim { it <= ' ' },
+ walletRestoreHeightEditText.text.toString().trim { it <= ' ' }
+ )
+ }
+
+ walletPasswordEditText.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
+ override fun afterTextChanged(editable: Editable) {
+ mViewModel.setPassphrase(editable.toString())
+ }
+ })
+
+ 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) {
+ mViewModel.setConfirmedPassphrase(editable.toString())
+ }
+ })
+
+ walletSeedEditText.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
+ override fun afterTextChanged(editable: Editable) {
+ val text = editable.toString()
+ if (text.isEmpty()) {
+ createWalletButton.setText(R.string.create_wallet)
+ } else {
+ createWalletButton.setText(R.string.menu_restore)
+ }
+ }
+ })
+
+ seedTypeButton.setOnClickListener { toggleSeedType() }
+
+ torSwitch.setOnCheckedChangeListener { _, b: Boolean ->
+ mViewModel.setUseProxy(b)
+ }
+
+ walletProxyPortEditText.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
+ override fun afterTextChanged(editable: Editable) {
+ val text = editable.toString()
+ mViewModel.setProxyPort(text)
+ }
+ })
+
+ walletProxyAddressEditText.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
+ override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
+ override fun afterTextChanged(editable: Editable) {
+ val text = editable.toString()
+ mViewModel.setProxyAddress(text)
+ }
+ })
+
+ showXmrchanSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
+ mViewModel.setMonerochan(b)
+ }
+
+ selectNodeButton.setOnClickListener {
+ val activity = this
+ supportFragmentManager.let { fragmentManager ->
+ val dialog = NodeSelectionBottomSheetDialog()
+ dialog.listener = object : NodeSelectionBottomSheetDialog.NodeSelectionDialogListener {
+ override fun onNodeSelected() {
+ val node = PrefService.instance?.node
+ selectNodeButton.text = getString(R.string.node_button_text, node?.address)
+ Toast.makeText(
+ activity,
+ getString(R.string.node_selected, node?.name ?: node?.host),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ override fun onClickedEditNode(node: Node?) {}
+ override fun onClickedAddNode() {}
+ }
+ dialog.show(fragmentManager, "node_selection_dialog")
+ }
+ }
+
+ useBundledTor.setOnCheckedChangeListener { _, isChecked ->
+ mViewModel.setUseBundledTor(isChecked)
+ }
+ }
+
+ private fun toggleSeedType() {
+ val seedType = mViewModel.seedType.value ?: return
+ var newSeedType = OnboardingViewModel.SeedType.UNKNOWN
+ if (seedType == OnboardingViewModel.SeedType.POLYSEED) {
+ newSeedType = OnboardingViewModel.SeedType.LEGACY
+ } else if (seedType == OnboardingViewModel.SeedType.LEGACY) {
+ newSeedType = OnboardingViewModel.SeedType.POLYSEED
+ }
+ mViewModel.setSeedType(newSeedType)
+ }
+
+ private fun createOrImportWallet(
+ walletSeed: String,
+ restoreHeightText: String
+ ) {
+ this.let { act ->
+ lifecycleScope.launch(Dispatchers.IO) {
+ mViewModel.createOrImportWallet(
+ act,
+ walletSeed,
+ restoreHeightText,
+ mViewModel.useOffset,
+ applicationContext
+ )
+ }
+ }
+ }
+}
+
+internal class OnboardingViewModel : ViewModel() {
+ var useOffset = true
+ private val _showMoreOptions = MutableLiveData(false)
+ private val _creatingWallet = MutableLiveData(false)
+ private val _seedType = MutableLiveData(SeedType.POLYSEED)
+ private val _useProxy = MutableLiveData(false)
+ val useProxy: LiveData = _useProxy
+ private val _proxyAddress = MutableLiveData("")
+ private val _proxyPort = MutableLiveData("")
+ private val _useBundledTor = MutableLiveData(false)
+ val useBundledTor: LiveData = _useBundledTor
+ private val _passphrase = MutableLiveData("")
+ val passphrase: LiveData = _passphrase
+ private val _confirmedPassphrase = MutableLiveData("")
+ private val _showMonerochan = MutableLiveData(Constants.DEFAULT_PREF_MONEROCHAN)
+ val showMonerochan: LiveData = _showMonerochan
+ var showMoreOptions: LiveData = _showMoreOptions
+ var seedType: LiveData = _seedType
+
+ init {
+ _useProxy.value = ProxyService.instance?.usingProxy
+ _useBundledTor.value = ProxyService.instance?.useBundledTor
+ }
+
+ val enableButton = combineLiveDatas(
+ seedType,
+ _useProxy,
+ _proxyAddress,
+ _proxyPort,
+ _useBundledTor,
+ _passphrase,
+ _confirmedPassphrase,
+ _creatingWallet,
+ ProxyService.instance?.samouraiTorManager?.getTorStateLiveData()
+ ) { seedType, useProxy, proxyAddress, proxyPort, useBundledTor, passphrase, confirmedPassphrase, creatingWallet, torState ->
+ if (seedType == null || useProxy == null || proxyAddress == null || proxyPort == null || useBundledTor == null || passphrase == null || confirmedPassphrase == null || creatingWallet == null) return@combineLiveDatas false
+ if ((passphrase.isNotEmpty() || confirmedPassphrase.isNotEmpty()) && passphrase != confirmedPassphrase) return@combineLiveDatas false
+ if (creatingWallet) return@combineLiveDatas false
+ if (seedType == SeedType.POLYSEED && (passphrase.isEmpty() || confirmedPassphrase.isEmpty())) return@combineLiveDatas false
+ if (useProxy && (proxyAddress.isEmpty() || proxyPort.isEmpty()) && !useBundledTor) return@combineLiveDatas false
+ val progress = torState?.progressIndicator ?: 0
+ if (useBundledTor && progress < 100 && useProxy) return@combineLiveDatas false
+
+ return@combineLiveDatas true
+ }
+
+ fun onMoreOptionsClicked() {
+ val currentValue = showMoreOptions.value ?: false
+ val newValue = !currentValue
+ _showMoreOptions.value = newValue
+ }
+
+ fun setSeedType(seedType: SeedType?) {
+ _seedType.value = seedType
+ }
+
+ fun createOrImportWallet(
+ mainActivity: Activity,
+ walletSeed: String,
+ restoreHeightText: String,
+ useOffset: Boolean,
+ context: Context
+ ) {
+ val passphrase = _passphrase.value ?: return
+ val confirmedPassphrase = _confirmedPassphrase.value ?: return
+
+ val application = mainActivity.application as MoneroApplication
+ _creatingWallet.postValue(true)
+ val offset = if (useOffset) confirmedPassphrase else ""
+ if (passphrase.isNotEmpty()) {
+ if (passphrase != confirmedPassphrase) {
+ _creatingWallet.postValue(false)
+ mainActivity.runOnUiThread {
+ Toast.makeText(
+ mainActivity,
+ application.getString(R.string.invalid_confirmed_password),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ return
+ }
+ PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_PASSWORD, true)
+ ?.apply()
+ }
+ var restoreHeight = newRestoreHeight
+ val walletFile = File(mainActivity.applicationInfo.dataDir, Constants.WALLET_NAME)
+ var wallet: Wallet? = null
+ if (offset.isNotEmpty()) {
+ PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_OFFSET, true)?.apply()
+ }
+ val seedTypeValue = seedType.value ?: return
+ if (walletSeed.isEmpty()) {
+ if (seedTypeValue == SeedType.POLYSEED) {
+ wallet = if (offset.isEmpty()) {
+ mainActivity.runOnUiThread {
+ _creatingWallet.postValue(false)
+ Toast.makeText(
+ mainActivity,
+ application.getString(R.string.invalid_empty_passphrase),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ return
+ } else {
+ WalletManager.instance?.createWalletPolyseed(
+ walletFile,
+ passphrase,
+ offset,
+ Constants.MNEMONIC_LANGUAGE
+ )
+ }
+ } else if (seedTypeValue == SeedType.LEGACY) {
+ val tmpWalletFile =
+ File(mainActivity.applicationInfo.dataDir, Constants.WALLET_NAME + "_tmp")
+ val tmpWallet =
+ createTempWallet(tmpWalletFile) //we do this to get seed, then recover wallet so we can use seed offset
+ tmpWallet?.let {
+ wallet = WalletManager.instance?.recoveryWallet(
+ walletFile,
+ passphrase,
+ tmpWallet.getSeed("") ?: return@let,
+ offset,
+ restoreHeight
+ )
+ tmpWalletFile.delete()
+ }
+ }
+ } else {
+ if (getMnemonicType(walletSeed) == SeedType.UNKNOWN) {
+ mainActivity.runOnUiThread {
+ _creatingWallet.postValue(false)
+ Toast.makeText(
+ mainActivity,
+ application.getString(R.string.invalid_mnemonic_code),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ return
+ }
+ if (restoreHeightText.isNotEmpty()) {
+ restoreHeight = restoreHeightText.toLong()
+ }
+ if (seedTypeValue == SeedType.POLYSEED) {
+ wallet = WalletManager.instance?.recoveryWalletPolyseed(
+ walletFile,
+ passphrase,
+ walletSeed,
+ offset
+ )
+ } else if (seedTypeValue == SeedType.LEGACY) {
+ wallet = WalletManager.instance?.recoveryWallet(
+ walletFile,
+ passphrase,
+ walletSeed,
+ offset,
+ restoreHeight
+ )
+ }
+ }
+ val walletStatus = wallet?.status
+ wallet?.close()
+ 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) {
+ println("KEK")
+ MoneroHandlerThread.init(walletFile, passphrase, context)
+ val intent = Intent(mainActivity, HomeActivity::class.java)
+ mainActivity.startActivity(intent)
+ mainActivity.finish()
+ } else {
+ mainActivity.runOnUiThread {
+ _creatingWallet.postValue(false)
+ Toast.makeText(
+ mainActivity,
+ application.getString(
+ R.string.create_wallet_failed,
+ walletStatus?.errorString
+ ),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+
+ private val newRestoreHeight: Long
+ get() {
+ val restoreDate = Calendar.getInstance()
+ restoreDate.add(Calendar.DAY_OF_MONTH, 0)
+ return RestoreHeight.instance?.getHeight(restoreDate.time) ?: 0
+ }
+
+ private fun getMnemonicType(seed: String): SeedType {
+ val words = seed.split("\\s".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
+ val seedTypeValue = seedType.value ?: return SeedType.LEGACY
+ return if (words.size == 16 && seedTypeValue == SeedType.POLYSEED) {
+ SeedType.POLYSEED
+ } else if (words.size == 25 && seedTypeValue == SeedType.LEGACY) {
+ SeedType.LEGACY
+ } else {
+ SeedType.UNKNOWN
+ }
+ }
+
+ private fun createTempWallet(tmpWalletFile: File): Wallet? {
+ return WalletManager.instance?.createWallet(
+ tmpWalletFile,
+ "",
+ Constants.MNEMONIC_LANGUAGE,
+ 0
+ )
+ }
+
+ fun setProxyAddress(address: String) {
+ _proxyAddress.value = address
+ if (address.isEmpty()) PrefService.instance?.deleteProxy()
+ val port = _proxyPort.value ?: return
+ ProxyService.instance?.updateProxy(address, port)
+ }
+
+ fun setProxyPort(port: String) {
+ _proxyPort.value = port
+ if (port.isEmpty()) PrefService.instance?.deleteProxy()
+ val address = _proxyAddress.value ?: return
+ ProxyService.instance?.updateProxy(address, port)
+ }
+
+ fun setUseBundledTor(useBundled: Boolean) {
+ _useBundledTor.value = useBundled
+ ProxyService.instance?.useBundledTor = useBundled
+
+ val samouraiTorManager = ProxyService.instance?.samouraiTorManager
+ if (useBundled && ProxyService.instance?.usingProxy == true) {
+ samouraiTorManager?.start()
+ } else {
+ samouraiTorManager?.stop()
+ }
+ }
+
+ fun setUseProxy(useProxy: Boolean) {
+ _useProxy.value = useProxy
+ ProxyService.instance?.usingProxy = useProxy
+
+ val samouraiTorManager = ProxyService.instance?.samouraiTorManager
+ if (useProxy && ProxyService.instance?.useBundledTor == true) {
+ samouraiTorManager?.start()
+ } else {
+ samouraiTorManager?.stop()
+ }
+ }
+
+ fun setPassphrase(passphrase: String) {
+ _passphrase.value = passphrase
+ }
+
+ fun setConfirmedPassphrase(confirmedPassphrase: String) {
+ _confirmedPassphrase.value = confirmedPassphrase
+ }
+
+ fun setMonerochan(b: Boolean) {
+ _showMonerochan.value = b
+ PrefService.instance?.edit()?.putBoolean(Constants.PREF_MONEROCHAN, b)?.apply()
+ }
+
+ enum class SeedType(val descResId: Int) {
+ LEGACY(R.string.seed_desc_legacy), POLYSEED(R.string.seed_desc_polyseed), UNKNOWN(0)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/PasswordActivity.kt b/app/src/main/java/net/mynero/wallet/PasswordActivity.kt
new file mode 100644
index 0000000..07c2355
--- /dev/null
+++ b/app/src/main/java/net/mynero/wallet/PasswordActivity.kt
@@ -0,0 +1,64 @@
+package net.mynero.wallet
+
+import android.content.Intent
+import android.os.Bundle
+import android.widget.Button
+import android.widget.EditText
+import android.widget.Toast
+import androidx.activity.OnBackPressedCallback
+import androidx.appcompat.app.AppCompatActivity
+import net.mynero.wallet.model.WalletManager
+import net.mynero.wallet.util.Constants
+import java.io.File
+
+// Shows a password prompt
+// Finishes and returns the wallet's name and password in extra when user enters valid password
+class PasswordActivity : AppCompatActivity() {
+
+ private var preventGoingBack = false
+ private lateinit var passwordEdittext: EditText
+ private lateinit var unlockButton: Button
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_password)
+
+ preventGoingBack = intent.getBooleanExtra(Constants.EXTRA_PREVENT_GOING_BACK, false)
+
+ passwordEdittext = findViewById(R.id.wallet_password_edittext)
+ unlockButton = findViewById(R.id.unlock_wallet_button)
+
+ onBackPressedDispatcher.addCallback(this, object: OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ if (!preventGoingBack) {
+ finish()
+ }
+ }
+ })
+
+ val walletFile = File(applicationInfo.dataDir, Constants.WALLET_NAME)
+
+ unlockButton.setOnClickListener {
+ onUnlockClicked(walletFile, passwordEdittext.text.toString())
+ }
+ }
+
+ private fun onUnlockClicked(walletFile: File, password: String) {
+ if (checkPassword(walletFile, password)) {
+ val intent = Intent()
+ intent.putExtra(Constants.EXTRA_WALLET_NAME, walletFile.name)
+ intent.putExtra(Constants.EXTRA_WALLET_PASSWORD, password)
+ setResult(RESULT_OK, intent)
+ finish()
+ } else {
+ Toast.makeText(application, R.string.bad_password, Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ private fun checkPassword(walletFile: File, password: String): Boolean {
+ return WalletManager.instance?.verifyWalletPasswordOnly(
+ walletFile.absolutePath + ".keys",
+ password
+ ) == true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveFragment.kt b/app/src/main/java/net/mynero/wallet/ReceiveActivity.kt
similarity index 51%
rename from app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveFragment.kt
rename to app/src/main/java/net/mynero/wallet/ReceiveActivity.kt
index d7fb45c..81de65c 100644
--- a/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveFragment.kt
+++ b/app/src/main/java/net/mynero/wallet/ReceiveActivity.kt
@@ -1,16 +1,16 @@
-package net.mynero.wallet.fragment.receive
+package net.mynero.wallet
import android.graphics.Bitmap
import android.os.Bundle
import android.util.Log
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
-import androidx.fragment.app.Fragment
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
@@ -19,51 +19,49 @@ import com.google.zxing.EncodeHintType
import com.google.zxing.WriterException
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
import com.journeyapps.barcodescanner.BarcodeEncoder
-import net.mynero.wallet.R
import net.mynero.wallet.adapter.SubaddressAdapter
import net.mynero.wallet.adapter.SubaddressAdapter.SubaddressAdapterListener
import net.mynero.wallet.data.Subaddress
import net.mynero.wallet.fragment.dialog.EditAddressLabelBottomSheetDialog
import net.mynero.wallet.fragment.dialog.EditAddressLabelBottomSheetDialog.LabelListener
+import net.mynero.wallet.model.WalletManager
+import net.mynero.wallet.service.AddressService
import net.mynero.wallet.util.Helper.clipBoardCopy
import java.nio.charset.StandardCharsets
+import java.util.Collections
import java.util.EnumMap
-class ReceiveFragment : Fragment() {
- private var addressTextView: TextView? = null
- private var addressLabelTextView: TextView? = null
- private var addressImageView: ImageView? = null
- private var copyAddressImageButton: ImageButton? = null
- private var mViewModel: ReceiveViewModel? = null
+class ReceiveActivity : AppCompatActivity() {
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.fragment_receive, container, false)
- }
+ private lateinit var mViewModel: ReceiveViewModel
+ private lateinit var addressTextView: TextView
+ private lateinit var addressLabelTextView: TextView
+ private lateinit var addressImageView: ImageView
+ private lateinit var copyAddressImageButton: ImageButton
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_receive)
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
mViewModel = ViewModelProvider(this)[ReceiveViewModel::class.java]
- addressImageView = view.findViewById(R.id.monero_qr_imageview)
- addressTextView = view.findViewById(R.id.address_textview)
- addressLabelTextView = view.findViewById(R.id.address_label_textview)
- copyAddressImageButton = view.findViewById(R.id.copy_address_imagebutton)
- bindListeners(view)
- bindObservers(view)
- mViewModel?.init()
+ addressImageView = findViewById(R.id.monero_qr_imageview)
+ addressTextView = findViewById(R.id.address_textview)
+ addressLabelTextView = findViewById(R.id.address_label_textview)
+ copyAddressImageButton = findViewById(R.id.copy_address_imagebutton)
+ bindListeners()
+ bindObservers()
+ mViewModel.init()
}
- private fun bindListeners(view: View) {
- val freshAddressImageView = view.findViewById(R.id.fresh_address_imageview)
- freshAddressImageView.setOnClickListener { mViewModel?.freshSubaddress }
+ private fun bindListeners() {
+ val freshAddressImageView = findViewById(R.id.fresh_address_imageview)
+ freshAddressImageView.setOnClickListener { mViewModel.freshSubaddress }
}
- private fun bindObservers(view: View) {
+ private fun bindObservers() {
val subaddressAdapterListener = object : SubaddressAdapterListener {
override fun onSubaddressSelected(subaddress: Subaddress?) {
- mViewModel?.selectAddress(subaddress)
+ mViewModel.selectAddress(subaddress)
}
override fun onSubaddressEditLabel(subaddress: Subaddress?) {
@@ -71,14 +69,14 @@ class ReceiveFragment : Fragment() {
}
}
val adapter = SubaddressAdapter(emptyList(), null, subaddressAdapterListener)
- val recyclerView = view.findViewById(R.id.address_list_recyclerview)
- recyclerView.layoutManager = LinearLayoutManager(activity)
+ val recyclerView = findViewById(R.id.address_list_recyclerview)
+ recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = adapter
- mViewModel?.address?.observe(viewLifecycleOwner) { address: Subaddress? ->
+ mViewModel.address.observe(this) { address: Subaddress? ->
setAddress(address)
adapter.submitSelectedAddress(address)
}
- mViewModel?.addresses?.observe(viewLifecycleOwner) { addresses: List ->
+ mViewModel.addresses.observe(this) { addresses: List ->
// We want newer addresses addresses to be shown first
adapter.submitAddresses(addresses.reversed())
}
@@ -89,31 +87,31 @@ class ReceiveFragment : Fragment() {
dialog.addressIndex = subaddress?.addressIndex ?: return
dialog.listener = object : LabelListener {
override fun onDismiss() {
- mViewModel?.init()
+ mViewModel.init()
}
}
- dialog.show(parentFragmentManager, "edit_address_dialog")
+ dialog.show(supportFragmentManager, "edit_address_dialog")
}
private fun setAddress(subaddress: Subaddress?) {
val label = subaddress?.displayLabel
- val address = context?.getString(
+ val address = getString(
R.string.subbaddress_info_subtitle,
subaddress?.addressIndex, subaddress?.squashedAddress
)
- addressLabelTextView?.text = if (label?.isEmpty() == true) address else label
- addressTextView?.text = subaddress?.address
- addressImageView?.setImageBitmap(subaddress?.address?.let { generate(it, 256, 256) })
- copyAddressImageButton?.setOnClickListener {
+ addressLabelTextView.text = if (label?.isEmpty() == true) address else label
+ addressTextView.text = subaddress?.address
+ addressImageView.setImageBitmap(subaddress?.address?.let { generate(it, 256, 256) })
+ copyAddressImageButton.setOnClickListener {
clipBoardCopy(
- context, "address", subaddress?.address
+ this, "address", subaddress?.address
)
}
- addressLabelTextView?.setOnLongClickListener {
+ addressLabelTextView.setOnLongClickListener {
editAddressLabel(subaddress)
true
}
- addressTextView?.setOnLongClickListener {
+ addressTextView.setOnLongClickListener {
editAddressLabel(subaddress)
true
}
@@ -135,10 +133,7 @@ class ReceiveFragment : Fragment() {
if (bitMatrix[j, i]) {
pixels[i * width + j] = -0x1
} else {
- context?.let { ctx ->
- pixels[i * height + j] =
- ContextCompat.getColor(ctx, R.color.oled_colorBackground)
- }
+ pixels[i * height + j] = ContextCompat.getColor(this, R.color.oled_colorBackground)
}
}
}
@@ -148,4 +143,37 @@ class ReceiveFragment : Fragment() {
}
return null
}
+}
+
+class ReceiveViewModel : ViewModel() {
+ private val _address = MutableLiveData()
+ private val _addresses = MutableLiveData>()
+ val address: LiveData = _address
+ val addresses: LiveData> = _addresses
+
+ fun init() {
+ _addresses.value = subaddresses
+ _address.value = addresses.value?.lastOrNull()
+ }
+
+ private val subaddresses: List
+ get() {
+ val wallet = WalletManager.instance?.wallet
+ val subaddresses = ArrayList()
+ val numAddresses = AddressService.instance?.numAddresses ?: 1
+ for (i in 0 until numAddresses) {
+ wallet?.getSubaddressObject(i)?.let { subaddresses.add(it) }
+ }
+ return Collections.unmodifiableList(subaddresses)
+ }
+
+ val freshSubaddress: Unit
+ get() {
+ _address.value = AddressService.instance?.freshSubaddress()
+ _addresses.value = subaddresses
+ }
+
+ fun selectAddress(subaddress: Subaddress?) {
+ _address.value = subaddress
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/SendActivity.kt b/app/src/main/java/net/mynero/wallet/SendActivity.kt
new file mode 100644
index 0000000..46f25d8
--- /dev/null
+++ b/app/src/main/java/net/mynero/wallet/SendActivity.kt
@@ -0,0 +1,596 @@
+package net.mynero.wallet
+
+import android.app.Activity
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.view.View
+import android.widget.Button
+import android.widget.EditText
+import android.widget.ImageButton
+import android.widget.LinearLayout
+import android.widget.RadioGroup
+import android.widget.TextView
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.view.ViewCompat
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.google.zxing.client.android.Intents
+import com.journeyapps.barcodescanner.ScanContract
+import com.journeyapps.barcodescanner.ScanIntentResult
+import com.journeyapps.barcodescanner.ScanOptions
+import com.ncorti.slidetoact.SlideToActView
+import com.ncorti.slidetoact.SlideToActView.OnSlideCompleteListener
+import net.mynero.wallet.model.PendingTransaction
+import net.mynero.wallet.model.Wallet
+import net.mynero.wallet.service.BalanceService
+import net.mynero.wallet.service.TxService
+import net.mynero.wallet.service.UTXOService
+import net.mynero.wallet.util.Constants
+import net.mynero.wallet.util.Helper
+import net.mynero.wallet.util.UriData
+
+class SendActivity : AppCompatActivity() {
+
+ var priority: PendingTransaction.Priority = PendingTransaction.Priority.Priority_Low
+ private lateinit var mViewModel: SendViewModel
+ private lateinit var sendMaxButton: Button
+ private lateinit var addOutputImageView: ImageButton
+ private lateinit var destList: LinearLayout
+ private lateinit var createButton: Button
+ private lateinit var sendTxSlider: SlideToActView
+ private lateinit var feeRadioGroup: RadioGroup
+ private lateinit var feeRadioGroupLabelTextView: TextView
+ private lateinit var feeTextView: TextView
+ private lateinit var addressTextView: TextView
+ private lateinit var amountTextView: TextView
+ private lateinit var selectedUtxosValueTextView: TextView
+ private var currentEntryIndex = -1
+ private val qrCodeLauncher =
+ registerForActivityResult(ScanContract()) { result: ScanIntentResult ->
+ if (result.contents != null) {
+ if (currentEntryIndex != -1) {
+ pasteAddress(getDestView(currentEntryIndex), result.contents, false)
+ currentEntryIndex = -1
+ }
+ }
+ }
+ private val cameraPermissionsLauncher = registerForActivityResult(
+ ActivityResultContracts.RequestPermission()
+ ) { granted: Boolean ->
+ if (granted) {
+ onScan(currentEntryIndex)
+ } else {
+ Toast.makeText(this, getString(R.string.no_camera_permission), Toast.LENGTH_SHORT)
+ .show()
+ currentEntryIndex = -1
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_send)
+
+ mViewModel = ViewModelProvider(this)[SendViewModel::class.java]
+ sendMaxButton = findViewById(R.id.send_max_button)
+ addOutputImageView = findViewById(R.id.add_output_button)
+ destList = findViewById(R.id.transaction_destination_list)
+ createButton = findViewById(R.id.create_tx_button)
+ feeRadioGroup = findViewById(R.id.tx_fee_radiogroup)
+ feeTextView = findViewById(R.id.fee_textview)
+ sendTxSlider = findViewById(R.id.send_tx_slider)
+ addressTextView = findViewById(R.id.address_pending_textview)
+ amountTextView = findViewById(R.id.amount_pending_textview)
+ feeRadioGroup = findViewById(R.id.tx_fee_radiogroup)
+ feeRadioGroupLabelTextView = findViewById(R.id.tx_fee_radiogroup_label_textview)
+ selectedUtxosValueTextView = findViewById(R.id.selected_utxos_value_textview)
+
+ bindListeners()
+ bindObservers()
+ init()
+
+ val selectedUtxos = mViewModel.utxos.value
+ if (selectedUtxos?.isNotEmpty() == true) {
+ var selectedValue: Long = 0
+ val utxos = UTXOService.instance?.getUtxos() ?: return
+ for (coinsInfo in utxos) {
+ if (selectedUtxos.contains(coinsInfo.keyImage)) {
+ selectedValue += coinsInfo.amount
+ }
+ }
+ val valueString = Wallet.getDisplayAmount(selectedValue)
+ selectedUtxosValueTextView.visibility = View.VISIBLE
+ selectedUtxosValueTextView.text = resources.getString(
+ R.string.selected_utxos_value,
+ valueString
+ )
+ } else {
+ selectedUtxosValueTextView.visibility = View.GONE
+ }
+ }
+
+ private fun init() {
+ val address = intent.getStringExtra(Constants.EXTRA_SEND_ADDRESS)
+ val amount = intent.getStringExtra(Constants.EXTRA_SEND_AMOUNT)
+ val max = intent.getBooleanExtra(Constants.EXTRA_SEND_MAX, false)
+ val utxos = intent.getStringArrayListExtra(Constants.EXTRA_SEND_UTXOS) ?: ArrayList()
+ addOutput(true, address, amount)
+ mViewModel.setSendingMax(max)
+ mViewModel.setUtxos(utxos)
+ }
+
+ private fun bindListeners() {
+ feeRadioGroup.check(R.id.low_fee_radiobutton)
+ priority = PendingTransaction.Priority.Priority_Low
+ feeRadioGroup.setOnCheckedChangeListener { _: RadioGroup?, i: Int ->
+ when (i) {
+ R.id.low_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_Low
+ R.id.med_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_Medium
+ R.id.high_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_High
+ }
+ }
+ addOutputImageView.setOnClickListener {
+ sendMaxButton.visibility = View.GONE
+ val outputCount = destCount
+ if (outputCount < 8) {
+ addOutput(false)
+ } else {
+ Toast.makeText(
+ this,
+ getString(R.string.max_outputs_allowed),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ sendMaxButton.setOnClickListener { mViewModel.setSendingMax(!isSendAll) }
+ createButton.setOnClickListener {
+ val outputsValid = checkDestsValidity(isSendAll)
+ if (outputsValid) {
+ Toast.makeText(this, getString(R.string.creating_tx), Toast.LENGTH_SHORT).show()
+ createButton.isEnabled = false
+ sendMaxButton.isEnabled = false
+ createTx(rawDests, isSendAll, priority, mViewModel.utxos.value ?: ArrayList())
+ } else {
+ Toast.makeText(
+ this,
+ getString(R.string.creating_tx_failed_invalid_outputs),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ sendTxSlider.onSlideCompleteListener =
+ object : OnSlideCompleteListener {
+ override fun onSlideComplete(view: SlideToActView) {
+ confirmSlider()
+ }
+ }
+
+ sendTxSlider.let { slideToActView ->
+ ViewCompat.addAccessibilityAction(
+ slideToActView,
+ getString(R.string.approve_the_transaction)
+ ) { _, _ ->
+ confirmSlider()
+ return@addAccessibilityAction true
+ }
+ }
+ }
+
+ private fun confirmSlider() {
+ val pendingTx = mViewModel.pendingTransaction.value ?: return
+ Toast.makeText(this, getString(R.string.sending_tx), Toast.LENGTH_SHORT)
+ .show()
+ sendTx(pendingTx)
+ }
+
+ private fun checkDestsValidity(sendAll: Boolean): Boolean {
+ val dests = rawDests
+ for (dest in dests) {
+ val address = dest.component1()
+ val amount = dest.component2()
+ if (!sendAll) {
+ if (amount.isEmpty()) {
+ Toast.makeText(
+ this,
+ getString(R.string.send_amount_empty),
+ Toast.LENGTH_SHORT
+ ).show()
+ return false
+ }
+ val amountRaw = Wallet.getAmountFromString(amount)
+ val balance = BalanceService.instance?.unlockedBalanceRaw ?: 0
+ if (amountRaw >= balance || amountRaw <= 0) {
+ Toast.makeText(
+ this,
+ getString(R.string.send_amount_invalid),
+ Toast.LENGTH_SHORT
+ ).show()
+ return false
+ }
+ } else if (dests.size > 1) {
+ Toast.makeText(
+ this,
+ getString(R.string.send_amount_invalid_sendall_paytomany),
+ Toast.LENGTH_SHORT
+ ).show()
+ return false
+ }
+ val uriData = UriData.parse(address)
+ val isValidAddress = uriData != null
+ if (!isValidAddress) {
+ Toast.makeText(
+ this,
+ getString(R.string.send_address_invalid),
+ Toast.LENGTH_SHORT
+ ).show()
+ return false
+ }
+ if (dests.size > 1 && uriData?.hasPaymentId() == true) {
+ Toast.makeText(
+ this,
+ getString(R.string.paymentid_paytomany),
+ Toast.LENGTH_SHORT
+ ).show()
+ return false
+ }
+ }
+ return true
+ }
+
+ private fun destsHasPaymentId(): Boolean {
+ val dests = rawDests
+ for (dest in dests) {
+ val address = dest.component1()
+ val uriData = UriData.parse(address) ?: return false
+ if (uriData.hasPaymentId()) return true
+ }
+ return false
+ }
+
+ private fun bindObservers() {
+ mViewModel.sendingMax.observe(this) { sendingMax: Boolean? ->
+ if (mViewModel.pendingTransaction.value == null) {
+ if (sendingMax == true) {
+ prepareOutputsForMaxSend()
+ sendMaxButton.text = getText(R.string.undo)
+ } else {
+ unprepareMaxSend()
+ sendMaxButton.text = getText(R.string.send_max)
+ }
+ }
+ }
+ mViewModel.showAddOutputButton.observe(this) { show: Boolean? ->
+ setAddOutputButtonVisibility(
+ if (show == true && !destsHasPaymentId()) View.VISIBLE else View.INVISIBLE
+ )
+ }
+ mViewModel.pendingTransaction.observe(this) { pendingTx: PendingTransaction? ->
+ showConfirmationLayout(pendingTx != null)
+ if (pendingTx != null) {
+ val address = if (destCount == 1) getAddressField(0).text.toString() else "Multiple"
+ addressTextView.text = getString(R.string.tx_address_text, address)
+ amountTextView.text =
+ getString(
+ R.string.tx_amount_text,
+ Helper.getDisplayAmount(pendingTx.getAmount())
+ )
+ feeTextView.text =
+ getString(R.string.tx_fee_text, Helper.getDisplayAmount(pendingTx.getFee()))
+ }
+ }
+ }
+
+ private fun addOutput(initial: Boolean, address: String? = null, amount: String? = null) {
+ val index = destCount
+ val entryView =
+ layoutInflater.inflate(R.layout.transaction_output_item, null) as ConstraintLayout
+ val removeOutputImageButton =
+ entryView.findViewById(R.id.remove_output_imagebutton)
+ val addressField = entryView.findViewById(R.id.address_edittext)
+ val amountField = entryView.findViewById(R.id.amount_edittext)
+ val donateTextView = entryView.findViewById(R.id.donate_label)
+ val donatingTextView = entryView.findViewById(R.id.donating_label)
+ donateTextView.setOnClickListener {
+ addressField.setText(
+ Constants.DONATE_ADDRESS
+ )
+ }
+ donatingTextView.setOnClickListener {
+ addressField.setText("")
+ }
+ address?.let { addressField.setText(it) }
+ amount?.let { amountField.setText(it) }
+ val activity = this
+ addressField.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
+ override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
+ override fun afterTextChanged(s: Editable) {
+ val currentOutputs: Int = destCount
+ val uriData = UriData.parse(s.toString())
+ if (uriData != null) {
+ // we have valid address
+ val hasPaymentId = uriData.hasPaymentId()
+ if (currentOutputs > 1 && hasPaymentId) {
+ // multiple outputs when pasting/editing in integrated address. this is not allowed
+ Toast.makeText(
+ activity,
+ getString(R.string.paymentid_paytomany),
+ Toast.LENGTH_SHORT
+ ).show()
+ addressField.text = null
+ } else if (currentOutputs == 1 && hasPaymentId) {
+ // show add output button: we are sending to integrated address
+ mViewModel.setShowAddOutputButton(false)
+ }
+ } else if (currentOutputs == 1 && !isSendAll) {
+ // when send-all is false and this is our only dest and address is invalid, then show add output button
+ mViewModel.setShowAddOutputButton(true)
+ }
+
+ if (s.toString() == Constants.DONATE_ADDRESS) {
+ donateTextView.visibility = View.INVISIBLE
+ donatingTextView.visibility = View.VISIBLE
+ } else {
+ donateTextView.visibility = View.VISIBLE
+ donatingTextView.visibility = View.INVISIBLE
+ }
+ }
+ })
+ entryView.findViewById(R.id.paste_amount_imagebutton)
+ .setOnClickListener {
+ val clipboard = Helper.getClipBoardText(this)
+ if (clipboard != null) {
+ pasteAddress(entryView, clipboard, true)
+ }
+ }
+ entryView.findViewById(R.id.paste_address_imagebutton)
+ .setOnClickListener {
+ val clipboard = Helper.getClipBoardText(this)
+ if (clipboard != null) {
+ pasteAddress(entryView, clipboard, false)
+ }
+ }
+ entryView.findViewById(R.id.scan_address_imagebutton)
+ .setOnClickListener { onScan(index) }
+ if (initial) {
+ removeOutputImageButton.visibility = View.INVISIBLE
+ } else {
+ removeOutputImageButton.setOnClickListener {
+ val currentCount = destCount
+ if (currentCount > 1) {
+ if (currentCount == 2) {
+ sendMaxButton.visibility = View.VISIBLE
+ }
+ destList.removeView(entryView)
+ }
+ }
+ }
+ destList.addView(entryView)
+ }
+
+ private val destCount: Int
+ get() = destList.childCount ?: -1
+ private val rawDests: List>
+ get() {
+ val dests = ArrayList>()
+ for (i in 0 until destCount) {
+ val entryView = getDestView(i)
+ val amountField = entryView.findViewById(R.id.amount_edittext)
+ val addressField = entryView.findViewById(R.id.address_edittext)
+ val amount = amountField.text.toString().trim { it <= ' ' }
+ val address = addressField.text.toString().trim { it <= ' ' }
+ dests.add(Pair(address, amount))
+ }
+ return dests
+ }
+ private val isSendAll: Boolean
+ get() = mViewModel.sendingMax.value ?: false
+
+ private fun getDestView(pos: Int): ConstraintLayout {
+ return destList.getChildAt(pos) as ConstraintLayout
+ }
+
+ private fun getAddressField(pos: Int): EditText {
+ return getDestView(pos).findViewById(R.id.address_edittext) as EditText
+ }
+
+ private fun unprepareMaxSend() {
+ val entryView = getDestView(0)
+ entryView.findViewById(R.id.sending_all_textview).visibility = View.INVISIBLE
+ entryView.findViewById(R.id.amount_edittext).visibility =
+ View.VISIBLE
+ }
+
+ private fun prepareOutputsForMaxSend() {
+ val entryView = getDestView(0)
+ entryView.findViewById(R.id.sending_all_textview).visibility = View.VISIBLE
+ entryView.findViewById(R.id.amount_edittext).visibility =
+ View.INVISIBLE
+ }
+
+ private fun showConfirmationLayout(show: Boolean) {
+ if (show) {
+ destList.visibility = View.GONE
+ setAddOutputButtonVisibility(View.GONE)
+ sendMaxButton.visibility = View.GONE
+ createButton.visibility = View.GONE
+ feeRadioGroup.visibility = View.GONE
+ feeRadioGroupLabelTextView.visibility = View.GONE
+ sendTxSlider.visibility = View.VISIBLE
+ feeTextView.visibility = View.VISIBLE
+ addressTextView.visibility = View.VISIBLE
+ amountTextView.visibility = View.VISIBLE
+ } else {
+ destList.visibility = View.VISIBLE
+ setAddOutputButtonVisibility(View.VISIBLE)
+ sendMaxButton.visibility = View.VISIBLE
+ createButton.visibility = View.VISIBLE
+ feeRadioGroup.visibility = View.VISIBLE
+ feeRadioGroupLabelTextView.visibility = View.VISIBLE
+ sendTxSlider.visibility = View.GONE
+ feeTextView.visibility = View.GONE
+ addressTextView.visibility = View.GONE
+ amountTextView.visibility = View.GONE
+ }
+ }
+
+ private fun onScan(index: Int) {
+ currentEntryIndex = index
+ if (Helper.getCameraPermission(this, cameraPermissionsLauncher)) {
+ val options = ScanOptions()
+ options.setBeepEnabled(false)
+ options.setOrientationLocked(true)
+ options.setDesiredBarcodeFormats(listOf(Intents.Scan.QR_CODE_MODE))
+ options.addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
+ qrCodeLauncher.launch(options)
+ }
+ }
+
+ private fun pasteAddress(
+ entryView: ConstraintLayout,
+ clipboard: String,
+ pastingAmount: Boolean
+ ) {
+ if (pastingAmount) {
+ try {
+ clipboard.toDouble()
+ setAmount(entryView, clipboard)
+ } catch (e: Exception) {
+ Toast.makeText(
+ this,
+ getString(R.string.send_amount_invalid),
+ Toast.LENGTH_SHORT
+ ).show()
+ return
+ }
+ }
+ val uriData = UriData.parse(clipboard)
+ if (uriData != null) {
+ val currentOutputs = destCount
+ if (currentOutputs > 1 && uriData.hasPaymentId()) {
+ Toast.makeText(
+ this,
+ getString(R.string.paymentid_paytomany),
+ Toast.LENGTH_SHORT
+ ).show()
+ return
+ } else if (currentOutputs == 1 && uriData.hasPaymentId()) {
+ mViewModel.setShowAddOutputButton(false)
+ }
+ val addressField = entryView.findViewById(R.id.address_edittext)
+ addressField.setText(uriData.address)
+ if (uriData.hasAmount()) {
+ setAmount(entryView, uriData.amount)
+ }
+ } else {
+ Toast.makeText(this, getString(R.string.send_address_invalid), Toast.LENGTH_SHORT)
+ .show()
+ }
+ }
+
+ private fun setAmount(entryView: ConstraintLayout, amount: String?) {
+ sendMaxButton.isEnabled = false
+ val amountField = entryView.findViewById(R.id.amount_edittext)
+ amountField.setText(amount)
+ }
+
+ private fun createTx(
+ dests: List>,
+ sendAll: Boolean,
+ feePriority: PendingTransaction.Priority,
+ utxos: ArrayList = ArrayList()
+ ) {
+ (application as MoneroApplication).executor?.execute {
+ try {
+ val pendingTx =
+ TxService.instance?.createTx(dests, sendAll, feePriority, utxos)
+ if (pendingTx != null && pendingTx.status === PendingTransaction.Status.Status_Ok) {
+ mViewModel.setPendingTransaction(pendingTx)
+ } else {
+ val activity: Activity = this
+ if (pendingTx != null) {
+ activity.runOnUiThread {
+ createButton.isEnabled = true
+ sendMaxButton.isEnabled = true
+ if (pendingTx.getErrorString() != null) Toast.makeText(
+ activity,
+ getString(R.string.error_creating_tx, pendingTx.getErrorString()),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+ } catch (e: Exception) {
+ e.printStackTrace()
+ val activity: Activity = this
+ activity.runOnUiThread {
+ createButton.isEnabled = true
+ sendMaxButton.isEnabled = true
+ Toast.makeText(
+ activity,
+ getString(R.string.error_creating_tx, e.message),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+ }
+
+ private fun sendTx(pendingTx: PendingTransaction) {
+ (application as MoneroApplication).executor?.execute {
+ val success = TxService.instance?.sendTx(pendingTx)
+ val activity: Activity = this
+ activity.runOnUiThread {
+ if (success == true) {
+ Toast.makeText(this, getString(R.string.sent_tx), Toast.LENGTH_SHORT)
+ .show()
+ activity.onBackPressed()
+ } else {
+ sendTxSlider.resetSlider()
+ Toast.makeText(
+ this,
+ getString(R.string.error_sending_tx),
+ Toast.LENGTH_SHORT
+ )
+ .show()
+ }
+ }
+ }
+ }
+
+ private fun setAddOutputButtonVisibility(visibility: Int) {
+ addOutputImageView.visibility = visibility
+ }
+}
+
+class SendViewModel : ViewModel() {
+ private val _sendingMax = MutableLiveData(false)
+ private val _showAddOutputButton = MutableLiveData(true)
+ private val _utxos = MutableLiveData>(ArrayList())
+ private val _pendingTransaction = MutableLiveData(null)
+ var sendingMax: LiveData = _sendingMax
+ var showAddOutputButton: LiveData = _showAddOutputButton
+ var utxos: LiveData> = _utxos
+ var pendingTransaction: LiveData = _pendingTransaction
+ fun setSendingMax(value: Boolean) {
+ _sendingMax.value = value
+ setShowAddOutputButton(!value)
+ }
+
+ fun setShowAddOutputButton(value: Boolean) {
+ _showAddOutputButton.value = value
+ }
+
+ fun setUtxos(value: ArrayList) {
+ _utxos.value = value
+ }
+
+ fun setPendingTransaction(pendingTx: PendingTransaction?) {
+ _pendingTransaction.postValue(pendingTx)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/SettingsActivity.kt b/app/src/main/java/net/mynero/wallet/SettingsActivity.kt
new file mode 100644
index 0000000..9768143
--- /dev/null
+++ b/app/src/main/java/net/mynero/wallet/SettingsActivity.kt
@@ -0,0 +1,331 @@
+package net.mynero.wallet
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.Button
+import android.widget.CheckBox
+import android.widget.CompoundButton
+import android.widget.EditText
+import android.widget.ImageView
+import android.widget.Toast
+import androidx.activity.OnBackPressedCallback
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import androidx.appcompat.widget.SwitchCompat
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.ViewModelProvider
+import com.google.android.material.progressindicator.CircularProgressIndicator
+import net.mynero.wallet.data.Node
+import net.mynero.wallet.data.Node.Companion.fromJson
+import net.mynero.wallet.fragment.dialog.AddNodeBottomSheetDialog
+import net.mynero.wallet.fragment.dialog.EditNodeBottomSheetDialog
+import net.mynero.wallet.fragment.dialog.NodeSelectionBottomSheetDialog
+import net.mynero.wallet.fragment.dialog.WalletKeysBottomSheetDialog
+import net.mynero.wallet.model.EnumTorState
+import net.mynero.wallet.model.WalletManager
+import net.mynero.wallet.service.BalanceService
+import net.mynero.wallet.service.HistoryService
+import net.mynero.wallet.service.MoneroHandlerThread
+import net.mynero.wallet.service.PrefService
+import net.mynero.wallet.service.ProxyService
+import net.mynero.wallet.util.Constants
+import org.json.JSONArray
+import java.io.File
+
+class SettingsActivity : AppCompatActivity() {
+
+ private lateinit var mViewModel: SettingsViewModel
+ private lateinit var walletProxyAddressEditText: EditText
+ private lateinit var walletProxyPortEditText: EditText
+ private lateinit var selectNodeButton: Button
+ private lateinit var streetModeSwitch: SwitchCompat
+ private lateinit var monerochanSwitch: SwitchCompat
+ private lateinit var useBundledTor: CheckBox
+ private lateinit var displaySeedButton: Button
+ private lateinit var displayUtxosButton: Button
+ private lateinit var torSwitch: SwitchCompat
+
+ private val askForWalletPasswordAndDisplayWalletKeys = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == RESULT_OK) {
+ val password = result.data?.extras?.getString(Constants.EXTRA_WALLET_PASSWORD)
+ password?.let { displaySeedDialog(it) }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_settings)
+
+ mViewModel = ViewModelProvider(this)[SettingsViewModel::class.java]
+ displaySeedButton = findViewById(R.id.display_seed_button)
+ displayUtxosButton = findViewById(R.id.display_utxos_button)
+ selectNodeButton = findViewById(R.id.select_node_button)
+ streetModeSwitch = findViewById(R.id.street_mode_switch)
+ monerochanSwitch = findViewById(R.id.monerochan_switch)
+ torSwitch = findViewById(R.id.tor_switch)
+ val proxySettingsLayout = findViewById(R.id.wallet_proxy_settings_layout)
+ walletProxyAddressEditText = findViewById(R.id.wallet_proxy_address_edittext)
+ walletProxyPortEditText = findViewById(R.id.wallet_proxy_port_edittext)
+ useBundledTor = findViewById(R.id.bundled_tor_checkbox)
+
+ val cachedProxyAddress = ProxyService.instance?.proxyAddress ?: return
+ val cachedProxyPort = ProxyService.instance?.proxyPort ?: return
+ val cachedUsingProxy = ProxyService.instance?.usingProxy == true
+ val cachedUsingBundledTor = ProxyService.instance?.useBundledTor == true
+
+ walletProxyPortEditText.isEnabled = !cachedUsingBundledTor && cachedUsingProxy
+ walletProxyAddressEditText.isEnabled = !cachedUsingBundledTor && cachedUsingProxy
+ proxySettingsLayout.visibility = View.VISIBLE
+
+ streetModeSwitch.isChecked =
+ PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false) == true
+ monerochanSwitch.isChecked =
+ PrefService.instance?.getBoolean(Constants.PREF_MONEROCHAN, Constants.DEFAULT_PREF_MONEROCHAN) == true
+ useBundledTor.isChecked = cachedUsingBundledTor
+ torSwitch.isChecked = cachedUsingProxy
+ updateProxy(cachedProxyAddress, cachedProxyPort)
+
+ val node = PrefService.instance?.node // shouldn't use default value here
+ selectNodeButton.text = getString(R.string.node_button_text, node?.address)
+
+ bindListeners()
+ bindObservers()
+ }
+
+ private fun bindListeners() {
+ val activity = this
+ val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
+ override fun handleOnBackPressed() {
+ refreshProxy()
+ finish()
+ }
+ }
+ onBackPressedDispatcher.addCallback(this, onBackPressedCallback)
+
+ streetModeSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
+ PrefService.instance?.edit()?.putBoolean(Constants.PREF_STREET_MODE, b)?.apply()
+ BalanceService.instance?.refreshBalance()
+ }
+
+ monerochanSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
+ PrefService.instance?.edit()?.putBoolean(Constants.PREF_MONEROCHAN, b)?.apply()
+ HistoryService.instance?.refreshHistory()
+ }
+
+ selectNodeButton.setOnClickListener {
+ supportFragmentManager.let { fragmentManager ->
+ val dialog = NodeSelectionBottomSheetDialog()
+ dialog.listener = object : NodeSelectionBottomSheetDialog.NodeSelectionDialogListener {
+ override fun onNodeSelected() {
+ val node = PrefService.instance?.node
+ selectNodeButton.text = getString(R.string.node_button_text, node?.address)
+ refreshProxy()
+
+ runOnUiThread {
+ Toast.makeText(
+ activity,
+ activity.getString(R.string.node_selected, node?.name ?: node?.host),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+
+ override fun onClickedEditNode(node: Node?) {
+ val editNodeDialog = EditNodeBottomSheetDialog()
+ editNodeDialog.listener = object : EditNodeBottomSheetDialog.EditNodeListener {
+ override fun onNodeDeleted(node: Node?) {
+ try {
+ val nodesArray = PrefService.instance?.getString(Constants.PREF_CUSTOM_NODES, "[]")
+ val jsonArray = JSONArray(nodesArray)
+ for (i in 0 until jsonArray.length()) {
+ val nodeJsonObject = jsonArray.getJSONObject(i)
+ val savedNode = fromJson(nodeJsonObject)
+ if (savedNode?.toNodeString() == node?.toNodeString()) jsonArray.remove(i)
+ }
+ saveNodesAndReopen(jsonArray)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ override fun onNodeEdited(oldNode: Node?, newNode: Node?) {
+ try {
+ val nodesArray = PrefService.instance?.getString(Constants.PREF_CUSTOM_NODES, "[]")
+ val jsonArray = JSONArray(nodesArray)
+ for (i in 0 until jsonArray.length()) {
+ val nodeJsonObject = jsonArray.getJSONObject(i)
+ val savedNode = fromJson(nodeJsonObject)
+ if (savedNode?.toNodeString() == oldNode?.toNodeString()) jsonArray.put(
+ i,
+ newNode?.toJson()
+ )
+ }
+ saveNodesAndReopen(jsonArray)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ }
+ editNodeDialog.node = node
+ editNodeDialog.show(fragmentManager, "edit_node_dialog")
+ }
+
+ override fun onClickedAddNode() {
+ activity.supportFragmentManager.let { fragmentManager ->
+ val addNodeDialog = AddNodeBottomSheetDialog()
+ addNodeDialog.listener = object : AddNodeBottomSheetDialog.AddNodeListener {
+ override fun onNodeAdded() {}
+ }
+ addNodeDialog.show(fragmentManager, "add_node_dialog")
+ }
+ }
+
+ }
+ dialog.show(fragmentManager, "node_selection_dialog")
+ }
+ }
+
+ useBundledTor.setOnCheckedChangeListener { _, isChecked ->
+ mViewModel.setUseBundledTor(isChecked)
+ }
+
+ displaySeedButton.setOnClickListener {
+ val usesPassword =
+ PrefService.instance?.getBoolean(Constants.PREF_USES_PASSWORD, false) == true
+ if (usesPassword) {
+ val intent = Intent(this, PasswordActivity::class.java)
+ askForWalletPasswordAndDisplayWalletKeys.launch(intent)
+ } else {
+ displaySeedDialog("")
+ }
+ }
+
+ displayUtxosButton.setOnClickListener {
+ startActivity(Intent(this, UtxosActivity::class.java))
+ }
+
+ torSwitch.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
+ mViewModel.setUseProxy(b)
+ }
+ }
+
+ private fun bindObservers() {
+ mViewModel.useProxy.observe(this) { useProxy ->
+ useBundledTor.isEnabled = useProxy
+ walletProxyPortEditText.isEnabled = useProxy && mViewModel.useBundledTor.value == false
+ walletProxyAddressEditText.isEnabled = useProxy && mViewModel.useBundledTor.value == false
+
+ refreshProxy()
+ }
+
+ mViewModel.useBundledTor.observe(this) { isChecked ->
+ walletProxyPortEditText.isEnabled = !isChecked && mViewModel.useProxy.value == true
+ walletProxyAddressEditText.isEnabled = !isChecked && mViewModel.useProxy.value == true
+ }
+
+ val samouraiTorManager = ProxyService.instance?.samouraiTorManager
+ val indicatorCircle =
+ findViewById(R.id.settings_tor_loading_progressindicator)
+ val torIcon = findViewById(R.id.settings_tor_icon)
+
+ samouraiTorManager?.getTorStateLiveData()?.observe(this) { state ->
+ samouraiTorManager.getProxy()?.address()?.let { socketAddress ->
+ if (socketAddress.toString().isEmpty()) return@let
+ if (mViewModel.useProxy.value == true && mViewModel.useBundledTor.value == true) {
+ torIcon?.visibility = View.VISIBLE
+ indicatorCircle?.visibility = View.INVISIBLE
+ val proxyString = socketAddress.toString().substring(1)
+ val address = proxyString.split(":")[0]
+ val port = proxyString.split(":")[1]
+ updateProxy(address, port)
+ }
+ }
+
+ indicatorCircle?.isIndeterminate = state.progressIndicator == 0
+ indicatorCircle?.progress = state.progressIndicator
+
+ when (state.state) {
+ EnumTorState.OFF -> {
+ torIcon?.visibility = View.INVISIBLE
+ indicatorCircle?.visibility = View.INVISIBLE
+ }
+
+ EnumTorState.STARTING, EnumTorState.STOPPING -> {
+ torIcon?.visibility = View.INVISIBLE
+ indicatorCircle?.visibility = View.VISIBLE
+ }
+
+ else -> {}
+ }
+ }
+ }
+
+ private fun updateProxy(address: String, port: String) {
+ walletProxyPortEditText.setText(port)
+ walletProxyAddressEditText.setText(address)
+ refreshProxy()
+ }
+
+ private fun refreshProxy() {
+ val proxyAddress = walletProxyAddressEditText.text.toString()
+ val proxyPort = walletProxyPortEditText.text.toString()
+ val savedProxyAddress = ProxyService.instance?.proxyAddress
+ val savedProxyPort = ProxyService.instance?.proxyPort
+ val currentWalletProxy = WalletManager.instance?.proxy
+ val newProxy = "$proxyAddress:$proxyPort"
+ if (proxyAddress != savedProxyAddress || proxyPort != savedProxyPort || (newProxy != currentWalletProxy && newProxy != ":"))
+ ProxyService.instance?.updateProxy(proxyAddress, proxyPort)
+ }
+
+ private fun displaySeedDialog(password: String) {
+ val informationDialog = WalletKeysBottomSheetDialog()
+ informationDialog.password = password
+ informationDialog.show(supportFragmentManager, "information_seed_dialog")
+ }
+
+ private fun saveNodesAndReopen(jsonArray: JSONArray) {
+ PrefService.instance?.edit()?.putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString())
+ ?.apply()
+ }
+}
+
+class SettingsViewModel : ViewModel() {
+ private val _useProxy = MutableLiveData(false)
+ val useProxy: LiveData = _useProxy
+ private val _useBundledTor = MutableLiveData(false)
+ val useBundledTor: LiveData = _useBundledTor
+
+ init {
+ _useProxy.value = ProxyService.instance?.usingProxy
+ _useBundledTor.value = ProxyService.instance?.useBundledTor
+ }
+
+ fun setUseProxy(use: Boolean) {
+ _useProxy.value = use
+ ProxyService.instance?.usingProxy = use
+
+ val samouraiTorManager = ProxyService.instance?.samouraiTorManager
+ if (use && ProxyService.instance?.useBundledTor == true) {
+ samouraiTorManager?.start()
+ } else {
+ samouraiTorManager?.stop()
+ }
+ }
+
+ fun setUseBundledTor(use: Boolean) {
+ _useBundledTor.value = use
+ ProxyService.instance?.useBundledTor = use
+
+ val samouraiTorManager = ProxyService.instance?.samouraiTorManager
+ if (use && ProxyService.instance?.usingProxy == true) {
+ samouraiTorManager?.start()
+ } else {
+ samouraiTorManager?.stop()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/StartActivity.kt b/app/src/main/java/net/mynero/wallet/StartActivity.kt
new file mode 100644
index 0000000..f4af920
--- /dev/null
+++ b/app/src/main/java/net/mynero/wallet/StartActivity.kt
@@ -0,0 +1,75 @@
+package net.mynero.wallet
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.appcompat.app.AppCompatActivity
+import net.mynero.wallet.service.MoneroHandlerThread
+import net.mynero.wallet.service.PrefService
+import net.mynero.wallet.util.Constants
+import net.mynero.wallet.util.UriData
+import java.io.File
+
+class StartActivity : AppCompatActivity() {
+
+ private var uriData: UriData? = null
+
+ private val startPasswordActivityForOpeningWallet = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
+ if (result.resultCode == RESULT_OK) {
+ val walletName = result.data?.extras?.getString(Constants.EXTRA_WALLET_NAME)
+ val walletPassword = result.data?.extras?.getString(Constants.EXTRA_WALLET_PASSWORD)
+ if (walletName != null && walletPassword != null) {
+ val walletFile = File(applicationInfo.dataDir, walletName)
+ openWallet(walletFile, walletPassword)
+ } else {
+ // if we ever get here, it's a bug ¯\_(ツ)_/¯ so let's just recreate the activity
+ recreate()
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ intent.data?.let { uriData = UriData.parse(it.toString()) }
+ val walletFile = File(applicationInfo.dataDir, Constants.WALLET_NAME)
+ val walletKeysFile = File(applicationInfo.dataDir, Constants.WALLET_NAME + ".keys")
+ if (walletKeysFile.exists()) {
+ val usesPassword = PrefService.instance?.getBoolean(Constants.PREF_USES_PASSWORD, false) == true
+ if (!usesPassword) {
+ openWallet(walletFile, "")
+ return
+ } else {
+ val intent = Intent(this, PasswordActivity::class.java)
+ intent.putExtra(Constants.EXTRA_PREVENT_GOING_BACK, true)
+ startPasswordActivityForOpeningWallet.launch(intent)
+ return
+ }
+ } else {
+ startActivity(Intent(this, OnboardingActivity::class.java))
+ finish()
+ return
+ }
+ }
+
+ private fun openWallet(walletFile: File, password: String) {
+ MoneroHandlerThread.init(walletFile, password, applicationContext)
+
+ if (uriData == null) {
+ // the app was NOT started with a monero uri payment data, proceed to the home activity
+ startActivity(Intent(this, HomeActivity::class.java))
+ } else {
+ // the app was started with a monero uri payment data, we proceed to the send activity but launch the home activity as well
+ // so that when users press back button they go to home activity instead of closing the app
+ val homeIntent = Intent(this, HomeActivity::class.java)
+ homeIntent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
+ startActivity(homeIntent)
+
+ val sendIntent = Intent(this, SendActivity::class.java)
+ sendIntent.putExtra(Constants.EXTRA_SEND_ADDRESS, uriData!!.address)
+ uriData!!.amount?.let { sendIntent.putExtra(Constants.EXTRA_SEND_AMOUNT, it) }
+ startActivity(sendIntent)
+ }
+
+ finish()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionFragment.kt b/app/src/main/java/net/mynero/wallet/TransactionActivity.kt
similarity index 55%
rename from app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionFragment.kt
rename to app/src/main/java/net/mynero/wallet/TransactionActivity.kt
index 0f39c67..d780190 100644
--- a/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionFragment.kt
+++ b/app/src/main/java/net/mynero/wallet/TransactionActivity.kt
@@ -1,4 +1,4 @@
-package net.mynero.wallet.fragment.transaction
+package net.mynero.wallet
import android.os.Bundle
import android.view.LayoutInflater
@@ -6,10 +6,9 @@ import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
+import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
-import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
-import net.mynero.wallet.R
import net.mynero.wallet.model.TransactionInfo
import net.mynero.wallet.model.WalletManager
import net.mynero.wallet.service.HistoryService
@@ -21,62 +20,55 @@ import java.util.Calendar
import java.util.Date
import java.util.Objects
-class TransactionFragment : Fragment() {
- private var mViewModel: TransactionViewModel? = null
+class TransactionActivity : AppCompatActivity() {
private var transactionInfo: TransactionInfo? = null
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.fragment_transaction, container, false)
- }
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_transaction)
+
val cal = Calendar.getInstance()
val tz = cal.timeZone //get the local time zone.
DateHelper.DATETIME_FORMATTER.timeZone = tz
- mViewModel = ViewModelProvider(this)[TransactionViewModel::class.java]
- val args = arguments
- if (args != null) {
- transactionInfo = arguments?.getParcelable(Constants.NAV_ARG_TXINFO)
- }
- bindObservers(view)
- bindListeners(view)
+
+ transactionInfo = intent.extras?.getParcelable(Constants.NAV_ARG_TXINFO)
+
+ bindObservers()
+ bindListeners()
}
- private fun bindListeners(view: View) {
- val copyTxHashImageButton = view.findViewById(R.id.copy_txhash_imagebutton)
+ private fun bindListeners() {
+ val copyTxHashImageButton = findViewById(R.id.copy_txhash_imagebutton)
copyTxHashImageButton.setOnClickListener {
val txInfo = transactionInfo
if (txInfo != null) {
- Helper.clipBoardCopy(context, "transaction_hash", txInfo.hash)
+ Helper.clipBoardCopy(this, "transaction_hash", txInfo.hash)
}
}
val copyTxAddressImageButton =
- view.findViewById(R.id.copy_txaddress_imagebutton)
- val addressTextView = view.findViewById(R.id.transaction_address_textview)
+ findViewById(R.id.copy_txaddress_imagebutton)
+ val addressTextView = findViewById(R.id.transaction_address_textview)
copyTxAddressImageButton.setOnClickListener {
val txInfo = transactionInfo
if (txInfo != null) {
val destination = addressTextView.text.toString()
- Helper.clipBoardCopy(context, "transaction_address", destination)
+ Helper.clipBoardCopy(this, "transaction_address", destination)
}
}
}
- private fun bindObservers(view: View) {
- val txActionTextView = view.findViewById(R.id.transaction_action_textview)
- val confLabel2 = view.findViewById(R.id.transaction_conf_label2_textview)
- val txHashTextView = view.findViewById(R.id.transaction_hash_textview)
- val txConfTextView = view.findViewById(R.id.transaction_conf_textview)
- val txAddressTextView = view.findViewById(R.id.transaction_address_textview)
+ private fun bindObservers() {
+ val txActionTextView = findViewById(R.id.transaction_action_textview)
+ val confLabel2 = findViewById(R.id.transaction_conf_label2_textview)
+ val txHashTextView = findViewById(R.id.transaction_hash_textview)
+ val txConfTextView = findViewById(R.id.transaction_conf_textview)
+ val txAddressTextView = findViewById(R.id.transaction_address_textview)
val copyTxAddressImageButton =
- view.findViewById(R.id.copy_txaddress_imagebutton)
- val txDateTextView = view.findViewById(R.id.transaction_date_textview)
- val txAmountTextView = view.findViewById(R.id.transaction_amount_textview)
- val blockHeightTextView = view.findViewById(R.id.tx_block_height_textview)
- HistoryService.instance?.history?.observe(viewLifecycleOwner) { transactionInfos: List ->
+ findViewById(R.id.copy_txaddress_imagebutton)
+ val txDateTextView = findViewById(R.id.transaction_date_textview)
+ val txAmountTextView = findViewById(R.id.transaction_amount_textview)
+ val blockHeightTextView = findViewById(R.id.tx_block_height_textview)
+ HistoryService.instance?.history?.observe(this) { transactionInfos: List ->
val newTransactionInfo = findNewestVersionOfTransaction(
transactionInfo, transactionInfos
)
@@ -96,34 +88,31 @@ class TransactionFragment : Fragment() {
blockHeightTextView.visibility = View.GONE
confLabel2.text = getString(R.string.transaction_conf_desc2_unconfirmed)
}
- val ctx = context
- if (ctx != null) {
- val streetModeEnabled =
- PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false) == true
- val balanceString =
- if (streetModeEnabled) Constants.STREET_MODE_BALANCE else Helper.getDisplayAmount(
- newTransactionInfo.amount,
- 12
+ val streetModeEnabled =
+ PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false) == true
+ val balanceString =
+ if (streetModeEnabled) Constants.STREET_MODE_BALANCE else Helper.getDisplayAmount(
+ newTransactionInfo.amount,
+ 12
+ )
+ if (newTransactionInfo.direction === TransactionInfo.Direction.Direction_In) {
+ txActionTextView.text = getString(R.string.transaction_action_recv)
+ txAmountTextView.setTextColor(
+ ContextCompat.getColor(
+ this,
+ R.color.oled_positiveColor
)
- if (newTransactionInfo.direction === TransactionInfo.Direction.Direction_In) {
- txActionTextView.text = getString(R.string.transaction_action_recv)
- txAmountTextView.setTextColor(
- ContextCompat.getColor(
- ctx,
- R.color.oled_positiveColor
- )
+ )
+ } else {
+ txActionTextView.text = getString(R.string.transaction_action_sent)
+ txAmountTextView.setTextColor(
+ ContextCompat.getColor(
+ this,
+ R.color.oled_negativeColor
)
- } else {
- txActionTextView.text = getString(R.string.transaction_action_sent)
- txAmountTextView.setTextColor(
- ContextCompat.getColor(
- ctx,
- R.color.oled_negativeColor
- )
- )
- }
- txAmountTextView.text = balanceString
+ )
}
+ txAmountTextView.text = balanceString
var destination: String? = "-"
val wallet = WalletManager.instance?.wallet
if (newTransactionInfo.txKey == null) {
diff --git a/app/src/main/java/net/mynero/wallet/UtxosActivity.kt b/app/src/main/java/net/mynero/wallet/UtxosActivity.kt
new file mode 100644
index 0000000..fb9525f
--- /dev/null
+++ b/app/src/main/java/net/mynero/wallet/UtxosActivity.kt
@@ -0,0 +1,145 @@
+package net.mynero.wallet
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.View
+import android.widget.Button
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import net.mynero.wallet.adapter.CoinsInfoAdapter
+import net.mynero.wallet.model.CoinsInfo
+import net.mynero.wallet.service.AddressService
+import net.mynero.wallet.service.UTXOService
+import net.mynero.wallet.util.Constants
+import net.mynero.wallet.util.MoneroThreadPoolExecutor
+
+class UtxosActivity : AppCompatActivity() {
+
+ private lateinit var sendUtxosButton: Button
+ private lateinit var churnUtxosButton: Button
+ private lateinit var freezeUtxosButton: Button
+ private lateinit var adapter: CoinsInfoAdapter
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_utxos)
+
+ freezeUtxosButton = findViewById(R.id.freeze_utxos_button)
+ sendUtxosButton = findViewById(R.id.send_utxos_button)
+ churnUtxosButton = findViewById(R.id.churn_utxos_button)
+
+ adapter = CoinsInfoAdapter(object : CoinsInfoAdapter.CoinsInfoAdapterListener {
+ override fun onUtxoSelected(coinsInfo: CoinsInfo) {
+ val selected = adapter.contains(coinsInfo)
+ if (selected) {
+ adapter.deselectUtxo(coinsInfo)
+ } else {
+ adapter.selectUtxo(coinsInfo)
+ }
+ var frozenExists = false
+ var unfrozenExists = false
+ for (selectedUtxo in adapter.selectedUtxos.values) {
+ if (selectedUtxo.isFrozen || UTXOService.instance?.isCoinFrozen(selectedUtxo) == true)
+ frozenExists = true
+ else {
+ unfrozenExists = true
+ }
+ }
+ val bothExist: Boolean = frozenExists && unfrozenExists
+ if (adapter.selectedUtxos.isEmpty()) {
+ sendUtxosButton.visibility = View.GONE
+ churnUtxosButton.visibility = View.GONE
+ freezeUtxosButton.visibility = View.GONE
+ freezeUtxosButton.setBackgroundResource(R.drawable.button_bg_left)
+ } else {
+ if (frozenExists) {
+ freezeUtxosButton.setBackgroundResource(R.drawable.button_bg)
+ sendUtxosButton.visibility = View.GONE
+ churnUtxosButton.visibility = View.GONE
+ } else {
+ freezeUtxosButton.setBackgroundResource(R.drawable.button_bg_left)
+ sendUtxosButton.visibility = View.VISIBLE
+ churnUtxosButton.visibility = View.VISIBLE
+ }
+ freezeUtxosButton.visibility = View.VISIBLE
+ }
+ if (bothExist) {
+ freezeUtxosButton.setText(R.string.toggle_freeze)
+ } else if (frozenExists) {
+ freezeUtxosButton.setText(R.string.unfreeze)
+ } else if (unfrozenExists) {
+ freezeUtxosButton.setText(R.string.freeze)
+ }
+ }
+ })
+
+ bindListeners()
+ bindObservers()
+ }
+
+ private fun bindListeners() {
+ sendUtxosButton.visibility = View.GONE
+ churnUtxosButton.visibility = View.GONE
+ freezeUtxosButton.visibility = View.GONE
+ freezeUtxosButton.setOnClickListener {
+ Toast.makeText(this, "Toggling freeze status, please wait.", Toast.LENGTH_SHORT)
+ .show()
+ MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR?.execute {
+ UTXOService.instance?.toggleFrozen(adapter.selectedUtxos)
+ runOnUiThread {
+ adapter.clear()
+ sendUtxosButton.visibility = View.GONE
+ churnUtxosButton.visibility = View.GONE
+ freezeUtxosButton.visibility = View.GONE
+ }
+ }
+ }
+ sendUtxosButton.setOnClickListener {
+ val selectedKeyImages = ArrayList()
+ for (coinsInfo in adapter.selectedUtxos.values) {
+ coinsInfo.keyImage?.let { keyImage -> selectedKeyImages.add(keyImage) }
+ }
+ supportFragmentManager.let { fragmentManager ->
+ val intent = Intent(this, SendActivity::class.java)
+ intent.putStringArrayListExtra(Constants.EXTRA_SEND_UTXOS, selectedKeyImages)
+ startActivity(intent)
+ }
+
+ }
+ churnUtxosButton.setOnClickListener {
+ val selectedKeyImages = ArrayList()
+ for (coinsInfo in adapter.selectedUtxos.values) {
+ coinsInfo.keyImage?.let { keyImage -> selectedKeyImages.add(keyImage) }
+ }
+ val intent = Intent(this, SendActivity::class.java)
+ intent.putExtra(Constants.EXTRA_SEND_ADDRESS, AddressService.instance?.currentSubaddress()?.address)
+ intent.putExtra(Constants.EXTRA_SEND_MAX, true)
+ intent.putStringArrayListExtra(Constants.EXTRA_SEND_UTXOS, selectedKeyImages)
+ startActivity(intent)
+ }
+ }
+
+ private fun bindObservers() {
+ val utxosRecyclerView =
+ findViewById(R.id.transaction_history_recyclerview)
+ val utxoService = UTXOService.instance
+ utxosRecyclerView.layoutManager = LinearLayoutManager(this)
+ utxosRecyclerView.adapter = adapter
+ utxoService?.utxos?.observe(this) { utxos: List ->
+ val filteredUtxos = HashMap()
+ for (coinsInfo in utxos) {
+ if (!coinsInfo.isSpent) {
+ filteredUtxos[coinsInfo.pubKey] = coinsInfo
+ }
+ }
+ if (filteredUtxos.isEmpty()) {
+ utxosRecyclerView.visibility = View.GONE
+ } else {
+ adapter.submitList(filteredUtxos)
+ utxosRecyclerView.visibility = View.VISIBLE
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/PasswordBottomSheetDialog.kt b/app/src/main/java/net/mynero/wallet/fragment/dialog/PasswordBottomSheetDialog.kt
deleted file mode 100644
index bf4f4b6..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/dialog/PasswordBottomSheetDialog.kt
+++ /dev/null
@@ -1,65 +0,0 @@
-package net.mynero.wallet.fragment.dialog
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.EditText
-import android.widget.ImageButton
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment
-import net.mynero.wallet.R
-import net.mynero.wallet.model.WalletManager
-import net.mynero.wallet.util.Constants
-import net.mynero.wallet.util.Helper.getClipBoardText
-import java.io.File
-
-class PasswordBottomSheetDialog : BottomSheetDialogFragment() {
- var listener: PasswordListener? = null
- var canCancel = false
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.password_bottom_sheet_dialog, null)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- isCancelable = canCancel
- val walletFile = File(activity?.applicationInfo?.dataDir, Constants.WALLET_NAME)
- val pastePasswordImageButton =
- view.findViewById(R.id.paste_password_imagebutton)
- val passwordEditText = view.findViewById(R.id.wallet_password_edittext)
- val unlockWalletButton = view.findViewById(R.id.unlock_wallet_button)
- pastePasswordImageButton.setOnClickListener {
- passwordEditText.setText(
- getClipBoardText(view.context)
- )
- }
- unlockWalletButton.setOnClickListener {
- val password = passwordEditText.text.toString()
- val success = checkPassword(walletFile, password)
- if (success) {
- listener?.onPasswordSuccess(password)
- dismiss()
- } else {
- listener?.onPasswordFail()
- }
- }
- }
-
- private fun checkPassword(walletFile: File, password: String): Boolean {
- return WalletManager.instance?.verifyWalletPasswordOnly(
- walletFile.absolutePath + ".keys",
- password
- ) == true
- }
-
- interface PasswordListener {
- fun onPasswordSuccess(password: String)
- fun onPasswordFail()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/dialog/SendBottomSheetDialog.kt b/app/src/main/java/net/mynero/wallet/fragment/dialog/SendBottomSheetDialog.kt
deleted file mode 100644
index fcb06dc..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/dialog/SendBottomSheetDialog.kt
+++ /dev/null
@@ -1,407 +0,0 @@
-package net.mynero.wallet.fragment.dialog
-
-import android.app.Activity
-import android.os.Bundle
-import android.text.Editable
-import android.text.TextWatcher
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.EditText
-import android.widget.ImageButton
-import android.widget.RadioGroup
-import android.widget.TextView
-import android.widget.Toast
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import com.google.android.material.bottomsheet.BottomSheetDialogFragment
-import com.google.zxing.client.android.Intents
-import com.journeyapps.barcodescanner.ScanContract
-import com.journeyapps.barcodescanner.ScanIntentResult
-import com.journeyapps.barcodescanner.ScanOptions
-import net.mynero.wallet.MoneroApplication
-import net.mynero.wallet.R
-import net.mynero.wallet.model.PendingTransaction
-import net.mynero.wallet.model.Wallet
-import net.mynero.wallet.model.Wallet.Companion.getAmountFromString
-import net.mynero.wallet.model.Wallet.Companion.isAddressValid
-import net.mynero.wallet.service.BalanceService
-import net.mynero.wallet.service.TxService
-import net.mynero.wallet.service.UTXOService
-import net.mynero.wallet.util.Constants
-import net.mynero.wallet.util.Helper
-import net.mynero.wallet.util.Helper.getCameraPermission
-import net.mynero.wallet.util.Helper.getClipBoardText
-import net.mynero.wallet.util.UriData
-import net.mynero.wallet.util.UriData.Companion.parse
-
-class SendBottomSheetDialog : BottomSheetDialogFragment() {
- private val _sendingMax = MutableLiveData(false)
- private val _pendingTransaction = MutableLiveData(null)
- var selectedUtxos = ArrayList()
- private var sendingMax: LiveData = _sendingMax
- private var pendingTransaction: LiveData = _pendingTransaction
- var uriData: UriData? = null
- private val cameraPermissionsLauncher = registerForActivityResult(
- ActivityResultContracts.RequestPermission()
- ) { granted: Boolean ->
- if (granted) {
- onScan()
- } else {
- Toast.makeText(activity, getString(R.string.no_camera_permission), Toast.LENGTH_SHORT)
- .show()
- }
- }
-
- var isChurning = false
- var listener: Listener? = null
- var priority: PendingTransaction.Priority = PendingTransaction.Priority.Priority_Low
- private var addressEditText: EditText? = null
- private var amountEditText: EditText? = null
- private val barcodeLauncher = registerForActivityResult(
- ScanContract()
- ) { result: ScanIntentResult ->
- if (result.contents != null) {
- pasteAddress(result.contents)
- }
- }
- private var sendAllTextView: TextView? = null
- private var feeTextView: TextView? = null
- private var addressTextView: TextView? = null
- private var amountTextView: TextView? = null
- private var feeRadioGroupLabelTextView: TextView? = null
- private var selectedUtxosValueTextView: TextView? = null
- private var createButton: Button? = null
- private var sendButton: Button? = null
- private var sendMaxButton: Button? = null
- private var pasteAddressImageButton: ImageButton? = null
- private var scanAddressImageButton: ImageButton? = null
- private var feeRadioGroup: RadioGroup? = null
- private var donateTextView: TextView? = null
- private var donatingTextView: TextView? = null
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.send_bottom_sheet_dialog, null)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- pasteAddressImageButton = view.findViewById(R.id.paste_address_imagebutton)
- scanAddressImageButton = view.findViewById(R.id.scan_address_imagebutton)
- sendMaxButton = view.findViewById(R.id.send_max_button)
- addressEditText = view.findViewById(R.id.address_edittext)
- amountEditText = view.findViewById(R.id.amount_edittext)
- sendButton = view.findViewById(R.id.send_tx_button)
- createButton = view.findViewById(R.id.create_tx_button)
- sendAllTextView = view.findViewById(R.id.sending_all_textview)
- feeTextView = view.findViewById(R.id.fee_textview)
- addressTextView = view.findViewById(R.id.address_pending_textview)
- amountTextView = view.findViewById(R.id.amount_pending_textview)
- feeRadioGroup = view.findViewById(R.id.tx_fee_radiogroup)
- feeRadioGroupLabelTextView = view.findViewById(R.id.tx_fee_radiogroup_label_textview)
- selectedUtxosValueTextView = view.findViewById(R.id.selected_utxos_value_textview)
- donateTextView = view.findViewById(R.id.donate_label_textview)
- donatingTextView = view.findViewById(R.id.donating_label_textview)
- donateTextView?.setOnClickListener {
- addressEditText?.setText(
- Constants.DONATE_ADDRESS
- )
- }
- donatingTextView?.setOnClickListener {
- addressEditText?.setText("")
- }
- addressEditText?.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
- override fun afterTextChanged(s: Editable) {
- if (s.toString() == Constants.DONATE_ADDRESS) {
- donatingTextView?.visibility = View.VISIBLE
- donateTextView?.visibility = View.INVISIBLE
- } else {
- donatingTextView?.visibility = View.INVISIBLE
- donateTextView?.visibility = View.VISIBLE
- }
- }
- })
- if (uriData != null) {
- addressEditText?.setText(uriData?.address)
- if (uriData?.hasAmount() == true) {
- amountEditText?.setText(uriData?.amount)
- }
- }
- if (selectedUtxos.isNotEmpty()) {
- var selectedValue: Long = 0
- val utxos = UTXOService.instance?.getUtxos() ?: return
- for (coinsInfo in utxos) {
- if (selectedUtxos.contains(coinsInfo.keyImage)) {
- selectedValue += coinsInfo.amount
- }
- }
- val valueString = Wallet.getDisplayAmount(selectedValue)
- selectedUtxosValueTextView?.visibility = View.VISIBLE
- if (isChurning) {
- _sendingMax.postValue(true)
- sendMaxButton?.isEnabled = false
- selectedUtxosValueTextView?.text = resources.getString(
- R.string.selected_utxos_value_churning,
- valueString
- )
- } else {
- selectedUtxosValueTextView?.text = resources.getString(
- R.string.selected_utxos_value,
- valueString
- )
- }
- } else {
- selectedUtxosValueTextView?.visibility = View.GONE
- }
- bindObservers()
- bindListeners()
- }
-
- private fun bindObservers() {
- BalanceService.instance?.balanceInfo?.observe(viewLifecycleOwner) { balanceInfo ->
- createButton?.isEnabled = balanceInfo?.rawUnlocked != 0L
- if (!isChurning) {
- sendMaxButton?.isEnabled = balanceInfo?.rawUnlocked != 0L
- }
- }
- sendingMax.observe(viewLifecycleOwner) { sendingMax: Boolean? ->
- if (pendingTransaction.value == null) {
- if (sendingMax == true) {
- amountEditText?.visibility = View.INVISIBLE
- sendAllTextView?.visibility = View.VISIBLE
- sendMaxButton?.text = getText(R.string.undo)
- } else {
- amountEditText?.visibility = View.VISIBLE
- sendAllTextView?.visibility = View.GONE
- sendMaxButton?.text = getText(R.string.send_max)
- }
- }
- }
- pendingTransaction.observe(viewLifecycleOwner) { pendingTx: PendingTransaction? ->
- showConfirmationLayout(pendingTx != null)
- if (pendingTx != null) {
- val address = addressEditText?.text.toString()
- addressTextView?.text = getString(R.string.tx_address_text, address)
- amountTextView?.text = getString(
- R.string.tx_amount_text,
- Helper.getDisplayAmount(pendingTx.getAmount())
- )
- feeTextView?.text =
- getString(R.string.tx_fee_text, Helper.getDisplayAmount(pendingTx.getFee()))
- }
- }
- }
-
- private fun bindListeners() {
- feeRadioGroup?.check(R.id.low_fee_radiobutton)
- feeRadioGroup?.setOnCheckedChangeListener { _: RadioGroup?, i: Int ->
- when (i) {
- R.id.low_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_Low
- R.id.med_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_Medium
- R.id.high_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_High
- }
- }
- pasteAddressImageButton?.setOnClickListener {
- val ctx = context
- if (ctx != null) {
- val clipboard = getClipBoardText(ctx)
- clipboard?.let { pasteAddress(it) }
- }
- }
- scanAddressImageButton?.setOnClickListener { onScan() }
- sendMaxButton?.setOnClickListener {
- val currentValue = if (sendingMax.value != null) sendingMax.value else false
- _sendingMax.postValue(currentValue == false)
- }
- createButton?.setOnClickListener {
- val activity = activity
- if (activity != null) {
- val sendAll = sendingMax.value ?: false
- val address = addressEditText?.text.toString().trim { it <= ' ' }
- val amount = amountEditText?.text.toString().trim { it <= ' ' }
- val validAddress = isAddressValid(address)
- if (validAddress && (amount.isNotEmpty() || sendAll)) {
- val amountRaw = getAmountFromString(amount)
- val balance = BalanceService.instance?.unlockedBalanceRaw ?: 0
- if ((amountRaw >= balance || amountRaw <= 0) && !sendAll) {
- Toast.makeText(
- activity,
- getString(R.string.send_amount_invalid),
- Toast.LENGTH_SHORT
- ).show()
- return@setOnClickListener
- }
- Toast.makeText(activity, getString(R.string.creating_tx), Toast.LENGTH_SHORT)
- .show()
- createButton?.isEnabled = false
- createTx(address, amount, sendAll, priority)
- } else if (!validAddress) {
- Toast.makeText(
- activity,
- getString(R.string.send_address_invalid),
- Toast.LENGTH_SHORT
- ).show()
- } else {
- Toast.makeText(
- activity,
- getString(R.string.send_amount_empty),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
- }
- sendButton?.setOnClickListener {
- val pendingTx = pendingTransaction.value
- if (pendingTx != null) {
- Toast.makeText(activity, getString(R.string.sending_tx), Toast.LENGTH_SHORT).show()
- sendButton?.isEnabled = false
- sendTx(pendingTx)
- }
- }
- }
-
- private fun onScan() {
- if (activity?.let { getCameraPermission(it, cameraPermissionsLauncher) } == true) {
- val options = ScanOptions()
- options.setBeepEnabled(false)
- options.setOrientationLocked(true)
- options.setDesiredBarcodeFormats(listOf(Intents.Scan.QR_CODE_MODE))
- options.addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
- barcodeLauncher.launch(options)
- }
- }
-
- private fun sendTx(pendingTx: PendingTransaction) {
- val activity: Activity? = activity
- if (activity != null) {
- (activity.application as MoneroApplication).executor?.execute {
- val success = TxService.instance?.sendTx(pendingTx)
- activity.runOnUiThread(Runnable {
- if (success == true) {
- Toast.makeText(activity, getString(R.string.sent_tx), Toast.LENGTH_SHORT)
- .show()
- if (listener != null) {
- listener?.onSentTransaction()
- }
- dismiss()
- } else {
- sendButton?.isEnabled = true
- Toast.makeText(
- activity,
- getString(R.string.error_sending_tx),
- Toast.LENGTH_SHORT
- ).show()
- }
- })
- }
- }
- }
-
- private fun createTx(
- address: String,
- amount: String,
- sendAll: Boolean,
- feePriority: PendingTransaction.Priority
- ) {
- val activity: Activity? = activity
- if (activity != null) {
- (activity.application as MoneroApplication).executor?.execute {
- try {
- val pendingTx = TxService.instance?.createTx(
- address,
- amount,
- sendAll,
- feePriority,
- selectedUtxos
- )
- if (pendingTx != null && pendingTx.status === PendingTransaction.Status.Status_Ok) {
- _pendingTransaction.postValue(pendingTx)
- } else {
- activity.runOnUiThread(Runnable {
- createButton?.isEnabled = true
- if (pendingTx != null) {
- Toast.makeText(
- activity,
- getString(
- R.string.error_creating_tx,
- pendingTx.getErrorString()
- ),
- Toast.LENGTH_SHORT
- ).show()
- }
- })
- }
- } catch (e: Exception) {
- activity.runOnUiThread(Runnable {
- createButton?.isEnabled = true
- Toast.makeText(activity, e.message, Toast.LENGTH_SHORT).show()
- })
- }
- }
- }
- }
-
- private fun showConfirmationLayout(show: Boolean) {
- if (show) {
- sendButton?.visibility = View.VISIBLE
- addressEditText?.visibility = View.GONE
- amountEditText?.visibility = View.GONE
- sendAllTextView?.visibility = View.GONE
- createButton?.visibility = View.GONE
- sendMaxButton?.visibility = View.GONE
- pasteAddressImageButton?.visibility = View.GONE
- scanAddressImageButton?.visibility = View.GONE
- feeTextView?.visibility = View.VISIBLE
- addressTextView?.visibility = View.VISIBLE
- amountTextView?.visibility = View.VISIBLE
- selectedUtxosValueTextView?.visibility = View.GONE
- feeRadioGroup?.visibility = View.GONE
- feeRadioGroupLabelTextView?.visibility = View.GONE
- donateTextView?.visibility = View.GONE
- } else {
- sendButton?.visibility = View.GONE
- addressEditText?.visibility = View.VISIBLE
- amountEditText?.visibility =
- if (java.lang.Boolean.TRUE == sendingMax.value) View.GONE else View.VISIBLE
- sendAllTextView?.visibility =
- if (java.lang.Boolean.TRUE == sendingMax.value) View.VISIBLE else View.GONE
- createButton?.visibility = View.VISIBLE
- sendMaxButton?.visibility = View.VISIBLE
- pasteAddressImageButton?.visibility = View.VISIBLE
- scanAddressImageButton?.visibility = View.VISIBLE
- feeTextView?.visibility = View.GONE
- addressTextView?.visibility = View.GONE
- amountTextView?.visibility = View.GONE
- if (selectedUtxos.isNotEmpty()) {
- selectedUtxosValueTextView?.visibility = View.VISIBLE
- }
- feeRadioGroup?.visibility = View.VISIBLE
- feeRadioGroupLabelTextView?.visibility = View.VISIBLE
- donateTextView?.visibility = View.VISIBLE
- }
- }
-
- private fun pasteAddress(address: String) {
- val uriData = parse(address)
- if (uriData != null) {
- addressEditText?.setText(uriData.address)
- if (uriData.hasAmount()) {
- amountEditText?.setText(uriData.amount)
- }
- } else {
- Toast.makeText(activity, getString(R.string.send_address_invalid), Toast.LENGTH_SHORT)
- .show()
- }
- }
-
- interface Listener {
- fun onSentTransaction()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/home/HomeViewModel.kt b/app/src/main/java/net/mynero/wallet/fragment/home/HomeViewModel.kt
deleted file mode 100644
index 956649b..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/home/HomeViewModel.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package net.mynero.wallet.fragment.home
-
-import androidx.lifecycle.ViewModel
-
-class HomeViewModel : ViewModel()
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.kt b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.kt
deleted file mode 100644
index 0109a84..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingFragment.kt
+++ /dev/null
@@ -1,350 +0,0 @@
-package net.mynero.wallet.fragment.onboarding
-
-import android.os.Bundle
-import android.text.Editable
-import android.text.TextWatcher
-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.ImageView
-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 com.google.android.material.progressindicator.CircularProgressIndicator
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.launch
-import net.mynero.wallet.R
-import net.mynero.wallet.data.Node
-import net.mynero.wallet.fragment.dialog.AddNodeBottomSheetDialog
-import net.mynero.wallet.fragment.dialog.AddNodeBottomSheetDialog.AddNodeListener
-import net.mynero.wallet.fragment.dialog.NodeSelectionBottomSheetDialog
-import net.mynero.wallet.fragment.dialog.NodeSelectionBottomSheetDialog.NodeSelectionDialogListener
-import net.mynero.wallet.fragment.onboarding.OnboardingViewModel.SeedType
-import net.mynero.wallet.model.EnumTorState
-import net.mynero.wallet.service.PrefService
-import net.mynero.wallet.service.ProxyService
-
-class OnboardingFragment : Fragment(), NodeSelectionDialogListener, AddNodeListener {
- private var useOffset = true
- private var mViewModel: OnboardingViewModel? = null
- private var walletProxyAddressEditText: EditText? = null
- private var walletProxyPortEditText: EditText? = null
- private var walletPasswordEditText: EditText? = null
- private var walletPasswordConfirmEditText: EditText? = null
- private var walletSeedEditText: EditText? = null
- private var walletRestoreHeightEditText: EditText? = null
- private var createWalletButton: Button? = null
- private var moreOptionsDropdownTextView: TextView? = null
- private var torSwitch: SwitchCompat? = null
- private var advancedOptionsLayout: ConstraintLayout? = null
- private var moreOptionsChevronImageView: ImageView? = null
- private var seedOffsetCheckbox: CheckBox? = null
- private var selectNodeButton: Button? = null
- private var showXmrchanSwitch: SwitchCompat? = null
- private var xmrchanOnboardingImage: ImageView? = null
- private var seedTypeButton: Button? = null
- private var seedTypeDescTextView: TextView? = null
- private var useBundledTor: CheckBox? = null
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.fragment_onboarding, container, false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- mViewModel = ViewModelProvider(this)[OnboardingViewModel::class.java]
- selectNodeButton = view.findViewById(R.id.select_node_button)
- walletPasswordEditText = view.findViewById(R.id.wallet_password_edittext)
- walletPasswordConfirmEditText = view.findViewById(R.id.wallet_password_confirm_edittext)
- walletSeedEditText = view.findViewById(R.id.wallet_seed_edittext)
- walletRestoreHeightEditText = view.findViewById(R.id.wallet_restore_height_edittext)
- createWalletButton = view.findViewById(R.id.create_wallet_button)
- moreOptionsDropdownTextView = view.findViewById(R.id.advanced_settings_dropdown_textview)
- moreOptionsChevronImageView = view.findViewById(R.id.advanced_settings_chevron_imageview)
- torSwitch = view.findViewById(R.id.tor_onboarding_switch)
- seedOffsetCheckbox = view.findViewById(R.id.seed_offset_checkbox)
- walletProxyAddressEditText = view.findViewById(R.id.wallet_proxy_address_edittext)
- walletProxyPortEditText = view.findViewById(R.id.wallet_proxy_port_edittext)
- advancedOptionsLayout = view.findViewById(R.id.more_options_layout)
- showXmrchanSwitch = view.findViewById(R.id.show_xmrchan_switch)
- xmrchanOnboardingImage = view.findViewById(R.id.xmrchan_onboarding_imageview)
- seedTypeButton = view.findViewById(R.id.seed_type_button)
- seedTypeDescTextView = view.findViewById(R.id.seed_type_desc_textview)
- useBundledTor = view.findViewById(R.id.bundled_tor_checkbox)
-
- seedOffsetCheckbox?.isChecked = useOffset
- val usingProxy = ProxyService.instance?.usingProxy == true
- val usingBundledTor = ProxyService.instance?.useBundledTor == true
-
- torSwitch?.isChecked = usingProxy
- useBundledTor?.isChecked = usingBundledTor
- useBundledTor?.isEnabled = usingProxy
- walletProxyAddressEditText?.isEnabled = usingProxy && !usingBundledTor
- walletProxyPortEditText?.isEnabled = usingProxy && !usingBundledTor
-
- val node = PrefService.instance?.node // should be using default here
- selectNodeButton?.text = getString(R.string.node_button_text, node?.address)
-
- bindListeners()
- bindObservers()
- }
-
- private fun bindObservers() {
- mViewModel?.passphrase?.observe(viewLifecycleOwner) { text ->
- if (text.isEmpty()) {
- walletPasswordConfirmEditText?.text = null
- walletPasswordConfirmEditText?.visibility = View.GONE
- } else {
- walletPasswordConfirmEditText?.visibility = View.VISIBLE
- }
- }
-
- mViewModel?.showMoreOptions?.observe(viewLifecycleOwner) { show: Boolean ->
- if (show) {
- moreOptionsChevronImageView?.setImageResource(R.drawable.ic_keyboard_arrow_up)
- advancedOptionsLayout?.visibility = View.VISIBLE
- } else {
- moreOptionsChevronImageView?.setImageResource(R.drawable.ic_keyboard_arrow_down)
- advancedOptionsLayout?.visibility = View.GONE
- }
- }
-
- mViewModel?.enableButton?.observe(viewLifecycleOwner) { enable: Boolean ->
- createWalletButton?.isEnabled = enable
- }
-
- mViewModel?.seedType?.observe(viewLifecycleOwner) { seedType: SeedType ->
- seedTypeButton?.text = seedType.toString()
- seedTypeDescTextView?.text = getText(seedType.descResId)
- if (seedType == SeedType.LEGACY) {
- seedOffsetCheckbox?.visibility = View.VISIBLE
- walletRestoreHeightEditText?.visibility = View.VISIBLE
- walletPasswordEditText?.hint = getString(R.string.password_optional)
- walletSeedEditText?.hint = getString(R.string.recovery_phrase_optional_legacy)
- } else {
- seedOffsetCheckbox?.visibility = View.GONE
- walletRestoreHeightEditText?.visibility = View.GONE
- walletPasswordEditText?.hint = getString(R.string.password_non_optional)
- walletSeedEditText?.hint = getString(R.string.recovery_phrase_optional_polyseed)
- }
- }
-
- mViewModel?.showMonerochan?.observe(viewLifecycleOwner) {
- if (it) {
- xmrchanOnboardingImage?.visibility = View.VISIBLE
- } else {
- xmrchanOnboardingImage?.visibility = View.GONE
- }
- }
-
- mViewModel?.useBundledTor?.observe(viewLifecycleOwner) { isChecked ->
- walletProxyPortEditText?.isEnabled = !isChecked && mViewModel?.useProxy?.value == true
- walletProxyAddressEditText?.isEnabled = !isChecked && mViewModel?.useProxy?.value == true
- }
-
- mViewModel?.useProxy?.observe(viewLifecycleOwner) { useProxy ->
- useBundledTor?.isEnabled = useProxy
- walletProxyAddressEditText?.isEnabled = useProxy && mViewModel?.useBundledTor?.value == false
- walletProxyPortEditText?.isEnabled = useProxy && mViewModel?.useBundledTor?.value == false
- }
-
- val samouraiTorManager = ProxyService.instance?.samouraiTorManager
- val indicatorCircle =
- view?.findViewById(R.id.onboarding_tor_loading_progressindicator)
- val torIcon = view?.findViewById(R.id.onboarding_tor_icon)
-
- samouraiTorManager?.getTorStateLiveData()?.observe(viewLifecycleOwner) { state ->
- samouraiTorManager.getProxy()?.address()?.let { socketAddress ->
- if (socketAddress.toString().isEmpty()) return@let
- if (mViewModel?.useProxy?.value == true && mViewModel?.useBundledTor?.value == true) {
- torIcon?.visibility = View.VISIBLE
- indicatorCircle?.visibility = View.INVISIBLE
- val proxyString = socketAddress.toString().substring(1)
- val address = proxyString.split(":")[0]
- val port = proxyString.split(":")[1]
- updateProxy(address, port)
- }
- }
-
- indicatorCircle?.isIndeterminate = state.progressIndicator == 0
- indicatorCircle?.progress = state.progressIndicator
-
- when (state.state) {
- EnumTorState.OFF -> {
- torIcon?.visibility = View.INVISIBLE
- indicatorCircle?.visibility = View.INVISIBLE
- }
-
- EnumTorState.STARTING, EnumTorState.STOPPING -> {
- torIcon?.visibility = View.INVISIBLE
- indicatorCircle?.visibility = View.VISIBLE
- }
-
- else -> {}
- }
- }
- }
-
- private fun updateProxy(address: String, port: String) {
- walletProxyPortEditText?.setText(port)
- walletProxyAddressEditText?.setText(address)
- }
-
- private fun bindListeners() {
- // Disable onBack click
- val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {}
- }
- activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, onBackPressedCallback)
-
- moreOptionsDropdownTextView?.setOnClickListener { mViewModel?.onMoreOptionsClicked() }
-
- moreOptionsChevronImageView?.setOnClickListener { mViewModel?.onMoreOptionsClicked() }
-
- seedOffsetCheckbox?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
- useOffset = b
- }
-
- createWalletButton?.setOnClickListener {
- onBackPressedCallback.isEnabled = false
- createOrImportWallet(
- walletSeedEditText?.text.toString().trim { it <= ' ' },
- walletRestoreHeightEditText?.text.toString().trim { it <= ' ' }
- )
- }
-
- walletPasswordEditText?.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun afterTextChanged(editable: Editable) {
- mViewModel?.setPassphrase(editable.toString())
- }
- })
-
- 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) {
- mViewModel?.setConfirmedPassphrase(editable.toString())
- }
- })
-
- walletSeedEditText?.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun afterTextChanged(editable: Editable) {
- val text = editable.toString()
- if (text.isEmpty()) {
- createWalletButton?.setText(R.string.create_wallet)
- } else {
- createWalletButton?.setText(R.string.menu_restore)
- }
- }
- })
-
- seedTypeButton?.setOnClickListener { toggleSeedType() }
-
- torSwitch?.setOnCheckedChangeListener { _, b: Boolean ->
- mViewModel?.setUseProxy(b)
- }
-
- walletProxyPortEditText?.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun afterTextChanged(editable: Editable) {
- val text = editable.toString()
- mViewModel?.setProxyPort(text)
- }
- })
-
- walletProxyAddressEditText?.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) {}
- override fun afterTextChanged(editable: Editable) {
- val text = editable.toString()
- mViewModel?.setProxyAddress(text)
- }
- })
-
- showXmrchanSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
- mViewModel?.setMonerochan(b)
- }
-
- selectNodeButton?.setOnClickListener {
- activity?.supportFragmentManager?.let { fragmentManager ->
- val dialog = NodeSelectionBottomSheetDialog()
- dialog.listener = this
- dialog.show(fragmentManager, "node_selection_dialog")
- }
- }
-
- useBundledTor?.setOnCheckedChangeListener { _, isChecked ->
- mViewModel?.setUseBundledTor(isChecked)
- }
- }
-
- private fun toggleSeedType() {
- val seedType = mViewModel?.seedType?.value ?: return
- var newSeedType = SeedType.UNKNOWN
- if (seedType == SeedType.POLYSEED) {
- newSeedType = SeedType.LEGACY
- } else if (seedType == SeedType.LEGACY) {
- newSeedType = SeedType.POLYSEED
- }
- mViewModel?.setSeedType(newSeedType)
- }
-
- private fun createOrImportWallet(
- walletSeed: String,
- restoreHeightText: String
- ) {
- activity?.let { act ->
- lifecycleScope.launch(Dispatchers.IO) {
- mViewModel?.createOrImportWallet(
- act,
- walletSeed,
- restoreHeightText,
- useOffset
- )
- }
- }
- }
-
- override fun onNodeSelected() {
- val node = PrefService.instance?.node
- selectNodeButton?.text = getString(R.string.node_button_text, node?.address)
- Toast.makeText(
- activity,
- getString(R.string.node_selected, node?.name ?: node?.host),
- Toast.LENGTH_SHORT
- ).show()
- }
-
- override fun onClickedEditNode(node: Node?) {}
- override fun onClickedAddNode() {
- activity?.supportFragmentManager?.let { fragmentManager ->
- val addNodeDialog = AddNodeBottomSheetDialog()
- addNodeDialog.listener = this
- addNodeDialog.show(fragmentManager, "add_node_dialog")
- }
- }
-
- override fun onNodeAdded() {
- activity?.supportFragmentManager?.let { fragmentManager ->
- val dialog = NodeSelectionBottomSheetDialog()
- dialog.listener = this
- dialog.show(fragmentManager, "node_selection_dialog")
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingViewModel.kt b/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingViewModel.kt
deleted file mode 100644
index c2de57b..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/onboarding/OnboardingViewModel.kt
+++ /dev/null
@@ -1,283 +0,0 @@
-package net.mynero.wallet.fragment.onboarding
-
-import android.app.Activity
-import android.widget.Toast
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import net.mynero.wallet.MainActivity
-import net.mynero.wallet.MoneroApplication
-import net.mynero.wallet.R
-import net.mynero.wallet.livedata.combineLiveDatas
-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
-import java.util.Calendar
-
-class OnboardingViewModel : ViewModel() {
- private val _showMoreOptions = MutableLiveData(false)
- private val _creatingWallet = MutableLiveData(false)
- private val _seedType = MutableLiveData(SeedType.POLYSEED)
- private val _useProxy = MutableLiveData(false)
- val useProxy: LiveData = _useProxy
- private val _proxyAddress = MutableLiveData("")
- private val _proxyPort = MutableLiveData("")
- private val _useBundledTor = MutableLiveData(false)
- val useBundledTor: LiveData = _useBundledTor
- private val _passphrase = MutableLiveData("")
- val passphrase: LiveData = _passphrase
- private val _confirmedPassphrase = MutableLiveData("")
- private val _showMonerochan = MutableLiveData(Constants.DEFAULT_PREF_MONEROCHAN)
- val showMonerochan: LiveData = _showMonerochan
- var showMoreOptions: LiveData = _showMoreOptions
- var seedType: LiveData = _seedType
-
- init {
- _useProxy.value = ProxyService.instance?.usingProxy
- _useBundledTor.value = ProxyService.instance?.useBundledTor
- }
-
- val enableButton = combineLiveDatas(
- seedType,
- _useProxy,
- _proxyAddress,
- _proxyPort,
- _useBundledTor,
- _passphrase,
- _confirmedPassphrase,
- _creatingWallet,
- ProxyService.instance?.samouraiTorManager?.getTorStateLiveData()
- ) { seedType, useProxy, proxyAddress, proxyPort, useBundledTor, passphrase, confirmedPassphrase, creatingWallet, torState ->
- if (seedType == null || useProxy == null || proxyAddress == null || proxyPort == null || useBundledTor == null || passphrase == null || confirmedPassphrase == null || creatingWallet == null) return@combineLiveDatas false
- if ((passphrase.isNotEmpty() || confirmedPassphrase.isNotEmpty()) && passphrase != confirmedPassphrase) return@combineLiveDatas false
- if (creatingWallet) return@combineLiveDatas false
- if (seedType == SeedType.POLYSEED && (passphrase.isEmpty() || confirmedPassphrase.isEmpty())) return@combineLiveDatas false
- if (useProxy && (proxyAddress.isEmpty() || proxyPort.isEmpty()) && !useBundledTor) return@combineLiveDatas false
- val progress = torState?.progressIndicator ?: 0
- if (useBundledTor && progress < 100 && useProxy) return@combineLiveDatas false
-
- return@combineLiveDatas true
- }
-
- fun onMoreOptionsClicked() {
- val currentValue = showMoreOptions.value ?: false
- val newValue = !currentValue
- _showMoreOptions.value = newValue
- }
-
- fun setSeedType(seedType: SeedType?) {
- _seedType.value = seedType
- }
-
- fun createOrImportWallet(
- mainActivity: Activity,
- walletSeed: String,
- restoreHeightText: String,
- useOffset: Boolean
- ) {
- val passphrase = _passphrase.value ?: return
- val confirmedPassphrase = _confirmedPassphrase.value ?: return
-
- val application = mainActivity.application as MoneroApplication
- _creatingWallet.postValue(true)
- val offset = if (useOffset) confirmedPassphrase else ""
- if (passphrase.isNotEmpty()) {
- if (passphrase != confirmedPassphrase) {
- _creatingWallet.postValue(false)
- mainActivity.runOnUiThread {
- Toast.makeText(
- mainActivity,
- application.getString(R.string.invalid_confirmed_password),
- Toast.LENGTH_SHORT
- ).show()
- }
- return
- }
- PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_PASSWORD, true)
- ?.apply()
- }
- var restoreHeight = newRestoreHeight
- val walletFile = File(mainActivity.applicationInfo.dataDir, Constants.WALLET_NAME)
- var wallet: Wallet? = null
- if (offset.isNotEmpty()) {
- PrefService.instance?.edit()?.putBoolean(Constants.PREF_USES_OFFSET, true)?.apply()
- }
- val seedTypeValue = seedType.value ?: return
- if (walletSeed.isEmpty()) {
- if (seedTypeValue == SeedType.POLYSEED) {
- wallet = if (offset.isEmpty()) {
- mainActivity.runOnUiThread {
- _creatingWallet.postValue(false)
- Toast.makeText(
- mainActivity,
- application.getString(R.string.invalid_empty_passphrase),
- Toast.LENGTH_SHORT
- ).show()
- }
- return
- } else {
- WalletManager.instance?.createWalletPolyseed(
- walletFile,
- passphrase,
- offset,
- Constants.MNEMONIC_LANGUAGE
- )
- }
- } else if (seedTypeValue == SeedType.LEGACY) {
- val tmpWalletFile =
- File(mainActivity.applicationInfo.dataDir, Constants.WALLET_NAME + "_tmp")
- val tmpWallet =
- createTempWallet(tmpWalletFile) //we do this to get seed, then recover wallet so we can use seed offset
- tmpWallet?.let {
- wallet = WalletManager.instance?.recoveryWallet(
- walletFile,
- passphrase,
- tmpWallet.getSeed("") ?: return@let,
- offset,
- restoreHeight
- )
- tmpWalletFile.delete()
- }
- }
- } else {
- if (getMnemonicType(walletSeed) == SeedType.UNKNOWN) {
- mainActivity.runOnUiThread {
- _creatingWallet.postValue(false)
- Toast.makeText(
- mainActivity,
- application.getString(R.string.invalid_mnemonic_code),
- Toast.LENGTH_SHORT
- ).show()
- }
- return
- }
- if (restoreHeightText.isNotEmpty()) {
- restoreHeight = restoreHeightText.toLong()
- }
- if (seedTypeValue == SeedType.POLYSEED) {
- wallet = WalletManager.instance?.recoveryWalletPolyseed(
- walletFile,
- passphrase,
- walletSeed,
- offset
- )
- } else if (seedTypeValue == SeedType.LEGACY) {
- wallet = WalletManager.instance?.recoveryWallet(
- walletFile,
- passphrase,
- walletSeed,
- offset,
- restoreHeight
- )
- }
- }
- val walletStatus = wallet?.status
- wallet?.close()
- 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, passphrase)
- mainActivity.runOnUiThread { mainActivity.onBackPressed() }
- } else {
- mainActivity.runOnUiThread {
- _creatingWallet.postValue(false)
- Toast.makeText(
- mainActivity,
- application.getString(
- R.string.create_wallet_failed,
- walletStatus?.errorString
- ),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
- }
-
- private val newRestoreHeight: Long
- get() {
- val restoreDate = Calendar.getInstance()
- restoreDate.add(Calendar.DAY_OF_MONTH, 0)
- return RestoreHeight.instance?.getHeight(restoreDate.time) ?: 0
- }
-
- private fun getMnemonicType(seed: String): SeedType {
- val words = seed.split("\\s".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()
- val seedTypeValue = seedType.value ?: return SeedType.LEGACY
- return if (words.size == 16 && seedTypeValue == SeedType.POLYSEED) {
- SeedType.POLYSEED
- } else if (words.size == 25 && seedTypeValue == SeedType.LEGACY) {
- SeedType.LEGACY
- } else {
- SeedType.UNKNOWN
- }
- }
-
- private fun createTempWallet(tmpWalletFile: File): Wallet? {
- return WalletManager.instance?.createWallet(
- tmpWalletFile,
- "",
- Constants.MNEMONIC_LANGUAGE,
- 0
- )
- }
-
- fun setProxyAddress(address: String) {
- _proxyAddress.value = address
- if (address.isEmpty()) PrefService.instance?.deleteProxy()
- val port = _proxyPort.value ?: return
- ProxyService.instance?.updateProxy(address, port)
- }
-
- fun setProxyPort(port: String) {
- _proxyPort.value = port
- if (port.isEmpty()) PrefService.instance?.deleteProxy()
- val address = _proxyAddress.value ?: return
- ProxyService.instance?.updateProxy(address, port)
- }
-
- fun setUseBundledTor(useBundled: Boolean) {
- _useBundledTor.value = useBundled
- ProxyService.instance?.useBundledTor = useBundled
-
- val samouraiTorManager = ProxyService.instance?.samouraiTorManager
- if (useBundled && ProxyService.instance?.usingProxy == true) {
- samouraiTorManager?.start()
- } else {
- samouraiTorManager?.stop()
- }
- }
-
- fun setUseProxy(useProxy: Boolean) {
- _useProxy.value = useProxy
- ProxyService.instance?.usingProxy = useProxy
-
- val samouraiTorManager = ProxyService.instance?.samouraiTorManager
- if (useProxy && ProxyService.instance?.useBundledTor == true) {
- samouraiTorManager?.start()
- } else {
- samouraiTorManager?.stop()
- }
- }
-
- fun setPassphrase(passphrase: String) {
- _passphrase.value = passphrase
- }
-
- fun setConfirmedPassphrase(confirmedPassphrase: String) {
- _confirmedPassphrase.value = confirmedPassphrase
- }
-
- fun setMonerochan(b: Boolean) {
- _showMonerochan.value = b
- PrefService.instance?.edit()?.putBoolean(Constants.PREF_MONEROCHAN, b)?.apply()
- }
-
- enum class SeedType(val descResId: Int) {
- LEGACY(R.string.seed_desc_legacy), POLYSEED(R.string.seed_desc_polyseed), UNKNOWN(0)
-
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveViewModel.kt b/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveViewModel.kt
deleted file mode 100644
index 5c2b0d8..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/receive/ReceiveViewModel.kt
+++ /dev/null
@@ -1,42 +0,0 @@
-package net.mynero.wallet.fragment.receive
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import net.mynero.wallet.data.Subaddress
-import net.mynero.wallet.model.WalletManager
-import net.mynero.wallet.service.AddressService
-import java.util.Collections
-
-class ReceiveViewModel : ViewModel() {
- private val _address = MutableLiveData()
- private val _addresses = MutableLiveData>()
- val address: LiveData = _address
- val addresses: LiveData> = _addresses
-
- fun init() {
- _addresses.value = subaddresses
- _address.value = addresses.value?.lastOrNull()
- }
-
- private val subaddresses: List
- get() {
- val wallet = WalletManager.instance?.wallet
- val subaddresses = ArrayList()
- val numAddresses = AddressService.instance?.numAddresses ?: 1
- for (i in 0 until numAddresses) {
- wallet?.getSubaddressObject(i)?.let { subaddresses.add(it) }
- }
- return Collections.unmodifiableList(subaddresses)
- }
-
- val freshSubaddress: Unit
- get() {
- _address.value = AddressService.instance?.freshSubaddress()
- _addresses.value = subaddresses
- }
-
- fun selectAddress(subaddress: Subaddress?) {
- _address.value = subaddress
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/send/SendFragment.kt b/app/src/main/java/net/mynero/wallet/fragment/send/SendFragment.kt
deleted file mode 100644
index 065662d..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/send/SendFragment.kt
+++ /dev/null
@@ -1,550 +0,0 @@
-package net.mynero.wallet.fragment.send
-
-import android.app.Activity
-import android.os.Bundle
-import android.text.Editable
-import android.text.TextWatcher
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.EditText
-import android.widget.ImageButton
-import android.widget.LinearLayout
-import android.widget.RadioGroup
-import android.widget.TextView
-import android.widget.Toast
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.constraintlayout.widget.ConstraintLayout
-import androidx.core.view.ViewCompat
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
-import com.google.zxing.client.android.Intents
-import com.journeyapps.barcodescanner.ScanContract
-import com.journeyapps.barcodescanner.ScanIntentResult
-import com.journeyapps.barcodescanner.ScanOptions
-import com.ncorti.slidetoact.SlideToActView
-import com.ncorti.slidetoact.SlideToActView.OnSlideCompleteListener
-import net.mynero.wallet.MoneroApplication
-import net.mynero.wallet.R
-import net.mynero.wallet.model.PendingTransaction
-import net.mynero.wallet.model.Wallet
-import net.mynero.wallet.service.BalanceService
-import net.mynero.wallet.service.TxService
-import net.mynero.wallet.util.Constants
-import net.mynero.wallet.util.Helper
-import net.mynero.wallet.util.UriData
-
-class SendFragment : Fragment() {
- var priority: PendingTransaction.Priority = PendingTransaction.Priority.Priority_Low
- private var mViewModel: SendViewModel? = null
- private var sendMaxButton: Button? = null
- private var addOutputImageView: ImageButton? = null
- private var destList: LinearLayout? = null
- private var inflater: LayoutInflater? = null
- private var createButton: Button? = null
- private var sendTxSlider: SlideToActView? = null
- private var feeRadioGroup: RadioGroup? = null
- private var feeRadioGroupLabelTextView: TextView? = null
- private var feeTextView: TextView? = null
- private var addressTextView: TextView? = null
- private var amountTextView: TextView? = null
- private var currentEntryIndex = -1
- private val qrCodeLauncher =
- registerForActivityResult(ScanContract()) { result: ScanIntentResult ->
- if (result.contents != null) {
- if (currentEntryIndex != -1) {
- pasteAddress(getDestView(currentEntryIndex), result.contents, false)
- currentEntryIndex = -1
- }
- }
- }
- private val cameraPermissionsLauncher = registerForActivityResult(
- ActivityResultContracts.RequestPermission()
- ) { granted: Boolean ->
- if (granted) {
- onScan(currentEntryIndex)
- } else {
- Toast.makeText(activity, getString(R.string.no_camera_permission), Toast.LENGTH_SHORT)
- .show()
- currentEntryIndex = -1
- }
- }
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.fragment_send, container, false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- mViewModel = ViewModelProvider(this)[SendViewModel::class.java]
- sendMaxButton = view.findViewById(R.id.send_max_button)
- addOutputImageView = view.findViewById(R.id.add_output_button)
- destList = view.findViewById(R.id.transaction_destination_list)
- createButton = view.findViewById(R.id.create_tx_button)
- feeRadioGroup = view.findViewById(R.id.tx_fee_radiogroup)
- feeTextView = view.findViewById(R.id.fee_textview)
- sendTxSlider = view.findViewById(R.id.send_tx_slider)
- addressTextView = view.findViewById(R.id.address_pending_textview)
- amountTextView = view.findViewById(R.id.amount_pending_textview)
- feeRadioGroup = view.findViewById(R.id.tx_fee_radiogroup)
- feeRadioGroupLabelTextView = view.findViewById(R.id.tx_fee_radiogroup_label_textview)
- inflater = activity?.layoutInflater
- bindListeners()
- bindObservers()
- init()
- }
-
- private fun init() {
- addOutput(true)
- }
-
- private fun bindListeners() {
- feeRadioGroup?.check(R.id.low_fee_radiobutton)
- priority = PendingTransaction.Priority.Priority_Low
- feeRadioGroup?.setOnCheckedChangeListener { _: RadioGroup?, i: Int ->
- when (i) {
- R.id.low_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_Low
- R.id.med_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_Medium
- R.id.high_fee_radiobutton -> priority = PendingTransaction.Priority.Priority_High
- }
- }
- addOutputImageView?.setOnClickListener {
- sendMaxButton?.visibility = View.GONE
- val outputCount = destCount
- if (outputCount < 8) {
- addOutput(false)
- } else {
- Toast.makeText(
- activity,
- getString(R.string.max_outputs_allowed),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
- sendMaxButton?.setOnClickListener { mViewModel?.setSendingMax(!isSendAll) }
- createButton?.setOnClickListener {
- val outputsValid = checkDestsValidity(isSendAll)
- if (outputsValid) {
- Toast.makeText(activity, getString(R.string.creating_tx), Toast.LENGTH_SHORT).show()
- createButton?.isEnabled = false
- sendMaxButton?.isEnabled = false
- createTx(rawDests, isSendAll, priority)
- } else {
- Toast.makeText(
- activity,
- getString(R.string.creating_tx_failed_invalid_outputs),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
- sendTxSlider?.onSlideCompleteListener =
- object : OnSlideCompleteListener {
- override fun onSlideComplete(view: SlideToActView) {
- confirmSlider()
- }
- }
-
- sendTxSlider?.let { slideToActView ->
- ViewCompat.addAccessibilityAction(
- slideToActView,
- getString(R.string.approve_the_transaction)
- ) { _, _ ->
- confirmSlider()
- return@addAccessibilityAction true
- }
- }
- }
-
- private fun confirmSlider() {
- val pendingTx = mViewModel?.pendingTransaction?.value ?: return
- Toast.makeText(activity, getString(R.string.sending_tx), Toast.LENGTH_SHORT)
- .show()
- sendTx(pendingTx)
- }
-
- private fun checkDestsValidity(sendAll: Boolean): Boolean {
- val dests = rawDests
- for (dest in dests) {
- val address = dest.component1()
- val amount = dest.component2()
- if (!sendAll) {
- if (amount.isEmpty()) {
- Toast.makeText(
- activity,
- getString(R.string.send_amount_empty),
- Toast.LENGTH_SHORT
- ).show()
- return false
- }
- val amountRaw = Wallet.getAmountFromString(amount)
- val balance = BalanceService.instance?.unlockedBalanceRaw ?: 0
- if (amountRaw >= balance || amountRaw <= 0) {
- Toast.makeText(
- activity,
- getString(R.string.send_amount_invalid),
- Toast.LENGTH_SHORT
- ).show()
- return false
- }
- } else if (dests.size > 1) {
- Toast.makeText(
- activity,
- getString(R.string.send_amount_invalid_sendall_paytomany),
- Toast.LENGTH_SHORT
- ).show()
- return false
- }
- val uriData = UriData.parse(address)
- val isValidAddress = uriData != null
- if (!isValidAddress) {
- Toast.makeText(
- activity,
- getString(R.string.send_address_invalid),
- Toast.LENGTH_SHORT
- ).show()
- return false
- }
- if (dests.size > 1 && uriData?.hasPaymentId() == true) {
- Toast.makeText(
- activity,
- getString(R.string.paymentid_paytomany),
- Toast.LENGTH_SHORT
- ).show()
- return false
- }
- }
- return true
- }
-
- private fun destsHasPaymentId(): Boolean {
- val dests = rawDests
- for (dest in dests) {
- val address = dest.component1()
- val uriData = UriData.parse(address) ?: return false
- if (uriData.hasPaymentId()) return true
- }
- return false
- }
-
- private fun bindObservers() {
- mViewModel?.sendingMax?.observe(viewLifecycleOwner) { sendingMax: Boolean? ->
- if (mViewModel?.pendingTransaction?.value == null) {
- if (sendingMax == true) {
- prepareOutputsForMaxSend()
- sendMaxButton?.text = getText(R.string.undo)
- } else {
- unprepareMaxSend()
- sendMaxButton?.text = getText(R.string.send_max)
- }
- }
- }
- mViewModel?.showAddOutputButton?.observe(viewLifecycleOwner) { show: Boolean? ->
- setAddOutputButtonVisibility(
- if (show == true && !destsHasPaymentId()) View.VISIBLE else View.INVISIBLE
- )
- }
- mViewModel?.pendingTransaction?.observe(viewLifecycleOwner) { pendingTx: PendingTransaction? ->
- showConfirmationLayout(pendingTx != null)
- if (pendingTx != null) {
- val address = if (destCount == 1) getAddressField(0).text.toString() else "Multiple"
- addressTextView?.text = getString(R.string.tx_address_text, address)
- amountTextView?.text =
- getString(
- R.string.tx_amount_text,
- Helper.getDisplayAmount(pendingTx.getAmount())
- )
- feeTextView?.text =
- getString(R.string.tx_fee_text, Helper.getDisplayAmount(pendingTx.getFee()))
- }
- }
- }
-
- private fun addOutput(initial: Boolean) {
- if (inflater != null) {
- val index = destCount
- val entryView =
- inflater?.inflate(R.layout.transaction_output_item, null) as ConstraintLayout
- val removeOutputImageButton =
- entryView.findViewById(R.id.remove_output_imagebutton)
- val addressField = entryView.findViewById(R.id.address_edittext)
- val donateTextView = entryView.findViewById(R.id.donate_label)
- val donatingTextView = entryView.findViewById(R.id.donating_label)
- donateTextView.setOnClickListener {
- addressField.setText(
- Constants.DONATE_ADDRESS
- )
- }
- donatingTextView.setOnClickListener {
- addressField.setText("")
- }
- addressField.addTextChangedListener(object : TextWatcher {
- override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
- override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
- override fun afterTextChanged(s: Editable) {
- val currentOutputs: Int = destCount
- val uriData = UriData.parse(s.toString())
- if (uriData != null) {
- // we have valid address
- val hasPaymentId = uriData.hasPaymentId()
- if (currentOutputs > 1 && hasPaymentId) {
- // multiple outputs when pasting/editing in integrated address. this is not allowed
- Toast.makeText(
- activity,
- getString(R.string.paymentid_paytomany),
- Toast.LENGTH_SHORT
- ).show()
- addressField.text = null
- } else if (currentOutputs == 1 && hasPaymentId) {
- // show add output button: we are sending to integrated address
- mViewModel?.setShowAddOutputButton(false)
- }
- } else if (currentOutputs == 1 && !isSendAll) {
- // when send-all is false and this is our only dest and address is invalid, then show add output button
- mViewModel?.setShowAddOutputButton(true)
- }
-
- if (s.toString() == Constants.DONATE_ADDRESS) {
- donateTextView.visibility = View.INVISIBLE
- donatingTextView.visibility = View.VISIBLE
- } else {
- donateTextView.visibility = View.VISIBLE
- donatingTextView.visibility = View.INVISIBLE
- }
- }
- })
- entryView.findViewById(R.id.paste_amount_imagebutton)
- .setOnClickListener {
- val ctx = context
- if (ctx != null) {
- val clipboard = Helper.getClipBoardText(ctx)
- if (clipboard != null) {
- pasteAddress(entryView, clipboard, true)
- }
- }
- }
- entryView.findViewById(R.id.paste_address_imagebutton)
- .setOnClickListener {
- val ctx = context
- if (ctx != null) {
- val clipboard = Helper.getClipBoardText(ctx)
- if (clipboard != null) {
- pasteAddress(entryView, clipboard, false)
- }
- }
- }
- entryView.findViewById(R.id.scan_address_imagebutton)
- .setOnClickListener { onScan(index) }
- if (initial) {
- removeOutputImageButton.visibility = View.INVISIBLE
- } else {
- removeOutputImageButton.setOnClickListener {
- val currentCount = destCount
- if (currentCount > 1) {
- if (currentCount == 2) {
- sendMaxButton?.visibility = View.VISIBLE
- }
- destList?.removeView(entryView)
- }
- }
- }
- destList?.addView(entryView)
- }
- }
-
- private val destCount: Int
- get() = destList?.childCount ?: -1
- private val rawDests: List>
- get() {
- val dests = ArrayList>()
- for (i in 0 until destCount) {
- val entryView = getDestView(i)
- val amountField = entryView.findViewById(R.id.amount_edittext)
- val addressField = entryView.findViewById(R.id.address_edittext)
- val amount = amountField.text.toString().trim { it <= ' ' }
- val address = addressField.text.toString().trim { it <= ' ' }
- dests.add(Pair(address, amount))
- }
- return dests
- }
- private val isSendAll: Boolean
- get() = mViewModel?.sendingMax?.value ?: false
-
- private fun getDestView(pos: Int): ConstraintLayout {
- return destList?.getChildAt(pos) as ConstraintLayout
- }
-
- private fun getAddressField(pos: Int): EditText {
- return getDestView(pos).findViewById(R.id.address_edittext) as EditText
- }
-
- private fun unprepareMaxSend() {
- val entryView = getDestView(0)
- entryView.findViewById(R.id.sending_all_textview).visibility = View.INVISIBLE
- entryView.findViewById(R.id.amount_edittext).visibility =
- View.VISIBLE
- }
-
- private fun prepareOutputsForMaxSend() {
- val entryView = getDestView(0)
- entryView.findViewById(R.id.sending_all_textview).visibility = View.VISIBLE
- entryView.findViewById(R.id.amount_edittext).visibility =
- View.INVISIBLE
- }
-
- private fun showConfirmationLayout(show: Boolean) {
- if (show) {
- destList?.visibility = View.GONE
- setAddOutputButtonVisibility(View.GONE)
- sendMaxButton?.visibility = View.GONE
- createButton?.visibility = View.GONE
- feeRadioGroup?.visibility = View.GONE
- feeRadioGroupLabelTextView?.visibility = View.GONE
- sendTxSlider?.visibility = View.VISIBLE
- feeTextView?.visibility = View.VISIBLE
- addressTextView?.visibility = View.VISIBLE
- amountTextView?.visibility = View.VISIBLE
- } else {
- destList?.visibility = View.VISIBLE
- setAddOutputButtonVisibility(View.VISIBLE)
- sendMaxButton?.visibility = View.VISIBLE
- createButton?.visibility = View.VISIBLE
- feeRadioGroup?.visibility = View.VISIBLE
- feeRadioGroupLabelTextView?.visibility = View.VISIBLE
- sendTxSlider?.visibility = View.GONE
- feeTextView?.visibility = View.GONE
- addressTextView?.visibility = View.GONE
- amountTextView?.visibility = View.GONE
- }
- }
-
- private fun onScan(index: Int) {
- currentEntryIndex = index
- if (activity?.let { Helper.getCameraPermission(it, cameraPermissionsLauncher) } == true) {
- val options = ScanOptions()
- options.setBeepEnabled(false)
- options.setOrientationLocked(true)
- options.setDesiredBarcodeFormats(listOf(Intents.Scan.QR_CODE_MODE))
- options.addExtra(Intents.Scan.SCAN_TYPE, Intents.Scan.MIXED_SCAN)
- qrCodeLauncher.launch(options)
- }
- }
-
- private fun pasteAddress(
- entryView: ConstraintLayout,
- clipboard: String,
- pastingAmount: Boolean
- ) {
- if (pastingAmount) {
- try {
- clipboard.toDouble()
- setAmount(entryView, clipboard)
- } catch (e: Exception) {
- Toast.makeText(
- activity,
- getString(R.string.send_amount_invalid),
- Toast.LENGTH_SHORT
- ).show()
- return
- }
- }
- val uriData = UriData.parse(clipboard)
- if (uriData != null) {
- val currentOutputs = destCount
- if (currentOutputs > 1 && uriData.hasPaymentId()) {
- Toast.makeText(
- activity,
- getString(R.string.paymentid_paytomany),
- Toast.LENGTH_SHORT
- ).show()
- return
- } else if (currentOutputs == 1 && uriData.hasPaymentId()) {
- mViewModel?.setShowAddOutputButton(false)
- }
- val addressField = entryView.findViewById(R.id.address_edittext)
- addressField.setText(uriData.address)
- if (uriData.hasAmount()) {
- setAmount(entryView, uriData.amount)
- }
- } else {
- Toast.makeText(activity, getString(R.string.send_address_invalid), Toast.LENGTH_SHORT)
- .show()
- }
- }
-
- private fun setAmount(entryView: ConstraintLayout, amount: String?) {
- sendMaxButton?.isEnabled = false
- val amountField = entryView.findViewById(R.id.amount_edittext)
- amountField.setText(amount)
- }
-
- private fun createTx(
- dests: List>,
- sendAll: Boolean,
- feePriority: PendingTransaction.Priority
- ) {
- (activity?.application as MoneroApplication).executor?.execute {
- try {
- val pendingTx =
- TxService.instance?.createTx(dests, sendAll, feePriority, ArrayList())
- if (pendingTx != null && pendingTx.status === PendingTransaction.Status.Status_Ok) {
- mViewModel?.setPendingTransaction(pendingTx)
- } else {
- val activity: Activity? = activity
- if (activity != null && pendingTx != null) {
- activity.runOnUiThread(Runnable {
- createButton?.isEnabled = true
- sendMaxButton?.isEnabled = true
- if (pendingTx.getErrorString() != null) Toast.makeText(
- activity,
- getString(R.string.error_creating_tx, pendingTx.getErrorString()),
- Toast.LENGTH_SHORT
- ).show()
- })
- }
- }
- } catch (e: Exception) {
- e.printStackTrace()
- val activity: Activity? = activity
- activity?.runOnUiThread {
- createButton?.isEnabled = true
- sendMaxButton?.isEnabled = true
- Toast.makeText(
- activity,
- getString(R.string.error_creating_tx, e.message),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
- }
- }
-
- private fun sendTx(pendingTx: PendingTransaction) {
- (activity?.application as MoneroApplication).executor?.execute {
- val success = TxService.instance?.sendTx(pendingTx)
- val activity: Activity? = activity
- activity?.runOnUiThread {
- if (success == true) {
- Toast.makeText(getActivity(), getString(R.string.sent_tx), Toast.LENGTH_SHORT)
- .show()
- activity.onBackPressed()
- } else {
- sendTxSlider?.resetSlider()
- Toast.makeText(
- getActivity(),
- getString(R.string.error_sending_tx),
- Toast.LENGTH_SHORT
- )
- .show()
- }
- }
- }
- }
-
- private fun setAddOutputButtonVisibility(visibility: Int) {
- addOutputImageView?.visibility = visibility
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/send/SendViewModel.kt b/app/src/main/java/net/mynero/wallet/fragment/send/SendViewModel.kt
deleted file mode 100644
index 16777bb..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/send/SendViewModel.kt
+++ /dev/null
@@ -1,27 +0,0 @@
-package net.mynero.wallet.fragment.send
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import net.mynero.wallet.model.PendingTransaction
-
-class SendViewModel : ViewModel() {
- private val _sendingMax = MutableLiveData(false)
- private val _showAddOutputButton = MutableLiveData(true)
- private val _pendingTransaction = MutableLiveData(null)
- var sendingMax: LiveData = _sendingMax
- var showAddOutputButton: LiveData = _showAddOutputButton
- var pendingTransaction: LiveData = _pendingTransaction
- fun setSendingMax(value: Boolean) {
- _sendingMax.value = value
- setShowAddOutputButton(!value)
- }
-
- fun setShowAddOutputButton(value: Boolean) {
- _showAddOutputButton.value = value
- }
-
- fun setPendingTransaction(pendingTx: PendingTransaction?) {
- _pendingTransaction.postValue(pendingTx)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsFragment.kt b/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsFragment.kt
deleted file mode 100644
index d5be296..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsFragment.kt
+++ /dev/null
@@ -1,324 +0,0 @@
-package net.mynero.wallet.fragment.settings
-
-import android.os.Bundle
-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.ImageView
-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.navigation.fragment.NavHostFragment
-import androidx.navigation.fragment.findNavController
-import com.google.android.material.progressindicator.CircularProgressIndicator
-import net.mynero.wallet.R
-import net.mynero.wallet.data.Node
-import net.mynero.wallet.data.Node.Companion.fromJson
-import net.mynero.wallet.fragment.dialog.AddNodeBottomSheetDialog
-import net.mynero.wallet.fragment.dialog.AddNodeBottomSheetDialog.AddNodeListener
-import net.mynero.wallet.fragment.dialog.EditNodeBottomSheetDialog
-import net.mynero.wallet.fragment.dialog.EditNodeBottomSheetDialog.EditNodeListener
-import net.mynero.wallet.fragment.dialog.NodeSelectionBottomSheetDialog
-import net.mynero.wallet.fragment.dialog.NodeSelectionBottomSheetDialog.NodeSelectionDialogListener
-import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog
-import net.mynero.wallet.fragment.dialog.PasswordBottomSheetDialog.PasswordListener
-import net.mynero.wallet.fragment.dialog.WalletKeysBottomSheetDialog
-import net.mynero.wallet.model.EnumTorState
-import net.mynero.wallet.model.WalletManager
-import net.mynero.wallet.service.BalanceService
-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
-
-class SettingsFragment : Fragment(), PasswordListener, NodeSelectionDialogListener, AddNodeListener,
- EditNodeListener {
- private var mViewModel: SettingsViewModel? = null
- private var walletProxyAddressEditText: EditText? = null
- private var walletProxyPortEditText: EditText? = null
- private var selectNodeButton: Button? = null
- private var streetModeSwitch: SwitchCompat? = null
- private var monerochanSwitch: SwitchCompat? = null
- private var useBundledTor: CheckBox? = null
- private var displaySeedButton: Button? = null
- private var displayUtxosButton: Button? = null
- private var torSwitch: SwitchCompat? = null
-
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.fragment_settings, container, false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- mViewModel = ViewModelProvider(this)[SettingsViewModel::class.java]
- displaySeedButton = view.findViewById(R.id.display_seed_button)
- displayUtxosButton = view.findViewById(R.id.display_utxos_button)
- selectNodeButton = view.findViewById(R.id.select_node_button)
- streetModeSwitch = view.findViewById(R.id.street_mode_switch)
- monerochanSwitch = view.findViewById(R.id.monerochan_switch)
- torSwitch = view.findViewById(R.id.tor_switch)
- val proxySettingsLayout = view.findViewById(R.id.wallet_proxy_settings_layout)
- walletProxyAddressEditText = view.findViewById(R.id.wallet_proxy_address_edittext)
- walletProxyPortEditText = view.findViewById(R.id.wallet_proxy_port_edittext)
- useBundledTor = view.findViewById(R.id.bundled_tor_checkbox)
-
- val cachedProxyAddress = ProxyService.instance?.proxyAddress ?: return
- val cachedProxyPort = ProxyService.instance?.proxyPort ?: return
- val cachedUsingProxy = ProxyService.instance?.usingProxy == true
- val cachedUsingBundledTor = ProxyService.instance?.useBundledTor == true
-
- walletProxyPortEditText?.isEnabled = !cachedUsingBundledTor && cachedUsingProxy
- walletProxyAddressEditText?.isEnabled = !cachedUsingBundledTor && cachedUsingProxy
- proxySettingsLayout.visibility = View.VISIBLE
-
- streetModeSwitch?.isChecked =
- PrefService.instance?.getBoolean(Constants.PREF_STREET_MODE, false) == true
- monerochanSwitch?.isChecked =
- PrefService.instance?.getBoolean(Constants.PREF_MONEROCHAN, Constants.DEFAULT_PREF_MONEROCHAN) == true
- useBundledTor?.isChecked = cachedUsingBundledTor
- torSwitch?.isChecked = cachedUsingProxy
- updateProxy(cachedProxyAddress, cachedProxyPort)
-
- val node = PrefService.instance?.node // shouldn't use default value here
- selectNodeButton?.text = getString(R.string.node_button_text, node?.address)
-
- bindListeners()
- bindObservers()
- }
-
- private fun bindListeners() {
- val onBackPressedCallback: OnBackPressedCallback = object : OnBackPressedCallback(true) {
- override fun handleOnBackPressed() {
- refreshProxy()
- findNavController().popBackStack()
- }
- }
- activity?.onBackPressedDispatcher?.addCallback(viewLifecycleOwner, onBackPressedCallback)
-
- streetModeSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
- PrefService.instance?.edit()?.putBoolean(Constants.PREF_STREET_MODE, b)?.apply()
- BalanceService.instance?.refreshBalance()
- }
-
- monerochanSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
- PrefService.instance?.edit()?.putBoolean(Constants.PREF_MONEROCHAN, b)?.apply()
- HistoryService.instance?.refreshHistory()
- }
-
- selectNodeButton?.setOnClickListener {
- activity?.supportFragmentManager?.let { fragmentManager ->
- val dialog = NodeSelectionBottomSheetDialog()
- dialog.listener = this
- dialog.show(fragmentManager, "node_selection_dialog")
- }
- }
-
- useBundledTor?.setOnCheckedChangeListener { _, isChecked ->
- mViewModel?.setUseBundledTor(isChecked)
- }
-
- displaySeedButton?.setOnClickListener {
- val usesPassword =
- PrefService.instance?.getBoolean(Constants.PREF_USES_PASSWORD, false) == true
- if (usesPassword) {
- activity?.supportFragmentManager?.let { fragmentManager ->
- val passwordDialog = PasswordBottomSheetDialog()
- passwordDialog.canCancel = true
- passwordDialog.listener = this
- passwordDialog.show(fragmentManager, "password_dialog")
- }
- } else {
- displaySeedDialog("")
- }
- }
-
- displayUtxosButton?.setOnClickListener { navigate(R.id.nav_to_utxos) }
-
- torSwitch?.setOnCheckedChangeListener { _: CompoundButton?, b: Boolean ->
- mViewModel?.setUseProxy(b)
- }
- }
-
- private fun bindObservers() {
- mViewModel?.useProxy?.observe(viewLifecycleOwner) { useProxy ->
- useBundledTor?.isEnabled = useProxy
- walletProxyPortEditText?.isEnabled = useProxy && mViewModel?.useBundledTor?.value == false
- walletProxyAddressEditText?.isEnabled = useProxy && mViewModel?.useBundledTor?.value == false
-
- refreshProxy()
- }
-
- mViewModel?.useBundledTor?.observe(viewLifecycleOwner) { isChecked ->
- walletProxyPortEditText?.isEnabled = !isChecked && mViewModel?.useProxy?.value == true
- walletProxyAddressEditText?.isEnabled = !isChecked && mViewModel?.useProxy?.value == true
- }
-
- val samouraiTorManager = ProxyService.instance?.samouraiTorManager
- val indicatorCircle =
- view?.findViewById(R.id.settings_tor_loading_progressindicator)
- val torIcon = view?.findViewById(R.id.settings_tor_icon)
-
- samouraiTorManager?.getTorStateLiveData()?.observe(viewLifecycleOwner) { state ->
- samouraiTorManager.getProxy()?.address()?.let { socketAddress ->
- if (socketAddress.toString().isEmpty()) return@let
- if (mViewModel?.useProxy?.value == true && mViewModel?.useBundledTor?.value == true) {
- torIcon?.visibility = View.VISIBLE
- indicatorCircle?.visibility = View.INVISIBLE
- val proxyString = socketAddress.toString().substring(1)
- val address = proxyString.split(":")[0]
- val port = proxyString.split(":")[1]
- updateProxy(address, port)
- }
- }
-
- indicatorCircle?.isIndeterminate = state.progressIndicator == 0
- indicatorCircle?.progress = state.progressIndicator
-
- when (state.state) {
- EnumTorState.OFF -> {
- torIcon?.visibility = View.INVISIBLE
- indicatorCircle?.visibility = View.INVISIBLE
- }
-
- EnumTorState.STARTING, EnumTorState.STOPPING -> {
- torIcon?.visibility = View.INVISIBLE
- indicatorCircle?.visibility = View.VISIBLE
- }
-
- else -> {}
- }
- }
- }
-
- private fun updateProxy(address: String, port: String) {
- walletProxyPortEditText?.setText(port)
- walletProxyAddressEditText?.setText(address)
- refreshProxy()
- }
-
- private fun refreshProxy() {
- val proxyAddress = walletProxyAddressEditText?.text.toString()
- val proxyPort = walletProxyPortEditText?.text.toString()
- val savedProxyAddress = ProxyService.instance?.proxyAddress
- val savedProxyPort = ProxyService.instance?.proxyPort
- val currentWalletProxy = WalletManager.instance?.proxy
- val newProxy = "$proxyAddress:$proxyPort"
- if (proxyAddress != savedProxyAddress || proxyPort != savedProxyPort || (newProxy != currentWalletProxy && newProxy != ":"))
- ProxyService.instance?.updateProxy(proxyAddress, proxyPort)
- }
-
- private fun displaySeedDialog(password: String) {
- activity?.supportFragmentManager?.let { fragmentManager ->
- val informationDialog = WalletKeysBottomSheetDialog()
- informationDialog.password = password
- informationDialog.show(fragmentManager, "information_seed_dialog")
- }
- }
-
- override fun onPasswordSuccess(password: String) {
- displaySeedDialog(password)
- }
-
- override fun onPasswordFail() {
- Toast.makeText(context, R.string.bad_password, Toast.LENGTH_SHORT).show()
- }
-
- override fun onNodeSelected() {
- val node = PrefService.instance?.node
- selectNodeButton?.text = getString(R.string.node_button_text, node?.address)
- refreshProxy()
-
- activity?.runOnUiThread {
- Toast.makeText(
- activity,
- activity?.getString(R.string.node_selected, node?.name ?: node?.host),
- Toast.LENGTH_SHORT
- ).show()
- }
- }
-
- override fun onClickedAddNode() {
- activity?.supportFragmentManager?.let { fragmentManager ->
- val addNodeDialog = AddNodeBottomSheetDialog()
- addNodeDialog.listener = this
- addNodeDialog.show(fragmentManager, "add_node_dialog")
- }
- }
-
- override fun onClickedEditNode(node: Node?) {
- activity?.supportFragmentManager?.let { fragmentManager ->
- val editNodeDialog = EditNodeBottomSheetDialog()
- editNodeDialog.listener = this
- editNodeDialog.node = node
- editNodeDialog.show(fragmentManager, "edit_node_dialog")
- }
-
- }
-
- override fun onNodeAdded() {
- activity?.supportFragmentManager?.let { fragmentManager ->
- val dialog = NodeSelectionBottomSheetDialog()
- dialog.listener = this
- dialog.show(fragmentManager, "node_selection_dialog")
- }
- }
-
- private fun navigate(destination: Int) {
- val activity = activity
- if (activity != null) {
- val fm = activity.supportFragmentManager
- val navHostFragment = fm.findFragmentById(R.id.nav_host_fragment) as NavHostFragment?
- navHostFragment?.navController?.navigate(destination)
- }
- }
-
- override fun onNodeDeleted(node: Node?) {
- try {
- val nodesArray = PrefService.instance?.getString(Constants.PREF_CUSTOM_NODES, "[]")
- val jsonArray = JSONArray(nodesArray)
- for (i in 0 until jsonArray.length()) {
- val nodeJsonObject = jsonArray.getJSONObject(i)
- val savedNode = fromJson(nodeJsonObject)
- if (savedNode?.toNodeString() == node?.toNodeString()) jsonArray.remove(i)
- }
- saveNodesAndReopen(jsonArray)
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
-
- override fun onNodeEdited(oldNode: Node?, newNode: Node?) {
- try {
- val nodesArray = PrefService.instance?.getString(Constants.PREF_CUSTOM_NODES, "[]")
- val jsonArray = JSONArray(nodesArray)
- for (i in 0 until jsonArray.length()) {
- val nodeJsonObject = jsonArray.getJSONObject(i)
- val savedNode = fromJson(nodeJsonObject)
- if (savedNode?.toNodeString() == oldNode?.toNodeString()) jsonArray.put(
- i,
- newNode?.toJson()
- )
- }
- saveNodesAndReopen(jsonArray)
- } catch (e: Exception) {
- e.printStackTrace()
- }
- }
-
- private fun saveNodesAndReopen(jsonArray: JSONArray) {
- PrefService.instance?.edit()?.putString(Constants.PREF_CUSTOM_NODES, jsonArray.toString())
- ?.apply()
- onNodeAdded()
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsViewModel.kt b/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsViewModel.kt
deleted file mode 100644
index 9dc05f6..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/settings/SettingsViewModel.kt
+++ /dev/null
@@ -1,44 +0,0 @@
-package net.mynero.wallet.fragment.settings
-
-import androidx.lifecycle.LiveData
-import androidx.lifecycle.MutableLiveData
-import androidx.lifecycle.ViewModel
-import net.mynero.wallet.service.PrefService
-import net.mynero.wallet.service.ProxyService
-import net.mynero.wallet.util.Constants
-
-class SettingsViewModel : ViewModel() {
- private val _useProxy = MutableLiveData(false)
- val useProxy: LiveData = _useProxy
- private val _useBundledTor = MutableLiveData(false)
- val useBundledTor: LiveData = _useBundledTor
-
- init {
- _useProxy.value = ProxyService.instance?.usingProxy
- _useBundledTor.value = ProxyService.instance?.useBundledTor
- }
-
- fun setUseProxy(use: Boolean) {
- _useProxy.value = use
- ProxyService.instance?.usingProxy = use
-
- val samouraiTorManager = ProxyService.instance?.samouraiTorManager
- if (use && ProxyService.instance?.useBundledTor == true) {
- samouraiTorManager?.start()
- } else {
- samouraiTorManager?.stop()
- }
- }
-
- fun setUseBundledTor(use: Boolean) {
- _useBundledTor.value = use
- ProxyService.instance?.useBundledTor = use
-
- val samouraiTorManager = ProxyService.instance?.samouraiTorManager
- if (use && ProxyService.instance?.usingProxy == true) {
- samouraiTorManager?.start()
- } else {
- samouraiTorManager?.stop()
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionViewModel.kt b/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionViewModel.kt
deleted file mode 100644
index 7647a7f..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/transaction/TransactionViewModel.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package net.mynero.wallet.fragment.transaction
-
-import androidx.lifecycle.ViewModel
-
-class TransactionViewModel : ViewModel()
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/utxos/UtxosFragment.kt b/app/src/main/java/net/mynero/wallet/fragment/utxos/UtxosFragment.kt
deleted file mode 100644
index 5ab3119..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/utxos/UtxosFragment.kt
+++ /dev/null
@@ -1,166 +0,0 @@
-package net.mynero.wallet.fragment.utxos
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import android.widget.Button
-import android.widget.Toast
-import androidx.fragment.app.Fragment
-import androidx.lifecycle.ViewModelProvider
-import androidx.recyclerview.widget.LinearLayoutManager
-import androidx.recyclerview.widget.RecyclerView
-import net.mynero.wallet.R
-import net.mynero.wallet.adapter.CoinsInfoAdapter
-import net.mynero.wallet.adapter.CoinsInfoAdapter.CoinsInfoAdapterListener
-import net.mynero.wallet.fragment.dialog.SendBottomSheetDialog
-import net.mynero.wallet.model.CoinsInfo
-import net.mynero.wallet.service.AddressService
-import net.mynero.wallet.service.UTXOService
-import net.mynero.wallet.util.MoneroThreadPoolExecutor
-import net.mynero.wallet.util.UriData
-
-class UtxosFragment : Fragment(), CoinsInfoAdapterListener, SendBottomSheetDialog.Listener {
- private val adapter = CoinsInfoAdapter(this)
- private var mViewModel: UtxosViewModel? = null
- private var sendUtxosButton: Button? = null
- private var churnUtxosButton: Button? = null
- private var freezeUtxosButton: Button? = null
- override fun onCreateView(
- inflater: LayoutInflater, container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View? {
- return inflater.inflate(R.layout.fragment_utxos, container, false)
- }
-
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- super.onViewCreated(view, savedInstanceState)
- mViewModel = ViewModelProvider(this)[UtxosViewModel::class.java]
- bindListeners(view)
- bindObservers(view)
- }
-
- private fun bindListeners(view: View) {
- freezeUtxosButton = view.findViewById(R.id.freeze_utxos_button)
- sendUtxosButton = view.findViewById(R.id.send_utxos_button)
- churnUtxosButton = view.findViewById(R.id.churn_utxos_button)
- sendUtxosButton?.visibility = View.GONE
- churnUtxosButton?.visibility = View.GONE
- freezeUtxosButton?.visibility = View.GONE
- freezeUtxosButton?.setOnClickListener {
- Toast.makeText(context, "Toggling freeze status, please wait.", Toast.LENGTH_SHORT)
- .show()
- MoneroThreadPoolExecutor.MONERO_THREAD_POOL_EXECUTOR?.execute {
- UTXOService.instance?.toggleFrozen(adapter.selectedUtxos)
- activity?.runOnUiThread {
- adapter.clear()
- sendUtxosButton?.visibility = View.GONE
- churnUtxosButton?.visibility = View.GONE
- freezeUtxosButton?.visibility = View.GONE
- }
- }
- }
- sendUtxosButton?.setOnClickListener {
- val selectedKeyImages = ArrayList()
- for (coinsInfo in adapter.selectedUtxos.values) {
- coinsInfo.keyImage?.let { keyImage -> selectedKeyImages.add(keyImage) }
- }
- activity?.supportFragmentManager?.let { fragmentManager ->
- val sendDialog = SendBottomSheetDialog()
- sendDialog.listener = this
- sendDialog.selectedUtxos = selectedKeyImages
- sendDialog.show(fragmentManager, null)
- }
-
- }
- churnUtxosButton?.setOnClickListener {
- val selectedKeyImages = ArrayList()
- for (coinsInfo in adapter.selectedUtxos.values) {
- coinsInfo.keyImage?.let { keyImage -> selectedKeyImages.add(keyImage) }
- }
- activity?.supportFragmentManager?.let { fragmentManager ->
- val sendDialog = SendBottomSheetDialog()
- sendDialog.listener = this
- sendDialog.isChurning = true
- sendDialog.uriData =
- AddressService.instance?.currentSubaddress()?.address?.let { address ->
- UriData.parse(address)
- }
- sendDialog.selectedUtxos = selectedKeyImages
- sendDialog.show(fragmentManager, null)
- }
- }
- }
-
- private fun bindObservers(view: View) {
- val utxosRecyclerView =
- view.findViewById(R.id.transaction_history_recyclerview)
- val utxoService = UTXOService.instance
- utxosRecyclerView.layoutManager = LinearLayoutManager(activity)
- utxosRecyclerView.adapter = adapter
- utxoService?.utxos?.observe(viewLifecycleOwner) { utxos: List ->
- val filteredUtxos = HashMap()
- for (coinsInfo in utxos) {
- if (!coinsInfo.isSpent) {
- filteredUtxos[coinsInfo.pubKey] = coinsInfo
- }
- }
- if (filteredUtxos.isEmpty()) {
- utxosRecyclerView.visibility = View.GONE
- } else {
- adapter.submitList(filteredUtxos)
- utxosRecyclerView.visibility = View.VISIBLE
- }
- }
- }
-
- override fun onUtxoSelected(coinsInfo: CoinsInfo) {
- val selected = adapter.contains(coinsInfo)
- if (selected) {
- adapter.deselectUtxo(coinsInfo)
- } else {
- adapter.selectUtxo(coinsInfo)
- }
- var frozenExists = false
- var unfrozenExists = false
- for (selectedUtxo in adapter.selectedUtxos.values) {
- if (selectedUtxo.isFrozen || UTXOService.instance?.isCoinFrozen(selectedUtxo) == true)
- frozenExists = true
- else {
- unfrozenExists = true
- }
- }
- val bothExist: Boolean = frozenExists && unfrozenExists
- if (adapter.selectedUtxos.isEmpty()) {
- sendUtxosButton?.visibility = View.GONE
- churnUtxosButton?.visibility = View.GONE
- freezeUtxosButton?.visibility = View.GONE
- freezeUtxosButton?.setBackgroundResource(R.drawable.button_bg_left)
- } else {
- if (frozenExists) {
- freezeUtxosButton?.setBackgroundResource(R.drawable.button_bg)
- sendUtxosButton?.visibility = View.GONE
- churnUtxosButton?.visibility = View.GONE
- } else {
- freezeUtxosButton?.setBackgroundResource(R.drawable.button_bg_left)
- sendUtxosButton?.visibility = View.VISIBLE
- churnUtxosButton?.visibility = View.VISIBLE
- }
- freezeUtxosButton?.visibility = View.VISIBLE
- }
- if (bothExist) {
- freezeUtxosButton?.setText(R.string.toggle_freeze)
- } else if (frozenExists) {
- freezeUtxosButton?.setText(R.string.unfreeze)
- } else if (unfrozenExists) {
- freezeUtxosButton?.setText(R.string.freeze)
- }
- }
-
- override fun onSentTransaction() {
- adapter.clear()
- churnUtxosButton?.visibility = View.GONE
- sendUtxosButton?.visibility = View.GONE
- freezeUtxosButton?.visibility = View.GONE
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/fragment/utxos/UtxosViewModel.kt b/app/src/main/java/net/mynero/wallet/fragment/utxos/UtxosViewModel.kt
deleted file mode 100644
index 31cf83e..0000000
--- a/app/src/main/java/net/mynero/wallet/fragment/utxos/UtxosViewModel.kt
+++ /dev/null
@@ -1,5 +0,0 @@
-package net.mynero.wallet.fragment.utxos
-
-import androidx.lifecycle.ViewModel
-
-class UtxosViewModel : ViewModel()
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.kt b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.kt
index 50ce055..1dbf462 100644
--- a/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.kt
+++ b/app/src/main/java/net/mynero/wallet/service/MoneroHandlerThread.kt
@@ -16,7 +16,11 @@
*/
package net.mynero.wallet.service
+import android.content.Context
+import android.os.Handler
import android.util.Log
+import android.widget.Toast
+import net.mynero.wallet.R
import net.mynero.wallet.model.PendingTransaction
import net.mynero.wallet.model.TransactionOutput
import net.mynero.wallet.model.Wallet
@@ -24,24 +28,42 @@ import net.mynero.wallet.model.Wallet.Companion.getAmountFromString
import net.mynero.wallet.model.Wallet.ConnectionStatus
import net.mynero.wallet.model.WalletListener
import net.mynero.wallet.model.WalletManager
+import java.io.File
/**
* Handy class for starting a new thread that has a looper. The looper can then be
* used to create handler classes. Note that start() must still be called.
* The started Thread has a stck size of STACK_SIZE (=5MB)
*/
-class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet) :
- Thread(null, null, name, THREAD_STACK_SIZE), WalletListener {
- private val wallet: Wallet
+class MoneroHandlerThread(val wallet: Wallet, val context: Context) : Thread(null, null, "WalletService", THREAD_STACK_SIZE), WalletListener {
- init {
- this.wallet = wallet
+ private val handler = Handler(context.mainLooper)
+
+ companion object {
+ // from src/cryptonote_config.h
+ const val THREAD_STACK_SIZE = (5 * 1024 * 1024).toLong()
+
+ fun init(walletFile: File, password: String?, context: Context) {
+ val wallet = WalletManager.instance!!.openWallet(walletFile.absolutePath, password ?: "")
+ val thread = MoneroHandlerThread(wallet, context)
+
+ TxService(thread)
+ BalanceService(thread)
+ AddressService(thread)
+ HistoryService(thread)
+ BlockchainService(thread)
+ DaemonService(thread)
+ UTXOService(thread)
+ thread.start()
+ }
}
@Synchronized
override fun start() {
+ println("HERE1")
super.start()
- listener?.onRefresh(false)
+ println("HERE2")
+ onRefresh(false)
}
override fun run() {
@@ -101,7 +123,7 @@ class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet)
private fun tryRestartConnection() {
Log.d("MoneroHandlerThread.kt", "refreshed() Starting connection retry")
- listener?.onConnectionFail()
+ onConnectionFail()
wallet.init(0)
wallet.startRefresh()
}
@@ -111,7 +133,7 @@ class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet)
if (walletSynced) {
wallet.refreshCoins()
}
- listener?.onRefresh(walletSynced)
+ onRefresh(walletSynced)
}
@Throws(Exception::class)
@@ -179,13 +201,20 @@ class MoneroHandlerThread(name: String, val listener: Listener?, wallet: Wallet)
return pendingTx.commit("", true)
}
- interface Listener {
- fun onRefresh(walletSynced: Boolean)
- fun onConnectionFail()
+ fun onRefresh(walletSynced: Boolean) {
+ println("onRefresh")
+ if (walletSynced) {
+ UTXOService.instance?.refreshUtxos()
+ }
+ HistoryService.instance?.refreshHistory()
+ BalanceService.instance?.refreshBalance()
+ BlockchainService.instance?.refreshBlockchain()
+ AddressService.instance?.refreshAddresses()
}
- companion object {
- // from src/cryptonote_config.h
- const val THREAD_STACK_SIZE = (5 * 1024 * 1024).toLong()
+ fun onConnectionFail() {
+ handler.post {
+ Toast.makeText(context, R.string.connection_failed, Toast.LENGTH_SHORT).show()
+ }
}
}
\ No newline at end of file
diff --git a/app/src/main/java/net/mynero/wallet/util/Constants.kt b/app/src/main/java/net/mynero/wallet/util/Constants.kt
index a65dbd4..950996a 100644
--- a/app/src/main/java/net/mynero/wallet/util/Constants.kt
+++ b/app/src/main/java/net/mynero/wallet/util/Constants.kt
@@ -20,6 +20,14 @@ object Constants {
const val NAV_ARG_TXINFO = "nav_arg_txinfo"
const val STREET_MODE_BALANCE = "#.############"
+ const val EXTRA_PREVENT_GOING_BACK = "prevent_going_back"
+ const val EXTRA_WALLET_NAME = "wallet_name"
+ const val EXTRA_WALLET_PASSWORD = "wallet_password"
+ const val EXTRA_SEND_ADDRESS = "send_address"
+ const val EXTRA_SEND_AMOUNT = "send_amount"
+ const val EXTRA_SEND_MAX = "send_max"
+ const val EXTRA_SEND_UTXOS = "send_utxos"
+
const val DEFAULT_PREF_MONEROCHAN = false
// Donation address is also specified in strings.xml, it is used as a tooltip in address fields
diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/activity_home.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_home.xml
rename to app/src/main/res/layout/activity_home.xml
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
deleted file mode 100644
index 3ec0ec3..0000000
--- a/app/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_onboarding.xml
rename to app/src/main/res/layout/activity_onboarding.xml
diff --git a/app/src/main/res/layout/password_bottom_sheet_dialog.xml b/app/src/main/res/layout/activity_password.xml
similarity index 73%
rename from app/src/main/res/layout/password_bottom_sheet_dialog.xml
rename to app/src/main/res/layout/activity_password.xml
index 3dcb79f..31e103a 100644
--- a/app/src/main/res/layout/password_bottom_sheet_dialog.xml
+++ b/app/src/main/res/layout/activity_password.xml
@@ -1,44 +1,42 @@
-
-
+ tools:context="net.mynero.wallet.MainActivity">
+ android:layout_height="wrap_content"
+ app:layout_constraintBottom_toBottomOf="parent">
+ app:layout_constraintStart_toStartOf="parent" />
+ app:layout_constraintStart_toStartOf="parent" />
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="parent" />
-
-
\ No newline at end of file
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_receive.xml b/app/src/main/res/layout/activity_receive.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_receive.xml
rename to app/src/main/res/layout/activity_receive.xml
diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/activity_send.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_send.xml
rename to app/src/main/res/layout/activity_send.xml
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/activity_settings.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_settings.xml
rename to app/src/main/res/layout/activity_settings.xml
diff --git a/app/src/main/res/layout/fragment_transaction.xml b/app/src/main/res/layout/activity_transaction.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_transaction.xml
rename to app/src/main/res/layout/activity_transaction.xml
diff --git a/app/src/main/res/layout/fragment_utxos.xml b/app/src/main/res/layout/activity_utxos.xml
similarity index 100%
rename from app/src/main/res/layout/fragment_utxos.xml
rename to app/src/main/res/layout/activity_utxos.xml
diff --git a/app/src/main/res/layout/send_bottom_sheet_dialog.xml b/app/src/main/res/layout/send_bottom_sheet_dialog.xml
deleted file mode 100644
index 1bab4c1..0000000
--- a/app/src/main/res/layout/send_bottom_sheet_dialog.xml
+++ /dev/null
@@ -1,300 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/navigation/main_nav.xml b/app/src/main/res/navigation/main_nav.xml
deleted file mode 100644
index 020c2d9..0000000
--- a/app/src/main/res/navigation/main_nav.xml
+++ /dev/null
@@ -1,95 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file