From 9c6b09b964d014f50849c2a96ee8a266a0ade533 Mon Sep 17 00:00:00 2001 From: Rafael Date: Tue, 18 Jun 2024 15:58:50 +0600 Subject: [PATCH] Update UtxoExpertMode page --- .../modules/send/bitcoin/SendBitcoinScreen.kt | 4 - .../utxoexpert/UtxoExpertModeScreen.kt | 116 ++++++--------- .../utxoexpert/UtxoExpertModeViewModel.kt | 133 +++++++----------- app/src/main/res/values/strings.xml | 3 + 4 files changed, 94 insertions(+), 162 deletions(-) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/SendBitcoinScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/SendBitcoinScreen.kt index 1cdddf6e74b..06c076ff8e5 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/SendBitcoinScreen.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/SendBitcoinScreen.kt @@ -100,10 +100,6 @@ fun SendBitcoinNavHost( UtxoExpertModeScreen( adapter = viewModel.adapter, token = viewModel.wallet.token, - address = viewModel.uiState.address, - memo = viewModel.uiState.memo, - value = viewModel.uiState.amount, - feeRate = viewModel.uiState.feeRate, customUnspentOutputs = viewModel.customUnspentOutputs, updateUnspentOutputs = { viewModel.updateCustomUnspentOutputs(it) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeScreen.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeScreen.kt index eb9ff471125..db27ba133ba 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeScreen.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeScreen.kt @@ -1,8 +1,10 @@ package io.horizontalsystems.bankwallet.modules.send.bitcoin.utxoexpert import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height @@ -10,6 +12,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Divider import androidx.compose.material.Scaffold import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -21,33 +24,26 @@ import androidx.lifecycle.viewmodel.compose.viewModel import io.horizontalsystems.bankwallet.R import io.horizontalsystems.bankwallet.core.ISendBitcoinAdapter import io.horizontalsystems.bankwallet.core.shorten -import io.horizontalsystems.bankwallet.entities.Address -import io.horizontalsystems.bankwallet.modules.evmfee.ButtonsGroupWithShade import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme import io.horizontalsystems.bankwallet.ui.compose.components.AppBar -import io.horizontalsystems.bankwallet.ui.compose.components.ButtonPrimaryYellow +import io.horizontalsystems.bankwallet.ui.compose.components.ButtonSecondaryTransparent +import io.horizontalsystems.bankwallet.ui.compose.components.CellUniversalLawrenceSection import io.horizontalsystems.bankwallet.ui.compose.components.HSpacer import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton import io.horizontalsystems.bankwallet.ui.compose.components.HsCheckbox import io.horizontalsystems.bankwallet.ui.compose.components.RowUniversal import io.horizontalsystems.bankwallet.ui.compose.components.SectionItemBorderedRowUniversalClear -import io.horizontalsystems.bankwallet.ui.compose.components.SectionUniversalItem import io.horizontalsystems.bankwallet.ui.compose.components.VSpacer import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_grey import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_leah import io.horizontalsystems.bankwallet.ui.compose.components.subhead2_lucian import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo import io.horizontalsystems.marketkit.models.Token -import java.math.BigDecimal @Composable fun UtxoExpertModeScreen( adapter: ISendBitcoinAdapter, token: Token, - address: Address?, - memo: String?, - value: BigDecimal?, - feeRate: Int?, customUnspentOutputs: List?, updateUnspentOutputs: (List) -> Unit, onBackClick: () -> Unit @@ -57,10 +53,6 @@ fun UtxoExpertModeScreen( factory = UtxoExpertModeModule.Factory( adapter, token, - address, - memo, - value, - feeRate, customUnspentOutputs ) ) @@ -83,13 +75,11 @@ fun UtxoExpertModeScreen( .padding(it) .fillMaxSize() ) { - Column( - modifier = Modifier.background(ComposeAppTheme.colors.lawrence) - ) { - UtxoInfoSection( - uiState.sendToInfo, - uiState.changeInfo, - uiState.feeInfo, + CellUniversalLawrenceSection { + UtxoInfoCell( + title = stringResource(R.string.Send_Utxo_AvailableBalance), + value = uiState.availableBalanceInfo.value, + subValue = uiState.availableBalanceInfo.subValue ) } Box( @@ -103,14 +93,39 @@ fun UtxoExpertModeScreen( } ) } - ButtonsGroupWithShade { - ButtonPrimaryYellow( - modifier = Modifier - .fillMaxWidth() - .padding(start = 16.dp, end = 16.dp), - title = stringResource(R.string.Button_Done), - onClick = onBackClick, + Box( + modifier = Modifier + .height(62.dp) + .fillMaxWidth() + ){ + Divider( + modifier = Modifier.fillMaxWidth(), + thickness = 1.dp, + color = ComposeAppTheme.colors.steel10 ) + Row( + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxSize(), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.SpaceBetween + ) { + ButtonSecondaryTransparent( + title = stringResource(id = R.string.Send_Utxo_UnselectAll), + enabled = uiState.unselectAllIsEnabled, + onClick = { + viewModel.unselectAll() + updateUnspentOutputs(viewModel.customOutputs) + } + ) + ButtonSecondaryTransparent( + title = stringResource(id = R.string.Send_Utxo_SelectAll), + onClick = { + viewModel.selectAll() + updateUnspentOutputs(viewModel.customOutputs) + } + ) + } } } } @@ -150,53 +165,9 @@ private fun UtxoList( } } -@Composable -private fun UtxoInfoSection( - sendToInfo: UtxoExpertModeModule.InfoItem, - changeInfo: UtxoExpertModeModule.InfoItem?, - feeInfo: UtxoExpertModeModule.InfoItem -) { - val infoItems = buildList<@Composable () -> Unit> { - add { - UtxoInfoCell( - title = stringResource(R.string.Send_Utxo_SendTo), - subtitle = sendToInfo.subTitle, - value = sendToInfo.value, - subValue = sendToInfo.subValue - ) - } - changeInfo?.let { - add { - UtxoInfoCell( - title = stringResource(R.string.Send_Utxo_Change), - subtitle = changeInfo.subTitle, - value = changeInfo.value, - subValue = changeInfo.subValue - ) - } - } - add { - UtxoInfoCell( - title = stringResource(R.string.Send_Fee), - subtitle = feeInfo.subTitle, - value = feeInfo.value, - subValue = feeInfo.subValue - ) - } - } - infoItems.forEachIndexed { index, composable -> - SectionUniversalItem( - borderTop = index != 0, - ) { - composable() - } - } -} - @Composable private fun UtxoInfoCell( title: String, - subtitle: String?, value: String?, subValue: String? ) { @@ -211,9 +182,6 @@ private fun UtxoInfoCell( modifier = Modifier.weight(1f) ) { subhead2_leah(text = title) - subtitle?.let { - subhead2_grey(text = it) - } } Column( horizontalAlignment = Alignment.End diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeViewModel.kt index c05b3008421..6fbe9d478f0 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/send/bitcoin/utxoexpert/UtxoExpertModeViewModel.kt @@ -7,9 +7,7 @@ import cash.z.ecc.android.sdk.ext.collectWith import io.horizontalsystems.bankwallet.core.App import io.horizontalsystems.bankwallet.core.ISendBitcoinAdapter import io.horizontalsystems.bankwallet.core.ViewModelUiState -import io.horizontalsystems.bankwallet.core.shorten import io.horizontalsystems.bankwallet.core.toHexString -import io.horizontalsystems.bankwallet.entities.Address import io.horizontalsystems.bankwallet.modules.xrate.XRateService import io.horizontalsystems.bitcoincore.storage.UnspentOutputInfo import io.horizontalsystems.core.helpers.DateHelper @@ -20,30 +18,16 @@ import java.util.Date class UtxoExpertModeViewModel( private val adapter: ISendBitcoinAdapter, private val token: Token, - private val address: Address?, - private val memo: String?, - private val feeRate: Int?, - initialValue: BigDecimal?, initialCustomUnspentOutputs: List?, xRateService: XRateService, ) : ViewModelUiState() { - private var value = initialValue ?: BigDecimal.ZERO private var unspentOutputViewItems = listOf() private var selectedUnspentOutputs = listOf() private var coinRate = xRateService.getRate(token.coin.uid) - private var sendToInfo = UtxoExpertModeModule.InfoItem( - subTitle = address?.hex?.shorten(), - value = App.numberFormatter.formatCoinFull(value, token.coin.code, token.decimals), - subValue = coinRate?.let { rate -> - rate.copy(value = value.times(rate.value)).getFormattedFull() - } ?: "", - ) - private var changeInfo: UtxoExpertModeModule.InfoItem? = null - private var feeInfo = UtxoExpertModeModule.InfoItem( - subTitle = null, - value = null, - subValue = null, + private var availableBalanceInfo = UtxoExpertModeModule.InfoItem( + value = App.numberFormatter.formatCoinFull(BigDecimal.ZERO, token.coin.code, token.decimals), + subValue = "", ) val customOutputs: List @@ -57,73 +41,33 @@ class UtxoExpertModeViewModel( coinRate = it setUnspentOutputViewItems() } + setAvailableBalanceInfo() setUnspentOutputViewItems() - if (selectedUnspentOutputs.isNotEmpty()) { - updateFee() - } emitState() } override fun createState() = UtxoExpertModeModule.UiState( - sendToInfo = sendToInfo, - changeInfo = changeInfo, - feeInfo = feeInfo, + availableBalanceInfo = availableBalanceInfo, utxoItems = unspentOutputViewItems, + unselectAllIsEnabled = selectedUnspentOutputs.isNotEmpty(), ) private fun getUnspentId(unspentOutputInfo: UnspentOutputInfo) = "${unspentOutputInfo.transactionHash.toHexString()}-${unspentOutputInfo.outputIndex}" - fun onUnspentOutputClicked(id: String) { - selectedUnspentOutputs = if (selectedUnspentOutputs.contains(id)) { - selectedUnspentOutputs.filter { it != id } - } else { - selectedUnspentOutputs + id + private fun setAvailableBalanceInfo() { + var totalCoinValue = BigDecimal.ZERO + adapter.unspentOutputs.map { utxo -> + val utxoId = getUnspentId(utxo) + if (selectedUnspentOutputs.isEmpty() || selectedUnspentOutputs.contains(utxoId)) { + totalCoinValue += utxo.value.toBigDecimal().movePointLeft(token.decimals) + } } - unspentOutputViewItems = unspentOutputViewItems.map { utxo -> - utxo.copy(selected = selectedUnspentOutputs.contains(utxo.id)) - } - updateFee() - emitState() - } - - private fun updateFee() { - val feeRateNonNull = feeRate ?: return - val bitcoinFeeInfo = adapter.bitcoinFeeInfo( - amount = value, - feeRate = feeRateNonNull, - address = address?.hex, - memo = memo, - unspentOutputs = customOutputs, - pluginData = null, - ) - val fee = bitcoinFeeInfo?.fee - if (fee == null) { - feeInfo = feeInfo.copy( - value = null, - subValue = null, - ) - changeInfo = null - return - } - feeInfo = feeInfo.copy( - value = App.numberFormatter.formatCoinFull(fee, token.coin.code, token.decimals), + availableBalanceInfo = availableBalanceInfo.copy( + value = App.numberFormatter.formatCoinFull(totalCoinValue, token.coin.code, token.decimals), subValue = coinRate?.let { rate -> - rate.copy(value = fee.times(rate.value)).getFormattedFull() + rate.copy(value = totalCoinValue.times(rate.value)).getFormattedFull() } ?: "", ) - changeInfo = if (bitcoinFeeInfo.changeAddress != null && bitcoinFeeInfo.changeValue != null) { - val value = bitcoinFeeInfo.changeValue - UtxoExpertModeModule.InfoItem( - subTitle = bitcoinFeeInfo.changeAddress.stringValue.shorten(), - value = App.numberFormatter.formatCoinFull(value, token.coin.code, token.decimals), - subValue = coinRate?.let { rate -> - rate.copy(value = value.times(rate.value)).getFormattedFull() - } ?: "", - ) - } else { - null - } - emitState() } private fun setUnspentOutputViewItems() { @@ -144,6 +88,37 @@ class UtxoExpertModeViewModel( } } + private fun updateUtxoSelectedState() { + unspentOutputViewItems = unspentOutputViewItems.map { utxo -> + utxo.copy(selected = selectedUnspentOutputs.contains(utxo.id)) + } + } + + fun onUnspentOutputClicked(id: String) { + selectedUnspentOutputs = if (selectedUnspentOutputs.contains(id)) { + selectedUnspentOutputs.filter { it != id } + } else { + selectedUnspentOutputs + id + } + updateUtxoSelectedState() + setAvailableBalanceInfo() + emitState() + } + + fun unselectAll() { + selectedUnspentOutputs = listOf() + updateUtxoSelectedState() + setAvailableBalanceInfo() + emitState() + } + + fun selectAll() { + selectedUnspentOutputs = unspentOutputViewItems.map { it.id } + updateUtxoSelectedState() + setAvailableBalanceInfo() + emitState() + } + } object UtxoExpertModeModule { @@ -152,20 +127,12 @@ object UtxoExpertModeModule { class Factory( private val adapter: ISendBitcoinAdapter, private val token: Token, - private val address: Address?, - private val memo: String?, - private val value: BigDecimal?, - private val feeRate: Int?, private val customUnspentOutputs: List?, ) : ViewModelProvider.Factory { override fun create(modelClass: Class): T { return UtxoExpertModeViewModel( adapter = adapter, token = token, - address = address, - memo = memo, - feeRate = feeRate, - initialValue = value, initialCustomUnspentOutputs = customUnspentOutputs, xRateService = XRateService(App.marketKit, App.currencyManager.baseCurrency) ) as T @@ -173,14 +140,12 @@ object UtxoExpertModeModule { } data class UiState( - val sendToInfo: InfoItem, - val changeInfo: InfoItem?, - val feeInfo: InfoItem, + val availableBalanceInfo: InfoItem, val utxoItems: List, + val unselectAllIsEnabled: Boolean, ) data class InfoItem( - val subTitle: String?, val value: String?, val subValue: String?, ) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d22f74b04ce..6559e69253f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -472,6 +472,9 @@ This option allows you to resend a stuck transaction by increasing the amount of the fee. Enabled Disabled + Available Balance + Unsellect All + Sellect All Confirm Domain