diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ab376d6124..bb289cd06c 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -290,6 +290,17 @@
android:host="tangem.com"
android:path="/pay-app" />
+
+
+
+
+
+
+
+
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 820b018fc2..83d4c9f04b 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
@@ -727,6 +727,16 @@ internal class ChildFactory @Inject constructor(
componentFactory = yieldSupplyActiveComponentFactory,
)
}
+ is AppRoute.NewsDetails -> {
+ createComponentChild(
+ context = context,
+ params = FeedEntryRoute.NewsDetail(
+ articleId = route.newsId,
+ preselectedArticlesId = listOf(route.newsId),
+ ),
+ componentFactory = feedEntryComponentFactory,
+ )
+ }
}
}
}
\ 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 097b57b518..27c065e869 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
@@ -6,6 +6,8 @@ import com.tangem.common.routing.DeepLinkRoute
import com.tangem.common.routing.DeepLinkScheme
import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
+import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
+import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
@@ -50,6 +52,8 @@ internal class DeepLinkFactory @Inject constructor(
private val swapDeepLink: SwapDeepLinkHandler.Factory,
private val promoDeepLink: PromoDeeplinkHandler.Factory,
private val onboardVisaDeepLink: OnboardVisaDeepLinkHandler.Factory,
+ private val newsDetailsDeepLink: NewsDetailsDeepLinkHandler.Factory,
+ private val feedFeatureToggle: FeedFeatureToggle,
) {
private val permittedAppRoute = MutableStateFlow(false)
@@ -102,7 +106,7 @@ internal class DeepLinkFactory @Inject constructor(
private fun launchDeepLink(deeplinkUri: Uri, coroutineScope: CoroutineScope, isFromOnNewIntent: Boolean) {
when (deeplinkUri.scheme) {
- DeepLinkScheme.Https.scheme -> handleHttpDeepLinks(deeplinkUri)
+ DeepLinkScheme.Https.scheme -> handleHttpDeepLinks(deeplinkUri, coroutineScope)
DeepLinkScheme.Tangem.scheme -> handleTangemDeepLinks(deeplinkUri, coroutineScope, isFromOnNewIntent)
DeepLinkScheme.WalletConnect.scheme -> walletConnectDeepLink.create(deeplinkUri)
else -> {
@@ -116,10 +120,18 @@ internal class DeepLinkFactory @Inject constructor(
}
}
- private fun handleHttpDeepLinks(deeplinkUri: Uri) {
- if (deeplinkUri.host == DeepLinkRoute.PayApp.host && deeplinkUri.path?.startsWith("/pay-app") == true) {
- onboardVisaDeepLink.create(deeplinkUri)
- return
+ private fun handleHttpDeepLinks(deeplinkUri: Uri, coroutineScope: CoroutineScope) {
+ if (deeplinkUri.host == DeepLinkRoute.PayApp.host) {
+ when {
+ deeplinkUri.path?.startsWith("/pay-app") == true -> {
+ onboardVisaDeepLink.create(deeplinkUri)
+ return
+ }
+ deeplinkUri.path?.startsWith("/news") == true && feedFeatureToggle.isFeedEnabled -> {
+ newsDetailsDeepLink.create(coroutineScope, deeplinkUri)
+ return
+ }
+ }
}
}
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 ea548398d0..19782fca91 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
@@ -4,6 +4,8 @@ import android.net.Uri
import com.tangem.common.routing.AppRoute
import com.tangem.data.card.sdk.CardSdkProvider
import com.tangem.feature.referral.api.deeplink.ReferralDeepLinkHandler
+import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
+import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
import com.tangem.features.markets.deeplink.MarketsDeepLinkHandler
import com.tangem.features.markets.deeplink.MarketsTokenDetailDeepLinkHandler
import com.tangem.features.onramp.deeplink.BuyDeepLinkHandler
@@ -81,6 +83,12 @@ class DeepLinkFactoryTest {
private val cardSdkProvider = mockk(relaxed = true) {
every { sdk.uiVisibility() } returns MutableStateFlow(false)
}
+
+ private val newsDeeplink = mockk(relaxed = true) {
+ every { create(any(), any()) } returns mockk()
+ }
+ private val feedFeatureToggle = mockk()
+
private val mockedUri = mockk(relaxed = true)
private val isFromOnNewIntent: Boolean = false
@@ -103,6 +111,8 @@ class DeepLinkFactoryTest {
swapDeepLink = swapDeepLinkFactory,
promoDeepLink = promoDeepLinkFactory,
onboardVisaDeepLink = onboardVisaDeepLink,
+ newsDetailsDeepLink = newsDeeplink,
+ feedFeatureToggle = feedFeatureToggle,
)
@OptIn(ExperimentalCoroutinesApi::class)
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 dbb07560d9..ae5ccdd4d1 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
@@ -468,4 +468,7 @@ sealed class AppRoute(val path: String) : Route {
val cryptoCurrency: CryptoCurrency,
val apy: String,
) : AppRoute(path = "/yield_supply_active/${userWalletId.stringValue}/${cryptoCurrency.symbol}")
+
+ @Serializable
+ data class NewsDetails(val newsId: Int) : AppRoute(path = "/news_details/$newsId")
}
\ No newline at end of file
diff --git a/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/NewsDetailsDeepLinkHandler.kt b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/NewsDetailsDeepLinkHandler.kt
new file mode 100644
index 0000000000..f6ce381120
--- /dev/null
+++ b/features/feed/api/src/main/kotlin/com/tangem/features/feed/entry/deeplink/NewsDetailsDeepLinkHandler.kt
@@ -0,0 +1,11 @@
+package com.tangem.features.feed.entry.deeplink
+
+import android.net.Uri
+import kotlinx.coroutines.CoroutineScope
+
+interface NewsDetailsDeepLinkHandler {
+
+ interface Factory {
+ fun create(coroutineScope: CoroutineScope, deeplinkUri: Uri): NewsDetailsDeepLinkHandler
+ }
+}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultNewsDetailsDeepLinkHandler.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultNewsDetailsDeepLinkHandler.kt
new file mode 100644
index 0000000000..8409d68360
--- /dev/null
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/DefaultNewsDetailsDeepLinkHandler.kt
@@ -0,0 +1,61 @@
+package com.tangem.features.feed.deeplink
+
+import android.net.Uri
+import com.tangem.common.routing.AppRoute
+import com.tangem.common.routing.AppRouter
+import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
+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 DefaultNewsDetailsDeepLinkHandler @AssistedInject constructor(
+ @Assisted private val scope: CoroutineScope,
+ @Assisted private val deeplinkUri: Uri,
+ private val appRouter: AppRouter,
+) : NewsDetailsDeepLinkHandler {
+
+ init {
+ handleDeepLink()
+ }
+
+ private fun handleDeepLink() {
+ scope.launch {
+ val articleId = extractArticleIdFromUri(deeplinkUri)
+ if (articleId == null) {
+ Timber.e(
+ """
+ Failed to extract article ID from deep link
+ |- Received URI: $deeplinkUri
+ """.trimIndent(),
+ )
+ return@launch
+ }
+
+ appRouter.push(
+ AppRoute.NewsDetails(newsId = articleId),
+ )
+ }
+ }
+
+ /**
+ * Parsing URI with format https://tangem.com/news/{category}/{id}-{slug} to get id
+ */
+ private fun extractArticleIdFromUri(uri: Uri): Int? {
+ val path = uri.path ?: return null
+ val pathSegments = path.split("/").filter { it.isNotBlank() }
+ if (pathSegments.isEmpty() || pathSegments[0] != "news" || pathSegments.size < 2) {
+ return null
+ }
+ val lastSegment = pathSegments.last()
+ val idPart = lastSegment.substringBefore("-")
+ return idPart.toIntOrNull()
+ }
+
+ @AssistedFactory
+ interface Factory : NewsDetailsDeepLinkHandler.Factory {
+ override fun create(coroutineScope: CoroutineScope, deeplinkUri: Uri): DefaultNewsDetailsDeepLinkHandler
+ }
+}
\ 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
new file mode 100644
index 0000000000..d00b30cc79
--- /dev/null
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/deeplink/di/FeedDeepLinkModule.kt
@@ -0,0 +1,20 @@
+package com.tangem.features.feed.deeplink.di
+
+import com.tangem.features.feed.deeplink.DefaultNewsDetailsDeepLinkHandler
+import com.tangem.features.feed.entry.deeplink.NewsDetailsDeepLinkHandler
+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 FeedDeepLinkModule {
+
+ @Binds
+ @Singleton
+ fun bindNewsDetailsDeepLinkHandlerFactory(
+ impl: DefaultNewsDetailsDeepLinkHandler.Factory,
+ ): NewsDetailsDeepLinkHandler.Factory
+}
\ No newline at end of file
diff --git a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt
index 61ef4bc6c9..adb45733c1 100644
--- a/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt
+++ b/features/feed/impl/src/main/kotlin/com/tangem/features/feed/ui/news/details/NewsDetailsContent.kt
@@ -27,6 +27,7 @@ import com.tangem.common.ui.news.ArticleHeader
import com.tangem.core.ui.R
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.SpacerH
+import com.tangem.core.ui.components.SpacerHMax
import com.tangem.core.ui.components.SpacerW
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
import com.tangem.core.ui.components.pager.PagerIndicator
@@ -171,7 +172,10 @@ private fun ArticleDetail(article: ArticleUM, modifier: Modifier = Modifier, onL
items = article.sources,
key = SourceUM::id,
) { source ->
- SourceItem(source = source)
+ SourceItem(
+ source = source,
+ modifier = Modifier.fillParentMaxHeight(),
+ )
}
}
}
@@ -259,9 +263,11 @@ private fun SourceItem(source: SourceUM, modifier: Modifier = Modifier) {
color = TangemTheme.colors.text.primary1,
maxLines = 3,
overflow = TextOverflow.Ellipsis,
- modifier = Modifier.padding(bottom = 12.dp),
)
}
+
+ SpacerHMax()
+
Text(
text = source.publishedAt.resolveReference(),
style = TangemTheme.typography.caption2,