Updated on 2026-08-14

This commit is contained in:
Tangem 2024-08-22 13:01:59 +03:00
parent 56992e4431
commit 3e57f20092
30 changed files with 270 additions and 55 deletions

View file

@ -1,6 +1,7 @@
plugins {
alias(deps.plugins.android.library)
alias(deps.plugins.kotlin.android)
alias(deps.plugins.kotlin.serialization)
id("kotlin-parcelize")
id("configuration")
}
@ -15,4 +16,10 @@ dependencies {
/* Project - Core */
implementation(projects.core.decompose)
implementation(projects.core.ui)
/* Project - Domain */
implementation(projects.domain.core)
implementation(projects.domain.tokens.models)
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.markets.models)
}

View file

@ -1,4 +1,4 @@
package com.tangem.features.markets.details.api
package com.tangem.features.markets.details
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
@ -7,7 +7,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.features.markets.component.BottomSheetState
import com.tangem.features.markets.entry.BottomSheetState
import kotlinx.serialization.Serializable
@Stable

View file

@ -1,4 +1,4 @@
package com.tangem.features.markets.details.api
package com.tangem.features.markets.details
import com.tangem.domain.core.serialization.SerializedBigDecimal
import com.tangem.domain.markets.TokenMarket

View file

@ -1,4 +1,4 @@
package com.tangem.features.markets.component
package com.tangem.features.markets.entry
enum class BottomSheetState {
EXPANDED,

View file

@ -1,4 +1,4 @@
package com.tangem.features.markets.component
package com.tangem.features.markets.entry
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable

View file

@ -0,0 +1,20 @@
package com.tangem.features.markets.token.block
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.tokens.model.CryptoCurrency
import kotlinx.serialization.Serializable
@Stable
interface TokenMarketBlockComponent : ComposableContentComponent {
@Serializable
data class Params(
val cryptoCurrency: CryptoCurrency,
)
interface Factory {
fun create(appComponentContext: AppComponentContext, params: Params): TokenMarketBlockComponent
}
}

View file

@ -21,6 +21,7 @@ dependencies {
implementation(projects.domain.appCurrency)
implementation(projects.domain.appCurrency.models)
implementation(projects.domain.wallets.models)
implementation(projects.domain.tokens.models)
/* Compose */
implementation(deps.compose.coil)

View file

@ -8,8 +8,8 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.features.markets.component.BottomSheetState
import com.tangem.features.markets.details.api.MarketsTokenDetailsComponent
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.details.impl.model.MarketsTokenDetailsModel
import com.tangem.features.markets.details.impl.model.state.TokenNetworksState
import com.tangem.features.markets.details.impl.ui.MarketsTokenDetailsContent

View file

@ -1,6 +1,6 @@
package com.tangem.features.markets.details.impl.di
import com.tangem.features.markets.details.api.MarketsTokenDetailsComponent
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.details.impl.DefaultMarketsTokenDetailsComponent
import dagger.Binds
import dagger.Module

View file

@ -18,8 +18,8 @@ import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.*
import com.tangem.features.markets.component.BottomSheetState
import com.tangem.features.markets.details.api.MarketsTokenDetailsComponent
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.details.impl.model.converters.DescriptionConverter
import com.tangem.features.markets.details.impl.model.converters.TokenMarketInfoConverter
import com.tangem.features.markets.details.impl.model.formatter.*

View file

@ -1,18 +0,0 @@
package com.tangem.features.markets.di
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.markets.DefaultMarketsEntryComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface ComponentModule {
@Binds
@Singleton
fun bindMarketsListComponent(factory: DefaultMarketsEntryComponent.Factory): MarketsEntryComponent.Factory
}

View file

@ -1,7 +1,6 @@
package com.tangem.features.markets
package com.tangem.features.markets.entry.impl
import androidx.compose.animation.Animatable
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.tween
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
@ -18,10 +17,10 @@ import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarket
import com.tangem.features.markets.component.BottomSheetState
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.markets.details.api.MarketsTokenDetailsComponent
import com.tangem.features.markets.details.api.toSerializable
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.entry.MarketsEntryComponent
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.details.toSerializable
import com.tangem.features.markets.tokenlist.api.MarketsTokenListComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory

View file

@ -1,10 +1,10 @@
package com.tangem.features.markets
package com.tangem.features.markets.entry.impl
import androidx.compose.runtime.Immutable
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarket
import com.tangem.features.markets.details.api.MarketsTokenDetailsComponent
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.tokenlist.api.MarketsTokenListComponent
import kotlinx.serialization.Serializable
import javax.inject.Inject

View file

@ -0,0 +1,18 @@
package com.tangem.features.markets.entry.impl.di
import com.tangem.features.markets.entry.MarketsEntryComponent
import com.tangem.features.markets.entry.impl.DefaultMarketsEntryComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface ComponentModule {
@Binds
@Singleton
fun bindMarketsEntryComponent(factory: DefaultMarketsEntryComponent.Factory): MarketsEntryComponent.Factory
}

View file

@ -0,0 +1,33 @@
package com.tangem.features.markets.token.block.impl
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.ui.Modifier
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.markets.token.block.TokenMarketBlockComponent.Params
import com.tangem.features.markets.token.block.impl.model.TokenMarketBlockModel
import com.tangem.features.markets.token.block.impl.ui.TokenMarketBlock
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Stable
internal class DefaultTokenMarketBlockComponent @AssistedInject constructor(
@Assisted componentContext: AppComponentContext,
@Assisted params: Params,
) : TokenMarketBlockComponent, AppComponentContext by componentContext {
private val model: TokenMarketBlockModel = getOrCreateModel(params)
@Composable
override fun Content(modifier: Modifier) {
TokenMarketBlock(modifier)
}
@AssistedFactory
interface Factory : TokenMarketBlockComponent.Factory {
override fun create(appComponentContext: AppComponentContext, params: Params): DefaultTokenMarketBlockComponent
}
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.markets.token.block.impl.di
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.markets.token.block.impl.DefaultTokenMarketBlockComponent
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal interface ComponentModule {
@Binds
@Singleton
fun bindMarketsPortfolioComponent(
factory: DefaultTokenMarketBlockComponent.Factory,
): TokenMarketBlockComponent.Factory
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.markets.token.block.impl.di
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.decompose.model.Model
import com.tangem.features.markets.token.block.impl.model.TokenMarketBlockModel
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.multibindings.ClassKey
import dagger.multibindings.IntoMap
@Module
@InstallIn(DecomposeComponent::class)
internal interface ModelModule {
@Binds
@IntoMap
@ClassKey(TokenMarketBlockModel::class)
fun provideTokenMarketBlockModel(model: TokenMarketBlockModel): Model
}

View file

@ -0,0 +1,21 @@
package com.tangem.features.markets.token.block.impl.model
import androidx.compose.runtime.Stable
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import javax.inject.Inject
@Stable
@ComponentScoped
internal class TokenMarketBlockModel @Inject constructor(
paramsContainer: ParamsContainer,
override val dispatchers: CoroutineDispatcherProvider,
) : Model() {
val params = paramsContainer.require<TokenMarketBlockComponent.Params>()
// token price is available if cryptoCurrency.id.rawCurrencyId != null
}

View file

@ -0,0 +1,23 @@
package com.tangem.features.markets.token.block.impl.ui
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.block.information.InformationBlock
import com.tangem.core.ui.res.TangemTheme
@Composable
internal fun TokenMarketBlock(modifier: Modifier = Modifier) {
InformationBlock(
modifier = modifier,
title = {
Text(
text = "Token Market Block",
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.tertiary,
)
},
) {
Text("//TODO")
}
}

View file

@ -8,7 +8,7 @@ import androidx.compose.ui.unit.Dp
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarket
import com.tangem.features.markets.component.BottomSheetState
import com.tangem.features.markets.entry.BottomSheetState
@Stable
interface MarketsTokenListComponent {

View file

@ -9,7 +9,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.TokenMarket
import com.tangem.features.markets.component.BottomSheetState
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.tokenlist.api.MarketsTokenListComponent
import com.tangem.features.markets.tokenlist.impl.model.MarketsListModel
import com.tangem.features.markets.tokenlist.impl.ui.MarketsList

View file

@ -8,7 +8,7 @@ import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase
import com.tangem.domain.markets.TokenMarket
import com.tangem.features.markets.component.BottomSheetState
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListUMStateManager
import com.tangem.features.markets.tokenlist.impl.model.statemanager.MarketsListBatchFlowManager
import com.tangem.features.markets.tokenlist.impl.ui.state.ListUM

View file

@ -34,7 +34,7 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.markets.component.BottomSheetState
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.impl.R
import com.tangem.features.markets.tokenlist.impl.ui.components.MarketsListLazyColumn
import com.tangem.features.markets.tokenlist.impl.ui.components.MarketsListSortByBottomSheet

View file

@ -55,6 +55,7 @@ dependencies {
implementation(projects.core.deepLinks)
implementation(projects.core.deepLinks.global)
implementation(projects.core.featuretoggles)
implementation(projects.core.decompose)
implementation(projects.libs.crypto)
@ -87,5 +88,6 @@ dependencies {
implementation(projects.features.tokendetails.api)
implementation(projects.features.send.api)
implementation(projects.features.staking.api)
implementation(projects.features.markets.api)
}

View file

@ -1,17 +1,27 @@
package com.tangem.feature.tokendetails.presentation
import android.os.Bundle
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalLifecycleOwner
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.defaultComponentContext
import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.bundle.unbundle
import com.tangem.core.decompose.context.DefaultAppComponentContext
import com.tangem.core.decompose.di.DecomposeComponent
import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.components.NavigationBar3ButtonsScrim
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.feature.tokendetails.presentation.router.InnerTokenDetailsRouter
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.TokenDetailsScreen
import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsViewModel
import com.tangem.features.markets.MarketsFeatureToggles
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
import com.tangem.features.tokendetails.navigation.TokenDetailsRouter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
@ -24,17 +34,57 @@ internal class TokenDetailsFragment : ComposeFragment() {
@Inject
lateinit var tokenDetailsRouter: TokenDetailsRouter
@Inject
internal lateinit var marketsFeatureToggles: MarketsFeatureToggles
@Inject
internal lateinit var coroutineDispatcherProvider: CoroutineDispatcherProvider
@Inject
internal lateinit var componentBuilder: DecomposeComponent.Builder
@Inject
internal lateinit var tokenMarketBlockComponentFactory: TokenMarketBlockComponent.Factory
private var tokenMarketBlockComponent: TokenMarketBlockComponent? = null
private val internalTokenDetailsRouter: InnerTokenDetailsRouter
get() = requireNotNull(tokenDetailsRouter as? InnerTokenDetailsRouter) {
"internalTokenDetailsRouter should be instance of InnerTokenDetailsRouter"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (marketsFeatureToggles.isFeatureEnabled) {
val appContext = DefaultAppComponentContext(
componentContext = defaultComponentContext(requireActivity().onBackPressedDispatcher),
messageHandler = uiDependencies.eventMessageHandler,
dispatchers = coroutineDispatcherProvider,
hiltComponentBuilder = componentBuilder,
)
val cryptoCurrency: CryptoCurrency = arguments
?.getBundle(AppRoute.CurrencyDetails.CRYPTO_CURRENCY_KEY)
?.unbundle(CryptoCurrency.serializer())
?: error("This screen can't open without `CryptoCurrency`")
tokenMarketBlockComponent = tokenMarketBlockComponentFactory.create(
appComponentContext = appContext,
params = TokenMarketBlockComponent.Params(cryptoCurrency = cryptoCurrency),
)
}
}
@Composable
override fun ScreenContent(modifier: Modifier) {
val viewModel = hiltViewModel<TokenDetailsViewModel>()
viewModel.router = this@TokenDetailsFragment.internalTokenDetailsRouter
LocalLifecycleOwner.current.lifecycle.addObserver(viewModel)
NavigationBar3ButtonsScrim()
TokenDetailsScreen(state = viewModel.uiState.collectAsStateWithLifecycle().value)
TokenDetailsScreen(
state = viewModel.uiState.collectAsStateWithLifecycle().value,
tokenMarketBlockComponent = tokenMarketBlockComponent,
)
}
}

View file

@ -52,12 +52,13 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.e
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.ExchangeStatusBottomSheetConfig
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.exchange.swapTransactionsItems
import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking.TokenStakingBlock
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
// TODO: Split to blocks [REDACTED_JIRA]
@Suppress("LongMethod")
@OptIn(ExperimentalMaterialApi::class, ExperimentalFoundationApi::class)
@Composable
internal fun TokenDetailsScreen(state: TokenDetailsState) {
internal fun TokenDetailsScreen(state: TokenDetailsState, tokenMarketBlockComponent: TokenMarketBlockComponent?) {
BackHandler(onBack = state.topAppBarConfig.onBackClick)
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
@ -139,12 +140,27 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) {
}
},
)
if (state.isMarketPriceAvailable) {
item(
key = MarketPriceBlockState::class.java,
contentType = MarketPriceBlockState::class.java,
content = { MarketPriceBlock(modifier = itemModifier, state = state.marketPriceBlockState) },
)
when {
tokenMarketBlockComponent != null -> {
item(
key = TokenMarketBlockComponent::class.java,
contentType = TokenMarketBlockComponent::class.java,
content = { tokenMarketBlockComponent.Content(modifier = itemModifier) },
)
}
state.isMarketPriceAvailable -> {
item(
key = MarketPriceBlockState::class.java,
contentType = MarketPriceBlockState::class.java,
content = {
MarketPriceBlock(
modifier = itemModifier,
state = state.marketPriceBlockState,
)
},
)
}
}
if (state.isStakingBlockShown) {
@ -219,7 +235,10 @@ private fun TokenDetailsScreenPreview(
@PreviewParameter(TokenDetailsScreenParameterProvider::class) state: TokenDetailsState,
) {
TangemThemePreview {
TokenDetailsScreen(state)
TokenDetailsScreen(
state = state,
tokenMarketBlockComponent = null,
)
}
}

View file

@ -11,7 +11,7 @@ import com.tangem.core.ui.UiDependencies
import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.features.markets.MarketsFeatureToggles
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.markets.entry.MarketsEntryComponent
import com.tangem.features.wallet.navigation.WalletRouter
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.AndroidEntryPoint

View file

@ -25,7 +25,7 @@ import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScree
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensViewModel
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.viewmodels.WalletViewModel
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.markets.entry.MarketsEntryComponent
import kotlin.properties.Delegates
/** Default implementation of wallet feature router */

View file

@ -4,7 +4,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.markets.entry.MarketsEntryComponent
import com.tangem.features.wallet.navigation.WalletRouter
/**

View file

@ -71,9 +71,9 @@ import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.VisaTxDe
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.balancesAndLimitsBlock
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.depositButton
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
import com.tangem.features.markets.component.BottomSheetState
import com.tangem.features.markets.component.BottomSheetState.*
import com.tangem.features.markets.component.MarketsEntryComponent
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.entry.BottomSheetState.*
import com.tangem.features.markets.entry.MarketsEntryComponent
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.launch