diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 95ce7f4c75..27f8c3c8e9 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -296,6 +296,16 @@
android:host="onboard-visa"
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 a4d6cdc3b3..7351bc519d 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
@@ -700,6 +700,15 @@ internal class ChildFactory @Inject constructor(
componentFactory = feedEntryComponentFactory,
)
}
+ is AppRoute.News -> {
+ createComponentChild(
+ context = context,
+ params = FeedEntryRoute.NewsList(
+ preselectedCategoryId = route.categoryId,
+ ),
+ componentFactory = feedEntryComponentFactory,
+ )
+ }
}
}
}
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 492d1d23ae..3e9d268dd5 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
@@ -9,6 +9,7 @@ import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsTokenDetailDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsTokenExchangesDeepLinkHandler
+import com.tangem.features.feed.entry.deeplink.NewsDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
@@ -54,6 +55,7 @@ internal class DeepLinkFactory @Inject constructor(
private val onboardVisaDeepLink: OnboardVisaDeepLinkHandler.Factory,
private val marketsTokenExchangesDeepLink: MarketsTokenExchangesDeepLinkHandler.Factory,
private val newsDetailsDeepLink: NewsDetailsDeepLinkHandler.Factory,
+ private val newsDeepLink: NewsDeepLinkHandler.Factory,
) {
private val permittedAppRoute = MutableStateFlow(false)
@@ -159,6 +161,7 @@ internal class DeepLinkFactory @Inject constructor(
DeepLinkRoute.WalletConnect.host -> walletConnectDeepLink.create(deeplinkUri)
DeepLinkRoute.Promo.host -> promoDeepLink.create(coroutineScope, queryParams)
DeepLinkRoute.OnboardVisa.host -> onboardVisaDeepLink.create(deeplinkUri)
+ DeepLinkRoute.News.host -> newsDeepLink.create(queryParams)
else -> {
TangemLogger.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 43ba7a2555..7a1c5173b5 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
@@ -7,6 +7,7 @@ import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsTokenDetailDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.MarketsTokenExchangesDeepLinkHandler
+import com.tangem.features.feed.entry.deeplink.NewsDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
import com.tangem.features.onramp.deeplink.OnrampDeepLinkHandler
@@ -87,6 +88,10 @@ class DeepLinkFactoryTest {
every { create(any(), any()) } returns mockk()
}
+ private val newsDeepLinkFactory = mockk(relaxed = true) {
+ every { create(any()) } returns mockk()
+ }
+
private val marketsTokenExchangesDeepLinkFactory =
mockk(relaxed = true) {
every { create(any(), any()) } returns mockk()
@@ -116,6 +121,7 @@ class DeepLinkFactoryTest {
promoDeepLink = promoDeepLinkFactory,
onboardVisaDeepLink = onboardVisaDeepLink,
newsDetailsDeepLink = newsDeeplink,
+ newsDeepLink = newsDeepLinkFactory,
)
@OptIn(ExperimentalCoroutinesApi::class)
@@ -432,6 +438,21 @@ class DeepLinkFactoryTest {
verify { onrampDeepLinkFactory.create(eq(testScope), eq(emptyMap())) }
}
+ @Test
+ fun `handleTangemDeepLinks routes news host to dedicated handler`() = runTest {
+ every { mockedUri.scheme } returns "tangem"
+ every { mockedUri.host } returns "news"
+ every { mockedUri.query } returns null
+ every { mockedUri.queryParameterNames } returns emptySet()
+ every { mockedUri.getQueryParameter(any()) } returns null
+
+ deepLinkFactory.checkRoutingReadiness(AppRoute.Wallet)
+ deepLinkFactory.handleDeeplink(mockedUri, testScope, isFromOnNewIntent)
+ advanceUntilIdle()
+
+ verify { newsDeepLinkFactory.create(eq(emptyMap())) }
+ }
+
@Test
fun `handleTangemDeepLinks routes to promo handler`() = runTest {
every { mockedUri.scheme } returns "tangem"
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 b8c2d6bd0a..3a6eac7c41 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
@@ -489,4 +489,9 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId")
+
+ @Serializable
+ data class News(
+ val categoryId: Int? = null,
+ ) : AppRoute(path = "/news")
}
\ No newline at end of file
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 359839b059..b9caabe5d0 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
@@ -71,6 +71,10 @@ sealed class DeepLinkRoute {
data object PayApp : DeepLinkRoute() {
override val host: String = "tangem.com"
}
+
+ data object News : DeepLinkRoute() {
+ override val host: String = "news"
+ }
}
enum class DeepLinkScheme(val scheme: String) {
diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/deeplink/DeeplinkConst.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/deeplink/DeeplinkConst.kt
index 6fe8031620..8f745a3b32 100644
--- a/common/routing/src/main/kotlin/com/tangem/common/routing/deeplink/DeeplinkConst.kt
+++ b/common/routing/src/main/kotlin/com/tangem/common/routing/deeplink/DeeplinkConst.kt
@@ -18,4 +18,6 @@ object DeeplinkConst {
const val ORDER_KEY = "order"
const val INTERVAL_KEY = "interval"
const val SECTION_KEY = "section"
+ const val CATEGORY_ID_KEY = "category_id"
+ const val NEWS_ID_KEY = "news_id"
}
\ No newline at end of file
diff --git a/common/routing/src/test/kotlin/com/tangem/common/routing/deeplink/DeepLinkBuilderTest.kt b/common/routing/src/test/kotlin/com/tangem/common/routing/deeplink/DeepLinkBuilderTest.kt
index 596d7cd6c2..bdcb99e48d 100644
--- a/common/routing/src/test/kotlin/com/tangem/common/routing/deeplink/DeepLinkBuilderTest.kt
+++ b/common/routing/src/test/kotlin/com/tangem/common/routing/deeplink/DeepLinkBuilderTest.kt
@@ -88,6 +88,26 @@ internal class DeepLinkBuilderTest {
assertThat(result).isEqualTo("${DeeplinkConst.TANGEM_SCHEME}://$action?$key1=$value1&$key2=$value2")
}
+ @Test
+ fun `news host with categoryId produces expected uri`() {
+ val result = deepLinkBuilder
+ .setAction("news")
+ .addQueryParam(DeeplinkConst.CATEGORY_ID_KEY, "5")
+ .build()
+
+ assertThat(result).isEqualTo("${DeeplinkConst.TANGEM_SCHEME}://news?${DeeplinkConst.CATEGORY_ID_KEY}=5")
+ }
+
+ @Test
+ fun `news host with newsId produces expected uri`() {
+ val result = deepLinkBuilder
+ .setAction("news")
+ .addQueryParam(DeeplinkConst.NEWS_ID_KEY, "20533")
+ .build()
+
+ assertThat(result).isEqualTo("${DeeplinkConst.TANGEM_SCHEME}://news?${DeeplinkConst.NEWS_ID_KEY}=20533")
+ }
+
@Test
fun `GIVEN complex deep link WHEN build THEN should construct correct URI`() {
// GIVEN
diff --git a/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/components/FeedEntryRoute.kt b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/components/FeedEntryRoute.kt
index ab97474343..098b29f4da 100644
--- a/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/components/FeedEntryRoute.kt
+++ b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/components/FeedEntryRoute.kt
@@ -36,4 +36,7 @@ sealed interface FeedEntryRoute {
@Serializable
data class NewsDetail(val articleId: Int, val preselectedArticlesId: List) : FeedEntryRoute
+
+ @Serializable
+ data class NewsList(val preselectedCategoryId: Int? = null) : FeedEntryRoute
}
\ No newline at end of file
diff --git a/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/NewsDeepLinkHandler.kt b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/NewsDeepLinkHandler.kt
new file mode 100644
index 0000000000..4beed492b5
--- /dev/null
+++ b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/NewsDeepLinkHandler.kt
@@ -0,0 +1,8 @@
+package com.tangem.features.feed.entry.deeplink
+
+interface NewsDeepLinkHandler {
+
+ interface Factory {
+ fun create(queryParams: Map): NewsDeepLinkHandler
+ }
+}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/DefaultFeedEntryComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/DefaultFeedEntryComponent.kt
index a0b3a03088..198cdac711 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/DefaultFeedEntryComponent.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/DefaultFeedEntryComponent.kt
@@ -125,7 +125,7 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
}
override fun onOpenAllNews() {
- innerRouter.push(FeedEntryChildFactory.Child.NewsList)
+ innerRouter.push(FeedEntryChildFactory.Child.NewsList())
}
override fun onOpenEarnPage() {
@@ -263,6 +263,9 @@ internal class DefaultFeedEntryComponent @AssistedInject constructor(
},
),
)
+ is FeedEntryRoute.NewsList -> FeedEntryChildFactory.Child.NewsList(
+ preselectedCategoryId = entryRoute.preselectedCategoryId,
+ )
null -> FeedEntryChildFactory.Child.Feed
}
}
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt
index 768445bbd6..fb8c3a7f90 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/FeedEntryChildFactory.kt
@@ -53,7 +53,7 @@ internal class FeedEntryChildFactory @Inject constructor(
@Serializable
@Immutable
- data object NewsList : Child
+ data class NewsList(val preselectedCategoryId: Int? = null) : Child
@Serializable
@Immutable
@@ -110,7 +110,7 @@ internal class FeedEntryChildFactory @Inject constructor(
params = child.params,
)
}
- Child.NewsList -> {
+ is Child.NewsList -> {
DefaultNewsListComponent(
appComponentContext = appComponentContext,
params = Params(
@@ -123,6 +123,7 @@ internal class FeedEntryChildFactory @Inject constructor(
)
},
onBackClick = onBackClicked,
+ preselectedCategoryId = child.preselectedCategoryId,
),
)
}
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt
index b93cac2f9f..1325a29be6 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/components/news/list/DefaultNewsListComponent.kt
@@ -112,5 +112,6 @@ internal class DefaultNewsListComponent(
paginationConfig: NewsListConfig?,
) -> Unit,
val onBackClick: () -> Unit,
+ val preselectedCategoryId: Int? = null,
)
}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultNewsDeepLinkHandler.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultNewsDeepLinkHandler.kt
new file mode 100644
index 0000000000..66aab9b849
--- /dev/null
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultNewsDeepLinkHandler.kt
@@ -0,0 +1,35 @@
+package com.tangem.features.feed.deeplink
+
+import com.tangem.common.routing.AppRoute
+import com.tangem.common.routing.AppRouter
+import com.tangem.common.routing.deeplink.DeeplinkConst.CATEGORY_ID_KEY
+import com.tangem.common.routing.deeplink.DeeplinkConst.NEWS_ID_KEY
+import com.tangem.features.feed.entry.deeplink.NewsDeepLinkHandler
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+
+internal class DefaultNewsDeepLinkHandler @AssistedInject constructor(
+ @Assisted private val queryParams: Map,
+ private val appRouter: AppRouter,
+) : NewsDeepLinkHandler {
+
+ init {
+ handleDeepLink()
+ }
+
+ private fun handleDeepLink() {
+ val newsId = queryParams[NEWS_ID_KEY]?.toIntOrNull()
+ if (newsId != null) {
+ appRouter.push(AppRoute.NewsDetails(newsId = newsId))
+ return
+ }
+
+ appRouter.push(AppRoute.News(categoryId = queryParams[CATEGORY_ID_KEY]?.toIntOrNull()))
+ }
+
+ @AssistedFactory
+ interface Factory : NewsDeepLinkHandler.Factory {
+ override fun create(queryParams: Map): DefaultNewsDeepLinkHandler
+ }
+}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/di/FeedDeepLinkModule.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/di/FeedDeepLinkModule.kt
index d00b30cc79..05f42eb535 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/di/FeedDeepLinkModule.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/di/FeedDeepLinkModule.kt
@@ -1,6 +1,8 @@
package com.tangem.features.feed.deeplink.di
+import com.tangem.features.feed.deeplink.DefaultNewsDeepLinkHandler
import com.tangem.features.feed.deeplink.DefaultNewsDetailsDeepLinkHandler
+import com.tangem.features.feed.entry.deeplink.NewsDeepLinkHandler
import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
import dagger.Binds
import dagger.Module
@@ -17,4 +19,8 @@ internal interface FeedDeepLinkModule {
fun bindNewsDetailsDeepLinkHandlerFactory(
impl: DefaultNewsDetailsDeepLinkHandler.Factory,
): NewsDetailsDeepLinkHandler.Factory
+
+ @Binds
+ @Singleton
+ fun bindNewsDeepLinkHandlerFactory(impl: DefaultNewsDeepLinkHandler.Factory): NewsDeepLinkHandler.Factory
}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt
index 409a942b93..483ad0271e 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/model/news/list/NewsListModel.kt
@@ -6,6 +6,8 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.components.chip.entity.ChipUM
+import com.tangem.core.ui.event.consumedEvent
+import com.tangem.core.ui.event.triggeredEvent
import com.tangem.domain.news.model.NewsListConfig
import com.tangem.domain.news.usecase.GetNewsCategoriesUseCase
import com.tangem.domain.news.usecase.GetNewsListBatchFlowUseCase
@@ -39,7 +41,7 @@ internal class NewsListModel @Inject constructor(
) : Model() {
private val params = paramsContainer.require()
- private val selectedCategoryId = MutableStateFlow(null)
+ private val selectedCategoryId = MutableStateFlow(params.preselectedCategoryId)
private val categoriesLoader by lazy {
NewsCategoriesLoader(
@@ -67,7 +69,7 @@ internal class NewsListModel @Inject constructor(
private val _state = MutableStateFlow(
NewsListUM(
- selectedCategoryId = DEFAULT_ALL_NEWS_CATEGORIES_ID,
+ selectedCategoryId = params.preselectedCategoryId ?: DEFAULT_ALL_NEWS_CATEGORIES_ID,
filters = persistentListOf(),
newsListState = NewsListState.Loading,
listOfArticles = persistentListOf(),
@@ -84,20 +86,38 @@ internal class NewsListModel @Inject constructor(
val state = _state.asStateFlow()
init {
- loadCategories()
observeNewsList()
- batchFlowManager.reload()
+ loadCategories()
}
private fun loadCategories() {
modelScope.launch(dispatchers.default) {
val filterChips = categoriesLoader.load()
+ val validIds = filterChips.mapTo(mutableSetOf()) { it.id }
+ selectedCategoryId.value = selectedCategoryId.value?.takeIf { it in validIds }
+ val effectiveId = selectedCategoryId.value ?: DEFAULT_ALL_NEWS_CATEGORIES_ID
+ val scrollIndex = filterChips.indexOfFirst { it.id == effectiveId }.takeIf { it > 0 }
_state.update { currentState ->
- currentState.copy(filters = filterChips)
+ currentState.copy(
+ selectedCategoryId = effectiveId,
+ filters = filterChips.map { chip ->
+ chip.copy(isSelected = chip.id == effectiveId)
+ }.toImmutableList(),
+ scrollToCategoryEvent = if (scrollIndex != null) {
+ triggeredEvent(data = scrollIndex, onConsume = ::onScrollToCategoryConsumed)
+ } else {
+ currentState.scrollToCategoryEvent
+ },
+ )
}
+ batchFlowManager.reload()
}
}
+ private fun onScrollToCategoryConsumed() {
+ _state.update { it.copy(scrollToCategoryEvent = consumedEvent()) }
+ }
+
private fun observeNewsList() {
modelScope.launch(dispatchers.default) {
combine(
@@ -111,7 +131,6 @@ internal class NewsListModel @Inject constructor(
paginationStatus = paginationStatus,
onRetryClick = {
loadCategories()
- batchFlowManager.reload()
},
onLoadMore = { batchFlowManager.loadMore() },
)
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/NewsListContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/NewsListContent.kt
index ab551d070a..9bc099b815 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/NewsListContent.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/NewsListContent.kt
@@ -3,6 +3,7 @@ package com.tangem.features.feed.ui.news.list
import androidx.compose.animation.core.EaseOut
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
@@ -20,6 +21,7 @@ import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.components.haze.hazeSourceTangem
import com.tangem.core.ui.components.label.entity.LabelUM
import com.tangem.core.ui.ds.tabs.TangemTab
+import com.tangem.core.ui.event.EventEffect
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.res.*
import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
@@ -50,6 +52,9 @@ internal fun NewsListContent(contentPadding: PaddingValues, state: NewsListUM, m
internal fun NewsListContentV1(contentPadding: PaddingValues, state: NewsListUM, modifier: Modifier = Modifier) {
val background = LocalMainBottomSheetColor.current.value
val lazyListState = rememberLazyListState()
+ val chipsListState = rememberLazyListState()
+
+ ScrollChipsToSelected(state = state, chipsListState = chipsListState)
Column(
modifier = modifier
@@ -58,6 +63,7 @@ internal fun NewsListContentV1(contentPadding: PaddingValues, state: NewsListUM,
) {
SpacerH(contentPadding.calculateTopPadding())
LazyRow(
+ state = chipsListState,
contentPadding = PaddingValues(horizontal = 16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
@@ -84,9 +90,12 @@ internal fun NewsListContentV1(contentPadding: PaddingValues, state: NewsListUM,
internal fun NewsListContentV2(contentPadding: PaddingValues, state: NewsListUM) {
val background = LocalMainBottomSheetColor.current.value
val lazyListState = rememberLazyListState()
+ val chipsListState = rememberLazyListState()
var chipsHeight by remember { mutableStateOf(0.dp) }
val density = LocalDensity.current
+ ScrollChipsToSelected(state = state, chipsListState = chipsListState)
+
Box(
modifier = Modifier
.fillMaxSize()
@@ -103,6 +112,7 @@ internal fun NewsListContentV2(contentPadding: PaddingValues, state: NewsListUM)
onArticleClick = state.onArticleClick,
)
LazyRow(
+ state = chipsListState,
modifier = Modifier
.align(Alignment.TopStart)
.padding(top = contentPadding.calculateTopPadding(), bottom = TangemTheme.dimens2.x4)
@@ -141,6 +151,13 @@ internal fun NewsListContentV2(contentPadding: PaddingValues, state: NewsListUM)
}
}
+@Composable
+private fun ScrollChipsToSelected(state: NewsListUM, chipsListState: LazyListState) {
+ EventEffect(event = state.scrollToCategoryEvent) { index ->
+ chipsListState.animateScrollToItem(index)
+ }
+}
+
@Suppress("LongMethod")
@Preview(showBackground = true)
@Composable
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/state/NewsListUM.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/state/NewsListUM.kt
index 9d8c0e32b1..b910d4f30d 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/state/NewsListUM.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/list/state/NewsListUM.kt
@@ -1,8 +1,10 @@
package com.tangem.features.feed.ui.news.list.state
import androidx.compose.runtime.Immutable
-import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import com.tangem.core.ui.components.chip.entity.ChipUM
+import com.tangem.core.ui.event.StateEvent
+import com.tangem.core.ui.event.consumedEvent
+import com.tangem.features.feed.ui.feed.components.articles.ArticleConfigUM
import kotlinx.collections.immutable.ImmutableList
@Immutable
@@ -13,6 +15,7 @@ data class NewsListUM(
val newsListState: NewsListState,
val onArticleClick: (Int) -> Unit,
val onBackClick: () -> Unit,
+ val scrollToCategoryEvent: StateEvent = consumedEvent(),
)
@Immutable
diff --git a/features/feed/impl/src/test/kotlin/com/tangem/features/feed/deeplink/DefaultNewsDeepLinkHandlerTest.kt b/features/feed/impl/src/test/kotlin/com/tangem/features/feed/deeplink/DefaultNewsDeepLinkHandlerTest.kt
new file mode 100644
index 0000000000..d1113f142f
--- /dev/null
+++ b/features/feed/impl/src/test/kotlin/com/tangem/features/feed/deeplink/DefaultNewsDeepLinkHandlerTest.kt
@@ -0,0 +1,80 @@
+package com.tangem.features.feed.deeplink
+
+import com.tangem.common.routing.AppRoute
+import com.tangem.common.routing.AppRouter
+import com.tangem.common.routing.deeplink.DeeplinkConst.CATEGORY_ID_KEY
+import com.tangem.common.routing.deeplink.DeeplinkConst.NEWS_ID_KEY
+import io.mockk.Runs
+import io.mockk.every
+import io.mockk.just
+import io.mockk.mockk
+import io.mockk.verify
+import org.junit.jupiter.api.BeforeEach
+import org.junit.jupiter.api.Test
+
+class DefaultNewsDeepLinkHandlerTest {
+
+ private val appRouter: AppRouter = mockk()
+
+ @BeforeEach
+ fun setUp() {
+ every { appRouter.push(any(), any()) } just Runs
+ }
+
+ @Test
+ fun `no params opens news list with null category`() {
+ DefaultNewsDeepLinkHandler(queryParams = emptyMap(), appRouter = appRouter)
+
+ verify { appRouter.push(AppRoute.News(categoryId = null), any()) }
+ }
+
+ @Test
+ fun `valid categoryId opens news list with that category`() {
+ DefaultNewsDeepLinkHandler(
+ queryParams = mapOf(CATEGORY_ID_KEY to "5"),
+ appRouter = appRouter,
+ )
+
+ verify { appRouter.push(AppRoute.News(categoryId = 5), any()) }
+ }
+
+ @Test
+ fun `unknown categoryId is forwarded — sanitized in NewsListModel against loaded chips`() {
+ DefaultNewsDeepLinkHandler(
+ queryParams = mapOf(CATEGORY_ID_KEY to "42"),
+ appRouter = appRouter,
+ )
+
+ verify { appRouter.push(AppRoute.News(categoryId = 42), any()) }
+ }
+
+ @Test
+ fun `non-integer categoryId is silently dropped`() {
+ DefaultNewsDeepLinkHandler(
+ queryParams = mapOf(CATEGORY_ID_KEY to "not-a-number"),
+ appRouter = appRouter,
+ )
+
+ verify { appRouter.push(AppRoute.News(categoryId = null), any()) }
+ }
+
+ @Test
+ fun `newsId opens news details`() {
+ DefaultNewsDeepLinkHandler(
+ queryParams = mapOf(NEWS_ID_KEY to "20533"),
+ appRouter = appRouter,
+ )
+
+ verify { appRouter.push(AppRoute.NewsDetails(newsId = 20533), any()) }
+ }
+
+ @Test
+ fun `newsId takes priority over categoryId`() {
+ DefaultNewsDeepLinkHandler(
+ queryParams = mapOf(NEWS_ID_KEY to "20533", CATEGORY_ID_KEY to "5"),
+ appRouter = appRouter,
+ )
+
+ verify { appRouter.push(AppRoute.NewsDetails(newsId = 20533), any()) }
+ }
+}
\ No newline at end of file