Updated on 2026-08-14
This commit is contained in:
parent
14df650f1a
commit
be160483d2
25 changed files with 519 additions and 268 deletions
|
|
@ -3,6 +3,7 @@ package com.tangem.tap.di.domain
|
|||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.domain.notifications.*
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.notifications.repository.PushNotificationsRepository
|
||||
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
|
||||
import com.tangem.tap.domain.notifications.DefaultNotificationsFeatureToggles
|
||||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
|
|
@ -18,20 +19,22 @@ internal object NotificationsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesGetApplicationIdUseCase(notificationsRepository: NotificationsRepository): GetApplicationIdUseCase {
|
||||
fun providesGetApplicationIdUseCase(
|
||||
pushNotificationsRepository: PushNotificationsRepository,
|
||||
): GetApplicationIdUseCase {
|
||||
return GetApplicationIdUseCase(
|
||||
notificationsRepository = notificationsRepository,
|
||||
pushNotificationsRepository = pushNotificationsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesSendPushTokenUseCase(
|
||||
notificationsRepository: NotificationsRepository,
|
||||
pushNotificationsRepository: PushNotificationsRepository,
|
||||
pushNotificationsTokenProvider: PushNotificationsTokenProvider,
|
||||
): SendPushTokenUseCase {
|
||||
return SendPushTokenUseCase(
|
||||
notificationsRepository = notificationsRepository,
|
||||
pushNotificationsRepository = pushNotificationsRepository,
|
||||
pushNotificationsTokenProvider = pushNotificationsTokenProvider,
|
||||
)
|
||||
}
|
||||
|
|
@ -56,6 +59,26 @@ internal object NotificationsDomainModule {
|
|||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesShouldShowNotificationUseCase(
|
||||
notificationsRepository: NotificationsRepository,
|
||||
): ShouldShowNotificationUseCase {
|
||||
return ShouldShowNotificationUseCase(
|
||||
notificationsRepository = notificationsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesSetShouldShowNotificationUseCase(
|
||||
notificationsRepository: NotificationsRepository,
|
||||
): SetShouldShowNotificationUseCase {
|
||||
return SetShouldShowNotificationUseCase(
|
||||
notificationsRepository = notificationsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideNotificationsFeatureToggles(featureTogglesManager: FeatureTogglesManager): NotificationsFeatureToggles {
|
||||
|
|
@ -65,8 +88,8 @@ internal object NotificationsDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGetNetworksAvailableForNotifications(
|
||||
notificationsRepository: NotificationsRepository,
|
||||
pushNotificationsRepository: PushNotificationsRepository,
|
||||
): GetNetworksAvailableForNotificationsUseCase {
|
||||
return GetNetworksAvailableForNotificationsUseCase(notificationsRepository = notificationsRepository)
|
||||
return GetNetworksAvailableForNotificationsUseCase(pushNotificationsRepository = pushNotificationsRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -434,6 +434,7 @@ internal class ChildFactory @Inject constructor(
|
|||
initialCurrency = route.initialCurrency,
|
||||
selectedCurrency = route.selectedCurrency,
|
||||
source = ChooseManagedTokensComponent.Source.valueOf(route.source.name),
|
||||
showSendViaSwapNotification = route.showSendViaSwapNotification,
|
||||
),
|
||||
componentFactory = chooseManagedTokensComponentFactory,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ sealed class AppRoute(val path: String) : Route {
|
|||
val initialCurrency: CryptoCurrency,
|
||||
val selectedCurrency: CryptoCurrency?,
|
||||
val source: Source,
|
||||
val showSendViaSwapNotification: Boolean,
|
||||
) : AppRoute(path = "/$source/choose_managed_tokens/$userWalletId/${initialCurrency.id.value}") {
|
||||
enum class Source {
|
||||
SendViaSwap,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.common.ui.notifications
|
||||
|
||||
/**
|
||||
* NotificationId represents unique identifiers for notifications in the app.
|
||||
*
|
||||
* These ids can be used with [ShouldShowNotificationUseCase] and [SetShouldShowNotificationUseCase]
|
||||
* to check or update the visibility state of notifications.
|
||||
*/
|
||||
enum class NotificationId(val key: String) {
|
||||
SendViaSwapTokenSelectorNotification("SendViaSwapTokenSelectorNotificationKey"),
|
||||
}
|
||||
|
|
@ -137,6 +137,8 @@ object PreferencesKeys {
|
|||
val TRON_NETWORK_FEE_NOTIFICATION_SHOW_COUNT_KEY by lazy {
|
||||
intPreferencesKey(name = "tronNetworkFeeNotificationShowCount")
|
||||
}
|
||||
|
||||
fun getShouldShowNotificationKey(key: String) = booleanPreferencesKey("showShowNotificationUM_$key")
|
||||
// endregion
|
||||
|
||||
// region Promo
|
||||
|
|
|
|||
|
|
@ -1,48 +1,22 @@
|
|||
package com.tangem.data.notifications
|
||||
|
||||
import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConverter
|
||||
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.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.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultNotificationsRepository @Inject constructor(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val appInfoProvider: AppInfoProvider,
|
||||
class DefaultNotificationsRepository @Inject constructor(
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : NotificationsRepository {
|
||||
|
||||
override suspend fun createApplicationId(pushToken: String?): ApplicationId = withContext(dispatchers.io) {
|
||||
tangemTechApi.createApplicationId(
|
||||
NotificationApplicationCreateBody(
|
||||
platform = appInfoProvider.platform.lowercase(),
|
||||
device = appInfoProvider.device,
|
||||
systemVersion = appInfoProvider.osVersion,
|
||||
language = appInfoProvider.language,
|
||||
timezone = appInfoProvider.timezone,
|
||||
version = appInfoProvider.appVersion,
|
||||
pushToken = pushToken,
|
||||
),
|
||||
).getOrThrow().appId.let(::ApplicationId)
|
||||
override suspend fun shouldShowNotification(key: String): Boolean {
|
||||
return appPreferencesStore.getSyncOrDefault(PreferencesKeys.getShouldShowNotificationKey(key), true)
|
||||
}
|
||||
|
||||
override suspend fun saveApplicationId(appId: ApplicationId) {
|
||||
appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId.value)
|
||||
}
|
||||
|
||||
override suspend fun getApplicationId(): ApplicationId? {
|
||||
return appPreferencesStore.getSyncOrNull(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY)
|
||||
?.let(::ApplicationId)
|
||||
override suspend fun setShouldShowNotifications(key: String, value: Boolean) {
|
||||
appPreferencesStore.store(PreferencesKeys.getShouldShowNotificationKey(key), value)
|
||||
}
|
||||
|
||||
override suspend fun incrementTronTokenFeeNotificationShowCounter() {
|
||||
|
|
@ -61,25 +35,4 @@ internal class DefaultNotificationsRepository @Inject constructor(
|
|||
default = 0,
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun sendPushToken(appId: ApplicationId, pushToken: String) {
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
systemVersion = appInfoProvider.osVersion,
|
||||
language = appInfoProvider.language,
|
||||
timezone = appInfoProvider.timezone,
|
||||
version = appInfoProvider.appVersion,
|
||||
),
|
||||
).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getEligibleNetworks(): List<NotificationsEligibleNetwork> = withContext(dispatchers.io) {
|
||||
tangemTechApi.getEligibleNetworksForPushNotifications().getOrThrow().mapNotNull {
|
||||
NotificationsEligibleNetworkConverter.convert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.tangem.data.notifications
|
||||
|
||||
import com.tangem.data.notifications.converters.NotificationsEligibleNetworkConverter
|
||||
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.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.PushNotificationsRepository
|
||||
import com.tangem.domain.notifications.models.NotificationsEligibleNetwork
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultPushNotificationsRepository @Inject constructor(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val appInfoProvider: AppInfoProvider,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : PushNotificationsRepository {
|
||||
|
||||
override suspend fun createApplicationId(pushToken: String?): ApplicationId = withContext(dispatchers.io) {
|
||||
tangemTechApi.createApplicationId(
|
||||
NotificationApplicationCreateBody(
|
||||
platform = appInfoProvider.platform.lowercase(),
|
||||
device = appInfoProvider.device,
|
||||
systemVersion = appInfoProvider.osVersion,
|
||||
language = appInfoProvider.language,
|
||||
timezone = appInfoProvider.timezone,
|
||||
version = appInfoProvider.appVersion,
|
||||
pushToken = pushToken,
|
||||
),
|
||||
).getOrThrow().appId.let(::ApplicationId)
|
||||
}
|
||||
|
||||
override suspend fun saveApplicationId(appId: ApplicationId) {
|
||||
appPreferencesStore.store(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY, appId.value)
|
||||
}
|
||||
|
||||
override suspend fun getApplicationId(): ApplicationId? {
|
||||
return appPreferencesStore.getSyncOrNull(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY)
|
||||
?.let(::ApplicationId)
|
||||
}
|
||||
|
||||
override suspend fun sendPushToken(appId: ApplicationId, pushToken: String) {
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
systemVersion = appInfoProvider.osVersion,
|
||||
language = appInfoProvider.language,
|
||||
timezone = appInfoProvider.timezone,
|
||||
version = appInfoProvider.appVersion,
|
||||
),
|
||||
).getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getEligibleNetworks(): List<NotificationsEligibleNetwork> = withContext(dispatchers.io) {
|
||||
tangemTechApi.getEligibleNetworksForPushNotifications().getOrThrow().mapNotNull {
|
||||
NotificationsEligibleNetworkConverter.convert(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
package com.tangem.data.notifications.di
|
||||
|
||||
import com.tangem.data.notifications.DefaultNotificationsRepository
|
||||
import com.tangem.data.notifications.DefaultPushNotificationsRepository
|
||||
import com.tangem.domain.notifications.repository.NotificationsRepository
|
||||
import com.tangem.domain.notifications.repository.PushNotificationsRepository
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -12,6 +14,10 @@ import javax.inject.Singleton
|
|||
@InstallIn(SingletonComponent::class)
|
||||
internal interface NotificationsModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindPushNotificationsRepository(repository: DefaultPushNotificationsRepository): PushNotificationsRepository
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindNotificationsRepository(repository: DefaultNotificationsRepository): NotificationsRepository
|
||||
|
|
|
|||
|
|
@ -1,30 +1,21 @@
|
|||
package com.tangem.data.notifications
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
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
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import com.squareup.moshi.Moshi
|
||||
import androidx.datastore.core.DataStore
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
class DefaultNotificationsRepositoryTest {
|
||||
private val tangemTechApi: TangemTechApi = mockk()
|
||||
private val appInfoProvider: AppInfoProvider = mockk()
|
||||
private val preferencesDataStore: DataStore<Preferences> = mockk()
|
||||
private val appPreferencesStore = AppPreferencesStore(
|
||||
moshi = Moshi.Builder().build(),
|
||||
|
|
@ -32,147 +23,77 @@ class DefaultNotificationsRepositoryTest {
|
|||
preferencesDataStore = preferencesDataStore,
|
||||
)
|
||||
private val repository = DefaultNotificationsRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
appInfoProvider = appInfoProvider,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN valid push token WHEN createApplicationId THEN returns application id`() = runTest {
|
||||
fun `GIVEN shouldShowNotification returns true WHEN called THEN returns true`() = runTest {
|
||||
// GIVEN
|
||||
val pushToken = "test-push-token"
|
||||
val expectedAppId = ApplicationId("test-app-id")
|
||||
val expectedAppIdResponse = NotificationApplicationIdResponse(
|
||||
appId = expectedAppId.value,
|
||||
)
|
||||
coEvery { appInfoProvider.platform } returns "android"
|
||||
coEvery { appInfoProvider.device } returns "test-device"
|
||||
coEvery { appInfoProvider.osVersion } returns "11"
|
||||
coEvery { appInfoProvider.language } returns "en"
|
||||
coEvery { appInfoProvider.appVersion } returns "5.21.1"
|
||||
coEvery { appInfoProvider.timezone } returns "UTC"
|
||||
coEvery { tangemTechApi.createApplicationId(any()) } returns ApiResponse.Success(
|
||||
expectedAppIdResponse,
|
||||
)
|
||||
val key = "test-key"
|
||||
val preferences = mockk<Preferences>(relaxed = true)
|
||||
every { preferences[PreferencesKeys.getShouldShowNotificationKey(key)] } returns true
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
|
||||
// WHEN
|
||||
val result = repository.createApplicationId(pushToken)
|
||||
val result = repository.shouldShowNotification(key)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(expectedAppId)
|
||||
coVerify {
|
||||
tangemTechApi.createApplicationId(
|
||||
NotificationApplicationCreateBody(
|
||||
platform = "android",
|
||||
device = "test-device",
|
||||
systemVersion = "11",
|
||||
language = "en",
|
||||
timezone = "UTC",
|
||||
version = "5.21.1",
|
||||
pushToken = pushToken,
|
||||
),
|
||||
)
|
||||
}
|
||||
assertThat(result).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN application id WHEN saveApplicationId THEN stores it in preferences`() = runTest {
|
||||
fun `GIVEN shouldShowNotification returns false WHEN called THEN returns false`() = runTest {
|
||||
// GIVEN
|
||||
val appId = ApplicationId("test-app-id")
|
||||
val key = "test-key"
|
||||
val preferences = mockk<Preferences>(relaxed = true)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns preferences
|
||||
every { preferences[PreferencesKeys.getShouldShowNotificationKey(key)] } returns false
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
|
||||
// WHEN
|
||||
repository.saveApplicationId(appId)
|
||||
val result = repository.shouldShowNotification(key)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN setShouldShowNotifications WHEN called THEN stores value in preferences`() = runTest {
|
||||
// GIVEN
|
||||
val key = "test-key"
|
||||
val value = false
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk(relaxed = true)
|
||||
|
||||
// WHEN
|
||||
repository.setShouldShowNotifications(key, value)
|
||||
|
||||
// THEN
|
||||
coVerify { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN stored application id WHEN getApplicationId THEN returns it`() = runTest {
|
||||
fun `GIVEN incrementTronTokenFeeNotificationShowCounter WHEN called THEN increments counter`() = runTest {
|
||||
// GIVEN
|
||||
val expectedAppId = ApplicationId("test-app-id")
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk(relaxed = true)
|
||||
|
||||
// WHEN
|
||||
repository.incrementTronTokenFeeNotificationShowCounter()
|
||||
|
||||
// THEN
|
||||
coVerify { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN getTronTokenFeeNotificationShowCounter WHEN called THEN returns counter value`() = runTest {
|
||||
// GIVEN
|
||||
val expectedCount = 5
|
||||
val preferences = mockk<Preferences>(relaxed = true)
|
||||
val key = stringPreferencesKey(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY.name)
|
||||
every { preferences[key] } returns expectedAppId.value
|
||||
every { preferences[PreferencesKeys.TRON_NETWORK_FEE_NOTIFICATION_SHOW_COUNT_KEY] } returns expectedCount
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
|
||||
// WHEN
|
||||
val result = repository.getApplicationId()
|
||||
val result = repository.getTronTokenFeeNotificationShowCounter()
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(expectedAppId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN application id and push token WHEN sendPushToken THEN updates push token`() = runTest {
|
||||
// GIVEN
|
||||
val appId = ApplicationId("test-app-id")
|
||||
val pushToken = "test-push-token"
|
||||
coEvery { appInfoProvider.device } returns "test-device"
|
||||
coEvery { appInfoProvider.osVersion } returns "11"
|
||||
coEvery { appInfoProvider.language } returns "en"
|
||||
coEvery { appInfoProvider.appVersion } returns "5.21.1"
|
||||
coEvery { appInfoProvider.timezone } returns "UTC"
|
||||
coEvery {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
systemVersion = "11",
|
||||
language = "en",
|
||||
timezone = "UTC",
|
||||
version = "5.21.1",
|
||||
),
|
||||
)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
|
||||
// WHEN
|
||||
repository.sendPushToken(appId, pushToken)
|
||||
|
||||
// THEN
|
||||
coVerify {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
systemVersion = "11",
|
||||
language = "en",
|
||||
timezone = "UTC",
|
||||
version = "5.21.1",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN eligible networks WHEN getEligibleNetworks THEN returns converted networks`() = runTest {
|
||||
// GIVEN
|
||||
val expectedNetworks = listOf(
|
||||
CryptoNetworkResponse(
|
||||
id = 1,
|
||||
name = "Ethereum",
|
||||
networkId = "ethereum",
|
||||
),
|
||||
CryptoNetworkResponse(
|
||||
id = 2,
|
||||
name = "Bitcoin",
|
||||
networkId = "bitcoin",
|
||||
),
|
||||
)
|
||||
coEvery { tangemTechApi.getEligibleNetworksForPushNotifications() } returns ApiResponse.Success(
|
||||
expectedNetworks,
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = repository.getEligibleNetworks()
|
||||
|
||||
// THEN
|
||||
assertThat(result).hasSize(2)
|
||||
assertThat(result[0]).isEqualTo(NotificationsEligibleNetworkConverter.convert(expectedNetworks[0]))
|
||||
assertThat(result[1]).isEqualTo(NotificationsEligibleNetworkConverter.convert(expectedNetworks[1]))
|
||||
coVerify { tangemTechApi.getEligibleNetworksForPushNotifications() }
|
||||
assertThat(result).isEqualTo(expectedCount)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,178 @@
|
|||
package com.tangem.data.notifications
|
||||
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.utils.info.AppInfoProvider
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
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
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultPushNotificationsRepositoryTest {
|
||||
private val tangemTechApi: TangemTechApi = mockk()
|
||||
private val appInfoProvider: AppInfoProvider = mockk()
|
||||
private val preferencesDataStore: DataStore<Preferences> = mockk()
|
||||
private val appPreferencesStore = AppPreferencesStore(
|
||||
moshi = Moshi.Builder().build(),
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
preferencesDataStore = preferencesDataStore,
|
||||
)
|
||||
private val repository = DefaultPushNotificationsRepository(
|
||||
tangemTechApi = tangemTechApi,
|
||||
appInfoProvider = appInfoProvider,
|
||||
appPreferencesStore = appPreferencesStore,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `GIVEN valid push token WHEN createApplicationId THEN returns application id`() = runTest {
|
||||
// GIVEN
|
||||
val pushToken = "test-push-token"
|
||||
val expectedAppId = ApplicationId("test-app-id")
|
||||
val expectedAppIdResponse = NotificationApplicationIdResponse(
|
||||
appId = expectedAppId.value,
|
||||
)
|
||||
coEvery { appInfoProvider.platform } returns "android"
|
||||
coEvery { appInfoProvider.device } returns "test-device"
|
||||
coEvery { appInfoProvider.osVersion } returns "11"
|
||||
coEvery { appInfoProvider.language } returns "en"
|
||||
coEvery { appInfoProvider.appVersion } returns "5.21.1"
|
||||
coEvery { appInfoProvider.timezone } returns "UTC"
|
||||
coEvery { tangemTechApi.createApplicationId(any()) } returns ApiResponse.Success(
|
||||
expectedAppIdResponse,
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = repository.createApplicationId(pushToken)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(expectedAppId)
|
||||
coVerify {
|
||||
tangemTechApi.createApplicationId(
|
||||
NotificationApplicationCreateBody(
|
||||
platform = "android",
|
||||
device = "test-device",
|
||||
systemVersion = "11",
|
||||
language = "en",
|
||||
timezone = "UTC",
|
||||
version = "5.21.1",
|
||||
pushToken = pushToken,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN application id WHEN saveApplicationId THEN stores it in preferences`() = runTest {
|
||||
// GIVEN
|
||||
val appId = ApplicationId("test-app-id")
|
||||
val preferences = mockk<Preferences>(relaxed = true)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns preferences
|
||||
|
||||
// WHEN
|
||||
repository.saveApplicationId(appId)
|
||||
|
||||
// THEN
|
||||
coVerify { preferencesDataStore.updateData(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN stored application id WHEN getApplicationId THEN returns it`() = runTest {
|
||||
// GIVEN
|
||||
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.value
|
||||
coEvery { preferencesDataStore.data } returns flowOf(preferences)
|
||||
|
||||
// WHEN
|
||||
val result = repository.getApplicationId()
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(expectedAppId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN application id and push token WHEN sendPushToken THEN updates push token`() = runTest {
|
||||
// GIVEN
|
||||
val appId = ApplicationId("test-app-id")
|
||||
val pushToken = "test-push-token"
|
||||
coEvery { appInfoProvider.device } returns "test-device"
|
||||
coEvery { appInfoProvider.osVersion } returns "11"
|
||||
coEvery { appInfoProvider.language } returns "en"
|
||||
coEvery { appInfoProvider.appVersion } returns "5.21.1"
|
||||
coEvery { appInfoProvider.timezone } returns "UTC"
|
||||
coEvery {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
systemVersion = "11",
|
||||
language = "en",
|
||||
timezone = "UTC",
|
||||
version = "5.21.1",
|
||||
),
|
||||
)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
|
||||
// WHEN
|
||||
repository.sendPushToken(appId, pushToken)
|
||||
|
||||
// THEN
|
||||
coVerify {
|
||||
tangemTechApi.updatePushTokenForApplicationId(
|
||||
appId.value,
|
||||
NotificationApplicationCreateBody(
|
||||
pushToken = pushToken,
|
||||
systemVersion = "11",
|
||||
language = "en",
|
||||
timezone = "UTC",
|
||||
version = "5.21.1",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN eligible networks WHEN getEligibleNetworks THEN returns converted networks`() = runTest {
|
||||
// GIVEN
|
||||
val expectedNetworks = listOf(
|
||||
CryptoNetworkResponse(
|
||||
id = 1,
|
||||
name = "Ethereum",
|
||||
networkId = "ethereum",
|
||||
),
|
||||
CryptoNetworkResponse(
|
||||
id = 2,
|
||||
name = "Bitcoin",
|
||||
networkId = "bitcoin",
|
||||
),
|
||||
)
|
||||
coEvery { tangemTechApi.getEligibleNetworksForPushNotifications() } returns ApiResponse.Success(
|
||||
expectedNetworks,
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = repository.getEligibleNetworks()
|
||||
|
||||
// THEN
|
||||
assertThat(result).hasSize(2)
|
||||
assertThat(result[0]).isEqualTo(NotificationsEligibleNetworkConverter.convert(expectedNetworks[0]))
|
||||
assertThat(result[1]).isEqualTo(NotificationsEligibleNetworkConverter.convert(expectedNetworks[1]))
|
||||
coVerify { tangemTechApi.getEligibleNetworksForPushNotifications() }
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ interface ChooseManagedTokensComponent : ComposableContentComponent {
|
|||
val initialCurrency: CryptoCurrency,
|
||||
val selectedCurrency: CryptoCurrency?,
|
||||
val source: Source,
|
||||
val showSendViaSwapNotification: Boolean,
|
||||
)
|
||||
|
||||
enum class Source {
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ dependencies {
|
|||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.swap.models)
|
||||
implementation(projects.domain.notifications)
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.managetokens.choosetoken.model
|
|||
import androidx.annotation.StringRes
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.common.ui.notifications.NotificationId
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
|
|
@ -15,6 +16,7 @@ import com.tangem.core.ui.event.triggeredEvent
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.message.SnackbarMessage
|
||||
import com.tangem.domain.notifications.SetShouldShowNotificationUseCase
|
||||
import com.tangem.features.managetokens.choosetoken.entity.ChooseManageTokensBottomSheetConfig
|
||||
import com.tangem.features.managetokens.choosetoken.entity.ChooseManagedTokenUM
|
||||
import com.tangem.features.managetokens.component.ChooseManagedTokensComponent
|
||||
|
|
@ -43,6 +45,7 @@ internal class ChooseManagedTokensModel @Inject constructor(
|
|||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val uiMessageSender: UiMessageSender,
|
||||
private val setShouldShowNotificationUseCase: SetShouldShowNotificationUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
manageTokensListManagerFactory: ManageTokensListManager.Factory,
|
||||
) : Model() {
|
||||
|
|
@ -107,18 +110,21 @@ internal class ChooseManagedTokensModel @Inject constructor(
|
|||
}
|
||||
|
||||
private fun getNotification(): NotificationUM? {
|
||||
return when (params.source) {
|
||||
Source.SendViaSwap -> ChooseManagedTokensNotificationUM.SendViaSwap(
|
||||
onCloseClick = ::removeNotification,
|
||||
)
|
||||
return if (params.source == Source.SendViaSwap && params.showSendViaSwapNotification) {
|
||||
ChooseManagedTokensNotificationUM.SendViaSwap(onCloseClick = ::removeNotification)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun removeNotification() {
|
||||
uiState.update {
|
||||
it.copy(
|
||||
notificationUM = null,
|
||||
)
|
||||
modelScope.launch {
|
||||
setShouldShowNotificationUseCase(NotificationId.SendViaSwapTokenSelectorNotification.key, false)
|
||||
uiState.update {
|
||||
it.copy(
|
||||
notificationUM = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ package com.tangem.features.send.v2.entrypoint.model
|
|||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.notifications.NotificationId
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.domain.notifications.ShouldShowNotificationUseCase
|
||||
import com.tangem.features.send.v2.api.SendComponent
|
||||
import com.tangem.features.send.v2.api.SendEntryPointComponent
|
||||
import com.tangem.features.send.v2.subcomponents.amount.SendAmountUpdateTrigger
|
||||
|
|
@ -18,6 +20,7 @@ import jakarta.inject.Inject
|
|||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@ModelScoped
|
||||
internal class SendEntryPointModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
|
|
@ -26,6 +29,7 @@ internal class SendEntryPointModel @Inject constructor(
|
|||
private val swapChooseTokenNetworkListener: SwapChooseTokenNetworkListener,
|
||||
private val sendAmountUpdateTrigger: SendAmountUpdateTrigger,
|
||||
private val swapAmountUpdateTrigger: SwapAmountUpdateTrigger,
|
||||
private val shouldShowNotificationUseCase: ShouldShowNotificationUseCase,
|
||||
) : Model(), SendComponent.ModelCallback, SendWithSwapComponent.ModelCallback {
|
||||
|
||||
private val params: SendEntryPointComponent.Params = paramsContainer.require()
|
||||
|
|
@ -36,15 +40,21 @@ internal class SendEntryPointModel @Inject constructor(
|
|||
private var swapChooseTokenListenerJobHolder = JobHolder()
|
||||
|
||||
override fun onConvertToAnotherToken(lastAmount: String) {
|
||||
appRouter.push(
|
||||
AppRoute.ChooseManagedTokens(
|
||||
userWalletId = params.userWalletId,
|
||||
initialCurrency = params.cryptoCurrency,
|
||||
selectedCurrency = null,
|
||||
source = AppRoute.ChooseManagedTokens.Source.SendViaSwap,
|
||||
),
|
||||
)
|
||||
observeChooseSelectToken(lastAmount)
|
||||
modelScope.launch {
|
||||
val showSendViaSwapNotification = shouldShowNotificationUseCase(
|
||||
NotificationId.SendViaSwapTokenSelectorNotification.key,
|
||||
)
|
||||
appRouter.push(
|
||||
AppRoute.ChooseManagedTokens(
|
||||
userWalletId = params.userWalletId,
|
||||
initialCurrency = params.cryptoCurrency,
|
||||
selectedCurrency = null,
|
||||
source = AppRoute.ChooseManagedTokens.Source.SendViaSwap,
|
||||
showSendViaSwapNotification = showSendViaSwapNotification,
|
||||
),
|
||||
)
|
||||
observeChooseSelectToken(lastAmount)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCloseSwap(lastAmount: String) {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ dependencies {
|
|||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.txhistory.models)
|
||||
implementation(projects.domain.txhistory)
|
||||
implementation(projects.domain.notifications)
|
||||
implementation(projects.domain.feedback.models)
|
||||
implementation(projects.domain.feedback)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.common.ui.amountScreen.models.AmountState
|
|||
import com.tangem.common.ui.amountScreen.models.EnterAmountBoundary
|
||||
import com.tangem.common.ui.navigationButtons.NavigationButton
|
||||
import com.tangem.common.ui.navigationButtons.NavigationUM
|
||||
import com.tangem.common.ui.notifications.NotificationId
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
|
|
@ -22,6 +23,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
|||
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
||||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.swap.models.SwapCurrencies
|
||||
import com.tangem.domain.notifications.ShouldShowNotificationUseCase
|
||||
import com.tangem.domain.swap.models.SwapDirection
|
||||
import com.tangem.domain.swap.models.SwapDirection.Companion.withSwapDirection
|
||||
import com.tangem.domain.swap.models.SwapQuoteModel
|
||||
|
|
@ -81,6 +83,7 @@ internal class SwapAmountModel @Inject constructor(
|
|||
private val swapAmountUpdateListener: SwapAmountUpdateListener,
|
||||
private val swapAmountReduceListener: SwapAmountReduceListener,
|
||||
private val feeSelectorReloadTrigger: FeeSelectorReloadTrigger,
|
||||
private val shouldShowNotificationUseCase: ShouldShowNotificationUseCase,
|
||||
) : Model(), SwapAmountClickIntents, SwapChooseProviderComponent.ModelCallback {
|
||||
|
||||
private val params: SwapAmountComponentParams = paramsContainer.require()
|
||||
|
|
@ -221,6 +224,9 @@ internal class SwapAmountModel @Inject constructor(
|
|||
override fun onSelectTokenClick() {
|
||||
val amountParams = params as? SwapAmountComponentParams.AmountParams ?: return
|
||||
modelScope.launch {
|
||||
val showSendViaSwapNotification = shouldShowNotificationUseCase(
|
||||
NotificationId.SendViaSwapTokenSelectorNotification.key,
|
||||
)
|
||||
val isEditMode = amountParams.currentRoute.firstOrNull()?.isEditMode == true
|
||||
val selectedCurrency = (uiState.value as? SwapAmountUM.Content)?.secondaryCryptoCurrencyStatus?.currency
|
||||
appRouter.push(
|
||||
|
|
@ -229,6 +235,7 @@ internal class SwapAmountModel @Inject constructor(
|
|||
initialCurrency = primaryCryptoCurrency,
|
||||
selectedCurrency = selectedCurrency.takeIf { isEditMode },
|
||||
source = AppRoute.ChooseManagedTokens.Source.SendViaSwap,
|
||||
showSendViaSwapNotification = showSendViaSwapNotification,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -259,6 +266,7 @@ internal class SwapAmountModel @Inject constructor(
|
|||
private fun confirmSendWithSwapClose() {
|
||||
val amountParams = params as? SwapAmountComponentParams.AmountParams ?: return
|
||||
val amountFieldData = uiState.value.primaryAmount.amountField as? AmountState.Data
|
||||
val callback = (params as? SwapAmountComponentParams.AmountParams)?.callback ?: return
|
||||
|
||||
val primaryCryptoCurrencyStatus = (uiState.value as? SwapAmountUM.Content)?.primaryCryptoCurrencyStatus
|
||||
if (primaryCryptoCurrencyStatus != null) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue