Updated on 2026-08-14
This commit is contained in:
parent
47a323859a
commit
750a696a84
17 changed files with 636 additions and 200 deletions
|
|
@ -22,7 +22,7 @@ data class UserTokensResponse(
|
|||
@Json(name = "symbol") val symbol: String,
|
||||
@Json(name = "decimals") val decimals: Int,
|
||||
@Json(name = "contractAddress") val contractAddress: String?,
|
||||
@Json(name = "addresses") val list: List<String>? = null,
|
||||
@Json(name = "addresses") val addresses: List<String>? = null,
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
val otherToken = other as? Token ?: return false
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ tasks.withType<Test>().configureEach {
|
|||
dependencies {
|
||||
/* Core */
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.utils)
|
||||
|
||||
/* Domain */
|
||||
implementation(projects.domain.demo)
|
||||
|
|
@ -23,6 +24,9 @@ dependencies {
|
|||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.notifications.toggles)
|
||||
implementation(projects.domain.networks)
|
||||
implementation(projects.domain.wallets)
|
||||
|
||||
/* Libs - SDK */
|
||||
implementation(tangemDeps.blockchain)
|
||||
|
|
@ -47,4 +51,5 @@ dependencies {
|
|||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.moshi)
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusProducer
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
||||
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
class UserTokensResponseAddressesEnricher @Inject constructor(
|
||||
private val notificationsFeatureToggles: NotificationsFeatureToggles,
|
||||
private val walletsRepository: WalletsRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, response: UserTokensResponse): UserTokensResponse {
|
||||
if (!notificationsFeatureToggles.isNotificationsEnabled) {
|
||||
return response
|
||||
}
|
||||
|
||||
val isNotificationsEnabled = walletsRepository.isNotificationsEnabled(userWalletId)
|
||||
|
||||
return withContext(dispatchers.default) {
|
||||
val networksStatuses = if (isNotificationsEnabled) {
|
||||
multiNetworkStatusSupplier.invoke(MultiNetworkStatusProducer.Params(userWalletId)).first()
|
||||
} else {
|
||||
emptySet()
|
||||
}
|
||||
|
||||
val enrichedTokens = response.tokens.map { token ->
|
||||
if (isNotificationsEnabled) {
|
||||
val matchingNetwork = networksStatuses.find { status ->
|
||||
status.network.backendId == token.networkId &&
|
||||
status.network.derivationPath.value == token.derivationPath
|
||||
} ?: return@map token
|
||||
|
||||
val networkAddress = when (matchingNetwork.value) {
|
||||
is NetworkStatus.Verified -> (matchingNetwork.value as NetworkStatus.Verified).address
|
||||
is NetworkStatus.NoAccount -> (matchingNetwork.value as NetworkStatus.NoAccount).address
|
||||
else -> null
|
||||
}
|
||||
|
||||
val addresses = networkAddress
|
||||
?.availableAddresses
|
||||
?.map { it.value }
|
||||
?.toList()
|
||||
.orEmpty()
|
||||
|
||||
token.copy(addresses = addresses)
|
||||
} else {
|
||||
token.copy(addresses = emptyList())
|
||||
}
|
||||
}
|
||||
|
||||
response.copy(tokens = enrichedTokens, notifyStatus = isNotificationsEnabled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
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.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
class UserTokensSaver constructor(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val userTokensResponseAddressesEnricher: UserTokensResponseAddressesEnricher,
|
||||
) {
|
||||
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
|
||||
|
||||
suspend fun store(userWalletId: UserWalletId, response: UserTokensResponse, useEnricher: Boolean = true) =
|
||||
withContext(dispatchers.io) {
|
||||
val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response)
|
||||
val enrichedUserTokensResponse = if (useEnricher) {
|
||||
userTokensResponseAddressesEnricher(
|
||||
userWalletId = userWalletId,
|
||||
response = compatibleUserTokensResponse,
|
||||
)
|
||||
} else {
|
||||
compatibleUserTokensResponse
|
||||
}
|
||||
appPreferencesStore.storeObject(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue),
|
||||
value = enrichedUserTokensResponse,
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun storeAndPush(userWalletId: UserWalletId, response: UserTokensResponse) {
|
||||
val enrichedUserTokensResponse = userTokensResponseAddressesEnricher(
|
||||
userWalletId = userWalletId,
|
||||
response = response,
|
||||
)
|
||||
store(userWalletId, enrichedUserTokensResponse, false)
|
||||
push(userWalletId, enrichedUserTokensResponse, false)
|
||||
}
|
||||
|
||||
suspend fun push(userWalletId: UserWalletId, response: UserTokensResponse, useEnricher: Boolean = true) =
|
||||
withContext(dispatchers.io) {
|
||||
val enrichedUserTokensResponse = if (useEnricher) {
|
||||
userTokensResponseAddressesEnricher(
|
||||
userWalletId = userWalletId,
|
||||
response = response,
|
||||
)
|
||||
} else {
|
||||
response
|
||||
}
|
||||
safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedUserTokensResponse).bind() }) {
|
||||
Timber.e(it, "Unable to push user tokens for: ${userWalletId.stringValue}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,17 @@ package com.tangem.data.common.di
|
|||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.DefaultCardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseAddressesEnricher
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.demo.DemoConfig
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
||||
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -30,4 +38,36 @@ internal object DataCommonModule {
|
|||
userTokensResponseStore = userTokensResponseStore,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserTokensEncricher(
|
||||
notificationsFeatureToggles: NotificationsFeatureToggles,
|
||||
walletsRepository: WalletsRepository,
|
||||
multiNetworkStatusSupplier: MultiNetworkStatusSupplier,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): UserTokensResponseAddressesEnricher {
|
||||
return UserTokensResponseAddressesEnricher(
|
||||
notificationsFeatureToggles = notificationsFeatureToggles,
|
||||
walletsRepository = walletsRepository,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideUserTokensSaver(
|
||||
tangemTechApi: TangemTechApi,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
enricher: UserTokensResponseAddressesEnricher,
|
||||
): UserTokensSaver {
|
||||
return UserTokensSaver(
|
||||
tangemTechApi = tangemTechApi,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = dispatchers,
|
||||
userTokensResponseAddressesEnricher = enricher,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.data.tokens.utils
|
||||
package com.tangem.data.common.tokens
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchainsdk.utils.fromNetworkId
|
||||
|
|
@ -9,7 +9,7 @@ import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
|||
* Helper to apply compatibility changes for [UserTokensResponse] to support old saved tokens
|
||||
* in new application with new IDs
|
||||
*/
|
||||
class UserTokensBackwardCompatibility {
|
||||
internal class UserTokensBackwardCompatibility {
|
||||
|
||||
fun applyCompatibilityAndGetUpdated(userTokensResponse: UserTokensResponse): UserTokensResponse {
|
||||
return userTokensResponse.copy(
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.networks.multi.MultiNetworkStatusSupplier
|
||||
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||
import com.tangem.domain.tokens.model.NetworkStatus
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import com.tangem.domain.models.StatusSource
|
||||
import com.tangem.domain.tokens.model.NetworkAddress
|
||||
import io.mockk.clearAllMocks
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class UserTokensResponseAddressesEnricherTest {
|
||||
|
||||
private lateinit var notificationsFeatureToggles: NotificationsFeatureToggles
|
||||
private lateinit var walletsRepository: WalletsRepository
|
||||
private val dispatchers: CoroutineDispatcherProvider = TestingCoroutineDispatcherProvider()
|
||||
private lateinit var multiNetworkStatusSupplier: MultiNetworkStatusSupplier
|
||||
private lateinit var enricher: UserTokensResponseAddressesEnricher
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
notificationsFeatureToggles = mockk()
|
||||
walletsRepository = mockk()
|
||||
multiNetworkStatusSupplier = mockk()
|
||||
|
||||
enricher = UserTokensResponseAddressesEnricher(
|
||||
notificationsFeatureToggles = notificationsFeatureToggles,
|
||||
walletsRepository = walletsRepository,
|
||||
dispatchers = dispatchers,
|
||||
multiNetworkStatusSupplier = multiNetworkStatusSupplier,
|
||||
)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
clearAllMocks()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN notifications are disabled globally WHEN invoke THEN return original response`() = runTest {
|
||||
// GIVEN
|
||||
val userWalletId = UserWalletId("1234567890abcdef")
|
||||
val token = createToken()
|
||||
val response = createUserTokensResponse(tokens = listOf(token))
|
||||
every { notificationsFeatureToggles.isNotificationsEnabled } returns false
|
||||
|
||||
// WHEN
|
||||
val result = enricher(userWalletId, response)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(response)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN notifications are disabled for wallet WHEN invoke THEN return response with empty addresses`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
val userWalletId = UserWalletId("1234567890abcdef")
|
||||
val token = createToken()
|
||||
val response = createUserTokensResponse(tokens = listOf(token))
|
||||
every { notificationsFeatureToggles.isNotificationsEnabled } returns true
|
||||
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns false
|
||||
coEvery {
|
||||
multiNetworkStatusSupplier.invoke(any())
|
||||
} returns flowOf(
|
||||
setOf(
|
||||
NetworkStatus(
|
||||
network = mockk {
|
||||
every { backendId } returns "ethereum"
|
||||
every { derivationPath.value } returns "m/44'/60'/0'/0/0"
|
||||
},
|
||||
value = NetworkStatus.Verified(
|
||||
address = mockk {
|
||||
every { availableAddresses } returns emptySet()
|
||||
},
|
||||
amounts = emptyMap(),
|
||||
pendingTransactions = emptyMap(),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = enricher(userWalletId, response)
|
||||
|
||||
// THEN
|
||||
assertThat(result.tokens).hasSize(1)
|
||||
assertThat(result.tokens[0].addresses).isEmpty()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN notifications are enabled and addresses available WHEN invoke THEN return enriched response`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
val userWalletId = UserWalletId("1234567890abcdef")
|
||||
val token = createToken()
|
||||
val response = createUserTokensResponse(tokens = listOf(token))
|
||||
val addresses = listOf("0x123", "0x456")
|
||||
|
||||
every { notificationsFeatureToggles.isNotificationsEnabled } returns true
|
||||
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true
|
||||
coEvery {
|
||||
multiNetworkStatusSupplier.invoke(any())
|
||||
} returns flowOf(
|
||||
setOf(
|
||||
NetworkStatus(
|
||||
network = mockk {
|
||||
every { backendId } returns "ethereum"
|
||||
every { derivationPath.value } returns "m/44'/60'/0'/0/0"
|
||||
},
|
||||
value = NetworkStatus.Verified(
|
||||
address = mockk {
|
||||
every { availableAddresses } returns addresses.map { address ->
|
||||
mockk<NetworkAddress.Address> {
|
||||
every { value } returns address
|
||||
}
|
||||
}.toSet()
|
||||
},
|
||||
amounts = emptyMap(),
|
||||
pendingTransactions = emptyMap(),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = enricher(userWalletId, response)
|
||||
|
||||
// THEN
|
||||
assertThat(result.tokens).hasSize(1)
|
||||
assertThat(result.tokens[0].addresses).containsExactlyElementsIn(addresses)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN notifications are enabled but no matching network WHEN invoke THEN return original token`() = runTest {
|
||||
// GIVEN
|
||||
val userWalletId = UserWalletId("1234567890abcdef")
|
||||
val token = createToken()
|
||||
val response = createUserTokensResponse(tokens = listOf(token))
|
||||
|
||||
every { notificationsFeatureToggles.isNotificationsEnabled } returns true
|
||||
coEvery { walletsRepository.isNotificationsEnabled(userWalletId) } returns true
|
||||
coEvery {
|
||||
multiNetworkStatusSupplier.invoke(any())
|
||||
} returns flowOf(
|
||||
setOf(
|
||||
NetworkStatus(
|
||||
network = mockk {
|
||||
every { backendId } returns "bitcoin"
|
||||
every { derivationPath.value } returns "m/44'/0'/0'/0/0"
|
||||
},
|
||||
value = NetworkStatus.Verified(
|
||||
address = mockk {
|
||||
every { availableAddresses } returns emptySet()
|
||||
},
|
||||
amounts = emptyMap(),
|
||||
pendingTransactions = emptyMap(),
|
||||
source = StatusSource.ACTUAL,
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = enricher(userWalletId, response)
|
||||
|
||||
// THEN
|
||||
assertThat(result.tokens).hasSize(1)
|
||||
assertThat(result.tokens[0]).isEqualTo(token)
|
||||
}
|
||||
|
||||
private fun createToken(
|
||||
networkId: String = "ethereum",
|
||||
derivationPath: String = "m/44'/60'/0'/0/0",
|
||||
list: List<String> = emptyList(),
|
||||
name: String = "Ethereum",
|
||||
symbol: String = "ETH",
|
||||
decimals: Int = 18,
|
||||
contractAddress: String? = null,
|
||||
id: String? = null,
|
||||
) = UserTokensResponse.Token(
|
||||
id = id,
|
||||
networkId = networkId,
|
||||
derivationPath = derivationPath,
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
decimals = decimals,
|
||||
contractAddress = contractAddress,
|
||||
addresses = list,
|
||||
)
|
||||
|
||||
private fun createUserTokensResponse(
|
||||
tokens: List<UserTokensResponse.Token> = emptyList(),
|
||||
version: Int = 0,
|
||||
group: UserTokensResponse.GroupType = UserTokensResponse.GroupType.NETWORK,
|
||||
sort: UserTokensResponse.SortType = UserTokensResponse.SortType.BALANCE,
|
||||
notifyStatus: Boolean? = null,
|
||||
) = UserTokensResponse(
|
||||
version = version,
|
||||
group = group,
|
||||
sort = sort,
|
||||
notifyStatus = notifyStatus,
|
||||
tokens = tokens,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,175 @@
|
|||
package com.tangem.data.common.currency
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import com.squareup.moshi.Moshi
|
||||
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.local.preferences.AppPreferencesStore
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.After
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class UserTokensSaverTest {
|
||||
|
||||
private val tangemTechApi: TangemTechApi = mockk()
|
||||
private val dataStore: DataStore<Preferences> = mockk(relaxed = true)
|
||||
private val appPreferenceStore = AppPreferencesStore(
|
||||
moshi = Moshi.Builder().build(),
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
preferencesDataStore = dataStore,
|
||||
)
|
||||
private val dispatchers: CoroutineDispatcherProvider = TestingCoroutineDispatcherProvider()
|
||||
private val userTokensResponseAddressesEnricher: UserTokensResponseAddressesEnricher = mockk()
|
||||
|
||||
private lateinit var userTokensSaver: UserTokensSaver
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
coEvery { dataStore.data } returns flowOf(mockk(relaxed = true))
|
||||
userTokensSaver = UserTokensSaver(
|
||||
tangemTechApi = tangemTechApi,
|
||||
appPreferencesStore = appPreferenceStore,
|
||||
dispatchers = dispatchers,
|
||||
userTokensResponseAddressesEnricher = userTokensResponseAddressesEnricher,
|
||||
)
|
||||
}
|
||||
|
||||
@After
|
||||
fun tearDown() {
|
||||
clearAllMocks()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallet id and response WHEN store THEN should store enriched response`() = runTest {
|
||||
// GIVEN
|
||||
val userWalletId = UserWalletId("1234567890abcdef")
|
||||
val response = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
val enrichedResponse = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
coEvery { userTokensResponseAddressesEnricher(userWalletId, response) } returns enrichedResponse
|
||||
coEvery { dataStore.updateData(any()) } returns mockk(relaxed = true)
|
||||
|
||||
// WHEN
|
||||
userTokensSaver.store(userWalletId, response)
|
||||
|
||||
// THEN
|
||||
coVerify {
|
||||
dataStore.updateData(any())
|
||||
}
|
||||
coVerify(exactly = 0) {
|
||||
tangemTechApi.saveUserTokens(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallet id and response WHEN storeAndPush THEN should store and push enriched response`() = runTest {
|
||||
// GIVEN
|
||||
val userWalletId = UserWalletId("1234567890abcdef")
|
||||
val response = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
val enrichedResponse = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
coEvery { userTokensResponseAddressesEnricher(userWalletId, response) } returns enrichedResponse
|
||||
coEvery { dataStore.updateData(any()) } returns mockk(relaxed = true)
|
||||
coEvery { tangemTechApi.saveUserTokens(any(), any()) } returns ApiResponse.Success(Unit)
|
||||
|
||||
// WHEN
|
||||
userTokensSaver.storeAndPush(userWalletId, response)
|
||||
|
||||
// THEN
|
||||
coVerify {
|
||||
dataStore.updateData(any())
|
||||
tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedResponse)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallet id and response WHEN push THEN should push enriched response`() = runTest {
|
||||
// GIVEN
|
||||
val userWalletId = UserWalletId("1234567890abcdef")
|
||||
val response = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
val enrichedResponse = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
coEvery { userTokensResponseAddressesEnricher(userWalletId, response) } returns enrichedResponse
|
||||
coEvery { tangemTechApi.saveUserTokens(any(), any()) } returns ApiResponse.Success(Unit)
|
||||
|
||||
// WHEN
|
||||
userTokensSaver.push(userWalletId, response)
|
||||
|
||||
// THEN
|
||||
coVerify {
|
||||
tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedResponse)
|
||||
}
|
||||
coVerify(exactly = 0) {
|
||||
dataStore.updateData(any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN user wallet id and response WHEN push AND api call fails THEN should log error`() = runTest {
|
||||
// GIVEN
|
||||
val userWalletId = UserWalletId("1234567890abcdef")
|
||||
val response = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
val enrichedResponse = UserTokensResponse(
|
||||
version = 0,
|
||||
group = UserTokensResponse.GroupType.NETWORK,
|
||||
sort = UserTokensResponse.SortType.BALANCE,
|
||||
tokens = emptyList(),
|
||||
)
|
||||
val error = ApiResponseError.UnknownException(Exception("API Error"))
|
||||
coEvery { userTokensResponseAddressesEnricher(userWalletId, response) } returns enrichedResponse
|
||||
coEvery { tangemTechApi.saveUserTokens(any(), any()) } returns
|
||||
ApiResponse.Error(error) as ApiResponse<Unit>
|
||||
|
||||
// WHEN
|
||||
userTokensSaver.push(userWalletId, response)
|
||||
|
||||
// THEN
|
||||
coVerify {
|
||||
tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedResponse)
|
||||
}
|
||||
coVerify(exactly = 0) {
|
||||
dataStore.updateData(any())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,19 +4,17 @@ import com.tangem.blockchain.common.Blockchain
|
|||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.currency.CryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensResponseFactory
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.data.common.currency.getNetwork
|
||||
import com.tangem.data.managetokens.utils.TokenAddressesConverter
|
||||
import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.extensions.canHandleBlockchain
|
||||
import com.tangem.domain.common.extensions.supportedBlockchains
|
||||
|
|
@ -30,8 +28,8 @@ import com.tangem.domain.walletmanager.WalletManagersFacade
|
|||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultCustomTokensRepository(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val userWalletsStore: UserWalletsStore,
|
||||
|
|
@ -39,12 +37,12 @@ internal class DefaultCustomTokensRepository(
|
|||
private val walletManagersFacade: WalletManagersFacade,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val userTokensSaver: UserTokensSaver,
|
||||
) : CustomTokensRepository {
|
||||
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
||||
private val tokenAddressConverter = TokenAddressesConverter()
|
||||
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
|
||||
|
||||
override suspend fun validateContractAddress(contractAddress: String, networkId: Network.ID): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
|
|
@ -223,7 +221,7 @@ internal class DefaultCustomTokensRepository(
|
|||
}
|
||||
|
||||
val token = userTokensResponseFactory.createResponseToken(cryptoCurrency)
|
||||
storeAndPushTokens(
|
||||
userTokensSaver.storeAndPush(
|
||||
userWalletId = userWalletId,
|
||||
response = storedCurrencies.copy(tokens = storedCurrencies.tokens.filterNot { it == token }),
|
||||
)
|
||||
|
|
@ -267,20 +265,4 @@ internal class DefaultCustomTokensRepository(
|
|||
value = sdkPath.rawPath,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
|
||||
val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response)
|
||||
appPreferencesStore.storeObject(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue),
|
||||
value = compatibleUserTokensResponse,
|
||||
)
|
||||
|
||||
pushTokens(userWalletId, response)
|
||||
}
|
||||
|
||||
private suspend fun pushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
|
||||
safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, response).bind() }) {
|
||||
Timber.e(it, "Unable to push user tokens for: ${userWalletId.stringValue}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.managetokens.di
|
|||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.data.managetokens.DefaultCustomTokensRepository
|
||||
import com.tangem.data.managetokens.DefaultManageTokensRepository
|
||||
import com.tangem.data.managetokens.utils.ManageTokensUpdateFetcher
|
||||
|
|
@ -56,14 +57,16 @@ internal object ManageTokensDataModule {
|
|||
walletManagersFacade: WalletManagersFacade,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
userTokensSaver: UserTokensSaver,
|
||||
): CustomTokensRepository {
|
||||
return DefaultCustomTokensRepository(
|
||||
tangemTechApi,
|
||||
userWalletsStore,
|
||||
appPreferencesStore,
|
||||
walletManagersFacade,
|
||||
excludedBlockchains,
|
||||
dispatchers,
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsStore = userWalletsStore,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
dispatchers = dispatchers,
|
||||
userTokensSaver = userTokensSaver,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ package com.tangem.data.tokens.di
|
|||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.common.currency.CardCryptoCurrencyFactory
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.data.tokens.repository.DefaultCurrenciesRepository
|
||||
import com.tangem.data.tokens.repository.DefaultCurrencyChecksRepository
|
||||
import com.tangem.data.tokens.repository.DefaultPolkadotAccountHealthCheckRepository
|
||||
|
|
@ -40,6 +41,7 @@ internal object TokensDataModule {
|
|||
expressServiceLoader: ExpressServiceLoader,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
tokensSaver: UserTokensSaver,
|
||||
): CurrenciesRepository {
|
||||
return DefaultCurrenciesRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
|
|
@ -51,6 +53,7 @@ internal object TokensDataModule {
|
|||
dispatchers = dispatchers,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
cardCryptoCurrencyFactory = cardCryptoCurrencyFactory,
|
||||
userTokensSaver = tokensSaver,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import com.tangem.data.common.api.safeApiCall
|
|||
import com.tangem.data.common.cache.CacheRegistry
|
||||
import com.tangem.data.common.currency.*
|
||||
import com.tangem.data.tokens.utils.CustomTokensMerger
|
||||
import com.tangem.data.tokens.utils.UserTokensBackwardCompatibility
|
||||
import com.tangem.datasource.api.common.response.ApiResponseError
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.express.models.TangemExpressValues.EMPTY_CONTRACT_ADDRESS_VALUE
|
||||
|
|
@ -19,7 +18,6 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
|
|||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getObject
|
||||
import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull
|
||||
import com.tangem.datasource.local.preferences.utils.storeObject
|
||||
import com.tangem.datasource.local.userwallet.UserWalletsStore
|
||||
import com.tangem.domain.common.extensions.canHandleBlockchain
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
|
|
@ -51,14 +49,18 @@ internal class DefaultCurrenciesRepository(
|
|||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val excludedBlockchains: ExcludedBlockchains,
|
||||
private val cardCryptoCurrencyFactory: CardCryptoCurrencyFactory,
|
||||
private val userTokensSaver: UserTokensSaver,
|
||||
) : CurrenciesRepository {
|
||||
|
||||
private val demoConfig = DemoConfig()
|
||||
private val responseCurrenciesFactory = ResponseCryptoCurrenciesFactory(excludedBlockchains)
|
||||
private val cryptoCurrencyFactory = CryptoCurrencyFactory(excludedBlockchains)
|
||||
private val userTokensResponseFactory = UserTokensResponseFactory()
|
||||
private val userTokensBackwardCompatibility = UserTokensBackwardCompatibility()
|
||||
private val customTokensMerger = CustomTokensMerger(tangemTechApi, dispatchers)
|
||||
private val customTokensMerger = CustomTokensMerger(
|
||||
tangemTechApi = tangemTechApi,
|
||||
dispatchers = dispatchers,
|
||||
userTokensSaver = userTokensSaver,
|
||||
)
|
||||
|
||||
override suspend fun saveTokens(
|
||||
userWalletId: UserWalletId,
|
||||
|
|
@ -73,8 +75,7 @@ internal class DefaultCurrenciesRepository(
|
|||
isGroupedByNetwork = isGroupedByNetwork,
|
||||
isSortedByBalance = isSortedByBalance,
|
||||
)
|
||||
|
||||
storeAndPushTokens(userWalletId, response)
|
||||
userTokensSaver.storeAndPush(userWalletId, response)
|
||||
}
|
||||
|
||||
override suspend fun saveNewCurrenciesList(userWalletId: UserWalletId, currencies: List<CryptoCurrency>) {
|
||||
|
|
@ -89,7 +90,7 @@ internal class DefaultCurrenciesRepository(
|
|||
val updatedResponse = savedResponse.copy(
|
||||
tokens = newCurrencies.map(userTokensResponseFactory::createResponseToken),
|
||||
)
|
||||
storeAndPushTokens(
|
||||
userTokensSaver.storeAndPush(
|
||||
userWalletId = userWalletId,
|
||||
response = updatedResponse,
|
||||
)
|
||||
|
|
@ -113,7 +114,7 @@ internal class DefaultCurrenciesRepository(
|
|||
val updatedResponse = savedCurrencies.copy(
|
||||
tokens = savedCurrencies.tokens + currenciesToAdd.map(userTokensResponseFactory::createResponseToken),
|
||||
)
|
||||
storeAndPushTokens(
|
||||
userTokensSaver.storeAndPush(
|
||||
userWalletId = userWalletId,
|
||||
response = updatedResponse,
|
||||
)
|
||||
|
|
@ -177,9 +178,10 @@ internal class DefaultCurrenciesRepository(
|
|||
)
|
||||
|
||||
val token = userTokensResponseFactory.createResponseToken(currency)
|
||||
storeAndPushTokens(
|
||||
val updatedResponse = savedCurrencies.copy(tokens = savedCurrencies.tokens.filterNot { it == token })
|
||||
userTokensSaver.storeAndPush(
|
||||
userWalletId = userWalletId,
|
||||
response = savedCurrencies.copy(tokens = savedCurrencies.tokens.filterNot { it == token }),
|
||||
response = updatedResponse,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -191,11 +193,12 @@ internal class DefaultCurrenciesRepository(
|
|||
)
|
||||
|
||||
val tokens = currencies.map(userTokensResponseFactory::createResponseToken)
|
||||
storeAndPushTokens(
|
||||
val updatedResponse = savedCurrencies.copy(
|
||||
tokens = savedCurrencies.tokens.filterNot(tokens::contains),
|
||||
)
|
||||
userTokensSaver.storeAndPush(
|
||||
userWalletId = userWalletId,
|
||||
response = savedCurrencies.copy(
|
||||
tokens = savedCurrencies.tokens.filterNot(tokens::contains),
|
||||
),
|
||||
response = updatedResponse,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -583,13 +586,9 @@ internal class DefaultCurrenciesRepository(
|
|||
|
||||
val compatibleUserTokensResponse = response
|
||||
.let { it.copy(tokens = it.tokens.distinct()) }
|
||||
.let { customTokensMerger.mergeIfPresented(userWalletId, response) }
|
||||
.let(userTokensBackwardCompatibility::applyCompatibilityAndGetUpdated)
|
||||
.let { customTokensMerger.mergeIfPresented(userWalletId, it) }
|
||||
|
||||
appPreferencesStore.storeObject(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = userWallet.walletId.stringValue),
|
||||
value = compatibleUserTokensResponse,
|
||||
)
|
||||
userTokensSaver.store(userWalletId, compatibleUserTokensResponse)
|
||||
|
||||
fetchExpressAssetsByNetworkIds(userWalletId, compatibleUserTokensResponse)
|
||||
}
|
||||
|
|
@ -600,16 +599,6 @@ internal class DefaultCurrenciesRepository(
|
|||
return demoConfig.isDemoCardId(userWallet.cardId) && response == null
|
||||
}
|
||||
|
||||
private suspend fun storeAndPushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
|
||||
val compatibleUserTokensResponse = userTokensBackwardCompatibility.applyCompatibilityAndGetUpdated(response)
|
||||
appPreferencesStore.storeObject(
|
||||
key = PreferencesKeys.getUserTokensKey(userWalletId = userWalletId.stringValue),
|
||||
value = compatibleUserTokensResponse,
|
||||
)
|
||||
|
||||
pushTokens(userWalletId, response)
|
||||
}
|
||||
|
||||
private suspend fun fetchExpressAssetsByNetworkIds(userWalletId: UserWalletId, userTokens: UserTokensResponse) {
|
||||
val tokens = userTokens.tokens.map { token ->
|
||||
LeastTokenInfo(
|
||||
|
|
@ -657,7 +646,7 @@ internal class DefaultCurrenciesRepository(
|
|||
if (e is ApiResponseError.HttpException && e.code == ApiResponseError.HttpException.Code.NOT_FOUND) {
|
||||
Timber.w(e, "Requested currencies could not be found in the remote store for: $userWalletId")
|
||||
|
||||
pushTokens(userWalletId, response)
|
||||
userTokensSaver.push(userWalletId, response)
|
||||
} else {
|
||||
cacheRegistry.invalidate(getTokensCacheKey(userWalletId))
|
||||
}
|
||||
|
|
@ -665,12 +654,6 @@ internal class DefaultCurrenciesRepository(
|
|||
return response
|
||||
}
|
||||
|
||||
private suspend fun pushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
|
||||
safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, response).bind() }) {
|
||||
Timber.e(it, "Unable to save user tokens for: ${userWalletId.stringValue}")
|
||||
}
|
||||
}
|
||||
|
||||
private fun createDefaultUserTokensResponse(userWallet: UserWallet) =
|
||||
userTokensResponseFactory.createUserTokensResponse(
|
||||
currencies = cardCryptoCurrencyFactory.createDefaultCoinsForMultiCurrencyCard(userWallet.scanResponse),
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.data.tokens.utils
|
|||
|
||||
import arrow.atomic.AtomicBoolean
|
||||
import com.tangem.data.common.api.safeApiCall
|
||||
import com.tangem.data.common.currency.UserTokensSaver
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.UserTokensResponse
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
|
@ -18,6 +19,7 @@ import timber.log.Timber
|
|||
internal class CustomTokensMerger(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val userTokensSaver: UserTokensSaver,
|
||||
) {
|
||||
|
||||
/**
|
||||
|
|
@ -47,7 +49,7 @@ internal class CustomTokensMerger(
|
|||
// previously here was used compare response.tokens, but it's not working correctly
|
||||
// because Token.equals() skip some fields
|
||||
if (wasMerged.value) {
|
||||
pushTokens(userWalletId, updatedResponse)
|
||||
userTokensSaver.push(userWalletId, updatedResponse)
|
||||
}
|
||||
|
||||
return updatedResponse
|
||||
|
|
@ -102,10 +104,4 @@ internal class CustomTokensMerger(
|
|||
},
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun pushTokens(userWalletId: UserWalletId, response: UserTokensResponse) {
|
||||
safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, response).bind() }) {
|
||||
Timber.e(it, "Unable to save user tokens for: $userWalletId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -238,26 +238,18 @@ internal class DefaultWalletsRepository(
|
|||
}
|
||||
}
|
||||
|
||||
override suspend fun isNotificationsEnabled(userWalletId: UserWalletId, force: Boolean): Boolean =
|
||||
withContext(dispatchers.io) {
|
||||
if (force) {
|
||||
return@withContext loadAndSaveNotificationsEnabled(userWalletId)
|
||||
}
|
||||
|
||||
val localValue = appPreferencesStore.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY)
|
||||
.map { it[userWalletId.stringValue] }
|
||||
.firstOrNull()
|
||||
|
||||
localValue ?: loadAndSaveNotificationsEnabled(userWalletId)
|
||||
}
|
||||
override suspend fun isNotificationsEnabled(userWalletId: UserWalletId): Boolean =
|
||||
appPreferencesStore.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY)
|
||||
.map { it[userWalletId.stringValue] }
|
||||
.firstOrNull() ?: false
|
||||
|
||||
override suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean) {
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.setNotificationsEnabled(
|
||||
walletId = userWalletId.stringValue,
|
||||
body = WalletBody(notifyStatus = isEnabled),
|
||||
).getOrThrow()
|
||||
setNotificationsEnabledLocally(userWalletId, isEnabled)
|
||||
appPreferencesStore.editData {
|
||||
it.setObjectMap(
|
||||
key = PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY,
|
||||
value = it.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY)
|
||||
.plus(userWalletId.stringValue to isEnabled),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -283,7 +275,7 @@ internal class DefaultWalletsRepository(
|
|||
value = walletInfo,
|
||||
)
|
||||
if (updateCache) {
|
||||
setNotificationsEnabledLocally(
|
||||
setNotificationsEnabled(
|
||||
userWalletId = userWallet.walletId,
|
||||
isEnabled = userWallet.isNotificationsEnabled,
|
||||
)
|
||||
|
|
@ -307,21 +299,4 @@ internal class DefaultWalletsRepository(
|
|||
body = walletsBody,
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
private suspend fun loadAndSaveNotificationsEnabled(userWalletId: UserWalletId): Boolean {
|
||||
val walletResponse = tangemTechApi.getWalletById(walletId = userWalletId.stringValue).getOrThrow()
|
||||
val isEnabled = walletResponse.notifyStatus
|
||||
setNotificationsEnabledLocally(userWalletId, isEnabled)
|
||||
return isEnabled
|
||||
}
|
||||
|
||||
private suspend fun setNotificationsEnabledLocally(userWalletId: UserWalletId, isEnabled: Boolean) {
|
||||
appPreferencesStore.editData {
|
||||
it.setObjectMap(
|
||||
key = PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY,
|
||||
value = it.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY)
|
||||
.plus(userWalletId.stringValue to isEnabled),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,6 @@ import androidx.datastore.preferences.core.Preferences
|
|||
import com.squareup.moshi.Moshi
|
||||
import com.tangem.datasource.api.common.response.ApiResponse
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletBody
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletResponse
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
|
|
@ -35,10 +34,6 @@ class DefaultWalletsRepositoryTest {
|
|||
private lateinit var dispatchers: CoroutineDispatcherProvider
|
||||
|
||||
private val testWalletId = UserWalletId("1234567890abcdef")
|
||||
private val testWalletResponse = WalletResponse(
|
||||
id = testWalletId.stringValue,
|
||||
notifyStatus = true,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
|
|
@ -54,29 +49,6 @@ class DefaultWalletsRepositoryTest {
|
|||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN force is true WHEN isNotificationsEnabled THEN should fetch from API and update local storage`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
coEvery { tangemTechApi.getWalletById(testWalletId.stringValue) } returns ApiResponse.Success(
|
||||
testWalletResponse,
|
||||
)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk<Preferences>()
|
||||
|
||||
// WHEN
|
||||
val result = repository.isNotificationsEnabled(testWalletId, force = true)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isTrue()
|
||||
coVerify(exactly = 1) {
|
||||
tangemTechApi.getWalletById(testWalletId.stringValue)
|
||||
}
|
||||
coVerify(exactly = 0) {
|
||||
tangemTechApi.setNotificationsEnabled(any(), any())
|
||||
}
|
||||
coVerify(exactly = 1) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN local storage has value WHEN isNotificationsEnabled THEN should return local value`() = runTest {
|
||||
// GIVEN
|
||||
|
|
@ -84,96 +56,55 @@ class DefaultWalletsRepositoryTest {
|
|||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns expectedPreferences
|
||||
coEvery { tangemTechApi.getWalletById(any()) } returns ApiResponse.Success(testWalletResponse)
|
||||
|
||||
// WHEN
|
||||
val result = repository.isNotificationsEnabled(testWalletId, force = false)
|
||||
val result = repository.isNotificationsEnabled(testWalletId)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isTrue()
|
||||
coVerify(inverse = true) {
|
||||
tangemTechApi.getWalletById(any())
|
||||
tangemTechApi.setNotificationsEnabled(any(), any())
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN local storage is empty WHEN isNotificationsEnabled THEN should fetch from API and update local storage`() = runTest {
|
||||
fun `GIVEN local storage is empty WHEN isNotificationsEnabled THEN should return false`() = runTest {
|
||||
// GIVEN
|
||||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}"
|
||||
coEvery { tangemTechApi.getWalletById(testWalletId.stringValue) } returns ApiResponse.Success(
|
||||
testWalletResponse,
|
||||
)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk<Preferences>()
|
||||
|
||||
// WHEN
|
||||
val result = repository.isNotificationsEnabled(testWalletId, force = false)
|
||||
val result = repository.isNotificationsEnabled(testWalletId)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isTrue()
|
||||
coVerify(exactly = 1) {
|
||||
tangemTechApi.getWalletById(testWalletId.stringValue)
|
||||
}
|
||||
coVerify(exactly = 0) {
|
||||
tangemTechApi.setNotificationsEnabled(any(), any())
|
||||
}
|
||||
coVerify(exactly = 1) { preferencesDataStore.updateData(any()) }
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN enabled status WHEN setNotificationsEnabled THEN should update API and local storage`() = runTest {
|
||||
fun `GIVEN enabled status WHEN setNotificationsEnabled THEN should update local storage`() = runTest {
|
||||
// GIVEN
|
||||
coEvery {
|
||||
tangemTechApi.setNotificationsEnabled(
|
||||
eq(testWalletId.stringValue),
|
||||
eq(WalletBody(notifyStatus = true)),
|
||||
)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk<Preferences>()
|
||||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}"
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk()
|
||||
|
||||
// WHEN
|
||||
repository.setNotificationsEnabled(testWalletId, isEnabled = true)
|
||||
|
||||
// THEN
|
||||
coVerify(exactly = 1) {
|
||||
tangemTechApi.setNotificationsEnabled(
|
||||
eq(testWalletId.stringValue),
|
||||
eq(WalletBody(notifyStatus = true)),
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 1) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN disabled status WHEN setNotificationsEnabled THEN should update API and local storage`() = runTest {
|
||||
fun `GIVEN disabled status WHEN setNotificationsEnabled THEN should update local storage`() = runTest {
|
||||
// GIVEN
|
||||
coEvery {
|
||||
tangemTechApi.setNotificationsEnabled(
|
||||
eq(testWalletId.stringValue),
|
||||
eq(WalletBody(notifyStatus = false)),
|
||||
)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk<Preferences>()
|
||||
val preferences = mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
coEvery { preferences[PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] } returns "{}"
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk()
|
||||
|
||||
// WHEN
|
||||
repository.setNotificationsEnabled(testWalletId, isEnabled = false)
|
||||
|
||||
// THEN
|
||||
coVerifyOrder {
|
||||
tangemTechApi.setNotificationsEnabled(
|
||||
eq(testWalletId.stringValue),
|
||||
eq(WalletBody(notifyStatus = false)),
|
||||
)
|
||||
preferencesDataStore.updateData(any())
|
||||
}
|
||||
coVerify(exactly = 1) {
|
||||
tangemTechApi.setNotificationsEnabled(
|
||||
eq(testWalletId.stringValue),
|
||||
eq(WalletBody(notifyStatus = false)),
|
||||
)
|
||||
}
|
||||
coVerify(exactly = 1) { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
|
|
@ -194,7 +125,7 @@ class DefaultWalletsRepositoryTest {
|
|||
),
|
||||
)
|
||||
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk<Preferences>()
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk()
|
||||
|
||||
// WHEN
|
||||
val result = repository.getWalletsInfo(applicationId, updateCache = true)
|
||||
|
|
|
|||
|
|
@ -41,10 +41,8 @@ interface WalletsRepository {
|
|||
|
||||
suspend fun disableNFT(userWalletId: UserWalletId)
|
||||
|
||||
@Throws
|
||||
suspend fun isNotificationsEnabled(userWalletId: UserWalletId, force: Boolean): Boolean
|
||||
suspend fun isNotificationsEnabled(userWalletId: UserWalletId): Boolean
|
||||
|
||||
@Throws
|
||||
suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean)
|
||||
|
||||
@Throws
|
||||
|
|
|
|||
|
|
@ -8,10 +8,9 @@ class GetIsNotificationsEnabledUseCase(
|
|||
private val walletsRepository: WalletsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, force: Boolean): Either<Throwable, Boolean> = Either.catch {
|
||||
suspend operator fun invoke(userWalletId: UserWalletId): Either<Throwable, Boolean> = Either.catch {
|
||||
walletsRepository.isNotificationsEnabled(
|
||||
userWalletId = userWalletId,
|
||||
force = force,
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue