Updated on 2026-08-14
This commit is contained in:
parent
3da4771d66
commit
3baed1e815
57 changed files with 1797 additions and 370 deletions
|
|
@ -11,6 +11,7 @@ import com.tangem.features.feed.components.earn.DefaultEarnComponent
|
|||
import com.tangem.features.feed.components.feed.DefaultFeedComponent
|
||||
import com.tangem.features.feed.components.feed.DefaultFeedComponent.FeedParams
|
||||
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioPreselectedDataComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockComponent
|
||||
|
|
@ -29,6 +30,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val portfolioComponentFactory: MarketsPortfolioComponent.Factory,
|
||||
private val portfolioBlockComponentFactory: PortfolioBlockComponent.Factory,
|
||||
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
|
||||
private val addToPortfolioPreselectedDataComponent: AddToPortfolioPreselectedDataComponent.Factory,
|
||||
private val promoBannersBlockComponentFactory: PromoBannersBlockComponent.Factory,
|
||||
private val newPromoBannersFeatureToggles: NewPromoBannersFeatureToggles,
|
||||
|
|
@ -84,6 +86,7 @@ internal class FeedEntryChildFactory @Inject constructor(
|
|||
portfolioComponentFactory = portfolioComponentFactory,
|
||||
portfolioBlockComponentFactory = portfolioBlockComponentFactory,
|
||||
designFeatureToggles = designFeatureToggles,
|
||||
addToPortfolioComponentFactory = addToPortfolioComponentFactory,
|
||||
)
|
||||
}
|
||||
is Child.TokenList -> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,7 @@
|
|||
package com.tangem.features.feed.components.market.details
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data object AddToPortfolioSlotRoute : Route
|
||||
|
|
@ -16,16 +16,20 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
|||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network
|
||||
import com.tangem.domain.markets.PreselectedTokenDetailsSection
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.child
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.DesignFeatureToggles
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarType
|
||||
|
|
@ -34,10 +38,13 @@ import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
|||
import com.tangem.core.ui.res.LocalRedesignEnabled
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.PreselectedTokenDetailsSection
|
||||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolio.api.MarketsPortfolioComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockParentClickIntents
|
||||
import com.tangem.features.feed.model.market.details.MarketsTokenDetailsModel
|
||||
import com.tangem.features.feed.model.market.details.analytics.MarketDetailsAnalyticsEvent
|
||||
import com.tangem.features.feed.model.market.details.state.TokenNetworksState
|
||||
|
|
@ -48,13 +55,15 @@ import kotlinx.coroutines.flow.collectLatest
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultMarketsTokenDetailsComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
val params: Params,
|
||||
analyticsEventHandler: AnalyticsEventHandler,
|
||||
designFeatureToggles: DesignFeatureToggles,
|
||||
portfolioComponentFactory: MarketsPortfolioComponent.Factory,
|
||||
portfolioBlockComponentFactory: PortfolioBlockComponent.Factory,
|
||||
val params: Params,
|
||||
private val addToPortfolioComponentFactory: AddToPortfolioComponent.Factory,
|
||||
) : ComposableModularBottomSheetContentComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
// applying l2 compatibility
|
||||
|
|
@ -83,12 +92,28 @@ internal class DefaultMarketsTokenDetailsComponent(
|
|||
if (updatedParams.shouldShowPortfolio && designFeatureToggles.isRedesignEnabled) {
|
||||
portfolioBlockComponentFactory.create(
|
||||
context = child("portfolio_block"),
|
||||
params = PortfolioBlockComponent.Params(updatedParams.token),
|
||||
params = PortfolioBlockComponent.Params(token = updatedParams.token),
|
||||
parentRouter = object : PortfolioBlockParentClickIntents {
|
||||
override fun openAddToPortfolioDirect() {
|
||||
model.openAddToPortfolio()
|
||||
}
|
||||
|
||||
override fun openAddToPortfolioViaUserPortfolio(rawCurrencyId: CryptoCurrency.RawID) {
|
||||
model.openAddToPortfolioViaUserPortfolio(rawCurrencyId)
|
||||
}
|
||||
},
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
private val addToPortfolioSlot = childSlot(
|
||||
source = model.addToPortfolioSheetNavigation,
|
||||
serializer = AddToPortfolioSlotRoute.serializer(),
|
||||
handleBackButton = false,
|
||||
childFactory = ::addToPortfolioChild,
|
||||
)
|
||||
|
||||
init {
|
||||
componentScope.launch(dispatchers.default) {
|
||||
model.networksState.collectLatest { state ->
|
||||
|
|
@ -120,6 +145,17 @@ internal class DefaultMarketsTokenDetailsComponent(
|
|||
}
|
||||
}
|
||||
|
||||
private fun addToPortfolioChild(
|
||||
@Suppress("UNUSED_PARAMETER") config: AddToPortfolioSlotRoute,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent {
|
||||
val manager = model.addToPortfolioManagerOrNull() ?: return ComposableBottomSheetComponent.EMPTY
|
||||
return addToPortfolioComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = AddToPortfolioComponent.Params(addToPortfolioManager = manager),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
|
@ -198,6 +234,7 @@ internal class DefaultMarketsTokenDetailsComponent(
|
|||
}
|
||||
}
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val bottomSheet by addToPortfolioSlot.subscribeAsState()
|
||||
val bsState by bottomSheetState
|
||||
LaunchedEffect(bsState) {
|
||||
model.isVisibleOnScreen.value = bsState == BottomSheetState.EXPANDED
|
||||
|
|
@ -219,6 +256,7 @@ internal class DefaultMarketsTokenDetailsComponent(
|
|||
}
|
||||
},
|
||||
)
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
}
|
||||
|
||||
@Serializable
|
||||
|
|
|
|||
|
|
@ -5,24 +5,13 @@ import androidx.compose.runtime.Stable
|
|||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.markets.TokenMarketInfo
|
||||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.features.feed.components.market.details.portfolioblock.model.PortfolioBlockModel
|
||||
import com.tangem.features.feed.components.market.details.portfolioblock.model.PortfolioBlockRoute
|
||||
import com.tangem.features.feed.components.market.details.portfolioblock.ui.PortfolioBlock
|
||||
import com.tangem.features.feed.components.portfolio.PortfolioComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -32,26 +21,14 @@ import kotlinx.serialization.Serializable
|
|||
internal class PortfolioBlockComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: Params,
|
||||
@Assisted private val parentRouter: PortfolioBlockParentClickIntents?,
|
||||
) : ComposableContentComponent, AppComponentContext by context {
|
||||
|
||||
private val portfolioComponentFactory: PortfolioComponent.Factory = object : PortfolioComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: PortfolioComponent.Params): PortfolioComponent {
|
||||
TODO("STUB. Will be implemented")
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Params(
|
||||
val token: TokenMarketParams,
|
||||
)
|
||||
|
||||
private val model: PortfolioBlockModel = getOrCreateModel(params)
|
||||
|
||||
private val bottomSheetSlot = childSlot(
|
||||
source = model.bottomSheetNavigation,
|
||||
serializer = PortfolioBlockRoute.serializer(),
|
||||
handleBackButton = true,
|
||||
childFactory = ::createBottomSheetChild,
|
||||
private val model: PortfolioBlockModel = getOrCreateModel(
|
||||
PortfolioBlockModelParams(
|
||||
token = params.token,
|
||||
parentRouter = parentRouter,
|
||||
),
|
||||
)
|
||||
|
||||
fun setTokenNetworks(networks: List<TokenMarketInfo.Network>) {
|
||||
|
|
@ -65,51 +42,23 @@ internal class PortfolioBlockComponent @AssistedInject constructor(
|
|||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
|
||||
PortfolioBlock(modifier = modifier, state = state)
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
}
|
||||
|
||||
@Suppress("UnusedParameter")
|
||||
private fun createBottomSheetChild(
|
||||
config: PortfolioBlockRoute,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent {
|
||||
val currencyId = model.cryptoCurrencyIdState.value ?: return ComposableBottomSheetComponent.EMPTY
|
||||
val portfolioComponent = portfolioComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = PortfolioComponent.Params(id = currencyId),
|
||||
)
|
||||
return PortfolioBottomSheetWrapper(
|
||||
portfolioComponent = portfolioComponent,
|
||||
onDismiss = { model.bottomSheetNavigation.dismiss() },
|
||||
)
|
||||
}
|
||||
|
||||
private class PortfolioBottomSheetWrapper(
|
||||
private val portfolioComponent: PortfolioComponent,
|
||||
private val onDismiss: () -> Unit,
|
||||
) : ComposableBottomSheetComponent {
|
||||
|
||||
override fun dismiss() = onDismiss()
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = TangemBottomSheetConfig(
|
||||
isShown = true,
|
||||
onDismissRequest = ::dismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
),
|
||||
) {
|
||||
portfolioComponent.Content(modifier = Modifier)
|
||||
}
|
||||
}
|
||||
}
|
||||
@Serializable
|
||||
data class Params(val token: TokenMarketParams)
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(context: AppComponentContext, params: Params): PortfolioBlockComponent
|
||||
fun create(
|
||||
context: AppComponentContext,
|
||||
params: Params,
|
||||
parentRouter: PortfolioBlockParentClickIntents?,
|
||||
): PortfolioBlockComponent
|
||||
}
|
||||
|
||||
data class PortfolioBlockModelParams(
|
||||
val token: TokenMarketParams,
|
||||
val parentRouter: PortfolioBlockParentClickIntents?,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.feed.components.market.details.portfolioblock
|
||||
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
|
||||
internal interface PortfolioBlockParentClickIntents {
|
||||
fun openAddToPortfolioDirect()
|
||||
fun openAddToPortfolioViaUserPortfolio(rawCurrencyId: CryptoCurrency.RawID)
|
||||
}
|
||||
|
|
@ -2,8 +2,6 @@ package com.tangem.features.feed.components.market.details.portfolioblock.model
|
|||
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.blockchainsdk.compatibility.getTokenIdIfL2Network
|
||||
import com.tangem.common.getTotalFiatAmount
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
|
|
@ -26,6 +24,7 @@ import com.tangem.domain.models.wallet.UserWallet
|
|||
import com.tangem.domain.models.wallet.isMultiCurrency
|
||||
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
|
||||
import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockComponent
|
||||
import com.tangem.features.feed.components.market.details.portfolioblock.PortfolioBlockParentClickIntents
|
||||
import com.tangem.features.feed.components.market.details.portfolioblock.ui.state.PortfolioBlockUM
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -50,13 +49,12 @@ internal class PortfolioBlockModel @Inject constructor(
|
|||
val state: StateFlow<PortfolioBlockUM>
|
||||
field = MutableStateFlow<PortfolioBlockUM>(PortfolioBlockUM.Loading)
|
||||
|
||||
val bottomSheetNavigation: SlotNavigation<PortfolioBlockRoute> = SlotNavigation()
|
||||
|
||||
val cryptoCurrencyIdState: StateFlow<CryptoCurrency.ID?>
|
||||
field = MutableStateFlow(null)
|
||||
|
||||
private val params = paramsContainer.require<PortfolioBlockComponent.Params>()
|
||||
private val params = paramsContainer.require<PortfolioBlockComponent.PortfolioBlockModelParams>()
|
||||
private val currencyRawId: CryptoCurrency.RawID = params.token.id
|
||||
private val parentRouter: PortfolioBlockParentClickIntents? = params.parentRouter
|
||||
|
||||
private val tokenIcon: CurrencyIconState = CurrencyIconState.CoinIcon(
|
||||
url = params.token.imageUrl,
|
||||
|
|
@ -114,15 +112,12 @@ internal class PortfolioBlockModel @Inject constructor(
|
|||
}
|
||||
}.distinctUntilChanged()
|
||||
|
||||
val settingsFlow = combine(
|
||||
return combine(
|
||||
portfolioDataFlow,
|
||||
getSelectedAppCurrencyUseCase.invokeOrDefault(),
|
||||
getBalanceHidingSettingsUseCase.isBalanceHidden(),
|
||||
) { appCurrency, isBalanceHidden ->
|
||||
SettingsBox(appCurrency, isBalanceHidden)
|
||||
}.distinctUntilChanged()
|
||||
|
||||
return combine(portfolioDataFlow, settingsFlow) { portfolios, settings ->
|
||||
buildState(portfolios, settings)
|
||||
) { portfolios, appCurrency, isBalanceHidden ->
|
||||
buildState(portfolios, appCurrency, isBalanceHidden)
|
||||
}.distinctUntilChanged()
|
||||
}
|
||||
|
||||
|
|
@ -135,7 +130,11 @@ internal class PortfolioBlockModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun buildState(portfolios: List<WalletPortfolio>, settings: SettingsBox): PortfolioBlockUM {
|
||||
private fun buildState(
|
||||
portfolios: List<WalletPortfolio>,
|
||||
appCurrency: AppCurrency,
|
||||
isBalanceHidden: Boolean,
|
||||
): PortfolioBlockUM {
|
||||
val allCurrencies = portfolios.flatMap { it.currencies }
|
||||
val hasMultiCurrencyWallet = portfolios.any { it.userWallet.isMultiCurrency }
|
||||
|
||||
|
|
@ -143,36 +142,36 @@ internal class PortfolioBlockModel @Inject constructor(
|
|||
return if (hasMultiCurrencyWallet) {
|
||||
PortfolioBlockUM.AddToken(
|
||||
tokenIcon = tokenIcon,
|
||||
onClick = { bottomSheetNavigation.activate(PortfolioBlockRoute) },
|
||||
onAddClick = { parentRouter?.openAddToPortfolioDirect() },
|
||||
)
|
||||
} else {
|
||||
PortfolioBlockUM.Hidden
|
||||
}
|
||||
}
|
||||
|
||||
cryptoCurrencyIdState.update {
|
||||
it ?: allCurrencies.first().currency.id
|
||||
}
|
||||
cryptoCurrencyIdState.update { it ?: allCurrencies.first().currency.id }
|
||||
|
||||
val totalFiat = allCurrencies.mapNotNull { it.getTotalFiatAmount() }
|
||||
.fold(BigDecimal.ZERO, BigDecimal::add)
|
||||
|
||||
val formattedBalance = totalFiat.formatStyled {
|
||||
fiat(
|
||||
fiatCurrencyCode = settings.appCurrency.code,
|
||||
fiatCurrencySymbol = settings.appCurrency.symbol,
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
fiatCurrencySymbol = appCurrency.symbol,
|
||||
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
|
||||
)
|
||||
}
|
||||
|
||||
val firstCurrency = allCurrencies.first().currency
|
||||
return PortfolioBlockUM.Content(
|
||||
totalBalance = formattedBalance,
|
||||
tokensInPortfolioCount = allCurrencies.size,
|
||||
tokenIcon = tokenIcon,
|
||||
tokenName = allCurrencies.first().currency.name,
|
||||
tokenSymbol = allCurrencies.first().currency.symbol,
|
||||
isBalanceHidden = settings.isBalanceHidden,
|
||||
onClick = { bottomSheetNavigation.activate(PortfolioBlockRoute) },
|
||||
tokenName = firstCurrency.name,
|
||||
tokenSymbol = firstCurrency.symbol,
|
||||
isBalanceHidden = isBalanceHidden,
|
||||
onRowClick = { parentRouter?.openAddToPortfolioViaUserPortfolio(currencyRawId) },
|
||||
onAddFundsClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -180,9 +179,4 @@ internal class PortfolioBlockModel @Inject constructor(
|
|||
private data class WalletPortfolio(
|
||||
val userWallet: UserWallet,
|
||||
val currencies: List<CryptoCurrencyStatus>,
|
||||
)
|
||||
|
||||
private data class SettingsBox(
|
||||
val appCurrency: AppCurrency,
|
||||
val isBalanceHidden: Boolean,
|
||||
)
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
package com.tangem.features.feed.components.market.details.portfolioblock.model
|
||||
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data object PortfolioBlockRoute : Route
|
||||
|
|
@ -22,19 +22,15 @@ import androidx.compose.ui.Alignment
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.graphics.graphicsLayer
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
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.BottomFade
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIcon
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.ds.button.*
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.row.TangemRowContainer
|
||||
import com.tangem.core.ui.ds.row.TangemRowLayoutId
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.LocalWindowSize
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
|
|
@ -95,57 +91,53 @@ internal fun PortfolioBlock(state: PortfolioBlockUM, modifier: Modifier = Modifi
|
|||
|
||||
@Composable
|
||||
private fun ContentBlock(state: PortfolioBlockUM.Content, modifier: Modifier = Modifier) {
|
||||
FloatingCard(
|
||||
modifier = modifier,
|
||||
onClick = state.onClick,
|
||||
) {
|
||||
TangemRowContainer(
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
FloatingCard(modifier = modifier) { // TODO will be handle in [REDACTED_TASK_KEY]
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CurrencyIcon(
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(end = TangemTheme.dimens2.x3)
|
||||
.layoutId(TangemRowLayoutId.HEAD),
|
||||
state = state.tokenIcon,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
|
||||
text = state.tokenName,
|
||||
style = TangemTheme.typography2.bodyMedium16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.START_BOTTOM),
|
||||
text = stringResourceSafe(R.string.markets_portfolio_block_subtitle),
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
maxLines = 1,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.END_TOP),
|
||||
text = state.totalBalance.orMaskWithStars(state.isBalanceHidden).resolveAnnotatedReference(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
style = TangemTheme.typography2.bodyMedium16,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.END_BOTTOM),
|
||||
text = state.tokenSymbol,
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
maxLines = 1,
|
||||
)
|
||||
|
||||
.weight(1f)
|
||||
.clickable(onClick = state.onRowClick),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
CurrencyIcon(
|
||||
modifier = Modifier.padding(end = TangemTheme.dimens2.x3),
|
||||
state = state.tokenIcon,
|
||||
)
|
||||
Column(modifier = Modifier.weight(1f)) {
|
||||
Text(
|
||||
text = state.tokenName,
|
||||
style = TangemTheme.typography2.bodyMedium16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.markets_portfolio_block_subtitle),
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
Column(horizontalAlignment = Alignment.End) {
|
||||
Text(
|
||||
text = state.totalBalance.orMaskWithStars(state.isBalanceHidden).resolveAnnotatedReference(),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
style = TangemTheme.typography2.bodyMedium16,
|
||||
)
|
||||
Text(
|
||||
text = state.tokenSymbol,
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
TangemButton(
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens2.x3)
|
||||
.layoutId(TangemRowLayoutId.TAIL),
|
||||
modifier = Modifier.padding(start = TangemTheme.dimens2.x3),
|
||||
buttonUM = TangemButtonUM(
|
||||
type = TangemButtonType.Secondary,
|
||||
tangemIconUM = TangemIconUM.Icon(
|
||||
|
|
@ -154,7 +146,7 @@ private fun ContentBlock(state: PortfolioBlockUM.Content, modifier: Modifier = M
|
|||
),
|
||||
shape = TangemButtonShape.Rounded,
|
||||
size = TangemButtonSize.X9,
|
||||
onClick = state.onClick,
|
||||
onClick = state.onAddFundsClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -163,10 +155,7 @@ private fun ContentBlock(state: PortfolioBlockUM.Content, modifier: Modifier = M
|
|||
|
||||
@Composable
|
||||
private fun AddTokenBlock(state: PortfolioBlockUM.AddToken, modifier: Modifier = Modifier) {
|
||||
FloatingCard(
|
||||
modifier = modifier,
|
||||
onClick = state.onClick,
|
||||
) {
|
||||
FloatingCard(modifier = modifier) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
CurrencyIcon(state.tokenIcon)
|
||||
|
||||
|
|
@ -191,7 +180,7 @@ private fun AddTokenBlock(state: PortfolioBlockUM.AddToken, modifier: Modifier =
|
|||
text = resourceReference(R.string.common_add),
|
||||
shape = TangemButtonShape.Rounded,
|
||||
size = TangemButtonSize.X9,
|
||||
onClick = state.onClick,
|
||||
onClick = state.onAddClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -200,7 +189,7 @@ private fun AddTokenBlock(state: PortfolioBlockUM.AddToken, modifier: Modifier =
|
|||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
@Composable
|
||||
private fun FloatingCard(modifier: Modifier = Modifier, onClick: () -> Unit = {}, content: @Composable () -> Unit) {
|
||||
private fun FloatingCard(modifier: Modifier = Modifier, content: @Composable () -> Unit) {
|
||||
Box(
|
||||
modifier = modifier
|
||||
.navigationBarsPadding()
|
||||
|
|
@ -216,7 +205,6 @@ private fun FloatingCard(modifier: Modifier = Modifier, onClick: () -> Unit = {}
|
|||
color = TangemTheme.colors2.surface.level3,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens2.x5),
|
||||
)
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = TangemTheme.dimens2.x4, vertical = TangemTheme.dimens2.x3),
|
||||
) {
|
||||
content()
|
||||
|
|
@ -236,7 +224,8 @@ private fun ContentPreview() {
|
|||
tokenName = "Bitcoin",
|
||||
tokenSymbol = "BTC",
|
||||
isBalanceHidden = false,
|
||||
onClick = {},
|
||||
onRowClick = {},
|
||||
onAddFundsClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -250,7 +239,7 @@ private fun AddTokenPreview() {
|
|||
PortfolioBlock(
|
||||
state = PortfolioBlockUM.AddToken(
|
||||
tokenIcon = previewCoinIcon,
|
||||
onClick = {},
|
||||
onAddClick = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ internal sealed class PortfolioBlockUM {
|
|||
|
||||
data class AddToken(
|
||||
val tokenIcon: CurrencyIconState,
|
||||
val onClick: () -> Unit,
|
||||
val onAddClick: () -> Unit,
|
||||
) : PortfolioBlockUM()
|
||||
|
||||
data class Content(
|
||||
|
|
@ -22,6 +22,7 @@ internal sealed class PortfolioBlockUM {
|
|||
val tokenName: String,
|
||||
val tokenSymbol: String,
|
||||
val isBalanceHidden: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
val onRowClick: () -> Unit,
|
||||
val onAddFundsClick: () -> Unit,
|
||||
) : PortfolioBlockUM()
|
||||
}
|
||||
|
|
@ -1,15 +1,15 @@
|
|||
package com.tangem.features.feed.components.search
|
||||
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.search.model.UserAssetSearchEntry
|
||||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
|
||||
internal sealed interface SearchBottomSheetRoute {
|
||||
|
||||
data class TokenSelector(
|
||||
val entries: List<UserAssetSearchEntry>,
|
||||
val entries: List<UserAssetEntry>,
|
||||
val appCurrency: AppCurrency,
|
||||
val isBalanceHidden: Boolean,
|
||||
val onTokenSelected: (UserAssetSearchEntry) -> Unit,
|
||||
val onTokenSelected: (UserAssetEntry) -> Unit,
|
||||
val onDismiss: () -> Unit,
|
||||
) : SearchBottomSheetRoute
|
||||
}
|
||||
|
|
@ -7,9 +7,9 @@ import com.tangem.core.decompose.model.getOrCreateModel
|
|||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.search.model.UserAssetSearchEntry
|
||||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorBottomSheet
|
||||
import com.tangem.features.feed.model.search.SearchTokenSelectorModel
|
||||
import com.tangem.features.feed.ui.search.components.TokenSelectorBottomSheet
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
|
|
@ -37,10 +37,10 @@ internal class SearchTokenSelectorComponent @AssistedInject constructor(
|
|||
}
|
||||
|
||||
data class Params(
|
||||
val entries: List<UserAssetSearchEntry>,
|
||||
val entries: List<UserAssetEntry>,
|
||||
val appCurrency: AppCurrency,
|
||||
val isBalanceHidden: Boolean,
|
||||
val onTokenSelected: (UserAssetSearchEntry) -> Unit,
|
||||
val onTokenSelected: (UserAssetEntry) -> Unit,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
}
|
||||
|
|
@ -3,9 +3,13 @@ package com.tangem.features.feed.model.market.details
|
|||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.Either
|
||||
import arrow.core.getOrElse
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.common.TangemSiteShareUrlBuilder
|
||||
import com.tangem.domain.markets.PreselectedTokenDetailsSection
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.common.ui.charts.state.MarketChartData
|
||||
import com.tangem.common.ui.charts.state.MarketChartDataProducer
|
||||
import com.tangem.common.ui.charts.state.sorted
|
||||
|
|
@ -36,6 +40,7 @@ import com.tangem.domain.card.common.extensions.hotWalletExcludedBlockchains
|
|||
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
|
||||
import com.tangem.domain.feedback.models.FeedbackEmailType
|
||||
import com.tangem.domain.markets.*
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.news.model.NewsListConfig
|
||||
import com.tangem.domain.news.usecase.GetNewsUseCase
|
||||
|
|
@ -43,6 +48,8 @@ import com.tangem.domain.settings.usercountry.GetUserCountryUseCase
|
|||
import com.tangem.domain.settings.usercountry.models.UserCountry
|
||||
import com.tangem.domain.settings.usercountry.models.needApplyFCARestrictions
|
||||
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
|
||||
import com.tangem.features.commonfeatures.api.addtoportfolio.AddToPortfolioManager
|
||||
import com.tangem.features.feed.components.market.details.AddToPortfolioSlotRoute
|
||||
import com.tangem.features.feed.components.market.details.DefaultMarketsTokenDetailsComponent
|
||||
import com.tangem.features.feed.components.market.details.analytics.MarketTokenAnalyticsEvent
|
||||
import com.tangem.features.feed.impl.R
|
||||
|
|
@ -65,6 +72,7 @@ import com.tangem.utils.coroutines.saveIn
|
|||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -81,6 +89,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
getUserCountryUseCase: GetUserCountryUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
private val addToPortfolioManagerFactory: AddToPortfolioManager.Factory,
|
||||
private val designFeatureToggles: DesignFeatureToggles,
|
||||
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
|
||||
private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
|
||||
|
|
@ -93,6 +102,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
private val urlOpener: UrlOpener,
|
||||
private val getNewsUseCase: GetNewsUseCase,
|
||||
private val shareManager: ShareManager,
|
||||
private val appRouter: AppRouter,
|
||||
) : Model() {
|
||||
|
||||
private val quotesJob = JobHolder()
|
||||
|
|
@ -224,6 +234,13 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
val isVisibleOnScreen = MutableStateFlow(false)
|
||||
val networksState = MutableStateFlow<TokenNetworksState>(TokenNetworksState.Loading)
|
||||
|
||||
val addToPortfolioSheetNavigation = SlotNavigation<AddToPortfolioSlotRoute>()
|
||||
|
||||
private val isAddToPortfolioAvailable: Boolean =
|
||||
params.shouldShowPortfolio && designFeatureToggles.isRedesignEnabled
|
||||
private var addToPortfolioManager: AddToPortfolioManager? = null
|
||||
private var addToPortfolioListenersJob: Job? = null
|
||||
|
||||
val state = MutableStateFlow(
|
||||
MarketsTokenDetailsUM(
|
||||
tokenName = params.token.name,
|
||||
|
|
@ -309,6 +326,77 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
onListedOnClick(exchangesCount)
|
||||
}
|
||||
}
|
||||
|
||||
modelScope.launch {
|
||||
networksState.collect { tokenNetworkState ->
|
||||
val manager = addToPortfolioManager ?: return@collect
|
||||
when (tokenNetworkState) {
|
||||
is TokenNetworksState.NetworksAvailable -> manager.setTokenNetworks(tokenNetworkState.networks)
|
||||
TokenNetworksState.NoNetworksAvailable -> manager.setTokenNetworks(emptyList())
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun openAddToPortfolio() {
|
||||
if (!isAddToPortfolioAvailable) return
|
||||
prepareAddToPortfolioManager(AddToPortfolioManager.LaunchMode.DirectAdd)
|
||||
addToPortfolioSheetNavigation.activate(AddToPortfolioSlotRoute)
|
||||
}
|
||||
|
||||
fun openAddToPortfolioViaUserPortfolio(rawCurrencyId: CryptoCurrency.RawID) {
|
||||
if (!isAddToPortfolioAvailable) return
|
||||
prepareAddToPortfolioManager(AddToPortfolioManager.LaunchMode.ViaUserPortfolio(rawCurrencyId))
|
||||
addToPortfolioSheetNavigation.activate(AddToPortfolioSlotRoute)
|
||||
}
|
||||
|
||||
private fun openTokenDetails(result: AddToPortfolioManager.Result) {
|
||||
appRouter.push(
|
||||
AppRoute.CurrencyDetails(
|
||||
userWalletId = result.wallet.walletId,
|
||||
currency = result.addedCurrency.currency,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun addToPortfolioManagerOrNull(): AddToPortfolioManager? = addToPortfolioManager
|
||||
|
||||
private fun prepareAddToPortfolioManager(launchMode: AddToPortfolioManager.LaunchMode) {
|
||||
addToPortfolioListenersJob?.cancel()
|
||||
val manager = addToPortfolioManagerFactory.create(
|
||||
scope = modelScope,
|
||||
settings = AddToPortfolioManager.Settings(
|
||||
shouldSkipTokenActionsScreen = false,
|
||||
launchMode = launchMode,
|
||||
),
|
||||
analyticsParams = AddToPortfolioManager.AnalyticsParams(params.analyticsParams?.source),
|
||||
)
|
||||
manager.setTokenParams(params.token)
|
||||
when (val network = networksState.value) {
|
||||
is TokenNetworksState.NetworksAvailable -> manager.setTokenNetworks(network.networks)
|
||||
TokenNetworksState.NoNetworksAvailable -> manager.setTokenNetworks(emptyList())
|
||||
else -> Unit
|
||||
}
|
||||
addToPortfolioManager = manager
|
||||
addToPortfolioListenersJob = modelScope.launch {
|
||||
launch {
|
||||
manager.onDismiss.receiveAsFlow().collect {
|
||||
addToPortfolioSheetNavigation.dismiss()
|
||||
}
|
||||
}
|
||||
launch {
|
||||
manager.onSuccessAdded.receiveAsFlow().collect {
|
||||
addToPortfolioSheetNavigation.dismiss()
|
||||
}
|
||||
}
|
||||
launch {
|
||||
manager.onAddedTokenClick.receiveAsFlow().collect { result ->
|
||||
addToPortfolioSheetNavigation.dismiss()
|
||||
openTokenDetails(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initialLoad() {
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
|||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||
import com.tangem.domain.markets.*
|
||||
import com.tangem.domain.search.model.UserAssetSearchEntry
|
||||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
import com.tangem.domain.search.model.UserAssetSearchItem
|
||||
import com.tangem.domain.search.usecase.ClearSearchHistoryUseCase
|
||||
import com.tangem.domain.search.usecase.GetSearchResultsUseCase
|
||||
|
|
@ -213,7 +213,7 @@ internal class SearchModel @Inject constructor(
|
|||
stateController.update(UpdateSearchBarQueryTransformer(""))
|
||||
}
|
||||
|
||||
private fun onSingleUserAssetClick(entry: UserAssetSearchEntry) {
|
||||
private fun onSingleUserAssetClick(entry: UserAssetEntry) {
|
||||
searchAnalyticsHelper.sendPortfolioItemClicked(entry.currencyStatus.currency.symbol)
|
||||
appRouter.push(
|
||||
AppRoute.CurrencyDetails(
|
||||
|
|
@ -236,7 +236,7 @@ internal class SearchModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun onTokenSelectedFromGroup(entry: UserAssetSearchEntry) {
|
||||
private fun onTokenSelectedFromGroup(entry: UserAssetEntry) {
|
||||
bottomSheetNavigation.dismiss()
|
||||
onSingleUserAssetClick(entry)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
package com.tangem.features.feed.model.search
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.features.feed.components.search.SearchTokenSelectorComponent
|
||||
import com.tangem.features.feed.model.search.state.TokenSelectorStateController
|
||||
import com.tangem.features.feed.model.search.state.transformers.BuildTokenSelectorSectionsTransformer
|
||||
import com.tangem.features.feed.ui.search.state.TokenSelectorContentUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import javax.inject.Inject
|
||||
|
|
|
|||
|
|
@ -1,12 +1,14 @@
|
|||
package com.tangem.features.feed.model.search.converter
|
||||
|
||||
import com.tangem.common.ui.markets.toMarketsListItemPriceAnnotated
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.common.ui.extensions.networkIconResId
|
||||
import com.tangem.common.ui.markets.toMarketsListItemPriceAnnotated
|
||||
import com.tangem.common.ui.markets.tokenselector.BalanceDisplayState
|
||||
import com.tangem.common.ui.markets.tokenselector.UserAssetItemUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.common.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
|
|
@ -14,10 +16,8 @@ import com.tangem.core.ui.format.bigdecimal.format
|
|||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.search.model.UserAssetSearchEntry
|
||||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
import com.tangem.domain.search.model.UserAssetSearchItem
|
||||
import com.tangem.features.feed.ui.search.state.BalanceDisplayState
|
||||
import com.tangem.features.feed.ui.search.state.UserAssetItemUM
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
|
@ -26,7 +26,7 @@ import java.math.BigDecimal
|
|||
internal class UserAssetSearchItemConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val onSingleClick: (UserAssetSearchEntry) -> Unit,
|
||||
private val onSingleClick: (UserAssetEntry) -> Unit,
|
||||
private val onGroupedClick: (UserAssetSearchItem.Grouped) -> Unit,
|
||||
) : Converter<UserAssetSearchItem, UserAssetItemUM> {
|
||||
|
||||
|
|
@ -37,7 +37,7 @@ internal class UserAssetSearchItemConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun convertSingle(entry: UserAssetSearchEntry): UserAssetItemUM.Single {
|
||||
private fun convertSingle(entry: UserAssetEntry): UserAssetItemUM.Single {
|
||||
val currency = entry.currencyStatus.currency
|
||||
val value = entry.currencyStatus.value
|
||||
|
||||
|
|
@ -124,7 +124,7 @@ internal class UserAssetSearchItemConverter(
|
|||
}
|
||||
|
||||
private fun convertGroupedBalanceState(
|
||||
entries: List<UserAssetSearchEntry>,
|
||||
entries: List<UserAssetEntry>,
|
||||
symbol: String,
|
||||
decimals: Int,
|
||||
): BalanceDisplayState {
|
||||
|
|
@ -145,7 +145,7 @@ internal class UserAssetSearchItemConverter(
|
|||
}
|
||||
|
||||
private fun computeGroupBalance(
|
||||
entries: List<UserAssetSearchEntry>,
|
||||
entries: List<UserAssetEntry>,
|
||||
symbol: String,
|
||||
decimals: Int,
|
||||
): BalanceDisplayState.Loaded {
|
||||
|
|
@ -158,7 +158,7 @@ internal class UserAssetSearchItemConverter(
|
|||
}
|
||||
|
||||
private fun computeGroupBalanceFlickering(
|
||||
entries: List<UserAssetSearchEntry>,
|
||||
entries: List<UserAssetEntry>,
|
||||
symbol: String,
|
||||
decimals: Int,
|
||||
): BalanceDisplayState.Flickering {
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
package com.tangem.features.feed.model.search.state
|
||||
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.features.feed.model.search.state.transformers.TokenSelectorUMTransformer
|
||||
import com.tangem.features.feed.ui.search.state.TokenSelectorContentUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
package com.tangem.features.feed.model.search.state.transformers
|
||||
|
||||
import com.tangem.common.ui.account.toUM
|
||||
import com.tangem.common.ui.markets.tokenselector.AccountHeaderData
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorSectionUM
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.search.model.UserAssetSearchEntry
|
||||
import com.tangem.features.feed.ui.search.state.AccountHeaderData
|
||||
import com.tangem.features.feed.ui.search.state.TokenSelectorContentUM
|
||||
import com.tangem.features.feed.ui.search.state.TokenSelectorSectionUM
|
||||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
internal class BuildTokenSelectorSectionsTransformer(
|
||||
private val entries: List<UserAssetSearchEntry>,
|
||||
private val entries: List<UserAssetEntry>,
|
||||
private val appCurrency: AppCurrency,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val onTokenSelected: (UserAssetSearchEntry) -> Unit,
|
||||
private val onTokenSelected: (UserAssetEntry) -> Unit,
|
||||
) : TokenSelectorUMTransformer {
|
||||
|
||||
private val entryConverter = TokenSelectorEntryConverter(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.features.feed.model.search.state.transformers
|
||||
|
||||
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.common.ui.markets.tokenselector.BalanceDisplayState
|
||||
import com.tangem.common.ui.markets.tokenselector.UserAssetItemUM
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
|
|
@ -11,9 +13,7 @@ import com.tangem.core.ui.format.bigdecimal.format
|
|||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.search.model.UserAssetSearchEntry
|
||||
import com.tangem.features.feed.ui.search.state.BalanceDisplayState
|
||||
import com.tangem.features.feed.ui.search.state.UserAssetItemUM
|
||||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
import com.tangem.utils.StringsSigns
|
||||
import com.tangem.utils.converter.Converter
|
||||
import com.tangem.utils.extensions.orZero
|
||||
|
|
@ -22,12 +22,12 @@ import java.math.BigDecimal
|
|||
internal class TokenSelectorEntryConverter(
|
||||
private val appCurrency: AppCurrency,
|
||||
private val isBalanceHidden: Boolean,
|
||||
private val onTokenSelected: (UserAssetSearchEntry) -> Unit,
|
||||
) : Converter<UserAssetSearchEntry, UserAssetItemUM.Single> {
|
||||
private val onTokenSelected: (UserAssetEntry) -> Unit,
|
||||
) : Converter<UserAssetEntry, UserAssetItemUM.Single> {
|
||||
|
||||
private val iconConverter = CryptoCurrencyToIconStateConverter()
|
||||
|
||||
override fun convert(value: UserAssetSearchEntry): UserAssetItemUM.Single {
|
||||
override fun convert(value: UserAssetEntry): UserAssetItemUM.Single {
|
||||
val currency = value.currencyStatus.currency
|
||||
val currencyValue = value.currencyStatus.value
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
package com.tangem.features.feed.model.search.state.transformers
|
||||
|
||||
import com.tangem.features.feed.ui.search.state.TokenSelectorContentUM
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM
|
||||
|
||||
internal interface TokenSelectorUMTransformer {
|
||||
fun transform(prevState: TokenSelectorContentUM): TokenSelectorContentUM
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
package com.tangem.features.feed.model.search.state.transformers
|
||||
|
||||
import com.tangem.common.ui.markets.tokenselector.UserAssetItemUM
|
||||
import com.tangem.features.feed.ui.search.state.MarketSearchResultUM
|
||||
import com.tangem.features.feed.ui.search.state.SearchContentUM
|
||||
import com.tangem.features.feed.ui.search.state.SearchUM
|
||||
import com.tangem.features.feed.ui.search.state.UserAssetItemUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal class UpdateUserAssetsTransformer(
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
package com.tangem.features.feed.ui.components
|
||||
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.offset
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.ds.image.TangemIcon
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
private const val MIN_STACKED_ICON_COUNT = 1
|
||||
private const val MAX_STACKED_ICON_COUNT = 3
|
||||
private const val FIRST_BACK_LAYER_HORIZONTAL_OFFSET_MULTIPLIER = 1
|
||||
private const val SECOND_BACK_LAYER_HORIZONTAL_OFFSET_MULTIPLIER = 2
|
||||
private const val MIN_STACKED_COUNT_FOR_FIRST_BACK_LAYER = 2
|
||||
private const val FIRST_BACK_LAYER_ICON_ALPHA = 0.4f
|
||||
private const val SECOND_BACK_LAYER_ICON_ALPHA = 0.2f
|
||||
|
||||
@Composable
|
||||
fun LayeringIcons(
|
||||
tangemIconUM: TangemIconUM,
|
||||
modifier: Modifier = Modifier,
|
||||
count: Int = MIN_STACKED_ICON_COUNT,
|
||||
layerHorizontalShift: Dp = TangemTheme.dimens2.x1,
|
||||
iconSize: Dp = TangemTheme.dimens2.x10,
|
||||
) {
|
||||
require(count > 0)
|
||||
|
||||
val stackTrailingWidth = layerHorizontalShift * SECOND_BACK_LAYER_HORIZONTAL_OFFSET_MULTIPLIER
|
||||
|
||||
Box(
|
||||
modifier = modifier.size(
|
||||
width = iconSize + stackTrailingWidth,
|
||||
height = iconSize,
|
||||
),
|
||||
) {
|
||||
val baseIconModifier = Modifier
|
||||
.align(Alignment.TopStart)
|
||||
.size(iconSize)
|
||||
|
||||
if (count >= MAX_STACKED_ICON_COUNT) {
|
||||
TangemIcon(
|
||||
tangemIconUM = tangemIconUM,
|
||||
modifier = baseIconModifier
|
||||
.offset(x = layerHorizontalShift * SECOND_BACK_LAYER_HORIZONTAL_OFFSET_MULTIPLIER)
|
||||
.alpha(SECOND_BACK_LAYER_ICON_ALPHA),
|
||||
)
|
||||
}
|
||||
if (count >= MIN_STACKED_COUNT_FOR_FIRST_BACK_LAYER) {
|
||||
TangemIcon(
|
||||
tangemIconUM = tangemIconUM,
|
||||
modifier = baseIconModifier
|
||||
.offset(x = layerHorizontalShift * FIRST_BACK_LAYER_HORIZONTAL_OFFSET_MULTIPLIER)
|
||||
.alpha(FIRST_BACK_LAYER_ICON_ALPHA),
|
||||
)
|
||||
}
|
||||
TangemIcon(
|
||||
tangemIconUM = tangemIconUM,
|
||||
modifier = baseIconModifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Composable
|
||||
private fun LayeringIconsPreview() {
|
||||
val previewCurrencyIcon = TangemIconUM.Currency(
|
||||
currencyIconState = CurrencyIconState.TokenIcon(
|
||||
url = null,
|
||||
topBadgeIconResId = null,
|
||||
fallbackTint = TangemColorPalette.Black,
|
||||
fallbackBackground = TangemColorPalette.Meadow,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
)
|
||||
TangemThemePreview {
|
||||
Column(horizontalAlignment = Alignment.End) {
|
||||
LayeringIcons(count = MIN_STACKED_ICON_COUNT, tangemIconUM = previewCurrencyIcon)
|
||||
LayeringIcons(
|
||||
count = MAX_STACKED_ICON_COUNT,
|
||||
tangemIconUM = previewCurrencyIcon,
|
||||
)
|
||||
LayeringIcons(
|
||||
count = MIN_STACKED_COUNT_FOR_FIRST_BACK_LAYER,
|
||||
tangemIconUM = previewCurrencyIcon,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,9 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.common.ui.markets.MarketsListItem
|
||||
import com.tangem.common.ui.markets.MarketsListItemPlaceholder
|
||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
import com.tangem.common.ui.markets.tokenselector.GroupedUserAssetItem
|
||||
import com.tangem.common.ui.markets.tokenselector.SingleUserAssetItem
|
||||
import com.tangem.common.ui.markets.tokenselector.UserAssetItemUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
|
|
@ -33,8 +36,6 @@ import com.tangem.core.ui.extensions.resourceReference
|
|||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.ui.search.components.GroupedUserAssetItem
|
||||
import com.tangem.features.feed.ui.search.components.SingleUserAssetItem
|
||||
import com.tangem.features.feed.ui.search.state.*
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
|
|||
|
|
@ -1,175 +0,0 @@
|
|||
package com.tangem.features.feed.ui.search.components
|
||||
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.SpacerW
|
||||
import com.tangem.core.ui.components.flicker
|
||||
import com.tangem.core.ui.extensions.resolveAnnotatedReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.ui.search.state.BalanceDisplayState
|
||||
import com.tangem.utils.StringsSigns
|
||||
|
||||
@Composable
|
||||
internal fun BalanceColumn(
|
||||
balanceState: BalanceDisplayState,
|
||||
isBalanceHidden: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (isBalanceHidden) {
|
||||
HiddenBalance(modifier)
|
||||
return
|
||||
}
|
||||
when (balanceState) {
|
||||
is BalanceDisplayState.Loading -> LoadingBalanceColumn(modifier)
|
||||
is BalanceDisplayState.Flickering -> FlickeringBalanceColumn(balanceState, modifier)
|
||||
is BalanceDisplayState.Stale -> StaleBalanceColumn(balanceState, modifier)
|
||||
is BalanceDisplayState.Unreachable -> UnreachableBalanceColumn(modifier)
|
||||
is BalanceDisplayState.Loaded -> LoadedBalanceColumn(balanceState, modifier)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BalanceColumnLayout(modifier: Modifier = Modifier, content: @Composable ColumnScope.() -> Unit) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
horizontalAlignment = Alignment.End,
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp),
|
||||
content = content,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingBalanceColumn(modifier: Modifier = Modifier) {
|
||||
BalanceColumnLayout(modifier) {
|
||||
RectangleShimmer(modifier = Modifier.size(width = 108.dp, height = 20.dp), radius = 20.dp)
|
||||
RectangleShimmer(modifier = Modifier.size(width = 64.dp, height = 16.dp), radius = 20.dp)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun FlickeringBalanceColumn(state: BalanceDisplayState.Flickering, modifier: Modifier = Modifier) {
|
||||
val flickerModifier = Modifier.flicker(isFlickering = true)
|
||||
BalanceColumnLayout(modifier) {
|
||||
Text(
|
||||
modifier = flickerModifier,
|
||||
text = state.fiatBalance.resolveAnnotatedReference(),
|
||||
style = TangemTheme.typography2.bodyMedium16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Text(
|
||||
modifier = flickerModifier,
|
||||
text = state.cryptoBalance.resolveReference(),
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun StaleBalanceColumn(state: BalanceDisplayState.Stale, modifier: Modifier = Modifier) {
|
||||
BalanceColumnLayout(modifier) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
modifier = Modifier.size(16.dp),
|
||||
painter = painterResource(id = R.drawable.ic_error_sync_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors2.markers.iconGray,
|
||||
)
|
||||
SpacerW(TangemTheme.dimens2.x0_5)
|
||||
Text(
|
||||
text = state.fiatBalance.resolveAnnotatedReference(),
|
||||
style = TangemTheme.typography2.bodyMedium16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
Text(
|
||||
text = state.cryptoBalance.resolveReference(),
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UnreachableBalanceColumn(modifier: Modifier = Modifier) {
|
||||
BalanceColumnLayout(modifier) {
|
||||
Text(
|
||||
text = StringsSigns.DASH_SIGN,
|
||||
style = TangemTheme.typography2.bodyMedium16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.common_unreachable),
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.status.attention,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
SpacerW(TangemTheme.dimens2.x0_5)
|
||||
Icon(
|
||||
modifier = Modifier.size(16.dp),
|
||||
painter = painterResource(id = R.drawable.ic_alert_triange_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors2.graphic.status.attention,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadedBalanceColumn(state: BalanceDisplayState.Loaded, modifier: Modifier = Modifier) {
|
||||
BalanceColumnLayout(modifier) {
|
||||
Text(
|
||||
text = state.fiatBalance.resolveAnnotatedReference(),
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
)
|
||||
Text(
|
||||
text = state.cryptoBalance.resolveReference(),
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun HiddenBalance(modifier: Modifier = Modifier) {
|
||||
BalanceColumnLayout(modifier) {
|
||||
Text(
|
||||
text = StringsSigns.THREE_STARS,
|
||||
style = TangemTheme.typography2.bodySemibold16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
)
|
||||
Text(
|
||||
text = StringsSigns.THREE_STARS,
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
package com.tangem.features.feed.ui.search.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import com.tangem.core.ui.ds.button.*
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.ds.row.TangemRowContainer
|
||||
import com.tangem.core.ui.ds.row.TangemRowLayoutId
|
||||
import com.tangem.core.ui.extensions.pluralStringResourceSafe
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.impl.R
|
||||
import com.tangem.features.feed.ui.components.LayeringIcons
|
||||
import com.tangem.features.feed.ui.search.state.UserAssetItemUM
|
||||
|
||||
@Composable
|
||||
internal fun GroupedUserAssetItem(item: UserAssetItemUM.Grouped, modifier: Modifier = Modifier) {
|
||||
Box(
|
||||
modifier = modifier.background(
|
||||
color = TangemTheme.colors2.surface.level3,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens2.x5),
|
||||
),
|
||||
) {
|
||||
TangemRowContainer(
|
||||
modifier = Modifier.clickable(onClick = item.onClick),
|
||||
content = {
|
||||
LayeringIcons(
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = TangemRowLayoutId.HEAD)
|
||||
.padding(end = TangemTheme.dimens2.x1),
|
||||
tangemIconUM = item.icon,
|
||||
count = item.tokensCount,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
|
||||
text = item.tokenName,
|
||||
style = TangemTheme.typography2.bodyMedium16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.MiddleEllipsis,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.START_BOTTOM),
|
||||
text = pluralStringResourceSafe(R.plurals.common_tokens_count, item.tokensCount, item.tokensCount),
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
|
||||
BalanceColumn(
|
||||
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_TOP),
|
||||
balanceState = item.balanceState,
|
||||
isBalanceHidden = item.isBalanceHidden,
|
||||
)
|
||||
|
||||
TangemButton(
|
||||
modifier = Modifier
|
||||
.padding(start = TangemTheme.dimens2.x2)
|
||||
.layoutId(TangemRowLayoutId.TAIL),
|
||||
buttonUM = TangemButtonUM(
|
||||
type = TangemButtonType.Secondary,
|
||||
tangemIconUM = TangemIconUM.Icon(
|
||||
iconRes = R.drawable.ic_chevron_24,
|
||||
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
|
||||
),
|
||||
shape = TangemButtonShape.Rounded,
|
||||
size = TangemButtonSize.X10,
|
||||
onClick = item.onClick,
|
||||
),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,119 +0,0 @@
|
|||
package com.tangem.features.feed.ui.search.components
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.layout.layoutId
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeInPercent
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeState
|
||||
import com.tangem.core.ui.ds.image.TangemIcon
|
||||
import com.tangem.core.ui.ds.row.TangemRowContainer
|
||||
import com.tangem.core.ui.ds.row.TangemRowLayoutId
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.ui.search.state.BalanceDisplayState
|
||||
import com.tangem.features.feed.ui.search.state.UserAssetItemUM
|
||||
|
||||
@Composable
|
||||
fun SingleUserAssetItem(shouldUsePriceBlock: Boolean, item: UserAssetItemUM.Single, modifier: Modifier = Modifier) {
|
||||
TangemRowContainer(
|
||||
modifier = modifier.clickable(onClick = item.onClick),
|
||||
content = {
|
||||
TangemIcon(
|
||||
modifier = Modifier
|
||||
.layoutId(layoutId = TangemRowLayoutId.HEAD)
|
||||
.size(40.dp)
|
||||
.padding(end = TangemTheme.dimens2.x1),
|
||||
tangemIconUM = item.icon,
|
||||
)
|
||||
|
||||
Text(
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.START_TOP),
|
||||
text = item.tokenName,
|
||||
style = TangemTheme.typography2.bodyMedium16,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.MiddleEllipsis,
|
||||
)
|
||||
|
||||
if (shouldUsePriceBlock) {
|
||||
PriceBlock(
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.START_BOTTOM),
|
||||
priceChangeState = item.priceChangeState,
|
||||
fiatRate = item.fiatRate,
|
||||
balanceState = item.balanceState,
|
||||
)
|
||||
} else {
|
||||
Text(
|
||||
modifier = Modifier.layoutId(TangemRowLayoutId.START_BOTTOM),
|
||||
text = item.networkName,
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.secondary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.MiddleEllipsis,
|
||||
)
|
||||
}
|
||||
|
||||
BalanceColumn(
|
||||
modifier = Modifier.layoutId(layoutId = TangemRowLayoutId.END_TOP),
|
||||
balanceState = item.balanceState,
|
||||
isBalanceHidden = item.isBalanceHidden,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PriceBlock(
|
||||
priceChangeState: PriceChangeState,
|
||||
fiatRate: String?,
|
||||
balanceState: BalanceDisplayState,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val isDisabled = remember(balanceState) {
|
||||
balanceState is BalanceDisplayState.Unreachable
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
|
||||
) {
|
||||
if (fiatRate != null) {
|
||||
Text(
|
||||
text = fiatRate,
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = if (isDisabled) {
|
||||
TangemTheme.colors2.text.status.disabled
|
||||
} else {
|
||||
TangemTheme.colors2.text.neutral.secondary
|
||||
},
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
|
||||
AnimatedContent(
|
||||
targetState = priceChangeState,
|
||||
contentKey = { it::class },
|
||||
) { animatedState ->
|
||||
when (animatedState) {
|
||||
is PriceChangeState.Content -> {
|
||||
PriceChangeInPercent(
|
||||
valueInPercent = animatedState.valueInPercent,
|
||||
type = animatedState.type,
|
||||
textStyle = TangemTheme.typography2.captionMedium12,
|
||||
isDisabled = isDisabled,
|
||||
)
|
||||
}
|
||||
PriceChangeState.Unknown -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,146 +0,0 @@
|
|||
package com.tangem.features.feed.ui.search.components
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.core.EaseOut
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.Fade
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetType
|
||||
import com.tangem.core.ui.components.haze.hazeEffectTangem
|
||||
import com.tangem.core.ui.components.haze.hazeSourceTangem
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBar
|
||||
import com.tangem.core.ui.ds.topbar.TangemTopBarType
|
||||
import com.tangem.core.ui.extensions.clickableSingle
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.features.feed.ui.search.preview.TokenSelectorContentPreviewProvider
|
||||
import com.tangem.features.feed.ui.search.state.TokenSelectorContentUM
|
||||
import dev.chrisbanes.haze.HazeProgressive
|
||||
import dev.chrisbanes.haze.HazeState
|
||||
import dev.chrisbanes.haze.rememberHazeState
|
||||
|
||||
@Composable
|
||||
internal fun TokenSelectorBottomSheet(config: TangemBottomSheetConfig) {
|
||||
TangemBottomSheet<TokenSelectorContentUM>(
|
||||
config = config,
|
||||
type = TangemBottomSheetType.Modal,
|
||||
containerColor = TangemTheme.colors2.surface.level2,
|
||||
content = { content ->
|
||||
TokenSelectorContent(content, config.onDismissRequest)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenSelectorContent(content: TokenSelectorContentUM, onDismiss: () -> Unit) {
|
||||
val hazeState = rememberHazeState()
|
||||
var topBarHeight by remember { mutableStateOf(0.dp) }
|
||||
|
||||
Box(modifier = Modifier.fillMaxWidth()) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.hazeSourceTangem(state = hazeState, 1f),
|
||||
contentPadding = PaddingValues(
|
||||
start = TangemTheme.dimens2.x4,
|
||||
end = TangemTheme.dimens2.x4,
|
||||
top = topBarHeight,
|
||||
bottom = TangemTheme.dimens2.x10,
|
||||
),
|
||||
) {
|
||||
tokenSelectorSectionItems(content.sections)
|
||||
}
|
||||
TokenSelectorSheetTopBar(
|
||||
modifier = Modifier.align(Alignment.TopEnd),
|
||||
onDismiss = onDismiss,
|
||||
hazeState = hazeState,
|
||||
onChangeHeight = { topBarHeight = it },
|
||||
)
|
||||
Fade(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter),
|
||||
height = TangemTheme.dimens2.x10,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenSelectorSheetTopBar(
|
||||
hazeState: HazeState,
|
||||
onDismiss: () -> Unit,
|
||||
onChangeHeight: (Dp) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val bgColor = TangemTheme.colors2.surface.level2
|
||||
val density = LocalDensity.current
|
||||
TangemTopBar(
|
||||
modifier = modifier
|
||||
.onGloballyPositioned { coordinates ->
|
||||
if (coordinates.size.height > 0) {
|
||||
with(density) {
|
||||
onChangeHeight(coordinates.size.height.toDp())
|
||||
}
|
||||
}
|
||||
}
|
||||
.hazeEffectTangem(state = hazeState) {
|
||||
backgroundColor = bgColor
|
||||
progressive = HazeProgressive.verticalGradient(
|
||||
startIntensity = .55f,
|
||||
endIntensity = 0f,
|
||||
preferPerformance = true,
|
||||
easing = EaseOut,
|
||||
)
|
||||
},
|
||||
type = TangemTopBarType.BottomSheet,
|
||||
title = resourceReference(R.string.markets_search_portfolio_header),
|
||||
endContent = {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_close_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors2.graphic.neutral.primary,
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens2.x11)
|
||||
.background(
|
||||
color = TangemTheme.colors2.button.backgroundSecondary,
|
||||
shape = CircleShape,
|
||||
)
|
||||
.clickableSingle(onClick = onDismiss)
|
||||
.padding(TangemTheme.dimens2.x2_5),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(name = "Dark Theme", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun TokenSelectorBottomSheetPreview(
|
||||
@PreviewParameter(TokenSelectorContentPreviewProvider::class) content: TokenSelectorContentUM,
|
||||
) {
|
||||
TangemThemePreviewRedesign {
|
||||
TokenSelectorBottomSheet(
|
||||
config = TangemBottomSheetConfig(
|
||||
onDismissRequest = {},
|
||||
content = content,
|
||||
isShown = true,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,140 +0,0 @@
|
|||
package com.tangem.features.feed.ui.search.components
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
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.TextOverflow
|
||||
import com.tangem.common.ui.account.getResId
|
||||
import com.tangem.common.ui.account.getUiColor
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.SpacerH
|
||||
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.feed.ui.search.state.AccountHeaderData
|
||||
import com.tangem.features.feed.ui.search.state.TokenSelectorSectionUM
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal fun LazyListScope.tokenSelectorSectionItems(sections: ImmutableList<TokenSelectorSectionUM>) {
|
||||
sections.forEachIndexed { index, section ->
|
||||
when (section) {
|
||||
is TokenSelectorSectionUM.WalletHeader -> {
|
||||
item(key = "wallet_${section.walletName}_$index") {
|
||||
WalletHeaderSection(section)
|
||||
}
|
||||
}
|
||||
is TokenSelectorSectionUM.TokenGroup -> {
|
||||
if (section.items.isEmpty()) return@forEachIndexed
|
||||
|
||||
if (index > 0 && sections[index - 1] is TokenSelectorSectionUM.TokenGroup) {
|
||||
item(key = "spacer_before_group_$index") {
|
||||
SpacerH(TangemTheme.dimens2.x2)
|
||||
}
|
||||
}
|
||||
|
||||
val lastIndex = if (section.accountHeader != null) {
|
||||
section.items.size
|
||||
} else {
|
||||
section.items.lastIndex.coerceAtLeast(0)
|
||||
}
|
||||
|
||||
section.accountHeader?.let { header ->
|
||||
item(key = "account_header_$index") {
|
||||
TokenGroupAccountHeaderRow(
|
||||
data = header,
|
||||
modifier = Modifier.tokenGroupRowDecoration(
|
||||
currentIndex = 0,
|
||||
lastIndex = lastIndex,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val indexOffset = if (section.accountHeader != null) 1 else 0
|
||||
section.items.forEachIndexed { itemIndex, single ->
|
||||
item(key = "token_${single.id}_$index") {
|
||||
SingleUserAssetItem(
|
||||
item = single,
|
||||
modifier = Modifier.tokenGroupRowDecoration(
|
||||
currentIndex = indexOffset + itemIndex,
|
||||
lastIndex = lastIndex,
|
||||
),
|
||||
shouldUsePriceBlock = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Modifier.tokenGroupRowDecoration(currentIndex: Int, lastIndex: Int): Modifier =
|
||||
this.roundedShapeItemDecoration(
|
||||
currentIndex = currentIndex,
|
||||
lastIndex = lastIndex,
|
||||
addDefaultPadding = false,
|
||||
radius = TangemTheme.dimens2.x6,
|
||||
backgroundColor = TangemTheme.colors2.surface.level3,
|
||||
)
|
||||
|
||||
@Composable
|
||||
private fun WalletHeaderSection(section: TokenSelectorSectionUM.WalletHeader) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.padding(top = TangemTheme.dimens2.x4, bottom = TangemTheme.dimens2.x2)
|
||||
.padding(horizontal = TangemTheme.dimens2.x3),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x1),
|
||||
) {
|
||||
Text(
|
||||
text = section.walletName,
|
||||
style = TangemTheme.typography2.subheadlineMedium14,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_key_card_20),
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x5),
|
||||
tint = TangemTheme.colors2.graphic.neutral.tertiary,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TokenGroupAccountHeaderRow(data: AccountHeaderData, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = TangemTheme.dimens2.x4, bottom = TangemTheme.dimens2.x2)
|
||||
.padding(horizontal = TangemTheme.dimens2.x4),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens2.x2),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens2.x4),
|
||||
imageVector = ImageVector.vectorResource(data.cryptoPortfolioIcon.value.getResId()),
|
||||
tint = data.cryptoPortfolioIcon.color.getUiColor(),
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = data.accountName.resolveReference(),
|
||||
style = TangemTheme.typography2.captionMedium12,
|
||||
color = TangemTheme.colors2.text.neutral.primary,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,8 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.charts.state.MarketChartRawData
|
||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
import com.tangem.common.ui.markets.tokenselector.BalanceDisplayState
|
||||
import com.tangem.common.ui.markets.tokenselector.UserAssetItemUM
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
|
|
|
|||
|
|
@ -1,122 +0,0 @@
|
|||
package com.tangem.features.feed.ui.search.preview
|
||||
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeState
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.features.feed.ui.search.state.AccountHeaderData
|
||||
import com.tangem.features.feed.ui.search.state.BalanceDisplayState
|
||||
import com.tangem.features.feed.ui.search.state.TokenSelectorContentUM
|
||||
import com.tangem.features.feed.ui.search.state.TokenSelectorSectionUM
|
||||
import com.tangem.features.feed.ui.search.state.UserAssetItemUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Suppress("StringLiteralDuplication")
|
||||
internal class TokenSelectorContentPreviewProvider :
|
||||
CollectionPreviewParameterProvider<TokenSelectorContentUM>(
|
||||
listOf(
|
||||
tokenSelectorPreviewSimple(),
|
||||
tokenSelectorPreviewWithAccountHeaders(),
|
||||
tokenSelectorPreviewMultiWallet(),
|
||||
),
|
||||
)
|
||||
|
||||
private fun tokenSelectorPreviewSimple(): TokenSelectorContentUM {
|
||||
return TokenSelectorContentUM(
|
||||
sections = persistentListOf(
|
||||
TokenSelectorSectionUM.TokenGroup(
|
||||
accountHeader = null,
|
||||
items = persistentListOf(
|
||||
previewTokenItem(id = "eth", name = "Ethereum", symbol = "ETH"),
|
||||
previewTokenItem(id = "btc", name = "Bitcoin", symbol = "BTC"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun tokenSelectorPreviewWithAccountHeaders(): TokenSelectorContentUM {
|
||||
val accountIcon = CryptoPortfolioIcon.ofCustomAccount(
|
||||
value = CryptoPortfolioIcon.Icon.Wallet,
|
||||
color = CryptoPortfolioIcon.Color.CaribbeanBlue,
|
||||
)
|
||||
return TokenSelectorContentUM(
|
||||
sections = persistentListOf(
|
||||
TokenSelectorSectionUM.TokenGroup(
|
||||
accountHeader = AccountHeaderData(
|
||||
accountName = stringReference(value = "Main account"),
|
||||
cryptoPortfolioIcon = accountIcon,
|
||||
),
|
||||
items = persistentListOf(
|
||||
previewTokenItem(id = "eth_main", name = "Ethereum", symbol = "ETH"),
|
||||
),
|
||||
),
|
||||
TokenSelectorSectionUM.TokenGroup(
|
||||
accountHeader = AccountHeaderData(
|
||||
accountName = stringReference(value = "Trading"),
|
||||
cryptoPortfolioIcon = accountIcon,
|
||||
),
|
||||
items = persistentListOf(
|
||||
previewTokenItem(id = "sol_trade", name = "Solana", symbol = "SOL"),
|
||||
previewTokenItem(id = "avax_trade", name = "Avalanche", symbol = "AVAX"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun tokenSelectorPreviewMultiWallet(): TokenSelectorContentUM {
|
||||
return TokenSelectorContentUM(
|
||||
sections = persistentListOf(
|
||||
TokenSelectorSectionUM.WalletHeader(walletName = "Cold wallet"),
|
||||
TokenSelectorSectionUM.TokenGroup(
|
||||
accountHeader = null,
|
||||
items = persistentListOf(
|
||||
previewTokenItem(id = "btc_cold", name = "Bitcoin", symbol = "BTC"),
|
||||
),
|
||||
),
|
||||
TokenSelectorSectionUM.WalletHeader(walletName = "Hot wallet"),
|
||||
TokenSelectorSectionUM.TokenGroup(
|
||||
accountHeader = null,
|
||||
items = persistentListOf(
|
||||
previewTokenItem(id = "eth_hot", name = "Ethereum", symbol = "ETH"),
|
||||
previewTokenItem(id = "usdt_hot", name = "Tether", symbol = "USDT"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun previewTokenItem(id: String, name: String, symbol: String): UserAssetItemUM.Single {
|
||||
val cryptoRef = stringReference(value = "1.234 $symbol")
|
||||
val fiatRef = stringReference(value = "$1,234.56")
|
||||
return UserAssetItemUM.Single(
|
||||
id = id,
|
||||
icon = TangemIconUM.Currency(
|
||||
CurrencyIconState.CoinIcon(
|
||||
url = null,
|
||||
fallbackResId = R.drawable.ic_ethereumpow_22,
|
||||
isGrayscale = false,
|
||||
shouldShowCustomBadge = false,
|
||||
),
|
||||
),
|
||||
tokenName = name,
|
||||
tokenSymbol = symbol,
|
||||
fiatRate = "$98,765.43",
|
||||
priceChangeState = PriceChangeState.Content(
|
||||
valueInPercent = "+2.34%",
|
||||
type = PriceChangeType.UP,
|
||||
),
|
||||
balanceState = BalanceDisplayState.Loaded(
|
||||
cryptoBalance = cryptoRef,
|
||||
fiatBalance = fiatRef,
|
||||
),
|
||||
isBalanceHidden = false,
|
||||
onClick = {},
|
||||
networkName = "Ethereum",
|
||||
)
|
||||
}
|
||||
|
|
@ -19,10 +19,10 @@ import com.tangem.core.ui.ds.image.TangemIconUM
|
|||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||
import com.tangem.features.feed.ui.search.components.GroupedUserAssetItem
|
||||
import com.tangem.features.feed.ui.search.components.SingleUserAssetItem
|
||||
import com.tangem.features.feed.ui.search.state.BalanceDisplayState
|
||||
import com.tangem.features.feed.ui.search.state.UserAssetItemUM
|
||||
import com.tangem.common.ui.markets.tokenselector.BalanceDisplayState
|
||||
import com.tangem.common.ui.markets.tokenselector.GroupedUserAssetItem
|
||||
import com.tangem.common.ui.markets.tokenselector.SingleUserAssetItem
|
||||
import com.tangem.common.ui.markets.tokenselector.UserAssetItemUM
|
||||
|
||||
/** Labeled UI state for [SingleUserAssetItem] previews (dropdown label in Studio). */
|
||||
internal data class SingleUserAssetItemPreviewScenario(
|
||||
|
|
|
|||
|
|
@ -2,10 +2,8 @@ package com.tangem.features.feed.ui.search.state
|
|||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.common.ui.markets.models.MarketsListItemUM
|
||||
import com.tangem.common.ui.markets.tokenselector.UserAssetItemUM
|
||||
import com.tangem.core.ui.components.fields.entity.SearchBarUM
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeState
|
||||
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class SearchUM(
|
||||
|
|
@ -43,59 +41,4 @@ sealed interface MarketSearchResultUM {
|
|||
data object Empty : MarketSearchResultUM
|
||||
}
|
||||
|
||||
data class TextHintItemUM(val text: String)
|
||||
|
||||
@Immutable
|
||||
sealed interface BalanceDisplayState {
|
||||
|
||||
data class Loaded(
|
||||
val cryptoBalance: TextReference,
|
||||
val fiatBalance: TextReference,
|
||||
) : BalanceDisplayState
|
||||
|
||||
data class Flickering(
|
||||
val cryptoBalance: TextReference,
|
||||
val fiatBalance: TextReference,
|
||||
) : BalanceDisplayState
|
||||
|
||||
data class Stale(
|
||||
val cryptoBalance: TextReference,
|
||||
val fiatBalance: TextReference,
|
||||
) : BalanceDisplayState
|
||||
|
||||
data object Loading : BalanceDisplayState
|
||||
data object Unreachable : BalanceDisplayState
|
||||
}
|
||||
|
||||
@Immutable
|
||||
sealed interface UserAssetItemUM {
|
||||
val id: String
|
||||
val icon: TangemIconUM
|
||||
val tokenName: String
|
||||
val tokenSymbol: String
|
||||
val onClick: () -> Unit
|
||||
|
||||
data class Single(
|
||||
override val id: String,
|
||||
override val icon: TangemIconUM,
|
||||
override val tokenName: String,
|
||||
override val tokenSymbol: String,
|
||||
val fiatRate: String?,
|
||||
val priceChangeState: PriceChangeState,
|
||||
val balanceState: BalanceDisplayState,
|
||||
val isBalanceHidden: Boolean,
|
||||
val networkName: String,
|
||||
override val onClick: () -> Unit,
|
||||
) : UserAssetItemUM
|
||||
|
||||
data class Grouped(
|
||||
override val id: String,
|
||||
override val icon: TangemIconUM,
|
||||
override val tokenName: String,
|
||||
override val tokenSymbol: String,
|
||||
val tokensCount: Int,
|
||||
val balanceState: BalanceDisplayState,
|
||||
val isBalanceHidden: Boolean,
|
||||
override val onClick: () -> Unit,
|
||||
) : UserAssetItemUM
|
||||
}
|
||||
data class TextHintItemUM(val text: String)
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
package com.tangem.features.feed.ui.search.state
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
@Immutable
|
||||
internal data class TokenSelectorContentUM(
|
||||
val sections: ImmutableList<TokenSelectorSectionUM>,
|
||||
) : TangemBottomSheetConfigContent
|
||||
|
||||
@Immutable
|
||||
internal data class AccountHeaderData(
|
||||
val accountName: TextReference,
|
||||
val cryptoPortfolioIcon: CryptoPortfolioIcon,
|
||||
)
|
||||
|
||||
@Immutable
|
||||
internal sealed interface TokenSelectorSectionUM {
|
||||
|
||||
data class WalletHeader(val walletName: String) : TokenSelectorSectionUM
|
||||
|
||||
data class TokenGroup(
|
||||
val accountHeader: AccountHeaderData?,
|
||||
val items: ImmutableList<UserAssetItemUM.Single>,
|
||||
) : TokenSelectorSectionUM
|
||||
}
|
||||
|
|
@ -8,9 +8,9 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.search.model.UserAssetSearchEntry
|
||||
import com.tangem.features.feed.ui.search.state.TokenSelectorContentUM
|
||||
import com.tangem.features.feed.ui.search.state.TokenSelectorSectionUM
|
||||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorContentUM
|
||||
import com.tangem.common.ui.markets.tokenselector.TokenSelectorSectionUM
|
||||
import io.mockk.*
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
|
|
@ -22,7 +22,7 @@ import java.math.BigDecimal
|
|||
class BuildTokenSelectorSectionsTransformerTest {
|
||||
|
||||
private val appCurrency: AppCurrency = AppCurrency.Default
|
||||
private val onTokenSelected: (UserAssetSearchEntry) -> Unit = mockk(relaxed = true)
|
||||
private val onTokenSelected: (UserAssetEntry) -> Unit = mockk(relaxed = true)
|
||||
private val prevState = TokenSelectorContentUM(sections = persistentListOf())
|
||||
|
||||
@BeforeEach
|
||||
|
|
@ -231,7 +231,7 @@ class BuildTokenSelectorSectionsTransformerTest {
|
|||
|
||||
// region Helpers
|
||||
|
||||
private fun createTransformer(entries: List<UserAssetSearchEntry>): BuildTokenSelectorSectionsTransformer {
|
||||
private fun createTransformer(entries: List<UserAssetEntry>): BuildTokenSelectorSectionsTransformer {
|
||||
return BuildTokenSelectorSectionsTransformer(
|
||||
entries = entries,
|
||||
appCurrency = appCurrency,
|
||||
|
|
@ -259,7 +259,7 @@ class BuildTokenSelectorSectionsTransformerTest {
|
|||
currencyId: String = "btc",
|
||||
currencyName: String = "Bitcoin",
|
||||
currencySymbol: String = "BTC",
|
||||
): UserAssetSearchEntry {
|
||||
): UserAssetEntry {
|
||||
val network = mockk<Network>(relaxed = true) {
|
||||
every { name } returns "Network"
|
||||
}
|
||||
|
|
@ -290,7 +290,7 @@ class BuildTokenSelectorSectionsTransformerTest {
|
|||
sources = CryptoCurrencyStatus.Sources(),
|
||||
)
|
||||
}
|
||||
return mockk<UserAssetSearchEntry> {
|
||||
return mockk<UserAssetEntry> {
|
||||
every { userWalletId } returns walletId
|
||||
every { userWalletName } returns walletName
|
||||
every { this@mockk.accountId } returns accountId
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ import com.tangem.domain.models.currency.CryptoCurrency
|
|||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
||||
import com.tangem.domain.models.network.Network
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.search.model.UserAssetSearchEntry
|
||||
import com.tangem.features.feed.ui.search.state.BalanceDisplayState
|
||||
import com.tangem.domain.models.portfolio.UserAssetEntry
|
||||
import com.tangem.common.ui.markets.tokenselector.BalanceDisplayState
|
||||
import io.mockk.clearMocks
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
|
|
@ -23,7 +23,7 @@ import java.math.BigDecimal
|
|||
class TokenSelectorEntryConverterTest {
|
||||
|
||||
private val appCurrency: AppCurrency = AppCurrency.Default
|
||||
private val onTokenSelected: (UserAssetSearchEntry) -> Unit = mockk(relaxed = true)
|
||||
private val onTokenSelected: (UserAssetEntry) -> Unit = mockk(relaxed = true)
|
||||
|
||||
private lateinit var converter: TokenSelectorEntryConverter
|
||||
|
||||
|
|
@ -276,7 +276,7 @@ class TokenSelectorEntryConverterTest {
|
|||
fiatRate = BigDecimal("30000.0"),
|
||||
priceChange = BigDecimal("1.0"),
|
||||
),
|
||||
): UserAssetSearchEntry {
|
||||
): UserAssetEntry {
|
||||
val userWalletId = mockk<UserWalletId> {
|
||||
every { stringValue } returns walletId
|
||||
}
|
||||
|
|
@ -302,7 +302,7 @@ class TokenSelectorEntryConverterTest {
|
|||
every { this@mockk.currency } returns currency
|
||||
every { this@mockk.value } returns value
|
||||
}
|
||||
return mockk<UserAssetSearchEntry> {
|
||||
return mockk<UserAssetEntry> {
|
||||
every { this@mockk.userWalletId } returns userWalletId
|
||||
every { this@mockk.userWalletName } returns "Wallet"
|
||||
every { this@mockk.accountId } returns accountIdMock
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue