diff --git a/Gemfile.lock b/Gemfile.lock index b4221a320d..535cdb9a32 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -217,4 +217,4 @@ DEPENDENCIES fastlane-plugin-firebase_app_distribution BUNDLED WITH - 1.17.2 + 2.5.23 diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 25efafca87..90f319d272 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -34,6 +34,7 @@ dependencies { implementation(projects.domain.appCurrency.models) implementation(projects.domain.balanceHiding) implementation(projects.domain.balanceHiding.models) + implementation(projects.domain.demo) implementation(projects.domain.legacy) implementation(projects.domain.models) implementation(projects.domain.onramp) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt index 5829d1f651..85c14d6e3d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/DefaultOnrampOperationComponent.kt @@ -2,39 +2,26 @@ package com.tangem.features.onramp.selecttoken import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import arrow.core.getOrElse -import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child -import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase -import com.tangem.domain.appcurrency.model.AppCurrency -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.core.decompose.model.getOrCreateModel +import com.tangem.features.onramp.selecttoken.model.OnrampOperationModel import com.tangem.features.onramp.selecttoken.ui.OnrampSelectToken import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent import com.tangem.features.onramp.tokenlist.entity.OnrampOperation import dagger.assisted.Assisted import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject -import kotlinx.coroutines.launch -// TODO: Add OnrampOperationModel -@Suppress("LongParameterList") internal class DefaultOnrampOperationComponent @AssistedInject constructor( @Assisted appComponentContext: AppComponentContext, onrampTokenListComponentFactory: OnrampTokenListComponent.Factory, @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 { + private val model: OnrampOperationModel = getOrCreateModel(params) + private val onrampTokenListComponent: OnrampTokenListComponent = onrampTokenListComponentFactory.create( context = child(key = "token_list"), params = OnrampTokenListComponent.Params( @@ -44,78 +31,21 @@ internal class DefaultOnrampOperationComponent @AssistedInject constructor( }, hasSearchBar = true, 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 override fun Content(modifier: Modifier) { + val state = model.state.collectAsStateWithLifecycle() + OnrampSelectToken( - titleResId = when (params) { - is OnrampOperationComponent.Params.Buy -> R.string.common_buy - is OnrampOperationComponent.Params.Sell -> R.string.common_sell - }, - onBackClick = ::onBackClick, + state = state.value, onrampTokenListComponent = onrampTokenListComponent, 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 interface Factory : OnrampOperationComponent.Factory { diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampOperationModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampOperationModelModule.kt new file mode 100644 index 0000000000..9c9eb054d2 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/di/OnrampOperationModelModule.kt @@ -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 +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/entity/OnrampOperationUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/entity/OnrampOperationUM.kt new file mode 100644 index 0000000000..fb87ad8d17 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/entity/OnrampOperationUM.kt @@ -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, +) \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt new file mode 100644 index 0000000000..cb2f24483a --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/model/OnrampOperationModel.kt @@ -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 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() + } + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt index 8d66dfa574..174a8d1ba2 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selecttoken/ui/OnrampSelectToken.kt @@ -1,7 +1,6 @@ package com.tangem.features.onramp.selecttoken.ui import androidx.activity.compose.BackHandler -import androidx.annotation.StringRes import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background 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.res.TangemTheme import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.selecttoken.entity.OnrampOperationUM import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent @OptIn(ExperimentalFoundationApi::class) @Composable internal fun OnrampSelectToken( - @StringRes titleResId: Int, - onBackClick: () -> Unit, + state: OnrampOperationUM, onrampTokenListComponent: OnrampTokenListComponent, modifier: Modifier = Modifier, ) { - BackHandler(onBack = onBackClick) + BackHandler(onBack = state.onBackClick) LazyColumn( modifier = modifier @@ -35,8 +34,8 @@ internal fun OnrampSelectToken( ) { stickyHeader(key = "header") { AppBarWithBackButton( - onBackClick = onBackClick, - text = stringResource(id = titleResId), + onBackClick = state.onBackClick, + text = stringResource(id = state.titleResId), iconRes = R.drawable.ic_close_24, containerColor = TangemTheme.colors.background.secondary, )