Updated on 2026-08-14

This commit is contained in:
Tangem 2024-06-10 18:41:26 +01:00
commit 3fba2ae059
872 changed files with 17188 additions and 8108 deletions

View file

@ -11,20 +11,36 @@
"name": "REDESIGNED_SEND_SCREEN_ENABLED",
"version": "5.10.0"
},
{
"name": "GENERAL_USER_WALLETS_LIST_MANAGER_ENABLED",
"version": "5.8.0"
},
{
"name": "LOCAL_USER_LOGS_ENABLED",
"version": "5.12.0"
"version": "5.13.0"
},
{
"name": "GENERATE_XPUB_ENABLED",
"version": "undefined"
"version": "5.12.0"
},
{
"name": "WC_SOLANA_TX_SIGN_ENABLED",
"version": "undefined"
},
{
"name": "TOKEN_LIST_LCE_ENABLED",
"version": "5.12.0"
},
{
"name": "CARDANO_TOKENS_SUPPORT_ENABLED",
"version": "5.12.0"
},
{
"name": "STAKING_ENABLED",
"version": "undefined"
},
{
"name": "FULL_RESET_ENABLED",
"version": "5.12.0"
},
{
"name": "DETAILS_REDESIGN_ENABLED",
"version": "undefined"
}
]

View file

@ -1,16 +1,13 @@
package com.tangem.core.featuretoggle.di
import android.content.Context
import com.squareup.moshi.Moshi
import com.squareup.moshi.adapter
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import com.tangem.core.featuretoggle.manager.DevFeatureTogglesManager
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.core.featuretoggle.manager.ProdFeatureTogglesManager
import com.tangem.core.featuretoggle.storage.LocalFeatureTogglesStorage
import com.tangem.core.featuretoggle.version.DefaultVersionProvider
import com.tangem.core.featuretoggles.BuildConfig
import com.tangem.datasource.asset.AssetReader
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.local.preferences.AppPreferencesStore
import dagger.Module
import dagger.Provides
@ -25,17 +22,12 @@ internal object FeatureTogglesManagerModule {
@Provides
@Singleton
@OptIn(ExperimentalStdlibApi::class)
fun provideFeatureTogglesManager(
@ApplicationContext context: Context,
assetReader: AssetReader,
assetLoader: AssetLoader,
appPreferencesStore: AppPreferencesStore,
): FeatureTogglesManager {
val moshi = Moshi.Builder().addLast(KotlinJsonAdapterFactory()).build()
val localFeatureTogglesStorage = LocalFeatureTogglesStorage(
assetReader = assetReader,
jsonAdapter = moshi.adapter(),
)
val localFeatureTogglesStorage = LocalFeatureTogglesStorage(assetLoader)
val versionProvider = DefaultVersionProvider(context)
return if (BuildConfig.TESTER_MENU_ENABLED) {

View file

@ -1,41 +1,28 @@
package com.tangem.core.featuretoggle.storage
import androidx.annotation.VisibleForTesting
import com.squareup.moshi.JsonAdapter
import com.tangem.core.featuretoggle.storage.LocalFeatureTogglesStorage.Companion.LOCAL_CONFIG_PATH
import com.tangem.datasource.asset.AssetReader
import timber.log.Timber
import com.tangem.datasource.asset.loader.AssetLoader
import kotlin.properties.Delegates
/**
* Storage implementation for storing local feature toggles.
* Feature toggles are declared in file [LOCAL_CONFIG_PATH].
*
* @property assetReader asset reader
* @property jsonAdapter adapter for parsing local json config
* @property assetLoader asset loader
*
[REDACTED_AUTHOR]
*/
internal class LocalFeatureTogglesStorage(
private val assetReader: AssetReader,
private val jsonAdapter: JsonAdapter<List<FeatureToggle>>,
private val assetLoader: AssetLoader,
) : FeatureTogglesStorage {
override var featureToggles: List<FeatureToggle> by Delegates.notNull()
private set
override suspend fun init() {
runCatching { requireNotNull(jsonAdapter.fromJson(assetReader.readJson(LOCAL_CONFIG_PATH))) }
.onSuccess { featureToggles = it }
.onFailure { Timber.e(LocalFeatureTogglesStorage::class.java.name, "Failed to parse $LOCAL_CONFIG_PATH") }
featureToggles = assetLoader.loadList<FeatureToggle>(LOCAL_CONFIG_PATH)
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun getConfigPath() = LOCAL_CONFIG_PATH
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun getFeatureToggles(): Iterable<FeatureToggle> = featureToggles
private companion object {
const val LOCAL_CONFIG_PATH: String = "configs/feature_toggles_config"
}

View file

@ -3,12 +3,11 @@ package com.tangem.core.featuretoggle.storage
import android.annotation.SuppressLint
import com.google.common.truth.Truth
import com.squareup.moshi.JsonAdapter
import com.tangem.datasource.asset.AssetReader
import io.mockk.coEvery
import io.mockk.mockk
import io.mockk.verifyAll
import io.mockk.verifyOrder
import kotlinx.coroutines.ExperimentalCoroutinesApi
import com.squareup.moshi.Moshi
import com.squareup.moshi.Types
import com.tangem.datasource.asset.loader.AssetLoader
import com.tangem.datasource.asset.reader.AssetReader
import io.mockk.*
import kotlinx.coroutines.test.runTest
import org.junit.Test
import java.io.IOException
@ -16,75 +15,73 @@ import java.io.IOException
/**
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalCoroutinesApi::class)
@SuppressLint("CheckResult")
internal class LocalFeatureTogglesStorageTest {
private val assetReader = mockk<AssetReader>()
private val moshi = mockk<Moshi>()
private val jsonAdapter = mockk<JsonAdapter<List<FeatureToggle>>>()
private val storage = LocalFeatureTogglesStorage(assetReader, jsonAdapter)
// Impossible to mockk AssetLoader because it implement inline functions
private val assetLoader = AssetLoader(assetReader = assetReader, moshi = moshi)
private val storage = LocalFeatureTogglesStorage(assetLoader)
@Test
fun `successfully initialize storage`() = runTest {
coEvery { assetReader.readJson(storage.getConfigPath()) } returns json
coEvery { jsonAdapter.fromJson(json) } returns featureToggles
everyReadingJson() returns json
everyCreatingMoshiAdapter() returns jsonAdapter
everyMappingJson() returns featureToggles
storage.init()
verifyOrder {
assetReader.readJson(storage.getConfigPath())
coVerifyOrder {
assetReader.read(CONFIG_FILE_NAME)
jsonAdapter.fromJson(json)
}
Truth.assertThat(storage.getFeatureToggles()).containsExactlyElementsIn(featureToggles)
Truth.assertThat(storage.featureToggles).containsExactlyElementsIn(featureToggles)
}
@Test
fun `failure initialize storage if assetReader throws exception`() = runTest {
coEvery { assetReader.readJson(storage.getConfigPath()) } returns json
coEvery { jsonAdapter.fromJson(json) } throws IOException()
everyReadingJson() returns json
everyCreatingMoshiAdapter() returns jsonAdapter
everyMappingJson() throws IOException()
storage.init()
verifyOrder {
assetReader.readJson(storage.getConfigPath())
coVerifyOrder {
assetReader.read(CONFIG_FILE_NAME)
jsonAdapter.fromJson(json)
}
runCatching { storage.getFeatureToggles() }
.onSuccess { throw IllegalStateException("featureToggles shouldn't be initialized") }
.onFailure {
Truth
.assertThat(it)
.hasMessageThat()
.contains("Property featureToggles should be initialized before get.")
Truth.assertThat(it).isInstanceOf(IllegalStateException::class.java)
}
Truth.assertThat(storage.featureToggles).containsExactlyElementsIn(emptyList<FeatureToggle>())
}
@Test
fun `failure initialize storage if jsonAdapter throws exception`() = runTest {
coEvery { assetReader.readJson(storage.getConfigPath()) } throws IOException()
everyReadingJson() throws IOException()
storage.init()
verifyOrder { assetReader.readJson(storage.getConfigPath()) }
coVerifyOrder { assetReader.read(CONFIG_FILE_NAME) }
verifyAll(inverse = true) { jsonAdapter.fromJson(any<String>()) }
runCatching { storage.getFeatureToggles() }
.onSuccess { throw IllegalStateException("featureToggles shouldn't be initialized") }
.onFailure {
Truth
.assertThat(it)
.hasMessageThat()
.contains("Property featureToggles should be initialized before get.")
Truth.assertThat(it).isInstanceOf(IllegalStateException::class.java)
}
Truth.assertThat(storage.featureToggles).containsExactlyElementsIn(emptyList<FeatureToggle>())
}
private fun everyReadingJson() = coEvery { assetReader.read(CONFIG_FILE_NAME) }
private fun everyCreatingMoshiAdapter() = every {
val types = Types.newParameterizedType(List::class.java, FeatureToggle::class.java)
moshi.adapter<List<FeatureToggle>>(types)
}
private fun everyMappingJson() = every { jsonAdapter.fromJson(json) }
private companion object {
const val CONFIG_FILE_NAME = "configs/feature_toggles_config.json"
val json = """
[