Updated on 2026-08-14
This commit is contained in:
parent
d6735f937f
commit
95aa4b7a0a
35 changed files with 839 additions and 209 deletions
|
|
@ -1,10 +1,8 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.markets.GetMarketsTokenListFlowUseCase
|
||||
import com.tangem.domain.markets.GetTokenMarketInfoUseCase
|
||||
import com.tangem.domain.markets.GetTokenPriceChartUseCase
|
||||
import com.tangem.domain.markets.GetTokenQuotesUseCase
|
||||
import com.tangem.domain.markets.*
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -37,7 +35,13 @@ object MarketsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetTokenQuotesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenQuotesUseCase {
|
||||
return GetTokenQuotesUseCase(marketsTokenRepository = marketsTokenRepository)
|
||||
fun provideTokenFullQuotesUseCase(marketsTokenRepository: MarketsTokenRepository): GetTokenFullQuotesUseCase {
|
||||
return GetTokenFullQuotesUseCase(marketsTokenRepository = marketsTokenRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetTokenQuotesUseCase(quotesRepository: QuotesRepository): GetTokenQuotesUseCase {
|
||||
return GetTokenQuotesUseCase(quotesRepository = quotesRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,7 @@ import com.tangem.features.details.component.DetailsComponent
|
|||
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
|
||||
import com.tangem.features.managetokens.ManageTokensToggles
|
||||
import com.tangem.features.managetokens.component.ManageTokensComponent
|
||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||
import com.tangem.features.pushnotifications.api.featuretoggles.PushNotificationsFeatureToggles
|
||||
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
|
||||
import com.tangem.features.send.api.navigation.SendRouter
|
||||
|
|
@ -50,6 +51,7 @@ internal class ChildFactory @Inject constructor(
|
|||
private val walletSettingsComponentFactory: WalletSettingsComponent.Factory,
|
||||
private val disclaimerComponentFactory: DisclaimerComponent.Factory,
|
||||
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
|
||||
private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory,
|
||||
private val sendRouter: SendRouter,
|
||||
private val tokenDetailsRouter: TokenDetailsRouter,
|
||||
private val walletRouter: WalletRouter,
|
||||
|
|
@ -185,6 +187,16 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = walletSettingsComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.MarketsTokenDetails -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = MarketsTokenDetailsComponent.Params(
|
||||
token = route.token,
|
||||
appCurrency = route.appCurrency,
|
||||
),
|
||||
componentFactory = marketsTokenDetailsComponentFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,8 @@ dependencies {
|
|||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.staking.models)
|
||||
implementation(projects.domain.markets.models)
|
||||
implementation(projects.domain.appCurrency.models)
|
||||
|
||||
/* Libs - Other */
|
||||
api(deps.kotlin.serialization)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import com.tangem.common.routing.bundle.RouteBundleParams
|
|||
import com.tangem.common.routing.bundle.bundle
|
||||
import com.tangem.common.routing.entity.SerializableIntent
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.TokenMarketParam
|
||||
import com.tangem.domain.qrscanning.models.SourceType
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
|
@ -262,4 +264,10 @@ sealed class AppRoute(val path: String) : Route {
|
|||
data class WalletSettings(
|
||||
val userWalletId: UserWalletId,
|
||||
) : AppRoute(path = "/wallet_settings/${userWalletId.stringValue}")
|
||||
|
||||
@Serializable
|
||||
data class MarketsTokenDetails(
|
||||
val token: TokenMarketParam,
|
||||
val appCurrency: AppCurrency,
|
||||
) : AppRoute(path = "/markets_token_details/${token.id}")
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package com.tangem.common.routing.utils
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
/**
|
||||
* Temporary solution to convert [AppRouter] to [Router].
|
||||
|
||||
* (through manual ComponentContext creation).
|
||||
*
|
||||
* **Will be removed when all screens will be migrated to Decompose.**
|
||||
*
|
||||
* @return [Router] that wraps [AppRouter].
|
||||
*/
|
||||
fun AppRouter.asRouter(): Router {
|
||||
return RouterProxy(appRouter = this)
|
||||
}
|
||||
|
||||
private class RouterProxy(
|
||||
private val appRouter: AppRouter,
|
||||
) : Router {
|
||||
override fun push(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
(route as? AppRoute)?.let {
|
||||
appRouter.push(it, onComplete)
|
||||
}
|
||||
}
|
||||
|
||||
override fun replaceAll(vararg routes: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
routes.filterIsInstance<AppRoute>().let {
|
||||
appRouter.replaceAll(*it.toTypedArray(), onComplete = onComplete)
|
||||
}
|
||||
}
|
||||
|
||||
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
appRouter.pop(onComplete)
|
||||
}
|
||||
|
||||
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
(route as? AppRoute)?.let {
|
||||
appRouter.popTo(route, onComplete)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun popTo(routeClass: KClass<out Route>, onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
appRouter.popTo(routeClass as KClass<out AppRoute>, onComplete)
|
||||
}
|
||||
}
|
||||
|
|
@ -22,6 +22,11 @@ class PriceAndTimePointValuesConverter(
|
|||
private val formatYValuesCache = mutableMapOf<Double, BigDecimal>()
|
||||
private val formatXValuesCache = mutableMapOf<Double, BigDecimal>()
|
||||
|
||||
private data class Point(
|
||||
val x: BigDecimal,
|
||||
val y: BigDecimal,
|
||||
)
|
||||
|
||||
override fun convert(data: MarketChartData.Data): MarketChartRawData {
|
||||
formatYValuesCache.clear()
|
||||
formatXValuesCache.clear()
|
||||
|
|
@ -33,8 +38,10 @@ class PriceAndTimePointValuesConverter(
|
|||
)
|
||||
minMaxCache = cache
|
||||
|
||||
val normY = data.y.normalizeToDouble(min = cache.minY, max = cache.maxY)
|
||||
val normX = data.x.normalizeTime(min = cache.minX, max = cache.maxX)
|
||||
val points = data.x.zip(data.y) { x, y -> Point(x, y) }.sortedBy { it.x }
|
||||
|
||||
val normY = points.map { it.y }.normalizeToDouble(min = cache.minY, max = cache.maxY)
|
||||
val normX = points.map { it.x }.normalizeTime(min = cache.minX, max = cache.maxX)
|
||||
|
||||
return if (normX.size > MAX_POINTS) {
|
||||
LTThreeBuckets
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ class DefaultAppComponentContext(
|
|||
messageHandler: UiMessageHandler,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
override val hiltComponentBuilder: DecomposeComponent.Builder,
|
||||
private val replaceRouter: Router? = null,
|
||||
) : AppComponentContext, ComponentContext by componentContext {
|
||||
|
||||
override val tags: HashMap<String, Any> = HashMap()
|
||||
|
|
@ -31,5 +32,5 @@ class DefaultAppComponentContext(
|
|||
get() = instanceKeeper.getOrCreate { DefaultAppNavigationProvider() }
|
||||
|
||||
override val router: Router
|
||||
get() = instanceKeeper.getOrCreate { DefaultRouter(navigationProvider) }
|
||||
get() = replaceRouter ?: instanceKeeper.getOrCreate { DefaultRouter(navigationProvider) }
|
||||
}
|
||||
|
|
@ -1,6 +1,21 @@
|
|||
package com.tangem.core.ui.components.marketprice
|
||||
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
/** Price changing type */
|
||||
enum class PriceChangeType {
|
||||
UP, DOWN, NEUTRAL,
|
||||
;
|
||||
|
||||
companion object {
|
||||
@Suppress("MagicNumber")
|
||||
fun fromBigDecimal(priceChangePercent: BigDecimal): PriceChangeType {
|
||||
return when {
|
||||
priceChangePercent < BigDecimal.ZERO -> DOWN
|
||||
priceChangePercent.setScale(4, RoundingMode.HALF_UP) > BigDecimal.ZERO -> UP
|
||||
else -> NEUTRAL
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -108,6 +108,22 @@ internal class DefaultMarketsTokenRepository(
|
|||
return TokenChartConverter.convert(interval, response.getOrThrow())
|
||||
}
|
||||
|
||||
override suspend fun getChartPreview(
|
||||
fiatCurrencyCode: String,
|
||||
interval: PriceChangeInterval,
|
||||
tokenId: String,
|
||||
): TokenChart {
|
||||
val response = marketsApi.getCoinsListCharts(
|
||||
coinIds = tokenId,
|
||||
currency = fiatCurrencyCode,
|
||||
interval = interval.toRequestParam(),
|
||||
)
|
||||
|
||||
val chart = response.getOrThrow()[tokenId] ?: error("No chart preview data for token $tokenId")
|
||||
|
||||
return TokenChartConverter.convert(interval, chart)
|
||||
}
|
||||
|
||||
override suspend fun getTokenInfo(
|
||||
fiatCurrencyCode: String,
|
||||
tokenId: String,
|
||||
|
|
|
|||
|
|
@ -11,12 +11,15 @@ android {
|
|||
|
||||
|
||||
dependencies {
|
||||
/* Domain */
|
||||
api(projects.domain.appCurrency.models)
|
||||
api(projects.domain.core)
|
||||
api(projects.core.pagination)
|
||||
api(projects.domain.markets.models)
|
||||
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(projects.domain.tokens.models)
|
||||
implementation(projects.domain.tokens)
|
||||
|
||||
/* Utils */
|
||||
implementation(deps.kotlin.serialization)
|
||||
implementation(projects.core.utils)
|
||||
}
|
||||
|
|
@ -1,35 +1,32 @@
|
|||
package com.tangem.features.markets.details
|
||||
package com.tangem.domain.markets
|
||||
|
||||
import com.tangem.domain.core.serialization.SerializedBigDecimal
|
||||
import com.tangem.domain.markets.TokenMarket
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class TokenMarketSerializable(
|
||||
data class TokenMarketParam(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val symbol: String,
|
||||
val marketCap: SerializedBigDecimal?,
|
||||
val tokenQuotes: Quotes,
|
||||
val imageUrl: String,
|
||||
val imageUrl: String?,
|
||||
) {
|
||||
|
||||
@Serializable
|
||||
data class Quotes(
|
||||
val currentPrice: SerializedBigDecimal,
|
||||
val h24Percent: SerializedBigDecimal,
|
||||
val weekPercent: SerializedBigDecimal,
|
||||
val monthPercent: SerializedBigDecimal,
|
||||
val weekPercent: SerializedBigDecimal?,
|
||||
val monthPercent: SerializedBigDecimal?,
|
||||
)
|
||||
}
|
||||
|
||||
fun TokenMarket.toSerializable(): TokenMarketSerializable {
|
||||
return TokenMarketSerializable(
|
||||
fun TokenMarket.toSerializableParam(): TokenMarketParam {
|
||||
return TokenMarketParam(
|
||||
id = id,
|
||||
name = name,
|
||||
symbol = symbol,
|
||||
marketCap = marketCap,
|
||||
tokenQuotes = TokenMarketSerializable.Quotes(
|
||||
tokenQuotes = TokenMarketParam.Quotes(
|
||||
currentPrice = tokenQuotesShort.currentPrice,
|
||||
h24Percent = tokenQuotesShort.h24ChangePercent,
|
||||
weekPercent = tokenQuotesShort.weekChangePercent,
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.domain.markets
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
|
||||
class GetTokenFullQuotesUseCase(
|
||||
private val marketsTokenRepository: MarketsTokenRepository,
|
||||
) {
|
||||
suspend operator fun invoke(appCurrency: AppCurrency, tokenId: String): Either<Unit, TokenQuotes> {
|
||||
return Either.catch {
|
||||
marketsTokenRepository.getTokenQuotes(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
tokenId = tokenId,
|
||||
)
|
||||
}.mapLeft {}
|
||||
}
|
||||
}
|
||||
|
|
@ -12,13 +12,22 @@ class GetTokenPriceChartUseCase(
|
|||
appCurrency: AppCurrency,
|
||||
interval: PriceChangeInterval,
|
||||
tokenId: String,
|
||||
preview: Boolean,
|
||||
): Either<Unit, TokenChart> {
|
||||
return Either.catch {
|
||||
marketsTokenRepository.getChart(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
interval = interval,
|
||||
tokenId = tokenId,
|
||||
)
|
||||
if (preview) {
|
||||
marketsTokenRepository.getChartPreview(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
interval = interval,
|
||||
tokenId = tokenId,
|
||||
)
|
||||
} else {
|
||||
marketsTokenRepository.getChart(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
interval = interval,
|
||||
tokenId = tokenId,
|
||||
)
|
||||
}
|
||||
}.mapLeft {}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +1,26 @@
|
|||
package com.tangem.domain.markets
|
||||
|
||||
import arrow.core.Either
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.repositories.MarketsTokenRepository
|
||||
import com.tangem.domain.tokens.model.Quote
|
||||
import com.tangem.domain.tokens.repository.QuotesRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
class GetTokenQuotesUseCase(
|
||||
private val marketsTokenRepository: MarketsTokenRepository,
|
||||
private val quotesRepository: QuotesRepository,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(appCurrency: AppCurrency, tokenId: String): Either<Unit, TokenQuotes> {
|
||||
return Either.catch {
|
||||
marketsTokenRepository.getTokenQuotes(
|
||||
fiatCurrencyCode = appCurrency.code,
|
||||
tokenId = tokenId,
|
||||
)
|
||||
}.mapLeft {}
|
||||
operator fun invoke(tokenId: String, interval: PriceChangeInterval): Flow<Either<Unit, Quote>> {
|
||||
return flowOf(
|
||||
Either.catch {
|
||||
Quote(
|
||||
rawCurrencyId = "USD",
|
||||
fiatRate = 100.toBigDecimal(), // mock
|
||||
priceChange = 10.toBigDecimal(), // mock
|
||||
)
|
||||
}.mapLeft {},
|
||||
)
|
||||
// TODO implement quotes fetching from repository [REDACTED_TASK_KEY]
|
||||
// quotesRepository.getQuotesUpdates()
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,8 @@ interface MarketsTokenRepository {
|
|||
|
||||
suspend fun getChart(fiatCurrencyCode: String, interval: PriceChangeInterval, tokenId: String): TokenChart
|
||||
|
||||
suspend fun getChartPreview(fiatCurrencyCode: String, interval: PriceChangeInterval, tokenId: String): TokenChart
|
||||
|
||||
suspend fun getTokenInfo(fiatCurrencyCode: String, tokenId: String, languageCode: String): TokenMarketInfo
|
||||
|
||||
suspend fun getTokenQuotes(fiatCurrencyCode: String, tokenId: String): TokenQuotes
|
||||
|
|
|
|||
|
|
@ -5,17 +5,19 @@ import androidx.compose.runtime.Stable
|
|||
import androidx.compose.runtime.State
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.TokenMarketParam
|
||||
import com.tangem.features.markets.entry.BottomSheetState
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Stable
|
||||
interface MarketsTokenDetailsComponent {
|
||||
interface MarketsTokenDetailsComponent : ComposableContentComponent {
|
||||
|
||||
@Serializable
|
||||
data class Params(
|
||||
val token: TokenMarketSerializable,
|
||||
val token: TokenMarketParam,
|
||||
val appCurrency: AppCurrency,
|
||||
)
|
||||
|
||||
|
|
@ -26,7 +28,5 @@ interface MarketsTokenDetailsComponent {
|
|||
modifier: Modifier,
|
||||
)
|
||||
|
||||
interface Factory {
|
||||
fun create(context: AppComponentContext, params: Params, onBack: () -> Unit): MarketsTokenDetailsComponent
|
||||
}
|
||||
interface Factory : ComponentFactory<Params, MarketsTokenDetailsComponent>
|
||||
}
|
||||
|
|
@ -3,7 +3,6 @@ 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
|
||||
|
|
@ -11,7 +10,10 @@ interface TokenMarketBlockComponent : ComposableContentComponent {
|
|||
|
||||
@Serializable
|
||||
data class Params(
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val tokenId: String,
|
||||
val tokenName: String,
|
||||
val tokenSymbol: String,
|
||||
val tokenImageUrl: String?,
|
||||
)
|
||||
|
||||
interface Factory {
|
||||
|
|
|
|||
|
|
@ -51,4 +51,5 @@ dependencies {
|
|||
/* Common */
|
||||
implementation(projects.common.ui)
|
||||
implementation(projects.common.uiCharts)
|
||||
implementation(projects.common.routing)
|
||||
}
|
||||
|
|
@ -8,11 +8,14 @@ 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.entry.BottomSheetState
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent.Params
|
||||
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
|
||||
import com.tangem.features.markets.entry.BottomSheetState
|
||||
import com.tangem.features.markets.portfolio.api.MarketsPortfolioComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -23,8 +26,7 @@ import kotlinx.coroutines.launch
|
|||
@Stable
|
||||
internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted params: MarketsTokenDetailsComponent.Params,
|
||||
@Assisted private val onBack: () -> Unit,
|
||||
@Assisted params: Params,
|
||||
portfolioComponentFactory: MarketsPortfolioComponent.Factory,
|
||||
) : AppComponentContext by appComponentContext, MarketsTokenDetailsComponent {
|
||||
|
||||
|
|
@ -64,26 +66,50 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
|
|||
val bsState by bottomSheetState
|
||||
|
||||
LaunchedEffect(bsState) {
|
||||
model.containerBottomSheetState.value = bsState
|
||||
model.isVisibleOnScreen.value = bsState == BottomSheetState.EXPANDED
|
||||
}
|
||||
|
||||
MarketsTokenDetailsContent(
|
||||
state = state,
|
||||
onBackClick = onBack,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
portfolioBlock = { modifier ->
|
||||
portfolioComponent.Content(modifier)
|
||||
},
|
||||
modifier = modifier,
|
||||
backgroundColor = LocalMainBottomSheetColor.current.value,
|
||||
addTopBarStatusBarPadding = false,
|
||||
state = state,
|
||||
onBackClick = ::navigateBack,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
portfolioBlock = { blockModifier ->
|
||||
portfolioComponent.Content(blockModifier)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
LifecycleStartEffect(Unit) {
|
||||
model.isVisibleOnScreen.value = true
|
||||
onStopOrDispose {
|
||||
model.isVisibleOnScreen.value = false
|
||||
}
|
||||
}
|
||||
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
MarketsTokenDetailsContent(
|
||||
modifier = modifier,
|
||||
backgroundColor = TangemTheme.colors.background.tertiary,
|
||||
addTopBarStatusBarPadding = true,
|
||||
state = state,
|
||||
onBackClick = ::navigateBack,
|
||||
onHeaderSizeChange = {},
|
||||
portfolioBlock = { blockModifier ->
|
||||
portfolioComponent.Content(blockModifier)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun navigateBack() = router.pop()
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : MarketsTokenDetailsComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: MarketsTokenDetailsComponent.Params,
|
||||
onBack: () -> Unit,
|
||||
): DefaultMarketsTokenDetailsComponent
|
||||
override fun create(context: AppComponentContext, params: Params): DefaultMarketsTokenDetailsComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ 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.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
|
||||
|
|
@ -52,7 +51,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
|
||||
private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
|
||||
private val getTokenQuotesUseCase: GetTokenQuotesUseCase,
|
||||
private val getTokenFullQuotesUseCase: GetTokenFullQuotesUseCase,
|
||||
private val urlOpener: UrlOpener,
|
||||
) : Model() {
|
||||
|
||||
|
|
@ -118,7 +117,6 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
|
||||
private var lastUpdatedTimestamp: Long = DateTime.now().millis
|
||||
|
||||
val containerBottomSheetState = MutableStateFlow(BottomSheetState.COLLAPSED)
|
||||
val isVisibleOnScreen = MutableStateFlow(false)
|
||||
val networksState = MutableStateFlow<TokenNetworksState>(TokenNetworksState.Loading)
|
||||
|
||||
|
|
@ -135,11 +133,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
percent = params.token.tokenQuotes.h24Percent,
|
||||
useAbsoluteValue = true,
|
||||
),
|
||||
priceChangeType = if (params.token.tokenQuotes.h24Percent < BigDecimal.ZERO) {
|
||||
PriceChangeType.DOWN
|
||||
} else {
|
||||
PriceChangeType.UP
|
||||
},
|
||||
priceChangeType = params.token.tokenQuotes.h24Percent.percentChangeType(),
|
||||
iconUrl = params.token.imageUrl,
|
||||
chartState = MarketsTokenDetailsUM.ChartState(
|
||||
dataProducer = chartDataProducer,
|
||||
|
|
@ -184,7 +178,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
|
||||
private fun loadQuotes() {
|
||||
modelScope.launch {
|
||||
val result = getTokenQuotesUseCase(
|
||||
val result = getTokenFullQuotesUseCase(
|
||||
tokenId = params.token.id,
|
||||
appCurrency = currentAppCurrency.value,
|
||||
)
|
||||
|
|
@ -213,6 +207,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
appCurrency = currentAppCurrency.value,
|
||||
interval = interval,
|
||||
tokenId = params.token.id,
|
||||
preview = false,
|
||||
)
|
||||
|
||||
state.update {
|
||||
|
|
@ -246,6 +241,11 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
chartState = it.chartState.copy(
|
||||
status = MarketsTokenDetailsUM.ChartState.Status.DATA,
|
||||
),
|
||||
body = if (it.body is MarketsTokenDetailsUM.Body.Nothing) {
|
||||
MarketsTokenDetailsUM.Body.Error(onLoadRetryClick = ::onLoadRetryClicked)
|
||||
} else {
|
||||
it.body
|
||||
},
|
||||
)
|
||||
}
|
||||
}.onLeft {
|
||||
|
|
@ -488,7 +488,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
|
|||
while (true) {
|
||||
delay(timeMillis)
|
||||
// Update quotes only when the container bottom sheet is in the expanded state
|
||||
containerBottomSheetState.first { it == BottomSheetState.EXPANDED }
|
||||
|
||||
// and is visible on the screen
|
||||
isVisibleOnScreen.first { it }
|
||||
|
||||
|
|
|
|||
|
|
@ -48,11 +48,13 @@ internal fun TokenQuotes.getPercentByInterval(interval: PriceChangeInterval): Bi
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
internal fun BigDecimal?.percentChangeType(): PriceChangeType {
|
||||
val scaled = this?.setScale(4, RoundingMode.HALF_UP)
|
||||
return when {
|
||||
this == null -> PriceChangeType.NEUTRAL
|
||||
this > BigDecimal.ZERO -> PriceChangeType.UP
|
||||
this < BigDecimal.ZERO -> PriceChangeType.DOWN
|
||||
scaled == null -> PriceChangeType.NEUTRAL
|
||||
scaled > BigDecimal.ZERO -> PriceChangeType.UP
|
||||
scaled < BigDecimal.ZERO -> PriceChangeType.DOWN
|
||||
else -> PriceChangeType.NEUTRAL
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.runtime.*
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.layout.onGloballyPositioned
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -33,7 +34,6 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.utils.disableNestedScroll
|
||||
|
|
@ -45,10 +45,12 @@ import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
|
|||
import com.tangem.features.markets.impl.R
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
internal fun MarketsTokenDetailsContent(
|
||||
state: MarketsTokenDetailsUM,
|
||||
backgroundColor: Color,
|
||||
addTopBarStatusBarPadding: Boolean,
|
||||
onBackClick: () -> Unit,
|
||||
onHeaderSizeChange: (Dp) -> Unit,
|
||||
portfolioBlock: @Composable (Modifier) -> Unit,
|
||||
|
|
@ -56,41 +58,46 @@ internal fun MarketsTokenDetailsContent(
|
|||
) {
|
||||
Content(
|
||||
modifier = modifier,
|
||||
backgroundColor = backgroundColor,
|
||||
state = state,
|
||||
onBackClick = onBackClick,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
portfolioBlock = portfolioBlock,
|
||||
addTopBarStatusBarInsets = addTopBarStatusBarPadding,
|
||||
)
|
||||
|
||||
InfoBottomSheet(config = state.infoBottomSheet)
|
||||
}
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@Suppress("LongParameterList")
|
||||
@Composable
|
||||
private fun Content(
|
||||
state: MarketsTokenDetailsUM,
|
||||
backgroundColor: Color,
|
||||
addTopBarStatusBarInsets: Boolean,
|
||||
onBackClick: () -> Unit,
|
||||
onHeaderSizeChange: (Dp) -> Unit,
|
||||
portfolioBlock: @Composable (Modifier) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val backgroundColor = LocalMainBottomSheetColor.current.value
|
||||
val density = LocalDensity.current
|
||||
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(this).toDp() }
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.drawBehind { drawRect(backgroundColor) }
|
||||
.let { if (addTopBarStatusBarInsets) it.statusBarsPadding() else it }
|
||||
.fillMaxSize(),
|
||||
) {
|
||||
TangemTopAppBar(
|
||||
modifier = Modifier.onGloballyPositioned {
|
||||
if (it.size.height > 0) {
|
||||
with(density) {
|
||||
onHeaderSizeChange(it.size.height.toDp())
|
||||
modifier = Modifier
|
||||
.onGloballyPositioned {
|
||||
if (it.size.height > 0) {
|
||||
with(density) {
|
||||
onHeaderSizeChange(it.size.height.toDp())
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
title = state.tokenName,
|
||||
startButton = TopAppBarButtonUM.Back(onBackClick),
|
||||
)
|
||||
|
|
@ -125,6 +132,7 @@ private fun Content(
|
|||
) {
|
||||
MarketTokenDetailsChart(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
backgroundColor = backgroundColor,
|
||||
state = state.chartState,
|
||||
)
|
||||
}
|
||||
|
|
@ -265,6 +273,7 @@ private fun Preview() {
|
|||
TangemThemePreview {
|
||||
Content(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.tertiary),
|
||||
addTopBarStatusBarInsets = false,
|
||||
state = MarketsTokenDetailsUM(
|
||||
tokenName = "Token Name",
|
||||
priceText = "$0.00000000324",
|
||||
|
|
@ -292,6 +301,7 @@ private fun Preview() {
|
|||
),
|
||||
onHeaderSizeChange = {},
|
||||
onBackClick = {},
|
||||
backgroundColor = TangemTheme.colors.background.tertiary,
|
||||
portfolioBlock = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,17 +9,21 @@ import androidx.compose.runtime.Composable
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import com.tangem.common.ui.charts.MarketChart
|
||||
import com.tangem.common.ui.charts.getMarketChartBottomAxisHeight
|
||||
import com.tangem.common.ui.charts.state.MarketChartLook
|
||||
import com.tangem.common.ui.charts.state.rememberMarketChartState
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
|
||||
import com.tangem.features.markets.tokenlist.impl.ui.components.UnableToLoadData
|
||||
|
||||
@Composable
|
||||
internal fun MarketTokenDetailsChart(state: MarketsTokenDetailsUM.ChartState, modifier: Modifier = Modifier) {
|
||||
internal fun MarketTokenDetailsChart(
|
||||
state: MarketsTokenDetailsUM.ChartState,
|
||||
backgroundColor: Color,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val growingColor = TangemTheme.colors.icon.accent
|
||||
val fallingColor = TangemTheme.colors.icon.warning
|
||||
val neutralColor = TangemTheme.colors.icon.informative
|
||||
|
|
@ -36,7 +40,6 @@ internal fun MarketTokenDetailsChart(state: MarketsTokenDetailsUM.ChartState, mo
|
|||
onMarkerShown = state.onMarkerPointSelected,
|
||||
)
|
||||
|
||||
val backgroundColor = LocalMainBottomSheetColor.current.value
|
||||
val bottomChartAxisHeight = getMarketChartBottomAxisHeight()
|
||||
|
||||
Box(modifier) {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import java.math.BigDecimal
|
|||
internal data class MarketsTokenDetailsUM(
|
||||
val tokenName: String,
|
||||
val priceText: String,
|
||||
val iconUrl: String,
|
||||
val iconUrl: String?,
|
||||
val dateTimeText: TextReference,
|
||||
val priceChangePercentText: String,
|
||||
val priceChangeType: PriceChangeType,
|
||||
|
|
|
|||
|
|
@ -1,27 +1,23 @@
|
|||
package com.tangem.features.markets.entry.impl
|
||||
|
||||
import androidx.compose.animation.Animatable
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.arkivanov.decompose.ExperimentalDecomposeApi
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.*
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
|
||||
import com.arkivanov.decompose.router.stack.*
|
||||
import com.arkivanov.decompose.value.Value
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||
import com.tangem.domain.markets.TokenMarket
|
||||
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 com.tangem.domain.markets.toSerializableParam
|
||||
import com.tangem.features.markets.entry.impl.MarketsEntryChildFactory.Child
|
||||
import com.tangem.features.markets.entry.impl.ui.EntryBottomSheetContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
|
@ -32,20 +28,22 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
|
|||
private val marketsEntryChildFactory: MarketsEntryChildFactory,
|
||||
) : MarketsEntryComponent, AppComponentContext by context {
|
||||
|
||||
private val stackNavigation = StackNavigation<MarketsEntryChildFactory.Child>()
|
||||
private val stackNavigation = StackNavigation<Child>()
|
||||
|
||||
val stack: Value<ChildStack<MarketsEntryChildFactory.Child, Any>> = childStack(
|
||||
val stack: Value<ChildStack<Child, Any>> = childStack(
|
||||
key = "main",
|
||||
source = stackNavigation,
|
||||
serializer = MarketsEntryChildFactory.Child.serializer(),
|
||||
initialConfiguration = MarketsEntryChildFactory.Child.TokenList,
|
||||
serializer = Child.serializer(),
|
||||
initialConfiguration = Child.TokenList,
|
||||
handleBackButton = true,
|
||||
childFactory = { configuration, componentContext ->
|
||||
childFactory = { configuration, factoryContext ->
|
||||
marketsEntryChildFactory.createChild(
|
||||
child = configuration,
|
||||
appComponentContext = childByContext(componentContext),
|
||||
appComponentContext = childByContext(
|
||||
componentContext = factoryContext,
|
||||
router = createRouter(configuration),
|
||||
),
|
||||
onTokenSelected = ::marketsListTokenSelected,
|
||||
onDetailsBack = ::onDetailsBack,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -57,103 +55,36 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
|
|||
onHeaderSizeChange: (Dp) -> Unit,
|
||||
modifier: Modifier,
|
||||
) {
|
||||
val primary = TangemTheme.colors.background.primary
|
||||
val secondary = TangemTheme.colors.background.secondary
|
||||
val backgroundColor = remember { Animatable(primary) }
|
||||
val stackState = stack.subscribeAsState()
|
||||
|
||||
LocalMainBottomSheetColor.current.value = backgroundColor.value
|
||||
|
||||
Children(
|
||||
stack = stackState.value,
|
||||
animation = stackAnimation(slide()),
|
||||
) {
|
||||
when (it.configuration) {
|
||||
is MarketsEntryChildFactory.Child.TokenDetails -> {
|
||||
(it.instance as MarketsTokenDetailsComponent).BottomSheetContent(
|
||||
bottomSheetState = bottomSheetState,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
MarketsEntryChildFactory.Child.TokenList -> {
|
||||
(it.instance as MarketsTokenListComponent).BottomSheetContent(
|
||||
bottomSheetState = bottomSheetState,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// order of LaunchedEffects is important here
|
||||
|
||||
val activeChild = stackState.value.active.configuration
|
||||
|
||||
LaunchedEffect(activeChild) {
|
||||
when (activeChild) {
|
||||
is MarketsEntryChildFactory.Child.TokenDetails -> {
|
||||
backgroundColor.animateTo(
|
||||
secondary,
|
||||
animationSpec = tween(durationMillis = 500),
|
||||
)
|
||||
}
|
||||
MarketsEntryChildFactory.Child.TokenList -> {
|
||||
backgroundColor.animateTo(
|
||||
primary,
|
||||
animationSpec = tween(durationMillis = 500),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(bottomSheetState.value) {
|
||||
if (activeChild is MarketsEntryChildFactory.Child.TokenDetails) {
|
||||
when (bottomSheetState.value) {
|
||||
BottomSheetState.EXPANDED -> {
|
||||
backgroundColor.animateTo(
|
||||
secondary,
|
||||
animationSpec = tween(durationMillis = 100),
|
||||
)
|
||||
}
|
||||
BottomSheetState.COLLAPSED -> {
|
||||
backgroundColor.animateTo(
|
||||
primary,
|
||||
animationSpec = tween(durationMillis = 100),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(primary, secondary) {
|
||||
if (backgroundColor.isRunning) return@LaunchedEffect
|
||||
|
||||
when (activeChild) {
|
||||
is MarketsEntryChildFactory.Child.TokenDetails -> {
|
||||
backgroundColor.snapTo(secondary)
|
||||
}
|
||||
MarketsEntryChildFactory.Child.TokenList -> {
|
||||
backgroundColor.snapTo(primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
EntryBottomSheetContent(
|
||||
bottomSheetState = bottomSheetState,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
stackState = stack.subscribeAsState(),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalDecomposeApi::class)
|
||||
private fun marketsListTokenSelected(token: TokenMarket, appCurrency: AppCurrency) {
|
||||
stackNavigation.pushNew(
|
||||
configuration = MarketsEntryChildFactory.Child.TokenDetails(
|
||||
configuration = Child.TokenDetails(
|
||||
params = MarketsTokenDetailsComponent.Params(
|
||||
token = token.toSerializable(),
|
||||
token = token.toSerializableParam(),
|
||||
appCurrency = appCurrency,
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onDetailsBack() {
|
||||
stackNavigation.popWhile { it != MarketsEntryChildFactory.Child.TokenList }
|
||||
private fun AppComponentContext.createRouter(child: Child): Router {
|
||||
return when (child) {
|
||||
is Child.TokenDetails -> {
|
||||
MarketTokenDetailsRouter(
|
||||
contextRouter = this.router,
|
||||
stackNavigation = stackNavigation,
|
||||
)
|
||||
}
|
||||
else -> this.router
|
||||
}
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.features.markets.entry.impl
|
||||
|
||||
import com.arkivanov.decompose.router.stack.StackNavigation
|
||||
import com.arkivanov.decompose.router.stack.popWhile
|
||||
import com.tangem.core.decompose.navigation.Route
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import kotlin.reflect.KClass
|
||||
|
||||
internal class MarketTokenDetailsRouter(
|
||||
private val contextRouter: Router,
|
||||
private val stackNavigation: StackNavigation<MarketsEntryChildFactory.Child>,
|
||||
) : Router by contextRouter {
|
||||
|
||||
override fun pop(onComplete: (isSuccess: Boolean) -> Unit) {
|
||||
stackNavigation.popWhile({ it != MarketsEntryChildFactory.Child.TokenList }, onComplete)
|
||||
}
|
||||
|
||||
override fun popTo(route: Route, onComplete: (isSuccess: Boolean) -> Unit) = error("Not allowed")
|
||||
|
||||
override fun popTo(routeClass: KClass<out Route>, onComplete: (isSuccess: Boolean) -> Unit) = error("Not allowed")
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ 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.MarketsTokenDetailsComponent
|
||||
import com.tangem.features.markets.entry.impl.MarketsEntryChildFactory.Child
|
||||
import com.tangem.features.markets.tokenlist.api.MarketsTokenListComponent
|
||||
import kotlinx.serialization.Serializable
|
||||
import javax.inject.Inject
|
||||
|
|
@ -31,14 +32,12 @@ internal class MarketsEntryChildFactory @Inject constructor(
|
|||
child: Child,
|
||||
appComponentContext: AppComponentContext,
|
||||
onTokenSelected: (TokenMarket, AppCurrency) -> Unit,
|
||||
onDetailsBack: () -> Unit,
|
||||
): Any {
|
||||
return when (child) {
|
||||
is Child.TokenDetails -> {
|
||||
tokenDetailsComponentFactory.create(
|
||||
context = appComponentContext,
|
||||
params = child.params,
|
||||
onBack = onDetailsBack,
|
||||
)
|
||||
}
|
||||
is Child.TokenList -> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,128 @@
|
|||
package com.tangem.features.markets.entry.impl.ui
|
||||
|
||||
import androidx.compose.animation.Animatable
|
||||
import androidx.compose.animation.core.Animatable
|
||||
import androidx.compose.animation.core.AnimationVector4D
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.State
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.slide
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation
|
||||
import com.arkivanov.decompose.router.stack.ChildStack
|
||||
import com.tangem.core.ui.res.LocalMainBottomSheetColor
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
|
||||
import com.tangem.features.markets.entry.BottomSheetState
|
||||
import com.tangem.features.markets.entry.impl.MarketsEntryChildFactory
|
||||
import com.tangem.features.markets.tokenlist.api.MarketsTokenListComponent
|
||||
|
||||
@Composable
|
||||
internal fun EntryBottomSheetContent(
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
onHeaderSizeChange: (Dp) -> Unit,
|
||||
stackState: State<ChildStack<MarketsEntryChildFactory.Child, Any>>,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val primary = TangemTheme.colors.background.primary
|
||||
val secondary = TangemTheme.colors.background.secondary
|
||||
val backgroundColor = remember { Animatable(primary) }
|
||||
|
||||
LocalMainBottomSheetColor.current.value = backgroundColor.value
|
||||
|
||||
Children(
|
||||
stack = stackState.value,
|
||||
animation = stackAnimation(slide()),
|
||||
) {
|
||||
when (it.configuration) {
|
||||
is MarketsEntryChildFactory.Child.TokenDetails -> {
|
||||
(it.instance as MarketsTokenDetailsComponent).BottomSheetContent(
|
||||
bottomSheetState = bottomSheetState,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
MarketsEntryChildFactory.Child.TokenList -> {
|
||||
(it.instance as MarketsTokenListComponent).BottomSheetContent(
|
||||
bottomSheetState = bottomSheetState,
|
||||
onHeaderSizeChange = onHeaderSizeChange,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val activeChild = stackState.value.active.configuration
|
||||
|
||||
BackgroundColorEffects(
|
||||
activeChild = activeChild,
|
||||
backgroundColor = backgroundColor,
|
||||
bottomSheetState = bottomSheetState,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun BackgroundColorEffects(
|
||||
activeChild: MarketsEntryChildFactory.Child,
|
||||
backgroundColor: Animatable<Color, AnimationVector4D>,
|
||||
bottomSheetState: State<BottomSheetState>,
|
||||
) {
|
||||
val primary = TangemTheme.colors.background.primary
|
||||
val secondary = TangemTheme.colors.background.secondary
|
||||
|
||||
// Order of LaunchedEffects is important here
|
||||
|
||||
LaunchedEffect(activeChild) {
|
||||
when (activeChild) {
|
||||
is MarketsEntryChildFactory.Child.TokenDetails -> {
|
||||
backgroundColor.animateTo(
|
||||
secondary,
|
||||
animationSpec = tween(durationMillis = 500),
|
||||
)
|
||||
}
|
||||
MarketsEntryChildFactory.Child.TokenList -> {
|
||||
backgroundColor.animateTo(
|
||||
primary,
|
||||
animationSpec = tween(durationMillis = 500),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(bottomSheetState.value) {
|
||||
if (activeChild is MarketsEntryChildFactory.Child.TokenDetails) {
|
||||
when (bottomSheetState.value) {
|
||||
BottomSheetState.EXPANDED -> {
|
||||
backgroundColor.animateTo(
|
||||
secondary,
|
||||
animationSpec = tween(durationMillis = 100),
|
||||
)
|
||||
}
|
||||
BottomSheetState.COLLAPSED -> {
|
||||
backgroundColor.animateTo(
|
||||
primary,
|
||||
animationSpec = tween(durationMillis = 100),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(primary, secondary) {
|
||||
if (backgroundColor.isRunning) return@LaunchedEffect
|
||||
|
||||
when (activeChild) {
|
||||
is MarketsEntryChildFactory.Child.TokenDetails -> {
|
||||
backgroundColor.snapTo(secondary)
|
||||
}
|
||||
MarketsEntryChildFactory.Child.TokenList -> {
|
||||
backgroundColor.snapTo(primary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@ package com.tangem.features.markets.token.block.impl
|
|||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.markets.token.block.TokenMarketBlockComponent
|
||||
|
|
@ -23,7 +25,12 @@ internal class DefaultTokenMarketBlockComponent @AssistedInject constructor(
|
|||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
TokenMarketBlock(modifier)
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
|
||||
TokenMarketBlock(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.markets.token.block.impl.model
|
||||
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal class QuotesState(
|
||||
val currentPrice: BigDecimal,
|
||||
val h24Percent: BigDecimal,
|
||||
)
|
||||
|
|
@ -1,11 +1,25 @@
|
|||
package com.tangem.features.markets.token.block.impl.model
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.common.ui.charts.state.MarketChartData
|
||||
import com.tangem.common.ui.charts.state.converter.PriceAndTimePointValuesConverter
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
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.token.block.TokenMarketBlockComponent
|
||||
import com.tangem.features.markets.token.block.impl.ui.state.TokenMarketBlockUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@Stable
|
||||
|
|
@ -13,9 +27,116 @@ import javax.inject.Inject
|
|||
internal class TokenMarketBlockModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val router: Router,
|
||||
private val getTokenPriceChartUseCase: GetTokenPriceChartUseCase,
|
||||
private val getTokenQuotesUseCase: GetTokenQuotesUseCase,
|
||||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
) : Model() {
|
||||
|
||||
val params = paramsContainer.require<TokenMarketBlockComponent.Params>()
|
||||
private val params = paramsContainer.require<TokenMarketBlockComponent.Params>()
|
||||
private val priceAndTimePointValuesConverter = PriceAndTimePointValuesConverter(needToFormatAxis = false)
|
||||
|
||||
// token price is available if cryptoCurrency.id.rawCurrencyId != null
|
||||
private val currentAppCurrency = getSelectedAppCurrencyUseCase()
|
||||
.map { maybeAppCurrency ->
|
||||
maybeAppCurrency.getOrElse { AppCurrency.Default }
|
||||
}.stateIn(
|
||||
scope = modelScope,
|
||||
started = SharingStarted.Eagerly,
|
||||
initialValue = AppCurrency.Default,
|
||||
)
|
||||
|
||||
private var quotesState: QuotesState? = null
|
||||
|
||||
val state = MutableStateFlow(
|
||||
TokenMarketBlockUM(
|
||||
currencySymbol = params.tokenSymbol,
|
||||
currentPrice = null,
|
||||
h24Percent = null,
|
||||
priceChangeType = PriceChangeType.NEUTRAL,
|
||||
chartData = null,
|
||||
onClick = ::navigateToMarketDetails,
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
startFetching()
|
||||
}
|
||||
|
||||
private fun startFetching() {
|
||||
modelScope.launch {
|
||||
getTokenQuotesUseCase(
|
||||
tokenId = params.tokenId,
|
||||
interval = PriceChangeInterval.H24,
|
||||
).collect {
|
||||
it.onRight { res ->
|
||||
quotesState = QuotesState(
|
||||
currentPrice = res.fiatRate,
|
||||
h24Percent = res.priceChange,
|
||||
)
|
||||
|
||||
state.value = state.value.copy(
|
||||
currentPrice = BigDecimalFormatter.formatFiatPriceUncapped(
|
||||
fiatAmount = res.fiatRate,
|
||||
// TODO get currency from quotes use case [REDACTED_TASK_KEY]
|
||||
fiatCurrencyCode = currentAppCurrency.value.code,
|
||||
// TODO get currency from quotes use case [REDACTED_TASK_KEY]
|
||||
fiatCurrencySymbol = currentAppCurrency.value.symbol,
|
||||
),
|
||||
h24Percent = BigDecimalFormatter.formatPercent(
|
||||
percent = res.priceChange,
|
||||
useAbsoluteValue = true,
|
||||
),
|
||||
priceChangeType = PriceChangeType.fromBigDecimal(res.priceChange),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modelScope.launch(dispatchers.main) {
|
||||
val result = getTokenPriceChartUseCase(
|
||||
tokenId = params.tokenId,
|
||||
interval = PriceChangeInterval.H24,
|
||||
appCurrency = currentAppCurrency.value, // TODO get currency from quotes use case [REDACTED_TASK_KEY]
|
||||
preview = true,
|
||||
)
|
||||
|
||||
result.onRight { res ->
|
||||
state.update { stateToUpdate ->
|
||||
stateToUpdate.copy(
|
||||
chartData = priceAndTimePointValuesConverter.convert(
|
||||
MarketChartData.Data(
|
||||
y = res.priceY.toImmutableList(),
|
||||
x = res.timeStamps.sorted().map { it.toBigDecimal() }.toImmutableList(),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigateToMarketDetails() {
|
||||
val quotes = quotesState ?: return
|
||||
|
||||
val tokenParam = TokenMarketParam(
|
||||
id = params.tokenId,
|
||||
name = params.tokenSymbol,
|
||||
imageUrl = params.tokenImageUrl,
|
||||
symbol = params.tokenSymbol,
|
||||
tokenQuotes = TokenMarketParam.Quotes(
|
||||
currentPrice = quotes.currentPrice,
|
||||
h24Percent = quotes.h24Percent,
|
||||
weekPercent = null,
|
||||
monthPercent = null,
|
||||
),
|
||||
)
|
||||
|
||||
// FIXME navigation crash [REDACTED_TASK_KEY]
|
||||
router.push(
|
||||
AppRoute.MarketsTokenDetails(
|
||||
token = tokenParam,
|
||||
appCurrency = currentAppCurrency.value,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,209 @@
|
|||
package com.tangem.features.markets.token.block.impl.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
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 com.tangem.core.ui.components.block.information.InformationBlock
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.tangem.common.ui.charts.MarketChartMini
|
||||
import com.tangem.common.ui.charts.state.MarketChartRawData
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.SpacerW8
|
||||
import com.tangem.core.ui.components.TextShimmer
|
||||
import com.tangem.core.ui.components.block.BlockCard
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeInPercent
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.features.markets.details.impl.model.formatter.toChartType
|
||||
import com.tangem.features.markets.impl.R
|
||||
import com.tangem.features.markets.token.block.impl.ui.state.TokenMarketBlockUM
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlin.random.Random
|
||||
|
||||
@Composable
|
||||
internal fun TokenMarketBlock(modifier: Modifier = Modifier) {
|
||||
InformationBlock(
|
||||
internal fun TokenMarketBlock(state: TokenMarketBlockUM, modifier: Modifier = Modifier) {
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
title = {
|
||||
Text(
|
||||
text = "Token Market Block",
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
enabled = state.currentPrice != null,
|
||||
onClick = state.onClick,
|
||||
content = {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(TangemTheme.dimens.spacing12),
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
LeftSide(
|
||||
modifier = Modifier.weight(1f),
|
||||
symbol = state.currencySymbol,
|
||||
priceText = state.currentPrice,
|
||||
percentText = state.h24Percent,
|
||||
type = state.priceChangeType,
|
||||
)
|
||||
SpacerW8()
|
||||
RightSide(
|
||||
modifier = Modifier,
|
||||
priceChangeType = state.priceChangeType,
|
||||
chartRawData = state.chartData,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
private fun LeftSide(
|
||||
symbol: String,
|
||||
priceText: String?,
|
||||
percentText: String?,
|
||||
type: PriceChangeType,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4),
|
||||
) {
|
||||
Text("//TODO")
|
||||
Text(
|
||||
text = stringResource(id = R.string.wallet_marketplace_block_title, symbol),
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
)
|
||||
|
||||
if (priceText != null && percentText != null) {
|
||||
FlowRow(
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
Text(
|
||||
text = priceText,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
PriceChangeInPercent(
|
||||
valueInPercent = percentText,
|
||||
type = type,
|
||||
)
|
||||
Text(
|
||||
text = stringResource(id = R.string.wallet_marketprice_block_update_time),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
TextShimmer(
|
||||
modifier = Modifier.fillMaxWidth(fraction = 0.6f),
|
||||
style = TangemTheme.typography.body2,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RightSide(priceChangeType: PriceChangeType?, chartRawData: MarketChartRawData?, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier.padding(vertical = TangemTheme.dimens.spacing10),
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
if (chartRawData != null && priceChangeType != null) {
|
||||
MarketChartMini(
|
||||
rawData = chartRawData,
|
||||
type = priceChangeType.toChartType(),
|
||||
modifier = Modifier
|
||||
.requiredSize(
|
||||
width = TangemTheme.dimens.size56,
|
||||
height = TangemTheme.dimens.size24,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.padding(vertical = TangemTheme.dimens.spacing2)
|
||||
.requiredSize(
|
||||
width = TangemTheme.dimens.size56,
|
||||
height = TangemTheme.dimens.size20,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
if (priceChangeType != null) {
|
||||
Icon(
|
||||
modifier = Modifier.requiredSize(TangemTheme.dimens.size20),
|
||||
imageVector = ImageVector.vectorResource(id = R.drawable.ic_chevron_right_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
} else {
|
||||
RectangleShimmer(
|
||||
modifier = Modifier
|
||||
.requiredSize(
|
||||
width = TangemTheme.dimens.size20,
|
||||
height = TangemTheme.dimens.size20,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 360)
|
||||
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES, widthDp = 360)
|
||||
@Composable
|
||||
private fun Preview() {
|
||||
val data = MarketChartRawData(
|
||||
x = List(20) { Random.nextFloat().toDouble() }.toImmutableList(),
|
||||
y = List(20) { Random.nextFloat().toDouble() }.toImmutableList(),
|
||||
)
|
||||
|
||||
val state = TokenMarketBlockUM(
|
||||
currencySymbol = "XRP",
|
||||
currentPrice = "0,5$",
|
||||
h24Percent = "0,5%",
|
||||
priceChangeType = PriceChangeType.UP,
|
||||
chartData = data,
|
||||
onClick = {},
|
||||
)
|
||||
|
||||
TangemThemePreview {
|
||||
Column(
|
||||
modifier = Modifier.background(TangemTheme.colors.background.tertiary),
|
||||
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
|
||||
) {
|
||||
TokenMarketBlock(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = state,
|
||||
)
|
||||
TokenMarketBlock(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = state.copy(
|
||||
currentPrice = "0,0000000000012356786789$",
|
||||
),
|
||||
)
|
||||
TokenMarketBlock(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = state.copy(
|
||||
currentPrice = null,
|
||||
chartData = null,
|
||||
),
|
||||
)
|
||||
TokenMarketBlock(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = state.copy(
|
||||
chartData = null,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.markets.token.block.impl.ui.state
|
||||
|
||||
import com.tangem.common.ui.charts.state.MarketChartRawData
|
||||
import com.tangem.core.ui.components.marketprice.PriceChangeType
|
||||
|
||||
internal data class TokenMarketBlockUM(
|
||||
val currencySymbol: String,
|
||||
val currentPrice: String?,
|
||||
val h24Percent: String?,
|
||||
val priceChangeType: PriceChangeType,
|
||||
val chartData: MarketChartRawData?,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -78,6 +78,7 @@ dependencies {
|
|||
implementation(projects.domain.balanceHiding.models)
|
||||
implementation(projects.domain.transaction)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.domain.markets.models)
|
||||
|
||||
/** Temp dependency to swap domain */
|
||||
implementation(projects.features.swap.domain)
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ 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.AppRouter
|
||||
import com.tangem.common.routing.bundle.unbundle
|
||||
import com.tangem.common.routing.utils.asRouter
|
||||
import com.tangem.core.decompose.context.DefaultAppComponentContext
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
|
|
@ -46,6 +48,9 @@ internal class TokenDetailsFragment : ComposeFragment() {
|
|||
@Inject
|
||||
internal lateinit var tokenMarketBlockComponentFactory: TokenMarketBlockComponent.Factory
|
||||
|
||||
@Inject
|
||||
internal lateinit var appRouter: AppRouter
|
||||
|
||||
private var tokenMarketBlockComponent: TokenMarketBlockComponent? = null
|
||||
|
||||
private val internalTokenDetailsRouter: InnerTokenDetailsRouter
|
||||
|
|
@ -57,25 +62,39 @@ internal class TokenDetailsFragment : ComposeFragment() {
|
|||
super.onCreate(savedInstanceState)
|
||||
|
||||
if (marketsFeatureToggles.isFeatureEnabled) {
|
||||
val cryptoCurrency: CryptoCurrency = arguments
|
||||
?.getBundle(AppRoute.CurrencyDetails.CRYPTO_CURRENCY_KEY)
|
||||
?.unbundle(CryptoCurrency.serializer())
|
||||
?: error("Token Details screen can't open without `CryptoCurrency`")
|
||||
|
||||
val param = cryptoCurrency.toParam() ?: return
|
||||
|
||||
val appContext = DefaultAppComponentContext(
|
||||
componentContext = defaultComponentContext(requireActivity().onBackPressedDispatcher),
|
||||
messageHandler = uiDependencies.eventMessageHandler,
|
||||
dispatchers = coroutineDispatcherProvider,
|
||||
hiltComponentBuilder = componentBuilder,
|
||||
replaceRouter = appRouter.asRouter(),
|
||||
)
|
||||
|
||||
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),
|
||||
params = param,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun CryptoCurrency.toParam(): TokenMarketBlockComponent.Params? {
|
||||
val tokenId = id.rawCurrencyId ?: return null // token price is not available
|
||||
|
||||
return TokenMarketBlockComponent.Params(
|
||||
tokenId = tokenId,
|
||||
tokenName = name,
|
||||
tokenSymbol = symbol,
|
||||
tokenImageUrl = iconUrl,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun ScreenContent(modifier: Modifier) {
|
||||
val viewModel = hiltViewModel<TokenDetailsViewModel>()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue