Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-05 20:09:31 +05:00
parent 56bf204cc5
commit a6d0b50f43
19 changed files with 319 additions and 113 deletions

View file

@ -4,12 +4,12 @@ import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConv
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.NotificationApplicationCreateBody
import com.tangem.datasource.api.tangemTech.models.WalletBody
import com.tangem.datasource.api.tangemTech.models.WalletIdBody
import com.tangem.utils.info.AppInfoProvider
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.*
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -23,7 +23,7 @@ internal class DefaultNotificationsRepository @Inject constructor(
private val dispatchers: CoroutineDispatcherProvider,
) : NotificationsRepository {
override suspend fun createApplicationId(pushToken: String?): String = withContext(dispatchers.io) {
override suspend fun createApplicationId(pushToken: String?): ApplicationId = withContext(dispatchers.io) {
tangemTechApi.createApplicationId(
NotificationApplicationCreateBody(
platform = appInfoProvider.platform,
@ -33,15 +33,16 @@ internal class DefaultNotificationsRepository @Inject constructor(
timezone = appInfoProvider.timezone,
pushToken = pushToken,
),
).getOrThrow().appId
).getOrThrow().appId.let(::ApplicationId)
}
override suspend fun saveApplicationId(appId: String) {
appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId)
override suspend fun saveApplicationId(appId: ApplicationId) {
appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId.value)
}
override suspend fun getApplicationId(): String? {
override suspend fun getApplicationId(): ApplicationId? {
return appPreferencesStore.getSyncOrNull(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY)
?.let(::ApplicationId)
}
override suspend fun incrementTronTokenFeeNotificationShowCounter() {
@ -71,21 +72,10 @@ internal class DefaultNotificationsRepository @Inject constructor(
).getOrThrow()
}
override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) {
tangemTechApi.updateWallet(
walletId,
WalletBody(name = walletName),
).getOrThrow()
}
override suspend fun getWalletName(walletId: String): String? = withContext(dispatchers.io) {
tangemTechApi.getWalletById(walletId).getOrThrow().name
}
override suspend fun sendPushToken(appId: String, pushToken: String) {
override suspend fun sendPushToken(appId: ApplicationId, pushToken: String) {
withContext(dispatchers.io) {
tangemTechApi.updatePushTokenForApplicationId(
appId,
appId.value,
NotificationApplicationCreateBody(
pushToken = pushToken,
),

View file

@ -12,6 +12,7 @@ import com.squareup.moshi.Moshi
import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConverter
import com.tangem.datasource.api.common.response.ApiResponse
import com.tangem.datasource.api.tangemTech.models.*
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.coEvery
import io.mockk.coVerify
@ -41,9 +42,9 @@ class DefaultNotificationsRepositoryTest {
fun `GIVEN valid push token WHEN createApplicationId THEN returns application id`() = runTest {
// GIVEN
val pushToken = "test-push-token"
val expectedAppId = "test-app-id"
val expectedAppId = ApplicationId("test-app-id")
val expectedAppIdResponse = NotificationApplicationIdResponse(
appId = expectedAppId,
appId = expectedAppId.value,
)
coEvery { appInfoProvider.platform } returns "android"
coEvery { appInfoProvider.device } returns "test-device"
@ -76,7 +77,7 @@ class DefaultNotificationsRepositoryTest {
@Test
fun `GIVEN application id WHEN saveApplicationId THEN stores it in preferences`() = runTest {
// GIVEN
val appId = "test-app-id"
val appId = ApplicationId("test-app-id")
val preferences = mockk<Preferences>(relaxed = true)
coEvery { preferencesDataStore.updateData(any()) } returns preferences
@ -90,10 +91,10 @@ class DefaultNotificationsRepositoryTest {
@Test
fun `GIVEN stored application id WHEN getApplicationId THEN returns it`() = runTest {
// GIVEN
val expectedAppId = "test-app-id"
val expectedAppId = ApplicationId("test-app-id")
val preferences = mockk<Preferences>(relaxed = true)
val key = stringPreferencesKey(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY.name)
every { preferences[key] } returns expectedAppId
every { preferences[key] } returns expectedAppId.value
coEvery { preferencesDataStore.data } returns flowOf(preferences)
// WHEN
@ -122,56 +123,24 @@ class DefaultNotificationsRepositoryTest {
coVerify { tangemTechApi.associateApplicationIdWithWallets(appId, wallets.map { WalletIdBody(it) }) }
}
@Test
fun `GIVEN wallet id and name WHEN setWalletName THEN updates wallet name`() = runTest {
// GIVEN
val walletId = "test-wallet-id"
val walletName = "Test Wallet"
coEvery {
tangemTechApi.updateWallet(
walletId,
WalletBody(name = walletName),
)
} returns ApiResponse.Success(Unit)
// WHEN
repository.setWalletName(walletId, walletName)
// THEN
coVerify { tangemTechApi.updateWallet(walletId, WalletBody(name = walletName)) }
}
@Test
fun `GIVEN wallet id WHEN getWalletName THEN returns wallet name`() = runTest {
// GIVEN
val walletId = "test-wallet-id"
val expectedName = "Test Wallet"
coEvery { tangemTechApi.getWalletById(walletId) } returns ApiResponse.Success(
WalletResponse(
notifyStatus = false,
name = expectedName,
id = walletId,
),
)
// WHEN
val result = repository.getWalletName(walletId)
// THEN
assertThat(result).isEqualTo(expectedName)
}
@Test
fun `GIVEN application id and push token WHEN sendPushToken THEN updates push token`() = runTest {
// GIVEN
val appId = "test-app-id"
val appId = ApplicationId("test-app-id")
val pushToken = "test-push-token"
coEvery {
tangemTechApi.updatePushTokenForApplicationId(
appId,
NotificationApplicationCreateBody(pushToken = pushToken),
appId.value,
NotificationApplicationCreateBody(
pushToken = pushToken,
platform = null,
device = null,
systemVersion = null,
language = null,
timezone = null,
),
)
} returns ApiResponse.Success(appId)
} returns ApiResponse.Success(appId.value)
// WHEN
repository.sendPushToken(appId, pushToken)
@ -179,8 +148,15 @@ class DefaultNotificationsRepositoryTest {
// THEN
coVerify {
tangemTechApi.updatePushTokenForApplicationId(
appId,
NotificationApplicationCreateBody(pushToken = pushToken),
appId.value,
NotificationApplicationCreateBody(
pushToken = pushToken,
platform = null,
device = null,
systemVersion = null,
language = null,
timezone = null,
),
)
}
}

View file

@ -1,5 +1,6 @@
package com.tangem.data.wallets
import com.tangem.data.wallets.converters.UserWalletRemoteInfoConverter
import com.tangem.datasource.api.common.response.ApiResponseError.HttpException
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
@ -18,6 +19,7 @@ import com.tangem.datasource.local.preferences.utils.store
import com.tangem.datasource.local.userwallet.UserWalletsStore
import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.utils.WEEK_MILLIS
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -28,6 +30,7 @@ import kotlinx.coroutines.withContext
typealias SeedPhraseNotificationsStatuses = Map<UserWalletId, SeedPhraseNotificationsStatus>
@Suppress("TooManyFunctions")
internal class DefaultWalletsRepository(
private val appPreferencesStore: AppPreferencesStore,
private val tangemTechApi: TangemTechApi,
@ -254,6 +257,37 @@ internal class DefaultWalletsRepository(
}
}
override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) {
tangemTechApi.updateWallet(
walletId = walletId,
body = WalletBody(name = walletName),
).getOrThrow()
}
override suspend fun getWalletInfo(walletId: String): UserWalletRemoteInfo = withContext(dispatchers.io) {
UserWalletRemoteInfoConverter.convert(
value = tangemTechApi.getWalletById(walletId).getOrThrow(),
)
}
override suspend fun getWalletsInfo(applicationId: String, updateCache: Boolean): List<UserWalletRemoteInfo> =
withContext(dispatchers.io) {
tangemTechApi.getWallets(applicationId)
.getOrThrow()
.map { walletInfo ->
val userWallet = UserWalletRemoteInfoConverter.convert(
value = walletInfo,
)
if (updateCache) {
setNotificationsEnabledLocally(
userWalletId = userWallet.walletId,
isEnabled = userWallet.isNotificationsEnabled,
)
}
userWallet
}
}
private suspend fun loadAndSaveNotificationsEnabled(userWalletId: UserWalletId): Boolean {
val walletResponse = tangemTechApi.getWalletById(walletId = userWalletId.stringValue).getOrThrow()
val isEnabled = walletResponse.notifyStatus

View file

@ -0,0 +1,16 @@
package com.tangem.data.wallets.converters
import com.tangem.datasource.api.tangemTech.models.WalletResponse
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.models.UserWalletRemoteInfo
import com.tangem.utils.converter.Converter
internal object UserWalletRemoteInfoConverter : Converter<WalletResponse, UserWalletRemoteInfo> {
override fun convert(value: WalletResponse): UserWalletRemoteInfo {
return UserWalletRemoteInfo(
walletId = UserWalletId(value.id),
name = value.name.orEmpty(),
isNotificationsEnabled = value.notifyStatus,
)
}
}

View file

@ -173,4 +173,62 @@ class DefaultWalletsRepositoryTest {
}
coVerify(exactly = 1) { preferencesDataStore.updateData(any()) }
}
@Test
fun `GIVEN API returns wallets WHEN getWalletsInfo THEN should return converted wallets and update cache if requested`() = runTest {
// GIVEN
val applicationId = "test_app_id"
val wallet1Id = "1234567890abcdef"
val wallet2Id = "fedcba0987654321"
val walletResponses = listOf(
WalletResponse(
id = wallet1Id,
notifyStatus = true,
),
WalletResponse(
id = wallet2Id,
notifyStatus = false,
),
)
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
coEvery { preferencesDataStore.updateData(any()) } returns mockk<Preferences>()
// WHEN
val result = repository.getWalletsInfo(applicationId, updateCache = true)
// THEN
assertThat(result).hasSize(2)
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
assertThat(result[0].isNotificationsEnabled).isTrue()
assertThat(result[1].walletId.stringValue).isEqualTo(wallet2Id)
assertThat(result[1].isNotificationsEnabled).isFalse()
coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) }
coVerify(exactly = 2) { preferencesDataStore.updateData(any()) }
}
@Test
fun `GIVEN API returns wallets WHEN getWalletsInfo with updateCache false THEN should return converted wallets without updating cache`() = runTest {
// GIVEN
val applicationId = "test_app_id"
val wallet1Id = "1234567890abcdef"
val walletResponses = listOf(
WalletResponse(
id = wallet1Id,
notifyStatus = true,
),
)
coEvery { tangemTechApi.getWallets(applicationId) } returns ApiResponse.Success(walletResponses)
// WHEN
val result = repository.getWalletsInfo(applicationId, updateCache = false)
// THEN
assertThat(result).hasSize(1)
assertThat(result[0].walletId.stringValue).isEqualTo(wallet1Id)
assertThat(result[0].isNotificationsEnabled).isTrue()
coVerify(exactly = 1) { tangemTechApi.getWallets(applicationId) }
coVerify(exactly = 0) { preferencesDataStore.updateData(any()) }
}
}