diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt index e79062c4c7..c5609bbad4 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/modal/TangemModalBottomSheetWithFooter.kt @@ -21,6 +21,7 @@ import androidx.compose.ui.graphics.vector.rememberVectorPainter import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.tangem.core.ui.R import com.tangem.core.ui.components.* @@ -38,6 +39,9 @@ import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.utils.WindowInsetsZero import com.tangem.core.ui.utils.toPx +/** Default reserved height for the [TangemModalBottomSheetWithFooter] footer slot. */ +val DEFAULT_FOOTER_HEIGHT: Dp = 80.dp + /** * Modal bottom sheet with [content], [footer] and optional [title]. * @@ -50,6 +54,12 @@ inline fun TangemModalBottomSheetWi config: TangemBottomSheetConfig, containerColor: Color = TangemTheme.colors.background.primary, skipPartiallyExpanded: Boolean = true, + // FIXME([REDACTED_TASK_KEY]): temp workaround + // The footer slot reserves a fixed [DEFAULT_FOOTER_HEIGHT]; + // callers whose footer differs must pass the real height explicitly. Exposed as an opt-in + // parameter so existing usages keep the previous behavior and nothing else is affected. + // Rework so the sheet measures the actual footer height internally and drops this parameter. + footerHeight: Dp = DEFAULT_FOOTER_HEIGHT, noinline onBack: (() -> Unit)? = null, crossinline title: @Composable BoxScope.(T) -> Unit = {}, crossinline content: @Composable (T) -> Unit, @@ -65,6 +75,7 @@ inline fun TangemModalBottomSheetWi content = content, footer = footer, skipPartiallyExpanded = skipPartiallyExpanded, + footerHeight = footerHeight, ) } else { DefaultModalBottomSheetWithFooter( @@ -75,6 +86,7 @@ inline fun TangemModalBottomSheetWi footer = footer, onBack = onBack, skipPartiallyExpanded = skipPartiallyExpanded, + footerHeight = footerHeight, ) } } @@ -85,6 +97,7 @@ inline fun DefaultModalBottomSheetW config: TangemBottomSheetConfig, containerColor: Color, skipPartiallyExpanded: Boolean = true, + footerHeight: Dp = DEFAULT_FOOTER_HEIGHT, noinline onBack: (() -> Unit)? = null, crossinline title: @Composable BoxScope.(T) -> Unit, crossinline content: @Composable (T) -> Unit, @@ -117,6 +130,7 @@ inline fun DefaultModalBottomSheetW onBack = onBack, content = content, footer = footer, + footerHeight = footerHeight, ) } @@ -135,6 +149,7 @@ inline fun PreviewModalBottomSheetW config: TangemBottomSheetConfig, containerColor: Color, skipPartiallyExpanded: Boolean = true, + footerHeight: Dp = DEFAULT_FOOTER_HEIGHT, crossinline title: @Composable BoxScope.(T) -> Unit, crossinline content: @Composable (T) -> Unit, noinline footer: @Composable (BoxScope.(T) -> Unit)?, @@ -150,6 +165,7 @@ inline fun PreviewModalBottomSheetW title = title, content = content, footer = footer, + footerHeight = footerHeight, ) } @@ -161,6 +177,7 @@ inline fun BasicModalBottomSheetWit sheetState: TangemSheetState, containerColor: Color, modifier: Modifier = Modifier, + footerHeight: Dp = DEFAULT_FOOTER_HEIGHT, noinline onBack: (() -> Unit)? = null, crossinline title: @Composable BoxScope.(T) -> Unit, crossinline content: @Composable (T) -> Unit, @@ -178,11 +195,8 @@ inline fun BasicModalBottomSheetWit val isKeyboardOpen by rememberIsKeyboardVisible() val buttonHeight by animateDpAsState( - if (footer != null) { - 80.dp - } else { - 0.dp - }, + targetValue = if (footer != null) footerHeight else 0.dp, + label = "FooterHeight", ) // Offset calculation for keyboard scroll adjustment: // 1) Button height (footer) diff --git a/features/promo-banners/impl/build.gradle.kts b/features/promo-banners/impl/build.gradle.kts index 5b7f1e1e76..8d084ddf38 100644 --- a/features/promo-banners/impl/build.gradle.kts +++ b/features/promo-banners/impl/build.gradle.kts @@ -23,6 +23,7 @@ dependencies { implementation(projects.domain.models) implementation(projects.domain.appCurrency) implementation(projects.domain.account.status) + implementation(projects.domain.promo) /** Core */ api(projects.core.configToggles) diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/ActivateCampaignBottomSheetComponent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/ActivateCampaignBottomSheetComponent.kt index 6137d3f58a..c9068a7b07 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/ActivateCampaignBottomSheetComponent.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/ActivateCampaignBottomSheetComponent.kt @@ -1,12 +1,11 @@ package com.tangem.features.promobanners.impl.campaigns.component -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -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.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.child @@ -14,13 +13,10 @@ import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState +import com.tangem.core.ui.decompose.ComposableModularContentComponent import com.tangem.core.ui.ds2.button.Close import com.tangem.core.ui.ds2.button.TangemButton import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.models.account.Account -import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenComponent import com.tangem.features.promobanners.impl.campaigns.entity.CampaignType import com.tangem.features.promobanners.impl.campaigns.model.ActivateCampaignsModel @@ -32,7 +28,8 @@ internal class ActivateCampaignBottomSheetComponent( chooseTokenComponentFactory: ChooseTokenComponent.Factory, private val params: Params, val onDismiss: () -> Unit, -) : CampaignsModularComponent, AppComponentContext by appComponentContext { + val onFooterExtraHeightReady: (Dp) -> Unit, +) : ComposableModularContentComponent, AppComponentContext by appComponentContext { private val model: ActivateCampaignsModel = getOrCreateModel(params) @@ -42,7 +39,7 @@ internal class ActivateCampaignBottomSheetComponent( ) @Composable - override fun Title(bottomSheetState: State) { + override fun Title() { TangemTopNavigation( windowInsets = WindowInsets(0), blurBackground = false, @@ -51,10 +48,10 @@ internal class ActivateCampaignBottomSheetComponent( } @Composable - override fun Content(bottomSheetState: State, contentPadding: PaddingValues, modifier: Modifier) { + override fun Content(modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() - ActivateCampaignContent(um = state) + ActivateCampaignContent(um = state, modifier = modifier) if (state.isChoosingToken) { ChooseTokenBottomSheet(state.onChooseTokenDismiss) @@ -67,6 +64,7 @@ internal class ActivateCampaignBottomSheetComponent( ActivateCampaignFooter( footerUM = state.footerUM, + onFooterTextHeightReady = onFooterExtraHeightReady, ) } @@ -90,6 +88,6 @@ internal class ActivateCampaignBottomSheetComponent( interface ActivateCampaignModelCallbacks { val onActivated: (CampaignType) -> Unit - val onAlreadyActivated: (CampaignType, AppCurrency, Account?, CryptoCurrencyStatus) -> Unit + val onAlreadyActivated: (CampaignType) -> Unit } } \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignAlreadyActivatedBottomSheetComponent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignAlreadyActivatedBottomSheetComponent.kt index bbc6504059..398bc5d891 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignAlreadyActivatedBottomSheetComponent.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignAlreadyActivatedBottomSheetComponent.kt @@ -1,39 +1,33 @@ package com.tangem.features.promobanners.impl.campaigns.component -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.runtime.State -import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.tangem.core.decompose.context.AppComponentContext -import com.tangem.core.decompose.model.getOrCreateModel import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState +import com.tangem.core.ui.decompose.ComposableModularContentComponent import com.tangem.core.ui.ds2.button.Close import com.tangem.core.ui.ds2.button.TangemButton import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.models.account.Account -import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.core.ui.extensions.wrappedList import com.tangem.features.promobanners.impl.R import com.tangem.features.promobanners.impl.campaigns.entity.CampaignType -import com.tangem.features.promobanners.impl.campaigns.model.CampaignAlreadyActivatedModel -import com.tangem.features.promobanners.impl.campaigns.ui.ActivateCampaignContent +import com.tangem.features.promobanners.impl.campaigns.entity.CampaignTypeToContentConverter +import com.tangem.features.promobanners.impl.campaigns.ui.AlreadyActivatedCampaignContent internal class CampaignAlreadyActivatedBottomSheetComponent( appComponentContext: AppComponentContext, params: Params, val onDismiss: () -> Unit, -) : CampaignsModularComponent, AppComponentContext by appComponentContext { +) : ComposableModularContentComponent, AppComponentContext by appComponentContext { - private val model: CampaignAlreadyActivatedModel = getOrCreateModel(params) + private val campaignName = CampaignTypeToContentConverter().convert(params.campaignType).name @Composable - override fun Title(bottomSheetState: State) { + override fun Title() { TangemTopNavigation( windowInsets = WindowInsets(0), blurBackground = false, @@ -42,10 +36,14 @@ internal class CampaignAlreadyActivatedBottomSheetComponent( } @Composable - override fun Content(bottomSheetState: State, contentPadding: PaddingValues, modifier: Modifier) { - val state by model.uiState.collectAsStateWithLifecycle() - - ActivateCampaignContent(um = state) + override fun Content(modifier: Modifier) { + AlreadyActivatedCampaignContent( + message = resourceReference( + R.string.promo_campaign_already_activated_title, + wrappedList(campaignName), + ), + modifier = modifier, + ) } @Composable @@ -59,8 +57,5 @@ internal class CampaignAlreadyActivatedBottomSheetComponent( data class Params( val campaignType: CampaignType, - val account: Account?, - val appCurrency: AppCurrency, - val currency: CryptoCurrencyStatus, ) } \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignEnrolledBottomSheetComponent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignEnrolledBottomSheetComponent.kt index 8853bca703..03c1c5b5e3 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignEnrolledBottomSheetComponent.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignEnrolledBottomSheetComponent.kt @@ -1,30 +1,31 @@ package com.tangem.features.promobanners.impl.campaigns.component -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.runtime.State import androidx.compose.ui.Modifier import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState +import com.tangem.core.ui.decompose.ComposableModularContentComponent import com.tangem.core.ui.ds2.button.Close import com.tangem.core.ui.ds2.button.TangemButton import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation -import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe +import com.tangem.core.ui.extensions.wrappedList import com.tangem.features.promobanners.impl.R import com.tangem.features.promobanners.impl.campaigns.entity.CampaignType -import com.tangem.features.promobanners.impl.campaigns.entity.campaignName +import com.tangem.features.promobanners.impl.campaigns.entity.CampaignTypeToContentConverter import com.tangem.features.promobanners.impl.campaigns.ui.CampaignEnrolledMessageContent internal class CampaignEnrolledBottomSheetComponent( - private val campaignType: CampaignType, + params: Params, private val onDismissRequest: () -> Unit, -) : CampaignsModularComponent { +) : ComposableModularContentComponent { + + private val campaignName = CampaignTypeToContentConverter().convert(params.campaignType).name @Composable - override fun Title(bottomSheetState: State) { + override fun Title() { TangemTopNavigation( windowInsets = WindowInsets(0), blurBackground = false, @@ -33,9 +34,13 @@ internal class CampaignEnrolledBottomSheetComponent( } @Composable - override fun Content(bottomSheetState: State, contentPadding: PaddingValues, modifier: Modifier) { + override fun Content(modifier: Modifier) { CampaignEnrolledMessageContent( - message = stringReference("You're successfully enrolled in ${campaignType.campaignName()}"), + message = resourceReference( + R.string.promo_campaign_enroll_success_title, + wrappedList(campaignName), + ), + modifier = modifier, ) } @@ -47,4 +52,8 @@ internal class CampaignEnrolledBottomSheetComponent( onClick = { onDismissRequest() }, ) } + + data class Params( + val campaignType: CampaignType, + ) } \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignsModularComponent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignsModularComponent.kt deleted file mode 100644 index bb2368cd7e..0000000000 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignsModularComponent.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.features.promobanners.impl.campaigns.component - -import androidx.compose.runtime.Composable -import com.tangem.core.ui.decompose.ComposableModularBottomSheetContentComponent - -interface CampaignsModularComponent : ComposableModularBottomSheetContentComponent { - - @Composable - fun Footer() -} \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/DefaultCampaignsComponent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/DefaultCampaignsComponent.kt index 74c1e17c55..15ab2f6054 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/DefaultCampaignsComponent.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/DefaultCampaignsComponent.kt @@ -2,33 +2,26 @@ package com.tangem.features.promobanners.impl.campaigns.component import androidx.compose.animation.animateContentSize import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable -import androidx.compose.runtime.DisposableEffect -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.arkivanov.decompose.ComponentContext import com.arkivanov.decompose.extensions.compose.subscribeAsState import com.arkivanov.decompose.router.slot.childSlot import com.tangem.core.decompose.context.AppComponentContext import com.tangem.core.decompose.context.childByContext import com.tangem.core.decompose.model.getOrCreateModel -import com.tangem.core.ui.components.bottomsheets.LocalBottomSheetContentScrollable -import com.tangem.core.ui.components.bottomsheets.LocalTangemBottomSheetContentBottomInset import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet -import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState +import com.tangem.core.ui.components.bottomsheets.modal.DEFAULT_FOOTER_HEIGHT +import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetWithFooter +import com.tangem.core.ui.decompose.ComposableModularContentComponent import com.tangem.core.ui.extensions.rememberLastNonNull -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.models.account.Account -import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.core.ui.res.TangemTheme import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenComponent import com.tangem.features.promobanners.api.swapcashback.CampaignsComponent import com.tangem.features.promobanners.impl.campaigns.component.ActivateCampaignBottomSheetComponent.ActivateCampaignModelCallbacks @@ -59,41 +52,23 @@ internal class DefaultCampaignsComponent @AssistedInject constructor( val bottomSheet by bottomSheetSlot.subscribeAsState() val activeChild = bottomSheet.child?.instance val displayedChild = rememberLastNonNull(activeChild) - val bottomSheetState = remember { mutableStateOf(BottomSheetState.EXPANDED) } + val footerExtraHeight by model.footerExtraHeightState.collectAsStateWithLifecycle() - TangemBottomSheet( + TangemModalBottomSheetWithFooter( config = TangemBottomSheetConfig( isShown = activeChild != null, onDismissRequest = model::onDismiss, content = TangemBottomSheetConfigContent.Empty, ), + containerColor = TangemTheme.colors3.bg.primary, + footerHeight = DEFAULT_FOOTER_HEIGHT + footerExtraHeight, onBack = model::onDismiss, title = { - displayedChild?.Title(bottomSheetState) + displayedChild?.Title() }, content = { - val bottomInset = LocalTangemBottomSheetContentBottomInset.current - val scrollableSignal = LocalBottomSheetContentScrollable.current - - if (scrollableSignal != null) { - LaunchedEffect(Unit) { - scrollableSignal.value = false - } - DisposableEffect(scrollableSignal) { - onDispose { scrollableSignal.value = true } - } - } - - Box( - modifier = Modifier - .padding(bottom = bottomInset) - .animateContentSize(), - ) { - displayedChild?.Content( - bottomSheetState = bottomSheetState, - contentPadding = PaddingValues(), - modifier = Modifier, - ) + Box(modifier = Modifier.animateContentSize()) { + displayedChild?.Content(modifier = Modifier) } }, footer = { @@ -111,42 +86,37 @@ internal class DefaultCampaignsComponent @AssistedInject constructor( private fun bottomSheetChild( config: CampaignsBottomSheetConfig, componentContext: ComponentContext, - ): CampaignsModularComponent { + ): ComposableModularContentComponent { val context = childByContext(componentContext) return when (config) { CampaignsBottomSheetConfig.NotActive -> NotActiveCampaignBottomSheetComponent( onDismissRequest = model::onDismiss, ) is CampaignsBottomSheetConfig.Enrolled -> CampaignEnrolledBottomSheetComponent( - campaignType = config.campaignType, + params = CampaignEnrolledBottomSheetComponent.Params( + campaignType = config.campaignType, + ), onDismissRequest = model::onDismiss, ) is CampaignsBottomSheetConfig.Activate -> ActivateCampaignBottomSheetComponent( appComponentContext = context, chooseTokenComponentFactory = chooseTokenComponentFactory, onDismiss = model::onDismiss, + onFooterExtraHeightReady = model::onFooterExtraHeightReady, params = ActivateCampaignBottomSheetComponent.Params( campaignType = config.campaignType, modelCallbacks = object : ActivateCampaignModelCallbacks { override val onActivated: (CampaignType) -> Unit = model::onActivated - override val onAlreadyActivated: ( - CampaignType, - AppCurrency, - Account?, - CryptoCurrencyStatus, - ) -> Unit = model::onAlreadyActivated + override val onAlreadyActivated: (CampaignType) -> Unit = model::onAlreadyActivated }, ), ) is CampaignsBottomSheetConfig.AlreadyActivated -> CampaignAlreadyActivatedBottomSheetComponent( appComponentContext = context, - onDismiss = model::onDismiss, params = CampaignAlreadyActivatedBottomSheetComponent.Params( campaignType = config.campaignType, - appCurrency = config.appCurrency, - account = config.account, - currency = config.currency, ), + onDismiss = model::onDismiss, ) } } diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/NotActiveCampaignBottomSheetComponent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/NotActiveCampaignBottomSheetComponent.kt index 05dd624b48..8e90b9da88 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/NotActiveCampaignBottomSheetComponent.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/NotActiveCampaignBottomSheetComponent.kt @@ -1,13 +1,11 @@ package com.tangem.features.promobanners.impl.campaigns.component -import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable -import androidx.compose.runtime.State import androidx.compose.ui.Modifier import com.tangem.core.ui.components.PrimaryButton -import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState +import com.tangem.core.ui.decompose.ComposableModularContentComponent import com.tangem.core.ui.ds2.button.Close import com.tangem.core.ui.ds2.button.TangemButton import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation @@ -17,10 +15,10 @@ import com.tangem.features.promobanners.impl.campaigns.ui.NotActiveCampaignMessa internal class NotActiveCampaignBottomSheetComponent( private val onDismissRequest: () -> Unit, -) : CampaignsModularComponent { +) : ComposableModularContentComponent { @Composable - override fun Title(bottomSheetState: State) { + override fun Title() { TangemTopNavigation( windowInsets = WindowInsets(0), blurBackground = false, @@ -29,8 +27,8 @@ internal class NotActiveCampaignBottomSheetComponent( } @Composable - override fun Content(bottomSheetState: State, contentPadding: PaddingValues, modifier: Modifier) { - NotActiveCampaignMessageContent() + override fun Content(modifier: Modifier) { + NotActiveCampaignMessageContent(modifier = modifier) } @Composable diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/deeplink/DefaultCampaignsDeepLinkHandler.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/deeplink/DefaultCampaignsDeepLinkHandler.kt index 4d9c7cdac9..1a6fb4a0bc 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/deeplink/DefaultCampaignsDeepLinkHandler.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/deeplink/DefaultCampaignsDeepLinkHandler.kt @@ -1,6 +1,7 @@ package com.tangem.features.promobanners.impl.campaigns.deeplink import com.tangem.common.routing.deeplink.DeeplinkConst +import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase import com.tangem.features.promobanners.api.deeplink.CampaignsDeepLinkHandler import com.tangem.features.promobanners.api.toggles.PromoBannersFeatureToggles import com.tangem.features.promobanners.impl.campaigns.service.CampaignsService @@ -18,13 +19,24 @@ import dagger.assisted.AssistedInject internal class DefaultCampaignsDeepLinkHandler @AssistedInject constructor( @Assisted private val queryParams: Map, campaignsService: CampaignsService, - private val promoBannersFeatureToggles: PromoBannersFeatureToggles, + getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase, + promoBannersFeatureToggles: PromoBannersFeatureToggles, ) : CampaignsDeepLinkHandler { init { if (promoBannersFeatureToggles.isCampaignsToggleEnabled) { - val campaignId = queryParams[DeeplinkConst.CAMPAIGN_ID_KEY].orEmpty() - campaignsService.show(campaignId) + // It is okay here, we are navigating from outside, and there is no other way to getting UserWallet + getSelectedWalletSyncUseCase().fold( + ifLeft = { + TangemLogger.e("Error on getting user wallet") + }, + ifRight = { userWallet -> + val campaignId = queryParams[DeeplinkConst.CAMPAIGN_ID_KEY].orEmpty() + val userWalletId = userWallet.walletId + + campaignsService.show(campaignId = campaignId, userWalletId = userWalletId) + }, + ) } else { TangemLogger.i("Campaigns feature is disabled") } diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/di/CampaignsModule.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/di/CampaignsModule.kt index 981656f1da..a3ad2a34b4 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/di/CampaignsModule.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/di/CampaignsModule.kt @@ -7,7 +7,6 @@ import com.tangem.features.promobanners.api.swapcashback.CampaignsComponent import com.tangem.features.promobanners.impl.campaigns.component.DefaultCampaignsComponent import com.tangem.features.promobanners.impl.campaigns.deeplink.DefaultCampaignsDeepLinkHandler import com.tangem.features.promobanners.impl.campaigns.model.ActivateCampaignsModel -import com.tangem.features.promobanners.impl.campaigns.model.CampaignAlreadyActivatedModel import com.tangem.features.promobanners.impl.campaigns.model.CampaignsModel import com.tangem.features.promobanners.impl.campaigns.service.CampaignsService import com.tangem.features.promobanners.impl.campaigns.service.DefaultCampaignsService @@ -51,9 +50,4 @@ internal interface CampaignsModelModule { @IntoMap @ClassKey(ActivateCampaignsModel::class) fun bindCampaignActivateModel(model: ActivateCampaignsModel): Model - - @Binds - @IntoMap - @ClassKey(CampaignAlreadyActivatedModel::class) - fun bindCampaignAlreadyActivatedModel(model: CampaignAlreadyActivatedModel): Model } \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/ActivateCampaignUM.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/ActivateCampaignUM.kt index 4481ece80f..458103a638 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/ActivateCampaignUM.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/ActivateCampaignUM.kt @@ -3,6 +3,7 @@ package com.tangem.features.promobanners.impl.campaigns.entity import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.ds.image.TangemIconUM import com.tangem.core.ui.extensions.TextReference /** @@ -19,6 +20,7 @@ import com.tangem.core.ui.extensions.TextReference @Immutable internal data class ActivateCampaignUM( + val logo: TangemIconUM, val title: TextReference, val description: TextReference, val selectedToken: TokenItemState?, @@ -27,6 +29,7 @@ internal data class ActivateCampaignUM( val footerUM: FooterUM, val onChooseTokenDismiss: () -> Unit, val onLearnMoreClick: () -> Unit, + val onChooseTokenClick: () -> Unit, ) @Immutable diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignAlreadyActivatedUM.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignAlreadyActivatedUM.kt deleted file mode 100644 index eff722c71a..0000000000 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignAlreadyActivatedUM.kt +++ /dev/null @@ -1,10 +0,0 @@ -package com.tangem.features.promobanners.impl.campaigns.entity - -import androidx.compose.runtime.Immutable -import com.tangem.core.ui.components.token.state.TokenItemState - -@Immutable -internal data class CampaignAlreadyActivatedUM( - val selectedToken: TokenItemState, - val selectedAccount: SelectedAccountUM?, -) \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignTypeExt.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignTypeExt.kt index 3c202361c9..607977d1a3 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignTypeExt.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignTypeExt.kt @@ -1,7 +1,39 @@ package com.tangem.features.promobanners.impl.campaigns.entity -internal fun CampaignType.campaignName(): String = when (this) { - // TODO([REDACTED_TASK_KEY]): source real campaign display names. - is CampaignType.ReactivationCashback -> "Reactivation Cashback" - is CampaignType.WhaleSwapCashback -> "Whale Swap Cashback" +import com.tangem.core.ui.ds.image.TangemIconUM +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.domain.promo.models.PromoCampaignId +import com.tangem.features.promobanners.impl.R +import com.tangem.features.promobanners.impl.campaigns.model.CampaignContent +import com.tangem.utils.converter.Converter + +internal class CampaignTypeToContentConverter : Converter { + + override fun convert(value: CampaignType): CampaignContent = when (value) { + is CampaignType.ReactivationCashback -> CampaignContent( + name = "Summer Swap Cashback", + logo = TangemIconUM.Url( + url = "https://s3.dualstack.eu-central-1.amazonaws.com/tangem.api/stories/Reactivation_Cashback.webp", + fallbackRes = R.drawable.ic_alert_24, + ), + description = resourceReference(R.string.promo_campaign_reactivation_summary_description), + termsUrl = "https://tangem.com/docs/en/summer-swap-cashback-terms.pdf", + learnMoreUrl = "https://tangem.com/en/blog/post/summer-swap", + ) + is CampaignType.WhaleSwapCashback -> CampaignContent( + name = "Whale Swap Cashback", + logo = TangemIconUM.Url( + url = "https://s3.dualstack.eu-central-1.amazonaws.com/tangem.api/stories/Whale_Swap_Cashback.webp", + fallbackRes = R.drawable.ic_alert_24, + ), + description = resourceReference(R.string.promo_campaign_whale_swap_summary_description), + termsUrl = "https://tangem.com/docs/en/whale-swap-cashback-terms.pdf", + learnMoreUrl = "https://tangem.com/en/blog/post/whale-swap", + ) + } +} + +internal fun CampaignType.toPromoCampaignId(): PromoCampaignId = when (this) { + is CampaignType.ReactivationCashback -> PromoCampaignId.ReactivationCashback + is CampaignType.WhaleSwapCashback -> PromoCampaignId.WhaleSwapCashback } \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignsBottomSheetConfig.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignsBottomSheetConfig.kt index 8970c31429..3bc363ef98 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignsBottomSheetConfig.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignsBottomSheetConfig.kt @@ -1,8 +1,5 @@ package com.tangem.features.promobanners.impl.campaigns.entity -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.models.account.Account -import com.tangem.domain.models.currency.CryptoCurrencyStatus import kotlinx.serialization.Serializable @Serializable @@ -24,8 +21,5 @@ internal sealed class CampaignsBottomSheetConfig { @Serializable data class AlreadyActivated( val campaignType: CampaignType, - val appCurrency: AppCurrency, - val account: Account?, - val currency: CryptoCurrencyStatus, ) : CampaignsBottomSheetConfig() } \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/ActivateCampaignsModel.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/ActivateCampaignsModel.kt index 25e8f6cb9f..a4be2f2a3b 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/ActivateCampaignsModel.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/ActivateCampaignsModel.kt @@ -3,29 +3,40 @@ package com.tangem.features.promobanners.impl.campaigns.model import com.tangem.common.ui.account.AccountIconItemStateConverter import com.tangem.common.ui.account.toUM import com.tangem.common.ui.tokens.TokenItemStateConverter +import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.ui.components.account.AccountIconSize import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.message.SnackbarMessage import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.models.account.Account +import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.promo.models.EnrollResult +import com.tangem.domain.promo.models.PromoCampaignId +import com.tangem.domain.promo.models.TokenReward +import com.tangem.domain.promo.usecase.EnrollPromoCampaignUseCase import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenBridge import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult import com.tangem.features.promobanners.impl.R import com.tangem.features.promobanners.impl.campaigns.component.ActivateCampaignBottomSheetComponent import com.tangem.features.promobanners.impl.campaigns.entity.ActivateCampaignUM +import com.tangem.features.promobanners.impl.campaigns.entity.CampaignTypeToContentConverter import com.tangem.features.promobanners.impl.campaigns.entity.FooterUM import com.tangem.features.promobanners.impl.campaigns.entity.SelectedAccountUM import com.tangem.features.promobanners.impl.campaigns.entity.TermsUM -import com.tangem.features.promobanners.impl.campaigns.entity.campaignName +import com.tangem.features.promobanners.impl.campaigns.entity.toPromoCampaignId import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.logging.TangemLogger import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.launchIn @@ -35,6 +46,7 @@ import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import javax.inject.Inject +@Suppress("LongParameterList") @ModelScoped internal class ActivateCampaignsModel @Inject constructor( paramsContainer: ParamsContainer, @@ -42,17 +54,20 @@ internal class ActivateCampaignsModel @Inject constructor( chooseTokenBridgeFactory: ChooseTokenBridge.Factory, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, + private val enrollPromoCampaignUseCase: EnrollPromoCampaignUseCase, private val urlOpener: UrlOpener, + @GlobalUiMessageSender private val messageSender: UiMessageSender, ) : Model() { private val params = paramsContainer.require() + private val campaignType = params.campaignType private val accountIconConverter = AccountIconItemStateConverter(size = AccountIconSize.ExtraSmall) private var appCurrency: AppCurrency = AppCurrency.Default - private var selectedAccount: Account? = null - private var selectedCurrency: CryptoCurrencyStatus? = null + private val campaignId: PromoCampaignId = params.campaignType.toPromoCampaignId() + private val campaignContent = CampaignTypeToContentConverter().convert(campaignType) val uiState: StateFlow - field = MutableStateFlow(getInitialState()) + field = MutableStateFlow(buildInitialModel()) val bridge: ChooseTokenBridge = chooseTokenBridgeFactory.create( modelScope = modelScope, @@ -78,7 +93,26 @@ internal class ActivateCampaignsModel @Inject constructor( .launchIn(modelScope) } - private fun onSelectTokenClick() { + private fun buildInitialModel(): ActivateCampaignUM = ActivateCampaignUM( + logo = campaignContent.logo, + title = resourceReference( + R.string.promo_campaign_summary_title, + wrappedList(campaignContent.name), + ), + description = campaignContent.description, + selectedToken = null, + selectedAccount = null, + isChoosingToken = false, + footerUM = FooterUM( + label = resourceReference(R.string.promo_campaign_select_token), + onPrimaryButtonClick = ::onChooseTokenClick, + ), + onChooseTokenDismiss = ::onChooseTokenDismiss, + onLearnMoreClick = ::onLearnMoreClick, + onChooseTokenClick = ::onChooseTokenClick, + ) + + private fun onChooseTokenClick() { uiState.update { it.copy(isChoosingToken = true) } } @@ -86,42 +120,45 @@ internal class ActivateCampaignsModel @Inject constructor( uiState.update { it.copy(isChoosingToken = false) } } - private fun onEnrollClick() { + private fun onEnrollClick(selectedWalletId: UserWalletId, selectedCurrencyStatus: CryptoCurrencyStatus) { + val token = selectedCurrencyStatus.currency as? CryptoCurrency.Token ?: return + modelScope.launch { - // TODO([REDACTED_TASK_KEY]): call the real campaign enrollment use case; its result decides the next sheet. - // Success -> the "enrolled" sheet; "already activated" error -> hand the chosen token/account - // over to the "already activated" sheet. - if (enrollInCampaign()) { - params.modelCallbacks.onActivated(params.campaignType) - } else { - selectedCurrency?.let { - params.modelCallbacks.onAlreadyActivated(params.campaignType, appCurrency, selectedAccount, it) - } + enrollPromoCampaignUseCase.invoke( + campaign = campaignId, + tokenReward = TokenReward( + tokenAddress = token.contractAddress, + networkId = token.network.rawId, + ), + walletIds = listOf(selectedWalletId), + ).onLeft { error -> + TangemLogger.e("Error enrolling campaign ${campaignType.campaignId}", error) + messageSender.send(SnackbarMessage(message = resourceReference(R.string.common_unknown_error))) + }.onRight { + handleEnrollResponse(it) } } } - @Suppress("FunctionOnlyReturningConstant") // TODO([REDACTED_TASK_KEY]): stub until the real enrollment use case exists. - private fun enrollInCampaign(): Boolean { - // TODO([REDACTED_TASK_KEY]): replace with the real enrollment use case call; `true` = enrolled, `false` = already active. - return true + private fun handleEnrollResponse(enrollResult: EnrollResult) { + when (enrollResult) { + is EnrollResult.AlreadyEnrolled -> params.modelCallbacks.onAlreadyActivated(campaignType) + is EnrollResult.Success -> params.modelCallbacks.onActivated(campaignType) + } } private fun onTermsClick() { - urlOpener.openUrl(CAMPAIGN_TERMS_URL) + urlOpener.openUrl(campaignContent.termsUrl) } private fun onLearnMoreClick() { - urlOpener.openUrl(CAMPAIGN_TERMS_URL) + urlOpener.openUrl(campaignContent.learnMoreUrl) } private fun onTokenChosen(result: ChooseTokenResult) { modelScope.launch { val selectedAccountUM = if (isAccountsModeEnabledUseCase.invokeSync()) { - val account = result.account.account - selectedAccount = account - - when (account) { + when (val account = result.account.account) { is Account.CryptoPortfolio -> SelectedAccountUM( iconState = accountIconConverter.convert(account), name = account.accountName.toUM().value, @@ -136,7 +173,6 @@ internal class ActivateCampaignsModel @Inject constructor( null } - selectedCurrency = result.currency val tokenItem = TokenItemStateConverter(appCurrency = appCurrency).convert(result.currency) uiState.update { state -> @@ -145,12 +181,19 @@ internal class ActivateCampaignsModel @Inject constructor( selectedToken = tokenItem, selectedAccount = selectedAccountUM, footerUM = FooterUM( - label = stringReference("Enroll"), - onPrimaryButtonClick = ::onEnrollClick, + label = resourceReference(R.string.promo_campaign_enroll), + onPrimaryButtonClick = { + onEnrollClick( + selectedWalletId = result.walletId, + selectedCurrencyStatus = result.currency, + ) + }, terms = TermsUM( - // TODO([REDACTED_TASK_KEY]): localize - text = stringReference("I agree with"), - linkText = stringReference("${params.campaignType.campaignName()} Terms"), + text = resourceReference(R.string.promo_campaign_terms_agreement_android), + linkText = resourceReference( + R.string.promo_campaign_terms_link, + wrappedList(campaignContent.name), + ), onTermsClick = ::onTermsClick, ), ), @@ -158,29 +201,4 @@ internal class ActivateCampaignsModel @Inject constructor( } } } - - private fun getInitialState(): ActivateCampaignUM { - return ActivateCampaignUM( - // TODO([REDACTED_TASK_KEY]): source real campaign copy. - title = stringReference("Enroll in ${params.campaignType.campaignName()}"), - description = stringReference( - "Earn cashback on every swap from \$10K until the end of July.\n\n" + - "Rates step up with size: 0.10% from \$10K, 0.20% from \$20K, 0.50% from \$100K.", - ), - selectedToken = null, - selectedAccount = null, - isChoosingToken = false, - footerUM = FooterUM( - label = stringReference("Select token"), - onPrimaryButtonClick = ::onSelectTokenClick, - ), - onChooseTokenDismiss = ::onChooseTokenDismiss, - onLearnMoreClick = ::onLearnMoreClick, - ) - } - - private companion object { - // TODO([REDACTED_TASK_KEY]): replace with the real campaign terms URL. - const val CAMPAIGN_TERMS_URL = "https://tangem.com/en/" - } } \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignAlreadyActivatedModel.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignAlreadyActivatedModel.kt deleted file mode 100644 index 83af0ce6ba..0000000000 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignAlreadyActivatedModel.kt +++ /dev/null @@ -1,63 +0,0 @@ -package com.tangem.features.promobanners.impl.campaigns.model - -import com.tangem.common.ui.account.AccountIconItemStateConverter -import com.tangem.common.ui.account.toUM -import com.tangem.common.ui.tokens.TokenItemStateConverter -import com.tangem.core.decompose.di.ModelScoped -import com.tangem.core.decompose.model.Model -import com.tangem.core.decompose.model.ParamsContainer -import com.tangem.core.ui.components.account.AccountIconSize -import com.tangem.core.ui.components.currency.icon.CurrencyIconState -import com.tangem.domain.models.account.Account -import com.tangem.features.promobanners.impl.campaigns.component.CampaignAlreadyActivatedBottomSheetComponent -import com.tangem.features.promobanners.impl.campaigns.entity.CampaignAlreadyActivatedUM -import com.tangem.features.promobanners.impl.campaigns.entity.SelectedAccountUM -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import javax.inject.Inject - -/** - * Thin model for the "campaign already activated" bottom sheet. The screen is read-only: it just renders - * the token/account the cashback is paid out to. - * - * The state is the selection the user made in the activate flow, handed over via [Params] when enrollment - * reports the campaign is already active. That selection is in-memory only (it carries non-serializable UI - * state), so after process death it is `null` and the model assembles a placeholder state instead - * (see [REDACTED_TASK_KEY]). - */ -@ModelScoped -internal class CampaignAlreadyActivatedModel @Inject constructor( - paramsContainer: ParamsContainer, - override val dispatchers: CoroutineDispatcherProvider, -) : Model() { - - private val params = paramsContainer.require() - private val accountIconConverter = AccountIconItemStateConverter(size = AccountIconSize.ExtraSmall) - - val uiState: StateFlow - field = MutableStateFlow(buildState()) - - private fun buildState(): CampaignAlreadyActivatedUM { - val selectedAccountUM = params.account?.let { account -> - when (account) { - is Account.CryptoPortfolio -> SelectedAccountUM( - iconState = accountIconConverter.convert(account), - name = account.accountName.toUM().value, - ) - is Account.Payment -> SelectedAccountUM( - iconState = CurrencyIconState.PaymentAccount(size = AccountIconSize.ExtraSmall), - name = account.accountName.toUM().value, - ) - is Account.Virtual -> null - } - } - - val tokenItem = TokenItemStateConverter(appCurrency = params.appCurrency).convert(params.currency) - - return CampaignAlreadyActivatedUM( - selectedToken = tokenItem, - selectedAccount = selectedAccountUM, - ) - } -} \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignContent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignContent.kt new file mode 100644 index 0000000000..792aced5e2 --- /dev/null +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignContent.kt @@ -0,0 +1,12 @@ +package com.tangem.features.promobanners.impl.campaigns.model + +import com.tangem.core.ui.ds.image.TangemIconUM +import com.tangem.core.ui.extensions.TextReference + +internal data class CampaignContent( + val name: String, + val logo: TangemIconUM, + val description: TextReference, + val termsUrl: String, + val learnMoreUrl: String, +) \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignsModel.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignsModel.kt index 36c5a3f459..9bef5d07e1 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignsModel.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignsModel.kt @@ -1,20 +1,32 @@ package com.tangem.features.promobanners.impl.campaigns.model +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.dismiss +import com.tangem.core.decompose.di.GlobalUiMessageSender import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.model.Model -import com.tangem.domain.appcurrency.model.AppCurrency -import com.tangem.domain.models.account.Account -import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.core.decompose.ui.UiMessageSender +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.message.SnackbarMessage +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.promo.models.PromoCampaignState +import com.tangem.domain.promo.usecase.GetPromoCampaignStateUseCase +import com.tangem.features.promobanners.impl.R import com.tangem.features.promobanners.impl.campaigns.converters.CampaignIdConverter import com.tangem.features.promobanners.impl.campaigns.entity.CampaignsBottomSheetConfig import com.tangem.features.promobanners.impl.campaigns.entity.CampaignType +import com.tangem.features.promobanners.impl.campaigns.entity.toPromoCampaignId import com.tangem.features.promobanners.impl.campaigns.service.CampaignsService import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.logging.TangemLogger +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach +import kotlinx.coroutines.launch import javax.inject.Inject @ModelScoped @@ -22,35 +34,62 @@ internal class CampaignsModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val campaignIdConverter: CampaignIdConverter, campaignsService: CampaignsService, + private val getPromoCampaignStateUseCase: GetPromoCampaignStateUseCase, + @GlobalUiMessageSender private val messageSender: UiMessageSender, ) : Model() { val bottomSheetNavigation: SlotNavigation = SlotNavigation() + val footerExtraHeightState: StateFlow + field = MutableStateFlow(0.dp) + init { campaignsService.campaignFlow - .onEach { campaignId -> resolveStartNavigation(campaignIdConverter.convert(campaignId)) } + .onEach { request -> + resolveStartNavigation( + campaignType = campaignIdConverter.convert(request.campaignId), + userWalletId = request.userWalletId, + ) + } .launchIn(modelScope) } - @Suppress("UnusedPrivateMember") - private fun resolveStartNavigation(campaignType: CampaignType?) { - val config = when (campaignType) { - is CampaignType.ReactivationCashback -> checkReactivationCashbackCampaignState(campaignType) - is CampaignType.WhaleSwapCashback -> checkWhaleSwapCashbackCampaignState(campaignType) - null -> CampaignsBottomSheetConfig.NotActive + private fun resolveStartNavigation(campaignType: CampaignType?, userWalletId: UserWalletId) { + modelScope.launch { + val config = if (campaignType == null) { + CampaignsBottomSheetConfig.NotActive + } else { + checkCampaignState(campaignType, userWalletId) + } + + config?.let { bottomSheetNavigation.activate(it) } } - - bottomSheetNavigation.activate(config) } - // TODO - private fun checkReactivationCashbackCampaignState(campaignType: CampaignType): CampaignsBottomSheetConfig { - return CampaignsBottomSheetConfig.Activate(campaignType) - } + private suspend fun checkCampaignState( + campaignType: CampaignType, + userWalletId: UserWalletId, + ): CampaignsBottomSheetConfig? = getPromoCampaignStateUseCase.invoke( + campaign = campaignType.toPromoCampaignId(), + userWalletId = userWalletId, + ).fold( + ifLeft = { error -> + TangemLogger.e("Error getting campaign ${campaignType.campaignId} state", error) + messageSender.send(SnackbarMessage(message = resourceReference(R.string.common_unknown_error))) + null + }, + ifRight = { campaignState -> + when (campaignState) { + is PromoCampaignState.Enrolled, + is PromoCampaignState.Available, + -> CampaignsBottomSheetConfig.Activate(campaignType) + is PromoCampaignState.NotActive -> CampaignsBottomSheetConfig.NotActive + } + }, + ) - // TODO - private fun checkWhaleSwapCashbackCampaignState(campaignType: CampaignType): CampaignsBottomSheetConfig { - return CampaignsBottomSheetConfig.Activate(campaignType) + fun onFooterExtraHeightReady(height: Dp) { + footerExtraHeightState.value = height } fun onDismiss() { @@ -58,22 +97,12 @@ internal class CampaignsModel @Inject constructor( } fun onActivated(campaignType: CampaignType) { + footerExtraHeightState.value = 0.dp bottomSheetNavigation.activate(CampaignsBottomSheetConfig.Enrolled(campaignType)) } - fun onAlreadyActivated( - campaignType: CampaignType, - appCurrency: AppCurrency, - account: Account?, - currency: CryptoCurrencyStatus, - ) { - bottomSheetNavigation.activate( - CampaignsBottomSheetConfig.AlreadyActivated( - campaignType = campaignType, - appCurrency = appCurrency, - account = account, - currency = currency, - ), - ) + fun onAlreadyActivated(campaignType: CampaignType) { + footerExtraHeightState.value = 0.dp + bottomSheetNavigation.activate(CampaignsBottomSheetConfig.AlreadyActivated(campaignType = campaignType)) } } \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/service/CampaignsService.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/service/CampaignsService.kt index a05d5ccbb7..eaa186f620 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/service/CampaignsService.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/service/CampaignsService.kt @@ -1,5 +1,6 @@ package com.tangem.features.promobanners.impl.campaigns.service +import com.tangem.domain.models.wallet.UserWalletId import kotlinx.coroutines.flow.Flow /** @@ -9,9 +10,15 @@ import kotlinx.coroutines.flow.Flow */ internal interface CampaignsService { - /** Emits the campaignId requested via [show]. */ - val campaignFlow: Flow + /** Emits the campaign requested via [show]. */ + val campaignFlow: Flow - /** Requests showing the campaign identified by [campaignId]. */ - fun show(campaignId: String) -} \ No newline at end of file + /** Requests showing the campaign identified by [campaignId] for the given [userWalletId]. */ + fun show(campaignId: String, userWalletId: UserWalletId) +} + +/** Payload of the campaigns bus: the campaign id and the wallet the campaign should be activated for. */ +internal data class CampaignRequest( + val campaignId: String, + val userWalletId: UserWalletId, +) \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/service/DefaultCampaignsService.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/service/DefaultCampaignsService.kt index cc51f81c6f..fda538fd32 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/service/DefaultCampaignsService.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/service/DefaultCampaignsService.kt @@ -1,5 +1,6 @@ package com.tangem.features.promobanners.impl.campaigns.service +import com.tangem.domain.models.wallet.UserWalletId import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.receiveAsFlow @@ -9,10 +10,10 @@ import javax.inject.Singleton @Singleton internal class DefaultCampaignsService @Inject constructor() : CampaignsService { - private val _campaignFlow: Channel = Channel(Channel.BUFFERED) - override val campaignFlow: Flow = _campaignFlow.receiveAsFlow() + private val _campaignFlow: Channel = Channel(Channel.BUFFERED) + override val campaignFlow: Flow = _campaignFlow.receiveAsFlow() - override fun show(campaignId: String) { - _campaignFlow.trySend(campaignId) + override fun show(campaignId: String, userWalletId: UserWalletId) { + _campaignFlow.trySend(CampaignRequest(campaignId = campaignId, userWalletId = userWalletId)) } } \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignContent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignContent.kt index 7c9337d187..80472880e0 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignContent.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignContent.kt @@ -1,7 +1,6 @@ package com.tangem.features.promobanners.impl.campaigns.ui import android.content.res.Configuration -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* @@ -12,7 +11,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -21,7 +19,9 @@ import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.SpacerH8 import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.ds.image.TangemIcon import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.features.promobanners.impl.R @@ -29,21 +29,20 @@ import com.tangem.features.promobanners.impl.campaigns.entity.ActivateCampaignUM import com.tangem.features.promobanners.impl.campaigns.entity.SelectedAccountUM @Composable -internal fun ActivateCampaignContent(um: ActivateCampaignUM) { +internal fun ActivateCampaignContent(um: ActivateCampaignUM, modifier: Modifier = Modifier) { Column( - modifier = Modifier + modifier = modifier .fillMaxWidth() .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { - Image( - // TODO([REDACTED_TASK_KEY]): replace placeholder with the real campaign illustration. - painter = painterResource(R.drawable.ill_businessman_3d), - contentDescription = null, + TangemIcon( + tangemIconUM = um.logo, modifier = Modifier .size(80.dp) .clip(CircleShape), ) + SpacerH32() Text( @@ -66,18 +65,18 @@ internal fun ActivateCampaignContent(um: ActivateCampaignUM) { SpacerH12() Text( - // TODO([REDACTED_TASK_KEY]): localize - text = "Learn more", + text = stringResourceSafe(R.string.common_learn_more), style = TangemTheme.typography3.caption.medium, color = TangemTheme.colors3.text.primary, modifier = Modifier - .fillMaxWidth() + .align(Alignment.Start) .clickable(onClick = um.onLearnMoreClick), ) SelectedTokenContent( selectedToken = um.selectedToken, selectedAccount = um.selectedAccount, + onChooseTokenClick = um.onChooseTokenClick, ) SpacerH32() @@ -85,12 +84,16 @@ internal fun ActivateCampaignContent(um: ActivateCampaignUM) { } @Composable -private fun SelectedTokenContent(selectedToken: TokenItemState?, selectedAccount: SelectedAccountUM?) { +private fun SelectedTokenContent( + selectedToken: TokenItemState?, + selectedAccount: SelectedAccountUM?, + onChooseTokenClick: () -> Unit, +) { if (selectedToken != null) { SpacerH24() Text( - text = "Select cashback account", // TODO([REDACTED_TASK_KEY]): localize + text = stringResourceSafe(R.string.promo_campaign_select_cashback_account), style = TangemTheme.typography.subtitle1, color = TangemTheme.colors.text.primary1, modifier = Modifier.fillMaxWidth(), @@ -102,7 +105,10 @@ private fun SelectedTokenContent(selectedToken: TokenItemState?, selectedAccount modifier = Modifier .fillMaxWidth() .clip(RoundedCornerShape(24.dp)) - .background(TangemTheme.colors.background.primary), + .background(TangemTheme.colors3.bg.tertiary) + .clickable { + onChooseTokenClick.invoke() + }, selectedToken = selectedToken, selectedAccount = selectedAccount, ) diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignFooter.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignFooter.kt index 237962daee..92f9e7688d 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignFooter.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignFooter.kt @@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.layout.onSizeChanged +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.text.LinkAnnotation import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.buildAnnotatedString @@ -16,6 +18,7 @@ import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.text.withLink import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import com.tangem.core.ui.components.PrimaryButton import com.tangem.core.ui.components.SpacerH12 @@ -26,17 +29,28 @@ import com.tangem.features.promobanners.impl.campaigns.entity.FooterUM import com.tangem.features.promobanners.impl.campaigns.entity.TermsUM @Composable -internal fun ActivateCampaignFooter(footerUM: FooterUM, modifier: Modifier = Modifier) { +internal fun ActivateCampaignFooter( + footerUM: FooterUM, + onFooterTextHeightReady: (Dp) -> Unit, + modifier: Modifier = Modifier, +) { Column(modifier = modifier) { val terms = footerUM.terms if (terms != null) { + val density = LocalDensity.current + Text( text = termsAnnotatedString(terms), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .onSizeChanged { + val termsBlockHeight = with(density) { it.height.toDp() } + 12.dp + onFooterTextHeightReady.invoke(termsBlockHeight) + }, ) SpacerH12() @@ -85,6 +99,7 @@ private fun Preview_ActivateCampaignFooter_WithTerms() { modifier = Modifier .background(TangemTheme.colors3.bg.primary) .padding(16.dp), + onFooterTextHeightReady = {}, ) } } @@ -99,6 +114,7 @@ private fun Preview_ActivateCampaignFooter_NoTerms() { modifier = Modifier .background(TangemTheme.colors3.bg.primary) .padding(16.dp), + onFooterTextHeightReady = {}, ) } } diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/AlreadyActivatedCampaignContent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/AlreadyActivatedCampaignContent.kt index fa2367418d..d654226708 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/AlreadyActivatedCampaignContent.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/AlreadyActivatedCampaignContent.kt @@ -13,21 +13,19 @@ import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.SpacerH12 -import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.components.SpacerH32 -import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.generated.icons.Icons import com.tangem.core.ui.res.generated.icons.ic_info_24 -import com.tangem.features.promobanners.impl.campaigns.entity.CampaignAlreadyActivatedUM -import com.tangem.features.promobanners.impl.campaigns.entity.SelectedAccountUM @Composable -internal fun ActivateCampaignContent(um: CampaignAlreadyActivatedUM) { +internal fun AlreadyActivatedCampaignContent(message: TextReference, modifier: Modifier = Modifier) { Column( - modifier = Modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally, ) { Box( @@ -48,7 +46,7 @@ internal fun ActivateCampaignContent(um: CampaignAlreadyActivatedUM) { SpacerH32() Text( - text = "You’re already enrolled in Whale Swap Cashback", // TODO localization + text = message.resolveReference(), style = TangemTheme.typography3.heading.small, color = TangemTheme.colors3.text.primary, textAlign = TextAlign.Center, @@ -57,39 +55,10 @@ internal fun ActivateCampaignContent(um: CampaignAlreadyActivatedUM) { .padding(horizontal = 16.dp), ) - SpacerH12() - - SelectedTokenContent( - selectedToken = um.selectedToken, - selectedAccount = um.selectedAccount, - ) - SpacerH32() } } -@Composable -private fun SelectedTokenContent(selectedToken: TokenItemState, selectedAccount: SelectedAccountUM?) { - SpacerH24() - - Text( - text = "Eligible cashback will be distributed to:", // TODO([REDACTED_TASK_KEY]): localize - style = TangemTheme.typography.subtitle1, - color = TangemTheme.colors.text.primary1, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp), - ) - - SpacerH12() - - PromoCampaignTokenItem( - modifier = Modifier.fillMaxWidth(), - selectedToken = selectedToken, - selectedAccount = selectedAccount, - ) -} - // region Preview @Preview(showBackground = true, widthDp = 360) @Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) @@ -97,7 +66,7 @@ private fun SelectedTokenContent(selectedToken: TokenItemState, selectedAccount: private fun Preview_AlreadyActivatedCampaignContent() { TangemThemePreviewRedesign { Box(modifier = Modifier.background(TangemTheme.colors3.bg.primary)) { - ActivateCampaignContent(um = CampaignPreviewData.alreadyActivated) + AlreadyActivatedCampaignContent(message = stringReference("You’re already enrolled in Whale Swap Cashback")) } } } diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/CampaignEnrolledMessageContent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/CampaignEnrolledMessageContent.kt index ef064a4a4d..c55a819bbf 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/CampaignEnrolledMessageContent.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/CampaignEnrolledMessageContent.kt @@ -1,5 +1,6 @@ package com.tangem.features.promobanners.impl.campaigns.ui +import android.content.res.Configuration import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -14,12 +15,14 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.SpacerH import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.core.ui.res.generated.icons.Icons import com.tangem.core.ui.res.generated.icons.ic_success_24 @@ -56,6 +59,21 @@ fun CampaignEnrolledMessageContent(message: TextReference, modifier: Modifier = modifier = Modifier.fillMaxWidth(), ) - SpacerH(48.dp) + SpacerH32() } -} \ No newline at end of file +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_CampaignEnrolledMessageContent() { + TangemThemePreviewRedesign { + Box(modifier = Modifier.background(TangemTheme.colors3.bg.primary)) { + CampaignEnrolledMessageContent( + message = stringReference("You’re successfully enrolled in Enroll in Whale Swap Cashback"), + ) + } + } +} +// endregion \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/CampaignPreviewData.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/CampaignPreviewData.kt index 0aa7391b16..6589fd0136 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/CampaignPreviewData.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/CampaignPreviewData.kt @@ -3,9 +3,10 @@ package com.tangem.features.promobanners.impl.campaigns.ui import androidx.compose.ui.graphics.Color import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.ds.image.TangemIconUM import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.promobanners.impl.R import com.tangem.features.promobanners.impl.campaigns.entity.ActivateCampaignUM -import com.tangem.features.promobanners.impl.campaigns.entity.CampaignAlreadyActivatedUM import com.tangem.features.promobanners.impl.campaigns.entity.FooterUM import com.tangem.features.promobanners.impl.campaigns.entity.SelectedAccountUM import com.tangem.features.promobanners.impl.campaigns.entity.TermsUM @@ -51,6 +52,7 @@ internal object CampaignPreviewData { ) val activateCampaign: ActivateCampaignUM = ActivateCampaignUM( + logo = TangemIconUM.Icon(R.drawable.ic_alert_24), title = stringReference("Whale Swap Cashback"), description = stringReference( "Get cashback on every swap. Pick a token and the account where your rewards will be paid out.", @@ -61,10 +63,6 @@ internal object CampaignPreviewData { footerUM = footer, onChooseTokenDismiss = {}, onLearnMoreClick = {}, - ) - - val alreadyActivated: CampaignAlreadyActivatedUM = CampaignAlreadyActivatedUM( - selectedToken = tokenItem, - selectedAccount = selectedAccount, + onChooseTokenClick = {}, ) } \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/NotActiveCampaignMessageContent.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/NotActiveCampaignMessageContent.kt index 9557228a22..9a30f5d86d 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/NotActiveCampaignMessageContent.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/NotActiveCampaignMessageContent.kt @@ -15,9 +15,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp -import com.tangem.core.ui.components.SpacerH +import com.tangem.features.promobanners.impl.R import com.tangem.core.ui.components.SpacerH32 import com.tangem.core.ui.components.SpacerH8 +import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.generated.icons.Icons import com.tangem.core.ui.res.generated.icons.ic_warning_24 @@ -48,7 +49,7 @@ fun NotActiveCampaignMessageContent(modifier: Modifier = Modifier) { SpacerH32() Text( - text = " Campaign not active", // TODO localization + text = stringResourceSafe(R.string.promo_campaign_not_active_title), style = TangemTheme.typography3.heading.small, color = TangemTheme.colors3.text.primary, textAlign = TextAlign.Center, @@ -58,13 +59,13 @@ fun NotActiveCampaignMessageContent(modifier: Modifier = Modifier) { SpacerH8() Text( - text = "This campaign no longer exists or has expired", // TODO localization + text = stringResourceSafe(R.string.promo_campaign_not_active_subtitle), style = TangemTheme.typography3.subheading.medium, color = TangemTheme.colors3.text.secondary, textAlign = TextAlign.Center, modifier = Modifier.fillMaxWidth(), ) - SpacerH(48.dp) + SpacerH32() } } \ No newline at end of file