Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-17 14:39:32 +04:00
parent 6b75b48f1e
commit 8e6eca1b4c
23 changed files with 327 additions and 216 deletions

View file

@ -2,7 +2,6 @@ package com.tangem.tap.features.demo
import com.tangem.domain.demo.models.DemoConfig
import com.tangem.domain.models.scan.ScanResponse
import com.tangem.tap.common.redux.AppState
object DemoHelper {
val config = DemoConfig
@ -12,15 +11,4 @@ object DemoHelper {
fun isTestDemoCard(scanResponse: ScanResponse): Boolean = config.isTestDemoCardId(scanResponse.card.cardId)
fun isDemoCardId(cardId: String): Boolean = config.isDemoCardId(cardId)
fun tryHandle(appState: () -> AppState?): Boolean {
val scanResponse = getScanResponse(appState) ?: return false
if (!scanResponse.isDemoCard()) return false
return false
}
private fun getScanResponse(appState: () -> AppState?): ScanResponse? {
return appState()?.globalState?.scanResponse
}
}

View file

@ -1,10 +0,0 @@
package com.tangem.tap.features.demo
import com.tangem.domain.models.scan.CardDTO
import com.tangem.domain.models.scan.ScanResponse
/**
[REDACTED_AUTHOR]
*/
fun ScanResponse.isDemoCard(): Boolean = DemoHelper.isDemoCardId(card.cardId)
fun CardDTO.isDemoCard(): Boolean = DemoHelper.isDemoCardId(cardId)

View file

@ -7,10 +7,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.api.AccessCodeRecoveryComponent
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.model.AccessCodeRecoveryModel
import com.tangem.tap.store
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -19,6 +17,7 @@ import dagger.assisted.AssistedInject
internal class DefaultAccessCodeRecoveryComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Unit,
private val appRouter: AppRouter,
) : AccessCodeRecoveryComponent, AppComponentContext by appComponentContext {
private val model: AccessCodeRecoveryModel = getOrCreateModel()
@ -29,7 +28,7 @@ internal class DefaultAccessCodeRecoveryComponent @AssistedInject constructor(
AccessCodeRecoveryScreen(
state = state,
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
onBackClick = { appRouter.pop() },
)
}

View file

@ -10,11 +10,9 @@ import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.ui.cardsettings.coderecovery.AccessCodeRecoveryScreenState
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.isAccessCodeRecoveryEnabled
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
@ -27,6 +25,7 @@ internal class AccessCodeRecoveryModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val tangemSdkManager: TangemSdkManager,
private val cardSettingsInteractor: CardSettingsInteractor,
private val appRouter: AppRouter,
) : Model() {
private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value
@ -73,7 +72,7 @@ internal class AccessCodeRecoveryModel @Inject constructor(
)
}
store.dispatchNavigationAction(AppRouter::pop)
appRouter.pop()
}
}

View file

@ -27,13 +27,11 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.ui.cardsettings.CardInfo
import com.tangem.tap.features.details.ui.cardsettings.CardSettingsScreenState
import com.tangem.tap.features.details.ui.cardsettings.api.CardSettingsComponent
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.*
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.addIf
import com.tangem.utils.logging.TangemLogger
@ -56,6 +54,7 @@ internal class CardSettingsModel @Inject constructor(
private val settingsRepository: SettingsRepository,
private val onboardingRepository: OnboardingRepository,
private val uiMessageSender: UiMessageSender,
private val appRouter: AppRouter,
) : Model() {
private val params = paramsContainer.require<CardSettingsComponent.Params>()
@ -188,12 +187,10 @@ internal class CardSettingsModel @Inject constructor(
}
is CardInfo.SecurityMode -> {
Analytics.send(Settings.CardSettings.ButtonChangeSecurityMode())
store.dispatchNavigationAction {
push(route = AppRoute.DetailsSecurity(userWalletId))
}
appRouter.push(route = AppRoute.DetailsSecurity(userWalletId))
}
is CardInfo.AccessCodeRecovery -> {
store.dispatchNavigationAction { push(AppRoute.AccessCodeRecovery) }
appRouter.push(AppRoute.AccessCodeRecovery)
}
else -> {}
}
@ -205,30 +202,26 @@ internal class CardSettingsModel @Inject constructor(
}
if (scanResponse.cardTypesResolver.isTangemTwins()) {
store.dispatchNavigationAction {
push(
AppRoute.Onboarding(
scanResponse = scanResponse,
mode = AppRoute.Onboarding.Mode.RecreateWalletTwin,
),
)
}
appRouter.push(
AppRoute.Onboarding(
scanResponse = scanResponse,
mode = AppRoute.Onboarding.Mode.RecreateWalletTwin,
),
)
} else {
val card = scanResponse.card
modelScope.launch {
val hasTangemPay = onboardingRepository.hasTangemPayInWallet(userWalletId).getOrNull() == true
store.dispatchNavigationAction {
push(
route = AppRoute.ResetToFactory(
userWalletId = userWalletId,
cardId = card.cardId,
isActiveBackupStatus = card.backupStatus?.isActive == true,
backupCardsCount = scanResponse.getBackupCardsCount() ?: 0,
hasTangemPay = hasTangemPay,
),
)
}
appRouter.push(
route = AppRoute.ResetToFactory(
userWalletId = userWalletId,
cardId = card.cardId,
isActiveBackupStatus = card.backupStatus?.isActive == true,
backupCardsCount = scanResponse.getBackupCardsCount() ?: 0,
hasTangemPay = hasTangemPay,
),
)
}
}
}
@ -252,6 +245,6 @@ internal class CardSettingsModel @Inject constructor(
private fun onBackClick() {
cardSettingsInteractor.clear()
store.dispatchNavigationAction(AppRouter::pop)
appRouter.pop()
}
}

