diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b61add5332..06a868e0a4 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -186,6 +186,28 @@
android:host="staking"
android:scheme="tangem" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
index 238574faa4..1c3e20f554 100644
--- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
+++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
@@ -12,6 +12,7 @@ import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.managetokens.component.ManageTokensComponent
import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
+import com.tangem.features.markets.tokenlist.MarketsTokenListComponent
import com.tangem.features.nft.component.NFTComponent
import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent
import com.tangem.features.onramp.component.*
@@ -47,6 +48,7 @@ internal class ChildFactory @Inject constructor(
private val disclaimerComponentFactory: DisclaimerComponent.Factory,
private val manageTokensComponentFactory: ManageTokensComponent.Factory,
private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory,
+ private val marketsTokenListComponentFactory: MarketsTokenListComponent.Factory,
private val onrampComponentFactory: OnrampComponent.Factory,
private val onrampSuccessComponentFactory: OnrampSuccessComponent.Factory,
private val buyCryptoComponentFactory: BuyCryptoComponent.Factory,
@@ -389,6 +391,13 @@ internal class ChildFactory @Inject constructor(
componentFactory = nftSendComponentFactory,
)
}
+ is AppRoute.Markets -> {
+ createComponentChild(
+ context = context,
+ params = Unit,
+ componentFactory = marketsTokenListComponentFactory,
+ )
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
index 00e016b292..f33be6d875 100644
--- a/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
+++ b/app/src/main/java/com/tangem/tap/routing/utils/DeepLinkFactory.kt
@@ -5,6 +5,8 @@ import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.DeepLinkRoute
import com.tangem.common.routing.DeepLinkScheme
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
+import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
+import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler
@@ -35,6 +37,8 @@ internal class DeepLinkFactory @Inject constructor(
private val walletDeepLink: WalletDeepLinkHandler.Factory,
private val tokenDetailsDeepLink: TokenDetailsDeepLinkHandler.Factory,
private val stakingDeepLink: StakingDeepLinkHandler.Factory,
+ private val marketsDeepLink: MarketsDeepLinkHandler.Factory,
+ private val marketsTokenDetailDeepLink: MarketsTokenDetailDeepLinkHandler.Factory,
) {
private val permittedAppRoute = MutableStateFlow(false)
@@ -109,6 +113,8 @@ internal class DeepLinkFactory @Inject constructor(
isFromOnNewIntent = isFromOnNewIntent,
)
DeepLinkRoute.Staking.host -> stakingDeepLink.create(coroutineScope, queryParams)
+ DeepLinkRoute.Markets.host -> marketsDeepLink.create()
+ DeepLinkRoute.MarketTokenDetail.host -> marketsTokenDetailDeepLink.create(coroutineScope, queryParams)
else -> {
Timber.i(
"""
diff --git a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
index 40a82fad7e..8078f58b05 100644
--- a/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
+++ b/app/src/test/kotlin/com/tangem/tap/routing/utils/DeepLinkFactoryTest.kt
@@ -3,6 +3,8 @@ package com.tangem.tap.routing.utils
import android.net.Uri
import com.tangem.common.routing.AppRoute
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
+import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
+import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
import com.tangem.features.send.v2.api.deeplink.SellDeepLinkHandler
@@ -49,6 +51,12 @@ class DeepLinkFactoryTest {
private val stakingDeepLinkFactory = mockk(relaxed = true) {
every { create(any(), any()) } returns mockk()
}
+ private val marketsDeepLinkFactory = mockk(relaxed = true) {
+ every { create() } returns mockk()
+ }
+ private val marketsTokenDetailDeepLinkFactory = mockk(relaxed = true) {
+ every { create(any(), any()) } returns mockk()
+ }
private val mockedUri = mockk(relaxed = true)
private val isFromOnNewIntent: Boolean = false
@@ -65,6 +73,8 @@ class DeepLinkFactoryTest {
walletDeepLinkFactory,
tokenDetailsDeepLinkFactory,
stakingDeepLinkFactory,
+ marketsDeepLinkFactory,
+ marketsTokenDetailDeepLinkFactory,
)
@OptIn(ExperimentalCoroutinesApi::class)
@@ -220,13 +230,13 @@ class DeepLinkFactoryTest {
every { mockedUri.host } returns "staking"
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
- verify {
- tokenDetailsDeepLinkFactory.create(
- eq(testScope),
- eq(mapOf("param" to "value")),
- eq(isFromOnNewIntent),
- )
- }
+ verify { stakingDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
+
+ // Test Market Token Detail
+ every { mockedUri.host } returns "token_chart"
+ deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
+ advanceUntilIdle()
+ verify { marketsTokenDetailDeepLinkFactory.create(eq(testScope), eq(mapOf("param" to "value"))) }
// Reset params
every { mockedUri.queryParameterNames } returns emptySet()
@@ -249,6 +259,12 @@ class DeepLinkFactoryTest {
deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
advanceUntilIdle()
verify { walletDeepLinkFactory.create() }
+
+ // Test Markets
+ every { mockedUri.host } returns "markets"
+ deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
+ advanceUntilIdle()
+ verify { marketsDeepLinkFactory.create() }
}
@Test
diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
index a8361504f7..b9629af57a 100644
--- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
+++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
@@ -182,6 +182,9 @@ sealed class AppRoute(val path: String) : Route {
val userWalletId: UserWalletId,
) : AppRoute(path = "/wallet_settings/${userWalletId.stringValue}")
+ @Serializable
+ data object Markets : AppRoute(path = "/markets")
+
@Serializable
data class MarketsTokenDetails(
val token: TokenMarketParams,
diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt
index 3d7be7ec68..26a1ac0ce5 100644
--- a/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt
+++ b/common/routing/src/main/kotlin/com/tangem/common/routing/DeepLinkRoute.kt
@@ -31,6 +31,14 @@ sealed class DeepLinkRoute {
data object Staking : DeepLinkRoute() {
override val host: String = "staking"
}
+
+ data object Markets : DeepLinkRoute() {
+ override val host: String = "markets"
+ }
+
+ data object MarketTokenDetail : DeepLinkRoute() {
+ override val host: String = "token_chart"
+ }
}
enum class DeepLinkScheme(val scheme: String) {
diff --git a/features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsDeepLinkHandler.kt b/features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsDeepLinkHandler.kt
new file mode 100644
index 0000000000..e88a78c729
--- /dev/null
+++ b/features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsDeepLinkHandler.kt
@@ -0,0 +1,8 @@
+package com.tangem.features.markets.deeplink
+
+interface MarketsDeepLinkHandler {
+
+ interface Factory {
+ fun create(): MarketsDeepLinkHandler
+ }
+}
\ No newline at end of file
diff --git a/features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsTokenDetailDeepLinkHandler.kt b/features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsTokenDetailDeepLinkHandler.kt
new file mode 100644
index 0000000000..45ff62203c
--- /dev/null
+++ b/features/markets/api/src/main/kotlin/com/tangem/features/markets/deeplink/MarketsTokenDetailDeepLinkHandler.kt
@@ -0,0 +1,10 @@
+package com.tangem.features.markets.deeplink
+
+import kotlinx.coroutines.CoroutineScope
+
+interface MarketsTokenDetailDeepLinkHandler {
+
+ interface Factory {
+ fun create(coroutineScope: CoroutineScope, params: Map): MarketsTokenDetailDeepLinkHandler
+ }
+}
\ No newline at end of file
diff --git a/features/markets/api/src/main/kotlin/com/tangem/features/markets/tokenlist/MarketsTokenListComponent.kt b/features/markets/api/src/main/kotlin/com/tangem/features/markets/tokenlist/MarketsTokenListComponent.kt
new file mode 100644
index 0000000000..670816199f
--- /dev/null
+++ b/features/markets/api/src/main/kotlin/com/tangem/features/markets/tokenlist/MarketsTokenListComponent.kt
@@ -0,0 +1,23 @@
+package com.tangem.features.markets.tokenlist
+
+import androidx.compose.runtime.Composable
+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.factory.ComponentFactory
+import com.tangem.core.ui.decompose.ComposableContentComponent
+import com.tangem.features.markets.entry.BottomSheetState
+
+@Stable
+interface MarketsTokenListComponent : ComposableContentComponent {
+
+ @Composable
+ fun BottomSheetContent(
+ bottomSheetState: State,
+ onHeaderSizeChange: (Dp) -> Unit,
+ modifier: Modifier,
+ )
+
+ interface Factory : ComponentFactory
+}
\ No newline at end of file
diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts
index fded1c34bc..6609be68ac 100644
--- a/features/markets/impl/build.gradle.kts
+++ b/features/markets/impl/build.gradle.kts
@@ -17,6 +17,9 @@ dependencies {
api(projects.features.onramp.api)
implementation(projects.core.navigation)
+ /* Data */
+ implementation(projects.data.common)
+
/* Domain */
implementation(projects.domain.appCurrency)
implementation(projects.domain.appCurrency.models)
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsDeepLinkHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsDeepLinkHandler.kt
new file mode 100644
index 0000000000..e620aaae47
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsDeepLinkHandler.kt
@@ -0,0 +1,20 @@
+package com.tangem.features.markets.deeplink
+
+import com.tangem.common.routing.AppRoute
+import com.tangem.common.routing.AppRouter
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+internal class DefaultMarketsDeepLinkHandler @AssistedInject constructor(
+ appRouter: AppRouter,
+) : MarketsDeepLinkHandler {
+
+ init {
+ appRouter.push(AppRoute.Markets)
+ }
+
+ @AssistedFactory
+ interface Factory : MarketsDeepLinkHandler.Factory {
+ override fun create(): DefaultMarketsDeepLinkHandler
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsTokenDetailDeepLinkHandler.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsTokenDetailDeepLinkHandler.kt
new file mode 100644
index 0000000000..5007ac70ab
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/DefaultMarketsTokenDetailDeepLinkHandler.kt
@@ -0,0 +1,79 @@
+package com.tangem.features.markets.deeplink
+
+import arrow.core.getOrElse
+import com.tangem.common.routing.AppRoute
+import com.tangem.common.routing.AppRouter
+import com.tangem.data.common.currency.getTokenIconUrlFromDefaultHost
+import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
+import com.tangem.domain.appcurrency.model.AppCurrency
+import com.tangem.domain.markets.GetTokenMarketInfoUseCase
+import com.tangem.domain.markets.TokenMarketParams
+import com.tangem.domain.models.currency.CryptoCurrency
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import timber.log.Timber
+
+internal class DefaultMarketsTokenDetailDeepLinkHandler @AssistedInject constructor(
+ @Assisted scope: CoroutineScope,
+ @Assisted queryParams: Map,
+ appRouter: AppRouter,
+ getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
+ getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
+) : MarketsTokenDetailDeepLinkHandler {
+
+ init {
+ val tokenId = queryParams[TOKEN_ID_KEY]
+
+ val rawTokenId = CryptoCurrency.RawID(tokenId.orEmpty())
+
+ scope.launch {
+ val appCurrency = getSelectedAppCurrencyUseCase.invokeSync().getOrElse {
+ AppCurrency.Default
+ }
+ val tokenInfo = getTokenMarketInfoUseCase(
+ appCurrency = appCurrency,
+ tokenId = rawTokenId,
+ tokenSymbol = TOKEN_SYMBOL_KEY,
+ ).getOrElse {
+ Timber.e("Failed to get market token info")
+ return@launch
+ }
+
+ appRouter.push(
+ AppRoute.MarketsTokenDetails(
+ token = TokenMarketParams(
+ id = rawTokenId,
+ name = tokenInfo.name,
+ symbol = tokenInfo.symbol,
+ tokenQuotes = TokenMarketParams.Quotes(
+ currentPrice = tokenInfo.quotes.currentPrice,
+ h24Percent = tokenInfo.quotes.h24ChangePercent,
+ weekPercent = tokenInfo.quotes.weekChangePercent,
+ monthPercent = tokenInfo.quotes.monthChangePercent,
+ ),
+ imageUrl = getTokenIconUrlFromDefaultHost(rawTokenId),
+ ),
+ appCurrency = appCurrency,
+ showPortfolio = true,
+ analyticsParams = null,
+ ),
+ )
+ }
+ }
+
+ @AssistedFactory
+ interface Factory : MarketsTokenDetailDeepLinkHandler.Factory {
+ override fun create(
+ coroutineScope: CoroutineScope,
+ queryParams: Map,
+ ): DefaultMarketsTokenDetailDeepLinkHandler
+ }
+
+ private companion object {
+ const val TOKEN_ID_KEY = "token_id"
+ const val TOKEN_SYMBOL_KEY = "token_symbol"
+ }
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/di/MarketsDeepLinkModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/di/MarketsDeepLinkModule.kt
new file mode 100644
index 0000000000..168ea58a46
--- /dev/null
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/deeplink/di/MarketsDeepLinkModule.kt
@@ -0,0 +1,26 @@
+package com.tangem.features.markets.deeplink.di
+
+import com.tangem.features.markets.deeplink.DefaultMarketsDeepLinkHandler
+import com.tangem.features.markets.deeplink.DefaultMarketsTokenDetailDeepLinkHandler
+import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
+import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
+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 MarketsDeepLinkModule {
+
+ @Binds
+ @Singleton
+ fun bindMarketsDeepLinkHandlerFactory(impl: DefaultMarketsDeepLinkHandler.Factory): MarketsDeepLinkHandler.Factory
+
+ @Binds
+ @Singleton
+ fun bindMarketsTokenDetailDeepLinkHandlerFactory(
+ impl: DefaultMarketsTokenDetailDeepLinkHandler.Factory,
+ ): MarketsTokenDetailDeepLinkHandler.Factory
+}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt
index d2a5ed9d0b..da862ba234 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/DefaultMarketsEntryComponent.kt
@@ -5,20 +5,15 @@ import androidx.compose.runtime.Stable
import androidx.compose.runtime.State
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
-import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
-import com.arkivanov.decompose.router.stack.pushNew
+import com.arkivanov.decompose.router.stack.popWhile
import com.arkivanov.decompose.value.Value
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
-import com.tangem.core.decompose.navigation.Router
-import com.tangem.domain.appcurrency.model.AppCurrency
-import com.tangem.domain.markets.TokenMarket
-import com.tangem.domain.markets.toSerializableParam
-import com.tangem.features.markets.details.MarketsTokenDetailsComponent
+import com.tangem.core.decompose.navigation.inner.InnerRouter
import com.tangem.features.markets.entry.BottomSheetState
import com.tangem.features.markets.entry.MarketsEntryComponent
import com.tangem.features.markets.entry.impl.MarketsEntryChildFactory.Child
@@ -35,7 +30,12 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
private val stackNavigation = StackNavigation()
- val stack: Value> = childStack(
+ private val innerRouter = InnerRouter(
+ stackNavigation = stackNavigation,
+ popCallback = { onChildBack() },
+ )
+
+ private val stack: Value> = childStack(
key = "main",
source = stackNavigation,
serializer = Child.serializer(),
@@ -46,9 +46,8 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
child = configuration,
appComponentContext = childByContext(
componentContext = factoryContext,
- router = createRouter(configuration),
+ router = innerRouter,
),
- onTokenSelected = ::marketsListTokenSelected,
)
},
)
@@ -68,32 +67,9 @@ internal class DefaultMarketsEntryComponent @AssistedInject constructor(
)
}
- @OptIn(ExperimentalDecomposeApi::class)
- private fun marketsListTokenSelected(token: TokenMarket, appCurrency: AppCurrency) {
- stackNavigation.pushNew(
- configuration = Child.TokenDetails(
- params = MarketsTokenDetailsComponent.Params(
- token = token.toSerializableParam(),
- appCurrency = appCurrency,
- showPortfolio = true,
- analyticsParams = MarketsTokenDetailsComponent.AnalyticsParams(
- blockchain = null,
- source = "Market",
- ),
- ),
- ),
- )
- }
-
- private fun AppComponentContext.createRouter(child: Child): Router {
- return when (child) {
- is Child.TokenDetails -> {
- MarketTokenDetailsRouter(
- contextRouter = this.router,
- stackNavigation = stackNavigation,
- )
- }
- else -> this.router
+ private fun onChildBack() {
+ if (stack.value.active.configuration !is Child.TokenList) {
+ stackNavigation.popWhile { it != Child.TokenList }
}
}
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/MarketTokenDetailsRouter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/MarketTokenDetailsRouter.kt
deleted file mode 100644
index fb1d1b1eff..0000000000
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/MarketTokenDetailsRouter.kt
+++ /dev/null
@@ -1,25 +0,0 @@
-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,
-) : 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) {
- /** Not allowed */
- }
-
- override fun popTo(routeClass: KClass, onComplete: (isSuccess: Boolean) -> Unit) {
- /** Not allowed */
- }
-}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/MarketsEntryChildFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/MarketsEntryChildFactory.kt
index 31e6f5e1e0..9a11de132d 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/MarketsEntryChildFactory.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/MarketsEntryChildFactory.kt
@@ -2,11 +2,9 @@ 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.core.decompose.navigation.Route
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 com.tangem.features.markets.tokenlist.MarketsTokenListComponent
import kotlinx.serialization.Serializable
import javax.inject.Inject
@@ -17,7 +15,7 @@ internal class MarketsEntryChildFactory @Inject constructor(
@Serializable
@Immutable
- sealed interface Child {
+ sealed interface Child : Route {
@Serializable
@Immutable
@@ -28,11 +26,7 @@ internal class MarketsEntryChildFactory @Inject constructor(
data class TokenDetails(val params: MarketsTokenDetailsComponent.Params) : Child
}
- fun createChild(
- child: Child,
- appComponentContext: AppComponentContext,
- onTokenSelected: (TokenMarket, AppCurrency) -> Unit,
- ): Any {
+ fun createChild(child: Child, appComponentContext: AppComponentContext): Any {
return when (child) {
is Child.TokenDetails -> {
tokenDetailsComponentFactory.create(
@@ -43,7 +37,7 @@ internal class MarketsEntryChildFactory @Inject constructor(
is Child.TokenList -> {
tokenListComponentFactory.create(
context = appComponentContext,
- onTokenSelected = onTokenSelected,
+ params = Unit,
)
}
}
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/ui/EntryBottomSheetContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/ui/EntryBottomSheetContent.kt
index 4adeb8b8c5..5d6ff14c2a 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/ui/EntryBottomSheetContent.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/entry/impl/ui/EntryBottomSheetContent.kt
@@ -20,7 +20,7 @@ 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
+import com.tangem.features.markets.tokenlist.MarketsTokenListComponent
@Composable
internal fun EntryBottomSheetContent(
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/api/MarketsTokenListComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/api/MarketsTokenListComponent.kt
deleted file mode 100644
index 64b0f12b74..0000000000
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/api/MarketsTokenListComponent.kt
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.tangem.features.markets.tokenlist.api
-
-import androidx.compose.runtime.Composable
-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.domain.appcurrency.model.AppCurrency
-import com.tangem.domain.markets.TokenMarket
-import com.tangem.features.markets.entry.BottomSheetState
-
-@Stable
-interface MarketsTokenListComponent {
-
- @Composable
- fun BottomSheetContent(
- bottomSheetState: State,
- onHeaderSizeChange: (Dp) -> Unit,
- modifier: Modifier,
- )
-
- interface Factory {
- fun create(
- context: AppComponentContext,
- onTokenSelected: (TokenMarket, AppCurrency) -> Unit,
- ): MarketsTokenListComponent
- }
-}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/DefaultMarketsTokenListComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/DefaultMarketsTokenListComponent.kt
index a04a5f8b63..16ecb9a9c7 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/DefaultMarketsTokenListComponent.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/DefaultMarketsTokenListComponent.kt
@@ -1,34 +1,58 @@
package com.tangem.features.markets.tokenlist.impl
-import androidx.compose.runtime.*
+import androidx.compose.foundation.layout.imePadding
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.material3.Scaffold
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.lifecycle.compose.LifecycleStartEffect
import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.tangem.common.routing.AppRoute
+import com.tangem.common.routing.AppRoute.MarketsTokenDetails.AnalyticsParams
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.core.ui.res.TangemTheme
+import com.tangem.core.ui.utils.WindowInsetsZero
+import com.tangem.domain.markets.toSerializableParam
import com.tangem.features.markets.entry.BottomSheetState
-import com.tangem.features.markets.tokenlist.api.MarketsTokenListComponent
+import com.tangem.features.markets.tokenlist.MarketsTokenListComponent
import com.tangem.features.markets.tokenlist.impl.model.MarketsListModel
import com.tangem.features.markets.tokenlist.impl.ui.MarketsList
+import com.tangem.features.markets.tokenlist.impl.ui.MarketsListWithBack
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
+@Suppress("UnusedPrivateMember")
class DefaultMarketsTokenListComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
- @Assisted private val onTokenSelected: (TokenMarket, AppCurrency) -> Unit,
+ @Assisted params: Unit,
) : AppComponentContext by appComponentContext, MarketsTokenListComponent {
private val model: MarketsListModel = getOrCreateModel()
init {
model.tokenSelected
- .onEach { onTokenSelected(it.first, it.second) }
+ .onEach { (token, appCurrency) ->
+ router.push(
+ AppRoute.MarketsTokenDetails(
+ token = token.toSerializableParam(),
+ appCurrency = appCurrency,
+ showPortfolio = true,
+ analyticsParams = AnalyticsParams(
+ blockchain = null,
+ source = "Market",
+ ),
+ ),
+ )
+ }
.launchIn(componentScope)
}
@@ -60,11 +84,35 @@ class DefaultMarketsTokenListComponent @AssistedInject constructor(
)
}
+ @Composable
+ override fun Content(modifier: Modifier) {
+ LifecycleStartEffect(Unit) {
+ model.isVisibleOnScreen.value = true
+ onStopOrDispose {
+ model.isVisibleOnScreen.value = false
+ }
+ }
+
+ val state by model.state.collectAsStateWithLifecycle()
+
+ Scaffold(
+ contentWindowInsets = WindowInsetsZero,
+ containerColor = TangemTheme.colors.background.primary,
+ ) {
+ MarketsListWithBack(
+ modifier = Modifier
+ .statusBarsPadding()
+ .imePadding()
+ .padding(it),
+ state = state,
+ bottomSheetState = BottomSheetState.EXPANDED,
+ onBackClick = router::pop,
+ )
+ }
+ }
+
@AssistedFactory
interface Factory : MarketsTokenListComponent.Factory {
- override fun create(
- context: AppComponentContext,
- onTokenSelected: (TokenMarket, AppCurrency) -> Unit,
- ): DefaultMarketsTokenListComponent
+ override fun create(context: AppComponentContext, params: Unit): DefaultMarketsTokenListComponent
}
}
\ No newline at end of file
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ComponentModule.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ComponentModule.kt
index bb44e19164..4befeceeea 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ComponentModule.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/di/ComponentModule.kt
@@ -1,6 +1,6 @@
package com.tangem.features.markets.tokenlist.impl.di
-import com.tangem.features.markets.tokenlist.api.MarketsTokenListComponent
+import com.tangem.features.markets.tokenlist.MarketsTokenListComponent
import com.tangem.features.markets.tokenlist.impl.DefaultMarketsTokenListComponent
import dagger.Binds
import dagger.Module
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt
index a0f8508f89..041df48f96 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/tokenlist/impl/ui/MarketsList.kt
@@ -3,22 +3,29 @@ package com.tangem.features.markets.tokenlist.impl.ui
import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.rememberLazyListState
+import androidx.compose.material3.Icon
import androidx.compose.material3.Text
+import androidx.compose.material3.ripple
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
+import androidx.compose.ui.res.vectorResource
+import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
-import androidx.compose.ui.text.buildAnnotatedString
import com.tangem.core.ui.components.Keyboard
import com.tangem.core.ui.components.SpacerH12
import com.tangem.core.ui.components.SpacerH8
@@ -62,26 +69,8 @@ internal fun MarketsList(
bottomSheetState: BottomSheetState,
modifier: Modifier = Modifier,
) {
- Content(
- modifier = modifier,
- state = state,
- onHeaderSizeChange = onHeaderSizeChange,
- )
- MarketsListSortByBottomSheet(config = state.sortByBottomSheet)
- KeyboardEvents(
- isSortByBottomSheetShown = state.sortByBottomSheet.isShown,
- bottomSheetState = bottomSheetState,
- )
-}
-
-@Suppress("LongMethod")
-@Composable
-private fun Content(state: MarketsListUM, onHeaderSizeChange: (Dp) -> Unit, modifier: Modifier = Modifier) {
val density = LocalDensity.current
val background = LocalMainBottomSheetColor.current.value
- val strokeColor = TangemTheme.colors.stroke.primary
- val scrolledState = remember { mutableStateOf(false) }
-
Column(
modifier = modifier
.fillMaxSize()
@@ -106,85 +95,148 @@ private fun Content(state: MarketsListUM, onHeaderSizeChange: (Dp) -> Unit, modi
.padding(bottom = 4.dp),
state = state.searchBar,
)
- Column(Modifier.padding(horizontal = TangemTheme.dimens.size16)) {
- AnimatedVisibility(
- visible = scrolledState.value.not(),
- ) {
- Column {
- SpacerH8()
- Title(isInSearchMode = state.isInSearchMode)
- SpacerH12()
- }
- }
+ Content(state = state)
+ }
+ MarketsListSortByBottomSheet(config = state.sortByBottomSheet)
+ KeyboardEvents(
+ isSortByBottomSheetShown = state.sortByBottomSheet.isShown,
+ bottomSheetState = bottomSheetState,
+ )
+}
+
+@Composable
+internal fun MarketsListWithBack(
+ state: MarketsListUM,
+ bottomSheetState: BottomSheetState,
+ onBackClick: () -> Unit,
+ modifier: Modifier = Modifier,
+) {
+ val background = LocalMainBottomSheetColor.current.value
+ Column(
+ modifier = modifier
+ .fillMaxSize()
+ .imePadding()
+ .drawBehind { drawRect(background) },
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ painter = rememberVectorPainter(
+ ImageVector.vectorResource(R.drawable.ic_close_24),
+ ),
+ contentDescription = null,
+ tint = TangemTheme.colors.icon.primary1,
+ modifier = Modifier
+ .padding(16.dp)
+ .clickable(
+ interactionSource = remember { MutableInteractionSource() },
+ indication = ripple(bounded = false),
+ onClick = onBackClick,
+ ),
+ )
+ SearchBar(
+ modifier = Modifier
+ .drawBehind { drawRect(background) }
+ .padding(
+ end = 16.dp,
+ ),
+ state = state.searchBar,
+ )
+ }
+ Content(state = state)
+ }
+ MarketsListSortByBottomSheet(config = state.sortByBottomSheet)
+ KeyboardEvents(
+ isSortByBottomSheetShown = state.sortByBottomSheet.isShown,
+ bottomSheetState = bottomSheetState,
+ )
+}
+
+@Suppress("LongMethod")
+@Composable
+private fun ColumnScope.Content(state: MarketsListUM, modifier: Modifier = Modifier) {
+ val strokeColor = TangemTheme.colors.stroke.primary
+ val scrolledState = remember { mutableStateOf(false) }
+
+ Column(modifier.padding(horizontal = TangemTheme.dimens.size16)) {
+ AnimatedVisibility(
+ visible = scrolledState.value.not(),
+ ) {
Column {
- AnimatedVisibility(state.isInSearchMode.not()) {
- Options(
- modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
- sortByTypeUM = state.selectedSortBy,
- trendInterval = state.selectedInterval,
- onIntervalClick = state.onIntervalClick,
- onSortByClick = state.onSortByButtonClick,
- )
- }
-
- AnimatedVisibility(
- state.isInSearchMode.not() &&
- state.stakingNotificationMaxApy != null &&
- state.selectedSortBy != SortByTypeUM.Staking,
- ) {
- val showMore = stringResourceSafe(R.string.common_show_more)
- val description = stringResourceSafe(
- R.string.markets_staking_banner_description_placeholder,
- showMore,
- )
-
- val clickableDescription = buildAnnotatedString {
- append(description.substringBefore(showMore))
-
- pushStringAnnotation(SHOW_MORE_KEY, "")
- appendColored(showMore, TangemTheme.colors.text.accent)
- pop()
- }
-
- StakingInMarketsPromoNotification(
- config = NotificationConfig(
- iconResId = R.drawable.img_staking_in_market_notification,
- title = resourceReference(
- R.string.markets_staking_banner_title,
- wrappedList(state.stakingNotificationMaxApy.format { percent() }),
- ),
- subtitle = annotatedReference(clickableDescription),
- onClick = state.onStakingNotificationClick,
- onCloseClick = state.onStakingNotificationCloseClick,
- ),
- modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
- )
- }
+ SpacerH8()
+ Title(isInSearchMode = state.isInSearchMode)
+ SpacerH12()
+ }
+ }
+ Column {
+ AnimatedVisibility(state.isInSearchMode.not()) {
+ Options(
+ modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
+ sortByTypeUM = state.selectedSortBy,
+ trendInterval = state.selectedInterval,
+ onIntervalClick = state.onIntervalClick,
+ onSortByClick = state.onSortByButtonClick,
+ )
+ }
+
+ AnimatedVisibility(
+ state.isInSearchMode.not() &&
+ state.stakingNotificationMaxApy != null &&
+ state.selectedSortBy != SortByTypeUM.Staking,
+ ) {
+ val showMore = stringResourceSafe(R.string.common_show_more)
+ val description = stringResourceSafe(
+ R.string.markets_staking_banner_description_placeholder,
+ showMore,
+ )
+
+ val clickableDescription = buildAnnotatedString {
+ append(description.substringBefore(showMore))
+
+ pushStringAnnotation(SHOW_MORE_KEY, "")
+ appendColored(showMore, TangemTheme.colors.text.accent)
+ pop()
+ }
+
+ StakingInMarketsPromoNotification(
+ config = NotificationConfig(
+ iconResId = R.drawable.img_staking_in_market_notification,
+ title = resourceReference(
+ R.string.markets_staking_banner_title,
+ wrappedList(state.stakingNotificationMaxApy.format { percent() }),
+ ),
+ subtitle = annotatedReference(clickableDescription),
+ onClick = state.onStakingNotificationClick,
+ onCloseClick = state.onStakingNotificationCloseClick,
+ ),
+ modifier = Modifier.padding(bottom = TangemTheme.dimens.spacing12),
+ )
}
}
- val strokeWidth = TangemTheme.dimens.size0_5
- Box(
- Modifier
- .fillMaxWidth()
- .height(strokeWidth)
- .drawBehind {
- // draw horizontal line
- if (scrolledState.value) {
- drawLine(
- color = strokeColor,
- start = Offset(0f, size.height),
- end = Offset(size.width, size.height),
- strokeWidth = strokeWidth.toPx(),
- )
- }
- },
- )
- ItemsList(
- scrolledState = scrolledState,
- isInSearchMode = state.isInSearchMode,
- state = state.list,
- )
}
+ val strokeWidth = TangemTheme.dimens.size0_5
+ Box(
+ Modifier
+ .fillMaxWidth()
+ .height(strokeWidth)
+ .drawBehind {
+ // draw horizontal line
+ if (scrolledState.value) {
+ drawLine(
+ color = strokeColor,
+ start = Offset(0f, size.height),
+ end = Offset(size.width, size.height),
+ strokeWidth = strokeWidth.toPx(),
+ )
+ }
+ },
+ )
+ ItemsList(
+ scrolledState = scrolledState,
+ isInSearchMode = state.isInSearchMode,
+ state = state.list,
+ )
}
@Composable