Updated on 2026-08-14
This commit is contained in:
commit
c3214d41f8
25 changed files with 391 additions and 187 deletions
|
|
@ -691,9 +691,7 @@ internal class ChildFactory @Inject constructor(
|
|||
deeplink = mode.deeplink,
|
||||
)
|
||||
is AppRoute.TangemPayOnboarding.Mode.FromBannerInSettings -> FromBannerInSettings
|
||||
is AppRoute.TangemPayOnboarding.Mode.FromBannerOnMain -> FromBannerOnMain(
|
||||
userWalletId = mode.userWalletId,
|
||||
)
|
||||
is AppRoute.TangemPayOnboarding.Mode.FromBannerOnMain -> FromBannerOnMain
|
||||
},
|
||||
componentFactory = tangemPayOnboardingComponentFactory,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -444,9 +444,7 @@ sealed class AppRoute(val path: String) : Route {
|
|||
) : Mode()
|
||||
|
||||
@Serializable
|
||||
data class FromBannerOnMain(
|
||||
val userWalletId: UserWalletId,
|
||||
) : Mode()
|
||||
data object FromBannerOnMain : Mode()
|
||||
|
||||
@Serializable
|
||||
data object FromBannerInSettings : Mode()
|
||||
|
|
|
|||
|
|
@ -2,7 +2,10 @@ package com.tangem.common.ui.news
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CardColors
|
||||
import androidx.compose.material3.Text
|
||||
|
|
@ -11,6 +14,8 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.ContentScale
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
|
@ -21,15 +26,12 @@ import com.tangem.core.ui.components.SpacerH
|
|||
import com.tangem.core.ui.components.SpacerHMax
|
||||
import com.tangem.core.ui.components.block.BlockCard
|
||||
import com.tangem.core.ui.components.block.TangemBlockCardColors
|
||||
import com.tangem.core.ui.components.label.Label
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.utils.StringsSigns
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.collections.immutable.toImmutableSet
|
||||
|
|
@ -65,52 +67,61 @@ private fun TrendingArticle(
|
|||
colors: CardColors,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
Box(
|
||||
modifier = modifier
|
||||
.clip(TangemTheme.shapes.roundedCornersXMedium)
|
||||
.background(colors.containerColor)
|
||||
.clickable { onArticleClick() }
|
||||
.padding(vertical = 24.dp, horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
.clickable { onArticleClick() },
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.icon.accent.copy(alpha = 0.1f),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
text = stringResourceSafe(R.string.feed_trending_now),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
Image(
|
||||
modifier = Modifier.matchParentSize(),
|
||||
painter = painterResource(R.drawable.bg_trending_article),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.FillWidth,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 24.dp, horizontal = 16.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.icon.accent.copy(alpha = 0.1f),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
)
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
text = stringResourceSafe(R.string.feed_trending_now),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
|
||||
SpacerH(12.dp)
|
||||
SpacerH(12.dp)
|
||||
|
||||
Text(
|
||||
text = articleConfigUM.title,
|
||||
color = if (articleConfigUM.isViewed) {
|
||||
TangemTheme.colors.text.tertiary
|
||||
} else {
|
||||
TangemTheme.colors.text.primary1
|
||||
},
|
||||
style = TangemTheme.typography.h3,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
Text(
|
||||
text = articleConfigUM.title,
|
||||
color = if (articleConfigUM.isViewed) {
|
||||
TangemTheme.colors.text.tertiary
|
||||
} else {
|
||||
TangemTheme.colors.text.primary1
|
||||
},
|
||||
style = TangemTheme.typography.h3,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
|
||||
SpacerH(8.dp)
|
||||
SpacerH(8.dp)
|
||||
|
||||
ArticleInfo(
|
||||
score = articleConfigUM.score,
|
||||
createdAt = articleConfigUM.createdAt.resolveReference(),
|
||||
)
|
||||
ArticleInfo(
|
||||
score = articleConfigUM.score,
|
||||
createdAt = articleConfigUM.createdAt.resolveReference(),
|
||||
)
|
||||
|
||||
SpacerH(32.dp)
|
||||
SpacerH(32.dp)
|
||||
|
||||
Tags(
|
||||
modifier = Modifier.padding(horizontal = 46.dp),
|
||||
tags = articleConfigUM.tags.toImmutableList(),
|
||||
)
|
||||
Tags(
|
||||
modifier = Modifier.padding(horizontal = 46.dp),
|
||||
tags = articleConfigUM.tags.toImmutableList(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,46 +193,14 @@ private fun DefaultArticle(
|
|||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
SpacerHMax(modifier = Modifier.padding(bottom = 16.dp))
|
||||
SpacerH(16.dp)
|
||||
|
||||
SpacerHMax()
|
||||
|
||||
Tags(tags = articleConfigUM.tags.toImmutableList())
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Tags(tags: ImmutableList<LabelUM>, modifier: Modifier = Modifier) {
|
||||
FlowRow(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
maxItemsInEachRow = MAX_TAGS_COUNT_TO_SHOW + 1,
|
||||
maxLines = 1,
|
||||
itemVerticalAlignment = Alignment.Bottom,
|
||||
) {
|
||||
val displayedTags = if (tags.size > MAX_TAGS_COUNT_TO_SHOW) {
|
||||
tags.take(MAX_TAGS_COUNT_TO_SHOW)
|
||||
} else {
|
||||
tags
|
||||
}
|
||||
|
||||
displayedTags.forEach { tag ->
|
||||
Label(state = tag)
|
||||
}
|
||||
|
||||
if (tags.size > MAX_TAGS_COUNT_TO_SHOW) {
|
||||
val remainingItems = tags.size - MAX_TAGS_COUNT_TO_SHOW
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("${StringsSigns.PLUS}$remainingItems"),
|
||||
maxLines = 1,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private const val MAX_TAGS_COUNT_TO_SHOW = 3
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
|
|
|
|||
128
common/ui/src/main/java/com/tangem/common/ui/news/Tags.kt
Normal file
128
common/ui/src/main/java/com/tangem/common/ui/news/Tags.kt
Normal file
|
|
@ -0,0 +1,128 @@
|
|||
package com.tangem.common.ui.news
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.Placeable
|
||||
import androidx.compose.ui.layout.SubcomposeLayout
|
||||
import androidx.compose.ui.layout.SubcomposeMeasureScope
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.label.Label
|
||||
import com.tangem.core.ui.components.label.entity.LabelUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.utils.StringsSigns
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
private val TAG_SPACING = 4.dp
|
||||
|
||||
/**
|
||||
* A layout that displays a list of tags, automatically calculating how many can fit
|
||||
* in a single line. If not all tags fit, it shows an overflow indicator (e.g., "+N").
|
||||
*
|
||||
* @param tags The immutable list of [LabelUM] to display.
|
||||
* @param modifier The modifier to be applied to the layout.
|
||||
*/
|
||||
@Composable
|
||||
internal fun Tags(tags: ImmutableList<LabelUM>, modifier: Modifier = Modifier) {
|
||||
if (tags.isEmpty()) return
|
||||
|
||||
SubcomposeLayout(modifier = modifier) { constraints ->
|
||||
val spacingPx = TAG_SPACING.roundToPx()
|
||||
|
||||
val tagPlaceables = subcompose(ContentSlot.Tags) {
|
||||
tags.forEach { tag ->
|
||||
Label(state = tag)
|
||||
}
|
||||
}.map { it.measure(constraints) }
|
||||
|
||||
val layoutInfo = calculateLayoutInfo(
|
||||
maxWidth = constraints.maxWidth,
|
||||
spacingPx = spacingPx,
|
||||
tagPlaceables = tagPlaceables,
|
||||
)
|
||||
|
||||
val overflowPlaceable = layoutInfo.overflowCount.takeIf { it > 0 }?.let {
|
||||
subcompose(ContentSlot.Overflow) {
|
||||
OverflowLabel(count = it)
|
||||
}.first().measure(constraints)
|
||||
}
|
||||
|
||||
val visiblePlaceables = tagPlaceables.take(layoutInfo.visibleCount)
|
||||
val allVisibleItems = visiblePlaceables + listOfNotNull(overflowPlaceable)
|
||||
|
||||
val width = calculateRowWidth(
|
||||
placeables = allVisibleItems,
|
||||
spacingPx = spacingPx,
|
||||
).coerceIn(constraints.minWidth, constraints.maxWidth)
|
||||
|
||||
val height = allVisibleItems.maxOfOrNull { it.height } ?: 0
|
||||
|
||||
layout(width = width, height = height) {
|
||||
var xPosition = 0
|
||||
allVisibleItems.forEachIndexed { index, placeable ->
|
||||
if (index > 0) {
|
||||
xPosition += spacingPx
|
||||
}
|
||||
val yPosition = height - placeable.height
|
||||
placeable.placeRelative(xPosition, yPosition)
|
||||
xPosition += placeable.width
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates how many tags can be displayed and how many are overflowing.
|
||||
*/
|
||||
private fun SubcomposeMeasureScope.calculateLayoutInfo(
|
||||
maxWidth: Int,
|
||||
spacingPx: Int,
|
||||
tagPlaceables: List<Placeable>,
|
||||
): TagsLayoutInfo {
|
||||
val totalWidthWithoutOverflow = calculateRowWidth(tagPlaceables, spacingPx)
|
||||
|
||||
if (totalWidthWithoutOverflow <= maxWidth) {
|
||||
return TagsLayoutInfo(visibleCount = tagPlaceables.size, overflowCount = 0)
|
||||
}
|
||||
|
||||
for (count in tagPlaceables.indices.reversed()) {
|
||||
val visibleTags = tagPlaceables.take(count)
|
||||
val overflowCount = tagPlaceables.size - count
|
||||
|
||||
val tempOverflowPlaceable = subcompose("overflow_temp_$overflowCount") {
|
||||
OverflowLabel(count = overflowCount)
|
||||
}.first().measure(Constraints())
|
||||
|
||||
val widthWithOverflow = calculateRowWidth(
|
||||
placeables = visibleTags + tempOverflowPlaceable,
|
||||
spacingPx = spacingPx,
|
||||
)
|
||||
|
||||
if (widthWithOverflow <= maxWidth) {
|
||||
return TagsLayoutInfo(visibleCount = count, overflowCount = overflowCount)
|
||||
}
|
||||
}
|
||||
|
||||
return TagsLayoutInfo(visibleCount = 0, overflowCount = tagPlaceables.size)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OverflowLabel(count: Int) {
|
||||
Label(
|
||||
state = LabelUM(
|
||||
text = TextReference.Str("${StringsSigns.PLUS}$count"),
|
||||
maxLines = 1,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun calculateRowWidth(placeables: List<Placeable>, spacingPx: Int): Int {
|
||||
if (placeables.isEmpty()) return 0
|
||||
val tagsWidth = placeables.sumOf { it.width }
|
||||
val spacingWidth = spacingPx * (placeables.size - 1).coerceAtLeast(0)
|
||||
return tagsWidth + spacingWidth
|
||||
}
|
||||
|
||||
private data class TagsLayoutInfo(val visibleCount: Int, val overflowCount: Int)
|
||||
|
||||
private enum class ContentSlot { Tags, Overflow }
|
||||
|
|
@ -120,7 +120,7 @@ fun TokenItem(
|
|||
composables.icon.invoke(
|
||||
Modifier
|
||||
.layoutId(layoutId = LayoutId.ICON)
|
||||
.padding(end = TangemTheme.dimens.spacing8)
|
||||
.padding(end = TangemTheme.dimens.spacing12)
|
||||
.testTag(TokenElementsTestTags.TOKEN_ICON),
|
||||
)
|
||||
} else {
|
||||
|
|
@ -128,7 +128,7 @@ fun TokenItem(
|
|||
state = state.iconState,
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = LayoutId.ICON)
|
||||
.padding(end = TangemTheme.dimens.spacing8)
|
||||
.padding(end = TangemTheme.dimens.spacing12)
|
||||
.testTag(TokenElementsTestTags.TOKEN_ICON),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ import androidx.compose.animation.core.tween
|
|||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.LocalRippleConfiguration
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -32,6 +34,7 @@ import com.tangem.core.ui.components.token.state.TokenItemState
|
|||
import com.tangem.core.ui.components.tokenlist.internal.GroupTitleItem
|
||||
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
|
||||
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
|
||||
import com.tangem.core.ui.extensions.orMaskWithStars
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.ProvideSharedTransitionScope
|
||||
|
|
@ -71,13 +74,13 @@ fun PortfolioListItem(state: TokensListItemUM.Portfolio, isBalanceHidden: Boolea
|
|||
ProvideSharedTransitionScope(modifier) {
|
||||
val iconSharedContentState = rememberSharedContentState(key = "icon_${state.id}")
|
||||
val titleSharedContentState = rememberSharedContentState(key = "title_${state.id}")
|
||||
val boundsTransform = BoundsTransform { _, _ -> tween(350) }
|
||||
val boundsTransform = BoundsTransform { _, _ -> tween(250) }
|
||||
|
||||
AnimatedContent(
|
||||
targetState = state.isExpanded,
|
||||
transitionSpec = {
|
||||
fadeIn(animationSpec = tween(350, delayMillis = 90))
|
||||
.togetherWith(fadeOut(animationSpec = tween(350)))
|
||||
fadeIn(animationSpec = tween(250, delayMillis = 90))
|
||||
.togetherWith(fadeOut(animationSpec = tween(250)))
|
||||
},
|
||||
) { isExpanded ->
|
||||
val animatedContentScope = this
|
||||
|
|
@ -116,7 +119,7 @@ fun PortfolioListItem(state: TokensListItemUM.Portfolio, isBalanceHidden: Boolea
|
|||
|
||||
val textSize by animateFloatAsState(
|
||||
targetValue = textStyle.fontSize.value,
|
||||
animationSpec = tween(350),
|
||||
animationSpec = tween(250),
|
||||
)
|
||||
|
||||
TokenTitle(
|
||||
|
|
@ -133,32 +136,34 @@ fun PortfolioListItem(state: TokensListItemUM.Portfolio, isBalanceHidden: Boolea
|
|||
},
|
||||
)
|
||||
|
||||
if (isExpanded) {
|
||||
ExpandedPortfolioHeader(
|
||||
state = state.tokenItemUM,
|
||||
isCollapsable = state.isCollapsable,
|
||||
composables = composables,
|
||||
balanceTextModifier = Modifier
|
||||
.animateEnterExit(
|
||||
enter = fadeIn(tween(350)) +
|
||||
slideInHorizontally(
|
||||
tween(350),
|
||||
initialOffsetX = { fullWidth -> fullWidth * 2 },
|
||||
),
|
||||
exit = fadeOut(
|
||||
tween(350),
|
||||
) + slideOutHorizontally(
|
||||
tween(350),
|
||||
targetOffsetX = { fullWidth -> fullWidth * 2 },
|
||||
CompositionLocalProvider(LocalRippleConfiguration provides null) {
|
||||
if (isExpanded) {
|
||||
ExpandedPortfolioHeader(
|
||||
state = state.tokenItemUM,
|
||||
isCollapsable = state.isCollapsable,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
composables = composables,
|
||||
balanceTextModifier = Modifier
|
||||
.animateEnterExit(
|
||||
enter = fadeIn(tween(250)) +
|
||||
slideInHorizontally(
|
||||
tween(250),
|
||||
initialOffsetX = { fullWidth -> fullWidth },
|
||||
),
|
||||
exit = fadeOut(tween(250)) +
|
||||
slideOutHorizontally(
|
||||
tween(250),
|
||||
targetOffsetX = { fullWidth -> fullWidth },
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
TokenItem(
|
||||
state = state.tokenItemUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
composables = composables,
|
||||
)
|
||||
)
|
||||
} else {
|
||||
TokenItem(
|
||||
state = state.tokenItemUM,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
composables = composables,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -180,6 +185,7 @@ fun PortfolioTokensListItem(state: PortfolioTokensListItemUM, isBalanceHidden: B
|
|||
@Composable
|
||||
fun ExpandedPortfolioHeader(
|
||||
state: TokenItemState,
|
||||
isBalanceHidden: Boolean,
|
||||
isCollapsable: Boolean,
|
||||
composables: TokenItemComposables?,
|
||||
modifier: Modifier = Modifier,
|
||||
|
|
@ -191,8 +197,10 @@ fun ExpandedPortfolioHeader(
|
|||
.fillMaxWidth()
|
||||
.clickable(onClick = { state.onItemClick?.invoke(state) })
|
||||
.padding(
|
||||
vertical = if (composables != null) 8.dp else 4.dp,
|
||||
horizontal = 12.dp,
|
||||
top = 12.dp,
|
||||
bottom = 4.dp,
|
||||
start = 12.dp,
|
||||
end = 12.dp,
|
||||
),
|
||||
) {
|
||||
if (composables != null) {
|
||||
|
|
@ -227,12 +235,19 @@ fun ExpandedPortfolioHeader(
|
|||
modifier = Modifier.weight(1f),
|
||||
) {
|
||||
if (composables != null) {
|
||||
composables.title.invoke(Modifier.alignByBaseline())
|
||||
composables.title.invoke(
|
||||
Modifier
|
||||
.padding(vertical = 2.dp)
|
||||
.alignByBaseline(),
|
||||
)
|
||||
} else {
|
||||
when (val titleState = state.titleState) {
|
||||
TokenItemState.TitleState.Loading -> Unit
|
||||
TokenItemState.TitleState.Locked -> Unit
|
||||
is TokenItemState.TitleState.Content -> Text(
|
||||
modifier = Modifier
|
||||
.padding(vertical = 2.dp)
|
||||
.alignByBaseline(),
|
||||
text = titleState.text.resolveReference(),
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
@ -249,10 +264,10 @@ fun ExpandedPortfolioHeader(
|
|||
}
|
||||
|
||||
Text(
|
||||
text = text.orEmpty(),
|
||||
text = text.orEmpty().orMaskWithStars(isBalanceHidden),
|
||||
modifier = balanceTextModifier
|
||||
.alignByBaseline()
|
||||
.padding(horizontal = 8.dp),
|
||||
.padding(horizontal = 4.dp),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.caption1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,9 @@
|
|||
package com.tangem.core.ui.utils
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.BoundsTransform
|
||||
import androidx.compose.animation.EnterTransition
|
||||
import androidx.compose.animation.ExitTransition
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.foundation.layout.Box
|
||||
|
|
@ -7,6 +11,13 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.Shape
|
||||
import androidx.compose.ui.layout.LayoutCoordinates
|
||||
import androidx.compose.ui.layout.Placeable
|
||||
import androidx.compose.ui.unit.Density
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
|
||||
@Composable
|
||||
fun TangemSharedTransitionLayout(
|
||||
|
|
@ -31,6 +42,78 @@ fun ProvideSharedTransitionScope(modifier: Modifier = Modifier, content: @Compos
|
|||
}
|
||||
}
|
||||
|
||||
private val LocalSharedTransitionScope = staticCompositionLocalOf<SharedTransitionScope> {
|
||||
error("No SharedTransitionScope provided")
|
||||
private val LocalSharedTransitionScope = staticCompositionLocalOf<SharedTransitionScope> { StubSharedTransitionScope() }
|
||||
|
||||
private class StubSharedTransitionScope : SharedTransitionScope {
|
||||
override val isTransitionActive: Boolean = false
|
||||
|
||||
override val Placeable.PlacementScope.lookaheadScopeCoordinates: LayoutCoordinates
|
||||
get() = error("No LookaheadScopeCoordinates available in StubSharedTransitionScope")
|
||||
|
||||
override fun Modifier.skipToLookaheadSize(enabled: () -> Boolean): Modifier {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun Modifier.renderInSharedTransitionScopeOverlay(
|
||||
zIndexInOverlay: Float,
|
||||
renderInOverlay: () -> Boolean,
|
||||
): Modifier {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun Modifier.sharedElement(
|
||||
sharedContentState: SharedTransitionScope.SharedContentState,
|
||||
animatedVisibilityScope: AnimatedVisibilityScope,
|
||||
boundsTransform: BoundsTransform,
|
||||
placeholderSize: SharedTransitionScope.PlaceholderSize,
|
||||
renderInOverlayDuringTransition: Boolean,
|
||||
zIndexInOverlay: Float,
|
||||
clipInOverlayDuringTransition: SharedTransitionScope.OverlayClip,
|
||||
): Modifier {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun Modifier.sharedBounds(
|
||||
sharedContentState: SharedTransitionScope.SharedContentState,
|
||||
animatedVisibilityScope: AnimatedVisibilityScope,
|
||||
enter: EnterTransition,
|
||||
exit: ExitTransition,
|
||||
boundsTransform: BoundsTransform,
|
||||
resizeMode: SharedTransitionScope.ResizeMode,
|
||||
placeholderSize: SharedTransitionScope.PlaceholderSize,
|
||||
renderInOverlayDuringTransition: Boolean,
|
||||
zIndexInOverlay: Float,
|
||||
clipInOverlayDuringTransition: SharedTransitionScope.OverlayClip,
|
||||
): Modifier {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun Modifier.sharedElementWithCallerManagedVisibility(
|
||||
sharedContentState: SharedTransitionScope.SharedContentState,
|
||||
visible: Boolean,
|
||||
boundsTransform: BoundsTransform,
|
||||
placeholderSize: SharedTransitionScope.PlaceholderSize,
|
||||
renderInOverlayDuringTransition: Boolean,
|
||||
zIndexInOverlay: Float,
|
||||
clipInOverlayDuringTransition: SharedTransitionScope.OverlayClip,
|
||||
): Modifier {
|
||||
return this
|
||||
}
|
||||
|
||||
override fun OverlayClip(clipShape: Shape): SharedTransitionScope.OverlayClip {
|
||||
return object : SharedTransitionScope.OverlayClip {
|
||||
override fun getClipPath(
|
||||
sharedContentState: SharedTransitionScope.SharedContentState,
|
||||
bounds: Rect,
|
||||
layoutDirection: LayoutDirection,
|
||||
density: Density,
|
||||
): Path? {
|
||||
return Path()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun LayoutCoordinates.toLookaheadCoordinates(): LayoutCoordinates {
|
||||
return this
|
||||
}
|
||||
}
|
||||
BIN
core/ui/src/main/res/drawable/bg_trending_article.webp
Normal file
BIN
core/ui/src/main/res/drawable/bg_trending_article.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 23 KiB |
|
|
@ -12,8 +12,7 @@ import kotlinx.serialization.Serializable
|
|||
data class AvailableToAddData(
|
||||
val availableToAddWallets: Map<UserWalletId, AvailableToAddWallet>,
|
||||
) {
|
||||
val isAvailableToAdd: Boolean
|
||||
get() = availableToAddWallets.isNotEmpty()
|
||||
val isAvailableToAdd: Boolean = availableToAddWallets.values.any { item -> item.isAvailableToAdd }
|
||||
val isSinglePortfolio: Boolean
|
||||
get() = availableToAddWallets.size == 1 && availableToAddWallets.values.first().accounts.size == 1
|
||||
}
|
||||
|
|
@ -23,7 +22,9 @@ data class AvailableToAddWallet(
|
|||
val accounts: List<AccountStatus>,
|
||||
val availableNetworks: Set<TokenMarketInfo.Network>,
|
||||
val availableToAddAccounts: Map<AccountId, AvailableToAddAccount>,
|
||||
)
|
||||
) {
|
||||
val isAvailableToAdd: Boolean = availableToAddAccounts.values.any { item -> item.isAvailableToAdd }
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class AvailableToAddAccount(
|
||||
|
|
@ -38,6 +39,8 @@ data class AvailableToAddAccount(
|
|||
.filter { available -> addedNetworks.none { added -> added.backendId == available.networkId } }
|
||||
.toSet()
|
||||
|
||||
val isAvailableToAdd: Boolean = availableToAddNetworks.isNotEmpty()
|
||||
|
||||
val addedMarketNetworks: Set<TokenMarketInfo.Network> = availableNetworks
|
||||
.filter { available -> addedNetworks.any { added -> added.backendId == available.networkId } }
|
||||
.toSet()
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ internal class AvailableToAddDataConverter @Inject constructor(
|
|||
val availableToAddAccount = accountStatus.getAvailableToAddAccount(wallet) ?: return@mapNotNull null
|
||||
accountStatus.account.accountId to availableToAddAccount
|
||||
}
|
||||
.filter { (_, account) -> account.availableToAddNetworks.isNotEmpty() }
|
||||
.toMap()
|
||||
return AvailableToAddWallet(
|
||||
userWallet = wallet,
|
||||
|
|
|
|||
|
|
@ -344,9 +344,10 @@ internal class AddToPortfolioModel @Inject constructor(
|
|||
portfolioSelectorController.isEnabled.value = isEnabled@{ userWallet, accountStatus ->
|
||||
val availableWallet = state.availableToAddData.availableToAddWallets[userWallet.walletId]
|
||||
?: return@isEnabled false
|
||||
val availableAccount =
|
||||
val isAvailableAccount =
|
||||
availableWallet.availableToAddAccounts[accountStatus.account.accountId]
|
||||
return@isEnabled availableAccount != null
|
||||
?.isAvailableToAdd == true
|
||||
return@isEnabled isAvailableAccount
|
||||
}
|
||||
AddToPortfolioManager.State.Init,
|
||||
AddToPortfolioManager.State.NothingToAdd,
|
||||
|
|
|
|||
|
|
@ -25,15 +25,19 @@ internal class UpdateGlobalFeedStateTransformer(
|
|||
prevState.news.newsUMState == NewsUMState.ERROR
|
||||
|
||||
val hasLoadingInCharts = loadingStatesByOrder.values.any { it }
|
||||
val hasErrorInCharts = errorStatesByOrder.values.any { it != null }
|
||||
val hasAllErrorInCharts = errorStatesByOrder.values.isNotEmpty() && errorStatesByOrder.values.all { it != null }
|
||||
val hasErrorInMarketChart = errorStatesByOrder[SortByTypeUM.Rating] != null
|
||||
val hasErrorInMarketPulseChart = errorStatesByOrder
|
||||
.any { it.key != SortByTypeUM.Rating && it.value != null }
|
||||
|
||||
val areAllChartsLoading = loadingStatesByOrder.values.isNotEmpty() && loadingStatesByOrder.values.all { it }
|
||||
val areAllChartsError = hasAllErrorInCharts || hasErrorInCharts && hasLoadingInCharts
|
||||
val areAllChartsError = when {
|
||||
hasErrorInMarketChart && hasErrorInMarketPulseChart -> true
|
||||
hasErrorInMarketPulseChart && hasLoadingInCharts -> true
|
||||
hasErrorInMarketChart && hasLoadingInCharts -> true
|
||||
else -> false
|
||||
}
|
||||
|
||||
val newGlobalState = when {
|
||||
isNewsLoading && areAllChartsError -> GlobalFeedState.Loading
|
||||
isNewsLoading && areAllChartsLoading -> GlobalFeedState.Loading
|
||||
isNewsError && areAllChartsLoading -> GlobalFeedState.Loading
|
||||
isNewsError && areAllChartsError -> {
|
||||
if (previousGlobalState !is GlobalFeedState.Error) {
|
||||
|
|
@ -43,18 +47,11 @@ internal class UpdateGlobalFeedStateTransformer(
|
|||
onRetryClicked = onRetryClicked,
|
||||
)
|
||||
}
|
||||
isNewsLoading && areAllChartsError -> GlobalFeedState.Loading
|
||||
isNewsLoading && areAllChartsLoading -> GlobalFeedState.Loading
|
||||
else -> GlobalFeedState.Content
|
||||
}
|
||||
|
||||
return prevState.copy(
|
||||
globalState = newGlobalState,
|
||||
news = prevState.news.copy(
|
||||
newsUMState = when {
|
||||
isNewsError -> NewsUMState.ERROR
|
||||
else -> NewsUMState.CONTENT
|
||||
},
|
||||
),
|
||||
)
|
||||
return prevState.copy(globalState = newGlobalState)
|
||||
}
|
||||
|
||||
private fun sendErrorAnalytics() {
|
||||
|
|
|
|||
|
|
@ -423,7 +423,7 @@ private fun Charts(
|
|||
) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.action),
|
||||
colors = TangemBlockCardColors.copy(containerColor = TangemTheme.colors.background.primary),
|
||||
) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
when (marketChart) {
|
||||
|
|
|
|||
|
|
@ -12,8 +12,7 @@ import kotlinx.serialization.Serializable
|
|||
internal data class AvailableToAddData(
|
||||
val availableToAddWallets: Map<UserWalletId, AvailableToAddWallet>,
|
||||
) {
|
||||
val isAvailableToAdd: Boolean
|
||||
get() = availableToAddWallets.isNotEmpty()
|
||||
val isAvailableToAdd: Boolean = availableToAddWallets.values.any { item -> item.isAvailableToAdd }
|
||||
val isSinglePortfolio: Boolean
|
||||
get() = availableToAddWallets.size == 1 && availableToAddWallets.values.first().accounts.size == 1
|
||||
}
|
||||
|
|
@ -23,7 +22,9 @@ internal data class AvailableToAddWallet(
|
|||
val accounts: List<AccountStatus>,
|
||||
val availableNetworks: Set<TokenMarketInfo.Network>,
|
||||
val availableToAddAccounts: Map<AccountId, AvailableToAddAccount>,
|
||||
)
|
||||
) {
|
||||
val isAvailableToAdd: Boolean = availableToAddAccounts.values.any { item -> item.isAvailableToAdd }
|
||||
}
|
||||
|
||||
@Serializable
|
||||
internal data class AvailableToAddAccount(
|
||||
|
|
@ -31,6 +32,7 @@ internal data class AvailableToAddAccount(
|
|||
val availableNetworks: Set<TokenMarketInfo.Network>,
|
||||
val addedNetworks: Set<Network>,
|
||||
) {
|
||||
|
||||
val isSingleNetwork: Boolean
|
||||
get() = availableNetworks.size == 1
|
||||
|
||||
|
|
@ -38,6 +40,8 @@ internal data class AvailableToAddAccount(
|
|||
.filter { available -> addedNetworks.none { added -> added.backendId == available.networkId } }
|
||||
.toSet()
|
||||
|
||||
val isAvailableToAdd: Boolean = availableToAddNetworks.isNotEmpty()
|
||||
|
||||
val addedMarketNetworks: Set<TokenMarketInfo.Network> = availableNetworks
|
||||
.filter { available -> addedNetworks.any { added -> added.backendId == available.networkId } }
|
||||
.toSet()
|
||||
|
|
|
|||
|
|
@ -70,7 +70,6 @@ internal class AvailableToAddDataConverter @Inject constructor(
|
|||
val availableToAddAccount = accountStatus.getAvailableToAddAccount(wallet) ?: return@mapNotNull null
|
||||
accountStatus.account.accountId to availableToAddAccount
|
||||
}
|
||||
.filter { (_, account) -> account.availableToAddNetworks.isNotEmpty() }
|
||||
.toMap()
|
||||
return AvailableToAddWallet(
|
||||
userWallet = wallet,
|
||||
|
|
|
|||
|
|
@ -334,9 +334,10 @@ internal class AddToPortfolioModel @Inject constructor(
|
|||
portfolioSelectorController.isEnabled.value = isEnabled@{ userWallet, accountStatus ->
|
||||
val availableWallet = state.availableToAddData.availableToAddWallets[userWallet.walletId]
|
||||
?: return@isEnabled false
|
||||
val availableAccount =
|
||||
val isAvailableAccount =
|
||||
availableWallet.availableToAddAccounts[accountStatus.account.accountId]
|
||||
return@isEnabled availableAccount != null
|
||||
?.isAvailableToAdd == true
|
||||
return@isEnabled isAvailableAccount
|
||||
}
|
||||
AddToPortfolioManager.State.Init,
|
||||
AddToPortfolioManager.State.NothingToAdd,
|
||||
|
|
|
|||
|
|
@ -1536,9 +1536,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
swapAmount = amount,
|
||||
quotesLoadedState = swapState,
|
||||
isAllowedToSpend = isAllowedToSpend,
|
||||
spenderAddress = requireNotNull(quoteModel.allowanceContract) {
|
||||
"allowanceContract is required for DEX"
|
||||
},
|
||||
spenderAddress = quoteModel.allowanceContract,
|
||||
)
|
||||
if (state !is SwapState.QuotesLoadedState) return state
|
||||
state.copy(
|
||||
|
|
@ -2081,14 +2079,14 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
) ?: TxFeeState.Empty
|
||||
}
|
||||
|
||||
@Suppress("LongParameterList", "LongMethod")
|
||||
@Suppress("LongParameterList", "LongMethod", "CanBeNonNullable")
|
||||
private suspend fun updatePermissionState(
|
||||
networkId: String,
|
||||
fromTokenStatus: CryptoCurrencyStatus,
|
||||
fromAccount: Account.CryptoPortfolio?,
|
||||
swapAmount: SwapAmount,
|
||||
quotesLoadedState: SwapState.QuotesLoadedState,
|
||||
spenderAddress: String,
|
||||
spenderAddress: String?,
|
||||
isAllowedToSpend: Boolean,
|
||||
): SwapState {
|
||||
val fromToken = fromTokenStatus.currency
|
||||
|
|
@ -2111,7 +2109,7 @@ internal class SwapInteractorImpl @AssistedInject constructor(
|
|||
}
|
||||
// setting up amount for approve with given amount for swap [SwapApproveType.Limited]
|
||||
val callData = SmartContractCallDataProviderFactory.getApprovalCallData(
|
||||
spenderAddress = spenderAddress,
|
||||
spenderAddress = requireNotNull(spenderAddress) { "spenderAddress cant be null" },
|
||||
amount = swapAmount.value.convertToSdkAmount(fromTokenStatus),
|
||||
blockchain = fromToken.network.toBlockchain(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,9 +2,7 @@ package com.tangem.feature.swap
|
|||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
|
|
@ -103,13 +101,9 @@ internal class DefaultSwapComponent @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
if (sendFeatureToggles.isGaslessTransactionsEnabled) {
|
||||
val fromCryptoCurrency = if (model.isOrderReversed) {
|
||||
model.dataState.toCryptoCurrency
|
||||
} else {
|
||||
model.dataState.fromCryptoCurrency
|
||||
}
|
||||
val feePaidCryptoCurrency = model.dataState.feePaidCryptoCurrency
|
||||
val amount = model.dataState.amount
|
||||
val fromCryptoCurrency by remember { derivedStateOf { model.dataState.fromCryptoCurrency } }
|
||||
val feePaidCryptoCurrency by remember { derivedStateOf { model.dataState.feePaidCryptoCurrency } }
|
||||
val amount by remember { derivedStateOf { model.dataState.amount } }
|
||||
|
||||
LaunchedEffect(fromCryptoCurrency, feePaidCryptoCurrency, amount.isNullOrBlank()) {
|
||||
if (amount.isNullOrBlank()) {
|
||||
|
|
|
|||
|
|
@ -220,9 +220,17 @@ internal class SwapModel @Inject constructor(
|
|||
private val swapRouter: SwapRouter = SwapRouter(router = router)
|
||||
private var userCountry: UserCountry? = null
|
||||
|
||||
private lateinit var fromAccountCurrencyStatus: AccountCryptoCurrencyStatus
|
||||
private var fromAccountCurrencyStatus: AccountCryptoCurrencyStatus? = null
|
||||
private var toAccountCurrencyStatus: AccountCryptoCurrencyStatus? = null
|
||||
|
||||
/**
|
||||
* If accountsFeatureToggles is off OR user came from Tangem Pay -> fromAccountCurrencyStatus == null
|
||||
* If accountsFeatureToggles is on AND user didn't come from Tangem Pay -> fromAccountCurrencyStatus != null
|
||||
*
|
||||
* Remove when accounts are integrated into Tangem Pay
|
||||
*/
|
||||
private val canUseFromAccountCurrencyStatus = accountsFeatureToggles.isFeatureEnabled && tangemPayInput == null
|
||||
|
||||
private val isUserResolvableError: (SwapState) -> Boolean = { swapState ->
|
||||
swapState is SwapState.SwapError &&
|
||||
(
|
||||
|
|
@ -288,7 +296,7 @@ internal class SwapModel @Inject constructor(
|
|||
}
|
||||
|
||||
modelScope.launch(dispatchers.io) {
|
||||
if (accountsFeatureToggles.isFeatureEnabled && tangemPayInput == null) {
|
||||
if (canUseFromAccountCurrencyStatus) {
|
||||
isAccountsMode = isAccountsModeEnabledUseCase.invokeSync()
|
||||
|
||||
val fromAccountStatus = getAccountCurrencyStatusUseCase.invokeSync(
|
||||
|
|
@ -579,11 +587,11 @@ internal class SwapModel @Inject constructor(
|
|||
} else {
|
||||
initialFromStatus to selectedCurrency
|
||||
}
|
||||
val (fromAccount, toAccount) = if (accountsFeatureToggles.isFeatureEnabled && tangemPayInput == null) {
|
||||
val (fromAccount, toAccount) = if (canUseFromAccountCurrencyStatus) {
|
||||
if (isOrderReversed) {
|
||||
selectedAccount to fromAccountCurrencyStatus.account
|
||||
selectedAccount to requireNotNull(fromAccountCurrencyStatus).account
|
||||
} else {
|
||||
fromAccountCurrencyStatus.account to selectedAccount
|
||||
requireNotNull(fromAccountCurrencyStatus).account to selectedAccount
|
||||
}
|
||||
} else {
|
||||
null to null
|
||||
|
|
@ -1266,7 +1274,7 @@ internal class SwapModel @Inject constructor(
|
|||
fromAccount = foundAccount
|
||||
toToken = initialFromStatus
|
||||
toAccount = if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
fromAccountCurrencyStatus.account
|
||||
fromAccountCurrencyStatus?.account
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
@ -1284,7 +1292,7 @@ internal class SwapModel @Inject constructor(
|
|||
} else {
|
||||
fromToken = initialFromStatus
|
||||
fromAccount = if (accountsFeatureToggles.isFeatureEnabled) {
|
||||
fromAccountCurrencyStatus.account
|
||||
fromAccountCurrencyStatus?.account
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,9 +16,7 @@ interface TangemPayOnboardingComponent : ComposableContentComponent {
|
|||
val userWalletId: UserWalletId,
|
||||
) : Params()
|
||||
|
||||
data class FromBannerOnMain(
|
||||
val userWalletId: UserWalletId,
|
||||
) : Params()
|
||||
data object FromBannerOnMain : Params()
|
||||
|
||||
data object FromBannerInSettings : Params()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,13 +131,7 @@ internal class TangemPayOnboardingModel @Inject constructor(
|
|||
is TangemPayOnboardingComponent.Params.ContinueOnboarding -> {
|
||||
checkCustomerInfo(params.userWalletId)
|
||||
}
|
||||
is TangemPayOnboardingComponent.Params.FromBannerOnMain -> {
|
||||
if (eligibleWalletsIds.any { walletId -> walletId == params.userWalletId }) {
|
||||
checkCustomerInfo(userWalletId = params.userWalletId)
|
||||
} else {
|
||||
openWalletSelectorIfNeeds(eligibleWalletsIds)
|
||||
}
|
||||
}
|
||||
is TangemPayOnboardingComponent.Params.FromBannerOnMain,
|
||||
is TangemPayOnboardingComponent.Params.Deeplink,
|
||||
is TangemPayOnboardingComponent.Params.FromBannerInSettings,
|
||||
-> {
|
||||
|
|
|
|||
|
|
@ -243,7 +243,7 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
|
|||
modelScope.launch {
|
||||
val isEligible = tangemPayEligibilityManager.getTangemPayAvailability()
|
||||
if (isEligible) {
|
||||
router.openTangemPayOnboarding(mode = AppRoute.TangemPayOnboarding.Mode.FromBannerOnMain(userWalletId))
|
||||
router.openTangemPayOnboarding(mode = AppRoute.TangemPayOnboarding.Mode.FromBannerOnMain)
|
||||
} else {
|
||||
stateHolder.update(transformer = TangemPayHideOnboardingStateTransformer(userWalletId))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -219,9 +219,8 @@ private fun LazyItemScope.DraggableItem(
|
|||
state = item.tokenItemState,
|
||||
isCollapsable = false,
|
||||
composables = null,
|
||||
modifier = modifierWithBackground
|
||||
.padding(top = 8.dp)
|
||||
.fillMaxWidth(),
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
modifier = modifierWithBackground.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency
|
||||
|
||||
import androidx.compose.animation.*
|
||||
import androidx.compose.animation.core.FastOutLinearInEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
|
|
@ -141,7 +142,7 @@ private fun LazyListScope.portfolioItem(
|
|||
lastIndexProxy = lastIndex
|
||||
return@LaunchedEffect
|
||||
}
|
||||
delay(timeMillis = minOf(50 * tokens.size, 350).toLong())
|
||||
delay(timeMillis = minOf(50 * tokens.size, 250).toLong())
|
||||
lastIndexProxy = 0
|
||||
}
|
||||
|
||||
|
|
@ -179,13 +180,13 @@ private fun SlideInItemVisibility(
|
|||
enter = fadeIn(
|
||||
tween(100, delayMillis = delayEnter),
|
||||
) + expandVertically(
|
||||
tween(100, delayMillis = delayEnter),
|
||||
tween(100, delayMillis = delayEnter, easing = FastOutLinearInEasing),
|
||||
expandFrom = Alignment.Top,
|
||||
),
|
||||
exit = fadeOut(
|
||||
tween(100, delayMillis = delayExit),
|
||||
) + shrinkVertically(
|
||||
tween(100, delayMillis = delayExit),
|
||||
tween(100, delayMillis = delayExit, easing = FastOutLinearInEasing),
|
||||
shrinkTowards = Alignment.Top,
|
||||
),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import com.tangem.domain.transaction.usecase.CreateApprovalTransactionUseCase
|
|||
import com.tangem.domain.transaction.usecase.GetFeeUseCase
|
||||
import com.tangem.domain.transaction.usecase.SendTransactionUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetContractAddressUseCase
|
||||
import com.tangem.domain.yield.supply.usecase.YieldSupplyPendingTracker
|
||||
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
|
||||
import com.tangem.features.yield.supply.impl.R
|
||||
import com.tangem.features.yield.supply.impl.common.YieldSupplyAlertFactory
|
||||
|
|
@ -59,6 +60,7 @@ internal class YieldSupplyApproveModel @Inject constructor(
|
|||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase,
|
||||
private val yieldSupplyGetContractAddressUseCase: YieldSupplyGetContractAddressUseCase,
|
||||
private val yieldSupplyPendingTracker: YieldSupplyPendingTracker,
|
||||
private val yieldSupplyAlertFactory: YieldSupplyAlertFactory,
|
||||
) : Model(), YieldSupplyNotificationsComponent.ModelCallback {
|
||||
|
||||
|
|
@ -159,7 +161,12 @@ internal class YieldSupplyApproveModel @Inject constructor(
|
|||
)
|
||||
params.callback.onTransactionProgress(false)
|
||||
},
|
||||
ifRight = {
|
||||
ifRight = { txHash ->
|
||||
yieldSupplyPendingTracker.addPending(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrency = cryptoCurrency,
|
||||
txIds = listOf(txHash),
|
||||
)
|
||||
val event = AnalyticsParam.TxSentFrom.Earning(
|
||||
blockchain = cryptoCurrency.network.name,
|
||||
token = cryptoCurrency.symbol,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue