Updated on 2026-08-14
This commit is contained in:
parent
8beb80f5a7
commit
43d6e92a89
26 changed files with 639 additions and 8 deletions
|
|
@ -13,6 +13,7 @@ dependencies {
|
|||
/* Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.navigation)
|
||||
|
||||
/* Domain */
|
||||
implementation(projects.domain.qrScanning.models)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.common.routing
|
||||
|
||||
import android.net.Uri
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Routes in-app content links (deep links and external URLs)
|
||||
* - `tangem://` scheme → parsed to AppRoute and pushed via AppRouter
|
||||
* - `https://` / `http://` → opened in external browser via UrlOpener
|
||||
* - Unknown scheme → logged and ignored
|
||||
*/
|
||||
class LinkHandler(
|
||||
private val appRouter: AppRouter,
|
||||
) {
|
||||
|
||||
fun navigate(link: String) {
|
||||
val uri = Uri.parse(link)
|
||||
handleTangemDeepLink(uri)
|
||||
}
|
||||
|
||||
private fun handleTangemDeepLink(uri: Uri) {
|
||||
val route = parseDeepLinkToRoute(uri)
|
||||
if (route != null) {
|
||||
appRouter.push(route)
|
||||
} else {
|
||||
Timber.w("ContentLinkHandler: unrecognized tangem deep link: %s", uri)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("UnusedParameter", "FunctionOnlyReturningConstant")
|
||||
private fun parseDeepLinkToRoute(uri: Uri): AppRoute? {
|
||||
// TODO [REDACTED_TASK_KEY] refactor deepling routing
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
|
@ -59,5 +59,9 @@
|
|||
{
|
||||
"name": "MAIN_SCREEN_QR_SCANNING_ENABLED",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "NEW_PROMO_BANNERS_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -5,6 +5,9 @@ import com.tangem.datasource.api.promotion.models.PromoBannerResponse
|
|||
import com.tangem.datasource.api.promotion.models.PromoBannerV2Response
|
||||
import com.tangem.datasource.api.promotion.models.StoryContentResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.*
|
||||
import com.tangem.datasource.api.tangemTech.models.promobanners.PromoBannerDisplaysResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.promobanners.DismissPromoBannerRequest
|
||||
import com.tangem.datasource.api.tangemTech.models.promobanners.DismissPromoBannerResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.GetWalletArchivedAccountsResponse
|
||||
import com.tangem.datasource.api.tangemTech.models.account.SaveWalletAccountsResponse
|
||||
|
|
@ -197,6 +200,21 @@ interface TangemTechApi {
|
|||
): ApiResponse<PromoBannerV2Response>
|
||||
// endregion
|
||||
|
||||
// region promo banners
|
||||
@GET("v1/banner/displays")
|
||||
suspend fun getPromoBannerDisplays(
|
||||
@Query("walletId") walletId: String,
|
||||
@Query("placeholder") placeholder: String,
|
||||
@Query("locale") locale: String,
|
||||
): ApiResponse<PromoBannerDisplaysResponse>
|
||||
|
||||
@PATCH("v1/displays/{displayId}")
|
||||
suspend fun dismissPromoBannerDisplay(
|
||||
@Path("displayId") displayId: String,
|
||||
@Body body: DismissPromoBannerRequest,
|
||||
): ApiResponse<DismissPromoBannerResponse>
|
||||
// endregion
|
||||
|
||||
/**
|
||||
* Stores transaction hash in cache to prevent duplicate push
|
||||
* notifications for yield operations (deposit, withdraw, send).
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.datasource.api.tangemTech.models.promobanners
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DismissPromoBannerRequest(
|
||||
@Json(name = "walletId") val walletId: String,
|
||||
@Json(name = "isDismissed") val isDismissed: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.tangem.datasource.api.tangemTech.models.promobanners
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class DismissPromoBannerResponse(
|
||||
@Json(name = "displayId") val displayId: String,
|
||||
@Json(name = "walletId") val walletId: String,
|
||||
@Json(name = "isDismissed") val isDismissed: Boolean,
|
||||
@Json(name = "dismissedAt") val dismissedAt: String?,
|
||||
)
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.datasource.api.tangemTech.models.promobanners
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PromoBannerDisplaysResponse(
|
||||
@Json(name = "items") val items: List<PromoBannerDisplayDTO>,
|
||||
)
|
||||
|
||||
@Suppress("BooleanPropertyNaming")
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class PromoBannerDisplayDTO(
|
||||
@Json(name = "id") val id: String,
|
||||
@Json(name = "placeholder") val placeholder: String,
|
||||
@Json(name = "priority") val priority: String,
|
||||
@Json(name = "title") val title: String,
|
||||
@Json(name = "subtitle") val subtitle: String,
|
||||
@Json(name = "iconUrl") val iconUrl: String?,
|
||||
@Json(name = "deeplink") val deeplink: String?,
|
||||
@Json(name = "buttonEnabled") val buttonEnabled: Boolean,
|
||||
@Json(name = "buttonText") val buttonText: String?,
|
||||
@Json(name = "dismissable") val dismissable: Boolean,
|
||||
)
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.core.ui.components.notifications
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.annotation.DrawableRes
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.LocalIndication
|
||||
|
|
@ -86,6 +87,7 @@ fun Notification(
|
|||
subtitleColor = subtitleColor,
|
||||
showArrowIcon = isEnabled && config.shouldShowArrowIcon,
|
||||
hasCloseButton = config.onCloseClick != null,
|
||||
iconUrl = config.iconUrl,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -150,16 +152,29 @@ private fun MainContent(
|
|||
subtitleColor: Color,
|
||||
showArrowIcon: Boolean,
|
||||
hasCloseButton: Boolean,
|
||||
iconUrl: String? = null,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
iconResId = iconResId,
|
||||
tint = iconTint,
|
||||
modifier = Modifier
|
||||
.size(size = iconSize)
|
||||
.align(alignment = Alignment.Top)
|
||||
.testTag(NotificationTestTags.ICON),
|
||||
)
|
||||
if (iconUrl != null) {
|
||||
UrlIcon(
|
||||
iconUrl = iconUrl,
|
||||
fallbackIconResId = iconResId,
|
||||
fallbackIconTint = iconTint,
|
||||
modifier = Modifier
|
||||
.size(size = iconSize)
|
||||
.align(alignment = Alignment.Top)
|
||||
.testTag(NotificationTestTags.ICON),
|
||||
)
|
||||
} else {
|
||||
Icon(
|
||||
iconResId = iconResId,
|
||||
tint = iconTint,
|
||||
modifier = Modifier
|
||||
.size(size = iconSize)
|
||||
.align(alignment = Alignment.Top)
|
||||
.testTag(NotificationTestTags.ICON),
|
||||
)
|
||||
}
|
||||
|
||||
SpacerW(width = TangemTheme.dimens.spacing10)
|
||||
|
||||
|
|
@ -212,6 +227,30 @@ private fun Icon(@DrawableRes iconResId: Int, tint: Color?, modifier: Modifier =
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun UrlIcon(
|
||||
iconUrl: String,
|
||||
@DrawableRes fallbackIconResId: Int,
|
||||
fallbackIconTint: Color?,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
model = iconUrl,
|
||||
contentDescription = null,
|
||||
modifier = modifier.clip(CircleShape),
|
||||
loading = {
|
||||
CircleShimmer(modifier = Modifier.matchParentSize())
|
||||
},
|
||||
error = {
|
||||
Icon(
|
||||
iconResId = fallbackIconResId,
|
||||
tint = fallbackIconTint,
|
||||
modifier = Modifier.matchParentSize(),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun TextsBlock(
|
||||
title: TextReference?,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
*
|
||||
* @property subtitle subtitle
|
||||
* @property iconResId icon resource id
|
||||
* @property iconUrl icon URL for remote images
|
||||
* @property title title
|
||||
* @property backgroundResId background resource id
|
||||
* @property buttonsState buttons state
|
||||
|
|
@ -21,6 +22,7 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
data class NotificationConfig(
|
||||
val subtitle: TextReference,
|
||||
@DrawableRes val iconResId: Int,
|
||||
val iconUrl: String? = null,
|
||||
val title: TextReference? = null,
|
||||
@DrawableRes val backgroundResId: Int? = null,
|
||||
val buttonsState: ButtonsState? = null,
|
||||
|
|
|
|||
15
features/promo-banners/api/build.gradle.kts
Normal file
15
features/promo-banners/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.promobanners.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.domain.models)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.promobanners.api
|
||||
|
||||
interface NewPromoBannersFeatureToggles {
|
||||
val isNewPromoBannersEnabled: Boolean
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.promobanners.api
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
interface PromoBannersBlockComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val placeholder: Placeholder,
|
||||
)
|
||||
|
||||
enum class Placeholder(val value: String) {
|
||||
MAIN("main"),
|
||||
FEED("shtorka"),
|
||||
}
|
||||
|
||||
interface Factory : ComponentFactory<Params, PromoBannersBlockComponent>
|
||||
}
|
||||
50
features/promo-banners/impl/build.gradle.kts
Normal file
50
features/promo-banners/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.promobanners.impl"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Project - API */
|
||||
implementation(projects.features.promoBanners.api)
|
||||
|
||||
/** Domain */
|
||||
implementation(projects.domain.models)
|
||||
|
||||
/** Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.utils)
|
||||
implementation(projects.core.datasource)
|
||||
implementation(projects.core.configToggles)
|
||||
|
||||
/** Common */
|
||||
implementation(projects.common.routing)
|
||||
|
||||
/** Compose */
|
||||
implementation(deps.compose.foundation)
|
||||
implementation(deps.compose.ui)
|
||||
implementation(deps.lifecycle.compose)
|
||||
|
||||
/** Other */
|
||||
implementation(deps.arrow.core)
|
||||
implementation(deps.kotlin.immutable.collections)
|
||||
implementation(deps.timber)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
|
||||
/** Tests */
|
||||
testImplementation(deps.test.junit5)
|
||||
testRuntimeOnly(deps.test.junit5.engine)
|
||||
testImplementation(deps.test.truth)
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.features.promobanners.impl.analytics
|
||||
|
||||
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||
|
||||
internal sealed class PromoBannerAnalyticsEvent(
|
||||
event: String,
|
||||
params: Map<String, String> = emptyMap(),
|
||||
) : AnalyticsEvent(category = "Banner", event = event, params = params) {
|
||||
|
||||
data class Shown(val bannerId: String) : PromoBannerAnalyticsEvent(
|
||||
event = "Banner Shown",
|
||||
params = mapOf("banner_id" to bannerId),
|
||||
)
|
||||
|
||||
data class Clicked(val bannerId: String) : PromoBannerAnalyticsEvent(
|
||||
event = "Banner Clicked",
|
||||
params = mapOf("banner_id" to bannerId),
|
||||
)
|
||||
|
||||
data class Dismissed(val bannerId: String) : PromoBannerAnalyticsEvent(
|
||||
event = "Banner Dismissed",
|
||||
params = mapOf("banner_id" to bannerId),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.tangem.features.promobanners.impl.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.promobanners.PromoBannerDisplayDTO
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerDisplay
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerPriority
|
||||
import com.tangem.utils.converter.Converter
|
||||
import java.util.Locale
|
||||
|
||||
internal class PromoBannerDisplayDTOConverter : Converter<PromoBannerDisplayDTO, PromoBannerDisplay> {
|
||||
|
||||
override fun convert(value: PromoBannerDisplayDTO): PromoBannerDisplay {
|
||||
return PromoBannerDisplay(
|
||||
id = value.id,
|
||||
placeholder = value.placeholder,
|
||||
priority = convertPriority(value.priority),
|
||||
title = value.title,
|
||||
subtitle = value.subtitle,
|
||||
iconUrl = value.iconUrl,
|
||||
deeplink = value.deeplink,
|
||||
isButtonEnabled = value.buttonEnabled,
|
||||
buttonText = value.buttonText,
|
||||
isDismissable = value.dismissable,
|
||||
)
|
||||
}
|
||||
|
||||
private fun convertPriority(value: String): PromoBannerPriority {
|
||||
return when (value.uppercase(Locale.ROOT)) {
|
||||
"IMPORTANT" -> PromoBannerPriority.IMPORTANT
|
||||
"HIGH" -> PromoBannerPriority.HIGH
|
||||
"MEDIUM" -> PromoBannerPriority.MEDIUM
|
||||
"LOW" -> PromoBannerPriority.LOW
|
||||
else -> PromoBannerPriority.LOW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
package com.tangem.features.promobanners.impl.converters
|
||||
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerDisplay
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerNotificationUM
|
||||
|
||||
internal class PromoBannerDisplayToNotificationConverter {
|
||||
|
||||
fun convert(
|
||||
banner: PromoBannerDisplay,
|
||||
onDeeplinkClick: (String?) -> Unit,
|
||||
onDismiss: (String) -> Unit,
|
||||
): PromoBannerNotificationUM {
|
||||
return PromoBannerNotificationUM(
|
||||
displayId = banner.id,
|
||||
config = NotificationConfig(
|
||||
title = TextReference.Str(banner.title),
|
||||
subtitle = TextReference.Str(banner.subtitle),
|
||||
iconResId = com.tangem.core.ui.R.drawable.ic_alert_circle_24,
|
||||
iconUrl = banner.iconUrl,
|
||||
onCloseClick = if (banner.isDismissable) {
|
||||
{ onDismiss(banner.id) }
|
||||
} else {
|
||||
null
|
||||
},
|
||||
buttonsState = banner.buttonText?.takeIf { banner.isButtonEnabled }?.let { text ->
|
||||
NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = TextReference.Str(text),
|
||||
onClick = { onDeeplinkClick(banner.deeplink) },
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.features.promobanners.impl.model
|
||||
|
||||
internal data class PromoBannerDisplay(
|
||||
val id: String,
|
||||
val placeholder: String,
|
||||
val priority: PromoBannerPriority,
|
||||
val title: String,
|
||||
val subtitle: String,
|
||||
val iconUrl: String?,
|
||||
val deeplink: String?,
|
||||
val isButtonEnabled: Boolean,
|
||||
val buttonText: String?,
|
||||
val isDismissable: Boolean,
|
||||
)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.promobanners.impl.model
|
||||
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
|
||||
internal data class PromoBannerNotificationUM(
|
||||
val displayId: String,
|
||||
val config: NotificationConfig,
|
||||
)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.promobanners.impl.model
|
||||
|
||||
internal enum class PromoBannerPriority(val order: Int) {
|
||||
IMPORTANT(order = 0),
|
||||
HIGH(order = 1),
|
||||
MEDIUM(order = 2),
|
||||
LOW(order = 3),
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.promobanners.impl.model
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
internal data class PromoBannersBlockUM(
|
||||
val banners: ImmutableList<PromoBannerNotificationUM> = persistentListOf(),
|
||||
)
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package com.tangem.features.promobanners.impl.repository
|
||||
|
||||
import com.tangem.datasource.api.common.response.getOrThrow
|
||||
import com.tangem.datasource.api.tangemTech.TangemTechApi
|
||||
import com.tangem.datasource.api.tangemTech.models.promobanners.DismissPromoBannerRequest
|
||||
import com.tangem.features.promobanners.impl.converters.PromoBannerDisplayDTOConverter
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerDisplay
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
internal class DefaultPromoBannersRepository @Inject constructor(
|
||||
private val tangemTechApi: TangemTechApi,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) : PromoBannersRepository {
|
||||
|
||||
private val converter = PromoBannerDisplayDTOConverter()
|
||||
|
||||
override suspend fun getBanners(walletId: String, placeholder: String, locale: String): List<PromoBannerDisplay> =
|
||||
withContext(dispatchers.io) {
|
||||
tangemTechApi.getPromoBannerDisplays(
|
||||
walletId = walletId,
|
||||
placeholder = placeholder,
|
||||
locale = locale,
|
||||
).getOrThrow()
|
||||
.items
|
||||
.map(converter::convert)
|
||||
.sortedBy { it.priority.order }
|
||||
}
|
||||
|
||||
override suspend fun dismissBanner(walletId: String, displayId: String) {
|
||||
withContext(dispatchers.io) {
|
||||
val request = DismissPromoBannerRequest(
|
||||
walletId = walletId,
|
||||
isDismissed = true,
|
||||
)
|
||||
tangemTechApi.dismissPromoBannerDisplay(displayId, request).getOrThrow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.promobanners.impl.repository
|
||||
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerDisplay
|
||||
|
||||
internal interface PromoBannersRepository {
|
||||
|
||||
suspend fun getBanners(walletId: String, placeholder: String, locale: String): List<PromoBannerDisplay>
|
||||
|
||||
suspend fun dismissBanner(walletId: String, displayId: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.tangem.features.promobanners.impl.toggles
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.promobanners.api.NewPromoBannersFeatureToggles
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class DefaultNewPromoBannersFeatureToggles @Inject constructor(
|
||||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : NewPromoBannersFeatureToggles {
|
||||
|
||||
override val isNewPromoBannersEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("NEW_PROMO_BANNERS_ENABLED")
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.tangem.features.promobanners.impl.converters
|
||||
|
||||
import com.tangem.datasource.api.tangemTech.models.promobanners.PromoBannerDisplayDTO
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerPriority
|
||||
import org.junit.jupiter.api.Test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
||||
class PromoBannerDisplayDTOConverterTest {
|
||||
|
||||
private val converter = PromoBannerDisplayDTOConverter()
|
||||
|
||||
@Test
|
||||
fun `should convert DTO to domain model`() {
|
||||
val dto = PromoBannerDisplayDTO(
|
||||
id = "123",
|
||||
placeholder = "MAIN",
|
||||
priority = "HIGH",
|
||||
title = "Test Banner",
|
||||
subtitle = "Test subtitle",
|
||||
iconUrl = "https://example.com/icon.png",
|
||||
deeplink = "tangem://wallet",
|
||||
buttonEnabled = true,
|
||||
buttonText = "Click me",
|
||||
dismissable = true,
|
||||
)
|
||||
|
||||
val result = converter.convert(dto)
|
||||
|
||||
assertThat(result.id).isEqualTo("123")
|
||||
assertThat(result.placeholder).isEqualTo("MAIN")
|
||||
assertThat(result.priority).isEqualTo(PromoBannerPriority.HIGH)
|
||||
assertThat(result.title).isEqualTo("Test Banner")
|
||||
assertThat(result.subtitle).isEqualTo("Test subtitle")
|
||||
assertThat(result.iconUrl).isEqualTo("https://example.com/icon.png")
|
||||
assertThat(result.deeplink).isEqualTo("tangem://wallet")
|
||||
assertThat(result.isButtonEnabled).isTrue()
|
||||
assertThat(result.buttonText).isEqualTo("Click me")
|
||||
assertThat(result.isDismissable).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should pass placeholder as is`() {
|
||||
val dto = createDTO(placeholder = "UNKNOWN")
|
||||
val result = converter.convert(dto)
|
||||
assertThat(result.placeholder).isEqualTo("UNKNOWN")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should map unknown priority to LOW`() {
|
||||
val dto = createDTO(priority = "UNKNOWN")
|
||||
val result = converter.convert(dto)
|
||||
assertThat(result.priority).isEqualTo(PromoBannerPriority.LOW)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should handle SHTORKA placeholder`() {
|
||||
val dto = createDTO(placeholder = "SHTORKA")
|
||||
val result = converter.convert(dto)
|
||||
assertThat(result.placeholder).isEqualTo("SHTORKA")
|
||||
}
|
||||
|
||||
private fun createDTO(
|
||||
placeholder: String = "MAIN",
|
||||
priority: String = "MEDIUM",
|
||||
) = PromoBannerDisplayDTO(
|
||||
id = "1",
|
||||
placeholder = placeholder,
|
||||
priority = priority,
|
||||
title = "Title",
|
||||
subtitle = "Subtitle",
|
||||
iconUrl = null,
|
||||
deeplink = null,
|
||||
buttonEnabled = false,
|
||||
buttonText = null,
|
||||
dismissable = false,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
package com.tangem.features.promobanners.impl.converters
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerDisplay
|
||||
import com.tangem.features.promobanners.impl.model.PromoBannerPriority
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Test
|
||||
|
||||
class PromoBannerDisplayToNotificationConverterTest {
|
||||
|
||||
private val converter = PromoBannerDisplayToNotificationConverter()
|
||||
|
||||
@Test
|
||||
fun `should convert banner with all fields`() {
|
||||
val banner = createBanner(
|
||||
id = "b1",
|
||||
deeplink = "tangem://wallet",
|
||||
isButtonEnabled = true,
|
||||
buttonText = "Open",
|
||||
isDismissable = true,
|
||||
)
|
||||
var clickedDeeplink: String? = "not_called"
|
||||
var dismissedId: String? = null
|
||||
|
||||
val result = converter.convert(
|
||||
banner = banner,
|
||||
onDeeplinkClick = { clickedDeeplink = it },
|
||||
onDismiss = { dismissedId = it },
|
||||
)
|
||||
|
||||
assertThat(result.displayId).isEqualTo("b1")
|
||||
assertThat(result.config.title).isEqualTo(TextReference.Str("Title"))
|
||||
assertThat(result.config.subtitle).isEqualTo(TextReference.Str("Subtitle"))
|
||||
assertThat(result.config.iconUrl).isEqualTo("https://icon.png")
|
||||
assertThat(result.config.onClick).isNull()
|
||||
assertThat(result.config.onCloseClick).isNotNull()
|
||||
assertThat(result.config.buttonsState).isNotNull()
|
||||
|
||||
// Button click navigates via deeplink
|
||||
val buttonState = result.config.buttonsState as com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState.SecondaryButtonConfig
|
||||
buttonState.onClick.invoke()
|
||||
assertThat(clickedDeeplink).isEqualTo("tangem://wallet")
|
||||
|
||||
result.config.onCloseClick!!.invoke()
|
||||
assertThat(dismissedId).isEqualTo("b1")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should always have null onClick - banner itself is not clickable`() {
|
||||
val banner = createBanner(deeplink = "tangem://wallet")
|
||||
|
||||
val result = converter.convert(
|
||||
banner = banner,
|
||||
onDeeplinkClick = {},
|
||||
onDismiss = {},
|
||||
)
|
||||
|
||||
assertThat(result.config.onClick).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not set onCloseClick when not dismissable`() {
|
||||
val banner = createBanner(isDismissable = false)
|
||||
|
||||
val result = converter.convert(
|
||||
banner = banner,
|
||||
onDeeplinkClick = {},
|
||||
onDismiss = {},
|
||||
)
|
||||
|
||||
assertThat(result.config.onCloseClick).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not set button when button is disabled`() {
|
||||
val banner = createBanner(isButtonEnabled = false, buttonText = "Open")
|
||||
|
||||
val result = converter.convert(
|
||||
banner = banner,
|
||||
onDeeplinkClick = {},
|
||||
onDismiss = {},
|
||||
)
|
||||
|
||||
assertThat(result.config.buttonsState).isNull()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should not set button when buttonText is null`() {
|
||||
val banner = createBanner(isButtonEnabled = true, buttonText = null)
|
||||
|
||||
val result = converter.convert(
|
||||
banner = banner,
|
||||
onDeeplinkClick = {},
|
||||
onDismiss = {},
|
||||
)
|
||||
|
||||
assertThat(result.config.buttonsState).isNull()
|
||||
}
|
||||
|
||||
private fun createBanner(
|
||||
id: String = "id",
|
||||
deeplink: String? = "https://example.com",
|
||||
isButtonEnabled: Boolean = false,
|
||||
buttonText: String? = null,
|
||||
isDismissable: Boolean = true,
|
||||
) = PromoBannerDisplay(
|
||||
id = id,
|
||||
placeholder = "main",
|
||||
priority = PromoBannerPriority.MEDIUM,
|
||||
title = "Title",
|
||||
subtitle = "Subtitle",
|
||||
iconUrl = "https://icon.png",
|
||||
deeplink = deeplink,
|
||||
isButtonEnabled = isButtonEnabled,
|
||||
buttonText = buttonText,
|
||||
isDismissable = isDismissable,
|
||||
)
|
||||
}
|
||||
|
|
@ -305,6 +305,9 @@ include(":features:approval:impl")
|
|||
|
||||
include(":features:feed:api")
|
||||
include(":features:feed:impl")
|
||||
|
||||
include(":features:promo-banners:api")
|
||||
include(":features:promo-banners:impl")
|
||||
// endregion Feature modules
|
||||
|
||||
// region Domain modules
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue