Updated on 2026-08-14

This commit is contained in:
Tangem 2024-12-02 14:16:01 +04:00
parent f2bf06648f
commit 537f15517e
7 changed files with 201 additions and 86 deletions

View file

@ -217,4 +217,4 @@ DEPENDENCIES
fastlane-plugin-firebase_app_distribution fastlane-plugin-firebase_app_distribution
BUNDLED WITH BUNDLED WITH
1.17.2 2.5.23

View file

@ -34,6 +34,7 @@ dependencies {
implementation(projects.domain.appCurrency.models) implementation(projects.domain.appCurrency.models)
implementation(projects.domain.balanceHiding) implementation(projects.domain.balanceHiding)
implementation(projects.domain.balanceHiding.models) implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.demo)
implementation(projects.domain.legacy) implementation(projects.domain.legacy)
implementation(projects.domain.models) implementation(projects.domain.models)
implementation(projects.domain.onramp) implementation(projects.domain.onramp)

View file

@ -2,39 +2,26 @@ package com.tangem.features.onramp.selecttoken
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import arrow.core.getOrElse import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child import com.tangem.core.decompose.context.child
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.features.onramp.selecttoken.model.OnrampOperationModel
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.selecttoken.ui.OnrampSelectToken import com.tangem.features.onramp.selecttoken.ui.OnrampSelectToken
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
import com.tangem.features.onramp.tokenlist.entity.OnrampOperation import com.tangem.features.onramp.tokenlist.entity.OnrampOperation
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import kotlinx.coroutines.launch
// TODO: Add OnrampOperationModel
@Suppress("LongParameterList")
internal class DefaultOnrampOperationComponent @AssistedInject constructor( internal class DefaultOnrampOperationComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext, @Assisted appComponentContext: AppComponentContext,
onrampTokenListComponentFactory: OnrampTokenListComponent.Factory, onrampTokenListComponentFactory: OnrampTokenListComponent.Factory,
@Assisted private val params: OnrampOperationComponent.Params, @Assisted private val params: OnrampOperationComponent.Params,
private val reduxStateHolder: ReduxStateHolder,
private val getWalletsUseCase: GetWalletsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
) : AppComponentContext by appComponentContext, OnrampOperationComponent { ) : AppComponentContext by appComponentContext, OnrampOperationComponent {
private val model: OnrampOperationModel = getOrCreateModel(params)
private val onrampTokenListComponent: OnrampTokenListComponent = onrampTokenListComponentFactory.create( private val onrampTokenListComponent: OnrampTokenListComponent = onrampTokenListComponentFactory.create(
context = child(key = "token_list"), context = child(key = "token_list"),
params = OnrampTokenListComponent.Params( params = OnrampTokenListComponent.Params(
@ -44,78 +31,21 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor(
}, },
hasSearchBar = true, hasSearchBar = true,
userWalletId = params.userWalletId, userWalletId = params.userWalletId,
onTokenClick = { _, status -> onTokenClick(status) }, onTokenClick = { _, status -> model.onTokenClick(status) },
), ),
) )
init {
analyticsEventHandler.send(
event = when (params) {
is OnrampOperationComponent.Params.Buy -> MainScreenAnalyticsEvent.BuyScreenOpened
is OnrampOperationComponent.Params.Sell -> MainScreenAnalyticsEvent.SellScreenOpened
},
)
}
@Composable @Composable
override fun Content(modifier: Modifier) { override fun Content(modifier: Modifier) {
val state = model.state.collectAsStateWithLifecycle()
OnrampSelectToken( OnrampSelectToken(
titleResId = when (params) { state = state.value,
is OnrampOperationComponent.Params.Buy -> R.string.common_buy
is OnrampOperationComponent.Params.Sell -> R.string.common_sell
},
onBackClick = ::onBackClick,
onrampTokenListComponent = onrampTokenListComponent, onrampTokenListComponent = onrampTokenListComponent,
modifier = modifier, modifier = modifier,
) )
} }
private fun onTokenClick(status: CryptoCurrencyStatus) {
val currencySymbol = status.currency.symbol
analyticsEventHandler.send(
event = when (params) {
is OnrampOperationComponent.Params.Buy -> {
MainScreenAnalyticsEvent.BuyTokenClicked(currencySymbol = currencySymbol)
}
is OnrampOperationComponent.Params.Sell -> {
MainScreenAnalyticsEvent.SellTokenClicked(currencySymbol = currencySymbol)
}
},
)
componentScope.launch {
val appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }.code
reduxStateHolder.dispatch(
action = when (params) {
is OnrampOperationComponent.Params.Buy -> getBuyAction(status, appCurrencyCode)
is OnrampOperationComponent.Params.Sell -> TradeCryptoAction.Sell(status, appCurrencyCode)
},
)
}
}
private fun getBuyAction(status: CryptoCurrencyStatus, appCurrencyCode: String): TradeCryptoAction {
return TradeCryptoAction.Buy(
userWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId },
cryptoCurrencyStatus = status,
appCurrencyCode = appCurrencyCode,
)
}
private fun onBackClick() {
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.ButtonClose(
source = when (params) {
is OnrampOperationComponent.Params.Buy -> AnalyticsParam.ScreensSources.Buy
is OnrampOperationComponent.Params.Sell -> AnalyticsParam.ScreensSources.Sell
},
),
)
router.pop()
}
@AssistedFactory @AssistedFactory
interface Factory : OnrampOperationComponent.Factory { interface Factory : OnrampOperationComponent.Factory {

View file

@ -0,0 +1,20 @@
package com.tangem.features.onramp.selecttoken.di
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.onramp.selecttoken.model.OnrampOperationModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(DecomposeComponent::class)
internal interface OnrampOperationModelModule {
@Binds
@IntoMap
@ClassKey(OnrampOperationModel::class)
fun bindOnrampOperationModel(model: OnrampOperationModel): Model
}

View file

@ -0,0 +1,8 @@
package com.tangem.features.onramp.selecttoken.entity
import androidx.annotation.StringRes
internal data class OnrampOperationUM(
@StringRes val titleResId: Int,
val onBackClick: () -> Unit,
)

View file

@ -0,0 +1,157 @@
package com.tangem.features.onramp.selecttoken.model
import androidx.compose.ui.res.stringResource
import arrow.core.getOrElse
import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.components.BasicDialog
import com.tangem.core.ui.components.DialogButtonUM
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.message.ContentMessage
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.selecttoken.OnrampOperationComponent.Params
import com.tangem.features.onramp.selecttoken.entity.OnrampOperationUM
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("LongParameterList")
@ComponentScoped
internal class OnrampOperationModel @Inject constructor(
paramsContainer: ParamsContainer,
getWalletsUseCase: GetWalletsUseCase,
override val dispatchers: CoroutineDispatcherProvider,
private val router: AppRouter,
private val analyticsEventHandler: AnalyticsEventHandler,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val reduxStateHolder: ReduxStateHolder,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val messageSender: UiMessageSender,
) : Model() {
val state: StateFlow<OnrampOperationUM> get() = _state
private val params: Params = paramsContainer.require()
private val _state = MutableStateFlow(value = getInitialState())
private val selectedUserWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
init {
analyticsEventHandler.send(
event = when (params) {
is Params.Buy -> MainScreenAnalyticsEvent.BuyScreenOpened
is Params.Sell -> MainScreenAnalyticsEvent.SellScreenOpened
},
)
}
fun onTokenClick(status: CryptoCurrencyStatus) {
val currencySymbol = status.currency.symbol
analyticsEventHandler.send(
event = when (params) {
is Params.Buy -> MainScreenAnalyticsEvent.BuyTokenClicked(currencySymbol = currencySymbol)
is Params.Sell -> MainScreenAnalyticsEvent.SellTokenClicked(currencySymbol = currencySymbol)
},
)
if (params is Params.Sell) {
showErrorIfDemoModeOrElse { selectToken(status) }
} else {
selectToken(status)
}
}
private fun selectToken(status: CryptoCurrencyStatus) {
modelScope.launch {
val appCurrencyCode = getSelectedAppCurrencyUseCase.invokeSync().getOrElse { AppCurrency.Default }.code
reduxStateHolder.dispatch(
action = when (params) {
is Params.Buy -> getBuyAction(status, appCurrencyCode)
is Params.Sell -> TradeCryptoAction.Sell(status, appCurrencyCode)
},
)
}
}
private fun getInitialState(): OnrampOperationUM {
return OnrampOperationUM(
titleResId = when (params) {
is Params.Buy -> R.string.common_buy
is Params.Sell -> R.string.common_sell
},
onBackClick = ::onBackClick,
)
}
private fun onBackClick() {
analyticsEventHandler.send(
event = MainScreenAnalyticsEvent.ButtonClose(
source = when (params) {
is Params.Buy -> AnalyticsParam.ScreensSources.Buy
is Params.Sell -> AnalyticsParam.ScreensSources.Sell
},
),
)
router.pop()
}
private fun getBuyAction(status: CryptoCurrencyStatus, appCurrencyCode: String): TradeCryptoAction {
return TradeCryptoAction.Buy(
userWallet = selectedUserWallet,
cryptoCurrencyStatus = status,
appCurrencyCode = appCurrencyCode,
)
}
private fun showErrorIfDemoModeOrElse(action: () -> Unit) {
if (isDemoCardUseCase(cardId = selectedUserWallet.cardId)) {
val alertUM = AlertDemoModeUM(onConfirmClick = {})
messageSender.send(
message = ContentMessage { onDismiss ->
val confirmButton = DialogButtonUM(
title = alertUM.confirmButtonText.resolveReference(),
onClick = {
alertUM.onConfirmClick()
onDismiss()
},
)
val dismissButton = DialogButtonUM(
title = stringResource(id = R.string.common_cancel),
onClick = onDismiss,
)
BasicDialog(
message = alertUM.message.resolveReference(),
confirmButton = confirmButton,
onDismissDialog = onDismiss,
title = alertUM.title.resolveReference(),
dismissButton = dismissButton,
)
},
)
} else {
action()
}
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.features.onramp.selecttoken.ui package com.tangem.features.onramp.selecttoken.ui
import androidx.activity.compose.BackHandler import androidx.activity.compose.BackHandler
import androidx.annotation.StringRes
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.imePadding import androidx.compose.foundation.layout.imePadding
@ -15,17 +14,17 @@ import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.appbar.AppBarWithBackButton import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.selecttoken.entity.OnrampOperationUM
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
internal fun OnrampSelectToken( internal fun OnrampSelectToken(
@StringRes titleResId: Int, state: OnrampOperationUM,
onBackClick: () -> Unit,
onrampTokenListComponent: OnrampTokenListComponent, onrampTokenListComponent: OnrampTokenListComponent,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
BackHandler(onBack = onBackClick) BackHandler(onBack = state.onBackClick)
LazyColumn( LazyColumn(
modifier = modifier modifier = modifier
@ -35,8 +34,8 @@ internal fun OnrampSelectToken(
) { ) {
stickyHeader(key = "header") { stickyHeader(key = "header") {
AppBarWithBackButton( AppBarWithBackButton(
onBackClick = onBackClick, onBackClick = state.onBackClick,
text = stringResource(id = titleResId), text = stringResource(id = state.titleResId),
iconRes = R.drawable.ic_close_24, iconRes = R.drawable.ic_close_24,
containerColor = TangemTheme.colors.background.secondary, containerColor = TangemTheme.colors.background.secondary,
) )