Updated on 2026-08-14

This commit is contained in:
Tangem 2025-08-04 13:54:22 +04:00
parent 14df650f1a
commit be160483d2
25 changed files with 519 additions and 268 deletions

View file

@ -2,25 +2,25 @@ package com.tangem.domain.notifications
import arrow.core.Either
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.notifications.repository.PushNotificationsRepository
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
class GetApplicationIdUseCase(
private val notificationsRepository: NotificationsRepository,
private val pushNotificationsRepository: PushNotificationsRepository,
) {
private val mutex = Mutex()
suspend operator fun invoke(): Either<Throwable, ApplicationId> = Either.catch {
val localApplicationId = notificationsRepository.getApplicationId()
val localApplicationId = pushNotificationsRepository.getApplicationId()
if (localApplicationId != null) return@catch localApplicationId
mutex.withLock {
val doubleCheckedId = notificationsRepository.getApplicationId()
val doubleCheckedId = pushNotificationsRepository.getApplicationId()
if (doubleCheckedId != null) return@withLock doubleCheckedId
val newApplicationId = notificationsRepository.createApplicationId()
notificationsRepository.saveApplicationId(newApplicationId)
val newApplicationId = pushNotificationsRepository.createApplicationId()
pushNotificationsRepository.saveApplicationId(newApplicationId)
newApplicationId
}
}

View file

@ -2,13 +2,13 @@ package com.tangem.domain.notifications
import arrow.core.Either
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.notifications.repository.PushNotificationsRepository
class GetNetworksAvailableForNotificationsUseCase(
private val notificationsRepository: NotificationsRepository,
private val pushNotificationsRepository: PushNotificationsRepository,
) {
suspend operator fun invoke(): Either<Throwable, List<NotificationsEligibleNetwork>> = Either.catch {
notificationsRepository.getEligibleNetworks()
pushNotificationsRepository.getEligibleNetworks()
}
}

View file

@ -2,16 +2,16 @@ package com.tangem.domain.notifications
import arrow.core.Either
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.notifications.repository.PushNotificationsRepository
import com.tangem.utils.notifications.PushNotificationsTokenProvider
class SendPushTokenUseCase(
private val notificationsRepository: NotificationsRepository,
private val pushNotificationsRepository: PushNotificationsRepository,
private val pushNotificationsTokenProvider: PushNotificationsTokenProvider,
) {
suspend operator fun invoke(applicationId: ApplicationId): Either<Throwable, Unit> = Either.catch {
val token = pushNotificationsTokenProvider.getToken()
notificationsRepository.sendPushToken(applicationId, token)
pushNotificationsRepository.sendPushToken(applicationId, token)
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.domain.notifications
import com.tangem.domain.notifications.repository.NotificationsRepository
class SetShouldShowNotificationUseCase(
private val notificationsRepository: NotificationsRepository,
) {
suspend operator fun invoke(key: String, value: Boolean) {
notificationsRepository.setShouldShowNotifications(key, value)
}
}

View file

@ -0,0 +1,12 @@
package com.tangem.domain.notifications
import com.tangem.domain.notifications.repository.NotificationsRepository
class ShouldShowNotificationUseCase(
private val notificationsRepository: NotificationsRepository,
) {
suspend operator fun invoke(key: String): Boolean {
return notificationsRepository.shouldShowNotification(key)
}
}

View file

@ -1,24 +1,40 @@
package com.tangem.domain.notifications.repository
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
/**
* Repository interface for managing local notification logic and state.
*
* This interface provides methods to check and update whether specific notifications should be shown,
* as well as to track the display count for certain notifications (e.g., Tron token fee).
*
* Note: This repository is responsible only for the local logic and state (such as preferences and counters)
* regarding notifications. It does **not** directly show or hide notifications to the user.
* The actual display and hiding of notifications in the UI is handled by [NotificationsUM],
* which uses this repository to determine the appropriate behavior.
*/
interface NotificationsRepository {
@Throws
suspend fun createApplicationId(pushToken: String? = null): ApplicationId
/**
* Checks whether a notification with the given [key] should be shown to the user.
* @param key The unique identifier for the notification.
* @return true if the notification should be shown, false otherwise.
*/
suspend fun shouldShowNotification(key: String): Boolean
suspend fun saveApplicationId(appId: ApplicationId)
suspend fun getApplicationId(): ApplicationId?
/**
* Sets whether a notification with the given [key] should be shown to the user.
* @param key The unique identifier for the notification.
* @param value true if the notification should be shown, false otherwise.
*/
suspend fun setShouldShowNotifications(key: String, value: Boolean)
/**
* Gets the number of times the Tron token fee notification has been shown.
* @return The current show counter for the Tron token fee notification.
*/
suspend fun getTronTokenFeeNotificationShowCounter(): Int
/**
* Increments the counter tracking how many times the Tron token fee notification has been shown.
*/
suspend fun incrementTronTokenFeeNotificationShowCounter()
@Throws
suspend fun sendPushToken(appId: ApplicationId, pushToken: String)
@Throws
suspend fun getEligibleNetworks(): List<NotificationsEligibleNetwork>
}

View file

@ -0,0 +1,20 @@
package com.tangem.domain.notifications.repository
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
interface PushNotificationsRepository {
@Throws
suspend fun createApplicationId(pushToken: String? = null): ApplicationId
suspend fun saveApplicationId(appId: ApplicationId)
suspend fun getApplicationId(): ApplicationId?
@Throws
suspend fun sendPushToken(appId: ApplicationId, pushToken: String)
@Throws
suspend fun getEligibleNetworks(): List<NotificationsEligibleNetwork>
}

View file

@ -3,7 +3,7 @@ package com.tangem.domain.notifications
import arrow.core.Either
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.notifications.repository.PushNotificationsRepository
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.coVerifyOrder
@ -15,14 +15,14 @@ import java.net.SocketTimeoutException
class GetApplicationIdUseCaseTest {
private val notificationsRepository: NotificationsRepository = mockk()
private val useCase = GetApplicationIdUseCase(notificationsRepository)
private val pushNotificationsRepository: PushNotificationsRepository = mockk()
private val useCase = GetApplicationIdUseCase(pushNotificationsRepository)
@Test
fun `GIVEN local application ID exists WHEN invoke THEN return local application ID`() = runTest {
// GIVEN
val expectedApplicationId = ApplicationId("test-app-id")
coEvery { notificationsRepository.getApplicationId() } returns expectedApplicationId
coEvery { pushNotificationsRepository.getApplicationId() } returns expectedApplicationId
// WHEN
val result = useCase()
@ -30,10 +30,10 @@ class GetApplicationIdUseCaseTest {
// THEN
assertThat(result).isInstanceOf(Either.Right::class.java)
assertThat((result as Either.Right).value).isEqualTo(expectedApplicationId)
coVerify(exactly = 1) { notificationsRepository.getApplicationId() }
coVerify(exactly = 1) { pushNotificationsRepository.getApplicationId() }
coVerify(inverse = true) {
notificationsRepository.createApplicationId()
notificationsRepository.saveApplicationId(any())
pushNotificationsRepository.createApplicationId()
pushNotificationsRepository.saveApplicationId(any())
}
}
@ -41,9 +41,9 @@ class GetApplicationIdUseCaseTest {
fun `GIVEN local application ID does not exist WHEN invoke THEN create and save new application ID`() = runTest {
// GIVEN
val newApplicationId = ApplicationId("new-app-id")
coEvery { notificationsRepository.getApplicationId() } returns null
coEvery { notificationsRepository.createApplicationId() } returns newApplicationId
coEvery { notificationsRepository.saveApplicationId(newApplicationId) } returns Unit
coEvery { pushNotificationsRepository.getApplicationId() } returns null
coEvery { pushNotificationsRepository.createApplicationId() } returns newApplicationId
coEvery { pushNotificationsRepository.saveApplicationId(newApplicationId) } returns Unit
// WHEN
val result = useCase()
@ -52,10 +52,10 @@ class GetApplicationIdUseCaseTest {
assertThat(result).isInstanceOf(Either.Right::class.java)
assertThat((result as Either.Right).value).isEqualTo(newApplicationId)
coVerifyOrder {
notificationsRepository.getApplicationId()
notificationsRepository.getApplicationId()
notificationsRepository.createApplicationId()
notificationsRepository.saveApplicationId(newApplicationId)
pushNotificationsRepository.getApplicationId()
pushNotificationsRepository.getApplicationId()
pushNotificationsRepository.createApplicationId()
pushNotificationsRepository.saveApplicationId(newApplicationId)
}
}
@ -63,7 +63,7 @@ class GetApplicationIdUseCaseTest {
fun `GIVEN repository throws exception WHEN invoke THEN return Either Left with error`() = runTest {
// GIVEN
val expectedError = SocketTimeoutException("Test error")
coEvery { notificationsRepository.getApplicationId() } throws expectedError
coEvery { pushNotificationsRepository.getApplicationId() } throws expectedError
// WHEN
val result = useCase()
@ -71,10 +71,10 @@ class GetApplicationIdUseCaseTest {
// THEN
assertThat(result).isInstanceOf(Either.Left::class.java)
assertThat((result as Either.Left).value).isEqualTo(expectedError)
coVerify(exactly = 1) { notificationsRepository.getApplicationId() }
coVerify(exactly = 1) { pushNotificationsRepository.getApplicationId() }
coVerify(inverse = true) {
notificationsRepository.createApplicationId()
notificationsRepository.saveApplicationId(any())
pushNotificationsRepository.createApplicationId()
pushNotificationsRepository.saveApplicationId(any())
}
}
@ -85,14 +85,14 @@ class GetApplicationIdUseCaseTest {
val newApplicationId = ApplicationId("new-app-id")
var isIdCreated = false
coEvery { notificationsRepository.getApplicationId() } answers {
coEvery { pushNotificationsRepository.getApplicationId() } answers {
if (!isIdCreated) null else newApplicationId
}
coEvery { notificationsRepository.createApplicationId() } answers {
coEvery { pushNotificationsRepository.createApplicationId() } answers {
isIdCreated = true
newApplicationId
}
coEvery { notificationsRepository.saveApplicationId(newApplicationId) } returns Unit
coEvery { pushNotificationsRepository.saveApplicationId(newApplicationId) } returns Unit
// WHEN
val results = coroutineScope {
@ -108,9 +108,9 @@ class GetApplicationIdUseCaseTest {
assertThat(result).isInstanceOf(Either.Right::class.java)
assertThat((result as Either.Right).value).isEqualTo(newApplicationId)
}
coVerify(exactly = PARALLEL_COUNT + 1) { notificationsRepository.getApplicationId() }
coVerify(exactly = 1) { notificationsRepository.createApplicationId() }
coVerify(exactly = 1) { notificationsRepository.saveApplicationId(newApplicationId) }
coVerify(exactly = PARALLEL_COUNT + 1) { pushNotificationsRepository.getApplicationId() }
coVerify(exactly = 1) { pushNotificationsRepository.createApplicationId() }
coVerify(exactly = 1) { pushNotificationsRepository.saveApplicationId(newApplicationId) }
}
@Test
@ -119,14 +119,14 @@ class GetApplicationIdUseCaseTest {
val newApplicationId = ApplicationId("new-app-id")
var isIdCreated = false
coEvery { notificationsRepository.getApplicationId() } answers {
coEvery { pushNotificationsRepository.getApplicationId() } answers {
if (!isIdCreated) null else newApplicationId
}
coEvery { notificationsRepository.createApplicationId() } answers {
coEvery { pushNotificationsRepository.createApplicationId() } answers {
isIdCreated = true
newApplicationId
}
coEvery { notificationsRepository.saveApplicationId(newApplicationId) } returns Unit
coEvery { pushNotificationsRepository.saveApplicationId(newApplicationId) } returns Unit
// WHEN
val results = coroutineScope {
@ -143,9 +143,9 @@ class GetApplicationIdUseCaseTest {
assertThat(result).isInstanceOf(Either.Right::class.java)
assertThat((result as Either.Right).value).isEqualTo(newApplicationId)
}
coVerify(exactly = PARALLEL_COUNT + 1) { notificationsRepository.getApplicationId() }
coVerify(exactly = 1) { notificationsRepository.createApplicationId() }
coVerify(exactly = 1) { notificationsRepository.saveApplicationId(newApplicationId) }
coVerify(exactly = PARALLEL_COUNT + 1) { pushNotificationsRepository.getApplicationId() }
coVerify(exactly = 1) { pushNotificationsRepository.createApplicationId() }
coVerify(exactly = 1) { pushNotificationsRepository.saveApplicationId(newApplicationId) }
}
companion object {

View file

@ -3,7 +3,7 @@ package com.tangem.domain.notifications
import arrow.core.Either
import com.google.common.truth.Truth.assertThat
import com.tangem.domain.notifications.models.ApplicationId
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.notifications.repository.PushNotificationsRepository
import com.tangem.utils.notifications.PushNotificationsTokenProvider
import io.mockk.coEvery
import io.mockk.coVerify
@ -14,16 +14,16 @@ import org.junit.Test
class SendPushTokenUseCaseTest {
private lateinit var notificationsRepository: NotificationsRepository
private lateinit var pushNotificationsRepository: PushNotificationsRepository
private lateinit var pushNotificationsTokenProvider: PushNotificationsTokenProvider
private lateinit var sendPushTokenUseCase: SendPushTokenUseCase
@Before
fun setup() {
notificationsRepository = mockk()
pushNotificationsRepository = mockk()
pushNotificationsTokenProvider = mockk()
sendPushTokenUseCase = SendPushTokenUseCase(
notificationsRepository = notificationsRepository,
pushNotificationsRepository = pushNotificationsRepository,
pushNotificationsTokenProvider = pushNotificationsTokenProvider,
)
}
@ -34,14 +34,14 @@ class SendPushTokenUseCaseTest {
val applicationId = ApplicationId("test-app-id")
val token = "test-token"
coEvery { pushNotificationsTokenProvider.getToken() } returns token
coEvery { notificationsRepository.sendPushToken(applicationId, token) } returns Unit
coEvery { pushNotificationsRepository.sendPushToken(applicationId, token) } returns Unit
// WHEN
val result = sendPushTokenUseCase(applicationId)
// THEN
assertThat(result).isEqualTo(Either.Right(Unit))
coVerify(exactly = 1) { notificationsRepository.sendPushToken(applicationId, token) }
coVerify(exactly = 1) { pushNotificationsRepository.sendPushToken(applicationId, token) }
}
@Test
@ -51,13 +51,13 @@ class SendPushTokenUseCaseTest {
val token = "test-token"
val expectedError = RuntimeException("Network error")
coEvery { pushNotificationsTokenProvider.getToken() } returns token
coEvery { notificationsRepository.sendPushToken(applicationId, token) } throws expectedError
coEvery { pushNotificationsRepository.sendPushToken(applicationId, token) } throws expectedError
// WHEN
val result = sendPushTokenUseCase(applicationId)
// THEN
assertThat(result).isEqualTo(Either.Left(expectedError))
coVerify(exactly = 1) { notificationsRepository.sendPushToken(applicationId, token) }
coVerify(exactly = 1) { pushNotificationsRepository.sendPushToken(applicationId, token) }
}
}