Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-03 13:42:07 +03:00
commit d994b7840a
150 changed files with 971 additions and 4621 deletions

View file

@ -21,9 +21,7 @@ internal class PreviewDetailsComponent : DetailsComponent {
ItemsBuilder(
router = DummyRouter(),
hotWalletFeatureToggles = object : HotWalletFeatureToggles {
override val isHotWalletEnabled: Boolean = true
override val isWalletCreationRestrictionEnabled: Boolean = true
override val isHotWalletVisible: Boolean = true
},
).buildAll(
isWalletConnectAvailable = true,

View file

@ -3,7 +3,6 @@ package com.tangem.features.details.model
import android.content.res.Resources
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.AppInstanceIdProvider
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
@ -34,7 +33,6 @@ import com.tangem.features.details.entity.DetailsUM
import com.tangem.features.details.entity.SelectEmailFeedbackTypeBS
import com.tangem.features.details.utils.ItemsBuilder
import com.tangem.features.details.utils.SocialsBuilder
import com.tangem.features.hotwallet.HotWalletFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.version.AppVersionProvider
import kotlinx.collections.immutable.ImmutableList
@ -59,7 +57,6 @@ internal class DetailsModel @Inject constructor(
private val checkIsWalletConnectAvailableUseCase: CheckIsWalletConnectAvailableUseCase,
private val router: Router,
private val urlOpener: UrlOpener,
private val appInstanceIdProvider: AppInstanceIdProvider,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val appStateHolder: ReduxStateHolder,
private val getWalletMetaInfoUseCase: GetWalletMetaInfoUseCase,
@ -67,7 +64,6 @@ internal class DetailsModel @Inject constructor(
private val getWalletsUseCase: GetWalletsUseCase,
override val dispatchers: CoroutineDispatcherProvider,
private val generateBuyTangemCardLinkUseCase: GenerateBuyTangemCardLinkUseCase,
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
private val analyticsEventHandler: AnalyticsEventHandler,
private val tangemPayEligibilityManager: TangemPayEligibilityManager,
) : Model() {
@ -229,14 +225,10 @@ internal class DetailsModel @Inject constructor(
private fun onBuyClick() {
modelScope.launch {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Settings))
generateBuyTangemCardLinkUseCase
.invoke(GenerateBuyTangemCardLinkUseCase.Source.Settings).let { urlOpener.openUrl(it) }
} else {
// This is incorrect implementation of buy link generation, but it is left here
urlOpener.openUrl(buildBuyLink())
}
analyticsEventHandler.send(Basic.ButtonBuy(source = AnalyticsParam.ScreensSources.Settings))
val url = generateBuyTangemCardLinkUseCase(GenerateBuyTangemCardLinkUseCase.Source.Settings)
urlOpener.openUrl(url)
}
}
@ -270,12 +262,6 @@ internal class DetailsModel @Inject constructor(
private fun getAppVersion(): String = "${appVersionProvider.versionName} (${appVersionProvider.versionCode})"
private suspend fun buildBuyLink(): String {
return appInstanceIdProvider.getAppInstanceId()?.let {
"$BUY_TANGEM_URL&app_instance_id=$it"
} ?: BUY_TANGEM_URL
}
private companion object {
val SYSTEM_LANGUAGE = runCatching { Resources.getSystem().configuration.locales[0].language }.getOrElse { "" }
val APP_LANGUAGE = Locale.getDefault().language
@ -283,7 +269,5 @@ internal class DetailsModel @Inject constructor(
"&utm_medium=app" +
"&utm_campaign=users-$SYSTEM_LANGUAGE" +
"&utm_content=devicelang-$APP_LANGUAGE"
val BUY_TANGEM_URL = "https://buy.tangem.com/?$UTM_MARKS"
}
}

View file

@ -13,7 +13,6 @@ import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsUseCase
import com.tangem.domain.wallets.usecase.UnlockWalletUseCase
import com.tangem.features.details.entity.UserWalletListUM
import com.tangem.features.details.impl.R
@ -32,7 +31,6 @@ import javax.inject.Inject
@ModelScoped
internal class UserWalletListModel @Inject constructor(
userWalletsFetcherFactory: UserWalletsFetcher.Factory,
shouldSaveUserWalletsUseCase: ShouldSaveUserWalletsUseCase,
private val router: Router,
private val messageSender: UiMessageSender,
override val dispatchers: CoroutineDispatcherProvider,
@ -47,7 +45,7 @@ internal class UserWalletListModel @Inject constructor(
messageSender = messageSender,
onlyMultiCurrency = false,
isAuthMode = false,
isClickableIfLocked = hotWalletFeatureToggles.isHotWalletEnabled,
isClickableIfLocked = true,
onWalletClick = ::onWalletClicked,
)
@ -67,67 +65,48 @@ internal class UserWalletListModel @Inject constructor(
combine(
flow = userWalletsFlow,
flow2 = shouldSaveUserWalletsUseCase(),
flow3 = isWalletSavingInProgress,
) { userWallets, shouldSaveUserWallets, isWalletSavingInProgress ->
updateState(userWallets, shouldSaveUserWallets, isWalletSavingInProgress)
flow2 = isWalletSavingInProgress,
) { userWallets, isWalletSavingInProgress ->
updateState(userWallets, isWalletSavingInProgress)
}.collect()
}
}
private fun updateState(
userWallets: ImmutableList<UserWalletItemUM>,
shouldSaveUserWallets: Boolean,
isWalletSavingInProgress: Boolean,
) = state.update { value ->
value.copy(
userWallets = userWallets,
isWalletSavingInProgress = isWalletSavingInProgress,
addNewWalletText = when {
shouldSaveUserWallets || hotWalletFeatureToggles.isHotWalletEnabled -> {
resourceReference(R.string.user_wallet_list_add_button)
}
else -> resourceReference(R.string.scan_card_settings_button)
},
)
}
private fun updateState(userWallets: ImmutableList<UserWalletItemUM>, isWalletSavingInProgress: Boolean) =
state.update { value ->
value.copy(
userWallets = userWallets,
isWalletSavingInProgress = isWalletSavingInProgress,
addNewWalletText = resourceReference(R.string.user_wallet_list_add_button),
)
}
private fun onAddNewWalletClick() {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.Settings))
analyticsEventHandler.send(SignIn.ButtonAddWallet(AnalyticsParam.ScreensSources.Settings))
if (hotWalletFeatureToggles.isWalletCreationRestrictionEnabled) {
withProgress(isWalletSavingInProgress) {
userWalletSaver.scanAndSaveUserWallet(modelScope)
}
} else {
router.push(AppRoute.CreateWalletSelection)
}
} else {
if (hotWalletFeatureToggles.isWalletCreationRestrictionEnabled) {
withProgress(isWalletSavingInProgress) {
userWalletSaver.scanAndSaveUserWallet(modelScope)
}
} else {
router.push(AppRoute.CreateWalletSelection)
}
}
private fun onWalletClicked(userWalletId: UserWalletId) {
if (hotWalletFeatureToggles.isHotWalletEnabled) {
modelScope.launch {
unlockWalletUseCase(userWalletId)
.onRight { router.push(AppRoute.WalletSettings(userWalletId)) }
.onLeft { error ->
Timber.e("Failed to unlock wallet $userWalletId: $error")
error.handle(
onUserCancelled = {},
isFromUnlockAll = false,
onAlreadyUnlocked = { router.push(AppRoute.WalletSettings(userWalletId)) },
analyticsEventHandler = analyticsEventHandler,
showMessage = messageSender::send,
)
}
}
} else {
router.push(AppRoute.WalletSettings(userWalletId))
modelScope.launch {
unlockWalletUseCase(userWalletId)
.onRight { router.push(AppRoute.WalletSettings(userWalletId)) }
.onLeft { error ->
Timber.e("Failed to unlock wallet $userWalletId: $error")
error.handle(
onUserCancelled = {},
isFromUnlockAll = false,
onAlreadyUnlocked = { router.push(AppRoute.WalletSettings(userWalletId)) },
analyticsEventHandler = analyticsEventHandler,
showMessage = messageSender::send,
)
}
}
}
}

View file

@ -24,7 +24,6 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase
import com.tangem.features.details.impl.R
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
@ -38,7 +37,6 @@ internal class UserWalletSaver @Inject constructor(
private val scanCardProcessor: ScanCardProcessor,
private val saveWalletUseCase: SaveWalletUseCase,
private val coldUserWalletBuilderFactory: ColdUserWalletBuilder.Factory,
private val shouldSaveUserWalletsSyncUseCase: ShouldSaveUserWalletsSyncUseCase,
private val reduxStateHolder: ReduxStateHolder,
private val messageSender: UiMessageSender,
private val router: Router,
@ -84,11 +82,7 @@ internal class UserWalletSaver @Inject constructor(
recover = { error ->
when (error) {
is SaveWalletError.WalletAlreadySaved -> {
if (shouldSaveUserWalletsSyncUseCase()) {
selectUserWallet()
} else {
router.popTo<AppRoute.Wallet>()
}
selectUserWallet()
}
is SaveWalletError.DataError -> {
val messageRef = ensureNotNull(error.messageId?.let(::resourceReference)) {
@ -116,7 +110,7 @@ internal class UserWalletSaver @Inject constructor(
)
}
private suspend fun Raise<Error>.createUserWallet(response: ScanResponse): UserWallet {
private fun Raise<Error>.createUserWallet(response: ScanResponse): UserWallet {
val userWallet = coldUserWalletBuilderFactory.create(scanResponse = response).build()
return ensureNotNull(userWallet) { Error.Unknown }