diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUM.kt index 6ac0d9d8ca..5a79c753b7 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUM.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetUM.kt @@ -49,11 +49,11 @@ data class MessageBottomSheetUM( var backgroundType: BackgroundType = BackgroundType.Unspecified, ) : Element { enum class Type { - Unspecified, Accent, Informative, Attention, Warning, + Unspecified, Accent, Informative, Attention, Warning, Success, } enum class BackgroundType { - Unspecified, SameAsTint, Accent, Informative, Attention, Warning, + Unspecified, SameAsTint, Accent, Informative, Attention, Warning, Success, } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetV2.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetV2.kt index baad8cca0a..cf02bcd878 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetV2.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheetV2.kt @@ -214,6 +214,7 @@ private fun BottomSheetVector(vector: MessageBottomSheetUM.Vector, modifier: Mod MessageBottomSheetUM.Vector.Type.Informative -> TangemTheme.colors3.icon.status.info MessageBottomSheetUM.Vector.Type.Attention -> TangemTheme.colors3.icon.status.warning MessageBottomSheetUM.Vector.Type.Warning -> TangemTheme.colors3.icon.status.error + MessageBottomSheetUM.Vector.Type.Success -> TangemTheme.colors3.icon.status.success } val backgroundColor = when (vector.backgroundType) { @@ -223,6 +224,7 @@ private fun BottomSheetVector(vector: MessageBottomSheetUM.Vector, modifier: Mod MessageBottomSheetUM.Vector.BackgroundType.Informative -> TangemTheme.colors3.bg.status.infoSubtle MessageBottomSheetUM.Vector.BackgroundType.Attention -> TangemTheme.colors3.bg.status.warningSubtle MessageBottomSheetUM.Vector.BackgroundType.Warning -> TangemTheme.colors3.bg.status.errorSubtle + MessageBottomSheetUM.Vector.BackgroundType.Success -> TangemTheme.colors3.bg.status.successSubtle } Box( diff --git a/features/promo-banners/impl/.gitignore b/features/promo-banners/impl/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/features/promo-banners/impl/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/features/promo-banners/impl/build.gradle.kts b/features/promo-banners/impl/build.gradle.kts index 1f23256963..8fd8f36248 100644 --- a/features/promo-banners/impl/build.gradle.kts +++ b/features/promo-banners/impl/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { api(projects.domain.common) implementation(projects.domain.models) implementation(projects.domain.appCurrency) + implementation(projects.domain.account.status) /** Core */ api(projects.core.configToggles) @@ -54,4 +55,5 @@ dependencies { /** Tests */ testImplementation(deps.test.junit5) testImplementation(deps.test.truth) + testImplementation(deps.kotlin.serialization) } \ No newline at end of file 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 97ffbc4835..6137d3f58a 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,26 +1,38 @@ 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.context.child 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.sheet.TangemBottomSheet -import com.tangem.core.ui.decompose.ComposableBottomSheetComponent +import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState +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 import com.tangem.features.promobanners.impl.campaigns.ui.ActivateCampaignContent +import com.tangem.features.promobanners.impl.campaigns.ui.ActivateCampaignFooter internal class ActivateCampaignBottomSheetComponent( appComponentContext: AppComponentContext, chooseTokenComponentFactory: ChooseTokenComponent.Factory, - params: ActivateCampaignsModel.Params, -) : ComposableBottomSheetComponent, AppComponentContext by appComponentContext { + private val params: Params, + val onDismiss: () -> Unit, +) : CampaignsModularComponent, AppComponentContext by appComponentContext { private val model: ActivateCampaignsModel = getOrCreateModel(params) @@ -29,36 +41,55 @@ internal class ActivateCampaignBottomSheetComponent( params = ChooseTokenComponent.Params(bridge = model.bridge), ) - override fun dismiss() = model.onDismiss() + @Composable + override fun Title(bottomSheetState: State) { + TangemTopNavigation( + windowInsets = WindowInsets(0), + blurBackground = false, + endButton = { TangemButton.Close(onClick = onDismiss) }, + ) + } @Composable - override fun BottomSheet() { + override fun Content(bottomSheetState: State, contentPadding: PaddingValues, modifier: Modifier) { val state by model.uiState.collectAsStateWithLifecycle() - ActivateCampaignContent( - state = state, - onSelectTokenClick = model::onSelectTokenClick, - onEnrollClick = model::onEnrollClick, - onLearnMoreClick = { /* [REDACTED_TODO_COMMENT] */ }, - onDismiss = ::dismiss, - ) + ActivateCampaignContent(um = state) if (state.isChoosingToken) { - ChooseTokenBottomSheet() + ChooseTokenBottomSheet(state.onChooseTokenDismiss) } } @Composable - private fun ChooseTokenBottomSheet() { + override fun Footer() { + val state by model.uiState.collectAsStateWithLifecycle() + + ActivateCampaignFooter( + footerUM = state.footerUM, + ) + } + + @Composable + private fun ChooseTokenBottomSheet(onChooseTokenDismiss: () -> Unit) { TangemBottomSheet( config = TangemBottomSheetConfig( isShown = true, - onDismissRequest = model::onChooseTokenDismiss, + onDismissRequest = onChooseTokenDismiss, content = TangemBottomSheetConfigContent.Empty, ), - onBack = model::onChooseTokenDismiss, - ) { - chooseTokenComponent.Content(modifier = Modifier.fillMaxWidth()) - } + onBack = onChooseTokenDismiss, + content = { chooseTokenComponent.Content(modifier = Modifier.fillMaxWidth()) }, + ) + } + + data class Params( + val campaignType: CampaignType, + val modelCallbacks: ActivateCampaignModelCallbacks, + ) + + interface ActivateCampaignModelCallbacks { + val onActivated: (CampaignType) -> Unit + val onAlreadyActivated: (CampaignType, AppCurrency, Account?, CryptoCurrencyStatus) -> 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 new file mode 100644 index 0000000000..bbc6504059 --- /dev/null +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignAlreadyActivatedBottomSheetComponent.kt @@ -0,0 +1,66 @@ +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.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.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.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 + +internal class CampaignAlreadyActivatedBottomSheetComponent( + appComponentContext: AppComponentContext, + params: Params, + val onDismiss: () -> Unit, +) : CampaignsModularComponent, AppComponentContext by appComponentContext { + + private val model: CampaignAlreadyActivatedModel = getOrCreateModel(params) + + @Composable + override fun Title(bottomSheetState: State) { + TangemTopNavigation( + windowInsets = WindowInsets(0), + blurBackground = false, + endButton = { TangemButton.Close(onClick = onDismiss) }, + ) + } + + @Composable + override fun Content(bottomSheetState: State, contentPadding: PaddingValues, modifier: Modifier) { + val state by model.uiState.collectAsStateWithLifecycle() + + ActivateCampaignContent(um = state) + } + + @Composable + override fun Footer() { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = stringResourceSafe(R.string.common_close), + onClick = onDismiss, + ) + } + + 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 8bdbfb6ac7..8853bca703 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,50 +1,50 @@ 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 com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheet -import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUM -import com.tangem.core.ui.components.bottomsheets.message.infoBlock -import com.tangem.core.ui.components.bottomsheets.message.messageBottomSheetUM -import com.tangem.core.ui.components.bottomsheets.message.onClick -import com.tangem.core.ui.components.bottomsheets.message.onDismiss -import com.tangem.core.ui.components.bottomsheets.message.primaryButton -import com.tangem.core.ui.components.bottomsheets.message.vector -import com.tangem.core.ui.decompose.ComposableBottomSheetComponent -import com.tangem.core.ui.extensions.resourceReference +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.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.res.generated.icons.Icons -import com.tangem.core.ui.res.generated.icons.ic_success_24 +import com.tangem.core.ui.extensions.stringResourceSafe 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.ui.CampaignEnrolledMessageContent internal class CampaignEnrolledBottomSheetComponent( private val campaignType: CampaignType, private val onDismissRequest: () -> Unit, -) : ComposableBottomSheetComponent { - - override fun dismiss() = onDismissRequest() +) : CampaignsModularComponent { @Composable - override fun BottomSheet() { - MessageBottomSheet( - state = messageBottomSheetUM { - onDismiss(onDismissRequest) - infoBlock { - vector(Icons.ic_success_24) { - type = MessageBottomSheetUM.Vector.Type.Accent - backgroundType = MessageBottomSheetUM.Vector.BackgroundType.SameAsTint - } + override fun Title(bottomSheetState: State) { + TangemTopNavigation( + windowInsets = WindowInsets(0), + blurBackground = false, + endButton = { TangemButton.Close(onClick = onDismissRequest) }, + ) + } - title = stringReference("You're successfully enrolled in ${campaignType.campaignName()}") - body = stringReference("Your cashback will be applied to eligible swaps automatically.") - } - primaryButton { - text = resourceReference(R.string.common_close) - onClick { closeBs() } - } - }, - onDismissRequest = onDismissRequest, + @Composable + override fun Content(bottomSheetState: State, contentPadding: PaddingValues, modifier: Modifier) { + CampaignEnrolledMessageContent( + message = stringReference("You're successfully enrolled in ${campaignType.campaignName()}"), + ) + } + + @Composable + override fun Footer() { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = stringResourceSafe(R.string.common_close), + onClick = { onDismissRequest() }, ) } } \ 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 new file mode 100644 index 0000000000..bb2368cd7e --- /dev/null +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/component/CampaignsModularComponent.kt @@ -0,0 +1,10 @@ +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 e815a741c6..74c1e17c55 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 @@ -1,19 +1,39 @@ 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 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.decompose.ComposableBottomSheetComponent +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.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.features.commonfeatures.api.choosetoken.ChooseTokenComponent import com.tangem.features.promobanners.api.swapcashback.CampaignsComponent +import com.tangem.features.promobanners.impl.campaigns.component.ActivateCampaignBottomSheetComponent.ActivateCampaignModelCallbacks +import com.tangem.features.promobanners.impl.campaigns.entity.CampaignType import com.tangem.features.promobanners.impl.campaigns.entity.CampaignsBottomSheetConfig -import com.tangem.features.promobanners.impl.campaigns.model.ActivateCampaignsModel import com.tangem.features.promobanners.impl.campaigns.model.CampaignsModel import dagger.assisted.Assisted import dagger.assisted.AssistedFactory @@ -29,7 +49,7 @@ internal class DefaultCampaignsComponent @AssistedInject constructor( private val bottomSheetSlot = childSlot( source = model.bottomSheetNavigation, - serializer = CampaignsBottomSheetConfig.serializer(), + serializer = null, handleBackButton = false, childFactory = ::bottomSheetChild, ) @@ -37,13 +57,61 @@ internal class DefaultCampaignsComponent @AssistedInject constructor( @Composable override fun Content(modifier: Modifier) { val bottomSheet by bottomSheetSlot.subscribeAsState() - bottomSheet.child?.instance?.BottomSheet() + val activeChild = bottomSheet.child?.instance + val displayedChild = rememberLastNonNull(activeChild) + val bottomSheetState = remember { mutableStateOf(BottomSheetState.EXPANDED) } + + TangemBottomSheet( + config = TangemBottomSheetConfig( + isShown = activeChild != null, + onDismissRequest = model::onDismiss, + content = TangemBottomSheetConfigContent.Empty, + ), + onBack = model::onDismiss, + title = { + displayedChild?.Title(bottomSheetState) + }, + 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, + ) + } + }, + footer = { + Box( + modifier = Modifier + .navigationBarsPadding() + .padding(12.dp), + ) { + displayedChild?.Footer() + } + }, + ) } private fun bottomSheetChild( config: CampaignsBottomSheetConfig, componentContext: ComponentContext, - ): ComposableBottomSheetComponent { + ): CampaignsModularComponent { val context = childByContext(componentContext) return when (config) { CampaignsBottomSheetConfig.NotActive -> NotActiveCampaignBottomSheetComponent( @@ -56,10 +124,28 @@ internal class DefaultCampaignsComponent @AssistedInject constructor( is CampaignsBottomSheetConfig.Activate -> ActivateCampaignBottomSheetComponent( appComponentContext = context, chooseTokenComponentFactory = chooseTokenComponentFactory, - params = ActivateCampaignsModel.Params( + onDismiss = model::onDismiss, + params = ActivateCampaignBottomSheetComponent.Params( campaignType = config.campaignType, - onDismiss = model::onDismiss, - onActivated = model::onActivated, + modelCallbacks = object : ActivateCampaignModelCallbacks { + override val onActivated: (CampaignType) -> Unit = model::onActivated + override val onAlreadyActivated: ( + CampaignType, + AppCurrency, + Account?, + CryptoCurrencyStatus, + ) -> 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, ), ) } 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 005d3c057e..05dd624b48 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,44 +1,44 @@ 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 com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheet -import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUM -import com.tangem.core.ui.components.bottomsheets.message.icon -import com.tangem.core.ui.components.bottomsheets.message.infoBlock -import com.tangem.core.ui.components.bottomsheets.message.messageBottomSheetUM -import com.tangem.core.ui.components.bottomsheets.message.onClick -import com.tangem.core.ui.components.bottomsheets.message.onDismiss -import com.tangem.core.ui.components.bottomsheets.message.primaryButton -import com.tangem.core.ui.decompose.ComposableBottomSheetComponent -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference +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.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.stringResourceSafe import com.tangem.features.promobanners.impl.R +import com.tangem.features.promobanners.impl.campaigns.ui.NotActiveCampaignMessageContent internal class NotActiveCampaignBottomSheetComponent( private val onDismissRequest: () -> Unit, -) : ComposableBottomSheetComponent { - - override fun dismiss() = onDismissRequest() +) : CampaignsModularComponent { @Composable - override fun BottomSheet() { - MessageBottomSheet( - state = messageBottomSheetUM { - onDismiss(onDismissRequest) - infoBlock { - icon(R.drawable.ic_alert_circle_24) { - type = MessageBottomSheetUM.Icon.Type.Warning - backgroundType = MessageBottomSheetUM.Icon.BackgroundType.SameAsTint - } - title = stringReference("Campaign not active") // TODO localization - body = stringReference("This campaign no longer exists or has expired.") // TODO localization - } - primaryButton { - text = resourceReference(R.string.common_close) - onClick { closeBs() } - } - }, - onDismissRequest = onDismissRequest, + override fun Title(bottomSheetState: State) { + TangemTopNavigation( + windowInsets = WindowInsets(0), + blurBackground = false, + endButton = { TangemButton.Close(onClick = onDismissRequest) }, + ) + } + + @Composable + override fun Content(bottomSheetState: State, contentPadding: PaddingValues, modifier: Modifier) { + NotActiveCampaignMessageContent() + } + + @Composable + override fun Footer() { + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = stringResourceSafe(R.string.common_close), + onClick = { onDismissRequest() }, ) } } \ No newline at end of file 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 a3ad2a34b4..981656f1da 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,6 +7,7 @@ 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 @@ -50,4 +51,9 @@ 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 40e312763e..4481ece80f 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 @@ -1,5 +1,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.extensions.TextReference @@ -10,11 +12,39 @@ import com.tangem.core.ui.extensions.TextReference * the footer shows "Select token"; once a token is chosen it shows the account block, the terms agreement * and the "Enroll" button. When [isChoosingToken] is `true` the token selector is shown on top of the * intro (as a stacked bottom sheet), not instead of it. + * + * [selectedAccount] is shown above the token only in accounts (multi-account) mode — it names the account + * the asset was picked from. It is `null` in single-account mode or before a token is chosen. */ + +@Immutable internal data class ActivateCampaignUM( - val campaignName: String, val title: TextReference, val description: TextReference, val selectedToken: TokenItemState?, + val selectedAccount: SelectedAccountUM?, val isChoosingToken: Boolean, + val footerUM: FooterUM, + val onChooseTokenDismiss: () -> Unit, + val onLearnMoreClick: () -> Unit, +) + +@Immutable +internal data class FooterUM( + val label: TextReference, + val onPrimaryButtonClick: () -> Unit, + val terms: TermsUM? = null, +) + +@Immutable +data class TermsUM( + val text: TextReference, + val linkText: TextReference, + val onTermsClick: () -> Unit, +) + +@Immutable +internal data class SelectedAccountUM( + val iconState: CurrencyIconState, + val name: TextReference, ) \ No newline at end of file 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 new file mode 100644 index 0000000000..eff722c71a --- /dev/null +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignAlreadyActivatedUM.kt @@ -0,0 +1,10 @@ +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/CampaignType.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignType.kt index f3d6574f6d..bd8106428e 100644 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignType.kt +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/entity/CampaignType.kt @@ -3,10 +3,13 @@ package com.tangem.features.promobanners.impl.campaigns.entity import kotlinx.serialization.Serializable @Serializable -sealed interface CampaignType { +internal sealed class CampaignType { - val campaignId: String + abstract val campaignId: String - data class ReactivationCashback(override val campaignId: String) : CampaignType - data class WhaleSwapCashback(override val campaignId: String) : CampaignType + @Serializable + data class ReactivationCashback(override val campaignId: String) : CampaignType() + + @Serializable + data class WhaleSwapCashback(override val campaignId: String) : CampaignType() } \ 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 50bf5c6cca..8970c31429 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,5 +1,8 @@ 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 @@ -17,4 +20,12 @@ internal sealed class CampaignsBottomSheetConfig { data class Activate( val campaignType: CampaignType, ) : 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 a4774195bf..25e8f6cb9f 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 @@ -1,18 +1,29 @@ 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.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.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.CryptoCurrencyStatus 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.CampaignType +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.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.MutableStateFlow @@ -21,6 +32,7 @@ import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch import javax.inject.Inject @ModelScoped @@ -29,11 +41,15 @@ internal class ActivateCampaignsModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, chooseTokenBridgeFactory: ChooseTokenBridge.Factory, getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase, + private val urlOpener: UrlOpener, ) : Model() { - private val params = paramsContainer.require() - + private val params = paramsContainer.require() + private val accountIconConverter = AccountIconItemStateConverter(size = AccountIconSize.ExtraSmall) private var appCurrency: AppCurrency = AppCurrency.Default + private var selectedAccount: Account? = null + private var selectedCurrency: CryptoCurrencyStatus? = null val uiState: StateFlow field = MutableStateFlow(getInitialState()) @@ -62,49 +78,109 @@ internal class ActivateCampaignsModel @Inject constructor( .launchIn(modelScope) } - fun onSelectTokenClick() { + private fun onSelectTokenClick() { uiState.update { it.copy(isChoosingToken = true) } } - fun onChooseTokenDismiss() { + private fun onChooseTokenDismiss() { uiState.update { it.copy(isChoosingToken = false) } } - fun onEnrollClick() { - // TODO([REDACTED_TASK_KEY]): call the real campaign enrollment use case with the chosen token before proceeding. - params.onActivated(params.campaignType) + private fun onEnrollClick() { + 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) + } + } + } } - fun onDismiss() = params.onDismiss() + @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 onTermsClick() { + urlOpener.openUrl(CAMPAIGN_TERMS_URL) + } + + private fun onLearnMoreClick() { + urlOpener.openUrl(CAMPAIGN_TERMS_URL) + } private fun onTokenChosen(result: ChooseTokenResult) { - val tokenItem = TokenItemStateConverter(appCurrency = appCurrency).convert(result.currency) + modelScope.launch { + val selectedAccountUM = if (isAccountsModeEnabledUseCase.invokeSync()) { + val account = result.account.account + selectedAccount = account - uiState.update { state -> - state.copy( - isChoosingToken = false, - selectedToken = tokenItem, - ) + 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 + } + } else { + null + } + + selectedCurrency = result.currency + val tokenItem = TokenItemStateConverter(appCurrency = appCurrency).convert(result.currency) + + uiState.update { state -> + state.copy( + isChoosingToken = false, + selectedToken = tokenItem, + selectedAccount = selectedAccountUM, + footerUM = FooterUM( + label = stringReference("Enroll"), + onPrimaryButtonClick = ::onEnrollClick, + terms = TermsUM( + // TODO([REDACTED_TASK_KEY]): localize + text = stringReference("I agree with"), + linkText = stringReference("${params.campaignType.campaignName()} Terms"), + onTermsClick = ::onTermsClick, + ), + ), + ) + } } } private fun getInitialState(): ActivateCampaignUM { return ActivateCampaignUM( - campaignName = params.campaignType.campaignName(), // 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.\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, ) } - data class Params( - val campaignType: CampaignType, - val onDismiss: () -> Unit, - val onActivated: (CampaignType) -> Unit, - ) + 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 new file mode 100644 index 0000000000..83af0ce6ba --- /dev/null +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignAlreadyActivatedModel.kt @@ -0,0 +1,63 @@ +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/CampaignsModel.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/model/CampaignsModel.kt index ab5bba4e11..36c5a3f459 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 @@ -5,6 +5,9 @@ import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.dismiss 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.features.promobanners.impl.campaigns.converters.CampaignIdConverter import com.tangem.features.promobanners.impl.campaigns.entity.CampaignsBottomSheetConfig import com.tangem.features.promobanners.impl.campaigns.entity.CampaignType @@ -57,4 +60,20 @@ internal class CampaignsModel @Inject constructor( fun onActivated(campaignType: CampaignType) { 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, + ), + ) + } } \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignBottomSheet.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignBottomSheet.kt deleted file mode 100644 index 4d3c39aa1c..0000000000 --- a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignBottomSheet.kt +++ /dev/null @@ -1,50 +0,0 @@ -package com.tangem.features.promobanners.impl.campaigns.ui - -import androidx.compose.foundation.layout.WindowInsets -import androidx.compose.runtime.Composable -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent -import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet -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.res.TangemTheme -import com.tangem.features.promobanners.impl.campaigns.entity.ActivateCampaignUM - -@Composable -internal fun ActivateCampaignContent( - state: ActivateCampaignUM, - onSelectTokenClick: () -> Unit, - onEnrollClick: () -> Unit, - onLearnMoreClick: () -> Unit, - onDismiss: () -> Unit, -) { - TangemBottomSheet( - config = TangemBottomSheetConfig( - isShown = true, - onDismissRequest = onDismiss, - content = TangemBottomSheetConfigContent.Empty, - ), - containerColor = TangemTheme.colors.background.tertiary, - onBack = onDismiss, - title = { - TangemTopNavigation( - windowInsets = WindowInsets(0), - blurBackground = false, - endButton = { - TangemButton.Close( - onClick = onDismiss, - ) - }, - ) - }, - content = { - ActivateCampaignContent( - state = state, - onSelectTokenClick = onSelectTokenClick, - onEnrollClick = onEnrollClick, - onLearnMoreClick = onLearnMoreClick, - ) - }, - ) -} \ 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 4ad65c2d5f..7c9337d187 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,5 +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 @@ -13,29 +14,26 @@ 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 com.tangem.core.ui.components.PrimaryButton +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.SpacerH16 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.TokenItem +import com.tangem.core.ui.components.token.state.TokenItemState import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign import com.tangem.features.promobanners.impl.R import com.tangem.features.promobanners.impl.campaigns.entity.ActivateCampaignUM +import com.tangem.features.promobanners.impl.campaigns.entity.SelectedAccountUM @Composable -internal fun ActivateCampaignContent( - state: ActivateCampaignUM, - onSelectTokenClick: () -> Unit, - onEnrollClick: () -> Unit, - onLearnMoreClick: () -> Unit, -) { +internal fun ActivateCampaignContent(um: ActivateCampaignUM) { Column( modifier = Modifier .fillMaxWidth() - .padding(horizontal = TangemTheme.dimens.spacing16) - .navigationBarsPadding(), + .padding(horizontal = 16.dp), horizontalAlignment = Alignment.CenterHorizontally, ) { Image( @@ -43,93 +41,99 @@ internal fun ActivateCampaignContent( painter = painterResource(R.drawable.ill_businessman_3d), contentDescription = null, modifier = Modifier - .size(TangemTheme.dimens.size96) + .size(80.dp) .clip(CircleShape), ) - SpacerH16() + SpacerH32() + Text( - text = state.title.resolveReference(), - style = TangemTheme.typography.h3, - color = TangemTheme.colors.text.primary1, + text = um.title.resolveReference(), + style = TangemTheme.typography3.heading.small, + color = TangemTheme.colors3.text.primary, textAlign = TextAlign.Start, modifier = Modifier.fillMaxWidth(), ) + SpacerH8() + Text( - text = state.description.resolveReference(), - style = TangemTheme.typography.body2, - color = TangemTheme.colors.text.secondary, + text = um.description.resolveReference(), + style = TangemTheme.typography3.caption.medium, + color = TangemTheme.colors3.text.secondary, modifier = Modifier.fillMaxWidth(), ) + SpacerH12() + Text( // TODO([REDACTED_TASK_KEY]): localize text = "Learn more", - style = TangemTheme.typography.button, - color = TangemTheme.colors.text.accent, + style = TangemTheme.typography3.caption.medium, + color = TangemTheme.colors3.text.primary, modifier = Modifier .fillMaxWidth() - .clickable(onClick = onLearnMoreClick), + .clickable(onClick = um.onLearnMoreClick), ) - val selectedToken = state.selectedToken - if (selectedToken != null) { - SpacerH24() - Text( - text = "Select cashback account", // TODO([REDACTED_TASK_KEY]): localize - style = TangemTheme.typography.subtitle1, - color = TangemTheme.colors.text.primary1, - modifier = Modifier.fillMaxWidth(), - ) - SpacerH12() - Box( - modifier = Modifier - .fillMaxWidth() - .clip(RoundedCornerShape(TangemTheme.dimens.radius16)) - .background(TangemTheme.colors.background.primary) - .padding(vertical = TangemTheme.dimens.spacing12), - ) { - TokenItem(state = selectedToken, isBalanceHidden = false) - } - } - - SpacerH24() - - Footer( - campaignName = state.campaignName, - hasSelectedToken = selectedToken != null, - onSelectTokenClick = onSelectTokenClick, - onEnrollClick = onEnrollClick, + SelectedTokenContent( + selectedToken = um.selectedToken, + selectedAccount = um.selectedAccount, ) + + SpacerH32() } } @Composable -private fun Footer( - campaignName: String, - hasSelectedToken: Boolean, - onSelectTokenClick: () -> Unit, - onEnrollClick: () -> Unit, -) { - if (hasSelectedToken) { +private fun SelectedTokenContent(selectedToken: TokenItemState?, selectedAccount: SelectedAccountUM?) { + if (selectedToken != null) { + SpacerH24() + Text( - text = "I agree with $campaignName Terms", // TODO([REDACTED_TASK_KEY]): localize + clickable terms - style = TangemTheme.typography.caption2, - color = TangemTheme.colors.text.secondary, - textAlign = TextAlign.Center, + text = "Select cashback account", // TODO([REDACTED_TASK_KEY]): localize + style = TangemTheme.typography.subtitle1, + color = TangemTheme.colors.text.primary1, modifier = Modifier.fillMaxWidth(), ) - SpacerH12() - } - PrimaryButton( - modifier = Modifier.fillMaxWidth(), - text = if (hasSelectedToken) { - "Enroll" // TODO([REDACTED_TASK_KEY]): localize - } else { - "Select token" // TODO([REDACTED_TASK_KEY]): localize - }, - onClick = if (hasSelectedToken) onEnrollClick else onSelectTokenClick, - ) - SpacerH16() -} \ No newline at end of file + SpacerH12() + + PromoCampaignTokenItem( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(24.dp)) + .background(TangemTheme.colors.background.primary), + selectedToken = selectedToken, + selectedAccount = selectedAccount, + ) + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_ActivateCampaignContent_WithToken() { + TangemThemePreviewRedesign { + Box(modifier = Modifier.background(TangemTheme.colors3.bg.primary)) { + ActivateCampaignContent(um = CampaignPreviewData.activateCampaign) + } + } +} + +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_ActivateCampaignContent_NoToken() { + TangemThemePreviewRedesign { + Box(modifier = Modifier.background(TangemTheme.colors3.bg.primary)) { + ActivateCampaignContent( + um = CampaignPreviewData.activateCampaign.copy( + selectedToken = null, + selectedAccount = null, + ), + ) + } + } +} +// endregion \ No newline at end of file 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 new file mode 100644 index 0000000000..237962daee --- /dev/null +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/ActivateCampaignFooter.kt @@ -0,0 +1,105 @@ +package com.tangem.features.promobanners.impl.campaigns.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +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.text.LinkAnnotation +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString +import androidx.compose.ui.text.style.TextAlign +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 com.tangem.core.ui.components.PrimaryButton +import com.tangem.core.ui.components.SpacerH12 +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign +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) { + Column(modifier = modifier) { + val terms = footerUM.terms + + if (terms != null) { + Text( + text = termsAnnotatedString(terms), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + + SpacerH12() + } + + PrimaryButton( + modifier = Modifier.fillMaxWidth(), + text = footerUM.label.resolveReference(), + onClick = footerUM.onPrimaryButtonClick, + ) + } +} + +@Composable +private fun termsAnnotatedString(terms: TermsUM) = buildAnnotatedString { + val startText = terms.text.resolveReference() + val linkText = terms.linkText.resolveReference() + + append(startText) + append(" ") + withLink( + link = LinkAnnotation.Clickable( + tag = "CAMPAIGN_TERMS", + linkInteractionListener = { terms.onTermsClick() }, + ), + ) { + withStyle( + SpanStyle( + color = TangemTheme.colors3.text.primary, + textDecoration = TextDecoration.None, + ), + ) { + append(linkText) + } + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_ActivateCampaignFooter_WithTerms() { + TangemThemePreviewRedesign { + ActivateCampaignFooter( + footerUM = CampaignPreviewData.footer, + modifier = Modifier + .background(TangemTheme.colors3.bg.primary) + .padding(16.dp), + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_ActivateCampaignFooter_NoTerms() { + TangemThemePreviewRedesign { + ActivateCampaignFooter( + footerUM = CampaignPreviewData.footer.copy(terms = null), + modifier = Modifier + .background(TangemTheme.colors3.bg.primary) + .padding(16.dp), + ) + } +} +// endregion \ No newline at end of file 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 new file mode 100644 index 0000000000..fa2367418d --- /dev/null +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/AlreadyActivatedCampaignContent.kt @@ -0,0 +1,104 @@ +package com.tangem.features.promobanners.impl.campaigns.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +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.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.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) { + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(TangemTheme.colors3.bg.status.infoSubtle), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = Icons.ic_info_24, + contentDescription = null, + tint = TangemTheme.colors3.icon.status.info, + ) + } + + SpacerH32() + + Text( + text = "You’re already enrolled in Whale Swap Cashback", // TODO localization + style = TangemTheme.typography3.heading.small, + color = TangemTheme.colors3.text.primary, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .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) +@Composable +private fun Preview_AlreadyActivatedCampaignContent() { + TangemThemePreviewRedesign { + Box(modifier = Modifier.background(TangemTheme.colors3.bg.primary)) { + ActivateCampaignContent(um = CampaignPreviewData.alreadyActivated) + } + } +} +// endregion \ No newline at end of file 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 new file mode 100644 index 0000000000..ef064a4a4d --- /dev/null +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/CampaignEnrolledMessageContent.kt @@ -0,0 +1,61 @@ +package com.tangem.features.promobanners.impl.campaigns.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +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.text.style.TextAlign +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.res.TangemTheme +import com.tangem.core.ui.res.generated.icons.Icons +import com.tangem.core.ui.res.generated.icons.ic_success_24 + +@Composable +fun CampaignEnrolledMessageContent(message: TextReference, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(TangemTheme.colors3.bg.status.successSubtle), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = Icons.ic_success_24, + contentDescription = null, + tint = TangemTheme.colors3.icon.status.success, + ) + } + + SpacerH32() + + Text( + text = message.resolveReference(), + style = TangemTheme.typography3.heading.small, + color = TangemTheme.colors3.text.primary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + + SpacerH(48.dp) + } +} \ 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 new file mode 100644 index 0000000000..0aa7391b16 --- /dev/null +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/CampaignPreviewData.kt @@ -0,0 +1,70 @@ +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.extensions.stringReference +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 + +/** + * Shared preview fixtures for the campaign UI `@Preview`s. Not used in production code. + */ +internal object CampaignPreviewData { + + val tokenItem: TokenItemState.Content = TokenItemState.Content( + id = "preview-token", + iconState = CurrencyIconState.CoinIcon( + url = null, + fallbackResId = com.tangem.core.ui.R.drawable.img_polygon_22, + isGrayscale = false, + shouldShowCustomBadge = false, + ), + titleState = TokenItemState.TitleState.Content(text = stringReference("Polygon")), + subtitleState = TokenItemState.SubtitleState.TextContent(value = stringReference("MATIC")), + fiatAmountState = TokenItemState.FiatAmountState.Content(text = "321 $"), + subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "5,412 MATIC"), + onItemClick = {}, + onItemLongClick = {}, + ) + + val selectedAccount: SelectedAccountUM = SelectedAccountUM( + iconState = CurrencyIconState.CryptoPortfolio.Icon( + resId = com.tangem.core.ui.R.drawable.ic_rounded_star_24, + color = Color(color = 0xFF0099FF), + isGrayscale = false, + ), + name = stringReference("Main account"), + ) + + val footer: FooterUM = FooterUM( + label = stringReference("Enroll"), + onPrimaryButtonClick = {}, + terms = TermsUM( + text = stringReference("By enrolling you agree to the"), + linkText = stringReference("Terms & Conditions"), + onTermsClick = {}, + ), + ) + + val activateCampaign: ActivateCampaignUM = ActivateCampaignUM( + 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.", + ), + selectedToken = tokenItem, + selectedAccount = selectedAccount, + isChoosingToken = false, + footerUM = footer, + onChooseTokenDismiss = {}, + onLearnMoreClick = {}, + ) + + val alreadyActivated: CampaignAlreadyActivatedUM = CampaignAlreadyActivatedUM( + selectedToken = tokenItem, + selectedAccount = selectedAccount, + ) +} \ 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 new file mode 100644 index 0000000000..9557228a22 --- /dev/null +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/NotActiveCampaignMessageContent.kt @@ -0,0 +1,70 @@ +package com.tangem.features.promobanners.impl.campaigns.ui + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +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.text.style.TextAlign +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.components.SpacerH8 +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 + +@Composable +fun NotActiveCampaignMessageContent(modifier: Modifier = Modifier) { + Column( + modifier = modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + Box( + modifier = Modifier + .size(80.dp) + .clip(CircleShape) + .background(TangemTheme.colors3.bg.status.warningSubtle), + contentAlignment = Alignment.Center, + ) { + Icon( + modifier = Modifier.size(24.dp), + imageVector = Icons.ic_warning_24, + contentDescription = null, + tint = TangemTheme.colors3.icon.status.warning, + ) + } + + SpacerH32() + + Text( + text = " Campaign not active", // TODO localization + style = TangemTheme.typography3.heading.small, + color = TangemTheme.colors3.text.primary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + + SpacerH8() + + Text( + text = "This campaign no longer exists or has expired", // TODO localization + style = TangemTheme.typography3.subheading.medium, + color = TangemTheme.colors3.text.secondary, + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth(), + ) + + SpacerH(48.dp) + } +} \ No newline at end of file diff --git a/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/PromoCampaignTokenItem.kt b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/PromoCampaignTokenItem.kt new file mode 100644 index 0000000000..df75c8e68b --- /dev/null +++ b/features/promo-banners/impl/src/main/kotlin/com/tangem/features/promobanners/impl/campaigns/ui/PromoCampaignTokenItem.kt @@ -0,0 +1,126 @@ +package com.tangem.features.promobanners.impl.campaigns.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +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.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.tangem.core.ui.components.SpacerH8 +import com.tangem.core.ui.components.SpacerW4 +import com.tangem.core.ui.components.account.AccountCharIcon +import com.tangem.core.ui.components.account.AccountIconSize +import com.tangem.core.ui.components.account.AccountResIcon +import com.tangem.core.ui.components.account.PaymentAccountIcon +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.token.TokenItem +import com.tangem.core.ui.components.token.state.TokenItemState +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreviewRedesign +import com.tangem.features.promobanners.impl.campaigns.entity.SelectedAccountUM + +@Composable +internal fun PromoCampaignTokenItem( + selectedToken: TokenItemState, + selectedAccount: SelectedAccountUM?, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + if (selectedAccount != null) { + Row( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + when (val icon = selectedAccount.iconState) { + is CurrencyIconState.PaymentAccount -> PaymentAccountIcon(size = icon.size) + is CurrencyIconState.CryptoPortfolio.Icon -> AccountResIcon( + resId = icon.resId, + color = icon.color, + size = AccountIconSize.ExtraSmall, + ) + is CurrencyIconState.CryptoPortfolio.Letter -> AccountCharIcon( + char = icon.char.resolveReference().first(), + color = icon.color, + size = AccountIconSize.ExtraSmall, + ) + is CurrencyIconState.CoinIcon, + is CurrencyIconState.CustomTokenIcon, + is CurrencyIconState.Empty, + is CurrencyIconState.FiatIcon, + CurrencyIconState.Loading, + CurrencyIconState.Locked, + is CurrencyIconState.TokenIcon, + -> Unit + } + + SpacerW4() + + Text( + modifier = Modifier + .padding(vertical = 2.dp) + .alignByBaseline(), + text = selectedAccount.name.resolveReference(), + color = TangemTheme.colors.text.primary1, + overflow = TextOverflow.Ellipsis, + maxLines = 1, + style = TangemTheme.typography.caption1, + ) + } + + SpacerH8() + } + + TokenItem( + state = selectedToken, + isBalanceHidden = false, + itemPaddingValues = PaddingValues(horizontal = 16.dp), + ) + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_PromoCampaignTokenItem_WithAccount() { + TangemThemePreviewRedesign { + PromoCampaignTokenItem( + modifier = Modifier + .background(TangemTheme.colors3.bg.primary) + .fillMaxWidth() + .clip(RoundedCornerShape(24.dp)) + .background(TangemTheme.colors.background.primary), + selectedToken = CampaignPreviewData.tokenItem, + selectedAccount = CampaignPreviewData.selectedAccount, + ) + } +} + +@Preview(showBackground = true, widthDp = 360) +@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun Preview_PromoCampaignTokenItem_NoAccount() { + TangemThemePreviewRedesign { + PromoCampaignTokenItem( + modifier = Modifier + .background(TangemTheme.colors3.bg.primary) + .fillMaxWidth() + .clip(RoundedCornerShape(24.dp)) + .background(TangemTheme.colors.background.primary), + selectedToken = CampaignPreviewData.tokenItem, + selectedAccount = null, + ) + } +} +// endregion \ No newline at end of file