Updated on 2026-08-14
This commit is contained in:
parent
a0f6e4a008
commit
6c8b59c569
12 changed files with 288 additions and 66 deletions
|
|
@ -4,7 +4,7 @@ import com.google.firebase.messaging.FirebaseMessaging
|
|||
import com.tangem.utils.notifications.PushNotificationsTokenProvider
|
||||
import kotlinx.coroutines.tasks.await
|
||||
|
||||
class FirebasePushNotificationsTokenProvider : PushNotificationsTokenProvider {
|
||||
internal class FirebasePushNotificationsTokenProvider : PushNotificationsTokenProvider {
|
||||
override suspend fun getToken(): String {
|
||||
return FirebaseMessaging.getInstance().token.await()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,6 +125,8 @@ object PreferencesKeys {
|
|||
|
||||
// region Notifications
|
||||
val NOTIFICATIONS_APPLICATION_ID_KEY by lazy { stringPreferencesKey(name = "notificationsApplicationId") }
|
||||
|
||||
val NOTIFICATIONS_ENABLED_STATES_KEY by lazy { stringPreferencesKey(name = "notificationsEnabledStates") }
|
||||
// endregion
|
||||
|
||||
fun getShouldShowStoriesKey(storyId: String) = booleanPreferencesKey("shouldShowStories_$storyId")
|
||||
|
|
|
|||
|
|
@ -44,14 +44,6 @@ internal class DefaultNotificationsRepository @Inject constructor(
|
|||
return appPreferencesStore.getSyncOrNull(PreferencesKeys.NOTIFICATIONS_APPLICATION_ID_KEY)
|
||||
}
|
||||
|
||||
override suspend fun setNotificationsEnabledForWallet(walletId: String, enabled: Boolean) =
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.setNotificationsEnabled(
|
||||
walletId = walletId,
|
||||
body = WalletBody(notifyStatus = enabled),
|
||||
).getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun associateApplicationIdWithWallets(appId: String, wallets: List<String>) =
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.associateApplicationIdWithWallets(
|
||||
|
|
@ -62,10 +54,6 @@ internal class DefaultNotificationsRepository @Inject constructor(
|
|||
).getOrThrow()
|
||||
}
|
||||
|
||||
override suspend fun isNotificationsEnabledForWallet(walletId: String): Boolean = withContext(dispatchers.io) {
|
||||
tangemTechApi.getWalletById(walletId).getOrThrow().notifyStatus
|
||||
}
|
||||
|
||||
override suspend fun setWalletName(walletId: String, walletName: String) = withContext(dispatchers.io) {
|
||||
tangemTechApi.updateWallet(
|
||||
walletId,
|
||||
|
|
|
|||
|
|
@ -103,26 +103,6 @@ class DefaultNotificationsRepositoryTest {
|
|||
assertThat(result).isEqualTo(expectedAppId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN wallet id and enabled status WHEN setNotificationsEnabledForWallet THEN updates notification status`() =
|
||||
runTest {
|
||||
// GIVEN
|
||||
val walletId = "test-wallet-id"
|
||||
val enabled = true
|
||||
coEvery {
|
||||
tangemTechApi.setNotificationsEnabled(
|
||||
walletId,
|
||||
WalletBody(notifyStatus = enabled),
|
||||
)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
|
||||
// WHEN
|
||||
repository.setNotificationsEnabledForWallet(walletId, enabled)
|
||||
|
||||
// THEN
|
||||
coVerify { tangemTechApi.setNotificationsEnabled(walletId, WalletBody(notifyStatus = enabled)) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN application id and wallet list WHEN associateApplicationIdWithWallets THEN associates them`() = runTest {
|
||||
// GIVEN
|
||||
|
|
@ -142,26 +122,6 @@ class DefaultNotificationsRepositoryTest {
|
|||
coVerify { tangemTechApi.associateApplicationIdWithWallets(appId, wallets.map { WalletIdBody(it) }) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN wallet id WHEN isNotificationsEnabledForWallet THEN returns notification status`() = runTest {
|
||||
// GIVEN
|
||||
val walletId = "test-wallet-id"
|
||||
val expectedStatus = true
|
||||
coEvery { tangemTechApi.getWalletById(walletId) } returns ApiResponse.Success(
|
||||
WalletResponse(
|
||||
notifyStatus = expectedStatus,
|
||||
name = "Test Wallet",
|
||||
id = walletId,
|
||||
),
|
||||
)
|
||||
|
||||
// WHEN
|
||||
val result = repository.isNotificationsEnabledForWallet(walletId)
|
||||
|
||||
// THEN
|
||||
assertThat(result).isEqualTo(expectedStatus)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN wallet id and name WHEN setWalletName THEN updates wallet name`() = runTest {
|
||||
// GIVEN
|
||||
|
|
|
|||
|
|
@ -34,4 +34,12 @@ dependencies {
|
|||
/** Other deps */
|
||||
implementation(deps.androidx.datastore)
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
/** tests */
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.mockk)
|
||||
testImplementation(deps.moshi)
|
||||
testImplementation(deps.moshi.kotlin)
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.tangem.datasource.api.tangemTech.TangemTechApi
|
|||
import com.tangem.datasource.api.tangemTech.models.MarkUserWalletWasCreatedBody
|
||||
import com.tangem.datasource.api.tangemTech.models.SeedPhraseNotificationDTO
|
||||
import com.tangem.datasource.api.tangemTech.models.SeedPhraseNotificationDTO.Status
|
||||
import com.tangem.datasource.api.tangemTech.models.WalletBody
|
||||
import com.tangem.datasource.local.datastore.RuntimeStateStore
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
|
|
@ -21,11 +22,9 @@ import com.tangem.domain.wallets.repository.WalletsRepository
|
|||
import com.tangem.utils.WEEK_MILLIS
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.runCatching
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
typealias SeedPhraseNotificationsStatuses = Map<UserWalletId, SeedPhraseNotificationsStatus>
|
||||
|
||||
|
|
@ -231,4 +230,44 @@ 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 setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean) {
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.setNotificationsEnabled(
|
||||
walletId = userWalletId.stringValue,
|
||||
body = WalletBody(notifyStatus = isEnabled),
|
||||
).getOrThrow()
|
||||
setNotificationsEnabledLocally(userWalletId, isEnabled)
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,176 @@
|
|||
package com.tangem.data.wallets
|
||||
|
||||
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.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
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.*
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultWalletsRepositoryTest {
|
||||
private lateinit var repository: DefaultWalletsRepository
|
||||
private val preferencesDataStore = mockk<DataStore<Preferences>>(relaxed = true)
|
||||
private val appPreferenceStore = AppPreferencesStore(
|
||||
moshi = Moshi.Builder().build(),
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
preferencesDataStore = preferencesDataStore,
|
||||
)
|
||||
private lateinit var tangemTechApi: TangemTechApi
|
||||
private lateinit var dispatchers: CoroutineDispatcherProvider
|
||||
|
||||
private val testWalletId = UserWalletId("1234567890abcdef")
|
||||
private val testWalletResponse = WalletResponse(
|
||||
id = testWalletId.stringValue,
|
||||
notifyStatus = true,
|
||||
)
|
||||
|
||||
@Before
|
||||
fun setup() {
|
||||
tangemTechApi = mockk()
|
||||
dispatchers = TestingCoroutineDispatcherProvider()
|
||||
repository = DefaultWalletsRepository(
|
||||
appPreferencesStore = appPreferenceStore,
|
||||
tangemTechApi = tangemTechApi,
|
||||
userWalletsStore = mockk(),
|
||||
seedPhraseNotificationVisibilityStore = mockk(),
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
@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
|
||||
val expectedPreferences = """{"${testWalletId.stringValue}":true}"""
|
||||
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)
|
||||
|
||||
// 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 {
|
||||
// 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)
|
||||
|
||||
// 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 enabled status WHEN setNotificationsEnabled THEN should update API and 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>()
|
||||
|
||||
// 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 {
|
||||
// GIVEN
|
||||
coEvery {
|
||||
tangemTechApi.setNotificationsEnabled(
|
||||
eq(testWalletId.stringValue),
|
||||
eq(WalletBody(notifyStatus = false)),
|
||||
)
|
||||
} returns ApiResponse.Success(Unit)
|
||||
coEvery { preferencesDataStore.updateData(any()) } returns mockk<Preferences>()
|
||||
|
||||
// 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()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,17 +1,31 @@
|
|||
plugins {
|
||||
alias(deps.plugins.kotlin.jvm)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.domain.notifications"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.domain.core)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.notifications.models)
|
||||
implementation(projects.domain.wallets)
|
||||
|
||||
/* Tests */
|
||||
// region DI
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
// end
|
||||
|
||||
// region Tests
|
||||
testImplementation(deps.test.junit)
|
||||
testImplementation(deps.test.coroutine)
|
||||
testImplementation(deps.test.truth)
|
||||
testImplementation(deps.test.mockk)
|
||||
// end
|
||||
}
|
||||
|
|
@ -11,15 +11,9 @@ interface NotificationsRepository {
|
|||
|
||||
suspend fun getApplicationId(): String?
|
||||
|
||||
@Throws
|
||||
suspend fun setNotificationsEnabledForWallet(walletId: String, enabled: Boolean)
|
||||
|
||||
@Throws
|
||||
suspend fun associateApplicationIdWithWallets(appId: String, wallets: List<String>)
|
||||
|
||||
@Throws
|
||||
suspend fun isNotificationsEnabledForWallet(walletId: String): Boolean
|
||||
|
||||
@Throws
|
||||
suspend fun setWalletName(walletId: String, walletName: String)
|
||||
|
||||
|
|
|
|||
|
|
@ -37,4 +37,10 @@ interface WalletsRepository {
|
|||
suspend fun enableNFT(userWalletId: UserWalletId)
|
||||
|
||||
suspend fun disableNFT(userWalletId: UserWalletId)
|
||||
|
||||
@Throws
|
||||
suspend fun isNotificationsEnabled(userWalletId: UserWalletId, force: Boolean): Boolean
|
||||
|
||||
@Throws
|
||||
suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean)
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
|
||||
class GetIsNotificationsEnabledUseCase(
|
||||
private val walletsRepository: WalletsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, force: Boolean): Either<Throwable, Boolean> = Either.catch {
|
||||
walletsRepository.isNotificationsEnabled(
|
||||
userWalletId = userWalletId,
|
||||
force = force,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.wallets.usecase
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||
|
||||
class SetNotificationsEnabledUseCase(
|
||||
private val walletsRepository: WalletsRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(userWalletId: UserWalletId, isEnabled: Boolean): Either<Throwable, Unit> =
|
||||
Either.catch {
|
||||
walletsRepository.setNotificationsEnabled(
|
||||
userWalletId = userWalletId,
|
||||
isEnabled = isEnabled,
|
||||
)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue