Updated on 2026-08-14

This commit is contained in:
Tangem 2024-03-15 13:30:57 +05:00
parent c1af0daa09
commit 39e8abe1bb
18 changed files with 208 additions and 14 deletions

View file

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

View file

@ -53,10 +53,14 @@ dependencies {
implementation(projects.core.datasource)
implementation(projects.core.deepLinks)
implementation(projects.core.deepLinks.global)
implementation(projects.core.featuretoggles)
implementation(projects.libs.crypto)
/** Domain modules */
implementation(projects.domain.appCurrency)
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.card)
implementation(projects.domain.demo)
implementation(projects.domain.legacy)
implementation(projects.domain.models)

View file

@ -0,0 +1,21 @@
package com.tangem.feature.tokendetails.di
import com.tangem.core.featuretoggle.manager.FeatureTogglesManager
import com.tangem.feature.tokendetails.featuretoggles.DefaultTokenDetailsFeatureToggles
import com.tangem.features.tokendetails.featuretoggles.TokenDetailsFeatureToggles
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object TokenDetailsFeatureTogglesModule {
@Provides
@Singleton
fun provideTokenDetailsFeatureToggles(featureTogglesManager: FeatureTogglesManager): TokenDetailsFeatureToggles {
return DefaultTokenDetailsFeatureToggles(featureTogglesManager)
}
}

View file

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

View file

