diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt index 3683819133..d26a2ec9c3 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/DefaultSendComponent.kt @@ -39,8 +39,12 @@ internal class DefaultSendComponent @AssistedInject constructor( popCallback = { onChildBack() }, ) - private val initialRoute = SendRoute.Empty - private val currentRoute = MutableStateFlow(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(), analyticsCategoryName = SendAnalyticEvents.SEND_CATEGORY, userWallet = model.userWallet, - cryptoCurrencyStatus = model.cryptoCurrencyStatus, + cryptoCurrency = params.currency, callback = model, isEditMode = route.isEditMode, ), diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt index 52c4fd8737..28e8110f7f 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/SendConfirmComponent.kt @@ -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, diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/ui/SendConfirmContent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/ui/SendConfirmContent.kt index 9b602a5c66..1339378697 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/ui/SendConfirmContent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/send/confirm/ui/SendConfirmContent.kt @@ -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) } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationAlertFactory.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationAlertFactory.kt new file mode 100644 index 0000000000..ed46533724 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationAlertFactory.kt @@ -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, + ), + ), + ) + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationBlockComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationBlockComponent.kt new file mode 100644 index 0000000000..ff98000eed --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationBlockComponent.kt @@ -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, + ) + } +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponent.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponent.kt index e7a6b44743..02ec170b2d 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponent.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponent.kt @@ -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, - val isEditMode: Boolean, - ) interface ModelCallback : SendNavigationModelCallback { fun onDestinationResult(destinationUM: DestinationUM) diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponentParams.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponentParams.kt new file mode 100644 index 0000000000..4dc45b2f96 --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/SendDestinationComponentParams.kt @@ -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, + val currentRoute: Flow, + 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, + val predefinedAddressValue: String?, + val predefinedMemoValue: String?, + val isPredefinedValues: Boolean, + ) : SendDestinationComponentParams() +} \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt index 199c1d86ce..b4d297cca8 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/SendDestinationModel.kt @@ -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() 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> { + 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 } } \ No newline at end of file diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt index fdd0e7f59e..8a1be0fbc2 100644 --- a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/model/transformers/SendDestinationInitialStateTransformer.kt @@ -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 { 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, ) } diff --git a/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt new file mode 100644 index 0000000000..649422d27c --- /dev/null +++ b/features/send-v2/impl/src/main/java/com/tangem/features/send/v2/subcomponents/destination/ui/DestinationBlock.kt @@ -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), + ) + } +} \ No newline at end of file