Updated on 2026-08-14

This commit is contained in:
Tangem 2024-09-23 10:02:15 +03:00
commit b511924b86
40 changed files with 462 additions and 170 deletions

View file

@ -718,6 +718,16 @@
"networkId": "sei-network/test"
}
]
},
{
"id": "energy-web-chain",
"name": "Energy Web Chain",
"symbol": "EWT",
"networks": [
{
"networkId": "energy-web-chain/test"
}
]
}
]
}

View file

@ -24,6 +24,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
@ -35,6 +36,7 @@ import com.tangem.features.pushnotifications.api.featuretoggles.PushNotification
import com.tangem.tap.common.log.TangemAppLoggerInitializer
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.proxy.AppStateHolder
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
@ -119,4 +121,8 @@ interface ApplicationEntryPoint {
fun getPushNotificationsFeatureToggles(): PushNotificationsFeatureToggles
fun getTangemAppLogger(): TangemAppLoggerInitializer
fun getHomeFeatureToggles(): HomeFeatureToggles
fun getGetUserCountryCodeUseCase(): GetUserCountryUseCase
}

View file

@ -37,6 +37,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
@ -60,6 +61,7 @@ import com.tangem.tap.common.redux.global.GlobalAction
import com.tangem.tap.domain.tasks.product.DerivationsFinder
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.proxy.redux.DaggerGraphState
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
@ -191,6 +193,12 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
private val tangemAppLoggerInitializer: TangemAppLoggerInitializer
get() = entryPoint.getTangemAppLogger()
private val homeFeatureToggles: HomeFeatureToggles
get() = entryPoint.getHomeFeatureToggles()
private val getUserCountryUseCase: GetUserCountryUseCase
get() = entryPoint.getGetUserCountryCodeUseCase()
// endregion
override fun onCreate() {
@ -273,6 +281,8 @@ abstract class TangemApplication : Application(), ImageLoaderFactory {
shareManager = shareManager,
appRouter = appRouter,
pushNotificationsFeatureToggles = pushNotificationsFeatureToggles,
homeFeatureToggles = homeFeatureToggles,
getUserCountryUseCase = getUserCountryUseCase,
),
),
)

View file

@ -2,12 +2,9 @@ package com.tangem.tap.common.redux.global
import com.tangem.domain.redux.domainStore
import com.tangem.domain.redux.global.DomainGlobalAction
import com.tangem.tap.common.extensions.dispatchOnMain
import com.tangem.tap.common.redux.AppState
import com.tangem.tap.features.home.redux.HomeAction
import com.tangem.tap.features.onboarding.OnboardingManager
import com.tangem.tap.proxy.AppStateHolder
import com.tangem.tap.store
import com.tangem.utils.extensions.replaceBy
import org.rekotlin.Action
@ -86,10 +83,7 @@ fun globalReducer(action: Action, state: AppState, appStateHolder: AppStateHolde
is GlobalAction.SetIfCardVerifiedOnline ->
globalState.copy(cardVerifiedOnline = action.verified)
is GlobalAction.FetchUserCountry.Success -> {
store.dispatchOnMain(HomeAction.UpdateCountryCode(action.countryCode))
globalState.copy(
userCountryCode = action.countryCode,
)
globalState.copy(userCountryCode = action.countryCode)
}
else -> globalState
}

View file

@ -10,6 +10,8 @@ import com.tangem.domain.settings.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.PermissionRepository
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.tap.domain.sdk.TangemSdkManager
import com.tangem.tap.domain.settings.DefaultLegacySettingsRepository
import dagger.Module
@ -212,4 +214,16 @@ internal object SettingsDomainModule {
return ShouldSaveAccessCodesUseCase(settingsRepository = settingsRepository)
}
// endregion
@Provides
@Singleton
fun provideFetchUserCountryCodeUseCase(settingsRepository: SettingsRepository): FetchUserCountryUseCase {
return FetchUserCountryUseCase(settingsRepository)
}
@Provides
@Singleton
fun provideGetUserCountryCodeUseCase(settingsRepository: SettingsRepository): GetUserCountryUseCase {
return GetUserCountryUseCase(settingsRepository)
}
}

View file

@ -239,9 +239,8 @@ class WalletConnectSdkHelper {
private suspend fun signTransaction(data: WcTransactionData, cardId: String?): String? {
val dataToSign = EthereumUtils.buildTransactionToSign(
transactionData = data.transaction,
nonce = null,
blockchain = data.walletManager.wallet.blockchain,
) ?: return null
)
val command = SignHashCommand(
hash = dataToSign.hash,

View file

@ -58,7 +58,9 @@ internal fun StoriesScreen(
}
StoriesScreenContent(
modifier = Modifier.fillMaxSize().testTag(TestTags.STORIES_SCREEN),
modifier = Modifier
.fillMaxSize()
.testTag(TestTags.STORIES_SCREEN),
config = StoriesScreenContentConfig(
storiesSize = state.stories.lastIndex,
currentStoryIndex = currentStoryIndex,
@ -157,7 +159,7 @@ private fun StoriesScreenContent(config: StoriesScreenContentConfig, modifier: M
duration = currentStoryDuration,
)
Stories.RevolutionaryWallet -> StoriesRevolutionaryWallet()
is Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
Stories.UltraSecureBackup -> StoriesUltraSecureBackup(
isPaused = isPaused,
stepDuration = currentStoryDuration,
)

View file

@ -0,0 +1,12 @@
package com.tangem.tap.features.home.featuretoggles
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import javax.inject.Inject
class HomeFeatureToggles @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
) {
val isMigrateUserCountryCodeEnabled: Boolean
get() = featureTogglesManager.isFeatureEnabled(name = "MIGRATE_USER_COUNTRY_CODE_ENABLED")
}

View file

@ -15,6 +15,4 @@ sealed class HomeAction : Action {
data class ReadCard(val scope: CoroutineScope) : HomeAction()
data class ScanInProgress(val scanInProgress: Boolean) : HomeAction()
data class UpdateCountryCode(val userCountryCode: String) : HomeAction()
}

View file

@ -10,16 +10,10 @@ object HomeReducer {
private fun internalReduce(action: Action, appState: AppState): HomeState {
if (action !is HomeAction) return appState.homeState
var state = appState.homeState
when (action) {
return when (action) {
is HomeAction.ScanInProgress -> {
state = state.copy(scanInProgress = action.scanInProgress)
appState.homeState.copy(scanInProgress = action.scanInProgress)
}
is HomeAction.UpdateCountryCode -> {
state.onCountryCodeUpdate(state, action.userCountryCode)
}
else -> {}
else -> appState.homeState
}
return state
}

View file

@ -1,56 +1,24 @@
package com.tangem.tap.features.home.redux
import androidx.compose.runtime.Immutable
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import org.rekotlin.StateType
import java.util.Locale
@Immutable
data class HomeState(
val scanInProgress: Boolean = false,
val stories: List<Stories> = initDefaultStories(),
val stories: ImmutableList<Stories> = Stories.entries.toImmutableList(),
) : StateType {
val firstStory: Stories
get() = stories[0]
val firstStory: Stories get() = stories[0]
fun stepOf(story: Stories): Int = stories.indexOf(story)
fun onCountryCodeUpdate(homeState: HomeState, countryCode: String) {
val isNewWalletAvailable = !(countryCode == RUSSIA_COUNTRY_CODE || countryCode == BELARUS_COUNTRY_CODE)
homeState.stories.forEach {
it.isNewWalletAvailable.value = isNewWalletAvailable
}
}
companion object {
private const val RUSSIA_COUNTRY_CODE = "ru"
private const val BELARUS_COUNTRY_CODE = "by"
fun initDefaultStories(): List<Stories> = listOf(
Stories.TangemIntro,
Stories.RevolutionaryWallet,
Stories.UltraSecureBackup,
Stories.Currencies,
Stories.Web3,
Stories.WalletForEveryone,
)
fun isNewWalletAvailableInit(): Boolean {
val locale = Locale.getDefault().language
return !(locale == RUSSIA_COUNTRY_CODE || locale == BELARUS_COUNTRY_CODE)
}
}
}
sealed class Stories(
val duration: Int,
val isNewWalletAvailable: MutableState<Boolean> = mutableStateOf(HomeState.isNewWalletAvailableInit()),
) {
object TangemIntro : Stories(duration = 6000)
object RevolutionaryWallet : Stories(duration = 6000)
object UltraSecureBackup : Stories(duration = 6000)
object Currencies : Stories(duration = 6000)
object Web3 : Stories(duration = 6000)
object WalletForEveryone : Stories(duration = 6000)
enum class Stories(val duration: Int = 6000) {
TangemIntro,
RevolutionaryWallet,
UltraSecureBackup,
Currencies,
Web3,
WalletForEveryone,
}

View file

@ -22,6 +22,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.onboarding.SaveTwinsOnboardingShownUseCase
import com.tangem.domain.onboarding.WasTwinsOnboardingShownUseCase
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.tokens.repository.NetworksRepository
import com.tangem.domain.walletmanager.WalletManagersFacade
@ -41,6 +42,7 @@ import com.tangem.tap.domain.walletconnect2.domain.LegacyWalletConnectRepository
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectInteractor
import com.tangem.tap.domain.walletconnect2.domain.WalletConnectSessionsRepository
import com.tangem.tap.features.customtoken.api.featuretoggles.CustomTokenFeatureToggles
import com.tangem.tap.features.home.featuretoggles.HomeFeatureToggles
import com.tangem.tap.proxy.AppStateHolder
import org.rekotlin.StateType
@ -87,4 +89,6 @@ data class DaggerGraphState(
val appRouter: AppRouter? = null,
val pushNotificationsFeatureToggles: PushNotificationsFeatureToggles? = null,
val pushNotificationsRouter: PushNotificationsRouter? = null,
val homeFeatureToggles: HomeFeatureToggles? = null,
val getUserCountryUseCase: GetUserCountryUseCase? = null,
) : StateType

View file

@ -1,7 +1,9 @@
package com.tangem.datasource.api.tangemTech.models
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class GeoResponse(
@Json(name = "code") val code: String,
)

View file

@ -7,10 +7,6 @@
"name": "LOCAL_USER_LOGS_ENABLED",
"version": "5.14.0"
},
{
"name": "GENERATE_XPUB_ENABLED",
"version": "5.12.0"
},
{
"name": "WC_SOLANA_TX_SIGN_ENABLED",
"version": "undefined"
@ -38,5 +34,9 @@
{
"name": "IS_ETHEREUM_EIP_1559_ENABLED",
"version": "5.16.0"
},
{
"name": "MIGRATE_USER_COUNTRY_CODE_ENABLED",
"version": "5.16.0"
}
]

View file

@ -24,6 +24,7 @@ internal class DevFeatureTogglesManager(
) : MutableFeatureTogglesManager {
private var featureTogglesMap: MutableMap<String, Boolean> by Delegates.notNull()
private var localFeatureTogglesMap: Map<String, Boolean> by Delegates.notNull()
override suspend fun init() {
localFeatureTogglesStorage.init()
@ -32,8 +33,12 @@ internal class DevFeatureTogglesManager(
key = PreferencesKeys.FEATURE_TOGGLES_KEY,
) ?: emptyMap()
featureTogglesMap = localFeatureTogglesStorage.featureToggles
val localFeatureToggles = localFeatureTogglesStorage.featureToggles
.associateToggles(currentVersion = versionProvider.get().orEmpty())
localFeatureTogglesMap = localFeatureToggles
featureTogglesMap = localFeatureToggles
.mapValues { resultToggle ->
savedFeatureToggles[resultToggle.key] ?: resultToggle.value
}
@ -42,16 +47,27 @@ internal class DevFeatureTogglesManager(
override fun isFeatureEnabled(name: String): Boolean = featureTogglesMap[name] ?: false
override fun isMatchLocalConfig(): Boolean = featureTogglesMap == localFeatureTogglesMap
override fun getFeatureToggles(): Map<String, Boolean> = featureTogglesMap
override suspend fun changeToggle(name: String, isEnabled: Boolean) {
featureTogglesMap[name] ?: return
featureTogglesMap[name] = isEnabled
appPreferencesStore.storeObject(PreferencesKeys.FEATURE_TOGGLES_KEY, featureTogglesMap)
appPreferencesStore.storeFeatureToggles(value = featureTogglesMap)
}
override suspend fun recoverLocalConfig() {
featureTogglesMap = localFeatureTogglesMap.toMutableMap()
appPreferencesStore.storeFeatureToggles(value = localFeatureTogglesMap)
}
@VisibleForTesting(otherwise = VisibleForTesting.NONE)
fun setFeatureToggles(map: MutableMap<String, Boolean>) {
featureTogglesMap = map
}
private suspend fun AppPreferencesStore.storeFeatureToggles(value: Map<String, Boolean>) {
storeObject(PreferencesKeys.FEATURE_TOGGLES_KEY, value)
}
}

View file

@ -7,9 +7,15 @@ package com.tangem.core.featuretoggle.manager
*/
interface MutableFeatureTogglesManager : FeatureTogglesManager {
/** Check if the current state of the feature toggles matches the local config state. */
fun isMatchLocalConfig(): Boolean
/** Get feature toggles */
fun getFeatureToggles(): Map<String, Boolean>
/** Change availability [isEnabled] of toggle with name [name] */
suspend fun changeToggle(name: String, isEnabled: Boolean)
/** Recover local config state */
suspend fun recoverLocalConfig()
}

View file

@ -75,6 +75,7 @@ fun getActiveIconRes(blockchainId: String): Int {
"cyber", "cyber/test" -> R.drawable.img_cyber_22
"sei", "sei/test" -> R.drawable.img_sei_22
"internet-computer" -> R.drawable.img_icp_22
"energy-web-chain", "energy-web-chain/test" -> R.drawable.img_energy_web_22
else -> R.drawable.ic_alert_24
}
}
@ -151,6 +152,7 @@ fun getActiveIconResByNetworkId(networkId: String): Int {
"cyber", "cyber/test" -> R.drawable.img_cyber_22
"sei", "sei/test" -> R.drawable.img_sei_22
"internet-computer" -> R.drawable.img_icp_22
"energy-web-chain", "energy-web-chain/test" -> R.drawable.img_energy_web_22
else -> R.drawable.ic_alert_24
}
}
@ -224,6 +226,7 @@ fun getActiveIconResByCoinId(coinId: String): Int {
"cyber", "cyber/test" -> R.drawable.img_cyber_22
"sei", "sei/test" -> R.drawable.img_sei_22
"internet-computer" -> R.drawable.img_icp_22
"energy-web-chain", "energy-web-chain/test" -> R.drawable.img_energy_web_22
else -> R.drawable.ic_alert_24
}
}
@ -300,6 +303,7 @@ fun getGreyedOutIconRes(blockchainId: String): Int {
"cyber", "cyber/test" -> R.drawable.ic_cyber_22
"sei", "sei/test" -> R.drawable.ic_sei_22
"internet-computer" -> R.drawable.ic_icp_22
"energy-web-chain", "energy-web-chain/test" -> R.drawable.ic_energy_web_22
else -> R.drawable.ic_alert_24
}
}
@ -376,6 +380,7 @@ fun getGreyedOutIconResByNetworkId(networkId: String): Int {
"cyber", "cyber/test" -> R.drawable.ic_cyber_22
"sei", "sei/test" -> R.drawable.ic_sei_22
"internet-computer" -> R.drawable.ic_icp_22
"energy-web-chain", "energy-web-chain/test" -> R.drawable.ic_energy_web_22
else -> R.drawable.ic_alert_24
}
}

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<path
android:pathData="M10.426,16.82C9.756,17.299 8.955,17.56 8.131,17.568C6.707,17.566 5.405,16.793 4.418,15.369C3.531,14.007 3.04,12.425 3,10.801C3,10.77 3.01,10.74 3.028,10.715C3.046,10.691 3.071,10.672 3.1,10.662L4.071,10.229C4.089,10.22 4.108,10.215 4.128,10.215C4.149,10.214 4.169,10.218 4.187,10.225C4.206,10.233 4.223,10.244 4.237,10.258C4.251,10.273 4.262,10.29 4.269,10.308C4.269,10.328 4.288,10.347 4.288,10.367C4.286,11.982 4.718,13.51 5.489,14.639C6.217,15.708 7.167,16.283 8.156,16.284C8.691,16.286 9.214,16.128 9.659,15.831C9.691,15.814 9.729,15.809 9.764,15.817C9.8,15.824 9.833,15.844 9.856,15.872C10.053,16.129 10.251,16.366 10.467,16.603C10.48,16.639 10.483,16.679 10.476,16.717C10.469,16.755 10.452,16.791 10.426,16.82ZM17.567,15.389C16.596,16.791 15.269,17.579 13.847,17.577C12.425,17.575 11.12,16.783 10.135,15.378C9.208,14.033 8.697,12.252 8.7,10.369C8.702,8.631 9.713,7.267 10.978,7.269C12.243,7.271 13.251,8.638 13.248,10.376C13.268,11.891 12.905,13.386 12.193,14.724C12.184,14.741 12.172,14.756 12.157,14.769C12.143,14.781 12.125,14.791 12.107,14.796C12.088,14.802 12.069,14.804 12.05,14.801C12.031,14.799 12.012,14.793 11.995,14.783C11.976,14.783 11.976,14.762 11.955,14.743C11.837,14.604 11.718,14.466 11.62,14.327C11.501,14.156 11.394,13.978 11.297,13.793C11.288,13.772 11.283,13.748 11.283,13.724C11.283,13.7 11.288,13.677 11.297,13.655C11.748,12.613 11.979,11.489 11.976,10.354C11.978,9.505 11.543,8.535 10.969,8.534C10.396,8.533 9.96,9.502 9.958,10.351C9.956,11.966 10.388,13.495 11.159,14.623C11.889,15.691 12.837,16.265 13.825,16.267C14.814,16.268 15.765,15.677 16.497,14.632C17.27,13.502 17.708,11.985 17.711,10.363C17.713,8.742 17.282,7.228 16.51,6.096C16.29,5.763 16.024,5.463 15.72,5.205C15.694,5.175 15.678,5.139 15.674,5.1C15.671,5.061 15.68,5.021 15.701,4.988C15.72,4.967 15.741,4.948 15.761,4.948L16.731,4.515C16.758,4.5 16.789,4.496 16.819,4.503C16.849,4.511 16.875,4.529 16.892,4.554C17.139,4.804 17.364,5.075 17.565,5.363C18.492,6.708 19.003,8.488 19,10.372C18.997,12.255 18.498,14.047 17.567,15.389Z"
android:fillColor="#000000"/>
</vector>

View file

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="22dp"
android:height="22dp"
android:viewportWidth="22"
android:viewportHeight="22">
<group>
<clip-path
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"/>
<path
android:pathData="M11,0L11,0A11,11 0,0 1,22 11L22,11A11,11 0,0 1,11 22L11,22A11,11 0,0 1,0 11L0,11A11,11 0,0 1,11 0z"
android:fillColor="#ffffff"/>
<path
android:pathData="M0,0h22v22h-22z"
android:fillColor="#A466FF"/>
<path
android:pathData="M10.426,16.82C9.756,17.299 8.955,17.56 8.131,17.568C6.707,17.566 5.405,16.793 4.418,15.369C3.531,14.007 3.04,12.425 3,10.801C3,10.77 3.01,10.74 3.028,10.715C3.046,10.691 3.071,10.672 3.1,10.662L4.071,10.229C4.089,10.22 4.108,10.215 4.128,10.215C4.149,10.214 4.169,10.218 4.187,10.225C4.206,10.233 4.223,10.244 4.237,10.258C4.251,10.273 4.262,10.29 4.269,10.308C4.269,10.328 4.288,10.347 4.288,10.367C4.286,11.982 4.718,13.51 5.489,14.639C6.217,15.708 7.167,16.283 8.156,16.284C8.691,16.286 9.214,16.128 9.659,15.831C9.691,15.814 9.729,15.809 9.764,15.817C9.8,15.824 9.833,15.844 9.856,15.872C10.053,16.129 10.251,16.366 10.467,16.603C10.48,16.639 10.483,16.679 10.476,16.717C10.469,16.755 10.452,16.791 10.426,16.82ZM17.567,15.389C16.596,16.791 15.269,17.579 13.847,17.577C12.425,17.575 11.12,16.783 10.135,15.378C9.208,14.033 8.697,12.252 8.7,10.369C8.702,8.631 9.713,7.267 10.978,7.269C12.243,7.271 13.251,8.638 13.248,10.376C13.268,11.891 12.905,13.386 12.193,14.724C12.184,14.741 12.172,14.756 12.157,14.769C12.143,14.781 12.125,14.791 12.107,14.796C12.088,14.802 12.069,14.804 12.05,14.801C12.031,14.799 12.012,14.793 11.995,14.783C11.976,14.783 11.976,14.762 11.955,14.743C11.837,14.604 11.718,14.466 11.62,14.327C11.501,14.156 11.394,13.978 11.297,13.793C11.288,13.772 11.283,13.748 11.283,13.724C11.283,13.7 11.288,13.677 11.297,13.655C11.748,12.613 11.979,11.489 11.976,10.354C11.978,9.505 11.543,8.535 10.969,8.534C10.396,8.533 9.96,9.502 9.958,10.351C9.956,11.966 10.388,13.495 11.159,14.623C11.889,15.691 12.837,16.265 13.825,16.267C14.814,16.268 15.765,15.677 16.497,14.632C17.27,13.502 17.708,11.985 17.711,10.363C17.713,8.742 17.282,7.228 16.51,6.096C16.29,5.763 16.024,5.463 15.72,5.205C15.694,5.175 15.678,5.139 15.674,5.1C15.671,5.061 15.68,5.021 15.701,4.988C15.72,4.967 15.741,4.948 15.761,4.948L16.731,4.515C16.758,4.5 16.789,4.496 16.819,4.503C16.849,4.511 16.875,4.529 16.892,4.554C17.139,4.804 17.364,5.075 17.565,5.363C18.492,6.708 19.003,8.488 19,10.372C18.997,12.255 18.498,14.047 17.567,15.389Z"
android:fillColor="#ffffff"/>
</group>
</vector>

View file

@ -30,5 +30,6 @@ dependencies {
implementation(deps.kotlin.coroutines)
implementation(deps.moshi)
implementation(deps.moshi.kotlin)
implementation(deps.timber)
// endregion
}

View file

@ -1,17 +1,31 @@
package com.tangem.data.settings
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.api.tangemTech.models.GeoResponse
import com.tangem.datasource.local.logs.AppLogsStore
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
import com.tangem.datasource.local.preferences.utils.store
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import timber.log.Timber
import java.util.Locale
internal class DefaultSettingsRepository(
private val appPreferencesStore: AppPreferencesStore,
private val appLogsStore: AppLogsStore,
private val tangemTechApi: TangemTechApi,
private val dispatchers: CoroutineDispatcherProvider,
) : SettingsRepository {
private val userCountryFlow = MutableStateFlow<UserCountry?>(value = null)
override suspend fun shouldShowSaveUserWalletScreen(): Boolean {
return appPreferencesStore.getSyncOrDefault(
key = PreferencesKeys.SHOULD_SHOW_SAVE_USER_WALLET_SCREEN_KEY,
@ -99,4 +113,38 @@ internal class DefaultSettingsRepository(
override suspend fun setMarketsTooltipShown(value: Boolean) {
appPreferencesStore.store(key = PreferencesKeys.SHOULD_SHOW_MARKETS_TOOLTIP_KEY, value = !value)
}
override suspend fun getUserCountryCodeSync(): UserCountry? {
// If user country code is already set, return it
val countryCode = userCountryFlow.value
if (countryCode != null) return countryCode
coroutineScope {
launch { fetchUserCountryCode() }
}
return null
}
override suspend fun fetchUserCountryCode() {
Timber.i("Start fetching user country code")
withContext(dispatchers.io) {
val country = runCatching { tangemTechApi.getUserCountryCode() }
.fold(
onSuccess = GeoResponse::code,
onFailure = { Locale.getDefault().country },
)
.lowercase()
val code = when (country) {
UserCountry.Russia.code -> UserCountry.Russia
else -> UserCountry.Other(code = country)
}
Timber.i("User code country is $code")
userCountryFlow.value = code
}
}
}

View file

@ -4,12 +4,14 @@ import com.tangem.data.settings.DefaultAppRatingRepository
import com.tangem.data.settings.DefaultPermissionRepository
import com.tangem.data.settings.DefaultPromoSettingsRepository
import com.tangem.data.settings.DefaultSettingsRepository
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.repositories.AppRatingRepository
import com.tangem.domain.settings.repositories.PermissionRepository
import com.tangem.domain.settings.repositories.PromoSettingsRepository
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -25,8 +27,15 @@ internal object SettingsDataModule {
fun provideSettingsRepository(
appPreferencesStore: AppPreferencesStore,
appLogsStore: AppLogsStore,
tangemTechApi: TangemTechApi,
dispatchers: CoroutineDispatcherProvider,
): SettingsRepository {
return DefaultSettingsRepository(appPreferencesStore = appPreferencesStore, appLogsStore = appLogsStore)
return DefaultSettingsRepository(
appPreferencesStore = appPreferencesStore,
appLogsStore = appLogsStore,
tangemTechApi = tangemTechApi,
dispatchers = dispatchers,
)
}
@Provides

View file

@ -1,5 +1,7 @@
package com.tangem.domain.settings.repositories
import com.tangem.domain.settings.usercountry.models.UserCountry
interface SettingsRepository {
suspend fun shouldShowSaveUserWalletScreen(): Boolean
@ -33,4 +35,8 @@ interface SettingsRepository {
suspend fun shouldShowMarketsTooltip(): Boolean
suspend fun setMarketsTooltipShown(value: Boolean)
suspend fun getUserCountryCodeSync(): UserCountry?
suspend fun fetchUserCountryCode()
}

View file

@ -0,0 +1,28 @@
package com.tangem.domain.settings.usercountry
import arrow.core.Either
import arrow.core.raise.catch
import arrow.core.raise.either
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.models.UserCountryError
/**
* Fetch user country use case
*
* @property settingsRepository settings repository
*
[REDACTED_AUTHOR]
*/
class FetchUserCountryUseCase(
private val settingsRepository: SettingsRepository,
) {
suspend operator fun invoke(): Either<UserCountryError, Unit> {
return either {
catch(
block = { settingsRepository.fetchUserCountryCode() },
catch = { raise(UserCountryError.DataError) },
)
}
}
}

View file

@ -0,0 +1,34 @@
package com.tangem.domain.settings.usercountry
import arrow.core.Either
import arrow.core.raise.catch
import arrow.core.raise.either
import arrow.core.raise.ensureNotNull
import com.tangem.domain.settings.repositories.SettingsRepository
import com.tangem.domain.settings.usercountry.models.UserCountry
import com.tangem.domain.settings.usercountry.models.UserCountryError
/**
* Get user country code use case
*
* @property settingsRepository settings repository
*
[REDACTED_AUTHOR]
*/
class GetUserCountryUseCase(
private val settingsRepository: SettingsRepository,
) {
suspend operator fun invoke(): Either<UserCountryError, UserCountry> {
return either {
val userCountryCode = catch(
block = { settingsRepository.getUserCountryCodeSync() },
catch = { raise(UserCountryError.DataError) },
)
ensureNotNull(userCountryCode) { UserCountryError.NotSetup }
userCountryCode
}
}
}

View file

@ -0,0 +1,13 @@
package com.tangem.domain.settings.usercountry.models
/**
* User country
*
[REDACTED_AUTHOR]
*/
sealed class UserCountry(open val code: String) {
data object Russia : UserCountry("ru")
data class Other(override val code: String) : UserCountry(code)
}

View file

@ -0,0 +1,15 @@
package com.tangem.domain.settings.usercountry.models
/**
* User country error
*
[REDACTED_AUTHOR]
*/
sealed interface UserCountryError {
/** User country is still loading */
data object NotSetup : UserCountryError
/** Data error */
data object DataError : UserCountryError
}

View file

@ -9,7 +9,10 @@ import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
@ -65,7 +68,6 @@ internal fun EnvironmentTogglesScreen(uiModel: EnvironmentTogglesScreenUM) {
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun EnvironmentButtons(
uiModel: EnvironmentTogglesScreenUM.ApiInfoUM,

View file

@ -6,14 +6,37 @@ import kotlinx.collections.immutable.ImmutableList
/**
* Content state of feature toggles screen
*
* @property topBarState top bar state
* @property appVersion app version
* @property featureToggles feature toggles list
* @property onBackClick the lambda to be invoked when back button is pressed
* @property onToggleValueChange the lambda to be invoked when switch button is pressed
* @property onApplyChangesClick the lambda to be invoked when apply changes button is pressed
* @property onRestartAppClick the lambda to be invoked when restart app button is pressed
*/
internal data class FeatureTogglesContentState(
val topBarState: TopBarState,
val appVersion: String,
val featureToggles: ImmutableList<TesterFeatureToggle>,
val onToggleValueChange: (String, Boolean) -> Unit,
val onBackClick: () -> Unit,
val onApplyChangesClick: () -> Unit,
)
val onToggleValueChange: (String, Boolean) -> Unit,
val onRestartAppClick: () -> Unit,
) {
/** Top bar state */
sealed interface TopBarState {
/**
* Config setup
*
* @property onBackClick the lambda to be invoked when back button is pressed
*/
data object ConfigSetup : TopBarState
/**
* Custom setup
*
* @property onRecoverClick the lambda to be invoked when recover button is pressed
*/
data class CustomSetup(val onRecoverClick: () -> Unit) : TopBarState
}
}

View file

@ -6,17 +6,21 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Switch
import androidx.compose.material.SwitchDefaults
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.appbar.AppBarWithBackButton
import com.tangem.core.ui.components.TangemSwitch
import com.tangem.core.ui.components.appbar.AppBarWithBackButtonAndIcon
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.tester.impl.R
@ -37,22 +41,35 @@ internal fun FeatureTogglesScreen(state: FeatureTogglesContentState) {
.fillMaxSize()
.background(TangemTheme.colors.background.secondary),
) {
stickyHeader {
AppBarWithBackButton(
onBackClick = state.onBackClick,
text = stringResource(id = R.string.feature_toggles),
)
stickyHeader { TopAppBar(state = state.topBarState, onBackClick = state.onBackClick) }
if (state.topBarState is FeatureTogglesContentState.TopBarState.CustomSetup) {
item(key = "warning_notification", contentType = "warning_notification") {
WarningNotification(
appVersion = state.appVersion,
modifier = Modifier
.animateItem()
.padding(horizontal = 16.dp)
.padding(bottom = 8.dp),
)
}
}
items(state.featureToggles) { featureToggle ->
items(
items = state.featureToggles,
key = TesterFeatureToggle::name,
contentType = { "feature_toggle_item" },
) { featureToggle ->
FeatureToggleItem(
toggle = featureToggle,
onCheckedChange = { isChange -> state.onToggleValueChange(featureToggle.name, isChange) },
)
}
item {
PrimaryButton(
text = stringResource(id = R.string.apply_changes),
onClick = state.onApplyChangesClick,
text = stringResource(id = R.string.restart_app),
onClick = state.onRestartAppClick,
modifier = Modifier
.fillMaxWidth()
.padding(TangemTheme.dimens.spacing16),
@ -61,12 +78,48 @@ internal fun FeatureTogglesScreen(state: FeatureTogglesContentState) {
}
}
@Composable
private fun TopAppBar(state: FeatureTogglesContentState.TopBarState, onBackClick: () -> Unit) {
AppBarWithBackButtonAndIcon(
onBackClick = onBackClick,
text = stringResource(id = R.string.feature_toggles),
iconRes = if (state is FeatureTogglesContentState.TopBarState.CustomSetup) {
R.drawable.ic_refresh_24
} else {
null
},
onIconClick = if (state is FeatureTogglesContentState.TopBarState.CustomSetup) {
state.onRecoverClick
} else {
null
},
)
}
@Composable
private fun WarningNotification(appVersion: String, modifier: Modifier = Modifier) {
Notification(
config = NotificationConfig(
subtitle = resourceReference(
id = R.string.feature_toggles_custom_setup_warning_description,
wrappedList(appVersion),
),
iconResId = R.drawable.ic_alert_triangle_20,
title = resourceReference(id = R.string.feature_toggles_custom_setup_warning_title),
),
modifier = modifier,
)
}
@Composable
private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boolean) -> Unit) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = TangemTheme.dimens.spacing18),
.padding(
horizontal = TangemTheme.dimens.spacing18,
vertical = TangemTheme.dimens.spacing8,
),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
@ -78,14 +131,8 @@ private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boo
maxLines = 1,
style = TangemTheme.typography.body2,
)
Switch(
checked = toggle.isEnabled,
onCheckedChange = onCheckedChange,
colors = SwitchDefaults.colors(
checkedThumbColor = TangemTheme.colors.control.checked,
uncheckedThumbColor = TangemTheme.colors.control.unchecked,
),
)
TangemSwitch(onCheckedChange = onCheckedChange, checked = toggle.isEnabled)
}
}
@ -94,15 +141,23 @@ private fun FeatureToggleItem(toggle: TesterFeatureToggle, onCheckedChange: (Boo
@Composable
private fun PreviewFeatureTogglesScreen() {
TangemThemePreview {
var isCustomSetup by remember { mutableStateOf(value = true) }
FeatureTogglesScreen(
state = FeatureTogglesContentState(
topBarState = if (isCustomSetup) {
FeatureTogglesContentState.TopBarState.CustomSetup(onRecoverClick = { isCustomSetup = false })
} else {
FeatureTogglesContentState.TopBarState.ConfigSetup
},
appVersion = "5.15",
featureToggles = persistentListOf(
TesterFeatureToggle(name = "FEATURE_TOGGLE_1", isEnabled = true),
TesterFeatureToggle(name = "FEATURE_TOGGLE_2", isEnabled = false),
),
onToggleValueChange = { _, _ -> },
onBackClick = {},
onApplyChangesClick = {},
onToggleValueChange = { _, _ -> isCustomSetup = true },
onRestartAppClick = {},
),
)
}

View file

@ -11,10 +11,11 @@ import com.tangem.core.navigation.finisher.AppFinisher
import com.tangem.feature.tester.presentation.featuretoggles.models.TesterFeatureToggle
import com.tangem.feature.tester.presentation.featuretoggles.state.FeatureTogglesContentState
import com.tangem.feature.tester.presentation.navigation.InnerTesterRouter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.version.AppVersionProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -22,14 +23,14 @@ import javax.inject.Inject
* ViewModel for screen with list of feature toggles
*
* @property featureTogglesManager manager for getting information about the availability of feature toggles
* @property dispatchers coroutine dispatchers provider
* @property appVersionProvider app version provider
*
[REDACTED_AUTHOR]
*/
@HiltViewModel
internal class FeatureTogglesViewModel @Inject constructor(
private val featureTogglesManager: FeatureTogglesManager,
private val dispatchers: CoroutineDispatcherProvider,
private val appVersionProvider: AppVersionProvider,
) : ViewModel() {
/** Current ui state */
@ -41,28 +42,54 @@ internal class FeatureTogglesViewModel @Inject constructor(
"Feature toggle manager must be mutable (debug build type)"
}
/** Setup navigation state property by router [router] and provides app restart method by [appRestarter] */
/** Setup navigation state property by router [router] and provides app restart method by [appFinisher] */
fun setupInteractions(router: InnerTesterRouter, appFinisher: AppFinisher) {
uiState = uiState.copy(
onBackClick = router::back,
onApplyChangesClick = appFinisher::restart,
onRestartAppClick = appFinisher::restart,
)
}
private fun initState(): FeatureTogglesContentState {
return FeatureTogglesContentState(
topBarState = getConfigSetupState(),
appVersion = appVersionProvider.versionName,
featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles(),
onToggleValueChange = ::onToggleValueChange,
onBackClick = {},
onApplyChangesClick = {},
onRestartAppClick = {},
)
}
private fun onToggleValueChange(name: String, isEnabled: Boolean) {
viewModelScope.launch(dispatchers.main) {
viewModelScope.launch {
mutableFeatureTogglesManager.changeToggle(name = name, isEnabled = isEnabled)
uiState = uiState.copy(featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles())
// delay for smoothly update animations
delay(timeMillis = 300)
uiState = uiState.copy(topBarState = getConfigSetupState())
}
}
private fun getConfigSetupState(): FeatureTogglesContentState.TopBarState {
return if (mutableFeatureTogglesManager.isMatchLocalConfig()) {
FeatureTogglesContentState.TopBarState.ConfigSetup
} else {
FeatureTogglesContentState.TopBarState.CustomSetup(
onRecoverClick = {
viewModelScope.launch {
mutableFeatureTogglesManager.recoverLocalConfig()
uiState = uiState.copy(
topBarState = getConfigSetupState(),
featureToggles = mutableFeatureTogglesManager.getTesterFeatureToggles(),
)
}
},
)
}
}

View file

@ -2,7 +2,9 @@
<resources>
<string name="tester_menu" translatable="false">Tester menu</string>
<string name="feature_toggles" translatable="false">Feature toggles</string>
<string name="apply_changes" translatable="false">Apply changes</string>
<string name="feature_toggles_custom_setup_warning_title" translatable="false">Custom setup</string>
<string name="feature_toggles_custom_setup_warning_description" translatable="false">Feature toggles differs from v%s config</string>
<string name="restart_app" translatable="false">Restart app</string>
<string name="environment_toggles" translatable="false">Environment toggles</string>
<string name="tester_actions" translatable="false">Tester actions</string>
<string name="hide_all_currencies" translatable="false">Hide all currencies</string>

View file

@ -1,6 +0,0 @@
package com.tangem.features.tokendetails.featuretoggles
interface TokenDetailsFeatureToggles {
fun isGenerateXPubEnabled(): Boolean
}

View file

@ -1,21 +0,0 @@
package com.tangem.feature.tokendetails.di
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.feature.tokendetails.featuretoggles.DefaultTokenDetailsFeatureToggles
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
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 TokenDetailsFeatureTogglesModule {
@Provides
@Singleton
fun provideTokenDetailsFeatureToggles(featureTogglesManager: FeatureTogglesManager): TokenDetailsFeatureToggles {
return DefaultTokenDetailsFeatureToggles(featureTogglesManager)
}
}

View file

@ -1,11 +0,0 @@
package com.tangem.feature.tokendetails.featuretoggles
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
internal class DefaultTokenDetailsFeatureToggles(
private val featureTogglesManager: FeatureTogglesManager,
) : TokenDetailsFeatureToggles {
override fun isGenerateXPubEnabled() = featureTogglesManager.isFeatureEnabled(name = "GENERATE_XPUB_ENABLED")
}

View file

@ -17,7 +17,6 @@ import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.feature.tokendetails.presentation.tokendetails.state.*
import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsActionButton
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
import com.tangem.features.tokendetails.impl.R
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
import com.tangem.utils.converter.Converter
@ -28,7 +27,6 @@ import kotlinx.coroutines.flow.MutableStateFlow
internal class TokenDetailsSkeletonStateConverter(
private val clickIntents: TokenDetailsClickIntents,
private val featureToggles: TokenDetailsFeatureToggles,
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val userWalletId: UserWalletId,
@ -98,12 +96,14 @@ internal class TokenDetailsSkeletonStateConverter(
cryptoCurrency: CryptoCurrency,
) {
val userWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return
val isGenerateXPubEnabled = featureToggles.isGenerateXPubEnabled()
val isBitcoin = isBitcoin(cryptoCurrency.network.id.value)
val hasDerivations =
networkHasDerivationUseCase(userWallet.scanResponse, cryptoCurrency.network).getOrElse { false }
if (isGenerateXPubEnabled && isBitcoin && hasDerivations) {
val isBitcoin = isBitcoin(cryptoCurrency.network.id.value)
val hasDerivations = networkHasDerivationUseCase(
scanResponse = userWallet.scanResponse,
network = cryptoCurrency.network,
).getOrElse { false }
if (isBitcoin && hasDerivations) {
add(
TokenDetailsAppBarMenuConfig.MenuItem(
title = resourceReference(R.string.token_details_generate_xpub),

View file

@ -39,7 +39,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.t
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.Provider
import kotlinx.collections.immutable.toImmutableList
@ -53,7 +52,6 @@ internal class TokenDetailsStateFactory(
private val stakingEntryInfoProvider: Provider<StakingEntryInfo?>,
private val cryptoCurrencyStatusProvider: Provider<CryptoCurrencyStatus?>,
private val clickIntents: TokenDetailsClickIntents,
private val featureToggles: TokenDetailsFeatureToggles,
private val networkHasDerivationUseCase: NetworkHasDerivationUseCase,
private val getUserWalletUseCase: GetUserWalletUseCase,
private val userWalletId: UserWalletId,
@ -65,7 +63,6 @@ internal class TokenDetailsStateFactory(
private val skeletonStateConverter by lazy {
TokenDetailsSkeletonStateConverter(
clickIntents = clickIntents,
featureToggles = featureToggles,
networkHasDerivationUseCase = networkHasDerivationUseCase,
getUserWalletUseCase = getUserWalletUseCase,
userWalletId = userWalletId,
@ -390,10 +387,9 @@ internal class TokenDetailsStateFactory(
isSupported: Boolean,
): TokenDetailsAppBarMenuConfig? {
if (cardTypesResolver.isSingleWalletWithToken()) return null
val showGenerateExtendedKey = featureToggles.isGenerateXPubEnabled() && isSupported
return copy(
items = buildList {
if (showGenerateExtendedKey && hasDerivations) {
if (isSupported && hasDerivations) {
TokenDetailsAppBarMenuConfig.MenuItem(
title = resourceReference(R.string.token_details_generate_xpub),
textColorProvider = { TangemTheme.colors.text.primary1 },

View file

@ -71,7 +71,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDeta
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
import com.tangem.features.staking.api.featuretoggles.StakingFeatureToggles
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
import com.tangem.features.tokendetails.impl.R
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
@ -124,7 +123,6 @@ internal class TokenDetailsViewModel @Inject constructor(
private val vibratorHapticManager: VibratorHapticManager,
private val clipboardManager: ClipboardManager,
getUserWalletUseCase: GetUserWalletUseCase,
tokenDetailsFeatureToggles: TokenDetailsFeatureToggles,
deepLinksRegistry: DeepLinksRegistry,
savedStateHandle: SavedStateHandle,
private val appRouter: AppRouter,
@ -159,13 +157,12 @@ internal class TokenDetailsViewModel @Inject constructor(
stakingEntryInfoProvider = Provider { stakingEntryInfo },
cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus },
clickIntents = this,
symbol = cryptoCurrency.symbol,
decimals = cryptoCurrency.decimals,
featureToggles = tokenDetailsFeatureToggles,
stakingFeatureToggles = stakingFeatureToggles,
userWalletId = userWalletId,
networkHasDerivationUseCase = networkHasDerivationUseCase,
getUserWalletUseCase = getUserWalletUseCase,
userWalletId = userWalletId,
stakingFeatureToggles = stakingFeatureToggles,
symbol = cryptoCurrency.symbol,
decimals = cryptoCurrency.decimals,
)
private val exchangeStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) {

View file

@ -89,9 +89,9 @@ markdownComposeView = "0.5.4"
# endregion Other libraries
# region Tangem
tangemBlockchainSdk = "release-app_5.15-781"
tangemBlockchainSdk = "develop-785"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
tangemCardSdk = "release-app_5.15-382"
tangemCardSdk = "develop-381"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
tangemVico = "2.0.0-alpha.25-tangem16"
#tangemVico = "0.0.1" # Keep it! - used for local builds ^

View file

@ -126,6 +126,8 @@ fun Blockchain.Companion.fromNetworkId(networkId: String): Blockchain? {
"sei-network" -> Blockchain.Sei
"sei-network/test" -> Blockchain.SeiTestnet
"internet-computer" -> Blockchain.InternetComputer
"energy-web-chain" -> Blockchain.EnergyWebChain
"energy-web-chain/test" -> Blockchain.EnergyWebChainTestnet
else -> null
}
}
@ -253,6 +255,8 @@ fun Blockchain.toNetworkId(): String {
Blockchain.Sei -> "sei-network"
Blockchain.SeiTestnet -> "sei-network/test"
Blockchain.InternetComputer -> "internet-computer"
Blockchain.EnergyWebChain -> "energy-web-chain"
Blockchain.EnergyWebChainTestnet -> "energy-web-chain/test"
}
}
@ -337,6 +341,7 @@ fun Blockchain.toCoinId(): String {
Blockchain.Cyber, Blockchain.CyberTestnet -> "cyber-ethereum"
Blockchain.Sei, Blockchain.SeiTestnet -> "sei-network"
Blockchain.InternetComputer -> "internet-computer"
Blockchain.EnergyWebChain, Blockchain.EnergyWebChainTestnet -> "energy-web-chain"
}
}
@ -363,10 +368,11 @@ fun Blockchain.minimalAmount(): BigDecimal {
private const val NODL = "NODL"
private const val NODL_AMOUNT_TO_CREATE_ACCOUNT = 1.5
// no need to add testnets
private val excludedBlockchains = listOf(
Blockchain.Unknown,
Blockchain.Nexa,
Blockchain.NexaTestnet,
Blockchain.Sei,
Blockchain.InternetComputer,
Blockchain.EnergyWebChain,
)