zcash-android-wallet-sdk/demo-app/src/main/java/cash/z/ecc/android/sdk/demoapp/demos/getbalance/GetBalanceFragment.kt

211 lines
7.7 KiB
Kotlin

package cash.z.ecc.android.sdk.demoapp.demos.getbalance
import android.os.Bundle
import android.view.LayoutInflater
import android.view.Menu
import android.view.View
import androidx.lifecycle.lifecycleScope
import cash.z.ecc.android.bip39.Mnemonics
import cash.z.ecc.android.bip39.toSeed
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.demoapp.BaseDemoFragment
import cash.z.ecc.android.sdk.demoapp.R
import cash.z.ecc.android.sdk.demoapp.databinding.FragmentGetBalanceBinding
import cash.z.ecc.android.sdk.demoapp.ext.requireApplicationContext
import cash.z.ecc.android.sdk.demoapp.util.SyncBlockchainBenchmarkTrace
import cash.z.ecc.android.sdk.demoapp.util.fromResources
import cash.z.ecc.android.sdk.ext.BenchmarkingExt
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.collectWith
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.ecc.android.sdk.fixture.BlockRangeFixture
import cash.z.ecc.android.sdk.internal.twig
import cash.z.ecc.android.sdk.model.Account
import cash.z.ecc.android.sdk.model.LightWalletEndpoint
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import cash.z.ecc.android.sdk.model.ZcashNetwork
import cash.z.ecc.android.sdk.model.defaultForNetwork
import cash.z.ecc.android.sdk.tool.DerivationTool
import kotlinx.coroutines.launch
/**
* Displays the available balance && total balance associated with the seed defined by the default config.
* comments.
*/
@Suppress("TooManyFunctions")
class GetBalanceFragment : BaseDemoFragment<FragmentGetBalanceBinding>() {
private lateinit var synchronizer: Synchronizer
override fun inflateBinding(layoutInflater: LayoutInflater): FragmentGetBalanceBinding =
FragmentGetBalanceBinding.inflate(layoutInflater)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
reportTraceEvent(SyncBlockchainBenchmarkTrace.Event.BALANCE_SCREEN_START)
setHasOptionsMenu(true)
setup()
}
override fun onPrepareOptionsMenu(menu: Menu) {
super.onPrepareOptionsMenu(menu)
// We rather hide options menu actions while actively using the Synchronizer
menu.setGroupVisible(R.id.main_menu_group, false)
}
override fun onDestroy() {
super.onDestroy()
reportTraceEvent(SyncBlockchainBenchmarkTrace.Event.BALANCE_SCREEN_END)
}
private fun setup() {
// defaults to the value of `DemoConfig.seedWords` but can also be set by the user
val seedPhrase = sharedViewModel.seedPhrase.value
// Use a BIP-39 library to convert a seed phrase into a byte array. Most wallets already
// have the seed stored
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
val network = ZcashNetwork.fromResources(requireApplicationContext())
synchronizer = Synchronizer.newBlocking(
requireApplicationContext(),
network,
lightWalletEndpoint = LightWalletEndpoint.defaultForNetwork(network),
seed = seed,
birthday = if (BenchmarkingExt.isBenchmarking()) {
BlockRangeFixture.new().start
} else {
sharedViewModel.birthdayHeight.value
}
)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val seedPhrase = sharedViewModel.seedPhrase.value
val seed = Mnemonics.MnemonicCode(seedPhrase).toSeed()
val network = ZcashNetwork.fromResources(requireApplicationContext())
binding.shield.apply {
setOnClickListener {
lifecycleScope.launch {
synchronizer.shieldFunds(DerivationTool.deriveUnifiedSpendingKey(seed, network, Account.DEFAULT))
}
}
}
}
override fun onResume() {
super.onResume()
// the lifecycleScope is used to dispose of the synchronize when the fragment dies
synchronizer.start(lifecycleScope)
monitorChanges()
}
private fun monitorChanges() {
synchronizer.status.collectWith(lifecycleScope, ::onStatus)
synchronizer.progress.collectWith(lifecycleScope, ::onProgress)
synchronizer.processorInfo.collectWith(lifecycleScope, ::onProcessorInfoUpdated)
synchronizer.orchardBalances.collectWith(lifecycleScope, ::onOrchardBalance)
synchronizer.saplingBalances.collectWith(lifecycleScope, ::onSaplingBalance)
synchronizer.transparentBalances.collectWith(lifecycleScope, ::onTransparentBalance)
}
private fun onOrchardBalance(
orchardBalance: WalletBalance?
) {
binding.orchardBalance.apply {
text = orchardBalance.humanString()
}
}
private fun onSaplingBalance(
saplingBalance: WalletBalance?
) {
binding.saplingBalance.apply {
text = saplingBalance.humanString()
}
}
private fun onTransparentBalance(
transparentBalance: WalletBalance?
) {
binding.transparentBalance.apply {
text = transparentBalance.humanString()
}
binding.shield.apply {
// TODO [#776]: Support variable fees
// TODO [#776]: https://github.com/zcash/zcash-android-wallet-sdk/issues/776
visibility = if ((transparentBalance?.available ?: Zatoshi(0)) > ZcashSdk.MINERS_FEE) {
View.VISIBLE
} else {
View.GONE
}
}
}
private fun onStatus(status: Synchronizer.Status) {
twig("Synchronizer status: $status")
// report benchmark event
val traceEvents = when (status) {
Synchronizer.Status.DOWNLOADING -> {
listOf(
SyncBlockchainBenchmarkTrace.Event.BLOCKCHAIN_SYNC_START,
SyncBlockchainBenchmarkTrace.Event.DOWNLOAD_START
)
}
Synchronizer.Status.VALIDATING -> {
listOf(
SyncBlockchainBenchmarkTrace.Event.DOWNLOAD_END,
SyncBlockchainBenchmarkTrace.Event.VALIDATION_START
)
}
Synchronizer.Status.SCANNING -> {
listOf(
SyncBlockchainBenchmarkTrace.Event.VALIDATION_END,
SyncBlockchainBenchmarkTrace.Event.SCAN_START
)
}
Synchronizer.Status.SYNCED -> {
listOf(
SyncBlockchainBenchmarkTrace.Event.SCAN_END,
SyncBlockchainBenchmarkTrace.Event.BLOCKCHAIN_SYNC_END
)
}
else -> null
}
traceEvents?.forEach { reportTraceEvent(it) }
binding.textStatus.text = "Status: $status"
onOrchardBalance(synchronizer.orchardBalances.value)
onSaplingBalance(synchronizer.saplingBalances.value)
onTransparentBalance(synchronizer.transparentBalances.value)
}
@Suppress("MagicNumber")
private fun onProgress(i: Int) {
if (i < 100) {
binding.textStatus.text = "Downloading blocks...$i%"
}
}
private fun onProcessorInfoUpdated(info: CompactBlockProcessor.ProcessorInfo) {
if (info.isScanning) binding.textStatus.text = "Scanning blocks...${info.scanProgress}%"
}
}
@Suppress("MagicNumber")
private fun WalletBalance?.humanString() = if (null == this) {
"Calculating balance"
} else {
"""
Pending balance: ${pending.convertZatoshiToZecString(12)}
Available balance: ${available.convertZatoshiToZecString(12)}
Total balance: ${total.convertZatoshiToZecString(12)}
""".trimIndent()
}