Updated on 2026-08-14

This commit is contained in:
Tangem 2025-06-03 12:25:53 +03:00
commit 7c629b68d1
1179 changed files with 24718 additions and 16703 deletions

View file

@ -2,8 +2,8 @@ package com.tangem.features.onramp.confirmresidency
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent {

View file

@ -0,0 +1,49 @@
package com.tangem.features.onramp.deeplink
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent
import com.tangem.domain.wallets.models.isMultiCurrency
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.features.onramp.OnrampFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
internal class DefaultBuyDeepLinkHandler @AssistedInject constructor(
@Assisted scope: CoroutineScope,
onrampFeatureToggles: OnrampFeatureToggles,
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase,
analyticsEventHandler: AnalyticsEventHandler,
) : BuyDeepLinkHandler {
init {
// It is okay here, we are navigating from outside, and there is no other way to getting UserWallet
getSelectedWalletSyncUseCase().fold(
ifLeft = {
Timber.e("Error on getting user wallet: $it")
},
ifRight = { userWallet ->
if (!onrampFeatureToggles.isFeatureEnabled && !userWallet.isMultiCurrency) {
scope.launch {
val cryptoCurrency = getCryptoCurrencyUseCase(userWallet.walletId).getOrElse {
Timber.e("Error on getting cryptoCurrency: $it")
return@launch
}
analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol))
}
}
},
)
}
@AssistedFactory
interface Factory : BuyDeepLinkHandler.Factory {
override fun create(coroutineScope: CoroutineScope): DefaultBuyDeepLinkHandler
}
}

View file

@ -0,0 +1,61 @@
package com.tangem.features.onramp.deeplink
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.features.onramp.success.OnrampSuccessScreenTrigger
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import timber.log.Timber
internal class DefaultOnrampDeepLinkHandler @AssistedInject constructor(
@Assisted scope: CoroutineScope,
@Assisted queryParams: Map<String, String>,
appRouter: AppRouter,
onrampSuccessScreenTrigger: OnrampSuccessScreenTrigger,
) : OnrampDeepLinkHandler {
init {
val txId = queryParams[TX_ID_KEY]
val result = OnrampRedirectResult.getResult(queryParams[RESULT_KEY])
when {
!txId.isNullOrEmpty() -> {
// finish current onramp flow and show onramp success screen
val replaceOnrampScreens = appRouter.stack
.filterNot { it is AppRoute.Onramp || it is AppRoute.OnrampSuccess }
.toMutableList() + AppRoute.OnrampSuccess(txId)
appRouter.replaceAll(*replaceOnrampScreens.toTypedArray())
}
result != OnrampRedirectResult.Unknown -> {
scope.launch {
onrampSuccessScreenTrigger.triggerOnrampSuccess(result == OnrampRedirectResult.Success)
}
}
else -> {
Timber.e(
"""
Invalid parameters for ONRAMP deeplink
|- Params: $queryParams
""".trimIndent(),
)
}
}
}
@AssistedFactory
interface Factory : OnrampDeepLinkHandler.Factory {
override fun create(
coroutineScope: CoroutineScope,
queryParams: Map<String, String>,
): DefaultOnrampDeepLinkHandler
}
private companion object {
const val TX_ID_KEY = "tx_id"
const val RESULT_KEY = "result"
}
}

View file

@ -1,7 +1,6 @@
package com.tangem.features.onramp.deeplink.di
import com.tangem.features.onramp.deeplink.DefaultOnrampDeepLink
import com.tangem.features.onramp.deeplink.OnrampDeepLink
import com.tangem.features.onramp.deeplink.*
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@ -15,4 +14,12 @@ internal interface OnrampDeeplinkModule {
@Binds
@Singleton
fun bindFactory(impl: DefaultOnrampDeepLink.Factory): OnrampDeepLink.Factory
@Binds
@Singleton
fun bindOnrampDeepLinkHandlerFactory(impl: DefaultOnrampDeepLinkHandler.Factory): OnrampDeepLinkHandler.Factory
@Binds
@Singleton
fun bindBuyDeepLinkHandler(impl: DefaultBuyDeepLinkHandler.Factory): BuyDeepLinkHandler.Factory
}

View file

