Updated on 2026-08-14
This commit is contained in:
parent
7e5a678d84
commit
d442154b82
17 changed files with 137 additions and 6 deletions
|
|
@ -284,6 +284,7 @@ internal class ChildFactory @Inject constructor(
|
|||
params = TokenDetailsComponent.Params(
|
||||
userWalletId = route.userWalletId,
|
||||
currency = route.currency,
|
||||
navigationAction = route.navigationAction,
|
||||
),
|
||||
componentFactory = tokenDetailsComponentFactory,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.nft.models.NFTAsset
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
|
|
@ -54,6 +55,7 @@ sealed class AppRoute(val path: String) : Route {
|
|||
data class CurrencyDetails(
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
val navigationAction: NavigationAction? = null,
|
||||
) : AppRoute(path = "/currency_details/${userWalletId.stringValue}/${currency.id.value}")
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ class TokenItemStateConverter(
|
|||
},
|
||||
private val onItemClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null,
|
||||
private val onItemLongClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null,
|
||||
private val onApyLabelClick: ((TokenItemState, CryptoCurrencyStatus) -> Unit)? = null,
|
||||
) : Converter<CryptoCurrencyStatus, TokenItemState> {
|
||||
|
||||
override fun convert(value: CryptoCurrencyStatus): TokenItemState {
|
||||
|
|
@ -102,6 +103,9 @@ class TokenItemStateConverter(
|
|||
onItemLongClick = onItemLongClick?.let { onItemLongClick ->
|
||||
{ onItemLongClick(it, this) }
|
||||
},
|
||||
onApyLabelClick = onApyLabelClick?.let { onApyLabelClick ->
|
||||
{ onApyLabelClick(it, this) }
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ package com.tangem.core.analytics.models.event
|
|||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
import com.tangem.core.analytics.models.AnalyticsParam
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ACTION
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.BLOCKCHAIN
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.ERROR_CODE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.STATE
|
||||
import com.tangem.core.analytics.models.AnalyticsParam.Key.TOKEN_PARAM
|
||||
|
||||
/**
|
||||
|
|
@ -108,6 +111,21 @@ sealed class MainScreenAnalyticsEvent(
|
|||
event = "Hot Token Error",
|
||||
params = mapOf(ERROR_CODE to errorCode),
|
||||
)
|
||||
|
||||
data class ApyClicked(
|
||||
val token: String,
|
||||
val blockchain: String,
|
||||
val action: String,
|
||||
val state: String,
|
||||
) : MainScreenAnalyticsEvent(
|
||||
event = "APY Clicked",
|
||||
params = mapOf(
|
||||
TOKEN_PARAM to token,
|
||||
BLOCKCHAIN to blockchain,
|
||||
ACTION to action,
|
||||
STATE to state,
|
||||
),
|
||||
)
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -110,6 +110,7 @@ fun TokenItem(
|
|||
|
||||
TokenTitle(
|
||||
state = state.titleState,
|
||||
onApyClick = { state.onApyLabelClick?.invoke(state) },
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = LayoutId.TITLE)
|
||||
.padding(end = TangemTheme.dimens.spacing8)
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.tangem.core.ui.components.token.internal
|
|||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
|
@ -20,10 +21,10 @@ import com.tangem.core.ui.res.TangemTheme
|
|||
import com.tangem.core.ui.components.token.state.TokenItemState.TitleState as TokenTitleState
|
||||
|
||||
@Composable
|
||||
internal fun TokenTitle(state: TokenTitleState?, modifier: Modifier = Modifier) {
|
||||
internal fun TokenTitle(state: TokenTitleState?, onApyClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is TokenTitleState.Content -> {
|
||||
ContentTitle(state = state, modifier = modifier)
|
||||
ContentTitle(state = state, modifier = modifier, onApyClick = onApyClick)
|
||||
}
|
||||
is TokenTitleState.Loading -> {
|
||||
RectangleShimmer(modifier = modifier.placeholderSize(), radius = TangemTheme.dimens.radius4)
|
||||
|
|
@ -36,7 +37,7 @@ internal fun TokenTitle(state: TokenTitleState?, modifier: Modifier = Modifier)
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun ContentTitle(state: TokenTitleState.Content, modifier: Modifier = Modifier) {
|
||||
private fun ContentTitle(state: TokenTitleState.Content, onApyClick: () -> Unit, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing6),
|
||||
|
|
@ -60,7 +61,11 @@ private fun ContentTitle(state: TokenTitleState.Content, modifier: Modifier = Mo
|
|||
YieldSupplyApyLabel(
|
||||
apy = state.earnApy,
|
||||
isActive = state.earnApyIsActive,
|
||||
modifier = Modifier.align(alignment = Alignment.CenterVertically),
|
||||
modifier = Modifier
|
||||
.align(alignment = Alignment.CenterVertically)
|
||||
.clickable {
|
||||
onApyClick()
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ sealed class TokenItemState {
|
|||
/** Callback which will be called when an item is long clicked */
|
||||
abstract val onItemLongClick: ((TokenItemState) -> Unit)?
|
||||
|
||||
/** Callback which will be called when an apy label is clicked */
|
||||
abstract val onApyLabelClick: ((TokenItemState) -> Unit)?
|
||||
|
||||
/**
|
||||
* Loading token state
|
||||
*
|
||||
|
|
@ -58,6 +61,7 @@ sealed class TokenItemState {
|
|||
override val subtitle2State: Subtitle2State = Subtitle2State.Loading
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -73,6 +77,7 @@ sealed class TokenItemState {
|
|||
override val subtitle2State: Subtitle2State = Subtitle2State.Locked
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -96,6 +101,7 @@ sealed class TokenItemState {
|
|||
override val subtitle2State: Subtitle2State?,
|
||||
override val onItemClick: ((TokenItemState) -> Unit)?,
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)?,
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null,
|
||||
) : TokenItemState()
|
||||
|
||||
/**
|
||||
|
|
@ -116,6 +122,7 @@ sealed class TokenItemState {
|
|||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -133,6 +140,7 @@ sealed class TokenItemState {
|
|||
override val iconState: CurrencyIconState,
|
||||
override val titleState: TitleState,
|
||||
override val subtitleState: SubtitleState? = null,
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null,
|
||||
override val onItemClick: ((TokenItemState) -> Unit)?,
|
||||
override val onItemLongClick: ((TokenItemState) -> Unit)?,
|
||||
) : TokenItemState() {
|
||||
|
|
@ -159,6 +167,7 @@ sealed class TokenItemState {
|
|||
override val fiatAmountState: FiatAmountState? = null
|
||||
override val subtitle2State: Subtitle2State? = null
|
||||
override val onItemClick: ((TokenItemState) -> Unit)? = null
|
||||
override val onApyLabelClick: ((TokenItemState) -> Unit)? = null
|
||||
}
|
||||
|
||||
@Immutable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,9 @@
|
|||
package com.tangem.domain.tokens.model.details
|
||||
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
sealed class NavigationAction {
|
||||
data object Staking : NavigationAction()
|
||||
data class YieldSupply(val isActive: Boolean) : NavigationAction()
|
||||
}
|
||||
|
|
@ -4,12 +4,14 @@ import com.tangem.core.decompose.factory.ComponentFactory
|
|||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
|
||||
interface TokenDetailsComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val currency: CryptoCurrency,
|
||||
val navigationAction: NavigationAction? = null,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, TokenDetailsComponent>
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
|||
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsModel
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.route.TokenDetailsBottomSheetConfig
|
||||
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen
|
||||
|
|
@ -76,6 +77,8 @@ internal class DefaultTokenDetailsComponent @AssistedInject constructor(
|
|||
params = YieldSupplyComponent.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.currency,
|
||||
handleNavigation = (params.navigationAction as? NavigationAction.YieldSupply)
|
||||
?.isActive,
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ import com.tangem.domain.tokens.model.TokenActionsState
|
|||
import com.tangem.domain.tokens.model.analytics.*
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.Companion.toReasonAnalyticsText
|
||||
import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent.DetailsScreenOpened.TokenBalance
|
||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.domain.transaction.error.AssociateAssetError
|
||||
import com.tangem.domain.transaction.error.IncompleteTransactionError
|
||||
|
|
@ -218,6 +219,7 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
updateContent()
|
||||
handleBalanceHiding()
|
||||
checkForActionUpdates()
|
||||
handleNavigationParam()
|
||||
}
|
||||
|
||||
fun onResume() {
|
||||
|
|
@ -1221,6 +1223,12 @@ internal class TokenDetailsModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun handleNavigationParam() {
|
||||
if (params.navigationAction is NavigationAction.Staking) {
|
||||
openStaking()
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val EXPRESS_STATUS_UPDATE_DELAY = 10_000L
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,12 @@ package com.tangem.feature.wallet.child.wallet.model.intents
|
|||
import arrow.core.getOrElse
|
||||
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.domain.models.account.Account
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.staking.YieldBalance
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.models.wallet.isLocked
|
||||
|
|
@ -14,6 +17,7 @@ import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase
|
|||
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
|
||||
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
|
||||
import com.tangem.domain.tokens.model.TokenActionsState
|
||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.feature.wallet.presentation.account.AccountDependencies
|
||||
|
|
@ -45,6 +49,8 @@ internal interface WalletContentClickIntents {
|
|||
|
||||
fun onTokenItemLongClick(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onApyLabelClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus)
|
||||
|
||||
fun onAccountExpandClick(account: Account)
|
||||
|
||||
fun onAccountCollapseClick(account: Account)
|
||||
|
|
@ -154,6 +160,42 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onApyLabelClick(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) {
|
||||
val navigationAction = if (currencyStatus.currency is CryptoCurrency.Token) {
|
||||
NavigationAction.YieldSupply(currencyStatus.value.yieldSupplyStatus?.isActive == true)
|
||||
} else {
|
||||
NavigationAction.Staking
|
||||
}
|
||||
val event = when (navigationAction) {
|
||||
is NavigationAction.YieldSupply -> {
|
||||
MainScreenAnalyticsEvent.ApyClicked(
|
||||
token = currencyStatus.currency.symbol,
|
||||
blockchain = currencyStatus.currency.network.name,
|
||||
action = "Earning",
|
||||
state = if (currencyStatus.value.yieldSupplyStatus?.isActive == true) {
|
||||
"Enabled"
|
||||
} else {
|
||||
"Disabled"
|
||||
},
|
||||
)
|
||||
}
|
||||
is NavigationAction.Staking -> {
|
||||
MainScreenAnalyticsEvent.ApyClicked(
|
||||
token = currencyStatus.currency.symbol,
|
||||
blockchain = currencyStatus.currency.network.name,
|
||||
action = "Staking",
|
||||
state = if (currencyStatus.value.yieldBalance is YieldBalance.Data) {
|
||||
"Enabled"
|
||||
} else {
|
||||
"Disabled"
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
analyticsEventHandler.send(event)
|
||||
router.openTokenDetails(userWalletId, currencyStatus, navigationAction)
|
||||
}
|
||||
|
||||
override fun onAccountExpandClick(account: Account) {
|
||||
val userWalletId = stateHolder.getSelectedWalletId()
|
||||
accountDependencies.expandedAccountsHolder.expandAccount(userWalletId, account.accountId)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.domain.models.wallet.UserWalletId
|
|||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.redux.StateDialog
|
||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.feature.wallet.navigation.WalletRoute
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
|
||||
|
|
@ -66,13 +67,18 @@ internal class DefaultWalletRouter @Inject constructor(
|
|||
urlOpener.openUrl(url)
|
||||
}
|
||||
|
||||
override fun openTokenDetails(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus) {
|
||||
override fun openTokenDetails(
|
||||
userWalletId: UserWalletId,
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
navigationAction: NavigationAction?,
|
||||
) {
|
||||
val networkAddress = currencyStatus.value.networkAddress
|
||||
if (networkAddress != null && networkAddress.defaultAddress.value.isNotEmpty()) {
|
||||
router.push(
|
||||
AppRoute.CurrencyDetails(
|
||||
userWalletId = userWalletId,
|
||||
currency = currencyStatus.currency,
|
||||
navigationAction = navigationAction,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pay.TangemPayDetailsConfig
|
||||
import com.tangem.domain.tokens.model.details.NavigationAction
|
||||
import com.tangem.domain.tokens.model.details.TokenAction
|
||||
import com.tangem.feature.wallet.navigation.WalletRoute
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
|
||||
|
|
@ -42,7 +43,11 @@ internal interface InnerWalletRouter {
|
|||
fun openUrl(url: String)
|
||||
|
||||
/** Open token details screen */
|
||||
fun openTokenDetails(userWalletId: UserWalletId, currencyStatus: CryptoCurrencyStatus)
|
||||
fun openTokenDetails(
|
||||
userWalletId: UserWalletId,
|
||||
currencyStatus: CryptoCurrencyStatus,
|
||||
navigationAction: NavigationAction? = null,
|
||||
)
|
||||
|
||||
/** Open stories screen */
|
||||
fun openStoriesScreen()
|
||||
|
|
|
|||
|
|
@ -49,12 +49,17 @@ internal class TokenListStateConverter(
|
|||
clickIntents.onTokenItemLongClick(selectedWallet.walletId, currencyStatus)
|
||||
}
|
||||
|
||||
private val onApyLabelClick: (currencyStatus: CryptoCurrencyStatus) -> Unit = { currencyStatus ->
|
||||
clickIntents.onApyLabelClick(selectedWallet.walletId, currencyStatus)
|
||||
}
|
||||
|
||||
private fun tokenStatusConverter(accountId: AccountId? = null) = TokenItemStateConverter(
|
||||
appCurrency = appCurrency,
|
||||
yieldModuleApyMap = yieldModuleApyMap,
|
||||
stakingApyMap = stakingApyMap,
|
||||
onItemClick = { _, status -> onTokenClick(accountId, status) },
|
||||
onItemLongClick = { _, status -> onTokenLongClick(accountId, status) },
|
||||
onApyLabelClick = { _, status -> onApyLabelClick(status) },
|
||||
)
|
||||
|
||||
override fun convert(value: WalletTokensListState): WalletTokensListState {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ interface YieldSupplyComponent : ComposableContentComponent {
|
|||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val handleNavigation: Boolean? = null,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, YieldSupplyComponent>
|
||||
|
|
|
|||
|
|
@ -91,6 +91,16 @@ internal class YieldSupplyModel @Inject constructor(
|
|||
|
||||
init {
|
||||
checkIfYieldSupplyIsAvailable()
|
||||
if (params.handleNavigation != null) {
|
||||
if (params.handleNavigation == true) {
|
||||
modelScope.launch {
|
||||
delay(timeMillis = 1000)
|
||||
bottomSheetNavigation.activate(Unit)
|
||||
}
|
||||
} else {
|
||||
onStartEarningClick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkIfYieldSupplyIsAvailable() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue