Updated on 2026-08-14

This commit is contained in:
Tangem 2025-01-10 13:09:31 +05:00
parent 563d95d75e
commit 1b25b81caf
12 changed files with 100 additions and 4 deletions

View file

@ -56,15 +56,19 @@ inline fun <reified T : StoryConfig> StoriesContainer(
onNextStory = storyState::nextStory,
)
currentStoryContent(storyState.currentStory, isPaused)
StoriesProgressBar(
steps = storyState.steps,
currentStep = storyState.currentIndex.intValue,
stepDuration = storyState.currentStory.duration,
paused = isPaused,
onStepFinish = storyState::nextStory,
onStepFinish = {
if (storyState.nextStory()) {
config.onClose()
}
},
)
currentStoryContent(storyState.currentStory, isPaused)
}
}
@ -97,6 +101,7 @@ private data class StoryContainerPreviewProviderData(
StoryConfigPreviewProviderData(title = "Wallet"),
),
override val isRestartable: Boolean = true,
override val onClose: () -> Unit = {},
) : StoriesContentConfig<StoryConfigPreviewProviderData>
private data class StoryConfigPreviewProviderData(

View file

@ -80,6 +80,7 @@ fun StoriesProgressBar(
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier
.statusBarsPadding()
.padding(
start = 16.dp,
end = 16.dp,

View file

@ -11,6 +11,7 @@ import kotlinx.collections.immutable.ImmutableList
interface StoriesContentConfig<T : StoryConfig> {
val stories: ImmutableList<T>
val isRestartable: Boolean
val onClose: () -> Unit
}
interface StoryConfig {

View file

@ -38,6 +38,7 @@ dependencies {
implementation(projects.domain.settings)
implementation(projects.domain.staking)
implementation(projects.domain.feedback)
implementation(projects.domain.promo)
/** Feature modules */
implementation(projects.features.swap.domain)

View file

@ -34,6 +34,7 @@ internal data class SwapStateHolder(
val successState: SwapSuccessStateHolder? = null,
val selectTokenState: SwapSelectTokenStateHolder? = null,
val bottomSheetConfig: TangemBottomSheetConfig? = null,
val storiesConfig: SwapStoriesContentConfig? = null,
val swapButton: SwapButton,
val shouldShowMaxAmount: Boolean,

View file

@ -0,0 +1,19 @@
package com.tangem.feature.swap.models
import androidx.annotation.DrawableRes
import com.tangem.core.ui.components.stories.model.StoriesContentConfig
import com.tangem.core.ui.components.stories.model.StoryConfig
import kotlinx.collections.immutable.ImmutableList
data class SwapStoriesContentConfig(
override val stories: ImmutableList<SwapStoryConfig>,
override val onClose: () -> Unit,
) : StoriesContentConfig<SwapStoryConfig> {
override val isRestartable: Boolean = false
}
data class SwapStoryConfig(
@DrawableRes val imageRes: Int,
) : StoryConfig {
override val duration: Int = 2000
}

View file

@ -21,6 +21,7 @@ data class UiActions(
val openPermissionBottomSheet: () -> Unit,
val onChangeApproveType: (ApproveType) -> Unit,
// region new actions
val onStoriesClose: () -> Unit,
val onRetryClick: () -> Unit,
val onClickFee: () -> Unit,
val onSelectFeeType: (TxFee) -> Unit,

View file

@ -13,6 +13,7 @@ import com.tangem.core.ui.screen.ComposeFragment
import com.tangem.feature.swap.router.SwapNavScreen
import com.tangem.feature.swap.ui.SwapScreen
import com.tangem.feature.swap.ui.SwapSelectTokenScreen
import com.tangem.feature.swap.ui.SwapStoriesScreen
import com.tangem.feature.swap.ui.SwapSuccessScreen
import com.tangem.feature.swap.viewmodels.SwapViewModel
import dagger.hilt.android.AndroidEntryPoint
@ -46,6 +47,14 @@ class SwapFragment : ComposeFragment() {
label = "",
) { screen ->
when (screen) {
SwapNavScreen.PromoStories -> {
val storiesConfig = viewModel.uiState.storiesConfig
if (storiesConfig != null) {
SwapStoriesScreen(config = storiesConfig)
} else {
SwapScreen(stateHolder = viewModel.uiState)
}
}
SwapNavScreen.Main -> SwapScreen(stateHolder = viewModel.uiState)
SwapNavScreen.Success -> {
val successState = viewModel.uiState.successState

View file

@ -67,5 +67,5 @@ internal class SwapRouter(
}
enum class SwapNavScreen {
Main, Success, SelectToken
Main, Success, SelectToken, PromoStories
}

View file

@ -125,6 +125,26 @@ internal class StateBuilder(
)
}
fun createStoriesState(uiStateHolder: SwapStateHolder): SwapStateHolder {
return uiStateHolder.copy(
// todo stories [REDACTED_TASK_KEY] fix when design is ready
storiesConfig = SwapStoriesContentConfig(
stories = persistentListOf(
SwapStoryConfig(
imageRes = R.drawable.img_card_with_ring,
),
SwapStoryConfig(
imageRes = R.drawable.ill_businessman_3d,
),
SwapStoryConfig(
imageRes = R.drawable.ill_one_inch_powered,
),
),
onClose = actions.onStoriesClose,
),
)
}
fun createNoAvailableTokensToSwapState(
uiStateHolder: SwapStateHolder,
fromToken: CryptoCurrencyStatus,

View file

@ -0,0 +1,20 @@
package com.tangem.feature.swap.ui
import androidx.compose.foundation.Image
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.painterResource
import com.tangem.core.ui.components.stories.StoriesContainer
import com.tangem.feature.swap.models.SwapStoriesContentConfig
@Composable
internal fun SwapStoriesScreen(config: SwapStoriesContentConfig) {
StoriesContainer(
config = config,
) { current, _ ->
// todo stories [REDACTED_TASK_KEY] fix when design is ready
Image(
painter = painterResource(current.imageRes),
contentDescription = null,
)
}
}

View file

@ -27,6 +27,7 @@ import com.tangem.domain.feedback.SaveBlockchainErrorUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
@ -50,6 +51,7 @@ import com.tangem.feature.swap.router.SwapNavScreen
import com.tangem.feature.swap.router.SwapRouter
import com.tangem.feature.swap.ui.StateBuilder
import com.tangem.feature.swap.utils.formatToUIRepresentation
import com.tangem.features.swap.SwapFeatureToggles
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import com.tangem.utils.isNullOrZero
@ -86,6 +88,8 @@ internal class SwapViewModel @Inject constructor(
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase,
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
private val featureToggles: SwapFeatureToggles,
swapInteractorFactory: SwapInteractor.Factory,
private val savedStateHandle: SavedStateHandle,
private val urlOpener: UrlOpener,
@ -168,6 +172,13 @@ internal class SwapViewModel @Inject constructor(
val fromStatus = getCryptoCurrencyStatusUseCase(userWalletId, initialCurrencyFrom.id).getOrNull()
val toStatus = initialCurrencyTo?.let { getCryptoCurrencyStatusUseCase(userWalletId, it.id).getOrNull() }
val wallet = getUserWalletUseCase(userWalletId).getOrNull()
val isShowPromoStories = shouldShowSwapStoriesUseCase.invokeSync() && featureToggles.isPromoStoriesEnabled
if (isShowPromoStories) {
initStories()
swapRouter.openScreen(SwapNavScreen.PromoStories)
}
if (fromStatus == null || wallet == null) {
uiState = stateBuilder.addAlert(uiState = uiState, onDismiss = swapRouter::back)
} else {
@ -271,6 +282,10 @@ internal class SwapViewModel @Inject constructor(
}
}
private fun initStories() {
uiState = stateBuilder.createStoriesState(uiState)
}
private fun applyInitialTokenChoice(
state: TokensDataStateExpress,
selectedCurrency: CryptoCurrencyStatus?,
@ -1115,6 +1130,9 @@ internal class SwapViewModel @Inject constructor(
onSuccess = {
swapRouter.openScreen(SwapNavScreen.Success)
},
onStoriesClose = {
swapRouter.openScreen(SwapNavScreen.Main)
},
)
}