Updated on 2026-08-14

This commit is contained in:
Tangem 2025-11-24 17:01:39 +04:00
parent df32ea7ab6
commit 4ef7082504
24 changed files with 214 additions and 185 deletions

View file

@ -515,4 +515,10 @@ internal object WalletsDomainModule {
): HasSecuredWalletsUseCase {
return HasSecuredWalletsUseCase(userWalletsListRepository = userWalletsListRepository)
}
@Provides
@Singleton
fun provideSyncWalletWithRemoteUseCase(walletsRepository: WalletsRepository): SyncWalletWithRemoteUseCase {
return SyncWalletWithRemoteUseCase(walletsRepository = walletsRepository)
}
}

View file

@ -50,9 +50,6 @@ interface TangemTechApi {
@Body userTokens: UserTokensResponse,
): ApiResponse<Unit>
@POST("v1/user-tokens")
suspend fun markUserWallerWasCreated(@Body body: MarkUserWalletWasCreatedBody): ApiResponse<Unit>
/** Returns referral status by [walletId] */
@GET("v1/referral/{walletId}")
suspend fun getReferralStatus(@Path("walletId") walletId: String): ApiResponse<ReferralResponse>

View file

@ -1,19 +1,21 @@
package com.tangem.data.wallets.converters
package com.tangem.datasource.api.tangemTech.converters
import com.tangem.datasource.api.tangemTech.models.CardInfoBody
import com.tangem.datasource.api.tangemTech.models.WalletIdBody
import com.tangem.datasource.api.tangemTech.models.WalletType
import com.tangem.domain.models.wallet.UserWallet
internal object WalletIdBodyConverter {
object WalletIdBodyConverter {
fun convert(userWallet: UserWallet, publicKeys: Map<String, String>): WalletIdBody {
fun convert(userWallet: UserWallet, publicKeys: Map<String, String>? = null): WalletIdBody {
return WalletIdBody(
walletId = userWallet.walletId.stringValue,
name = userWallet.name,
cards = publicKeys.map {
walletType = WalletType.from(userWallet),
cards = publicKeys?.map { publicKeyById ->
CardInfoBody(
cardId = it.key,
cardPublicKey = it.value,
cardId = publicKeyById.key,
cardPublicKey = publicKeyById.value,
)
},
)

View file

@ -1,9 +0,0 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class MarkUserWalletWasCreatedBody(
@Json(name = "user_wallet_id") val userWalletId: String,
)

View file

@ -13,8 +13,10 @@ data class UserTokensResponse(
@Json(name = "version") val version: Int = 0,
@Json(name = "group") val group: GroupType,
@Json(name = "sort") val sort: SortType,
@Json(name = "notifyStatus") val notifyStatus: Boolean? = null,
@Json(name = "tokens") val tokens: List<Token> = emptyList(),
@Json(name = "notifyStatus") val notifyStatus: Boolean? = null,
@Json(name = "name") val walletName: String? = null,
@Json(name = "type") val walletType: WalletType? = null,
) {
@JsonClass(generateAdapter = true)

View file

@ -4,8 +4,7 @@ import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
@Suppress("BooleanPropertyNaming")
data class WalletBody(
@Json(name = "notifyStatus") val notifyStatus: Boolean? = null,
@Json(name = "name") val name: String? = null,
@Json(name = "type") val type: WalletType? = null,
)

View file

@ -9,14 +9,4 @@ data class WalletIdBody(
@Json(name = "name") val name: String,
@Json(name = "type") val walletType: WalletType? = null,
@Json(name = "cards") val cards: List<CardInfoBody>? = null,
) {
@JsonClass(generateAdapter = false)
enum class WalletType {
@Json(name = "card")
COLD,
@Json(name = "mobile")
HOT,
}
}
)

View file

@ -0,0 +1,26 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import com.tangem.domain.models.wallet.UserWallet
@JsonClass(generateAdapter = false)
enum class WalletType {
@Json(name = "card")
COLD,
@Json(name = "mobile")
HOT,
;
companion object {
fun from(userWallet: UserWallet?): WalletType? {
return when (userWallet) {
is UserWallet.Cold -> COLD
is UserWallet.Hot -> HOT
null -> null
}
}
}
}

View file

@ -1,12 +1,13 @@
package com.tangem.data.wallets.converters
package com.tangem.datasource.api.tangemTech.converters
import com.google.common.truth.Truth.assertThat
import com.tangem.datasource.api.tangemTech.models.CardInfoBody
import com.tangem.datasource.api.tangemTech.models.WalletIdBody
import com.tangem.datasource.api.tangemTech.models.WalletType
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import io.mockk.mockk
import org.junit.Test
import org.junit.jupiter.api.Test
class WalletIdBodyConverterTest {
@ -36,6 +37,7 @@ class WalletIdBodyConverterTest {
WalletIdBody(
walletId = walletId.stringValue,
name = walletName,
walletType = WalletType.COLD,
cards = listOf(
CardInfoBody(
cardId = "card1",
@ -72,6 +74,7 @@ class WalletIdBodyConverterTest {
assertThat(result).isEqualTo(
WalletIdBody(
walletId = walletId.stringValue,
walletType = WalletType.COLD,
name = walletName,
cards = emptyList(),
),

View file

@ -4,7 +4,6 @@
<CurrentIssues>
<ID>CanBeNonNullable:FetchWalletAccountsErrorHandler.kt$FetchWalletAccountsErrorHandler$savedAccountsResponse: GetWalletAccountsResponse?</ID>
<ID>MultilineLambdaItParameter:DefaultAccountsCRUDRepository.kt$DefaultAccountsCRUDRepository${ if (it is HttpException &amp;&amp; it.code == HttpException.Code.NOT_MODIFIED) { null } else { throw it } }</ID>
<ID>MultilineLambdaItParameter:FetchWalletAccountsErrorHandler.kt$FetchWalletAccountsErrorHandler${ it.copy( tokens = UserTokensResponseAccountIdEnricher(userWalletId = userWalletId, tokens = it.tokens), ) }</ID>
<ID>MultilineLambdaItParameter:GetWalletAccountsResponseExt.kt${ enrichedTokensByAccountId[it].orEmpty().map { token -&gt; // Tokens from unexisting accounts should be copied to the main account token.copy(accountId = accountDTO.id) } }</ID>
<ID>NoNameShadowing:GetWalletAccountsResponseExt.kt$tokens</ID>
<ID>NullableToStringCall:AccountListCryptoCurrenciesProducer.kt$AccountListCryptoCurrenciesProducer$${this::class.simpleName}</ID>

View file

@ -11,15 +11,13 @@ import com.tangem.datasource.api.common.response.ApiResponseError.HttpException.
import com.tangem.datasource.api.common.response.ETAG_HEADER
import com.tangem.datasource.api.common.response.isNetworkError
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.converters.WalletIdBodyConverter
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.WalletIdBody
import com.tangem.datasource.api.tangemTech.models.WalletIdBody.WalletType
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
@ -110,9 +108,9 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor(
private suspend fun getFromLegacyStore(userWalletId: UserWalletId): UserTokensResponse? {
return userTokensResponseStore.getSyncOrNull(userWalletId)
?.let {
it.copy(
tokens = UserTokensResponseAccountIdEnricher(userWalletId = userWalletId, tokens = it.tokens),
?.let { response ->
response.copy(
tokens = UserTokensResponseAccountIdEnricher(userWalletId = userWalletId, tokens = response.tokens),
)
}
.also { userTokensResponseStore.clear(userWalletId) }
@ -129,14 +127,7 @@ internal class FetchWalletAccountsErrorHandler @Inject constructor(
val creationResponse = withContext(dispatchers.io) {
tangemTechApi.createWallet(
body = WalletIdBody(
walletId = userWalletId.stringValue,
name = userWallet.name,
walletType = when (userWallet) {
is UserWallet.Cold -> WalletType.COLD
is UserWallet.Hot -> WalletType.HOT
},
),
body = WalletIdBodyConverter.convert(userWallet),
)
}

View file

@ -12,6 +12,7 @@ import com.tangem.datasource.api.common.response.ETAG_HEADER
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.WalletIdBody
import com.tangem.datasource.api.tangemTech.models.WalletType
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
import com.tangem.datasource.api.tangemTech.models.account.WalletAccountDTO
import com.tangem.datasource.api.tangemTech.models.account.toUserTokensResponse
@ -124,7 +125,7 @@ class FetchWalletAccountsErrorHandlerTest {
WalletIdBody(
walletId = userWalletId.stringValue,
name = walletName,
walletType = WalletIdBody.WalletType.COLD,
walletType = WalletType.COLD,
),
)
} returns apiResponse
@ -146,7 +147,7 @@ class FetchWalletAccountsErrorHandlerTest {
WalletIdBody(
walletId = userWalletId.stringValue,
name = walletName,
walletType = WalletIdBody.WalletType.COLD,
walletType = WalletType.COLD,
),
)
eTagsStore.store(userWalletId, ETagsStore.Key.WalletAccounts, eTagValue)

View file

@ -4,7 +4,9 @@ import com.tangem.data.common.api.safeApiCall
import com.tangem.data.common.tokens.UserTokensBackwardCompatibility
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.WalletType
import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -13,8 +15,10 @@ import com.tangem.utils.retryer.RetryerPool
import kotlinx.coroutines.withContext
import timber.log.Timber
@Suppress("LongParameterList")
class UserTokensSaver(
private val tangemTechApi: TangemTechApi,
private val userWalletsStore: UserWalletsStore,
private val userTokensResponseStore: UserTokensResponseStore,
private val dispatchers: CoroutineDispatcherProvider,
private val addressesEnricher: UserTokensResponseAddressesEnricher,
@ -48,7 +52,12 @@ class UserTokensSaver(
onFailSend: () -> Unit = {},
) {
withContext(dispatchers.default) {
val enrichedResponse = response.enrichIf(userWalletId = userWalletId, condition = useEnricher)
val userWallet = userWalletsStore.getSyncOrNull(key = userWalletId)
val enrichedResponse = response.enrichIf(userWalletId = userWalletId, condition = useEnricher).copy(
walletName = userWallet?.name,
walletType = WalletType.from(userWallet),
)
safeApiCall(
call = {

View file

@ -68,6 +68,7 @@ internal object DataCommonModule {
@Singleton
fun provideUserTokensSaver(
tangemTechApi: TangemTechApi,
userWalletsStore: UserWalletsStore,
userTokensResponseStore: UserTokensResponseStore,
dispatchers: CoroutineDispatcherProvider,
addressesEnricher: UserTokensResponseAddressesEnricher,
@ -75,6 +76,7 @@ internal object DataCommonModule {
): UserTokensSaver {
return UserTokensSaver(
tangemTechApi = tangemTechApi,
userWalletsStore = userWalletsStore,
userTokensResponseStore = userTokensResponseStore,
dispatchers = dispatchers,
addressesEnricher = addressesEnricher,

View file

@ -4,8 +4,11 @@ import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.common.response.ApiResponseError
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
import com.tangem.datasource.api.tangemTech.models.WalletType
import com.tangem.datasource.local.token.UserTokensResponseStore
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.*
@ -18,6 +21,7 @@ import org.junit.jupiter.api.TestInstance
class UserTokensSaverTest {
private val tangemTechApi: TangemTechApi = mockk()
private val userWalletsStore: UserWalletsStore = mockk(relaxUnitFun = true)
private val userTokensResponseStore: UserTokensResponseStore = mockk(relaxed = true)
private val enricher: UserTokensResponseAddressesEnricher = mockk()
private val accountsFeatureToggles = mockk<AccountsFeatureToggles> {
@ -26,6 +30,7 @@ class UserTokensSaverTest {
private val userTokensSaver: UserTokensSaver = UserTokensSaver(
tangemTechApi = tangemTechApi,
userWalletsStore = userWalletsStore,
userTokensResponseStore = userTokensResponseStore,
dispatchers = TestingCoroutineDispatcherProvider(),
addressesEnricher = enricher,
@ -35,7 +40,7 @@ class UserTokensSaverTest {
@BeforeEach
fun resetMocks() {
clearMocks(tangemTechApi, userTokensResponseStore, enricher)
clearMocks(tangemTechApi, userWalletsStore, userTokensResponseStore, enricher)
}
@Test
@ -77,20 +82,31 @@ class UserTokensSaverTest {
runTest {
// GIVEN
val userWalletId = UserWalletId("1234567890abcdef")
val userWallet = mockk<UserWallet.Cold> {
every { this@mockk.walletId } returns userWalletId
every { this@mockk.name } returns "Wallet"
}
val response = UserTokensResponse(
version = 0,
group = UserTokensResponse.GroupType.NETWORK,
sort = UserTokensResponse.SortType.BALANCE,
tokens = emptyList(),
walletName = userWallet.name,
walletType = WalletType.COLD,
)
val enrichedResponse = UserTokensResponse(
version = 0,
group = UserTokensResponse.GroupType.NETWORK,
sort = UserTokensResponse.SortType.MANUAL,
tokens = emptyList(),
walletName = userWallet.name,
walletType = WalletType.COLD,
)
val error = ApiResponseError.UnknownException(Exception("API Error"))
var onFailSendCalled = false
coEvery { userWalletsStore.getSyncOrNull(userWalletId) } returns userWallet
coEvery { enricher(userWalletId, response) } returns enrichedResponse
coEvery { tangemTechApi.saveUserTokens(any(), any()) } returns ApiResponse.Error(error) as ApiResponse<Unit>
@ -114,18 +130,29 @@ class UserTokensSaverTest {
fun `GIVEN user wallet id and response WHEN storeAndPush THEN should store and push enriched response`() = runTest {
// GIVEN
val userWalletId = UserWalletId("1234567890abcdef")
val userWallet = mockk<UserWallet.Cold> {
every { this@mockk.walletId } returns userWalletId
every { this@mockk.name } returns "Wallet"
}
val response = UserTokensResponse(
version = 0,
group = UserTokensResponse.GroupType.NETWORK,
sort = UserTokensResponse.SortType.BALANCE,
tokens = emptyList(),
walletName = userWallet.name,
walletType = WalletType.COLD,
)
val enrichedResponse = UserTokensResponse(
version = 0,
group = UserTokensResponse.GroupType.NETWORK,
sort = UserTokensResponse.SortType.BALANCE,
tokens = emptyList(),
walletName = userWallet.name,
walletType = WalletType.COLD,
)
coEvery { userWalletsStore.getSyncOrNull(userWalletId) } returns userWallet
coEvery { enricher(userWalletId, response) } returns enrichedResponse
coEvery {
tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedResponse)

View file

@ -2,10 +2,6 @@
<SmellBaseline>
<ManuallySuppressedIssues/>
<CurrentIssues>
<ID>BooleanPropertyNaming:DefaultWalletsRepository.kt$DefaultWalletsRepository$val legacySaveWalletsInTheApp = appPreferencesStore.getSyncOrNull( key = PreferencesKeys.SAVE_USER_WALLETS_KEY, )</ID>
<ID>BooleanPropertyNaming:DefaultWalletsRepository.kt$DefaultWalletsRepository$val legacyShouldSaveAccessCode = appPreferencesStore.getSyncOrNull( key = PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY, )</ID>
<ID>BooleanPropertyNaming:DefaultWalletsRepository.kt$DefaultWalletsRepository$val requireAccessCode = appPreferencesStore.getSyncOrNull( key = PreferencesKeys.REQUIRE_ACCESS_CODE_KEY, )</ID>
<ID>BooleanPropertyNaming:DefaultWalletsRepository.kt$DefaultWalletsRepository$val useBiometricAuthentication = appPreferencesStore.getSyncOrNull( key = PreferencesKeys.USE_BIOMETRIC_AUTHENTICATION_KEY, )</ID>
<ID>MultilineLambdaItParameter:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository${ networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, userWallet = userWallet, ) }</ID>
<ID>MultilineLambdaItParameter:DefaultDerivationsRepository.kt$DefaultDerivationsRepository${ userWallet.update(it.first) it.second }</ID>
<ID>MultilineLambdaItParameter:DefaultHotMapDerivationsRepository.kt$DefaultHotMapDerivationsRepository${ networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, userWallet = userWallet, ) }</ID>
@ -14,19 +10,9 @@
<ID>MultilineLambdaItParameter:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository${ while (true) { emit(toState(id, it.attempts, it.deadline, it.bootCount)) val remaining = remainingSeconds(it.deadline, it.bootCount) if (remaining &lt;= 0) break delay(timeMillis = 1000) } }</ID>
<ID>MultilineLambdaItParameter:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor${ block(UnlockHotWallet(hotWalletId, it)).also { hotWalletPasswordRequester.successfulAuthentication() hotWalletPasswordRequester.dismiss() } }</ID>
<ID>MultilineLambdaItParameter:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor${ tangemHotSdk.getContextUnlock(it).also { unlockHotWallet -&gt; contextualUnlockHotWallet[hotWalletId] = unlockHotWallet } }</ID>
<ID>MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ if (it is HttpException &amp;&amp; it.code == HttpException.Code.NOT_FOUND) { Status.NOTIFIED } else { Status.NOT_NEEDED } }</ID>
<ID>MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ it.getOrDefault( key = userWalletId, defaultValue = SeedPhraseNotificationsStatus.NOT_NEEDED, ) }</ID>
<ID>MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ it.setObjectMap( key = PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY, value = it.getObjectMap&lt;Boolean&gt;(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY) .plus(userWalletId.stringValue to isEnabled), ) }</ID>
<ID>MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ it.setObjectMap( key = PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY, value = it.getObjectMap&lt;Boolean&gt;(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY) .plus(userWalletId.stringValue to false), ) }</ID>
<ID>MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ it.setObjectMap( key = PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY, value = it.getObjectMap&lt;Boolean&gt;(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY) .plus(userWalletId.stringValue to true), ) }</ID>
<ID>MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ it.toMutableMap().apply { this[id] = value } }</ID>
<ID>MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ val added = it[PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY].orEmpty() it[PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY] = added + userWalletId.stringValue }</ID>
<ID>MultilineLambdaItParameter:TangemHotWalletSigner.kt$TangemHotWalletSigner${ Timber.e(it) return if (it is TangemSdkError) { CompletionResult.Failure(it) } else { CompletionResult.Failure(TangemSdkError.ExceptionError(it)) } }</ID>
<ID>MultilineLambdaItParameter:WalletIdBodyConverter.kt$WalletIdBodyConverter${ CardInfoBody( cardId = it.key, cardPublicKey = it.value, ) }</ID>
<ID>NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, count, deadline, boot)</ID>
<ID>NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, it.attempts, it.deadline, it.bootCount)</ID>
<ID>NoNameShadowing:DefaultWalletsRepository.kt$DefaultWalletsRepository$error</ID>
<ID>NoNameShadowing:DefaultWalletsRepository.kt$DefaultWalletsRepository${ UserWalletId(it.key) }</ID>
<ID>SuspendFunSwallowedCancellation:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor$runCatching</ID>
<ID>SuspendFunSwallowedCancellation:TangemHotWalletSigner.kt$TangemHotWalletSigner$runCatching</ID>
<ID>UnnecessaryLet:MissedDerivationsFinder.kt$MissedDerivationsFinder$let(::findByNetworks)</ID>

View file

@ -4,15 +4,17 @@ import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.data.wallets.converters.UserWalletRemoteInfoConverter
import com.tangem.data.wallets.converters.WalletIdBodyConverter
import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException
import com.tangem.datasource.api.common.response.fold
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.*
import com.tangem.datasource.api.tangemTech.converters.WalletIdBodyConverter
import com.tangem.datasource.api.tangemTech.models.PromocodeActivationBody
import com.tangem.datasource.api.tangemTech.models.SeedPhraseNotificationDTO
import com.tangem.datasource.api.tangemTech.models.SeedPhraseNotificationDTO.Status
import com.tangem.datasource.api.tangemTech.models.WalletIdBody.WalletType
import com.tangem.datasource.api.tangemTech.models.WalletBody
import com.tangem.datasource.api.tangemTech.models.WalletType
import com.tangem.datasource.local.datastore.RuntimeStateStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
@ -62,25 +64,25 @@ internal class DefaultWalletsRepository(
}
override suspend fun useBiometricAuthentication(): Boolean {
val useBiometricAuthentication = appPreferencesStore.getSyncOrNull(
val shouldUseBiometricAuth = appPreferencesStore.getSyncOrNull(
key = PreferencesKeys.USE_BIOMETRIC_AUTHENTICATION_KEY,
)
if (useBiometricAuthentication != null) {
return useBiometricAuthentication
if (shouldUseBiometricAuth != null) {
return shouldUseBiometricAuth
}
val legacySaveWalletsInTheApp = appPreferencesStore.getSyncOrNull(
val isLegacySaveWalletsInTheApp = appPreferencesStore.getSyncOrNull(
key = PreferencesKeys.SAVE_USER_WALLETS_KEY,
)
if (legacySaveWalletsInTheApp != null) {
if (isLegacySaveWalletsInTheApp != null) {
// Migrate legacy setting to new one
appPreferencesStore.store(
key = PreferencesKeys.USE_BIOMETRIC_AUTHENTICATION_KEY,
value = legacySaveWalletsInTheApp,
value = isLegacySaveWalletsInTheApp,
)
return legacySaveWalletsInTheApp
return isLegacySaveWalletsInTheApp
} else {
// Default value for new users
setUseBiometricAuthentication(false)
@ -93,25 +95,25 @@ internal class DefaultWalletsRepository(
}
override suspend fun requireAccessCode(): Boolean {
val requireAccessCode = appPreferencesStore.getSyncOrNull(
val isRequireAccessCode = appPreferencesStore.getSyncOrNull(
key = PreferencesKeys.REQUIRE_ACCESS_CODE_KEY,
)
if (requireAccessCode != null) {
return requireAccessCode
if (isRequireAccessCode != null) {
return isRequireAccessCode
}
val legacyShouldSaveAccessCode = appPreferencesStore.getSyncOrNull(
val isLegacyShouldSaveAccessCode = appPreferencesStore.getSyncOrNull(
key = PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY,
)
if (legacyShouldSaveAccessCode != null) {
if (isLegacyShouldSaveAccessCode != null) {
// Migrate legacy setting to new one
appPreferencesStore.store(
key = PreferencesKeys.REQUIRE_ACCESS_CODE_KEY,
value = legacyShouldSaveAccessCode.not(),
value = isLegacyShouldSaveAccessCode.not(),
)
return legacyShouldSaveAccessCode.not()
return isLegacyShouldSaveAccessCode.not()
} else {
// Default value for new users
setRequireAccessCode(true)
@ -130,10 +132,10 @@ internal class DefaultWalletsRepository(
}
override suspend fun setHasWalletsWithRing(userWalletId: UserWalletId) {
appPreferencesStore.editData {
val added = it[PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY].orEmpty()
appPreferencesStore.editData { mutablePreferences ->
val added = mutablePreferences[PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY].orEmpty()
it[PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY] = added + userWalletId.stringValue
mutablePreferences[PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY] = added + userWalletId.stringValue
}
}
@ -141,8 +143,8 @@ internal class DefaultWalletsRepository(
return channelFlow {
launch {
seedPhraseNotificationVisibilityStore.get()
.map {
it.getOrDefault(
.map { map ->
map.getOrDefault(
key = userWalletId,
defaultValue = SeedPhraseNotificationsStatus.NOT_NEEDED,
)
@ -169,8 +171,8 @@ internal class DefaultWalletsRepository(
tangemTechApi.getSeedPhraseNotificationStatus(walletId = userWalletId.stringValue).getOrThrow()
}.fold(
onSuccess = { it.status },
onFailure = {
if (it is HttpException && it.code == HttpException.Code.NOT_FOUND) {
onFailure = { throwable ->
if (throwable is HttpException && throwable.code == HttpException.Code.NOT_FOUND) {
Status.NOTIFIED
} else {
Status.NOT_NEEDED
@ -245,27 +247,12 @@ internal class DefaultWalletsRepository(
updateNotificationVisibility(id = userWalletId, value = SeedPhraseNotificationsStatus.NOT_NEEDED)
}
override suspend fun markWallet2WasCreated(userWalletId: UserWalletId) {
runCatching(dispatchers.io) {
tangemTechApi.markUserWallerWasCreated(
body = MarkUserWalletWasCreatedBody(userWalletId = userWalletId.stringValue),
)
}
}
override suspend fun createWallet(userWalletId: UserWalletId) {
withContext(dispatchers.io) {
runCatching(dispatchers.io) {
val userWallet = userWalletsStore.getSyncStrict(key = userWalletId)
tangemTechApi.createWallet(
body = WalletIdBody(
walletId = userWalletId.stringValue,
name = userWallet.name,
walletType = when (userWallet) {
is UserWallet.Cold -> WalletType.COLD
is UserWallet.Hot -> WalletType.HOT
},
),
body = WalletIdBodyConverter.convert(userWallet = userWallet),
)
}
}
@ -293,8 +280,8 @@ internal class DefaultWalletsRepository(
}
private suspend fun updateNotificationVisibility(id: UserWalletId, value: SeedPhraseNotificationsStatus) {
return seedPhraseNotificationVisibilityStore.update {
it.toMutableMap().apply {
return seedPhraseNotificationVisibilityStore.update { map ->
map.toMutableMap().apply {
this[id] = value
}
}
@ -306,23 +293,23 @@ internal class DefaultWalletsRepository(
override fun nftEnabledStatuses(): Flow<Map<UserWalletId, Boolean>> = appPreferencesStore
.getObjectMap<Boolean>(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY)
.map { it.mapKeys { UserWalletId(it.key) } }
.map { map -> map.mapKeys { UserWalletId(it.key) } }
override suspend fun enableNFT(userWalletId: UserWalletId) {
appPreferencesStore.editData {
it.setObjectMap(
appPreferencesStore.editData { mutablePreferences ->
mutablePreferences.setObjectMap(
key = PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY,
value = it.getObjectMap<Boolean>(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY)
value = mutablePreferences.getObjectMap<Boolean>(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY)
.plus(userWalletId.stringValue to true),
)
}
}
override suspend fun disableNFT(userWalletId: UserWalletId) {
appPreferencesStore.editData {
it.setObjectMap(
appPreferencesStore.editData { mutablePreferences ->
mutablePreferences.setObjectMap(
key = PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY,
value = it.getObjectMap<Boolean>(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY)
value = mutablePreferences.getObjectMap<Boolean>(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY)
.plus(userWalletId.stringValue to false),
)
}
@ -338,10 +325,10 @@ internal class DefaultWalletsRepository(
.firstOrNull() == true
override suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean) {
appPreferencesStore.editData {
it.setObjectMap(
appPreferencesStore.editData { mutablePreferences ->
mutablePreferences.setObjectMap(
key = PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY,
value = it.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY)
value = mutablePreferences.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY)
.plus(userWalletId.stringValue to isEnabled),
)
}
@ -358,9 +345,11 @@ internal class DefaultWalletsRepository(
}
override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) {
val userWallet = userWalletsStore.getSyncOrNull(key = UserWalletId(walletId))
tangemTechApi.updateWallet(
walletId = walletId,
body = WalletBody(name = walletName),
body = WalletBody(name = walletName, type = WalletType.from(userWallet)),
).getOrThrow()
}
@ -419,18 +408,20 @@ internal class DefaultWalletsRepository(
promoCode = promoCode,
address = bitcoinAddress,
),
).fold({
return@fold it.status.right()
}, { error ->
val error = when (error) {
is HttpException -> when (error.code) {
HttpException.Code.NOT_FOUND -> ActivatePromoCodeError.InvalidPromoCode
HttpException.Code.CONFLICT -> ActivatePromoCodeError.PromocodeAlreadyUsed
).fold(
onSuccess = { it.status.right() },
onError = { apiResponseError ->
val error = when (apiResponseError) {
is HttpException -> when (apiResponseError.code) {
HttpException.Code.NOT_FOUND -> ActivatePromoCodeError.InvalidPromoCode
HttpException.Code.CONFLICT -> ActivatePromoCodeError.PromocodeAlreadyUsed
else -> ActivatePromoCodeError.ActivationFailed
}
else -> ActivatePromoCodeError.ActivationFailed
}
else -> ActivatePromoCodeError.ActivationFailed
}
return@fold error.left()
})
error.left()
},
)
}
}

View file

@ -1,7 +1,7 @@
==========================================
Detekt Baseline Updater & Issue Counter
==========================================
Date: 2025-11-20 16:34:32
Date: 2025-11-21 15:22:23
Updating detekt baseline for debug variant...
@ -13,13 +13,13 @@ Counting issues in baseline files...
==========================================
Summary:
Total Issues: 1744
Total Issues: 1720
Modules with Issues: 90
Average Issues per Module: 19
Progress:
Fixed: 58 out of 1802 (3%)
Remaining: 1744
Fixed: 82 out of 1802 (4%)
Remaining: 1720
==========================================
All Modules with Issues (sorted by count)
@ -29,11 +29,11 @@ Module Issues
────────────────────────────────────────────────────────────────
features/wallet/impl 170
features/markets/impl 155
features/onboarding-v2/impl 137
features/onboarding-v2/impl 131
features/onramp/impl 89
features/send-v2/impl 80
features/swap/impl 73
features/hot-wallet/impl 61
features/hot-wallet/impl 58
features/staking/impl 56
data/wallet-connect 55
features/swap-v2/impl 53
@ -42,7 +42,6 @@ features/tokendetails/impl 49
features/manage-tokens/impl 45
domain/wallets 39
features/nft/impl 36
data/wallets 32
features/tester/impl 31
features/yield-supply/impl 28
domain/tokens 28
@ -52,6 +51,7 @@ common/ui 26
data/visa 23
features/tangempay/details/impl 22
data/nft 20
data/wallets 18
features/swap/data 15
data/swap 13
domain/account/status 12
@ -69,10 +69,10 @@ data/networks 9
features/welcome/impl 8
features/home/impl 8
domain/transaction 8
data/account 8
libs/tangem-sdk-api 7
data/txhistory 7
data/tokens 7
data/account 7
features/send-v2/api 6
domain/markets 6
data/wallet-manager 6

View file

@ -43,8 +43,6 @@ interface WalletsRepository {
suspend fun acceptSeedPhraseSecondNotification(userWalletId: UserWalletId)
suspend fun markWallet2WasCreated(userWalletId: UserWalletId)
suspend fun createWallet(userWalletId: UserWalletId)
fun nftEnabledStatus(userWalletId: UserWalletId): Flow<Boolean>

View file

@ -0,0 +1,21 @@
package com.tangem.domain.wallets.usecase
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.coroutines.runSuspendCatching
import timber.log.Timber
/**
* Use case to sync wallet with remote
*
[REDACTED_AUTHOR]
*/
class SyncWalletWithRemoteUseCase(
private val walletsRepository: WalletsRepository,
) {
suspend operator fun invoke(userWalletId: UserWalletId) {
runSuspendCatching { walletsRepository.createWallet(userWalletId) }
.onFailure(Timber::e)
}
}

View file

@ -25,7 +25,6 @@
<ID>MultilineLambdaItParameter:AddExistingWalletImportModel.kt$AddExistingWalletImportModel${ Timber.e(it) setImportProgress(false) }</ID>
<ID>MultilineLambdaItParameter:AddExistingWalletImportModel.kt$AddExistingWalletImportModel${ setImportProgress(false) when (it) { is SaveWalletError.DataError -&gt; Timber.e(it.toString(), "Unable to save user wallet") is SaveWalletError.WalletAlreadySaved -&gt; { uiMessageSender.send( SnackbarMessage(resourceReference(R.string.hw_import_seed_phrase_already_imported)), ) } } }</ID>
<ID>MultilineLambdaItParameter:CreateHardwareWalletModel.kt$CreateHardwareWalletModel${ delay(HIDE_PROGRESS_DELAY) setLoading(false) when (it) { is SaveWalletError.DataError -&gt; Timber.e(it.toString(), "Unable to save user wallet") is SaveWalletError.WalletAlreadySaved -&gt; { userWalletsListRepository.unlock( userWalletId = userWallet.walletId, unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse), ).onRight { router.replaceAll(AppRoute.Wallet) } } } }</ID>
<ID>MultilineLambdaItParameter:CreateMobileWalletModel.kt$CreateMobileWalletModel${ Timber.e(it) uiState.update { it.copy(createButtonLoading = false) } }</ID>
<ID>MultilineLambdaItParameter:ForgetWalletModel.kt$ForgetWalletModel${ Timber.e("Unable to delete wallet: $it") uiMessageSender.send( message = SnackbarMessage(resourceReference(R.string.common_unknown_error)), ) return@launch }</ID>
<ID>MultilineLambdaItParameter:ForgetWalletModel.kt$ForgetWalletModel${ val newValue = !it.firstCheckboxChecked it.copy( firstCheckboxChecked = newValue, isForgetButtonEnabled = newValue &amp;&amp; it.secondCheckboxChecked, ) }</ID>
<ID>MultilineLambdaItParameter:ForgetWalletModel.kt$ForgetWalletModel${ val newValue = !it.secondCheckboxChecked it.copy( secondCheckboxChecked = newValue, isForgetButtonEnabled = it.firstCheckboxChecked &amp;&amp; newValue, ) }</ID>
@ -52,7 +51,6 @@
<ID>MultilineLambdaItParameter:UpgradeWalletModel.kt$UpgradeWalletModel${ // Check if user attempted to upgrade before but something went wrong and a full reset is required val userWallet = coldUserWalletBuilderFactory.create(it).build() val sameWalletButNotFinishedBackup by lazy { userWallet?.walletId == params.userWalletId &amp;&amp; BackupValidator.isValidFull(it.card).not() } val otherWalletAndAlreadyCreated by lazy { userWallet?.walletId != params.userWalletId &amp;&amp; it.card.wallets.map { it.curve }.toSet().isNotEmpty() } if (userWallet != null &amp;&amp; (sameWalletButNotFinishedBackup || otherWalletAndAlreadyCreated)) { startResetCardsFlow.emit(userWallet) return@doOnSuccess } delay(DELAY_SDK_DIALOG_CLOSE) tangemSdkManager.changeDisplayedCardIdNumbersCount(it) navigateToUpgradeFlow(it) }</ID>
<ID>MultilineLambdaItParameter:ViewPhraseContent.kt${ EnumeratedTwoColumnGridItem( index = it + 1, mnemonic = "word${it + 1}", ) }</ID>
<ID>MultilineLambdaItParameter:ViewPhraseModel.kt$ViewPhraseModel${ it.copy( words = words.mapIndexed { index, s -&gt; EnumeratedTwoColumnGridItem(index + 1, s) }.toImmutableList(), ) }</ID>
<ID>NoNameShadowing:CreateMobileWalletModel.kt$CreateMobileWalletModel${ it.copy(createButtonLoading = false) }</ID>
<ID>NoNameShadowing:ManualBackupCheckModel.kt$ManualBackupCheckModel${ it.copy(completeButtonProgress = false) }</ID>
<ID>PropertyUsedBeforeDeclaration:AddExistingWalletImportModel.kt$AddExistingWalletImportModel$uiState</ID>
<ID>ReusedModifierInstance:AddExistingWalletImportContent.kt$OutlineTextFieldWithIcon( modifier = modifier .padding(horizontal = 16.dp) .fillMaxWidth(), value = state.passPhrase, onValueChange = state.passPhraseChange, iconResId = R.drawable.ic_information_24, iconColor = TangemTheme.colors.icon.informative, label = stringResourceSafe(id = R.string.common_passphrase), placeholder = stringResourceSafe(id = R.string.send_optional_field), onIconClick = state.onPassphraseInfoClick, keyboardOptions = KeyboardOptions( autoCorrectEnabled = false, keyboardType = KeyboardType.Password, ), )</ID>
@ -60,7 +58,6 @@
<ID>ReusedModifierInstance:HotWalletStepper.kt$TangemTopAppBar( startButton = if (state.showBackButton) { TopAppBarButtonUM.Back(onBackClick) } else { null }, endButton = when { state.showSkipButton -&gt; TopAppBarButtonUM.Text( text = resourceReference(R.string.common_skip), onClicked = onSkipClick, ) state.showFeedbackButton -&gt; TopAppBarButtonUM.Icon( iconRes = R.drawable.ic_chat_24, onClicked = onFeedbackClick, ) else -&gt; null }, title = state.title, containerColor = TangemTheme.colors.background.primary, modifier = modifier, titleAlignment = Alignment.CenterHorizontally, )</ID>
<ID>SuspendFunSwallowedCancellation:AccessCodeModel.kt$AccessCodeModel$runCatching</ID>
<ID>SuspendFunSwallowedCancellation:AddExistingWalletImportModel.kt$AddExistingWalletImportModel$runCatching</ID>
<ID>SuspendFunSwallowedCancellation:CreateMobileWalletModel.kt$CreateMobileWalletModel$runCatching</ID>
<ID>SuspendFunSwallowedCancellation:ManualBackupCheckModel.kt$ManualBackupCheckModel$runCatching</ID>
<ID>SuspendFunSwallowedCancellation:ManualBackupPhraseModel.kt$ManualBackupPhraseModel$runCatching</ID>
</CurrentIssues>

View file

@ -7,11 +7,14 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.navigation.Router
import com.tangem.domain.wallets.builder.HotUserWalletBuilder
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.domain.wallets.usecase.SyncWalletWithRemoteUseCase
import com.tangem.features.hotwallet.createmobilewallet.entity.CreateMobileWalletUM
import com.tangem.hot.sdk.TangemHotSdk
import com.tangem.hot.sdk.model.HotAuth
import com.tangem.hot.sdk.model.MnemonicType
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.runSuspendCatching
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
@ -19,11 +22,13 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
@Suppress("LongParameterList")
@ModelScoped
internal class CreateMobileWalletModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val hotUserWalletBuilderFactory: HotUserWalletBuilder.Factory,
private val saveUserWalletUseCase: SaveWalletUseCase,
private val syncWalletWithRemoteUseCase: SyncWalletWithRemoteUseCase,
private val router: Router,
private val tangemHotSdk: TangemHotSdk,
private val trackingContextProxy: TrackingContextProxy,
@ -58,18 +63,21 @@ internal class CreateMobileWalletModel @Inject constructor(
it.copy(createButtonLoading = true)
}
runCatching {
runSuspendCatching {
val hotWalletId = tangemHotSdk.generateWallet(HotAuth.NoAuth, mnemonicType = MnemonicType.Words12)
val hotUserWalletBuilder = hotUserWalletBuilderFactory.create(hotWalletId)
val userWallet = hotUserWalletBuilder.build()
saveUserWalletUseCase(userWallet)
router.replaceAll(AppRoute.Wallet)
}.onFailure {
Timber.e(it)
uiState.update {
it.copy(createButtonLoading = false)
saveUserWalletUseCase(userWallet)
launch(NonCancellable) {
syncWalletWithRemoteUseCase(userWalletId = userWallet.walletId)
}
router.replaceAll(AppRoute.Wallet)
}.onFailure { throwable ->
Timber.e(throwable)
uiState.update { it.copy(createButtonLoading = false) }
}
}
}

View file

@ -11,7 +11,6 @@
<ID>BooleanPropertyNaming:MultiWalletBackupUM.kt$MultiWalletBackupUMDialog$val warningCancelColor: Boolean = false</ID>
<ID>BooleanPropertyNaming:MultiWalletCreateWalletModel.kt$MultiWalletCreateWalletModel$val cardDoesNotSupportBackup = result.data.card.settings.isBackupAllowed.not()</ID>
<ID>BooleanPropertyNaming:MultiWalletCreateWalletUM.kt$MultiWalletCreateWalletUM$val showOtherOptionsButton: Boolean</ID>
<ID>BooleanPropertyNaming:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel$private var walletHasBackupError = false</ID>
<ID>BooleanPropertyNaming:MultiWalletFinalizeUM.kt$MultiWalletFinalizeUM$val scanPrimary: Boolean = true</ID>
<ID>BooleanPropertyNaming:MultiWalletSeedPhraseUM.kt$MultiWalletSeedPhraseUM.GeneratedWordsCheck$val createWalletButtonEnabled: Boolean = false</ID>
<ID>BooleanPropertyNaming:MultiWalletSeedPhraseUM.kt$MultiWalletSeedPhraseUM.GeneratedWordsCheck$val createWalletButtonProgress: Boolean = false</ID>
@ -76,8 +75,6 @@
<ID>MultilineLambdaItParameter:MultiWalletCreateWalletComponent.kt$MultiWalletCreateWalletComponent${ it.copy( stackSize = 2, stackMaxSize = 9, ) }</ID>
<ID>MultilineLambdaItParameter:MultiWalletCreateWalletModel.kt$MultiWalletCreateWalletModel${ it.copy( currentScanResponse = it.currentScanResponse.copy( card = result.data.card, derivedKeys = result.data.derivedKeys, primaryCard = result.data.primaryCard, ), ) }</ID>
<ID>MultilineLambdaItParameter:MultiWalletFinalizeComponent.kt$MultiWalletFinalizeComponent${ it.copy( stackSize = 7, stackMaxSize = 9, ) }</ID>
<ID>MultilineLambdaItParameter:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel${ it is UserWallet.Cold &amp;&amp; it.scanResponse.primaryCard?.cardId == scanResponse.primaryCard?.cardId }</ID>
<ID>MultilineLambdaItParameter:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel${ it.requireColdWallet().copy( scanResponse = scanResponse.updateScanResponseAfterBackup(), ) }</ID>
<ID>MultilineLambdaItParameter:MultiWalletScanPrimaryComponent.kt$MultiWalletScanPrimaryComponent${ it.copy( stackSize = 4, stackMaxSize = 9, ) }</ID>
<ID>MultilineLambdaItParameter:MultiWalletScanPrimaryModel.kt$MultiWalletScanPrimaryModel${ it.copy( currentScanResponse = scanResponse.copy( primaryCard = result.data, ), ) }</ID>
<ID>MultilineLambdaItParameter:MultiWalletSeedPhraseComponent.kt$MultiWalletSeedPhraseComponent${ // change stepper state based on the stack of the current step @Suppress("MagicNumber") params.innerNavigation.update { st -&gt; st.copy( stackSize = 3 + it.order, stackMaxSize = 11, ) } val title = when (it) { is MultiWalletSeedPhraseUM.Import -&gt; R.string.onboarding_seed_intro_button_import is MultiWalletSeedPhraseUM.GenerateSeedPhrase, is MultiWalletSeedPhraseUM.GeneratedWordsCheck, is MultiWalletSeedPhraseUM.Start, -&gt; R.string.onboarding_create_wallet_header } params.parentParams.titleProvider.changeTitle(text = resourceReference(title)) }</ID>
@ -123,7 +120,6 @@
<ID>ReusedModifierInstance:OnboardingVisaAccessCode.kt$OutlineTextFieldWithIcon( modifier = modifier .padding(horizontal = 16.dp) .focusRequester(focusRequester) .fillMaxWidth(), iconResId = if (state.accessCodeHidden) { R.drawable.ic_eye_outline_24 } else { R.drawable.ic_eye_off_outline_24 }, iconColor = TangemTheme.colors.icon.primary1, onIconClick = state.onAccessCodeHideClick, value = if (reEnterAccessCodeState) { state.accessCodeSecond } else { state.accessCodeFirst }, onValueChange = if (reEnterAccessCodeState) { state.onAccessCodeSecondChange } else { state.onAccessCodeFirstChange }, label = stringResourceSafe(id = R.string.onboarding_wallet_info_title_third), isError = state.codesNotMatchError || state.atLeastMinCharsError, visualTransformation = if (state.accessCodeHidden) { PasswordVisualTransformation() } else { VisualTransformation.None }, caption = when { state.codesNotMatchError &amp;&amp; reEnterAccessCodeState -&gt; stringResourceSafe(R.string.onboarding_access_codes_doesnt_match) state.atLeastMinCharsError &amp;&amp; !reEnterAccessCodeState -&gt; stringResourceSafe(R.string.onboarding_access_code_too_short) else -&gt; null }, )</ID>
<ID>ReusedModifierInstance:OnboardingVisaPinCode.kt$PinCode( modifier = modifier, value = state.pinCode, onValueChange = state.onPinCodeChange, focusRequester = focusRequester, )</ID>
<ID>ReusedModifierInstance:OnboardingVisaWelcome.kt$Image( painter = painterResource(R.drawable.img_card_visa), contentDescription = null, modifier = modifier .align(Alignment.Center) .onSizeChanged { cardHeightPx = it.height } .widthIn(max = 512.dp) .fillMaxWidth(), )</ID>
<ID>SuspendFunSwallowedCancellation:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel$runCatching</ID>
<ID>UnreachableCode:OnboardingNoteCreateWalletModel.kt$OnboardingNoteCreateWalletModel$params.childParams.commonState.value.scanResponse ?: return@launch</ID>
<ID>UnsafeCallOnNullableType:MultiWalletBackupModel.kt$MultiWalletBackupModel$backupServiceHolder.backupService.get()!!</ID>
<ID>UnsafeCallOnNullableType:OnboardingEntryModel.kt$OnboardingEntryModel$userWalletsListManager.asLockable()?.isLocked!!</ID>
@ -136,8 +132,6 @@
<ID>UseEmptyCounterpart:OnboardingVisaAnalyticsEvent.kt$OnboardingVisaAnalyticsEvent$mapOf()</ID>
<ID>UseEmptyCounterpart:VisaAnalyticsEvent.kt$VisaAnalyticsEvent$mapOf()</ID>
<ID>UseEmptyCounterpart:WalletArtwork.kt$listOf()</ID>
<ID>UseOrEmpty:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel$backupService.backupCardIds.firstOrNull()?.lastMasked() ?: ""</ID>
<ID>UseOrEmpty:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel$backupService.backupCardIds.getOrNull(cardIndex + 1) ?.lastMasked() ?: ""</ID>
<ID>UseOrEmpty:OnboardingVisaPinCode.kt$value.getOrNull(index)?.toString() ?: ""</ID>
</CurrentIssues>
</SmellBaseline>

View file

@ -9,7 +9,6 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.card.repository.CardRepository
import com.tangem.domain.feedback.GetWalletMetaInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
@ -25,6 +24,7 @@ import com.tangem.domain.wallets.builder.ColdUserWalletBuilder
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.domain.wallets.usecase.SaveWalletUseCase
import com.tangem.domain.wallets.usecase.SyncWalletWithRemoteUseCase
import com.tangem.domain.wallets.usecase.UpdateWalletUseCase
import com.tangem.features.onboarding.v2.common.ui.CantLeaveBackupDialog
import com.tangem.features.onboarding.v2.impl.R
@ -63,6 +63,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
private val saveWalletUseCase: SaveWalletUseCase,
private val getUserWalletsUseCase: GetWalletsUseCase,
private val updateWalletUseCase: UpdateWalletUseCase,
private val syncWalletWithRemoteUseCase: SyncWalletWithRemoteUseCase,
private val cardRepository: CardRepository,
private val onboardingRepository: OnboardingRepository,
private val walletsRepository: WalletsRepository,
@ -76,7 +77,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
private val backupCardIds = backupServiceHolder.backupService.get()?.backupCardIds.orEmpty()
private var walletHasBackupError = false
private var hasWalletBackupError = false
private var hasRing = false
val uiState = _uiState.asStateFlow()
@ -182,9 +183,9 @@ internal class MultiWalletFinalizeModel @Inject constructor(
_uiState.update { st ->
st.copy(
step = MultiWalletFinalizeUM.Step.BackupDevice1,
isRing = backupService.backupCardsBatchIds.getOrNull(0)?.let { isRing(it) } == true,
isRing = backupService.backupCardsBatchIds.getOrNull(0)?.let(::isRing) == true,
scanPrimary = false,
cardNumber = backupService.backupCardIds.firstOrNull()?.lastMasked() ?: "",
cardNumber = backupService.backupCardIds.firstOrNull()?.lastMasked().orEmpty(),
)
}
}
@ -207,7 +208,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
is CompletionResult.Success -> {
val backupValidator = BackupValidator()
if (backupValidator.isValidBackupStatus(CardDTO(result.data)).not()) {
walletHasBackupError = true
hasWalletBackupError = true
}
if (backupService.currentState == BackupService.State.Finished) {
@ -220,7 +221,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
isRing = backupService.backupCardsBatchIds
.getOrNull(cardIndex + 1)?.let { isRing(it) } == true,
cardNumber = backupService.backupCardIds.getOrNull(cardIndex + 1)
?.lastMasked() ?: "",
?.lastMasked().orEmpty(),
)
}
}
@ -246,7 +247,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
// Validate wallet before saving
// If something went wrong - start full reset flow
if (walletHasBackupError || !backupValidator.isValidFull(scanResponse.card)) {
if (hasWalletBackupError || !backupValidator.isValidFull(scanResponse.card)) {
startFullResetFlow.emit(
userWalletCreated.copy(
scanResponse = scanResponse.updateScanResponseAfterBackup(),
@ -269,16 +270,16 @@ internal class MultiWalletFinalizeModel @Inject constructor(
}
OnboardingMultiWalletComponent.Mode.AddBackup -> {
val userWallet = getUserWalletsUseCase.invokeSync()
.firstOrNull {
it is UserWallet.Cold &&
it.scanResponse.primaryCard?.cardId == scanResponse.primaryCard?.cardId
.firstOrNull { userWallet ->
userWallet is UserWallet.Cold &&
userWallet.scanResponse.primaryCard?.cardId == scanResponse.primaryCard?.cardId
}
?: userWalletCreated
updateWalletUseCase.invoke(
userWalletId = userWallet.walletId,
update = {
it.requireColdWallet().copy(
update = { wallet ->
wallet.requireColdWallet().copy(
scanResponse = scanResponse.updateScanResponseAfterBackup(),
)
},
@ -313,20 +314,8 @@ internal class MultiWalletFinalizeModel @Inject constructor(
it.copy(resultUserWallet = userWallet)
}
if (userWallet.scanResponse.cardTypesResolver.isWallet2() && userWallet.isImported) {
launch(NonCancellable) {
runCatching {
walletsRepository.markWallet2WasCreated(userWallet.walletId)
}
}
}
if (userWallet.isMultiCurrency) {
launch(NonCancellable) {
runCatching {
walletsRepository.createWallet(userWallet.walletId)
}
}
launch(NonCancellable) {
syncWalletWithRemoteUseCase(userWalletId = userWallet.walletId)
}
// user wallet is fully created and saved, remove scan response from preferences
@ -343,7 +332,7 @@ internal class MultiWalletFinalizeModel @Inject constructor(
return requireNotNull(
value = coldUserWalletBuilderFactory.create(scanResponse = scanResponse)
.backupCardsIds(backupCardIds.toSet())
.hasBackupError(walletHasBackupError)
.hasBackupError(hasWalletBackupError)
.build(),
lazyMessage = { "User wallet not created" },
)