@ -12,10 +12,10 @@ import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.GetHotCryptoUseCase
import com.tangem.domain.onramp.model.HotCryptoCurrency
import com.tangem.domain.tokens.GetCryptoCurrencyStatusSyncUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.features.onramp.hottokens.HotCryptoComponent
import com.tangem.features.onramp.hottokens.converter.HotTokenItemStateConverter
import com.tangem.features.onramp.hottokens.entity.HotCryptoUM
@ -45,7 +45,7 @@ internal class HotCryptoModel @Inject constructor(
paramsContainer: ParamsContainer,
getHotCryptoUseCase: GetHotCryptoUseCase,
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
@ -103,7 +103,10 @@ internal class HotCryptoModel @Inject constructor(
private fun onSuccessAdding(id: CryptoCurrency.ID) {
modelScope.launch {
getCryptoCurrencyStatusSyncUseCase(userWalletId = params.userWalletId, cryptoCurrencyId = id)
getSingleCryptoCurrencyStatusUseCase.invokeMultiWalletSync(
userWalletId = params.userWalletId,
cryptoCurrencyId = id,
)
.onRight {
bottomSheetNavigation.dismiss()
params.onTokenClick(it)

View file

@ -3,7 +3,7 @@ package com.tangem.features.onramp.hottokens.portfolio
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
/**

View file

@ -1,7 +1,7 @@
package com.tangem.features.onramp.hottokens.portfolio.entity
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
internal sealed class OnrampAddToPortfolioBSConfig {

View file

@ -2,9 +2,9 @@ package com.tangem.features.onramp.main
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface OnrampMainComponent : ComposableContentComponent {

View file

@ -8,11 +8,11 @@ import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.common.ui.notifications.NotificationUM
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.tokens.model.AmountType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.convertToAmount
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.main.entity.*

View file

@ -8,13 +8,13 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
import com.tangem.domain.onramp.model.OnrampQuote
import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.tokens.model.AmountType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.main.entity.*
import com.tangem.features.onramp.providers.entity.SelectProviderResult

View file

@ -23,6 +23,7 @@ import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onramp.main.OnrampMainComponent
import com.tangem.features.onramp.main.entity.*
@ -233,7 +234,7 @@ internal class OnrampMainComponentModel @Inject constructor(
}
override fun onBuyClick(quote: OnrampProviderWithQuote.Data) {
if (isDemoCardUseCase.invoke(userWallet.cardId)) {
if (userWallet is UserWallet.Cold && isDemoCardUseCase.invoke(userWallet.cardId)) {
showDemoWarning()
} else {
val currentContentState = state.value as? OnrampMainComponentUM.Content ?: return

View file

@ -8,12 +8,12 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.ClickableText
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.extensions.appendColored
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.main.entity.OnrampMainComponentUM
@ -35,7 +35,7 @@ internal fun OnrampButtonComponent(state: OnrampMainComponentUM) {
OnrampTosText(providerState)
PrimaryButton(
modifier = Modifier.fillMaxWidth(),
text = stringResource(id = R.string.common_buy),
text = stringResourceSafe(id = R.string.common_buy),
onClick = state.buyButtonConfig.onClick,
enabled = state.buyButtonConfig.enabled,
)
@ -44,9 +44,9 @@ internal fun OnrampButtonComponent(state: OnrampMainComponentUM) {
@Composable
private fun OnrampTosText(provider: OnrampProviderBlockUM.Content?) {
val termsOfUse = stringResource(R.string.common_terms_of_use)
val privacyPolicy = stringResource(R.string.common_privacy_policy)
val tosText = stringResource(R.string.onramp_legal, termsOfUse, privacyPolicy)
val termsOfUse = stringResourceSafe(R.string.common_terms_of_use)
val privacyPolicy = stringResourceSafe(R.string.common_privacy_policy)
val tosText = stringResourceSafe(R.string.onramp_legal, termsOfUse, privacyPolicy)
val clickableAnnotation = buildAnnotatedString {
append(tosText.substringBefore(termsOfUse))

View file

@ -2,8 +2,8 @@ package com.tangem.features.onramp.providers
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampPaymentMethod
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.onramp.providers.entity.SelectProviderResult
internal interface SelectProviderComponent : ComposableBottomSheetComponent {

View file

@ -2,8 +2,8 @@ package com.tangem.features.onramp.redirect
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface OnrampRedirectComponent : ComposableContentComponent {

View file

@ -1,7 +1,7 @@
package com.tangem.features.onramp.root.entity
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
import com.tangem.domain.tokens.model.CryptoCurrency
import kotlinx.serialization.Serializable
@Serializable

View file

@ -2,7 +2,7 @@ package com.tangem.features.onramp.selectcountry
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface SelectCountryComponent : ComposableBottomSheetComponent {

View file

@ -2,7 +2,7 @@ package com.tangem.features.onramp.selectcurrency
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWallet
internal interface SelectCurrencyComponent : ComposableBottomSheetComponent {

View file

@ -21,6 +21,7 @@ import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.features.onramp.impl.R
@ -55,7 +56,9 @@ internal class OnrampOperationModel @Inject constructor(
private val _state = MutableStateFlow(value = getInitialState())
private val selectedUserWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
private val selectedUserWallet = getWalletsUseCase.invokeSync()
.first { it.walletId == params.userWalletId }
.requireColdWallet()
init {
analyticsEventHandler.send(

View file

@ -2,7 +2,7 @@ package com.tangem.features.onramp.settings
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.wallets.models.UserWalletId
internal interface OnrampSettingsComponent : ComposableContentComponent {

View file

@ -12,9 +12,9 @@ import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.format.bigdecimal.crypto
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.model.OnrampStatus
import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM
import com.tangem.utils.converter.Converter

View file

@ -11,6 +11,7 @@ import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.onramp.GetOnrampStatusUseCase
import com.tangem.domain.onramp.GetOnrampTransactionUseCase
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
@ -19,7 +20,6 @@ import com.tangem.domain.onramp.model.OnrampStatus
import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.domain.onramp.model.error.OnrampError
import com.tangem.domain.tokens.GetCryptoCurrencyUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
@ -181,7 +181,9 @@ internal class OnrampSuccessComponentModel @Inject constructor(
} else {
resourceReference(R.string.express_error_code, wrappedList(errorCode))
},
onDismissRequest = router::pop,
firstActionBuilder = {
okAction(router::pop)
},
)
messageSender.send(message)

View file

@ -15,8 +15,8 @@ import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.core.utils.lceContent
import com.tangem.domain.core.utils.lceError
import com.tangem.domain.core.utils.lceLoading
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.wallets.usecase.GetWalletsUseCase

View file

@ -20,6 +20,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.TokenList
import com.tangem.domain.tokens.model.TotalFiatBalance
import com.tangem.domain.wallets.models.requireColdWallet
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.tokenlist.OnrampTokenListComponent
@ -59,7 +60,8 @@ internal class OnrampTokenListModel @Inject constructor(
private val params: OnrampTokenListComponent.Params = paramsContainer.require()
private val scanResponse by lazy {
getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }.scanResponse
getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId }
.requireColdWallet().scanResponse // TODO [REDACTED_TASK_KEY]
}
init {