Updated on 2026-08-14
This commit is contained in:
parent
3e250db77c
commit
f899b889e6
26 changed files with 383 additions and 371 deletions
|
|
@ -21,6 +21,7 @@ import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||||
import androidx.compose.ui.res.vectorResource
|
import androidx.compose.ui.res.vectorResource
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.components.*
|
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.WindowInsetsZero
|
||||||
import com.tangem.core.ui.utils.toPx
|
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].
|
* Modal bottom sheet with [content], [footer] and optional [title].
|
||||||
*
|
*
|
||||||
|
|
@ -50,6 +54,12 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemModalBottomSheetWi
|
||||||
config: TangemBottomSheetConfig,
|
config: TangemBottomSheetConfig,
|
||||||
containerColor: Color = TangemTheme.colors.background.primary,
|
containerColor: Color = TangemTheme.colors.background.primary,
|
||||||
skipPartiallyExpanded: Boolean = true,
|
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,
|
noinline onBack: (() -> Unit)? = null,
|
||||||
crossinline title: @Composable BoxScope.(T) -> Unit = {},
|
crossinline title: @Composable BoxScope.(T) -> Unit = {},
|
||||||
crossinline content: @Composable (T) -> Unit,
|
crossinline content: @Composable (T) -> Unit,
|
||||||
|
|
@ -65,6 +75,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemModalBottomSheetWi
|
||||||
content = content,
|
content = content,
|
||||||
footer = footer,
|
footer = footer,
|
||||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||||
|
footerHeight = footerHeight,
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
DefaultModalBottomSheetWithFooter<T>(
|
DefaultModalBottomSheetWithFooter<T>(
|
||||||
|
|
@ -75,6 +86,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> TangemModalBottomSheetWi
|
||||||
footer = footer,
|
footer = footer,
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
skipPartiallyExpanded = skipPartiallyExpanded,
|
skipPartiallyExpanded = skipPartiallyExpanded,
|
||||||
|
footerHeight = footerHeight,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -85,6 +97,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> DefaultModalBottomSheetW
|
||||||
config: TangemBottomSheetConfig,
|
config: TangemBottomSheetConfig,
|
||||||
containerColor: Color,
|
containerColor: Color,
|
||||||
skipPartiallyExpanded: Boolean = true,
|
skipPartiallyExpanded: Boolean = true,
|
||||||
|
footerHeight: Dp = DEFAULT_FOOTER_HEIGHT,
|
||||||
noinline onBack: (() -> Unit)? = null,
|
noinline onBack: (() -> Unit)? = null,
|
||||||
crossinline title: @Composable BoxScope.(T) -> Unit,
|
crossinline title: @Composable BoxScope.(T) -> Unit,
|
||||||
crossinline content: @Composable (T) -> Unit,
|
crossinline content: @Composable (T) -> Unit,
|
||||||
|
|
@ -117,6 +130,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> DefaultModalBottomSheetW
|
||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
content = content,
|
content = content,
|
||||||
footer = footer,
|
footer = footer,
|
||||||
|
footerHeight = footerHeight,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -135,6 +149,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewModalBottomSheetW
|
||||||
config: TangemBottomSheetConfig,
|
config: TangemBottomSheetConfig,
|
||||||
containerColor: Color,
|
containerColor: Color,
|
||||||
skipPartiallyExpanded: Boolean = true,
|
skipPartiallyExpanded: Boolean = true,
|
||||||
|
footerHeight: Dp = DEFAULT_FOOTER_HEIGHT,
|
||||||
crossinline title: @Composable BoxScope.(T) -> Unit,
|
crossinline title: @Composable BoxScope.(T) -> Unit,
|
||||||
crossinline content: @Composable (T) -> Unit,
|
crossinline content: @Composable (T) -> Unit,
|
||||||
noinline footer: @Composable (BoxScope.(T) -> Unit)?,
|
noinline footer: @Composable (BoxScope.(T) -> Unit)?,
|
||||||
|
|
@ -150,6 +165,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> PreviewModalBottomSheetW
|
||||||
title = title,
|
title = title,
|
||||||
content = content,
|
content = content,
|
||||||
footer = footer,
|
footer = footer,
|
||||||
|
footerHeight = footerHeight,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -161,6 +177,7 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
|
||||||
sheetState: TangemSheetState,
|
sheetState: TangemSheetState,
|
||||||
containerColor: Color,
|
containerColor: Color,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
|
footerHeight: Dp = DEFAULT_FOOTER_HEIGHT,
|
||||||
noinline onBack: (() -> Unit)? = null,
|
noinline onBack: (() -> Unit)? = null,
|
||||||
crossinline title: @Composable BoxScope.(T) -> Unit,
|
crossinline title: @Composable BoxScope.(T) -> Unit,
|
||||||
crossinline content: @Composable (T) -> Unit,
|
crossinline content: @Composable (T) -> Unit,
|
||||||
|
|
@ -178,11 +195,8 @@ inline fun <reified T : TangemBottomSheetConfigContent> BasicModalBottomSheetWit
|
||||||
|
|
||||||
val isKeyboardOpen by rememberIsKeyboardVisible()
|
val isKeyboardOpen by rememberIsKeyboardVisible()
|
||||||
val buttonHeight by animateDpAsState(
|
val buttonHeight by animateDpAsState(
|
||||||
if (footer != null) {
|
targetValue = if (footer != null) footerHeight else 0.dp,
|
||||||
80.dp
|
label = "FooterHeight",
|
||||||
} else {
|
|
||||||
0.dp
|
|
||||||
},
|
|
||||||
)
|
)
|
||||||
// Offset calculation for keyboard scroll adjustment:
|
// Offset calculation for keyboard scroll adjustment:
|
||||||
// 1) Button height (footer)
|
// 1) Button height (footer)
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ dependencies {
|
||||||
implementation(projects.domain.models)
|
implementation(projects.domain.models)
|
||||||
implementation(projects.domain.appCurrency)
|
implementation(projects.domain.appCurrency)
|
||||||
implementation(projects.domain.account.status)
|
implementation(projects.domain.account.status)
|
||||||
|
implementation(projects.domain.promo)
|
||||||
|
|
||||||
/** Core */
|
/** Core */
|
||||||
api(projects.core.configToggles)
|
api(projects.core.configToggles)
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.component
|
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.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.tangem.core.decompose.context.AppComponentContext
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
import com.tangem.core.decompose.context.child
|
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.TangemBottomSheet
|
||||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
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.Close
|
||||||
import com.tangem.core.ui.ds2.button.TangemButton
|
import com.tangem.core.ui.ds2.button.TangemButton
|
||||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
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.commonfeatures.api.choosetoken.ChooseTokenComponent
|
||||||
import com.tangem.features.promobanners.impl.campaigns.entity.CampaignType
|
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.model.ActivateCampaignsModel
|
||||||
|
|
@ -32,7 +28,8 @@ internal class ActivateCampaignBottomSheetComponent(
|
||||||
chooseTokenComponentFactory: ChooseTokenComponent.Factory,
|
chooseTokenComponentFactory: ChooseTokenComponent.Factory,
|
||||||
private val params: Params,
|
private val params: Params,
|
||||||
val onDismiss: () -> Unit,
|
val onDismiss: () -> Unit,
|
||||||
) : CampaignsModularComponent, AppComponentContext by appComponentContext {
|
val onFooterExtraHeightReady: (Dp) -> Unit,
|
||||||
|
) : ComposableModularContentComponent, AppComponentContext by appComponentContext {
|
||||||
|
|
||||||
private val model: ActivateCampaignsModel = getOrCreateModel(params)
|
private val model: ActivateCampaignsModel = getOrCreateModel(params)
|
||||||
|
|
||||||
|
|
@ -42,7 +39,7 @@ internal class ActivateCampaignBottomSheetComponent(
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
override fun Title() {
|
||||||
TangemTopNavigation(
|
TangemTopNavigation(
|
||||||
windowInsets = WindowInsets(0),
|
windowInsets = WindowInsets(0),
|
||||||
blurBackground = false,
|
blurBackground = false,
|
||||||
|
|
@ -51,10 +48,10 @@ internal class ActivateCampaignBottomSheetComponent(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(bottomSheetState: State<BottomSheetState>, contentPadding: PaddingValues, modifier: Modifier) {
|
override fun Content(modifier: Modifier) {
|
||||||
val state by model.uiState.collectAsStateWithLifecycle()
|
val state by model.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
ActivateCampaignContent(um = state)
|
ActivateCampaignContent(um = state, modifier = modifier)
|
||||||
|
|
||||||
if (state.isChoosingToken) {
|
if (state.isChoosingToken) {
|
||||||
ChooseTokenBottomSheet(state.onChooseTokenDismiss)
|
ChooseTokenBottomSheet(state.onChooseTokenDismiss)
|
||||||
|
|
@ -67,6 +64,7 @@ internal class ActivateCampaignBottomSheetComponent(
|
||||||
|
|
||||||
ActivateCampaignFooter(
|
ActivateCampaignFooter(
|
||||||
footerUM = state.footerUM,
|
footerUM = state.footerUM,
|
||||||
|
onFooterTextHeightReady = onFooterExtraHeightReady,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -90,6 +88,6 @@ internal class ActivateCampaignBottomSheetComponent(
|
||||||
|
|
||||||
interface ActivateCampaignModelCallbacks {
|
interface ActivateCampaignModelCallbacks {
|
||||||
val onActivated: (CampaignType) -> Unit
|
val onActivated: (CampaignType) -> Unit
|
||||||
val onAlreadyActivated: (CampaignType, AppCurrency, Account?, CryptoCurrencyStatus) -> Unit
|
val onAlreadyActivated: (CampaignType) -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,39 +1,33 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.component
|
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.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.runtime.getValue
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|
||||||
import com.tangem.core.decompose.context.AppComponentContext
|
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.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.Close
|
||||||
import com.tangem.core.ui.ds2.button.TangemButton
|
import com.tangem.core.ui.ds2.button.TangemButton
|
||||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
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.core.ui.extensions.stringResourceSafe
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
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.R
|
||||||
import com.tangem.features.promobanners.impl.campaigns.entity.CampaignType
|
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.entity.CampaignTypeToContentConverter
|
||||||
import com.tangem.features.promobanners.impl.campaigns.ui.ActivateCampaignContent
|
import com.tangem.features.promobanners.impl.campaigns.ui.AlreadyActivatedCampaignContent
|
||||||
|
|
||||||
internal class CampaignAlreadyActivatedBottomSheetComponent(
|
internal class CampaignAlreadyActivatedBottomSheetComponent(
|
||||||
appComponentContext: AppComponentContext,
|
appComponentContext: AppComponentContext,
|
||||||
params: Params,
|
params: Params,
|
||||||
val onDismiss: () -> Unit,
|
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
|
@Composable
|
||||||
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
override fun Title() {
|
||||||
TangemTopNavigation(
|
TangemTopNavigation(
|
||||||
windowInsets = WindowInsets(0),
|
windowInsets = WindowInsets(0),
|
||||||
blurBackground = false,
|
blurBackground = false,
|
||||||
|
|
@ -42,10 +36,14 @@ internal class CampaignAlreadyActivatedBottomSheetComponent(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(bottomSheetState: State<BottomSheetState>, contentPadding: PaddingValues, modifier: Modifier) {
|
override fun Content(modifier: Modifier) {
|
||||||
val state by model.uiState.collectAsStateWithLifecycle()
|
AlreadyActivatedCampaignContent(
|
||||||
|
message = resourceReference(
|
||||||
ActivateCampaignContent(um = state)
|
R.string.promo_campaign_already_activated_title,
|
||||||
|
wrappedList(campaignName),
|
||||||
|
),
|
||||||
|
modifier = modifier,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -59,8 +57,5 @@ internal class CampaignAlreadyActivatedBottomSheetComponent(
|
||||||
|
|
||||||
data class Params(
|
data class Params(
|
||||||
val campaignType: CampaignType,
|
val campaignType: CampaignType,
|
||||||
val account: Account?,
|
|
||||||
val appCurrency: AppCurrency,
|
|
||||||
val currency: CryptoCurrencyStatus,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -1,30 +1,31 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.component
|
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.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.tangem.core.ui.components.PrimaryButton
|
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.Close
|
||||||
import com.tangem.core.ui.ds2.button.TangemButton
|
import com.tangem.core.ui.ds2.button.TangemButton
|
||||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
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.stringResourceSafe
|
||||||
|
import com.tangem.core.ui.extensions.wrappedList
|
||||||
import com.tangem.features.promobanners.impl.R
|
import com.tangem.features.promobanners.impl.R
|
||||||
import com.tangem.features.promobanners.impl.campaigns.entity.CampaignType
|
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
|
import com.tangem.features.promobanners.impl.campaigns.ui.CampaignEnrolledMessageContent
|
||||||
|
|
||||||
internal class CampaignEnrolledBottomSheetComponent(
|
internal class CampaignEnrolledBottomSheetComponent(
|
||||||
private val campaignType: CampaignType,
|
params: Params,
|
||||||
private val onDismissRequest: () -> Unit,
|
private val onDismissRequest: () -> Unit,
|
||||||
) : CampaignsModularComponent {
|
) : ComposableModularContentComponent {
|
||||||
|
|
||||||
|
private val campaignName = CampaignTypeToContentConverter().convert(params.campaignType).name
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
override fun Title() {
|
||||||
TangemTopNavigation(
|
TangemTopNavigation(
|
||||||
windowInsets = WindowInsets(0),
|
windowInsets = WindowInsets(0),
|
||||||
blurBackground = false,
|
blurBackground = false,
|
||||||
|
|
@ -33,9 +34,13 @@ internal class CampaignEnrolledBottomSheetComponent(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(bottomSheetState: State<BottomSheetState>, contentPadding: PaddingValues, modifier: Modifier) {
|
override fun Content(modifier: Modifier) {
|
||||||
CampaignEnrolledMessageContent(
|
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() },
|
onClick = { onDismissRequest() },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class Params(
|
||||||
|
val campaignType: CampaignType,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -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()
|
|
||||||
}
|
|
||||||
|
|
@ -2,33 +2,26 @@ package com.tangem.features.promobanners.impl.campaigns.component
|
||||||
|
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.DisposableEffect
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.arkivanov.decompose.ComponentContext
|
import com.arkivanov.decompose.ComponentContext
|
||||||
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
import com.arkivanov.decompose.extensions.compose.subscribeAsState
|
||||||
import com.arkivanov.decompose.router.slot.childSlot
|
import com.arkivanov.decompose.router.slot.childSlot
|
||||||
import com.tangem.core.decompose.context.AppComponentContext
|
import com.tangem.core.decompose.context.AppComponentContext
|
||||||
import com.tangem.core.decompose.context.childByContext
|
import com.tangem.core.decompose.context.childByContext
|
||||||
import com.tangem.core.decompose.model.getOrCreateModel
|
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.TangemBottomSheetConfig
|
||||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
import com.tangem.core.ui.components.bottomsheets.modal.DEFAULT_FOOTER_HEIGHT
|
||||||
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
|
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.core.ui.extensions.rememberLastNonNull
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
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.commonfeatures.api.choosetoken.ChooseTokenComponent
|
||||||
import com.tangem.features.promobanners.api.swapcashback.CampaignsComponent
|
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.component.ActivateCampaignBottomSheetComponent.ActivateCampaignModelCallbacks
|
||||||
|
|
@ -59,41 +52,23 @@ internal class DefaultCampaignsComponent @AssistedInject constructor(
|
||||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||||
val activeChild = bottomSheet.child?.instance
|
val activeChild = bottomSheet.child?.instance
|
||||||
val displayedChild = rememberLastNonNull(activeChild)
|
val displayedChild = rememberLastNonNull(activeChild)
|
||||||
val bottomSheetState = remember { mutableStateOf(BottomSheetState.EXPANDED) }
|
val footerExtraHeight by model.footerExtraHeightState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
TangemModalBottomSheetWithFooter<TangemBottomSheetConfigContent.Empty>(
|
||||||
config = TangemBottomSheetConfig(
|
config = TangemBottomSheetConfig(
|
||||||
isShown = activeChild != null,
|
isShown = activeChild != null,
|
||||||
onDismissRequest = model::onDismiss,
|
onDismissRequest = model::onDismiss,
|
||||||
content = TangemBottomSheetConfigContent.Empty,
|
content = TangemBottomSheetConfigContent.Empty,
|
||||||
),
|
),
|
||||||
|
containerColor = TangemTheme.colors3.bg.primary,
|
||||||
|
footerHeight = DEFAULT_FOOTER_HEIGHT + footerExtraHeight,
|
||||||
onBack = model::onDismiss,
|
onBack = model::onDismiss,
|
||||||
title = {
|
title = {
|
||||||
displayedChild?.Title(bottomSheetState)
|
displayedChild?.Title()
|
||||||
},
|
},
|
||||||
content = {
|
content = {
|
||||||
val bottomInset = LocalTangemBottomSheetContentBottomInset.current
|
Box(modifier = Modifier.animateContentSize()) {
|
||||||
val scrollableSignal = LocalBottomSheetContentScrollable.current
|
displayedChild?.Content(modifier = Modifier)
|
||||||
|
|
||||||
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 = {
|
footer = {
|
||||||
|
|
@ -111,42 +86,37 @@ internal class DefaultCampaignsComponent @AssistedInject constructor(
|
||||||
private fun bottomSheetChild(
|
private fun bottomSheetChild(
|
||||||
config: CampaignsBottomSheetConfig,
|
config: CampaignsBottomSheetConfig,
|
||||||
componentContext: ComponentContext,
|
componentContext: ComponentContext,
|
||||||
): CampaignsModularComponent {
|
): ComposableModularContentComponent {
|
||||||
val context = childByContext(componentContext)
|
val context = childByContext(componentContext)
|
||||||
return when (config) {
|
return when (config) {
|
||||||
CampaignsBottomSheetConfig.NotActive -> NotActiveCampaignBottomSheetComponent(
|
CampaignsBottomSheetConfig.NotActive -> NotActiveCampaignBottomSheetComponent(
|
||||||
onDismissRequest = model::onDismiss,
|
onDismissRequest = model::onDismiss,
|
||||||
)
|
)
|
||||||
is CampaignsBottomSheetConfig.Enrolled -> CampaignEnrolledBottomSheetComponent(
|
is CampaignsBottomSheetConfig.Enrolled -> CampaignEnrolledBottomSheetComponent(
|
||||||
campaignType = config.campaignType,
|
params = CampaignEnrolledBottomSheetComponent.Params(
|
||||||
|
campaignType = config.campaignType,
|
||||||
|
),
|
||||||
onDismissRequest = model::onDismiss,
|
onDismissRequest = model::onDismiss,
|
||||||
)
|
)
|
||||||
is CampaignsBottomSheetConfig.Activate -> ActivateCampaignBottomSheetComponent(
|
is CampaignsBottomSheetConfig.Activate -> ActivateCampaignBottomSheetComponent(
|
||||||
appComponentContext = context,
|
appComponentContext = context,
|
||||||
chooseTokenComponentFactory = chooseTokenComponentFactory,
|
chooseTokenComponentFactory = chooseTokenComponentFactory,
|
||||||
onDismiss = model::onDismiss,
|
onDismiss = model::onDismiss,
|
||||||
|
onFooterExtraHeightReady = model::onFooterExtraHeightReady,
|
||||||
params = ActivateCampaignBottomSheetComponent.Params(
|
params = ActivateCampaignBottomSheetComponent.Params(
|
||||||
campaignType = config.campaignType,
|
campaignType = config.campaignType,
|
||||||
modelCallbacks = object : ActivateCampaignModelCallbacks {
|
modelCallbacks = object : ActivateCampaignModelCallbacks {
|
||||||
override val onActivated: (CampaignType) -> Unit = model::onActivated
|
override val onActivated: (CampaignType) -> Unit = model::onActivated
|
||||||
override val onAlreadyActivated: (
|
override val onAlreadyActivated: (CampaignType) -> Unit = model::onAlreadyActivated
|
||||||
CampaignType,
|
|
||||||
AppCurrency,
|
|
||||||
Account?,
|
|
||||||
CryptoCurrencyStatus,
|
|
||||||
) -> Unit = model::onAlreadyActivated
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
is CampaignsBottomSheetConfig.AlreadyActivated -> CampaignAlreadyActivatedBottomSheetComponent(
|
is CampaignsBottomSheetConfig.AlreadyActivated -> CampaignAlreadyActivatedBottomSheetComponent(
|
||||||
appComponentContext = context,
|
appComponentContext = context,
|
||||||
onDismiss = model::onDismiss,
|
|
||||||
params = CampaignAlreadyActivatedBottomSheetComponent.Params(
|
params = CampaignAlreadyActivatedBottomSheetComponent.Params(
|
||||||
campaignType = config.campaignType,
|
campaignType = config.campaignType,
|
||||||
appCurrency = config.appCurrency,
|
|
||||||
account = config.account,
|
|
||||||
currency = config.currency,
|
|
||||||
),
|
),
|
||||||
|
onDismiss = model::onDismiss,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.component
|
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.WindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.State
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.tangem.core.ui.components.PrimaryButton
|
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.Close
|
||||||
import com.tangem.core.ui.ds2.button.TangemButton
|
import com.tangem.core.ui.ds2.button.TangemButton
|
||||||
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
import com.tangem.core.ui.ds2.topnavigation.TangemTopNavigation
|
||||||
|
|
@ -17,10 +15,10 @@ import com.tangem.features.promobanners.impl.campaigns.ui.NotActiveCampaignMessa
|
||||||
|
|
||||||
internal class NotActiveCampaignBottomSheetComponent(
|
internal class NotActiveCampaignBottomSheetComponent(
|
||||||
private val onDismissRequest: () -> Unit,
|
private val onDismissRequest: () -> Unit,
|
||||||
) : CampaignsModularComponent {
|
) : ComposableModularContentComponent {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Title(bottomSheetState: State<BottomSheetState>) {
|
override fun Title() {
|
||||||
TangemTopNavigation(
|
TangemTopNavigation(
|
||||||
windowInsets = WindowInsets(0),
|
windowInsets = WindowInsets(0),
|
||||||
blurBackground = false,
|
blurBackground = false,
|
||||||
|
|
@ -29,8 +27,8 @@ internal class NotActiveCampaignBottomSheetComponent(
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
override fun Content(bottomSheetState: State<BottomSheetState>, contentPadding: PaddingValues, modifier: Modifier) {
|
override fun Content(modifier: Modifier) {
|
||||||
NotActiveCampaignMessageContent()
|
NotActiveCampaignMessageContent(modifier = modifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.deeplink
|
package com.tangem.features.promobanners.impl.campaigns.deeplink
|
||||||
|
|
||||||
import com.tangem.common.routing.deeplink.DeeplinkConst
|
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.deeplink.CampaignsDeepLinkHandler
|
||||||
import com.tangem.features.promobanners.api.toggles.PromoBannersFeatureToggles
|
import com.tangem.features.promobanners.api.toggles.PromoBannersFeatureToggles
|
||||||
import com.tangem.features.promobanners.impl.campaigns.service.CampaignsService
|
import com.tangem.features.promobanners.impl.campaigns.service.CampaignsService
|
||||||
|
|
@ -18,13 +19,24 @@ import dagger.assisted.AssistedInject
|
||||||
internal class DefaultCampaignsDeepLinkHandler @AssistedInject constructor(
|
internal class DefaultCampaignsDeepLinkHandler @AssistedInject constructor(
|
||||||
@Assisted private val queryParams: Map<String, String>,
|
@Assisted private val queryParams: Map<String, String>,
|
||||||
campaignsService: CampaignsService,
|
campaignsService: CampaignsService,
|
||||||
private val promoBannersFeatureToggles: PromoBannersFeatureToggles,
|
getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
|
||||||
|
promoBannersFeatureToggles: PromoBannersFeatureToggles,
|
||||||
) : CampaignsDeepLinkHandler {
|
) : CampaignsDeepLinkHandler {
|
||||||
|
|
||||||
init {
|
init {
|
||||||
if (promoBannersFeatureToggles.isCampaignsToggleEnabled) {
|
if (promoBannersFeatureToggles.isCampaignsToggleEnabled) {
|
||||||
val campaignId = queryParams[DeeplinkConst.CAMPAIGN_ID_KEY].orEmpty()
|
// It is okay here, we are navigating from outside, and there is no other way to getting UserWallet
|
||||||
campaignsService.show(campaignId)
|
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 {
|
} else {
|
||||||
TangemLogger.i("Campaigns feature is disabled")
|
TangemLogger.i("Campaigns feature is disabled")
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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.component.DefaultCampaignsComponent
|
||||||
import com.tangem.features.promobanners.impl.campaigns.deeplink.DefaultCampaignsDeepLinkHandler
|
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.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.model.CampaignsModel
|
||||||
import com.tangem.features.promobanners.impl.campaigns.service.CampaignsService
|
import com.tangem.features.promobanners.impl.campaigns.service.CampaignsService
|
||||||
import com.tangem.features.promobanners.impl.campaigns.service.DefaultCampaignsService
|
import com.tangem.features.promobanners.impl.campaigns.service.DefaultCampaignsService
|
||||||
|
|
@ -51,9 +50,4 @@ internal interface CampaignsModelModule {
|
||||||
@IntoMap
|
@IntoMap
|
||||||
@ClassKey(ActivateCampaignsModel::class)
|
@ClassKey(ActivateCampaignsModel::class)
|
||||||
fun bindCampaignActivateModel(model: ActivateCampaignsModel): Model
|
fun bindCampaignActivateModel(model: ActivateCampaignsModel): Model
|
||||||
|
|
||||||
@Binds
|
|
||||||
@IntoMap
|
|
||||||
@ClassKey(CampaignAlreadyActivatedModel::class)
|
|
||||||
fun bindCampaignAlreadyActivatedModel(model: CampaignAlreadyActivatedModel): Model
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@ package com.tangem.features.promobanners.impl.campaigns.entity
|
||||||
import androidx.compose.runtime.Immutable
|
import androidx.compose.runtime.Immutable
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
import com.tangem.core.ui.components.token.state.TokenItemState
|
||||||
|
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -19,6 +20,7 @@ import com.tangem.core.ui.extensions.TextReference
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
internal data class ActivateCampaignUM(
|
internal data class ActivateCampaignUM(
|
||||||
|
val logo: TangemIconUM,
|
||||||
val title: TextReference,
|
val title: TextReference,
|
||||||
val description: TextReference,
|
val description: TextReference,
|
||||||
val selectedToken: TokenItemState?,
|
val selectedToken: TokenItemState?,
|
||||||
|
|
@ -27,6 +29,7 @@ internal data class ActivateCampaignUM(
|
||||||
val footerUM: FooterUM,
|
val footerUM: FooterUM,
|
||||||
val onChooseTokenDismiss: () -> Unit,
|
val onChooseTokenDismiss: () -> Unit,
|
||||||
val onLearnMoreClick: () -> Unit,
|
val onLearnMoreClick: () -> Unit,
|
||||||
|
val onChooseTokenClick: () -> Unit,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Immutable
|
@Immutable
|
||||||
|
|
|
||||||
|
|
@ -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?,
|
|
||||||
)
|
|
||||||
|
|
@ -1,7 +1,39 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.entity
|
package com.tangem.features.promobanners.impl.campaigns.entity
|
||||||
|
|
||||||
internal fun CampaignType.campaignName(): String = when (this) {
|
import com.tangem.core.ui.ds.image.TangemIconUM
|
||||||
// TODO([REDACTED_TASK_KEY]): source real campaign display names.
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
is CampaignType.ReactivationCashback -> "Reactivation Cashback"
|
import com.tangem.domain.promo.models.PromoCampaignId
|
||||||
is CampaignType.WhaleSwapCashback -> "Whale Swap Cashback"
|
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<CampaignType, CampaignContent> {
|
||||||
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
|
|
@ -1,8 +1,5 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.entity
|
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
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
|
|
@ -24,8 +21,5 @@ internal sealed class CampaignsBottomSheetConfig {
|
||||||
@Serializable
|
@Serializable
|
||||||
data class AlreadyActivated(
|
data class AlreadyActivated(
|
||||||
val campaignType: CampaignType,
|
val campaignType: CampaignType,
|
||||||
val appCurrency: AppCurrency,
|
|
||||||
val account: Account?,
|
|
||||||
val currency: CryptoCurrencyStatus,
|
|
||||||
) : CampaignsBottomSheetConfig()
|
) : CampaignsBottomSheetConfig()
|
||||||
}
|
}
|
||||||
|
|
@ -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.AccountIconItemStateConverter
|
||||||
import com.tangem.common.ui.account.toUM
|
import com.tangem.common.ui.account.toUM
|
||||||
import com.tangem.common.ui.tokens.TokenItemStateConverter
|
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.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.core.decompose.model.ParamsContainer
|
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.navigation.url.UrlOpener
|
||||||
import com.tangem.core.ui.components.account.AccountIconSize
|
import com.tangem.core.ui.components.account.AccountIconSize
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||||
import com.tangem.core.ui.extensions.resourceReference
|
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.account.status.usecase.IsAccountsModeEnabledUseCase
|
||||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.domain.appcurrency.model.AppCurrency
|
||||||
import com.tangem.domain.models.account.Account
|
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.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.ChooseTokenBridge
|
||||||
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult
|
import com.tangem.features.commonfeatures.api.choosetoken.ChooseTokenResult
|
||||||
import com.tangem.features.promobanners.impl.R
|
import com.tangem.features.promobanners.impl.R
|
||||||
import com.tangem.features.promobanners.impl.campaigns.component.ActivateCampaignBottomSheetComponent
|
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.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.FooterUM
|
||||||
import com.tangem.features.promobanners.impl.campaigns.entity.SelectedAccountUM
|
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.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.coroutines.CoroutineDispatcherProvider
|
||||||
|
import com.tangem.utils.logging.TangemLogger
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
|
@ -35,6 +46,7 @@ import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@Suppress("LongParameterList")
|
||||||
@ModelScoped
|
@ModelScoped
|
||||||
internal class ActivateCampaignsModel @Inject constructor(
|
internal class ActivateCampaignsModel @Inject constructor(
|
||||||
paramsContainer: ParamsContainer,
|
paramsContainer: ParamsContainer,
|
||||||
|
|
@ -42,17 +54,20 @@ internal class ActivateCampaignsModel @Inject constructor(
|
||||||
chooseTokenBridgeFactory: ChooseTokenBridge.Factory,
|
chooseTokenBridgeFactory: ChooseTokenBridge.Factory,
|
||||||
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||||
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
|
||||||
|
private val enrollPromoCampaignUseCase: EnrollPromoCampaignUseCase,
|
||||||
private val urlOpener: UrlOpener,
|
private val urlOpener: UrlOpener,
|
||||||
|
@GlobalUiMessageSender private val messageSender: UiMessageSender,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
private val params = paramsContainer.require<ActivateCampaignBottomSheetComponent.Params>()
|
private val params = paramsContainer.require<ActivateCampaignBottomSheetComponent.Params>()
|
||||||
|
private val campaignType = params.campaignType
|
||||||
private val accountIconConverter = AccountIconItemStateConverter(size = AccountIconSize.ExtraSmall)
|
private val accountIconConverter = AccountIconItemStateConverter(size = AccountIconSize.ExtraSmall)
|
||||||
private var appCurrency: AppCurrency = AppCurrency.Default
|
private var appCurrency: AppCurrency = AppCurrency.Default
|
||||||
private var selectedAccount: Account? = null
|
private val campaignId: PromoCampaignId = params.campaignType.toPromoCampaignId()
|
||||||
private var selectedCurrency: CryptoCurrencyStatus? = null
|
private val campaignContent = CampaignTypeToContentConverter().convert(campaignType)
|
||||||
|
|
||||||
val uiState: StateFlow<ActivateCampaignUM>
|
val uiState: StateFlow<ActivateCampaignUM>
|
||||||
field = MutableStateFlow(getInitialState())
|
field = MutableStateFlow(buildInitialModel())
|
||||||
|
|
||||||
val bridge: ChooseTokenBridge = chooseTokenBridgeFactory.create(
|
val bridge: ChooseTokenBridge = chooseTokenBridgeFactory.create(
|
||||||
modelScope = modelScope,
|
modelScope = modelScope,
|
||||||
|
|
@ -78,7 +93,26 @@ internal class ActivateCampaignsModel @Inject constructor(
|
||||||
.launchIn(modelScope)
|
.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) }
|
uiState.update { it.copy(isChoosingToken = true) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -86,42 +120,45 @@ internal class ActivateCampaignsModel @Inject constructor(
|
||||||
uiState.update { it.copy(isChoosingToken = false) }
|
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 {
|
modelScope.launch {
|
||||||
// TODO([REDACTED_TASK_KEY]): call the real campaign enrollment use case; its result decides the next sheet.
|
enrollPromoCampaignUseCase.invoke(
|
||||||
// Success -> the "enrolled" sheet; "already activated" error -> hand the chosen token/account
|
campaign = campaignId,
|
||||||
// over to the "already activated" sheet.
|
tokenReward = TokenReward(
|
||||||
if (enrollInCampaign()) {
|
tokenAddress = token.contractAddress,
|
||||||
params.modelCallbacks.onActivated(params.campaignType)
|
networkId = token.network.rawId,
|
||||||
} else {
|
),
|
||||||
selectedCurrency?.let {
|
walletIds = listOf(selectedWalletId),
|
||||||
params.modelCallbacks.onAlreadyActivated(params.campaignType, appCurrency, selectedAccount, it)
|
).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 handleEnrollResponse(enrollResult: EnrollResult) {
|
||||||
private fun enrollInCampaign(): Boolean {
|
when (enrollResult) {
|
||||||
// TODO([REDACTED_TASK_KEY]): replace with the real enrollment use case call; `true` = enrolled, `false` = already active.
|
is EnrollResult.AlreadyEnrolled -> params.modelCallbacks.onAlreadyActivated(campaignType)
|
||||||
return true
|
is EnrollResult.Success -> params.modelCallbacks.onActivated(campaignType)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onTermsClick() {
|
private fun onTermsClick() {
|
||||||
urlOpener.openUrl(CAMPAIGN_TERMS_URL)
|
urlOpener.openUrl(campaignContent.termsUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onLearnMoreClick() {
|
private fun onLearnMoreClick() {
|
||||||
urlOpener.openUrl(CAMPAIGN_TERMS_URL)
|
urlOpener.openUrl(campaignContent.learnMoreUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onTokenChosen(result: ChooseTokenResult) {
|
private fun onTokenChosen(result: ChooseTokenResult) {
|
||||||
modelScope.launch {
|
modelScope.launch {
|
||||||
val selectedAccountUM = if (isAccountsModeEnabledUseCase.invokeSync()) {
|
val selectedAccountUM = if (isAccountsModeEnabledUseCase.invokeSync()) {
|
||||||
val account = result.account.account
|
when (val account = result.account.account) {
|
||||||
selectedAccount = account
|
|
||||||
|
|
||||||
when (account) {
|
|
||||||
is Account.CryptoPortfolio -> SelectedAccountUM(
|
is Account.CryptoPortfolio -> SelectedAccountUM(
|
||||||
iconState = accountIconConverter.convert(account),
|
iconState = accountIconConverter.convert(account),
|
||||||
name = account.accountName.toUM().value,
|
name = account.accountName.toUM().value,
|
||||||
|
|
@ -136,7 +173,6 @@ internal class ActivateCampaignsModel @Inject constructor(
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedCurrency = result.currency
|
|
||||||
val tokenItem = TokenItemStateConverter(appCurrency = appCurrency).convert(result.currency)
|
val tokenItem = TokenItemStateConverter(appCurrency = appCurrency).convert(result.currency)
|
||||||
|
|
||||||
uiState.update { state ->
|
uiState.update { state ->
|
||||||
|
|
@ -145,12 +181,19 @@ internal class ActivateCampaignsModel @Inject constructor(
|
||||||
selectedToken = tokenItem,
|
selectedToken = tokenItem,
|
||||||
selectedAccount = selectedAccountUM,
|
selectedAccount = selectedAccountUM,
|
||||||
footerUM = FooterUM(
|
footerUM = FooterUM(
|
||||||
label = stringReference("Enroll"),
|
label = resourceReference(R.string.promo_campaign_enroll),
|
||||||
onPrimaryButtonClick = ::onEnrollClick,
|
onPrimaryButtonClick = {
|
||||||
|
onEnrollClick(
|
||||||
|
selectedWalletId = result.walletId,
|
||||||
|
selectedCurrencyStatus = result.currency,
|
||||||
|
)
|
||||||
|
},
|
||||||
terms = TermsUM(
|
terms = TermsUM(
|
||||||
// TODO([REDACTED_TASK_KEY]): localize
|
text = resourceReference(R.string.promo_campaign_terms_agreement_android),
|
||||||
text = stringReference("I agree with"),
|
linkText = resourceReference(
|
||||||
linkText = stringReference("${params.campaignType.campaignName()} Terms"),
|
R.string.promo_campaign_terms_link,
|
||||||
|
wrappedList(campaignContent.name),
|
||||||
|
),
|
||||||
onTermsClick = ::onTermsClick,
|
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/"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -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<CampaignAlreadyActivatedBottomSheetComponent.Params>()
|
|
||||||
private val accountIconConverter = AccountIconItemStateConverter(size = AccountIconSize.ExtraSmall)
|
|
||||||
|
|
||||||
val uiState: StateFlow<CampaignAlreadyActivatedUM>
|
|
||||||
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,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
|
@ -1,20 +1,32 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.model
|
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.SlotNavigation
|
||||||
import com.arkivanov.decompose.router.slot.activate
|
import com.arkivanov.decompose.router.slot.activate
|
||||||
import com.arkivanov.decompose.router.slot.dismiss
|
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.di.ModelScoped
|
||||||
import com.tangem.core.decompose.model.Model
|
import com.tangem.core.decompose.model.Model
|
||||||
import com.tangem.domain.appcurrency.model.AppCurrency
|
import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
import com.tangem.domain.models.account.Account
|
import com.tangem.core.ui.extensions.resourceReference
|
||||||
import com.tangem.domain.models.currency.CryptoCurrencyStatus
|
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.converters.CampaignIdConverter
|
||||||
import com.tangem.features.promobanners.impl.campaigns.entity.CampaignsBottomSheetConfig
|
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.CampaignType
|
||||||
|
import com.tangem.features.promobanners.impl.campaigns.entity.toPromoCampaignId
|
||||||
import com.tangem.features.promobanners.impl.campaigns.service.CampaignsService
|
import com.tangem.features.promobanners.impl.campaigns.service.CampaignsService
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
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.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ModelScoped
|
@ModelScoped
|
||||||
|
|
@ -22,35 +34,62 @@ internal class CampaignsModel @Inject constructor(
|
||||||
override val dispatchers: CoroutineDispatcherProvider,
|
override val dispatchers: CoroutineDispatcherProvider,
|
||||||
private val campaignIdConverter: CampaignIdConverter,
|
private val campaignIdConverter: CampaignIdConverter,
|
||||||
campaignsService: CampaignsService,
|
campaignsService: CampaignsService,
|
||||||
|
private val getPromoCampaignStateUseCase: GetPromoCampaignStateUseCase,
|
||||||
|
@GlobalUiMessageSender private val messageSender: UiMessageSender,
|
||||||
) : Model() {
|
) : Model() {
|
||||||
|
|
||||||
val bottomSheetNavigation: SlotNavigation<CampaignsBottomSheetConfig> = SlotNavigation()
|
val bottomSheetNavigation: SlotNavigation<CampaignsBottomSheetConfig> = SlotNavigation()
|
||||||
|
|
||||||
|
val footerExtraHeightState: StateFlow<Dp>
|
||||||
|
field = MutableStateFlow(0.dp)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
campaignsService.campaignFlow
|
campaignsService.campaignFlow
|
||||||
.onEach { campaignId -> resolveStartNavigation(campaignIdConverter.convert(campaignId)) }
|
.onEach { request ->
|
||||||
|
resolveStartNavigation(
|
||||||
|
campaignType = campaignIdConverter.convert(request.campaignId),
|
||||||
|
userWalletId = request.userWalletId,
|
||||||
|
)
|
||||||
|
}
|
||||||
.launchIn(modelScope)
|
.launchIn(modelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("UnusedPrivateMember")
|
private fun resolveStartNavigation(campaignType: CampaignType?, userWalletId: UserWalletId) {
|
||||||
private fun resolveStartNavigation(campaignType: CampaignType?) {
|
modelScope.launch {
|
||||||
val config = when (campaignType) {
|
val config = if (campaignType == null) {
|
||||||
is CampaignType.ReactivationCashback -> checkReactivationCashbackCampaignState(campaignType)
|
CampaignsBottomSheetConfig.NotActive
|
||||||
is CampaignType.WhaleSwapCashback -> checkWhaleSwapCashbackCampaignState(campaignType)
|
} else {
|
||||||
null -> CampaignsBottomSheetConfig.NotActive
|
checkCampaignState(campaignType, userWalletId)
|
||||||
|
}
|
||||||
|
|
||||||
|
config?.let { bottomSheetNavigation.activate(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
bottomSheetNavigation.activate(config)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
private suspend fun checkCampaignState(
|
||||||
private fun checkReactivationCashbackCampaignState(campaignType: CampaignType): CampaignsBottomSheetConfig {
|
campaignType: CampaignType,
|
||||||
return CampaignsBottomSheetConfig.Activate(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
|
fun onFooterExtraHeightReady(height: Dp) {
|
||||||
private fun checkWhaleSwapCashbackCampaignState(campaignType: CampaignType): CampaignsBottomSheetConfig {
|
footerExtraHeightState.value = height
|
||||||
return CampaignsBottomSheetConfig.Activate(campaignType)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onDismiss() {
|
fun onDismiss() {
|
||||||
|
|
@ -58,22 +97,12 @@ internal class CampaignsModel @Inject constructor(
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onActivated(campaignType: CampaignType) {
|
fun onActivated(campaignType: CampaignType) {
|
||||||
|
footerExtraHeightState.value = 0.dp
|
||||||
bottomSheetNavigation.activate(CampaignsBottomSheetConfig.Enrolled(campaignType))
|
bottomSheetNavigation.activate(CampaignsBottomSheetConfig.Enrolled(campaignType))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onAlreadyActivated(
|
fun onAlreadyActivated(campaignType: CampaignType) {
|
||||||
campaignType: CampaignType,
|
footerExtraHeightState.value = 0.dp
|
||||||
appCurrency: AppCurrency,
|
bottomSheetNavigation.activate(CampaignsBottomSheetConfig.AlreadyActivated(campaignType = campaignType))
|
||||||
account: Account?,
|
|
||||||
currency: CryptoCurrencyStatus,
|
|
||||||
) {
|
|
||||||
bottomSheetNavigation.activate(
|
|
||||||
CampaignsBottomSheetConfig.AlreadyActivated(
|
|
||||||
campaignType = campaignType,
|
|
||||||
appCurrency = appCurrency,
|
|
||||||
account = account,
|
|
||||||
currency = currency,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.service
|
package com.tangem.features.promobanners.impl.campaigns.service
|
||||||
|
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -9,9 +10,15 @@ import kotlinx.coroutines.flow.Flow
|
||||||
*/
|
*/
|
||||||
internal interface CampaignsService {
|
internal interface CampaignsService {
|
||||||
|
|
||||||
/** Emits the campaignId requested via [show]. */
|
/** Emits the campaign requested via [show]. */
|
||||||
val campaignFlow: Flow<String>
|
val campaignFlow: Flow<CampaignRequest>
|
||||||
|
|
||||||
/** Requests showing the campaign identified by [campaignId]. */
|
/** Requests showing the campaign identified by [campaignId] for the given [userWalletId]. */
|
||||||
fun show(campaignId: String)
|
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,
|
||||||
|
)
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.service
|
package com.tangem.features.promobanners.impl.campaigns.service
|
||||||
|
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import kotlinx.coroutines.channels.Channel
|
import kotlinx.coroutines.channels.Channel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
|
|
@ -9,10 +10,10 @@ import javax.inject.Singleton
|
||||||
@Singleton
|
@Singleton
|
||||||
internal class DefaultCampaignsService @Inject constructor() : CampaignsService {
|
internal class DefaultCampaignsService @Inject constructor() : CampaignsService {
|
||||||
|
|
||||||
private val _campaignFlow: Channel<String> = Channel(Channel.BUFFERED)
|
private val _campaignFlow: Channel<CampaignRequest> = Channel(Channel.BUFFERED)
|
||||||
override val campaignFlow: Flow<String> = _campaignFlow.receiveAsFlow()
|
override val campaignFlow: Flow<CampaignRequest> = _campaignFlow.receiveAsFlow()
|
||||||
|
|
||||||
override fun show(campaignId: String) {
|
override fun show(campaignId: String, userWalletId: UserWalletId) {
|
||||||
_campaignFlow.trySend(campaignId)
|
_campaignFlow.trySend(CampaignRequest(campaignId = campaignId, userWalletId = userWalletId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.ui
|
package com.tangem.features.promobanners.impl.campaigns.ui
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.Image
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
|
|
@ -12,7 +11,6 @@ import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.res.painterResource
|
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
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.SpacerH32
|
||||||
import com.tangem.core.ui.components.SpacerH8
|
import com.tangem.core.ui.components.SpacerH8
|
||||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
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.resolveReference
|
||||||
|
import com.tangem.core.ui.extensions.stringResourceSafe
|
||||||
import com.tangem.core.ui.res.TangemTheme
|
import com.tangem.core.ui.res.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||||
import com.tangem.features.promobanners.impl.R
|
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
|
import com.tangem.features.promobanners.impl.campaigns.entity.SelectedAccountUM
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ActivateCampaignContent(um: ActivateCampaignUM) {
|
internal fun ActivateCampaignContent(um: ActivateCampaignUM, modifier: Modifier = Modifier) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
Image(
|
TangemIcon(
|
||||||
// TODO([REDACTED_TASK_KEY]): replace placeholder with the real campaign illustration.
|
tangemIconUM = um.logo,
|
||||||
painter = painterResource(R.drawable.ill_businessman_3d),
|
|
||||||
contentDescription = null,
|
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.size(80.dp)
|
.size(80.dp)
|
||||||
.clip(CircleShape),
|
.clip(CircleShape),
|
||||||
)
|
)
|
||||||
|
|
||||||
SpacerH32()
|
SpacerH32()
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
|
|
@ -66,18 +65,18 @@ internal fun ActivateCampaignContent(um: ActivateCampaignUM) {
|
||||||
SpacerH12()
|
SpacerH12()
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
// TODO([REDACTED_TASK_KEY]): localize
|
text = stringResourceSafe(R.string.common_learn_more),
|
||||||
text = "Learn more",
|
|
||||||
style = TangemTheme.typography3.caption.medium,
|
style = TangemTheme.typography3.caption.medium,
|
||||||
color = TangemTheme.colors3.text.primary,
|
color = TangemTheme.colors3.text.primary,
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.align(Alignment.Start)
|
||||||
.clickable(onClick = um.onLearnMoreClick),
|
.clickable(onClick = um.onLearnMoreClick),
|
||||||
)
|
)
|
||||||
|
|
||||||
SelectedTokenContent(
|
SelectedTokenContent(
|
||||||
selectedToken = um.selectedToken,
|
selectedToken = um.selectedToken,
|
||||||
selectedAccount = um.selectedAccount,
|
selectedAccount = um.selectedAccount,
|
||||||
|
onChooseTokenClick = um.onChooseTokenClick,
|
||||||
)
|
)
|
||||||
|
|
||||||
SpacerH32()
|
SpacerH32()
|
||||||
|
|
@ -85,12 +84,16 @@ internal fun ActivateCampaignContent(um: ActivateCampaignUM) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun SelectedTokenContent(selectedToken: TokenItemState?, selectedAccount: SelectedAccountUM?) {
|
private fun SelectedTokenContent(
|
||||||
|
selectedToken: TokenItemState?,
|
||||||
|
selectedAccount: SelectedAccountUM?,
|
||||||
|
onChooseTokenClick: () -> Unit,
|
||||||
|
) {
|
||||||
if (selectedToken != null) {
|
if (selectedToken != null) {
|
||||||
SpacerH24()
|
SpacerH24()
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "Select cashback account", // TODO([REDACTED_TASK_KEY]): localize
|
text = stringResourceSafe(R.string.promo_campaign_select_cashback_account),
|
||||||
style = TangemTheme.typography.subtitle1,
|
style = TangemTheme.typography.subtitle1,
|
||||||
color = TangemTheme.colors.text.primary1,
|
color = TangemTheme.colors.text.primary1,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|
@ -102,7 +105,10 @@ private fun SelectedTokenContent(selectedToken: TokenItemState?, selectedAccount
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clip(RoundedCornerShape(24.dp))
|
.clip(RoundedCornerShape(24.dp))
|
||||||
.background(TangemTheme.colors.background.primary),
|
.background(TangemTheme.colors3.bg.tertiary)
|
||||||
|
.clickable {
|
||||||
|
onChooseTokenClick.invoke()
|
||||||
|
},
|
||||||
selectedToken = selectedToken,
|
selectedToken = selectedToken,
|
||||||
selectedAccount = selectedAccount,
|
selectedAccount = selectedAccount,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,8 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
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.LinkAnnotation
|
||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.buildAnnotatedString
|
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.withLink
|
||||||
import androidx.compose.ui.text.withStyle
|
import androidx.compose.ui.text.withStyle
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.Dp
|
||||||
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.PrimaryButton
|
||||||
import com.tangem.core.ui.components.SpacerH12
|
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
|
import com.tangem.features.promobanners.impl.campaigns.entity.TermsUM
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun ActivateCampaignFooter(footerUM: FooterUM, modifier: Modifier = Modifier) {
|
internal fun ActivateCampaignFooter(
|
||||||
|
footerUM: FooterUM,
|
||||||
|
onFooterTextHeightReady: (Dp) -> Unit,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
Column(modifier = modifier) {
|
Column(modifier = modifier) {
|
||||||
val terms = footerUM.terms
|
val terms = footerUM.terms
|
||||||
|
|
||||||
if (terms != null) {
|
if (terms != null) {
|
||||||
|
val density = LocalDensity.current
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = termsAnnotatedString(terms),
|
text = termsAnnotatedString(terms),
|
||||||
style = TangemTheme.typography.caption2,
|
style = TangemTheme.typography.caption2,
|
||||||
color = TangemTheme.colors.text.secondary,
|
color = TangemTheme.colors.text.secondary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.onSizeChanged {
|
||||||
|
val termsBlockHeight = with(density) { it.height.toDp() } + 12.dp
|
||||||
|
onFooterTextHeightReady.invoke(termsBlockHeight)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
SpacerH12()
|
SpacerH12()
|
||||||
|
|
@ -85,6 +99,7 @@ private fun Preview_ActivateCampaignFooter_WithTerms() {
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(TangemTheme.colors3.bg.primary)
|
.background(TangemTheme.colors3.bg.primary)
|
||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
|
onFooterTextHeightReady = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -99,6 +114,7 @@ private fun Preview_ActivateCampaignFooter_NoTerms() {
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(TangemTheme.colors3.bg.primary)
|
.background(TangemTheme.colors3.bg.primary)
|
||||||
.padding(16.dp),
|
.padding(16.dp),
|
||||||
|
onFooterTextHeightReady = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,21 +13,19 @@ import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
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.SpacerH12
|
|
||||||
import com.tangem.core.ui.components.SpacerH24
|
|
||||||
import com.tangem.core.ui.components.SpacerH32
|
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.TangemTheme
|
||||||
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
import com.tangem.core.ui.res.TangemThemePreviewRedesign
|
||||||
import com.tangem.core.ui.res.generated.icons.Icons
|
import com.tangem.core.ui.res.generated.icons.Icons
|
||||||
import com.tangem.core.ui.res.generated.icons.ic_info_24
|
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
|
@Composable
|
||||||
internal fun ActivateCampaignContent(um: CampaignAlreadyActivatedUM) {
|
internal fun AlreadyActivatedCampaignContent(message: TextReference, modifier: Modifier = Modifier) {
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = modifier.fillMaxWidth(),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
) {
|
) {
|
||||||
Box(
|
Box(
|
||||||
|
|
@ -48,7 +46,7 @@ internal fun ActivateCampaignContent(um: CampaignAlreadyActivatedUM) {
|
||||||
SpacerH32()
|
SpacerH32()
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = "You’re already enrolled in Whale Swap Cashback", // TODO localization
|
text = message.resolveReference(),
|
||||||
style = TangemTheme.typography3.heading.small,
|
style = TangemTheme.typography3.heading.small,
|
||||||
color = TangemTheme.colors3.text.primary,
|
color = TangemTheme.colors3.text.primary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
|
@ -57,39 +55,10 @@ internal fun ActivateCampaignContent(um: CampaignAlreadyActivatedUM) {
|
||||||
.padding(horizontal = 16.dp),
|
.padding(horizontal = 16.dp),
|
||||||
)
|
)
|
||||||
|
|
||||||
SpacerH12()
|
|
||||||
|
|
||||||
SelectedTokenContent(
|
|
||||||
selectedToken = um.selectedToken,
|
|
||||||
selectedAccount = um.selectedAccount,
|
|
||||||
)
|
|
||||||
|
|
||||||
SpacerH32()
|
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
|
// region Preview
|
||||||
@Preview(showBackground = true, widthDp = 360)
|
@Preview(showBackground = true, widthDp = 360)
|
||||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
@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() {
|
private fun Preview_AlreadyActivatedCampaignContent() {
|
||||||
TangemThemePreviewRedesign {
|
TangemThemePreviewRedesign {
|
||||||
Box(modifier = Modifier.background(TangemTheme.colors3.bg.primary)) {
|
Box(modifier = Modifier.background(TangemTheme.colors3.bg.primary)) {
|
||||||
ActivateCampaignContent(um = CampaignPreviewData.alreadyActivated)
|
AlreadyActivatedCampaignContent(message = stringReference("You’re already enrolled in Whale Swap Cashback"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.features.promobanners.impl.campaigns.ui
|
package com.tangem.features.promobanners.impl.campaigns.ui
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
|
@ -14,12 +15,14 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
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.components.SpacerH
|
|
||||||
import com.tangem.core.ui.components.SpacerH32
|
import com.tangem.core.ui.components.SpacerH32
|
||||||
import com.tangem.core.ui.extensions.TextReference
|
import com.tangem.core.ui.extensions.TextReference
|
||||||
import com.tangem.core.ui.extensions.resolveReference
|
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.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.Icons
|
||||||
import com.tangem.core.ui.res.generated.icons.ic_success_24
|
import com.tangem.core.ui.res.generated.icons.ic_success_24
|
||||||
|
|
||||||
|
|
@ -56,6 +59,21 @@ fun CampaignEnrolledMessageContent(message: TextReference, modifier: Modifier =
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
SpacerH(48.dp)
|
SpacerH32()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
|
@ -3,9 +3,10 @@ package com.tangem.features.promobanners.impl.campaigns.ui
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
|
||||||
import com.tangem.core.ui.components.token.state.TokenItemState
|
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.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.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.FooterUM
|
||||||
import com.tangem.features.promobanners.impl.campaigns.entity.SelectedAccountUM
|
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.TermsUM
|
||||||
|
|
@ -51,6 +52,7 @@ internal object CampaignPreviewData {
|
||||||
)
|
)
|
||||||
|
|
||||||
val activateCampaign: ActivateCampaignUM = ActivateCampaignUM(
|
val activateCampaign: ActivateCampaignUM = ActivateCampaignUM(
|
||||||
|
logo = TangemIconUM.Icon(R.drawable.ic_alert_24),
|
||||||
title = stringReference("Whale Swap Cashback"),
|
title = stringReference("Whale Swap Cashback"),
|
||||||
description = stringReference(
|
description = stringReference(
|
||||||
"Get cashback on every swap. Pick a token and the account where your rewards will be paid out.",
|
"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,
|
footerUM = footer,
|
||||||
onChooseTokenDismiss = {},
|
onChooseTokenDismiss = {},
|
||||||
onLearnMoreClick = {},
|
onLearnMoreClick = {},
|
||||||
)
|
onChooseTokenClick = {},
|
||||||
|
|
||||||
val alreadyActivated: CampaignAlreadyActivatedUM = CampaignAlreadyActivatedUM(
|
|
||||||
selectedToken = tokenItem,
|
|
||||||
selectedAccount = selectedAccount,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -15,9 +15,10 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
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.SpacerH32
|
||||||
import com.tangem.core.ui.components.SpacerH8
|
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.TangemTheme
|
||||||
import com.tangem.core.ui.res.generated.icons.Icons
|
import com.tangem.core.ui.res.generated.icons.Icons
|
||||||
import com.tangem.core.ui.res.generated.icons.ic_warning_24
|
import com.tangem.core.ui.res.generated.icons.ic_warning_24
|
||||||
|
|
@ -48,7 +49,7 @@ fun NotActiveCampaignMessageContent(modifier: Modifier = Modifier) {
|
||||||
SpacerH32()
|
SpacerH32()
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
text = " Campaign not active", // TODO localization
|
text = stringResourceSafe(R.string.promo_campaign_not_active_title),
|
||||||
style = TangemTheme.typography3.heading.small,
|
style = TangemTheme.typography3.heading.small,
|
||||||
color = TangemTheme.colors3.text.primary,
|
color = TangemTheme.colors3.text.primary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
|
|
@ -58,13 +59,13 @@ fun NotActiveCampaignMessageContent(modifier: Modifier = Modifier) {
|
||||||
SpacerH8()
|
SpacerH8()
|
||||||
|
|
||||||
Text(
|
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,
|
style = TangemTheme.typography3.subheading.medium,
|
||||||
color = TangemTheme.colors3.text.secondary,
|
color = TangemTheme.colors3.text.secondary,
|
||||||
textAlign = TextAlign.Center,
|
textAlign = TextAlign.Center,
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
)
|
)
|
||||||
|
|
||||||
SpacerH(48.dp)
|
SpacerH32()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue