diff --git a/app/src/main/assets/testnet_tokens.json b/app/src/main/assets/testnet_tokens.json index cb960e3694..bbac69e02e 100644 --- a/app/src/main/assets/testnet_tokens.json +++ b/app/src/main/assets/testnet_tokens.json @@ -718,6 +718,16 @@ "networkId": "sei-network/test" } ] + }, + { + "id": "energy-web-chain", + "name": "Energy Web Chain", + "symbol": "EWT", + "networks": [ + { + "networkId": "energy-web-chain/test" + } + ] } ] } diff --git a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt index 21ab576e7f..772a78b90d 100644 --- a/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt +++ b/app/src/main/java/com/tangem/tap/ApplicationEntryPoint.kt @@ -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 } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index ef8490029a..cf7f9a9347 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -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, ), ), ) diff --git a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt index 6b011082d2..02c839de1f 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/global/GlobalReducer.kt @@ -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 } diff --git a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt index 1b5860c2c0..e175d51e25 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/SettingsDomainModule.kt @@ -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) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt index 8c78ffa3a5..deb7e332b7 100644 --- a/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt +++ b/app/src/main/java/com/tangem/tap/domain/walletconnect/WalletConnectSdkHelper.kt @@ -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, diff --git a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt index 9f4e9fbaa9..f33229cf24 100644 --- a/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt +++ b/app/src/main/java/com/tangem/tap/features/home/compose/StoriesScreen.kt @@ -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, ) diff --git a/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt b/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt new file mode 100644 index 0000000000..ce8c0bcec3 --- /dev/null +++ b/app/src/main/java/com/tangem/tap/features/home/featuretoggles/HomeFeatureToggles.kt @@ -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") +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt index e3f0375cc1..05ba17df63 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeAction.kt @@ -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() } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt index f9d977fdec..ab41e3d89f 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeReducer.kt @@ -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 } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt index 2e584d3d35..5341631157 100644 --- a/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt +++ b/app/src/main/java/com/tangem/tap/features/home/redux/HomeState.kt @@ -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 = initDefaultStories(), + val stories: ImmutableList = 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 = 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 = 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, } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt index 45b2035717..81fba12558 100644 --- a/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt +++ b/app/src/main/java/com/tangem/tap/proxy/redux/DaggerGraphState.kt @@ -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 \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/GeoResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/GeoResponse.kt index e6cdfa4b12..f444437037 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/GeoResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/tangemTech/models/GeoResponse.kt @@ -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, ) \ No newline at end of file diff --git a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json index 652af05e72..6af370b81e 100644 --- a/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json +++ b/core/featuretoggles/src/main/assets/configs/feature_toggles_config.json @@ -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" } ] diff --git a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManager.kt b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManager.kt index 9b29a65f66..303dce3c7b 100644 --- a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManager.kt +++ b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/DevFeatureTogglesManager.kt @@ -24,6 +24,7 @@ internal class DevFeatureTogglesManager( ) : MutableFeatureTogglesManager { private var featureTogglesMap: MutableMap by Delegates.notNull() + private var localFeatureTogglesMap: Map 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 = 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) { featureTogglesMap = map } + + private suspend fun AppPreferencesStore.storeFeatureToggles(value: Map) { + storeObject(PreferencesKeys.FEATURE_TOGGLES_KEY, value) + } } \ No newline at end of file diff --git a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/MutableFeatureTogglesManager.kt b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/MutableFeatureTogglesManager.kt index 8d7a063c5d..f04b423f4f 100644 --- a/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/MutableFeatureTogglesManager.kt +++ b/core/featuretoggles/src/main/kotlin/com/tangem/core/featuretoggle/manager/MutableFeatureTogglesManager.kt @@ -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 /** Change availability [isEnabled] of toggle with name [name] */ suspend fun changeToggle(name: String, isEnabled: Boolean) + + /** Recover local config state */ + suspend fun recoverLocalConfig() } \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt index 59fe1fa692..c36f4876be 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/extensions/BlockchainIcons.kt @@ -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 } } \ No newline at end of file diff --git a/core/ui/src/main/res/drawable/ic_energy_web_22.xml b/core/ui/src/main/res/drawable/ic_energy_web_22.xml new file mode 100644 index 0000000000..b4e0c5dc9b --- /dev/null +++ b/core/ui/src/main/res/drawable/ic_energy_web_22.xml @@ -0,0 +1,9 @@ + + + diff --git a/core/ui/src/main/res/drawable/img_energy_web_22.xml b/core/ui/src/main/res/drawable/img_energy_web_22.xml new file mode 100644 index 0000000000..30fbdcf859 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_energy_web_22.xml @@ -0,0 +1,19 @@ + + + + + + + + diff --git a/data/settings/build.gradle.kts b/data/settings/build.gradle.kts index 96f4640242..6856b1236f 100644 --- a/data/settings/build.gradle.kts +++ b/data/settings/build.gradle.kts @@ -30,5 +30,6 @@ dependencies { implementation(deps.kotlin.coroutines) implementation(deps.moshi) implementation(deps.moshi.kotlin) + implementation(deps.timber) // endregion } diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt index d9122fb1b3..6ea8cb2b4d 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultSettingsRepository.kt @@ -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(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 + } + } } \ No newline at end of file diff --git a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt index bddc8ef6e1..0ea5bce762 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/di/SettingsDataModule.kt @@ -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 diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt index 6067b99a64..b342dcd2ef 100644 --- a/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt +++ b/domain/settings/src/main/java/com/tangem/domain/settings/repositories/SettingsRepository.kt @@ -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() } \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/FetchUserCountryUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/FetchUserCountryUseCase.kt new file mode 100644 index 0000000000..5eab20c011 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/FetchUserCountryUseCase.kt @@ -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 { + return either { + catch( + block = { settingsRepository.fetchUserCountryCode() }, + catch = { raise(UserCountryError.DataError) }, + ) + } + } +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/GetUserCountryUseCase.kt b/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/GetUserCountryUseCase.kt new file mode 100644 index 0000000000..01768f5a6c --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/GetUserCountryUseCase.kt @@ -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 { + return either { + val userCountryCode = catch( + block = { settingsRepository.getUserCountryCodeSync() }, + catch = { raise(UserCountryError.DataError) }, + ) + + ensureNotNull(userCountryCode) { UserCountryError.NotSetup } + + userCountryCode + } + } +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/models/UserCountry.kt b/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/models/UserCountry.kt new file mode 100644 index 0000000000..e212b96917 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/models/UserCountry.kt @@ -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) +} \ No newline at end of file diff --git a/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/models/UserCountryError.kt b/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/models/UserCountryError.kt new file mode 100644 index 0000000000..8d6e483a09 --- /dev/null +++ b/domain/settings/src/main/java/com/tangem/domain/settings/usercountry/models/UserCountryError.kt @@ -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 +} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/environments/ui/EnvironmentTogglesScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/environments/ui/EnvironmentTogglesScreen.kt index b9c442ce86..10a459c2f3 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/environments/ui/EnvironmentTogglesScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/environments/ui/EnvironmentTogglesScreen.kt @@ -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, diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/state/FeatureTogglesContentState.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/state/FeatureTogglesContentState.kt index c1e4e290bb..fb97f05a55 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/state/FeatureTogglesContentState.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/state/FeatureTogglesContentState.kt @@ -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, - val onToggleValueChange: (String, Boolean) -> Unit, val onBackClick: () -> Unit, - val onApplyChangesClick: () -> Unit, -) \ No newline at end of file + 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 + } +} \ No newline at end of file diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt index 0ebd8cadbf..6fd2e7e7e9 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/ui/FeatureTogglesScreen.kt @@ -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 = {}, ), ) } diff --git a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt index 6200dd283a..2af75d3b80 100644 --- a/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt +++ b/features/tester/impl/src/main/java/com/tangem/feature/tester/presentation/featuretoggles/viewmodels/FeatureTogglesViewModel.kt @@ -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(), + ) + } + }, + ) } } diff --git a/features/tester/impl/src/main/res/values/strings.xml b/features/tester/impl/src/main/res/values/strings.xml index dfd6f4d39c..969d34bce4 100644 --- a/features/tester/impl/src/main/res/values/strings.xml +++ b/features/tester/impl/src/main/res/values/strings.xml @@ -2,7 +2,9 @@ Tester menu Feature toggles - Apply changes + Custom setup + Feature toggles differs from v%s config + Restart app Environment toggles Tester actions Hide all currencies diff --git a/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/featuretoggles/TokenDetailsFeatureToggles.kt b/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/featuretoggles/TokenDetailsFeatureToggles.kt deleted file mode 100644 index 1fc4007823..0000000000 --- a/features/tokendetails/api/src/main/kotlin/com/tangem/features/tokendetails/featuretoggles/TokenDetailsFeatureToggles.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.tangem.features.tokendetails.featuretoggles - -interface TokenDetailsFeatureToggles { - - fun isGenerateXPubEnabled(): Boolean -} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsFeatureTogglesModule.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsFeatureTogglesModule.kt deleted file mode 100644 index 21b9d911cb..0000000000 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/di/TokenDetailsFeatureTogglesModule.kt +++ /dev/null @@ -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) - } -} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/featuretoggles/DefaultTokenDetailsFeatureToggles.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/featuretoggles/DefaultTokenDetailsFeatureToggles.kt deleted file mode 100644 index 96d2dbbb72..0000000000 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/featuretoggles/DefaultTokenDetailsFeatureToggles.kt +++ /dev/null @@ -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") -} \ No newline at end of file diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt index 4aeac3dd4b..582ddd5560 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSkeletonStateConverter.kt @@ -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), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt index 934b75a0d1..54b171bf7d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsStateFactory.kt @@ -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, private val cryptoCurrencyStatusProvider: Provider, 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 }, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 5d65adaf1c..c59141254e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -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) { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 49b3c92007..aaa056ecb4 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -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 ^ diff --git a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt index cddafe6527..8cc1adb2f6 100644 --- a/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt +++ b/libs/blockchain-sdk/src/main/java/com/tangem/blockchainsdk/utils/Blockchain.kt @@ -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, ) \ No newline at end of file