Updated on 2026-08-14

This commit is contained in:
Tangem 2025-12-30 11:46:31 +03:00
commit 14a7ac4f5e
431 changed files with 23077 additions and 3701 deletions

View file

@ -19,7 +19,7 @@ dependencies {
implementation(projects.domain.models)
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
implementation(projects.domain.staking.models)
implementation(projects.domain.staking)
implementation(projects.domain.markets.models)
implementation(projects.domain.onramp.models)
implementation(projects.domain.appCurrency.models)

View file

@ -7,6 +7,7 @@ import android.os.Bundle
import com.tangem.common.routing.bundle.RouteBundleParams
import com.tangem.common.routing.bundle.bundle
import com.tangem.common.routing.entity.InitScreenLaunchMode
import com.tangem.domain.staking.model.StakingIntegrationID
import com.tangem.core.decompose.navigation.Route
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.feedback.models.WalletMetaInfo
@ -209,8 +210,8 @@ sealed class AppRoute(val path: String) : Route {
data class Staking(
val userWalletId: UserWalletId,
val cryptoCurrency: CryptoCurrency,
val yieldId: String,
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrency.id.value}/$yieldId")
val integrationId: StakingIntegrationID,
) : AppRoute(path = "/staking/${userWalletId.stringValue}/${cryptoCurrency.id.value}/${integrationId.value}")
@Serializable
data class PushNotification(
@ -467,4 +468,7 @@ sealed class AppRoute(val path: String) : Route {
val cryptoCurrency: CryptoCurrency,
val apy: String,
) : AppRoute(path = "/yield_supply_active/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
@Serializable
data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId")
}

View file

@ -8,7 +8,7 @@ import com.tangem.domain.staking.model.ethpool.P2PEthPoolVault
import java.math.BigDecimal
/**
* Factory for creating mock P2P ETH Pool account responses for testing
* Factory for creating mock P2PEthPool account responses for testing
*/
object MockP2PEthPoolAccountResponseFactory {
@ -35,7 +35,7 @@ object MockP2PEthPoolAccountResponseFactory {
availableToUnstake = stakedAmount,
availableToWithdraw = BigDecimal.ZERO,
exitQueue = P2PEthPoolExitQueueDTO(
total = 0.0,
total = BigDecimal.ZERO,
requests = emptyList(),
),
)
@ -55,7 +55,7 @@ object MockP2PEthPoolAccountResponseFactory {
availableToUnstake = BigDecimal.ZERO,
availableToWithdraw = BigDecimal.ZERO,
exitQueue = P2PEthPoolExitQueueDTO(
total = 0.0,
total = BigDecimal.ZERO,
requests = emptyList(),
),
)

View file

@ -19,7 +19,6 @@ import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.quote.PriceChange
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.isZero
import java.math.BigDecimal
class AccountCryptoPortfolioItemStateConverter(
@ -41,14 +40,11 @@ class AccountCryptoPortfolioItemStateConverter(
private fun Account.CryptoPortfolio.mapToContentState(
fiatBalance: TotalFiatBalance.Loaded,
): TokenItemState.Content {
val subtitle2State = when (fiatBalance.amount.isZero()) {
true -> null
false -> priceChangeLce?.fold(
ifLoading = { priceChange -> priceChange?.toSubtitle2State() ?: Subtitle2State.Loading },
ifError = { null },
ifContent = { priceChange -> priceChange.toSubtitle2State() },
)
}
val subtitle2State = priceChangeLce?.fold(
ifLoading = { priceChange -> priceChange?.toSubtitle2State() ?: Subtitle2State.Loading },
ifError = { null },
ifContent = { priceChange -> priceChange.toSubtitle2State() },
)
return TokenItemState.Content(
id = account.accountId.toItemId(),
iconState = AccountIconItemStateConverter.convert(this),

View file

@ -6,6 +6,7 @@ import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -19,7 +20,6 @@ import androidx.compose.ui.unit.dp
import com.tangem.common.ui.account.AccountTitle
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.SpacerH
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.format.bigdecimal.crypto
@ -66,11 +66,14 @@ fun AmountBlock(amountState: AmountState, isClickDisabled: Boolean, isEditingDis
state = amountState.tokenIconState,
iconSize = 40.dp,
)
ResizableText(
Text(
text = firstAmount,
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
autoSize = TextAutoSize.StepBased(
maxFontSize = TangemTheme.typography.h2.fontSize,
),
maxLines = 1,
modifier = Modifier
.fillMaxWidth()

View file

@ -4,6 +4,7 @@ import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.text.TextAutoSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -18,7 +19,6 @@ import com.tangem.common.ui.account.AccountTitle
import com.tangem.common.ui.account.AccountTitleUM
import com.tangem.common.ui.amountScreen.models.AmountState
import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData
import com.tangem.core.ui.components.ResizableText
import com.tangem.core.ui.components.SpacerWMax
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
@ -128,21 +128,23 @@ private fun AmountBlockV2(
.padding(top = 8.dp)
.weight(1f),
) {
ResizableText(
Text(
text = firstAmount,
style = TangemTheme.typography.h2,
color = TangemTheme.colors.text.primary1,
maxLines = 1,
autoSize = TextAutoSize.StepBased(maxFontSize = TangemTheme.typography.h2.fontSize),
modifier = Modifier.testTag(BaseAmountBlockTestTags.PRIMARY_AMOUNT),
)
Row(
horizontalArrangement = Arrangement.spacedBy(4.dp),
) {
ResizableText(
Text(
text = secondAmount,
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
maxLines = 1,
autoSize = TextAutoSize.StepBased(maxFontSize = TangemTheme.typography.body2.fontSize),
modifier = Modifier.testTag(BaseAmountBlockTestTags.SECONDARY_AMOUNT),
)
extraContent()

View file

@ -1,6 +1,7 @@
package com.tangem.common.ui.news
import android.content.res.Configuration
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
@ -12,6 +13,9 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -82,6 +86,7 @@ private fun TrendingArticle(articleConfigUM: ArticleConfigUM) {
style = TangemTheme.typography.h3,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
textAlign = TextAlign.Center,
)
SpacerH(8.dp)
@ -100,6 +105,41 @@ private fun TrendingArticle(articleConfigUM: ArticleConfigUM) {
}
}
@Composable
fun ShowMoreArticlesCard(modifier: Modifier = Modifier, onClick: () -> Unit) {
BlockCard(
modifier = modifier,
onClick = onClick,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxSize()
.padding(vertical = 31.dp, horizontal = 12.dp),
) {
Image(
imageVector = ImageVector.vectorResource(R.drawable.ic_show_more_news_48),
contentDescription = stringResourceSafe(R.string.common_show_more),
)
SpacerH(16.dp)
Text(
text = stringResourceSafe(R.string.news_all_news),
style = TangemTheme.typography.subtitle1,
color = TangemTheme.colors.text.primary1,
)
Text(
text = stringResourceSafe(R.string.news_stay_in_the_loop),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
@Composable
private fun DefaultArticle(articleConfigUM: ArticleConfigUM) {
Column(modifier = Modifier.padding(12.dp)) {

View file

@ -1,7 +1,10 @@
package com.tangem.common.ui.news
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -9,6 +12,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithCache
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.res.TangemTheme
@ -49,6 +53,8 @@ internal fun ArticleInfo(score: Float, createdAt: String, modifier: Modifier = M
Text(
text = createdAt,
style = TangemTheme.typography.subtitle2,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = TangemTheme.colors.text.secondary,
)
}

View file

@ -41,8 +41,9 @@ fun TrendingLoadingArticle(modifier: Modifier = Modifier) {
}
@Composable
fun DefaultLoadingArticle() {
fun DefaultLoadingArticle(modifier: Modifier = Modifier) {
BlockCard(
modifier = modifier,
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
) {
Column(modifier = Modifier.padding(12.dp)) {

View file

@ -23,7 +23,8 @@ import com.tangem.domain.models.currency.yieldSupplyKey
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.model.StakingOption
import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.common.RewardInfo
import com.tangem.domain.staking.model.common.RewardType
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.StringsSigns.DASH_SIGN
@ -251,9 +252,9 @@ class TokenItemStateConverter(
stakingApyMap = stakingApyMap,
)
val rewardTypeRes = when (stakingInfo.rewardType) {
Yield.RewardType.APR -> R.string.staking_apr_earn_badge
Yield.RewardType.UNKNOWN,
Yield.RewardType.APY,
RewardType.APR -> R.string.staking_apr_earn_badge
RewardType.UNKNOWN,
RewardType.APY,
null,
-> R.string.yield_module_earn_badge
}
@ -285,10 +286,11 @@ class TokenItemStateConverter(
val stakeKitBalance = stakingBalance as? StakingBalance.Data.StakeKit
val rateInfo = when (val stakingOptions = stakingAvailability.option) {
is StakingOption.P2P -> {
// P2P or no balance: use preferred validators
// TODO add p2p logic
null
is StakingOption.P2PEthPool -> {
RewardInfo(
rate = stakingOptions.apy,
type = RewardType.APY,
)
}
is StakingOption.StakeKit -> if (stakeKitBalance != null) {
val validatorsByAddress = stakingOptions.yield.validators.associateBy { it.address }
@ -469,7 +471,7 @@ class TokenItemStateConverter(
private data class StakingLocalInfo(
val rate: BigDecimal?,
val isActive: Boolean,
val rewardType: Yield.RewardType?,
val rewardType: RewardType?,
)
private data class EarnApyInfo(