diff --git a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt index d449675c38..f6d5141d1e 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/WalletsDomainModule.kt @@ -515,4 +515,10 @@ internal object WalletsDomainModule { ): HasSecuredWalletsUseCase { return HasSecuredWalletsUseCase(userWalletsListRepository = userWalletsListRepository) } + + @Provides + @Singleton + fun provideSyncWalletWithRemoteUseCase(walletsRepository: WalletsRepository): SyncWalletWithRemoteUseCase { + return SyncWalletWithRemoteUseCase(walletsRepository = walletsRepository) + } } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt index dbee720cef..d3e09a5f52 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/TangemTechApi.kt @@ -50,9 +50,6 @@ interface TangemTechApi { @Body userTokens: UserTokensResponse, ): ApiResponse - @POST("v1/user-tokens") - suspend fun markUserWallerWasCreated(@Body body: MarkUserWalletWasCreatedBody): ApiResponse - /** Returns referral status by [walletId] */ @GET("v1/referral/{walletId}") suspend fun getReferralStatus(@Path("walletId") walletId: String): ApiResponse diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/converters/WalletIdBodyConverter.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/converters/WalletIdBodyConverter.kt similarity index 52% rename from data/wallets/src/main/java/com/tangem/data/wallets/converters/WalletIdBodyConverter.kt rename to core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/converters/WalletIdBodyConverter.kt index fe6f1429a5..0e65ff47c9 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/converters/WalletIdBodyConverter.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/converters/WalletIdBodyConverter.kt @@ -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): WalletIdBody { + fun convert(userWallet: UserWallet, publicKeys: Map? = 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, ) }, ) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/MarkUserWalletWasCreatedBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/MarkUserWalletWasCreatedBody.kt deleted file mode 100644 index 0c4d2a5bd8..0000000000 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/MarkUserWalletWasCreatedBody.kt +++ /dev/null @@ -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, -) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt index b1b4d31f95..c9602c0247 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/UserTokensResponse.kt @@ -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 = 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) diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletBody.kt index 806199d729..87a2d7a563 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletBody.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletBody.kt @@ -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, ) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt index d8908b2d9f..cb4bd5f2df 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletIdBody.kt @@ -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? = null, -) { - - @JsonClass(generateAdapter = false) - enum class WalletType { - @Json(name = "card") - COLD, - - @Json(name = "mobile") - HOT, - } -} \ No newline at end of file +) \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletType.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletType.kt new file mode 100644 index 0000000000..417d95162b --- /dev/null +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/WalletType.kt @@ -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 + } + } + } +} \ No newline at end of file diff --git a/data/wallets/src/test/java/com/tangem/data/wallets/converters/WalletIdBodyConverterTest.kt b/core/datasource/src/test/kotlin/com/tangem/datasource/api/tangemTech/converters/WalletIdBodyConverterTest.kt similarity index 91% rename from data/wallets/src/test/java/com/tangem/data/wallets/converters/WalletIdBodyConverterTest.kt rename to core/datasource/src/test/kotlin/com/tangem/datasource/api/tangemTech/converters/WalletIdBodyConverterTest.kt index af8f47a2a9..299deb3a22 100644 --- a/data/wallets/src/test/java/com/tangem/data/wallets/converters/WalletIdBodyConverterTest.kt +++ b/core/datasource/src/test/kotlin/com/tangem/datasource/api/tangemTech/converters/WalletIdBodyConverterTest.kt @@ -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(), ), diff --git a/data/account/detekt-baseline-debug.xml b/data/account/detekt-baseline-debug.xml index b1e2281ba5..f0b34d22a4 100644 --- a/data/account/detekt-baseline-debug.xml +++ b/data/account/detekt-baseline-debug.xml @@ -4,7 +4,6 @@ CanBeNonNullable:FetchWalletAccountsErrorHandler.kt$FetchWalletAccountsErrorHandler$savedAccountsResponse: GetWalletAccountsResponse? MultilineLambdaItParameter:DefaultAccountsCRUDRepository.kt$DefaultAccountsCRUDRepository${ if (it is HttpException && it.code == HttpException.Code.NOT_MODIFIED) { null } else { throw it } } - MultilineLambdaItParameter:FetchWalletAccountsErrorHandler.kt$FetchWalletAccountsErrorHandler${ it.copy( tokens = UserTokensResponseAccountIdEnricher(userWalletId = userWalletId, tokens = it.tokens), ) } MultilineLambdaItParameter:GetWalletAccountsResponseExt.kt${ enrichedTokensByAccountId[it].orEmpty().map { token -> // Tokens from unexisting accounts should be copied to the main account token.copy(accountId = accountDTO.id) } } NoNameShadowing:GetWalletAccountsResponseExt.kt$tokens NullableToStringCall:AccountListCryptoCurrenciesProducer.kt$AccountListCryptoCurrenciesProducer$${this::class.simpleName} diff --git a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt index b8cc75cc58..9e86b4848b 100644 --- a/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt +++ b/data/account/src/main/kotlin/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandler.kt @@ -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), ) } diff --git a/data/account/src/test/java/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandlerTest.kt b/data/account/src/test/java/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandlerTest.kt index e4495ab395..3c8951fa2b 100644 --- a/data/account/src/test/java/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandlerTest.kt +++ b/data/account/src/test/java/com/tangem/data/account/fetcher/FetchWalletAccountsErrorHandlerTest.kt @@ -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) diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt index 990995357d..61f43d9ad7 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/UserTokensSaver.kt @@ -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 = { diff --git a/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt b/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt index 8158a250c7..fb49316219 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/di/DataCommonModule.kt @@ -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, diff --git a/data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensSaverTest.kt b/data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensSaverTest.kt index 1b5bdab260..24c2d207e7 100644 --- a/data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensSaverTest.kt +++ b/data/common/src/test/kotlin/com/tangem/data/common/currency/UserTokensSaverTest.kt @@ -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 { @@ -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 { + 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 @@ -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 { + 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) diff --git a/data/wallets/detekt-baseline-debug.xml b/data/wallets/detekt-baseline-debug.xml index fa109771d4..26f5591932 100644 --- a/data/wallets/detekt-baseline-debug.xml +++ b/data/wallets/detekt-baseline-debug.xml @@ -2,10 +2,6 @@ - BooleanPropertyNaming:DefaultWalletsRepository.kt$DefaultWalletsRepository$val legacySaveWalletsInTheApp = appPreferencesStore.getSyncOrNull( key = PreferencesKeys.SAVE_USER_WALLETS_KEY, ) - BooleanPropertyNaming:DefaultWalletsRepository.kt$DefaultWalletsRepository$val legacyShouldSaveAccessCode = appPreferencesStore.getSyncOrNull( key = PreferencesKeys.SHOULD_SAVE_ACCESS_CODES_KEY, ) - BooleanPropertyNaming:DefaultWalletsRepository.kt$DefaultWalletsRepository$val requireAccessCode = appPreferencesStore.getSyncOrNull( key = PreferencesKeys.REQUIRE_ACCESS_CODE_KEY, ) - BooleanPropertyNaming:DefaultWalletsRepository.kt$DefaultWalletsRepository$val useBiometricAuthentication = appPreferencesStore.getSyncOrNull( key = PreferencesKeys.USE_BIOMETRIC_AUTHENTICATION_KEY, ) MultilineLambdaItParameter:DefaultColdMapDerivationsRepository.kt$DefaultColdMapDerivationsRepository${ networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, userWallet = userWallet, ) } MultilineLambdaItParameter:DefaultDerivationsRepository.kt$DefaultDerivationsRepository${ userWallet.update(it.first) it.second } MultilineLambdaItParameter:DefaultHotMapDerivationsRepository.kt$DefaultHotMapDerivationsRepository${ networkFactory.create( blockchain = Blockchain.fromNetworkId(it.value) ?: return@mapNotNull null, extraDerivationPath = null, userWallet = userWallet, ) } @@ -14,19 +10,9 @@ MultilineLambdaItParameter:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository${ while (true) { emit(toState(id, it.attempts, it.deadline, it.bootCount)) val remaining = remainingSeconds(it.deadline, it.bootCount) if (remaining <= 0) break delay(timeMillis = 1000) } } MultilineLambdaItParameter:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor${ block(UnlockHotWallet(hotWalletId, it)).also { hotWalletPasswordRequester.successfulAuthentication() hotWalletPasswordRequester.dismiss() } } MultilineLambdaItParameter:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor${ tangemHotSdk.getContextUnlock(it).also { unlockHotWallet -> contextualUnlockHotWallet[hotWalletId] = unlockHotWallet } } - MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ if (it is HttpException && it.code == HttpException.Code.NOT_FOUND) { Status.NOTIFIED } else { Status.NOT_NEEDED } } - MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ it.getOrDefault( key = userWalletId, defaultValue = SeedPhraseNotificationsStatus.NOT_NEEDED, ) } - MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ it.setObjectMap( key = PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY, value = it.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY) .plus(userWalletId.stringValue to isEnabled), ) } - MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ it.setObjectMap( key = PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY, value = it.getObjectMap<Boolean>(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY) .plus(userWalletId.stringValue to false), ) } - MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ it.setObjectMap( key = PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY, value = it.getObjectMap<Boolean>(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY) .plus(userWalletId.stringValue to true), ) } - MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ it.toMutableMap().apply { this[id] = value } } - MultilineLambdaItParameter:DefaultWalletsRepository.kt$DefaultWalletsRepository${ val added = it[PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY].orEmpty() it[PreferencesKeys.ADDED_WALLETS_WITH_RING_KEY] = added + userWalletId.stringValue } MultilineLambdaItParameter:TangemHotWalletSigner.kt$TangemHotWalletSigner${ Timber.e(it) return if (it is TangemSdkError) { CompletionResult.Failure(it) } else { CompletionResult.Failure(TangemSdkError.ExceptionError(it)) } } - MultilineLambdaItParameter:WalletIdBodyConverter.kt$WalletIdBodyConverter${ CardInfoBody( cardId = it.key, cardPublicKey = it.value, ) } NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, count, deadline, boot) NamedArguments:DefaultHotWalletAccessCodeAttemptsRepository.kt$DefaultHotWalletAccessCodeAttemptsRepository$toState(id, it.attempts, it.deadline, it.bootCount) - NoNameShadowing:DefaultWalletsRepository.kt$DefaultWalletsRepository$error - NoNameShadowing:DefaultWalletsRepository.kt$DefaultWalletsRepository${ UserWalletId(it.key) } SuspendFunSwallowedCancellation:DefaultHotWalletAccessor.kt$DefaultHotWalletAccessor$runCatching SuspendFunSwallowedCancellation:TangemHotWalletSigner.kt$TangemHotWalletSigner$runCatching UnnecessaryLet:MissedDerivationsFinder.kt$MissedDerivationsFinder$let(::findByNetworks) diff --git a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt index 6824412ac3..ba985743b4 100644 --- a/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt +++ b/data/wallets/src/main/java/com/tangem/data/wallets/DefaultWalletsRepository.kt @@ -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> = appPreferencesStore .getObjectMap(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(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY) + value = mutablePreferences.getObjectMap(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(PreferencesKeys.WALLETS_NFT_ENABLED_STATES_KEY) + value = mutablePreferences.getObjectMap(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(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY) + value = mutablePreferences.getObjectMap(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() + }, + ) } } \ No newline at end of file diff --git a/detekt_baseline_report.txt b/detekt_baseline_report.txt index fac2141bcf..dd34c82236 100644 --- a/detekt_baseline_report.txt +++ b/detekt_baseline_report.txt @@ -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 diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt index a10a8c5492..2cbdf16c5c 100644 --- a/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/repository/WalletsRepository.kt @@ -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 diff --git a/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SyncWalletWithRemoteUseCase.kt b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SyncWalletWithRemoteUseCase.kt new file mode 100644 index 0000000000..1f111425fc --- /dev/null +++ b/domain/wallets/src/main/java/com/tangem/domain/wallets/usecase/SyncWalletWithRemoteUseCase.kt @@ -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) + } +} \ No newline at end of file diff --git a/features/hot-wallet/impl/detekt-baseline-debug.xml b/features/hot-wallet/impl/detekt-baseline-debug.xml index 75e6e8dacb..7c9a0a04ef 100644 --- a/features/hot-wallet/impl/detekt-baseline-debug.xml +++ b/features/hot-wallet/impl/detekt-baseline-debug.xml @@ -25,7 +25,6 @@ MultilineLambdaItParameter:AddExistingWalletImportModel.kt$AddExistingWalletImportModel${ Timber.e(it) setImportProgress(false) } MultilineLambdaItParameter:AddExistingWalletImportModel.kt$AddExistingWalletImportModel${ setImportProgress(false) when (it) { is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet") is SaveWalletError.WalletAlreadySaved -> { uiMessageSender.send( SnackbarMessage(resourceReference(R.string.hw_import_seed_phrase_already_imported)), ) } } } MultilineLambdaItParameter:CreateHardwareWalletModel.kt$CreateHardwareWalletModel${ delay(HIDE_PROGRESS_DELAY) setLoading(false) when (it) { is SaveWalletError.DataError -> Timber.e(it.toString(), "Unable to save user wallet") is SaveWalletError.WalletAlreadySaved -> { userWalletsListRepository.unlock( userWalletId = userWallet.walletId, unlockMethod = UserWalletsListRepository.UnlockMethod.Scan(scanResponse), ).onRight { router.replaceAll(AppRoute.Wallet) } } } } - MultilineLambdaItParameter:CreateMobileWalletModel.kt$CreateMobileWalletModel${ Timber.e(it) uiState.update { it.copy(createButtonLoading = false) } } MultilineLambdaItParameter:ForgetWalletModel.kt$ForgetWalletModel${ Timber.e("Unable to delete wallet: $it") uiMessageSender.send( message = SnackbarMessage(resourceReference(R.string.common_unknown_error)), ) return@launch } MultilineLambdaItParameter:ForgetWalletModel.kt$ForgetWalletModel${ val newValue = !it.firstCheckboxChecked it.copy( firstCheckboxChecked = newValue, isForgetButtonEnabled = newValue && it.secondCheckboxChecked, ) } MultilineLambdaItParameter:ForgetWalletModel.kt$ForgetWalletModel${ val newValue = !it.secondCheckboxChecked it.copy( secondCheckboxChecked = newValue, isForgetButtonEnabled = it.firstCheckboxChecked && newValue, ) } @@ -52,7 +51,6 @@ 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 && BackupValidator.isValidFull(it.card).not() } val otherWalletAndAlreadyCreated by lazy { userWallet?.walletId != params.userWalletId && it.card.wallets.map { it.curve }.toSet().isNotEmpty() } if (userWallet != null && (sameWalletButNotFinishedBackup || otherWalletAndAlreadyCreated)) { startResetCardsFlow.emit(userWallet) return@doOnSuccess } delay(DELAY_SDK_DIALOG_CLOSE) tangemSdkManager.changeDisplayedCardIdNumbersCount(it) navigateToUpgradeFlow(it) } MultilineLambdaItParameter:ViewPhraseContent.kt${ EnumeratedTwoColumnGridItem( index = it + 1, mnemonic = "word${it + 1}", ) } MultilineLambdaItParameter:ViewPhraseModel.kt$ViewPhraseModel${ it.copy( words = words.mapIndexed { index, s -> EnumeratedTwoColumnGridItem(index + 1, s) }.toImmutableList(), ) } - NoNameShadowing:CreateMobileWalletModel.kt$CreateMobileWalletModel${ it.copy(createButtonLoading = false) } NoNameShadowing:ManualBackupCheckModel.kt$ManualBackupCheckModel${ it.copy(completeButtonProgress = false) } PropertyUsedBeforeDeclaration:AddExistingWalletImportModel.kt$AddExistingWalletImportModel$uiState 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, ), ) @@ -60,7 +58,6 @@ ReusedModifierInstance:HotWalletStepper.kt$TangemTopAppBar( startButton = if (state.showBackButton) { TopAppBarButtonUM.Back(onBackClick) } else { null }, endButton = when { state.showSkipButton -> TopAppBarButtonUM.Text( text = resourceReference(R.string.common_skip), onClicked = onSkipClick, ) state.showFeedbackButton -> TopAppBarButtonUM.Icon( iconRes = R.drawable.ic_chat_24, onClicked = onFeedbackClick, ) else -> null }, title = state.title, containerColor = TangemTheme.colors.background.primary, modifier = modifier, titleAlignment = Alignment.CenterHorizontally, ) SuspendFunSwallowedCancellation:AccessCodeModel.kt$AccessCodeModel$runCatching SuspendFunSwallowedCancellation:AddExistingWalletImportModel.kt$AddExistingWalletImportModel$runCatching - SuspendFunSwallowedCancellation:CreateMobileWalletModel.kt$CreateMobileWalletModel$runCatching SuspendFunSwallowedCancellation:ManualBackupCheckModel.kt$ManualBackupCheckModel$runCatching SuspendFunSwallowedCancellation:ManualBackupPhraseModel.kt$ManualBackupPhraseModel$runCatching diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt index 800b694885..47d9334760 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/createmobilewallet/CreateMobileWalletModel.kt @@ -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) } } } } diff --git a/features/onboarding-v2/impl/detekt-baseline-debug.xml b/features/onboarding-v2/impl/detekt-baseline-debug.xml index e4d1590e43..3eced7c5d9 100644 --- a/features/onboarding-v2/impl/detekt-baseline-debug.xml +++ b/features/onboarding-v2/impl/detekt-baseline-debug.xml @@ -11,7 +11,6 @@ BooleanPropertyNaming:MultiWalletBackupUM.kt$MultiWalletBackupUMDialog$val warningCancelColor: Boolean = false BooleanPropertyNaming:MultiWalletCreateWalletModel.kt$MultiWalletCreateWalletModel$val cardDoesNotSupportBackup = result.data.card.settings.isBackupAllowed.not() BooleanPropertyNaming:MultiWalletCreateWalletUM.kt$MultiWalletCreateWalletUM$val showOtherOptionsButton: Boolean - BooleanPropertyNaming:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel$private var walletHasBackupError = false BooleanPropertyNaming:MultiWalletFinalizeUM.kt$MultiWalletFinalizeUM$val scanPrimary: Boolean = true BooleanPropertyNaming:MultiWalletSeedPhraseUM.kt$MultiWalletSeedPhraseUM.GeneratedWordsCheck$val createWalletButtonEnabled: Boolean = false BooleanPropertyNaming:MultiWalletSeedPhraseUM.kt$MultiWalletSeedPhraseUM.GeneratedWordsCheck$val createWalletButtonProgress: Boolean = false @@ -76,8 +75,6 @@ MultilineLambdaItParameter:MultiWalletCreateWalletComponent.kt$MultiWalletCreateWalletComponent${ it.copy( stackSize = 2, stackMaxSize = 9, ) } MultilineLambdaItParameter:MultiWalletCreateWalletModel.kt$MultiWalletCreateWalletModel${ it.copy( currentScanResponse = it.currentScanResponse.copy( card = result.data.card, derivedKeys = result.data.derivedKeys, primaryCard = result.data.primaryCard, ), ) } MultilineLambdaItParameter:MultiWalletFinalizeComponent.kt$MultiWalletFinalizeComponent${ it.copy( stackSize = 7, stackMaxSize = 9, ) } - MultilineLambdaItParameter:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel${ it is UserWallet.Cold && it.scanResponse.primaryCard?.cardId == scanResponse.primaryCard?.cardId } - MultilineLambdaItParameter:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel${ it.requireColdWallet().copy( scanResponse = scanResponse.updateScanResponseAfterBackup(), ) } MultilineLambdaItParameter:MultiWalletScanPrimaryComponent.kt$MultiWalletScanPrimaryComponent${ it.copy( stackSize = 4, stackMaxSize = 9, ) } MultilineLambdaItParameter:MultiWalletScanPrimaryModel.kt$MultiWalletScanPrimaryModel${ it.copy( currentScanResponse = scanResponse.copy( primaryCard = result.data, ), ) } MultilineLambdaItParameter:MultiWalletSeedPhraseComponent.kt$MultiWalletSeedPhraseComponent${ // change stepper state based on the stack of the current step @Suppress("MagicNumber") params.innerNavigation.update { st -> st.copy( stackSize = 3 + it.order, stackMaxSize = 11, ) } val title = when (it) { is MultiWalletSeedPhraseUM.Import -> R.string.onboarding_seed_intro_button_import is MultiWalletSeedPhraseUM.GenerateSeedPhrase, is MultiWalletSeedPhraseUM.GeneratedWordsCheck, is MultiWalletSeedPhraseUM.Start, -> R.string.onboarding_create_wallet_header } params.parentParams.titleProvider.changeTitle(text = resourceReference(title)) } @@ -123,7 +120,6 @@ 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 && reEnterAccessCodeState -> stringResourceSafe(R.string.onboarding_access_codes_doesnt_match) state.atLeastMinCharsError && !reEnterAccessCodeState -> stringResourceSafe(R.string.onboarding_access_code_too_short) else -> null }, ) ReusedModifierInstance:OnboardingVisaPinCode.kt$PinCode( modifier = modifier, value = state.pinCode, onValueChange = state.onPinCodeChange, focusRequester = focusRequester, ) 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(), ) - SuspendFunSwallowedCancellation:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel$runCatching UnreachableCode:OnboardingNoteCreateWalletModel.kt$OnboardingNoteCreateWalletModel$params.childParams.commonState.value.scanResponse ?: return@launch UnsafeCallOnNullableType:MultiWalletBackupModel.kt$MultiWalletBackupModel$backupServiceHolder.backupService.get()!! UnsafeCallOnNullableType:OnboardingEntryModel.kt$OnboardingEntryModel$userWalletsListManager.asLockable()?.isLocked!! @@ -136,8 +132,6 @@ UseEmptyCounterpart:OnboardingVisaAnalyticsEvent.kt$OnboardingVisaAnalyticsEvent$mapOf() UseEmptyCounterpart:VisaAnalyticsEvent.kt$VisaAnalyticsEvent$mapOf() UseEmptyCounterpart:WalletArtwork.kt$listOf() - UseOrEmpty:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel$backupService.backupCardIds.firstOrNull()?.lastMasked() ?: "" - UseOrEmpty:MultiWalletFinalizeModel.kt$MultiWalletFinalizeModel$backupService.backupCardIds.getOrNull(cardIndex + 1) ?.lastMasked() ?: "" UseOrEmpty:OnboardingVisaPinCode.kt$value.getOrNull(index)?.toString() ?: "" diff --git a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt index 3fa335c815..7664244a73 100644 --- a/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt +++ b/features/onboarding-v2/impl/src/main/kotlin/com/tangem/features/onboarding/v2/multiwallet/impl/child/finalize/model/MultiWalletFinalizeModel.kt @@ -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" }, )