View file

@ -7,10 +7,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent
import com.tangem.tap.features.details.ui.resetcard.model.ResetCardModel
import com.tangem.tap.store
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -18,6 +16,7 @@ import dagger.assisted.AssistedInject
internal class DefaultResetCardComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: ResetCardComponent.Params,
private val appRouter: AppRouter,
) : ResetCardComponent, AppComponentContext by appComponentContext {
private val model: ResetCardModel = getOrCreateModel(params)
@ -29,7 +28,7 @@ internal class DefaultResetCardComponent @AssistedInject constructor(
ResetCardScreen(
modifier = modifier,
state = state,
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
onBackClick = { appRouter.pop() },
)
}

View file

@ -3,6 +3,7 @@ package com.tangem.tap.features.details.ui.resetcard.model
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.common.routing.utils.popTo
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
@ -18,14 +19,11 @@ import com.tangem.domain.wallets.usecase.DeleteWalletUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.common.extensions.onUserWalletSelected
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.getResetToFactoryDescription
import com.tangem.tap.features.details.ui.resetcard.ResetCardDialog
import com.tangem.tap.features.details.ui.resetcard.ResetCardScreenState
import com.tangem.tap.features.details.ui.resetcard.api.ResetCardComponent
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.DELAY_SDK_DIALOG_CLOSE
import com.tangem.utils.logging.TangemLogger
@ -51,6 +49,7 @@ internal class ResetCardModel @Inject constructor(
private val deleteWalletUseCase: DeleteWalletUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val cardSettingsInteractor: CardSettingsInteractor,
private val appRouter: AppRouter,
) : Model() {
private val params = paramsContainer.require<ResetCardComponent.Params>()
@ -189,19 +188,11 @@ internal class ResetCardModel @Inject constructor(
modelScope.launch {
resetCardUseCase(cardId = primaryCardId, params = currentUserCodeParams).onRight {
deleteSavedAccessCodesUseCase(cardId = primaryCardId)
val hasUserWallets = deleteWalletUseCase(userWalletId = currentUserWalletId).getOrElse { error ->
deleteWalletUseCase(userWalletId = currentUserWalletId).getOrElse { error ->
TangemLogger.e("Unable to delete user wallet: $error")
return@launch
}
if (hasUserWallets) {
val newSelectedWallet = getSelectedWalletSyncUseCase().getOrElse { error ->
error("Failed to get selected wallet: $error")
}
store.onUserWalletSelected(newSelectedWallet)
}
delay(DELAY_SDK_DIALOG_CLOSE)
checkRemainingBackupCards()
@ -270,9 +261,9 @@ internal class ResetCardModel @Inject constructor(
val newSelectedWallet = getSelectedWalletSyncUseCase.invoke().getOrNull()
if (newSelectedWallet != null) {
store.dispatchNavigationAction { popTo<AppRoute.Wallet>() }
appRouter.popTo<AppRoute.Wallet>()
} else {
store.dispatchNavigationAction { replaceAll(AppRoute.Home()) }
appRouter.replaceAll(AppRoute.Home())
}
}

View file

@ -7,10 +7,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.ui.securitymode.api.SecurityModeComponent
import com.tangem.tap.features.details.ui.securitymode.model.SecurityModeModel
import com.tangem.tap.store
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -18,6 +16,7 @@ import dagger.assisted.AssistedInject
internal class DefaultSecurityModeComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: SecurityModeComponent.Params,
private val appRouter: AppRouter,
) : SecurityModeComponent, AppComponentContext by appComponentContext {
private val model: SecurityModeModel = getOrCreateModel(params)
@ -29,7 +28,7 @@ internal class DefaultSecurityModeComponent @AssistedInject constructor(
SecurityModeScreen(
modifier = modifier,
state = state,
onBackClick = { store.dispatchNavigationAction(AppRouter::pop) },
onBackClick = { appRouter.pop() },
)
}

View file

@ -13,13 +13,11 @@ import com.tangem.sdk.api.TangemSdkManager
import com.tangem.tap.common.analytics.events.AnalyticsParam
import com.tangem.tap.common.analytics.events.Settings
import com.tangem.tap.common.analytics.events.TangemSdkErrorEvent
import com.tangem.tap.common.extensions.dispatchNavigationAction
import com.tangem.tap.features.details.redux.SecurityOption
import com.tangem.tap.features.details.ui.cardsettings.domain.CardSettingsInteractor
import com.tangem.tap.features.details.ui.common.utils.getAllowedSecurityOptions
import com.tangem.tap.features.details.ui.common.utils.getCurrentSecurityOption
import com.tangem.tap.features.details.ui.securitymode.SecurityModeScreenState
import com.tangem.tap.store
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
@ -34,6 +32,7 @@ internal class SecurityModeModel @Inject constructor(
private val cardSettingsInteractor: CardSettingsInteractor,
private val analyticsEventHandler: AnalyticsEventHandler,
private val analyticsErrorHandler: AnalyticsErrorHandler,
private val appRouter: AppRouter,
) : Model() {
private val scannedScanResponse = cardSettingsInteractor.scannedScanResponse.value
@ -91,7 +90,7 @@ internal class SecurityModeModel @Inject constructor(
is CompletionResult.Success -> {
analyticsEventHandler.send(Settings.CardSettings.SecurityModeChanged(paramValue))
store.dispatchNavigationAction(AppRouter::pop)
appRouter.pop()
}
is CompletionResult.Failure -> {
val error = result.error
@ -99,7 +98,6 @@ internal class SecurityModeModel @Inject constructor(
analyticsErrorHandler.sendErrorEvent(TangemSdkErrorEvent(error))
}
}
else -> Unit
}
}
}

View file

@ -21,7 +21,6 @@ import com.tangem.domain.balancehiding.BalanceHidingSettings
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.notifications.ClearApplicationIdUseCase
import com.tangem.domain.notifications.GetApplicationIdUseCase
import com.tangem.domain.notifications.SendPushTokenUseCase
@ -37,7 +36,6 @@ import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
import com.tangem.domain.staking.FetchStakingOptionsUseCase
import com.tangem.domain.wallets.usecase.AssociateWalletsWithApplicationIdUseCase
import com.tangem.domain.wallets.usecase.GetSavedWalletsCountUseCase
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase
import com.tangem.feature.swap.analytics.StoriesEvents
import com.tangem.security.DeviceSecurityInfoProvider
@ -80,7 +78,6 @@ internal class MainViewModel @Inject constructor(
private val apiConfigsManager: ApiConfigsManager,
private val multiQuoteUpdater: MultiQuoteUpdater,
private val appStateHolder: AppStateHolder,
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
private val appRouterConfig: AppRouterConfig,
private val sellService: SellService,
private val deviceSecurityInfoProvider: DeviceSecurityInfoProvider,
@ -143,8 +140,6 @@ internal class MainViewModel @Inject constructor(
launch { fetchUserCountry() }
}
subscribeToSelectedWallet()
// await while initial route stack is initialized
appRouterConfig.initializedState.first { it }
@ -173,20 +168,6 @@ internal class MainViewModel @Inject constructor(
}
}
private fun subscribeToSelectedWallet() {
getSelectedWalletUseCase.invoke()
.mapLeft { emptyFlow<UserWallet>() }
.onRight { wallet ->
wallet.distinctUntilChanged()
.onEach {
// FIXME Do not remove this call without checking implications !!!
appStateHolder.onUserWalletSelected(it)
}
.flowOn(dispatchers.io)
.launchIn(viewModelScope)
}
}
private suspend fun fetchStakingOptions() {
fetchStakingOptionsUseCase()
.onLeft { TangemLogger.e("Unable to fetch staking options: $it") }