Updated on 2026-08-14
This commit is contained in:
parent
4578bb824e
commit
0979d1b71b
22 changed files with 565 additions and 0 deletions
|
|
@ -137,6 +137,7 @@ dependencies {
|
||||||
implementation(projects.domain.appTheme.models)
|
implementation(projects.domain.appTheme.models)
|
||||||
implementation(projects.domain.balanceHiding)
|
implementation(projects.domain.balanceHiding)
|
||||||
implementation(projects.domain.balanceHiding.models)
|
implementation(projects.domain.balanceHiding.models)
|
||||||
|
implementation(projects.domain.pushNotificationPreferences)
|
||||||
implementation(projects.domain.transaction)
|
implementation(projects.domain.transaction)
|
||||||
implementation(projects.domain.transaction.models)
|
implementation(projects.domain.transaction.models)
|
||||||
implementation(projects.domain.analytics)
|
implementation(projects.domain.analytics)
|
||||||
|
|
@ -197,6 +198,7 @@ dependencies {
|
||||||
implementation(projects.data.appCurrency)
|
implementation(projects.data.appCurrency)
|
||||||
implementation(projects.data.appTheme)
|
implementation(projects.data.appTheme)
|
||||||
implementation(projects.data.balanceHiding)
|
implementation(projects.data.balanceHiding)
|
||||||
|
implementation(projects.data.pushNotificationPreferences)
|
||||||
implementation(projects.data.card)
|
implementation(projects.data.card)
|
||||||
implementation(projects.data.common)
|
implementation(projects.data.common)
|
||||||
implementation(projects.data.settings)
|
implementation(projects.data.settings)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.ObserveWalletPushNotificationPreferencesUseCase
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.PreloadWalletPushNotificationPreferencesUseCase
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.UpdateWalletPushNotificationPreferenceUseCase
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.repository.WalletPushNotificationPreferencesRepository
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object PushNotificationPreferencesDomainModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providesPreloadWalletPushNotificationPreferencesUseCase(
|
||||||
|
repository: WalletPushNotificationPreferencesRepository,
|
||||||
|
): PreloadWalletPushNotificationPreferencesUseCase {
|
||||||
|
return PreloadWalletPushNotificationPreferencesUseCase(repository = repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providesObserveWalletPushNotificationPreferencesUseCase(
|
||||||
|
repository: WalletPushNotificationPreferencesRepository,
|
||||||
|
): ObserveWalletPushNotificationPreferencesUseCase {
|
||||||
|
return ObserveWalletPushNotificationPreferencesUseCase(repository = repository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun providesUpdateWalletPushNotificationPreferenceUseCase(
|
||||||
|
repository: WalletPushNotificationPreferencesRepository,
|
||||||
|
): UpdateWalletPushNotificationPreferenceUseCase {
|
||||||
|
return UpdateWalletPushNotificationPreferenceUseCase(repository = repository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -50,6 +50,17 @@ interface TangemTechApi {
|
||||||
@Body userTokens: UserTokensResponse,
|
@Body userTokens: UserTokensResponse,
|
||||||
): ApiResponse<Unit>
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
|
@GET("/v1/wallets/{wallet_id}/notification-preferences")
|
||||||
|
suspend fun getPushNotificationPreferences(
|
||||||
|
@Path("wallet_id") walletId: String,
|
||||||
|
): ApiResponse<PushNotificationPreferencesResponse>
|
||||||
|
|
||||||
|
@PUT("/v1/wallets/{wallet_id}/notification-preferences")
|
||||||
|
suspend fun updatePushNotificationPreferences(
|
||||||
|
@Path("wallet_id") walletId: String,
|
||||||
|
@Body body: PushNotificationPreferencesBody,
|
||||||
|
): ApiResponse<Unit>
|
||||||
|
|
||||||
// region Referral
|
// region Referral
|
||||||
/** Returns referral status by [walletId] */
|
/** Returns referral status by [walletId] */
|
||||||
@GET("v1/referral/{walletId}")
|
@GET("v1/referral/{walletId}")
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PushNotificationPreferenceState(
|
||||||
|
@Json(name = "isEnabled") val isEnabled: Boolean,
|
||||||
|
@Json(name = "isVisible") val isVisible: Boolean,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PushNotificationPreferencesBody(
|
||||||
|
@Json(name = "transactionAlerts")
|
||||||
|
val areTransactionAlertsEnabled: Boolean,
|
||||||
|
@Json(name = "offersUpdates")
|
||||||
|
val areOffersUpdatesEnabled: Boolean,
|
||||||
|
@Json(name = "priceAlerts")
|
||||||
|
val arePriceAlertsEnabled: Boolean,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.tangem.datasource.api.tangemTech.models
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
data class PushNotificationPreferencesResponse(
|
||||||
|
@Json(name = "transactionAlerts") val transactionAlerts: PushNotificationPreferenceState,
|
||||||
|
@Json(name = "offersUpdates") val offersUpdates: PushNotificationPreferenceState,
|
||||||
|
@Json(name = "priceAlerts") val priceAlerts: PushNotificationPreferenceState,
|
||||||
|
)
|
||||||
39
data/push-notification-preferences/build.gradle.kts
Normal file
39
data/push-notification-preferences/build.gradle.kts
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
plugins {
|
||||||
|
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.data.pushnotificationpreferences"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
/** Domain */
|
||||||
|
implementation(projects.domain.pushNotificationPreferences)
|
||||||
|
implementation(projects.domain.models)
|
||||||
|
|
||||||
|
/** Core */
|
||||||
|
implementation(projects.core.datasource)
|
||||||
|
implementation(projects.core.utils)
|
||||||
|
|
||||||
|
/** Other */
|
||||||
|
implementation(deps.androidx.datastore)
|
||||||
|
implementation(deps.arrow.core)
|
||||||
|
implementation(deps.kotlin.coroutines)
|
||||||
|
|
||||||
|
/** DI */
|
||||||
|
implementation(deps.hilt.android)
|
||||||
|
kapt(deps.hilt.kapt)
|
||||||
|
|
||||||
|
/** Tests */
|
||||||
|
testImplementation(deps.test.junit)
|
||||||
|
testImplementation(deps.test.coroutine)
|
||||||
|
testImplementation(deps.test.truth)
|
||||||
|
testImplementation(deps.test.mockk)
|
||||||
|
testImplementation(deps.test.turbine)
|
||||||
|
testImplementation(deps.moshi)
|
||||||
|
testImplementation(deps.moshi.kotlin)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,110 @@
|
||||||
|
package com.tangem.data.pushnotificationpreferences
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
|
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
|
import com.tangem.datasource.local.preferences.utils.getObjectMapSync
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.PushNotificationCategory
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.PushNotificationPreference
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.WalletPushNotificationPreferences
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.repository.WalletPushNotificationPreferencesRepository
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||||
|
import kotlinx.coroutines.flow.filterNotNull
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onStart
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In-memory cache implementation of [WalletPushNotificationPreferencesRepository].
|
||||||
|
*
|
||||||
|
* Mock-mode (current): defaults are computed locally and writes are kept in-memory only.
|
||||||
|
* Real-mode (when Variant C BE is ready): replace TODO blocks with [TangemTechApi] calls.
|
||||||
|
*
|
||||||
|
* Defaults for existing users (until BE migration runs): TX read from
|
||||||
|
* [PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY] (default true), Offers&Updates = true, Price Alerts = false,
|
||||||
|
* isVisible = true for all three.
|
||||||
|
*/
|
||||||
|
internal class DefaultWalletPushNotificationPreferencesRepository(
|
||||||
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
|
@Suppress("unused") private val tangemTechApi: TangemTechApi,
|
||||||
|
private val cache: RuntimeSharedStore<Map<String, WalletPushNotificationPreferences>>,
|
||||||
|
private val dispatchers: CoroutineDispatcherProvider,
|
||||||
|
) : WalletPushNotificationPreferencesRepository {
|
||||||
|
|
||||||
|
override suspend fun preload(userWalletId: UserWalletId) {
|
||||||
|
if (cache.getSyncOrNull()?.containsKey(userWalletId.stringValue) == true) return
|
||||||
|
val preferences = withContext(dispatchers.io) {
|
||||||
|
// TODO: uncomment when api is ready
|
||||||
|
// val response = tangemTechApi.getPushNotificationPreferences(userWalletId.stringValue).getOrThrow()
|
||||||
|
// PushNotificationPreferencesConverter.convert(response)
|
||||||
|
loadDefaults(userWalletId)
|
||||||
|
}
|
||||||
|
cache.update(default = emptyMap()) { current ->
|
||||||
|
if (current.containsKey(userWalletId.stringValue)) {
|
||||||
|
current
|
||||||
|
} else {
|
||||||
|
current + (userWalletId.stringValue to preferences)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun observePreferences(userWalletId: UserWalletId): Flow<WalletPushNotificationPreferences> = cache.get()
|
||||||
|
.onStart { preload(userWalletId) }
|
||||||
|
.map { it[userWalletId.stringValue] }
|
||||||
|
.filterNotNull()
|
||||||
|
.distinctUntilChanged()
|
||||||
|
|
||||||
|
override suspend fun updatePreference(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
category: PushNotificationCategory,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
): Either<Throwable, Unit> = Either.catch {
|
||||||
|
val current = cache.getSyncOrNull()?.get(userWalletId.stringValue) ?: loadDefaults(userWalletId)
|
||||||
|
val updated = applyCategory(current, category, isEnabled)
|
||||||
|
withContext(dispatchers.io) {
|
||||||
|
// TODO: uncomment when api is ready
|
||||||
|
// tangemTechApi.updatePushNotificationPreferences(
|
||||||
|
// walletId = userWalletId.stringValue,
|
||||||
|
// body = PushNotificationPreferencesBody(
|
||||||
|
// areTransactionAlertsEnabled = updated.transactionAlerts.isEnabled,
|
||||||
|
// areOffersUpdatesEnabled = updated.offersUpdates.isEnabled,
|
||||||
|
// arePriceAlertsEnabled = updated.priceAlerts.isEnabled,
|
||||||
|
// ),
|
||||||
|
// ).getOrThrow()
|
||||||
|
}
|
||||||
|
cache.update(default = emptyMap()) { it + (userWalletId.stringValue to updated) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun applyCategory(
|
||||||
|
current: WalletPushNotificationPreferences,
|
||||||
|
category: PushNotificationCategory,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
): WalletPushNotificationPreferences = when (category) {
|
||||||
|
PushNotificationCategory.TransactionAlerts -> current.copy(
|
||||||
|
transactionAlerts = current.transactionAlerts.copy(isEnabled = isEnabled),
|
||||||
|
)
|
||||||
|
PushNotificationCategory.OffersUpdates -> current.copy(
|
||||||
|
offersUpdates = current.offersUpdates.copy(isEnabled = isEnabled),
|
||||||
|
)
|
||||||
|
PushNotificationCategory.PriceAlerts -> current.copy(
|
||||||
|
priceAlerts = current.priceAlerts.copy(isEnabled = isEnabled),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO remove when api is ready, use api methods to load real settings
|
||||||
|
private suspend fun loadDefaults(userWalletId: UserWalletId): WalletPushNotificationPreferences {
|
||||||
|
val areTransactionAlertsEnabled = appPreferencesStore
|
||||||
|
.getObjectMapSync<Boolean>(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY)[userWalletId.stringValue] !=
|
||||||
|
false
|
||||||
|
return WalletPushNotificationPreferences(
|
||||||
|
transactionAlerts = PushNotificationPreference(isEnabled = areTransactionAlertsEnabled, isVisible = true),
|
||||||
|
offersUpdates = PushNotificationPreference(isEnabled = true, isVisible = true),
|
||||||
|
priceAlerts = PushNotificationPreference(isEnabled = false, isVisible = true),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.data.pushnotificationpreferences.converters
|
||||||
|
|
||||||
|
import com.tangem.datasource.api.tangemTech.models.PushNotificationPreferenceState
|
||||||
|
import com.tangem.datasource.api.tangemTech.models.PushNotificationPreferencesResponse
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.PushNotificationPreference
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.WalletPushNotificationPreferences
|
||||||
|
import com.tangem.utils.converter.Converter
|
||||||
|
|
||||||
|
internal object PushNotificationPreferencesConverter :
|
||||||
|
Converter<PushNotificationPreferencesResponse, WalletPushNotificationPreferences> {
|
||||||
|
|
||||||
|
override fun convert(value: PushNotificationPreferencesResponse): WalletPushNotificationPreferences =
|
||||||
|
WalletPushNotificationPreferences(
|
||||||
|
transactionAlerts = value.transactionAlerts.toDomain(),
|
||||||
|
offersUpdates = value.offersUpdates.toDomain(),
|
||||||
|
priceAlerts = value.priceAlerts.toDomain(),
|
||||||
|
)
|
||||||
|
|
||||||
|
private fun PushNotificationPreferenceState.toDomain(): PushNotificationPreference =
|
||||||
|
PushNotificationPreference(isEnabled = isEnabled, isVisible = isVisible)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
package com.tangem.data.pushnotificationpreferences.di
|
||||||
|
|
||||||
|
import com.tangem.data.pushnotificationpreferences.DefaultWalletPushNotificationPreferencesRepository
|
||||||
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
|
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.repository.WalletPushNotificationPreferencesRepository
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
internal object PushNotificationPreferencesModule {
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
@Provides
|
||||||
|
fun providesWalletPushNotificationPreferencesRepository(
|
||||||
|
appPreferencesStore: AppPreferencesStore,
|
||||||
|
tangemTechApi: TangemTechApi,
|
||||||
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
): WalletPushNotificationPreferencesRepository = DefaultWalletPushNotificationPreferencesRepository(
|
||||||
|
appPreferencesStore = appPreferencesStore,
|
||||||
|
tangemTechApi = tangemTechApi,
|
||||||
|
cache = RuntimeSharedStore(),
|
||||||
|
dispatchers = dispatchers,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,141 @@
|
||||||
|
package com.tangem.data.pushnotificationpreferences
|
||||||
|
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.emptyPreferences
|
||||||
|
import app.cash.turbine.test
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||||
|
import com.tangem.datasource.local.datastore.RuntimeSharedStore
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.PushNotificationCategory
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.PushNotificationPreference
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.WalletPushNotificationPreferences
|
||||||
|
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||||
|
import io.mockk.coEvery
|
||||||
|
import io.mockk.mockk
|
||||||
|
import kotlinx.coroutines.flow.flowOf
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class DefaultWalletPushNotificationPreferencesRepositoryTest {
|
||||||
|
|
||||||
|
private val tangemTechApi: TangemTechApi = mockk()
|
||||||
|
private val preferencesDataStore: DataStore<Preferences> = mockk()
|
||||||
|
private val appPreferencesStore = AppPreferencesStore(
|
||||||
|
moshi = Moshi.Builder().build(),
|
||||||
|
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||||
|
preferencesDataStore = preferencesDataStore,
|
||||||
|
)
|
||||||
|
|
||||||
|
private val userWalletId = UserWalletId(stringValue = "0011223344556677")
|
||||||
|
private val otherWalletId = UserWalletId(stringValue = "ffeeddccbbaa9988")
|
||||||
|
|
||||||
|
private val repository = DefaultWalletPushNotificationPreferencesRepository(
|
||||||
|
appPreferencesStore = appPreferencesStore,
|
||||||
|
tangemTechApi = tangemTechApi,
|
||||||
|
cache = RuntimeSharedStore(),
|
||||||
|
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||||
|
)
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN no prior state WHEN preload THEN cache contains defaults`() = runTest {
|
||||||
|
coEvery { preferencesDataStore.data } returns flowOf(emptyPreferences())
|
||||||
|
|
||||||
|
repository.preload(userWalletId)
|
||||||
|
|
||||||
|
repository.observePreferences(userWalletId).test {
|
||||||
|
assertThat(awaitItem()).isEqualTo(defaults(transactionAlertsEnabled = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN preload already done WHEN preload called again THEN no-op`() = runTest {
|
||||||
|
coEvery { preferencesDataStore.data } returns flowOf(emptyPreferences())
|
||||||
|
|
||||||
|
repository.preload(userWalletId)
|
||||||
|
repository.updatePreference(userWalletId, PushNotificationCategory.OffersUpdates, isEnabled = false)
|
||||||
|
repository.preload(userWalletId)
|
||||||
|
|
||||||
|
repository.observePreferences(userWalletId).test {
|
||||||
|
val item = awaitItem()
|
||||||
|
assertThat(item.offersUpdates.isEnabled).isFalse()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN cache miss WHEN updatePreference THEN loads defaults and applies update`() = runTest {
|
||||||
|
coEvery { preferencesDataStore.data } returns flowOf(emptyPreferences())
|
||||||
|
|
||||||
|
val result = repository.updatePreference(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
category = PushNotificationCategory.PriceAlerts,
|
||||||
|
isEnabled = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isInstanceOf(Either.Right::class.java)
|
||||||
|
repository.observePreferences(userWalletId).test {
|
||||||
|
val item = awaitItem()
|
||||||
|
assertThat(item.priceAlerts.isEnabled).isTrue()
|
||||||
|
assertThat(item.offersUpdates.isEnabled).isTrue()
|
||||||
|
assertThat(item.transactionAlerts.isEnabled).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN preloaded state WHEN updatePreference for each category THEN updates only that category`() = runTest {
|
||||||
|
coEvery { preferencesDataStore.data } returns flowOf(emptyPreferences())
|
||||||
|
|
||||||
|
repository.preload(userWalletId)
|
||||||
|
repository.updatePreference(userWalletId, PushNotificationCategory.TransactionAlerts, isEnabled = false)
|
||||||
|
repository.updatePreference(userWalletId, PushNotificationCategory.OffersUpdates, isEnabled = false)
|
||||||
|
repository.updatePreference(userWalletId, PushNotificationCategory.PriceAlerts, isEnabled = true)
|
||||||
|
|
||||||
|
repository.observePreferences(userWalletId).test {
|
||||||
|
val item = awaitItem()
|
||||||
|
assertThat(item.transactionAlerts.isEnabled).isFalse()
|
||||||
|
assertThat(item.offersUpdates.isEnabled).isFalse()
|
||||||
|
assertThat(item.priceAlerts.isEnabled).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN no subscription yet WHEN observePreferences subscribed THEN triggers preload and emits defaults`() =
|
||||||
|
runTest {
|
||||||
|
coEvery { preferencesDataStore.data } returns flowOf(emptyPreferences())
|
||||||
|
|
||||||
|
repository.observePreferences(userWalletId).test {
|
||||||
|
val item = awaitItem()
|
||||||
|
assertThat(item).isEqualTo(defaults(transactionAlertsEnabled = true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `GIVEN updates for different wallets WHEN observed independently THEN each wallet has its own state`() =
|
||||||
|
runTest {
|
||||||
|
coEvery { preferencesDataStore.data } returns flowOf(emptyPreferences())
|
||||||
|
|
||||||
|
repository.updatePreference(userWalletId, PushNotificationCategory.OffersUpdates, isEnabled = false)
|
||||||
|
repository.updatePreference(otherWalletId, PushNotificationCategory.PriceAlerts, isEnabled = true)
|
||||||
|
|
||||||
|
repository.observePreferences(userWalletId).test {
|
||||||
|
val item = awaitItem()
|
||||||
|
assertThat(item.offersUpdates.isEnabled).isFalse()
|
||||||
|
assertThat(item.priceAlerts.isEnabled).isFalse()
|
||||||
|
}
|
||||||
|
repository.observePreferences(otherWalletId).test {
|
||||||
|
val item = awaitItem()
|
||||||
|
assertThat(item.offersUpdates.isEnabled).isTrue()
|
||||||
|
assertThat(item.priceAlerts.isEnabled).isTrue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun defaults(transactionAlertsEnabled: Boolean) = WalletPushNotificationPreferences(
|
||||||
|
transactionAlerts = PushNotificationPreference(isEnabled = transactionAlertsEnabled, isVisible = true),
|
||||||
|
offersUpdates = PushNotificationPreference(isEnabled = true, isVisible = true),
|
||||||
|
priceAlerts = PushNotificationPreference(isEnabled = false, isVisible = true),
|
||||||
|
)
|
||||||
|
}
|
||||||
24
domain/push-notification-preferences/build.gradle.kts
Normal file
24
domain/push-notification-preferences/build.gradle.kts
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
plugins {
|
||||||
|
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.pushnotificationpreferences"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
/** Domain */
|
||||||
|
implementation(projects.domain.models)
|
||||||
|
|
||||||
|
/** Other */
|
||||||
|
implementation(deps.arrow.core)
|
||||||
|
implementation(deps.kotlin.coroutines)
|
||||||
|
|
||||||
|
/** DI */
|
||||||
|
implementation(deps.hilt.android)
|
||||||
|
kapt(deps.hilt.kapt)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.tangem.domain.pushnotificationpreferences
|
||||||
|
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.WalletPushNotificationPreferences
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.repository.WalletPushNotificationPreferencesRepository
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
class ObserveWalletPushNotificationPreferencesUseCase(
|
||||||
|
private val repository: WalletPushNotificationPreferencesRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
operator fun invoke(userWalletId: UserWalletId): Flow<WalletPushNotificationPreferences> =
|
||||||
|
repository.observePreferences(userWalletId)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.tangem.domain.pushnotificationpreferences
|
||||||
|
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.repository.WalletPushNotificationPreferencesRepository
|
||||||
|
|
||||||
|
class PreloadWalletPushNotificationPreferencesUseCase(
|
||||||
|
private val repository: WalletPushNotificationPreferencesRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(userWalletId: UserWalletId) = repository.preload(userWalletId)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
package com.tangem.domain.pushnotificationpreferences
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.PushNotificationCategory
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.repository.WalletPushNotificationPreferencesRepository
|
||||||
|
|
||||||
|
class UpdateWalletPushNotificationPreferenceUseCase(
|
||||||
|
private val repository: WalletPushNotificationPreferencesRepository,
|
||||||
|
) {
|
||||||
|
|
||||||
|
suspend operator fun invoke(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
category: PushNotificationCategory,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
): Either<Throwable, Unit> = repository.updatePreference(
|
||||||
|
userWalletId = userWalletId,
|
||||||
|
category = category,
|
||||||
|
isEnabled = isEnabled,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.tangem.domain.pushnotificationpreferences.models
|
||||||
|
|
||||||
|
enum class PushNotificationCategory {
|
||||||
|
TransactionAlerts,
|
||||||
|
OffersUpdates,
|
||||||
|
PriceAlerts,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package com.tangem.domain.pushnotificationpreferences.models
|
||||||
|
|
||||||
|
data class PushNotificationPreference(
|
||||||
|
val isEnabled: Boolean,
|
||||||
|
val isVisible: Boolean,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.tangem.domain.pushnotificationpreferences.models
|
||||||
|
|
||||||
|
data class WalletPushNotificationPreferences(
|
||||||
|
val transactionAlerts: PushNotificationPreference,
|
||||||
|
val offersUpdates: PushNotificationPreference,
|
||||||
|
val priceAlerts: PushNotificationPreference,
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.tangem.domain.pushnotificationpreferences.repository
|
||||||
|
|
||||||
|
import arrow.core.Either
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.PushNotificationCategory
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.models.WalletPushNotificationPreferences
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
/** Per-wallet push notification preferences. In-memory cache, not persisted. */
|
||||||
|
interface WalletPushNotificationPreferencesRepository {
|
||||||
|
|
||||||
|
/** Warms up the cache for [userWalletId]. No-op if already cached. */
|
||||||
|
suspend fun preload(userWalletId: UserWalletId)
|
||||||
|
|
||||||
|
fun observePreferences(userWalletId: UserWalletId): Flow<WalletPushNotificationPreferences>
|
||||||
|
|
||||||
|
/** Updates a single [category]; full-replace PUT under the hood. On failure cache is untouched. */
|
||||||
|
suspend fun updatePreference(
|
||||||
|
userWalletId: UserWalletId,
|
||||||
|
category: PushNotificationCategory,
|
||||||
|
isEnabled: Boolean,
|
||||||
|
): Either<Throwable, Unit>
|
||||||
|
}
|
||||||
|
|
@ -119,6 +119,7 @@ dependencies {
|
||||||
implementation(projects.domain.wallets)
|
implementation(projects.domain.wallets)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
implementation(projects.domain.notifications)
|
implementation(projects.domain.notifications)
|
||||||
|
implementation(projects.domain.pushNotificationPreferences)
|
||||||
implementation(projects.domain.transaction)
|
implementation(projects.domain.transaction)
|
||||||
implementation(projects.domain.yieldSupply)
|
implementation(projects.domain.yieldSupply)
|
||||||
implementation(projects.domain.yieldSupply.models)
|
implementation(projects.domain.yieldSupply.models)
|
||||||
|
|
@ -136,6 +137,7 @@ dependencies {
|
||||||
implementation(projects.features.onboardingV2.api)
|
implementation(projects.features.onboardingV2.api)
|
||||||
implementation(projects.features.onramp.api)
|
implementation(projects.features.onramp.api)
|
||||||
implementation(projects.features.pushNotifications.api)
|
implementation(projects.features.pushNotifications.api)
|
||||||
|
implementation(projects.features.pushNotificationSettings.api)
|
||||||
implementation(projects.features.swap.api)
|
implementation(projects.features.swap.api)
|
||||||
implementation(projects.features.tester.api)
|
implementation(projects.features.tester.api)
|
||||||
implementation(projects.features.tokendetails.api)
|
implementation(projects.features.tokendetails.api)
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWal
|
||||||
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
|
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
|
||||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletContentFetcher
|
import com.tangem.feature.wallet.presentation.wallet.domain.WalletContentFetcher
|
||||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
|
||||||
|
import com.tangem.domain.pushnotificationpreferences.PreloadWalletPushNotificationPreferencesUseCase
|
||||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
|
||||||
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
|
||||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||||
|
|
@ -61,6 +62,7 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejec
|
||||||
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
|
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
|
||||||
import com.tangem.features.biometry.AskBiometryComponent
|
import com.tangem.features.biometry.AskBiometryComponent
|
||||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||||
|
import com.tangem.features.pushnotificationsettings.PushNotificationSettingsFeatureToggles
|
||||||
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
|
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
|
||||||
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
|
||||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||||
|
|
@ -94,6 +96,7 @@ internal class WalletModel @Inject constructor(
|
||||||
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||||
private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender,
|
private val selectedWalletAnalyticsSender: SelectedWalletAnalyticsSender,
|
||||||
private val walletNameMigrationUseCase: WalletNameMigrationUseCase,
|
private val walletNameMigrationUseCase: WalletNameMigrationUseCase,
|
||||||
|
private val preloadWalletPushNotificationPreferencesUseCase: PreloadWalletPushNotificationPreferencesUseCase,
|
||||||
private val refreshMultiCurrencyWalletQuotesUseCase: RefreshMultiCurrencyWalletQuotesUseCase,
|
private val refreshMultiCurrencyWalletQuotesUseCase: RefreshMultiCurrencyWalletQuotesUseCase,
|
||||||
private val walletImageResolver: WalletImageResolver,
|
private val walletImageResolver: WalletImageResolver,
|
||||||
private val onrampStatusFactory: OnrampStatusFactory,
|
private val onrampStatusFactory: OnrampStatusFactory,
|
||||||
|
|
@ -122,6 +125,7 @@ internal class WalletModel @Inject constructor(
|
||||||
private val uiMessageSender: UiMessageSender,
|
private val uiMessageSender: UiMessageSender,
|
||||||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||||
private val walletFeatureToggles: WalletFeatureToggles,
|
private val walletFeatureToggles: WalletFeatureToggles,
|
||||||
|
private val pushNotificationSettingsFeatureToggles: PushNotificationSettingsFeatureToggles,
|
||||||
private val startAssetsDiscoveryUseCase: StartAssetsDiscoveryUseCase,
|
private val startAssetsDiscoveryUseCase: StartAssetsDiscoveryUseCase,
|
||||||
val screenLifecycleProvider: ScreenLifecycleProvider,
|
val screenLifecycleProvider: ScreenLifecycleProvider,
|
||||||
val innerWalletRouter: InnerWalletRouter,
|
val innerWalletRouter: InnerWalletRouter,
|
||||||
|
|
@ -146,6 +150,7 @@ internal class WalletModel @Inject constructor(
|
||||||
|
|
||||||
maybeMigrateNames()
|
maybeMigrateNames()
|
||||||
maybeSetWalletFirstTimeUsage()
|
maybeSetWalletFirstTimeUsage()
|
||||||
|
preloadPushNotificationPreferences()
|
||||||
updateYieldSupplyApy()
|
updateYieldSupplyApy()
|
||||||
subscribeToUserWalletsUpdates()
|
subscribeToUserWalletsUpdates()
|
||||||
subscribeOnBalanceHiding()
|
subscribeOnBalanceHiding()
|
||||||
|
|
@ -192,6 +197,19 @@ internal class WalletModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun preloadPushNotificationPreferences() {
|
||||||
|
if (!pushNotificationSettingsFeatureToggles.isPushNotificationSettingsEnabled) return
|
||||||
|
getWalletsUseCase()
|
||||||
|
.map { wallets -> wallets.map(UserWallet::walletId) }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
.onEach { walletIds ->
|
||||||
|
walletIds.forEach { walletId ->
|
||||||
|
modelScope.launch { preloadWalletPushNotificationPreferencesUseCase(walletId) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(modelScope)
|
||||||
|
}
|
||||||
|
|
||||||
private fun maybeSetWalletFirstTimeUsage() {
|
private fun maybeSetWalletFirstTimeUsage() {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
setWalletFirstTimeUsageUseCase()
|
setWalletFirstTimeUsageUseCase()
|
||||||
|
|
|
||||||
|
|
@ -357,6 +357,7 @@ include(":domain:app-theme")
|
||||||
include(":domain:app-theme:models")
|
include(":domain:app-theme:models")
|
||||||
include(":domain:balance-hiding")
|
include(":domain:balance-hiding")
|
||||||
include(":domain:balance-hiding:models")
|
include(":domain:balance-hiding:models")
|
||||||
|
include(":domain:push-notification-preferences")
|
||||||
include(":domain:transaction")
|
include(":domain:transaction")
|
||||||
include(":domain:transaction:models")
|
include(":domain:transaction:models")
|
||||||
include(":domain:analytics")
|
include(":domain:analytics")
|
||||||
|
|
@ -412,6 +413,7 @@ include(":data:account")
|
||||||
include(":data:app-currency")
|
include(":data:app-currency")
|
||||||
include(":data:app-theme")
|
include(":data:app-theme")
|
||||||
include(":data:balance-hiding")
|
include(":data:balance-hiding")
|
||||||
|
include(":data:push-notification-preferences")
|
||||||
include(":data:common")
|
include(":data:common")
|
||||||
include(":data:card")
|
include(":data:card")
|
||||||
include(":data:tokens")
|
include(":data:tokens")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue