Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-14 14:50:40 +04:00
parent aa03d39dc0
commit 49bd0090ff
18 changed files with 300 additions and 88 deletions

View file

@ -13,6 +13,10 @@ android {
namespace = "com.tangem.data.settings"
}
tasks.withType<Test>().configureEach {
useJUnitPlatform()
}
dependencies {
implementation(projects.core.datasource)
@ -28,6 +32,11 @@ dependencies {
kapt(deps.hilt.kapt)
// endregion
// region Test
testImplementation(projects.test.core)
testRuntimeOnly(deps.test.junit5.engine)
// endregion
// region Others dependencies
implementation(deps.jodatime)
implementation(deps.kotlin.coroutines)

View file

@ -0,0 +1,56 @@
package com.tangem.data.settings
import androidx.datastore.preferences.core.booleanPreferencesKey
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.utils.get
import com.tangem.domain.settings.HotWalletRestrictionManager
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
/**
* Development implementation of [HotWalletRestrictionManager].
*
* Reads and writes the restriction state from [AppPreferencesStore],
* allowing testers to toggle it via the Tester Menu.
* The preference [Flow] is converted to a [StateFlow] on construction,
* so [isCreationEnabledSync] can be called from non-suspending contexts.
* Defaults to `true` (restriction enabled) when no value is stored.
*/
internal class DevHotWalletRestrictionManager(
private val appPreferencesStore: AppPreferencesStore,
dispatchers: CoroutineDispatcherProvider,
) : HotWalletRestrictionManager {
private val isCreationEnabledState: StateFlow<Boolean> =
appPreferencesStore
.get(key = IS_HOT_WALLET_CREATION_RESTRICTION_ENABLED_KEY, default = true)
.stateIn(
scope = CoroutineScope(dispatchers.io + SupervisorJob()),
started = SharingStarted.Eagerly,
initialValue = true,
)
override fun isCreationEnabled(): StateFlow<Boolean> = isCreationEnabledState
override fun isCreationEnabledSync(): Boolean = isCreationEnabledState.value
override suspend fun toggleCreationEnabled() {
appPreferencesStore.editData { preferences ->
val isEnabled = preferences.getOrDefault(
key = IS_HOT_WALLET_CREATION_RESTRICTION_ENABLED_KEY,
default = true,
)
preferences[IS_HOT_WALLET_CREATION_RESTRICTION_ENABLED_KEY] = !isEnabled
}
}
private companion object {
val IS_HOT_WALLET_CREATION_RESTRICTION_ENABLED_KEY =
booleanPreferencesKey(name = "isHotWalletCreationRestrictionEnabled")
}
}

View file

@ -0,0 +1,21 @@
package com.tangem.data.settings
import com.tangem.domain.settings.HotWalletRestrictionManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
/**
* Production implementation of [HotWalletRestrictionManager].
*
* Hot wallet creation restriction is always enabled in production builds
* users must scan a physical Tangem card to add a wallet.
* [toggleCreationEnabled] is a no-op since the restriction cannot be changed at runtime.
*/
internal class ProdHotWalletRestrictionManager : HotWalletRestrictionManager {
private val state: StateFlow<Boolean> = MutableStateFlow(true)
override fun isCreationEnabled(): StateFlow<Boolean> = state
override fun isCreationEnabledSync(): Boolean = true
override suspend fun toggleCreationEnabled() = Unit
}

View file

@ -1,12 +1,16 @@
package com.tangem.data.settings.di
import android.content.Context
import com.tangem.data.settings.BuildConfig
import com.tangem.data.settings.DefaultAppRatingRepository
import com.tangem.data.settings.DefaultPermissionRepository
import com.tangem.data.settings.DefaultSettingsRepository
import com.tangem.data.settings.DevHotWalletRestrictionManager
import com.tangem.data.settings.ProdHotWalletRestrictionManager
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.domain.settings.HotWalletRestrictionManager
import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.PermissionRepository
import com.tangem.domain.settings.repositories.SettingsRepository
@ -55,4 +59,17 @@ internal object SettingsDataModule {
context = context,
)
}
@Provides
@Singleton
fun provideHotWalletRestrictionManager(
appPreferencesStore: AppPreferencesStore,
dispatchers: CoroutineDispatcherProvider,
): HotWalletRestrictionManager {
return if (BuildConfig.TESTER_MENU_ENABLED) {
DevHotWalletRestrictionManager(appPreferencesStore, dispatchers)
} else {
ProdHotWalletRestrictionManager()
}
}
}

View file

@ -0,0 +1,99 @@
package com.tangem.data.settings
import androidx.datastore.preferences.core.Preferences
import com.google.common.truth.Truth.assertThat
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.utils.get
import com.tangem.test.core.getEmittedValues
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkStatic
import io.mockk.unmockkStatic
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class DevHotWalletRestrictionManagerTest {
private val appPreferencesStore = mockk<AppPreferencesStore>(relaxed = true)
private val dispatchers: CoroutineDispatcherProvider = TestingCoroutineDispatcherProvider()
@BeforeEach
fun setup() {
mockkStatic("com.tangem.datasource.local.preferences.utils.PreferencesDataStoreExtKt")
}
@AfterEach
fun tearDown() {
unmockkStatic("com.tangem.datasource.local.preferences.utils.PreferencesDataStoreExtKt")
}
@Test
fun `GIVEN preference is true WHEN isCreationEnabled THEN emits true`() = runTest {
every {
appPreferencesStore.get(key = any<Preferences.Key<Boolean>>(), default = true)
} returns flowOf(true)
val manager = DevHotWalletRestrictionManager(appPreferencesStore, dispatchers)
val emitted = getEmittedValues(manager.isCreationEnabled())
assertThat(emitted).containsExactly(true)
}
@Test
fun `GIVEN preference is false WHEN isCreationEnabled THEN emits false`() = runTest {
every {
appPreferencesStore.get(key = any<Preferences.Key<Boolean>>(), default = true)
} returns flowOf(false)
val manager = DevHotWalletRestrictionManager(appPreferencesStore, dispatchers)
val emitted = getEmittedValues(manager.isCreationEnabled())
assertThat(emitted).containsExactly(false)
}
@Test
fun `GIVEN preference Flow emits true WHEN isCreationEnabledSync THEN returns cached true`() = runTest {
every {
appPreferencesStore.get(key = any<Preferences.Key<Boolean>>(), default = true)
} returns flowOf(true)
val manager = DevHotWalletRestrictionManager(appPreferencesStore, dispatchers)
assertThat(manager.isCreationEnabledSync()).isTrue()
}
@Test
fun `GIVEN preference Flow emits false WHEN isCreationEnabledSync THEN returns cached false`() = runTest {
every {
appPreferencesStore.get(key = any<Preferences.Key<Boolean>>(), default = true)
} returns flowOf(false)
val manager = DevHotWalletRestrictionManager(appPreferencesStore, dispatchers)
assertThat(manager.isCreationEnabledSync()).isFalse()
}
@Test
fun `WHEN toggleCreationEnabled THEN opens editData transaction`() = runTest {
every {
appPreferencesStore.get(key = any<Preferences.Key<Boolean>>(), default = true)
} returns flowOf(true)
coEvery { appPreferencesStore.editData(any()) } returns mockk(relaxed = true)
val manager = DevHotWalletRestrictionManager(appPreferencesStore, dispatchers)
manager.toggleCreationEnabled()
coVerify { appPreferencesStore.editData(any()) }
}
}

View file

@ -0,0 +1,31 @@
package com.tangem.data.settings
import com.google.common.truth.Truth.assertThat
import com.tangem.test.core.getEmittedValues
import kotlinx.coroutines.test.runTest
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class ProdHotWalletRestrictionManagerTest {
private val manager = ProdHotWalletRestrictionManager()
@Test
fun `WHEN isCreationEnabled THEN always emits true`() = runTest {
val emitted = getEmittedValues(manager.isCreationEnabled())
assertThat(emitted).containsExactly(true)
}
@Test
fun `WHEN isCreationEnabledSync THEN returns true`() = runTest {
assertThat(manager.isCreationEnabledSync()).isTrue()
}
@Test
fun `WHEN toggleCreationEnabled THEN isCreationEnabledSync still returns true`() = runTest {
manager.toggleCreationEnabled()
assertThat(manager.isCreationEnabledSync()).isTrue()
}
}