Hush lite wallet for Android
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 

158 lines
6.2 KiB

package cash.z.ecc.android.ui.home
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.widget.Toast
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import cash.z.ecc.android.R
import cash.z.ecc.android.ZcashWalletApp
import cash.z.ecc.android.databinding.FragmentBalanceDetailBinding
import cash.z.ecc.android.ext.goneIf
import cash.z.ecc.android.ext.onClickNavBack
import cash.z.ecc.android.ext.toAppColor
import cash.z.ecc.android.ext.toSplitColorSpan
import cash.z.ecc.android.feedback.Report.Tap.RECEIVE_BACK
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.ui.base.BaseFragment
import cash.z.ecc.android.ui.home.BalanceDetailViewModel.StatusModel
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
class BalanceDetailFragment : BaseFragment<FragmentBalanceDetailBinding>() {
private val viewModel: BalanceDetailViewModel by viewModels()
private var lastSignal: BlockHeight? = null
override fun inflate(inflater: LayoutInflater): FragmentBalanceDetailBinding =
FragmentBalanceDetailBinding.inflate(inflater)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.balances.onEach { onBalanceUpdated(it) }.launchIn(this)
viewModel.statuses.onEach { onStatusUpdated(it) }.launchIn(this)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.hitAreaExit.onClickNavBack() { tapped(RECEIVE_BACK) }
binding.textShieldedHushTitle.text = "SHIELDED ${getString(R.string.symbol)}"
}
private fun onBalanceUpdated(balanceModel: BalanceDetailViewModel.BalanceModel) {
balanceModel.apply {
if (balanceModel.hasData()) {
setBalances(paddedShielded, paddedTransparent, paddedTotal)
} else {
setBalances(" --", " --", " --")
}
}
}
private fun onStatusUpdated(status: StatusModel) {
binding.textStatus.text = status.toStatus()
if (status.missingBlocks > 100) {
binding.textBlockHeightPrefix.text = "Processing "
binding.textBlockHeight.text = String.format(
"%,d",
status.info.lastScannedHeight?.value ?: 0
) + " of " + String.format("%,d", status.info.networkBlockHeight?.value ?: 0)
} else {
status.info.lastScannedHeight.let { height ->
if (height == null) {
binding.textBlockHeightPrefix.text = "Processing..."
binding.textBlockHeight.text = ""
} else {
binding.textBlockHeightPrefix.text = "Balances as of block "
binding.textBlockHeight.text =
String.format("%,d", status.info.lastScannedHeight?.value ?: 0)
sendNewBlockSignal(status.info.lastScannedHeight)
}
}
}
}
private fun sendNewBlockSignal(currentHeight: BlockHeight?) {
// prevent a flood of signals while scanning blocks
if (lastSignal != null && (currentHeight?.value ?: 0) > lastSignal!!.value) {
mainActivity?.vibrate(0, 100, 100, 300)
Toast.makeText(mainActivity, "New block!", Toast.LENGTH_SHORT).show()
}
lastSignal = currentHeight
}
fun setBalances(shielded: String, transparent: String, total: String) {
binding.textShieldAmount.text = shielded.colorize()
}
private fun String.colorize(): CharSequence {
val dotIndex = indexOf('.')
return if (dotIndex < 0 || length < (dotIndex + 4)) {
this
} else {
toSplitColorSpan(R.color.text_light, R.color.zcashWhite_24, indexOf('.') + 4)
}
}
private fun StatusModel.toStatus(): String {
fun String.plural(count: Int) = if (count > 1) "${this}s" else this
if (viewModel.latestBalance?.hasData() == false) {
return "Balance info is not yet available"
}
var status = ""
if (hasUnmined) {
val count = pendingUnmined.count()
status += "Balance excludes $count unconfirmed ${"transaction".plural(count)}. "
}
status += when {
hasPendingTransparentBalance && hasPendingShieldedBalance -> {
"Awaiting ${pendingShieldedBalance.convertZatoshiToZecString(8)} ${
ZcashWalletApp.instance.getString(
R.string.symbol
)
} in shielded funds and {pendingTransparentBalance.convertZatoshiToZecString(8)} ${
ZcashWalletApp.instance.getString(
R.string.symbol
)
} in transparent funds"
}
hasPendingShieldedBalance -> {
"Awaiting ${pendingShieldedBalance.convertZatoshiToZecString(8)} ${
ZcashWalletApp.instance.getString(
R.string.symbol
)
} in shielded funds"
}
hasPendingTransparentBalance -> {
"Awaiting ${pendingTransparentBalance.convertZatoshiToZecString(8)} ${
ZcashWalletApp.instance.getString(
R.string.symbol
)
} in transparent funds"
}
else -> ""
}
pendingUnconfirmed.count().takeUnless { it == 0 }?.let { count ->
if (status.contains("Awaiting")) status += " and "
status += "$count outbound ${"transaction".plural(count)}"
remainingConfirmations().firstOrNull()?.let { remaining ->
status += " with $remaining ${"confirmation".plural(remaining.toInt())} remaining"
}
}
return if (status.isEmpty()) "All funds are available!" else status
}
}