Updated on 2026-08-14

This commit is contained in:
Tangem 2025-04-02 11:54:10 +05:00
parent 95f764c878
commit 9e98ebe77b
10 changed files with 370 additions and 73 deletions

View file

@ -39,8 +39,12 @@ internal class DefaultSendComponent @AssistedInject constructor(
popCallback = { onChildBack() },
)
private val initialRoute = SendRoute.Empty
private val currentRoute = MutableStateFlow<SendRoute>(initialRoute)
private val initialRoute = if (params.amount == null) {
SendRoute.Destination(isEditMode = false)
} else {
SendRoute.Empty
}
private val currentRoute = MutableStateFlow(initialRoute)
private val model: SendModel = getOrCreateModel(params = params, router = innerRouter)
@ -77,12 +81,12 @@ internal class DefaultSendComponent @AssistedInject constructor(
private fun getDestinationComponent(factoryContext: AppComponentContext, route: SendRoute) =
SendDestinationComponent(
appComponentContext = factoryContext,
params = SendDestinationComponent.Params(
params = SendDestinationComponentParams.DestinationParams(
state = model.uiState.value.destinationUM,
currentRoute = currentRoute.filterIsInstance<SendRoute.Destination>(),
analyticsCategoryName = SendAnalyticEvents.SEND_CATEGORY,
userWallet = model.userWallet,
cryptoCurrencyStatus = model.cryptoCurrencyStatus,
cryptoCurrency = params.currency,
callback = model,
isEditMode = route.isEditMode,
),

View file

@ -18,6 +18,8 @@ import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
import com.tangem.features.send.v2.send.ui.state.SendUM
import com.tangem.features.send.v2.subcomponents.amount.SendAmountBlockComponent
import com.tangem.features.send.v2.subcomponents.amount.SendAmountComponentParams
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationBlockComponent
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponentParams
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
import com.tangem.features.send.v2.subcomponents.fee.SendFeeComponentParams
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsComponent
@ -33,22 +35,22 @@ internal class SendConfirmComponent(
private val blockClickEnableFlow = MutableStateFlow(false)
// private val destinationBlockComponent =
// SendDestinationBlockComponent(
// appComponentContext = child("sendConfirmDestinationBlock"),
// params = SendDestinationComponentParams.DestinationBlockParams(
// state = model.uiState.value.destinationUM,
// analyticsCategoryName = params.analyticsCategoryName,
// userWallet = params.userWallet,
// cryptoCurrency = params.cryptoCurrencyStatus.currency,
// blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
// isPredefinedValues = params.predefinedValues is Params.PredefinedValues.Content,
// predefinedAddressValue = (params.predefinedValues as? Params.PredefinedValues.Content)?.address,
// predefinedMemoValue = (params.predefinedValues as? Params.PredefinedValues.Content)?.tag,
// ),
// onResult = model::onDestinationResult,
// onClick = model::showEditDestination,
// )
private val destinationBlockComponent =
SendDestinationBlockComponent(
appComponentContext = child("sendConfirmDestinationBlock"),
params = SendDestinationComponentParams.DestinationBlockParams(
state = model.uiState.value.destinationUM,
analyticsCategoryName = params.analyticsCategoryName,
userWallet = params.userWallet,
cryptoCurrency = params.cryptoCurrencyStatus.currency,
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
isPredefinedValues = params.predefinedValues is Params.PredefinedValues.Content,
predefinedAddressValue = (params.predefinedValues as? Params.PredefinedValues.Content)?.address,
predefinedMemoValue = (params.predefinedValues as? Params.PredefinedValues.Content)?.tag,
),
onResult = model::onDestinationResult,
onClick = model::showEditDestination,
)
private val amountBlockComponent = SendAmountBlockComponent(
appComponentContext = child("sendConfirmAmountBlock"),
@ -59,9 +61,8 @@ internal class SendConfirmComponent(
cryptoCurrencyStatus = params.cryptoCurrencyStatus,
appCurrency = params.appCurrency,
blockClickEnableFlow = blockClickEnableFlow.asStateFlow(),
blockEditDisabledFlow = TODO(),
// isPredefinedValues = params.predefinedValues is Params.PredefinedValues.Content,
// predefinedAmountValue = (params.predefinedValues as? Params.PredefinedValues.Content)?.amount,
isPredefinedValues = params.predefinedValues is Params.PredefinedValues.Content,
predefinedAmountValue = (params.predefinedValues as? Params.PredefinedValues.Content)?.amount,
),
onResult = model::onAmountResult,
onClick = model::showEditAmount,
@ -109,8 +110,7 @@ internal class SendConfirmComponent(
}
fun updateState(state: SendUM) {
// todo
// destinationBlockComponent.updateState(state.destinationUM)
destinationBlockComponent.updateState(state.destinationUM)
amountBlockComponent.updateState(state.amountUM)
feeBlockComponent.updateState(state.feeUM)
model.updateState(state)
@ -123,7 +123,7 @@ internal class SendConfirmComponent(
SendConfirmContent(
sendUM = state,
// destinationBlockComponent = destinationBlockComponent,
destinationBlockComponent = destinationBlockComponent,
amountBlockComponent = amountBlockComponent,
feeBlockComponent = feeBlockComponent,
notificationsComponent = notificationsComponent,

View file

@ -36,6 +36,7 @@ import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.send.confirm.ui.state.ConfirmUM
import com.tangem.features.send.v2.send.ui.state.SendUM
import com.tangem.features.send.v2.subcomponents.amount.SendAmountBlockComponent
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationBlockComponent
import com.tangem.features.send.v2.subcomponents.fee.SendFeeBlockComponent
import com.tangem.features.send.v2.subcomponents.notifications
import com.tangem.features.send.v2.subcomponents.notifications.NotificationsComponent
@ -50,7 +51,7 @@ private const val TAP_HELP_ANIMATION_DELAY = 500L
@Composable
internal fun SendConfirmContent(
sendUM: SendUM,
// destinationBlockComponent: SendDestinationBlockComponent,
destinationBlockComponent: SendDestinationBlockComponent,
amountBlockComponent: SendAmountBlockComponent,
feeBlockComponent: SendFeeBlockComponent,
notificationsComponent: NotificationsComponent,
@ -64,7 +65,7 @@ internal fun SendConfirmContent(
) {
blocks(
uiState = sendUM,
// destinationBlockComponent = destinationBlockComponent,
destinationBlockComponent = destinationBlockComponent,
amountBlockComponent = amountBlockComponent,
feeBlockComponent = feeBlockComponent,
)
@ -121,7 +122,7 @@ private fun SendingText(confirmUM: ConfirmUM.Content?, modifier: Modifier = Modi
private fun LazyListScope.blocks(
uiState: SendUM,
// destinationBlockComponent: SendDestinationBlockComponent,
destinationBlockComponent: SendDestinationBlockComponent,
amountBlockComponent: SendAmountBlockComponent,
feeBlockComponent: SendFeeBlockComponent,
) {
@ -144,8 +145,7 @@ private fun LazyListScope.blocks(
modifier = Modifier.padding(vertical = 12.dp),
)
}
// todo
// destinationBlockComponent.Content(modifier = Modifier)
destinationBlockComponent.Content(modifier = Modifier)
amountBlockComponent.Content(modifier = Modifier)
feeBlockComponent.Content(modifier = Modifier)
}

View file

@ -0,0 +1,28 @@
package com.tangem.features.send.v2.subcomponents.destination
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.core.ui.message.EventMessageAction
import com.tangem.features.send.v2.impl.R
import javax.inject.Inject
@ModelScoped
internal class SendDestinationAlertFactory @Inject constructor(
val messageSender: UiMessageSender,
) {
fun getGenericErrorState(onFailedTxEmailClick: () -> Unit) {
messageSender.send(
DialogMessage(
title = resourceReference(id = R.string.send_alert_transaction_failed_title),
message = resourceReference(id = R.string.common_unknown_error),
firstAction = EventMessageAction(
title = resourceReference(R.string.common_support),
onClick = onFailedTxEmailClick,
),
),
)
}
}

View file

@ -0,0 +1,45 @@
package com.tangem.features.send.v2.subcomponents.destination
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.features.send.v2.subcomponents.destination.model.SendDestinationModel
import com.tangem.features.send.v2.subcomponents.destination.ui.DestinationBlock
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
internal class SendDestinationBlockComponent(
appComponentContext: AppComponentContext,
private val params: SendDestinationComponentParams.DestinationBlockParams,
val onClick: () -> Unit,
val onResult: (DestinationUM) -> Unit,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: SendDestinationModel = getOrCreateModel(params = params)
init {
model.uiState.onEach {
onResult(it)
}.launchIn(componentScope)
}
fun updateState(destinationUM: DestinationUM) = model.updateState(destinationUM)
@Composable
override fun Content(modifier: Modifier) {
val state by model.uiState.collectAsStateWithLifecycle()
val isClickEnabled by params.blockClickEnableFlow.collectAsStateWithLifecycle()
DestinationBlock(
destinationUM = state,
isClickDisabled = !isClickEnabled,
isEditingDisabled = params.isPredefinedValues,
onClick = onClick,
)
}
}

View file

@ -1,44 +1,34 @@
package com.tangem.features.send.v2.subcomponents.destination
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.send.v2.send.SendRoute
import com.tangem.features.send.v2.common.SendNavigationModelCallback
import com.tangem.features.send.v2.subcomponents.destination.model.SendDestinationModel
import com.tangem.features.send.v2.subcomponents.destination.ui.SendDestinationContent
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM
import kotlinx.coroutines.flow.Flow
internal class SendDestinationComponent(
appComponentContext: AppComponentContext,
private val params: Params,
private val params: SendDestinationComponentParams.DestinationParams,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: SendDestinationModel = getOrCreateModel(params = params, router = router)
private val model: SendDestinationModel = getOrCreateModel(params = params)
fun updateState(state: DestinationUM) = model.updateState(state)
@Composable
override fun Content(modifier: Modifier) {
val state = model.uiState.collectAsStateWithLifecycle()
val state by model.uiState.collectAsStateWithLifecycle()
val isBalanceHidden by params.isBalanceHidingFlow.collectAsStateWithLifecycle()
SendDestinationContent(state = state.value, model, isBalanceHidden = false)
SendDestinationContent(state = state, clickIntents = model, isBalanceHidden = isBalanceHidden)
}
data class Params(
val state: DestinationUM,
val analyticsCategoryName: String,
val userWallet: UserWallet,
val cryptoCurrencyStatus: CryptoCurrencyStatus,
val callback: ModelCallback,
val currentRoute: Flow<SendRoute.Destination>,
val isEditMode: Boolean,
)
interface ModelCallback : SendNavigationModelCallback {
fun onDestinationResult(destinationUM: DestinationUM)

View file

@ -0,0 +1,39 @@
package com.tangem.features.send.v2.subcomponents.destination
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.features.send.v2.send.SendRoute
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponent.ModelCallback
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
internal sealed class SendDestinationComponentParams {
abstract val state: DestinationUM
abstract val analyticsCategoryName: String
abstract val userWallet: UserWallet
abstract val cryptoCurrency: CryptoCurrency
data class DestinationParams(
override val state: DestinationUM,
override val analyticsCategoryName: String,
override val userWallet: UserWallet,
override val cryptoCurrency: CryptoCurrency,
val isBalanceHidingFlow: StateFlow<Boolean>,
val currentRoute: Flow<SendRoute.Destination>,
val callback: ModelCallback,
val isEditMode: Boolean,
) : SendDestinationComponentParams()
data class DestinationBlockParams(
override val state: DestinationUM,
override val analyticsCategoryName: String,
override val userWallet: UserWallet,
override val cryptoCurrency: CryptoCurrency,
val blockClickEnableFlow: StateFlow<Boolean>,
val predefinedAddressValue: String?,
val predefinedMemoValue: String?,
val isPredefinedValues: Boolean,
) : SendDestinationComponentParams()
}

View file

@ -1,18 +1,32 @@
package com.tangem.features.send.v2.subcomponents.destination.model
import androidx.compose.runtime.Stable
import arrow.core.Either
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.features.send.v2.send.ui.state.ButtonsUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.qrscanning.models.SourceType
import com.tangem.domain.qrscanning.usecases.ListenToQrScanningUseCase
import com.tangem.domain.qrscanning.usecases.ParseQrCodeUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
import com.tangem.domain.tokens.GetCurrencyStatusUpdatesUseCase
import com.tangem.domain.tokens.GetNetworkAddressesUseCase
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.usecase.IsUtxoConsolidationAvailableUseCase
import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
import com.tangem.domain.transaction.usecase.ValidateWalletMemoUseCase
@ -22,7 +36,10 @@ import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.send.v2.common.NavigationUM
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.send.SendRoute
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponent
import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents
import com.tangem.features.send.v2.send.analytics.SendAnalyticEvents.SendScreenSource
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationAlertFactory
import com.tangem.features.send.v2.subcomponents.destination.SendDestinationComponentParams
import com.tangem.features.send.v2.subcomponents.destination.analytics.EnterAddressSource
import com.tangem.features.send.v2.subcomponents.destination.analytics.SendDestinationAnalyticEvents
import com.tangem.features.send.v2.subcomponents.destination.model.transformers.*
@ -31,6 +48,7 @@ import com.tangem.features.send.v2.subcomponents.destination.ui.state.Destinatio
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveIn
import com.tangem.utils.coroutines.waitForDelay
import com.tangem.utils.transformer.update
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
@ -38,10 +56,11 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
import kotlin.properties.Delegates
@Stable
@ModelScoped
@Suppress("LongParameterList")
@Suppress("LongParameterList", "LargeClass")
internal class SendDestinationModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
@ -55,43 +74,57 @@ internal class SendDestinationModel @Inject constructor(
private val getFixedTxHistoryItemsUseCase: GetFixedTxHistoryItemsUseCase,
private val isUtxoConsolidationAvailableUseCase: IsUtxoConsolidationAvailableUseCase,
private val listenToQrScanningUseCase: ListenToQrScanningUseCase,
private val getCurrencyStatusUpdatesUseCase: GetCurrencyStatusUpdatesUseCase,
private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase,
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val getCardInfoUseCase: GetCardInfoUseCase,
private val parseQrCodeUseCase: ParseQrCodeUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val sendDestinationAlertFactory: SendDestinationAlertFactory,
) : Model(), SendDestinationClickIntents {
private val params: SendDestinationComponent.Params = paramsContainer.require()
private val params: SendDestinationComponentParams = paramsContainer.require()
private val _uiState = MutableStateFlow(params.state)
val uiState = _uiState.asStateFlow()
private val analyticsCategoryName = params.analyticsCategoryName
private val userWallet = params.userWallet
private val cryptoCurrencyStatus = params.cryptoCurrencyStatus
private val cryptoCurrency = cryptoCurrencyStatus.currency
private val cryptoCurrency = params.cryptoCurrency
private var cryptoCurrencyStatus by Delegates.notNull<CryptoCurrencyStatus>()
private var validationJobHolder = JobHolder()
init {
configDestinationNavigation()
initialState()
getWalletsAndRecent()
subscribeOnCurrencyStatusUpdates()
subscribeOnQRScannerResult()
}
private fun initialState() {
if (uiState.value is DestinationUM.Empty) {
if ((uiState.value as? DestinationUM.Content)?.isInitialized == false) {
_uiState.update(
SendDestinationInitialStateTransformer(
cryptoCurrencyStatus = cryptoCurrencyStatus,
onAddressChange = ::onRecipientAddressValueChange,
onMemoChange = ::onRecipientMemoValueChange,
cryptoCurrency = cryptoCurrency,
isInitialized = true,
),
)
val params = params as? SendDestinationComponentParams.DestinationBlockParams
if (params?.predefinedAddressValue != null && params.predefinedMemoValue != null) {
_uiState.update(
SendDestinationPredefinedStateTransformer(
address = params.predefinedAddressValue,
memo = params.predefinedMemoValue,
),
)
}
}
}
fun updateState(state: DestinationUM) {
if (state !is DestinationUM.Empty) {
_uiState.value = state
fun updateState(destinationUM: DestinationUM) {
if (destinationUM is DestinationUM.Content && destinationUM.isInitialized) {
_uiState.value = destinationUM
}
}
@ -124,6 +157,69 @@ internal class SendDestinationModel @Inject constructor(
)
}
private fun subscribeOnCurrencyStatusUpdates() {
val isSingleWalletWithToken = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken()
val isMultiCurrency = userWallet.isMultiCurrency
getCurrenciesStatusUpdates(
isSingleWalletWithToken = isSingleWalletWithToken,
isMultiCurrency = isMultiCurrency,
)
}
private fun getCurrenciesStatusUpdates(isSingleWalletWithToken: Boolean, isMultiCurrency: Boolean) {
getCurrencyStatus(
isSingleWalletWithToken = isSingleWalletWithToken,
isMultiCurrency = isMultiCurrency,
).onEach { maybeCryptoCurrency ->
maybeCryptoCurrency.fold(
ifRight = { cryptoStatus ->
cryptoCurrencyStatus = cryptoStatus
getWalletsAndRecent()
},
ifLeft = {
sendDestinationAlertFactory.getGenericErrorState {
onFailedTxEmailClick(it.toString())
}
},
)
}.launchIn(modelScope)
}
private fun getCurrencyStatus(
isSingleWalletWithToken: Boolean,
isMultiCurrency: Boolean,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
return if (isMultiCurrency) {
getCurrencyStatusUpdatesUseCase(
userWalletId = userWallet.walletId,
currencyId = cryptoCurrency.id,
isSingleWalletWithTokens = isSingleWalletWithToken,
)
} else {
getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = userWallet.walletId)
}
}
private fun onFailedTxEmailClick(errorMessage: String? = null) {
saveBlockchainErrorUseCase(
error = BlockchainErrorInfo(
errorMessage = errorMessage.orEmpty(),
blockchainId = cryptoCurrency.network.id.value,
derivationPath = cryptoCurrency.network.derivationPath.value,
destinationAddress = "",
tokenSymbol = "",
amount = "",
fee = "",
),
)
val cardInfo = getCardInfoUseCase(userWallet.scanResponse).getOrNull() ?: return
modelScope.launch {
sendFeedbackEmailUseCase(type = FeedbackEmailType.TransactionSendingProblem(cardInfo = cardInfo))
}
}
private fun subscribeOnQRScannerResult() {
listenToQrScanningUseCase(SourceType.SEND)
.getOrElse { emptyFlow() }
@ -146,13 +242,15 @@ internal class SendDestinationModel @Inject constructor(
private fun getWalletsAndRecent() {
combine(
flow = getWalletsUseCase().conflate().map {
it.toAvailableWallets()
waitForDelay(RECENT_LOAD_DELAY) { it.toAvailableWallets() }
},
flow2 = getFixedTxHistoryItemsUseCase(
userWalletId = userWallet.walletId,
currency = cryptoCurrency,
pageSize = RECENT_TX_SIZE,
).getOrElse { flowOf(emptyList()) }.conflate(),
).getOrElse { flowOf(emptyList()) }.map {
waitForDelay(RECENT_LOAD_DELAY) { it }
}.conflate(),
) { destinationWalletList, txHistoryList ->
val isUtxoConsolidationAvailable = isUtxoConsolidationAvailableUseCase.invokeSync(
userWalletId = userWallet.walletId,
@ -240,12 +338,13 @@ internal class SendDestinationModel @Inject constructor(
}
private fun saveResult() {
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
params.callback.onDestinationResult(uiState.value)
}
private fun onNextClick() {
saveResult()
if (params.isEditMode) {
if ((params as? SendDestinationComponentParams.DestinationParams)?.isEditMode == true) {
router.pop()
} else {
router.push(SendRoute.Amount(isEditMode = false))
@ -253,6 +352,7 @@ internal class SendDestinationModel @Inject constructor(
}
private fun configDestinationNavigation() {
val params = params as? SendDestinationComponentParams.DestinationParams ?: return
combine(
flow = uiState,
flow2 = params.currentRoute,
@ -301,5 +401,6 @@ internal class SendDestinationModel @Inject constructor(
private companion object {
const val RECENT_TX_SIZE = 100
const val RECENT_LOAD_DELAY = 500L
}
}

View file

@ -4,30 +4,28 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
import com.tangem.features.send.v2.impl.R
import com.tangem.features.send.v2.subcomponents.destination.analytics.EnterAddressSource
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationTextFieldUM
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM
import com.tangem.utils.transformer.Transformer
internal class SendDestinationInitialStateTransformer(
val cryptoCurrencyStatus: CryptoCurrencyStatus,
val onAddressChange: (String, EnterAddressSource) -> Unit,
val onMemoChange: (String, Boolean) -> Unit,
val cryptoCurrency: CryptoCurrency,
val isInitialized: Boolean = false,
) : Transformer<DestinationUM> {
override fun transform(prevState: DestinationUM): DestinationUM {
val memoType = when (cryptoCurrencyStatus.currency.network.transactionExtrasType) {
val memoType = when (cryptoCurrency.network.transactionExtrasType) {
Network.TransactionExtrasType.NONE -> null
Network.TransactionExtrasType.MEMO -> R.string.send_extras_hint_memo
Network.TransactionExtrasType.DESTINATION_TAG -> R.string.send_destination_tag_field
}
return DestinationUM.Content(
isPrimaryButtonEnabled = false,
isInitialized = isInitialized,
addressTextField = DestinationTextFieldUM.RecipientAddress(
value = "",
onValueChange = { onAddressChange(it, EnterAddressSource.InputField) },
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Next,
keyboardType = KeyboardType.Text,
@ -40,7 +38,6 @@ internal class SendDestinationInitialStateTransformer(
memoTextField = memoType?.let {
DestinationTextFieldUM.RecipientMemo(
value = "",
onValueChange = { onMemoChange(it, false) },
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
keyboardType = KeyboardType.Text,
@ -55,7 +52,7 @@ internal class SendDestinationInitialStateTransformer(
},
wallets = loadingListState(WALLET_KEY_TAG, WALLET_DEFAULT_COUNT),
recent = loadingListState(RECENT_KEY_TAG, RECENT_DEFAULT_COUNT),
networkName = cryptoCurrencyStatus.currency.network.name,
networkName = cryptoCurrency.network.name,
isValidating = false,
)
}

View file

@ -0,0 +1,93 @@
package com.tangem.features.send.v2.subcomponents.destination.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import com.tangem.core.ui.components.icons.identicon.IdentIcon
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationTextFieldUM
import com.tangem.features.send.v2.subcomponents.destination.ui.state.DestinationUM
@Composable
internal fun DestinationBlock(
destinationUM: DestinationUM,
isClickDisabled: Boolean,
isEditingDisabled: Boolean,
onClick: () -> Unit,
) {
val destinationUM = destinationUM as? DestinationUM.Content ?: return
val backgroundColor = if (isEditingDisabled) {
TangemTheme.colors.button.disabled
} else {
TangemTheme.colors.background.action
}
Column(
modifier = Modifier
.fillMaxWidth()
.clip(TangemTheme.shapes.roundedCornersXMedium)
.background(backgroundColor)
.clickable(enabled = !isClickDisabled && !isEditingDisabled, onClick = onClick)
.padding(TangemTheme.dimens.spacing12),
) {
AddressBlock(destinationUM.addressTextField)
MemoBlock(destinationUM.memoTextField)
}
}
@Composable
private fun AddressBlock(address: DestinationTextFieldUM.RecipientAddress) {
Text(
text = address.label.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.secondary,
)
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
) {
IdentIcon(
address = address.value,
modifier = Modifier
.size(TangemTheme.dimens.size36)
.clip(RoundedCornerShape(TangemTheme.dimens.radius18))
.background(TangemTheme.colors.background.tertiary),
)
Text(
text = address.value,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
)
}
}
@Composable
private fun MemoBlock(memo: DestinationTextFieldUM.RecipientMemo?) {
if (memo != null && memo.value.isNotBlank()) {
HorizontalDivider(
color = TangemTheme.colors.icon.inactive,
modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12),
)
Text(
text = memo.label.resolveReference(),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.secondary,
)
Text(
text = memo.value,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.primary1,
modifier = Modifier.padding(top = TangemTheme.dimens.spacing8),
)
}
}