@ -24,6 +24,10 @@ internal class DefaultTokenDetailsRouter(
reduxNavController.navigate(NavigationAction.OpenUrl(url = url))
}
override fun share(text: String) {
reduxNavController.navigate(NavigationAction.Share(text))
}
override fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency) {
reduxNavController.navigate(
action = NavigationAction.NavigateTo(

View file

@ -12,5 +12,8 @@ internal interface InnerTokenDetailsRouter : TokenDetailsRouter {
/** Open website by [url] */
fun openUrl(url: String)
/** Share [text] */
fun share(text: String)
fun openTokenDetails(userWalletId: UserWalletId, currency: CryptoCurrency)
}

View file

@ -5,20 +5,25 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.networkIconResId
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.tokens.model.CryptoCurrency
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.state.components.TokenDetailsPullToRefreshConfig
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
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.MutableStateFlow
internal class TokenDetailsSkeletonStateConverter(
private val clickIntents: TokenDetailsClickIntents,
private val featureToggles: TokenDetailsFeatureToggles,
) : Converter<CryptoCurrency, TokenDetailsState> {
private val iconStateConverter by lazy { TokenDetailsIconStateConverter() }
@ -27,7 +32,7 @@ internal class TokenDetailsSkeletonStateConverter(
return TokenDetailsState(
topAppBarConfig = TokenDetailsTopAppBarConfig(
onBackClick = clickIntents::onBackClick,
tokenDetailsAppBarMenuConfig = createMenu(),
tokenDetailsAppBarMenuConfig = createMenu(value),
),
tokenInfoBlockState = TokenInfoBlockState(
name = value.name,
@ -60,14 +65,21 @@ internal class TokenDetailsSkeletonStateConverter(
)
}
private fun createMenu(): TokenDetailsAppBarMenuConfig = TokenDetailsAppBarMenuConfig(
items = persistentListOf(
private fun createMenu(cryptoCurrency: CryptoCurrency): TokenDetailsAppBarMenuConfig = TokenDetailsAppBarMenuConfig(
items = buildList {
if (featureToggles.isGenerateXPubEnabled() && isBitcoin(cryptoCurrency.network.id.value)) {
TokenDetailsAppBarMenuConfig.MenuItem(
title = resourceReference(R.string.token_details_generate_xpub),
textColorProvider = { TangemTheme.colors.text.primary1 },
onClick = clickIntents::onGenerateExtendedKey,
).let(::add)
}
TokenDetailsAppBarMenuConfig.MenuItem(
title = TextReference.Res(id = R.string.token_details_hide_token),
textColorProvider = { TangemTheme.colors.text.warning },
onClick = clickIntents::onHideClick,
),
),
).let(::add)
}.toImmutableList(),
)
private fun createButtons(): ImmutableList<TokenDetailsActionButton> {

View file

@ -11,6 +11,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.common.CardTypesResolver
@ -32,9 +33,9 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.t
import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.txhistory.TokenDetailsLoadingTxHistoryConverter.TokenDetailsLoadingTxHistoryModel
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
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.utils.Provider
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
@ -44,12 +45,16 @@ internal class TokenDetailsStateFactory(
private val currentStateProvider: Provider<TokenDetailsState>,
private val appCurrencyProvider: Provider<AppCurrency>,
private val clickIntents: TokenDetailsClickIntents,
private val featureToggles: TokenDetailsFeatureToggles,
symbol: String,
decimals: Int,
) {
private val skeletonStateConverter by lazy {
TokenDetailsSkeletonStateConverter(clickIntents = clickIntents)
TokenDetailsSkeletonStateConverter(
clickIntents = clickIntents,
featureToggles = featureToggles,
)
}
private val notificationConverter by lazy {
@ -282,12 +287,12 @@ internal class TokenDetailsStateFactory(
)
}
fun getStateWithUpdatedMenu(cardTypesResolver: CardTypesResolver): TokenDetailsState {
fun getStateWithUpdatedMenu(cardTypesResolver: CardTypesResolver, isBitcoin: Boolean): TokenDetailsState {
return with(currentStateProvider()) {
copy(
topAppBarConfig = topAppBarConfig.copy(
tokenDetailsAppBarMenuConfig = topAppBarConfig.tokenDetailsAppBarMenuConfig
?.updateMenu(cardTypesResolver),
?.updateMenu(cardTypesResolver, isBitcoin),
),
)
}
@ -295,17 +300,25 @@ internal class TokenDetailsStateFactory(
private fun TokenDetailsAppBarMenuConfig.updateMenu(
cardTypesResolver: CardTypesResolver,
isBitcoin: Boolean,
): TokenDetailsAppBarMenuConfig? {
if (cardTypesResolver.isSingleWalletWithToken()) return null
val showGenerateExtendedKey = featureToggles.isGenerateXPubEnabled() && isBitcoin
return copy(
items = persistentListOf(
items = buildList {
if (showGenerateExtendedKey) {
TokenDetailsAppBarMenuConfig.MenuItem(
title = resourceReference(R.string.token_details_generate_xpub),
textColorProvider = { TangemTheme.colors.text.primary1 },
onClick = clickIntents::onGenerateExtendedKey,
).let(::add)
}
TokenDetailsAppBarMenuConfig.MenuItem(
title = TextReference.Res(id = R.string.token_details_hide_token),
textColorProvider = { TangemTheme.colors.text.warning },
onClick = clickIntents::onHideClick,
),
),
).let(::add)
}.toImmutableList(),
)
}
}

View file

@ -47,4 +47,6 @@ interface TokenDetailsClickIntents {
fun onSwapPromoDismiss()
fun onSwapPromoClick()
fun onGenerateExtendedKey()
}

View file

@ -18,6 +18,7 @@ import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.card.GetExtendedPublicKeyForCurrencyUseCase
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.redux.ReduxStateHolder
@ -50,8 +51,10 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.state.SwapTrans
import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState
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.tokendetails.featuretoggles.TokenDetailsFeatureToggles
import com.tangem.features.tokendetails.impl.R
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.lib.crypto.BlockchainUtils.isBitcoin
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import dagger.hilt.android.lifecycle.HiltViewModel
@ -85,6 +88,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val shouldShowSwapPromoTokenUseCase: ShouldShowSwapPromoTokenUseCase,
private val updateDelayedCurrencyStatusUseCase: UpdateDelayedNetworkStatusUseCase,
private val getExtendedPublicKeyForCurrencyUseCase: GetExtendedPublicKeyForCurrencyUseCase,
private val swapRepository: SwapRepository,
private val swapTransactionRepository: SwapTransactionRepository,
private val quotesRepository: QuotesRepository,
@ -92,6 +96,7 @@ internal class TokenDetailsViewModel @Inject constructor(
private val isDemoCardUseCase: IsDemoCardUseCase,
private val reduxStateHolder: ReduxStateHolder,
private val analyticsEventsHandler: AnalyticsEventHandler,
featureToggles: TokenDetailsFeatureToggles,
deepLinksRegistry: DeepLinksRegistry,
savedStateHandle: SavedStateHandle,
) : ViewModel(), DefaultLifecycleObserver, TokenDetailsClickIntents {
@ -121,6 +126,7 @@ internal class TokenDetailsViewModel @Inject constructor(
clickIntents = this,
symbol = cryptoCurrency.symbol,
decimals = cryptoCurrency.decimals,
featureToggles = featureToggles,
)
private val exchangeStatusFactory by lazy(mode = LazyThreadSafetyMode.NONE) {
@ -359,7 +365,10 @@ internal class TokenDetailsViewModel @Inject constructor(
viewModelScope.launch(dispatchers.main) {
val wallet = getUserWalletUseCase(userWalletId).getOrElse { return@launch }
uiState = stateFactory.getStateWithUpdatedMenu(wallet.scanResponse.cardTypesResolver)
uiState = stateFactory.getStateWithUpdatedMenu(
cardTypesResolver = wallet.scanResponse.cardTypesResolver,
isBitcoin = isBitcoin(cryptoCurrency.network.id.value),
)
}
}
@ -496,6 +505,24 @@ internal class TokenDetailsViewModel @Inject constructor(
}
}
override fun onGenerateExtendedKey() {
viewModelScope.launch(dispatchers.main) {
val extendedKey = getExtendedPublicKeyForCurrencyUseCase(
userWalletId,
cryptoCurrency.network.derivationPath,
).fold(
ifLeft = {
Timber.e(it.cause?.localizedMessage.orEmpty())
""
},
ifRight = { it },
)
if (extendedKey.isNotBlank()) {
router.share(extendedKey)
}
}
}
override fun onSellClick() {
analyticsEventsHandler.send(TokenScreenAnalyticsEvent.ButtonSell(cryptoCurrency.symbol))

View file

@ -56,6 +56,8 @@ dependencies {
implementation(projects.core.deepLinks)
implementation(projects.core.deepLinks.global)
implementation(projects.libs.crypto)
/** Domain modules */
implementation(projects.domain.card)
implementation(projects.domain.demo)