Updated on 2026-08-14
This commit is contained in:
commit
b511924b86
40 changed files with 462 additions and 170 deletions
|
|
@ -718,6 +718,16 @@
|
|||
"networkId": "sei-network/test"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "energy-web-chain",
|
||||
"name": "Energy Web Chain",
|
||||
"symbol": "EWT",
|
||||
"networks": [
|
||||
{
|
||||
"networkId": "energy-web-chain/test"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
)
|
||||
|
|
@ -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"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
9
core/ui/src/main/res/drawable/ic_energy_web_22.xml
Normal file
9
core/ui/src/main/res/drawable/ic_energy_web_22.xml
Normal 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>
|
||||
19
core/ui/src/main/res/drawable/img_energy_web_22.xml
Normal file
19
core/ui/src/main/res/drawable/img_energy_web_22.xml
Normal 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>
|
||||
|
|
@ -30,5 +30,6 @@ dependencies {
|
|||
implementation(deps.kotlin.coroutines)
|
||||
implementation(deps.moshi)
|
||||
implementation(deps.moshi.kotlin)
|
||||
implementation(deps.timber)
|
||||
// endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
|
|
@ -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) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.features.tokendetails.featuretoggles
|
||||
|
||||
interface TokenDetailsFeatureToggles {
|
||||
|
||||
fun isGenerateXPubEnabled(): Boolean
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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 },
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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 ^
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue