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.

147 lines
5.6 KiB

package cash.z.ecc.android.ui.home
import androidx.lifecycle.ViewModel
import cash.z.ecc.android.di.DependenciesHolder
import cash.z.ecc.android.lockbox.LockBox
import cash.z.ecc.android.sdk.Synchronizer
import cash.z.ecc.android.sdk.block.CompactBlockProcessor
import cash.z.ecc.android.sdk.db.entity.PendingTransaction
import cash.z.ecc.android.sdk.db.entity.isMined
import cash.z.ecc.android.sdk.db.entity.isSubmitSuccess
import cash.z.ecc.android.sdk.ext.ZcashSdk
import cash.z.ecc.android.sdk.ext.convertZatoshiToZecString
import cash.z.ecc.android.sdk.model.BlockHeight
import cash.z.ecc.android.sdk.model.WalletBalance
import cash.z.ecc.android.sdk.model.Zatoshi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combineTransform
class BalanceDetailViewModel : ViewModel() {
private val synchronizer: Synchronizer = DependenciesHolder.synchronizer
private val lockBox: LockBox = DependenciesHolder.lockBox
var showAvailable: Boolean = true
set(value) {
field = value
latestBalance?.showAvailable = value
}
var latestBalance: BalanceModel? = null
val balances: Flow<BalanceModel>
get() = combineTransform(
synchronizer.saplingBalances,
synchronizer.transparentBalances
) { saplingBalance, transparentBalance ->
BalanceModel(saplingBalance, transparentBalance, showAvailable).let {
latestBalance = it
emit(it)
}
}
val statuses: Flow<StatusModel>
get() = combineTransform(
balances,
synchronizer.pendingTransactions,
synchronizer.processorInfo
) { balances, pending, info ->
emit(StatusModel(balances, pending, info))
}
data class BalanceModel(
val shieldedBalance: WalletBalance?,
val transparentBalance: WalletBalance?,
var showAvailable: Boolean = false
) {
/** Whether to make calculations based on total or available zatoshi */
val canAutoShield: Boolean =
(transparentBalance?.available?.value ?: 0L) > ZcashSdk.MINERS_FEE.value
val balanceShielded: String
get() {
return if (showAvailable) shieldedBalance?.available.toDisplay()
else shieldedBalance?.total.toDisplay()
}
val balanceTransparent: String
get() {
return if (showAvailable) transparentBalance?.available.toDisplay()
else transparentBalance?.total.toDisplay()
}
val balanceTotal: String
get() {
return if (showAvailable) ((shieldedBalance?.available
?: Zatoshi(0)) + (transparentBalance?.available ?: Zatoshi(0))).toDisplay()
else ((shieldedBalance?.total ?: Zatoshi(0)) + (transparentBalance?.total
?: Zatoshi(0))).toDisplay()
}
val paddedShielded get() = pad(balanceShielded)
val paddedTransparent get() = pad(balanceTransparent)
val paddedTotal get() = pad(balanceTotal)
val maxLength
get() = maxOf(
balanceShielded.length,
balanceTransparent.length,
balanceTotal.length
)
val hasPending =
(null != shieldedBalance && shieldedBalance.available != shieldedBalance.total) ||
(null != transparentBalance && transparentBalance.available != transparentBalance.total)
private fun Zatoshi?.toDisplay(): String {
return this?.convertZatoshiToZecString(8, 8) ?: "0"
}
private fun pad(balance: String): String {
var diffLength = maxLength - balance.length
return buildString {
repeat(diffLength) {
append(' ')
}
append(balance)
}
}
fun hasData(): Boolean {
return shieldedBalance != null || transparentBalance != null
}
}
data class StatusModel(
val balances: BalanceModel,
val pending: List<PendingTransaction>,
val info: CompactBlockProcessor.ProcessorInfo,
) {
val pendingUnconfirmed =
pending.filter { it.isSubmitSuccess() && it.isMined() && !it.isConfirmed(info.lastScannedHeight) }
val pendingUnmined = pending.filter { it.isSubmitSuccess() && !it.isMined() }
val pendingShieldedBalance = balances.shieldedBalance?.pending
val pendingTransparentBalance = balances.transparentBalance?.pending
val hasUnconfirmed = pendingUnconfirmed.isNotEmpty()
val hasUnmined = pendingUnmined.isNotEmpty()
val hasPendingShieldedBalance = (pendingShieldedBalance?.value ?: 0L) > 0L
val hasPendingTransparentBalance = (pendingTransparentBalance?.value ?: 0L) > 0L
val missingBlocks = ((info.networkBlockHeight?.value ?: 0) - (info.lastScannedHeight?.value
?: 0)).coerceAtLeast(0)
private fun PendingTransaction.isConfirmed(networkBlockHeight: BlockHeight?): Boolean {
return networkBlockHeight?.let {
isMined() && (it.value - minedHeight + 1) > 10 // fix: plus 1 because the mined block counts as the FIRST confirmation
} ?: false
}
fun remainingConfirmations(confirmationsRequired: Int = 10) =
pendingUnconfirmed
.map {
confirmationsRequired - ((info.lastScannedHeight?.value
?: -1) - it.minedHeight + 1)
} // fix: plus 1 because the mined block counts as the FIRST confirmation
.filter { it > 0 }
.sortedDescending()
}
}