Updated on 2026-08-14
This commit is contained in:
commit
41c98cfee7
22 changed files with 186 additions and 18 deletions
|
|
@ -1,9 +1,6 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.promo.ShouldShowRingPromoUseCase
|
||||
import com.tangem.domain.promo.ShouldShowSwapPromoTokenUseCase
|
||||
import com.tangem.domain.promo.ShouldShowSwapPromoWalletUseCase
|
||||
import com.tangem.domain.promo.*
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -33,4 +30,10 @@ internal object PromoDomainModule {
|
|||
fun provideShouldShowSwapPromoTokenUseCase(promoRepository: PromoRepository): ShouldShowSwapPromoTokenUseCase {
|
||||
return ShouldShowSwapPromoTokenUseCase(promoRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideShouldShowSwapStoriesUseCase(promoRepository: PromoRepository): ShouldShowSwapStoriesUseCase {
|
||||
return ShouldShowSwapStoriesUseCase(promoRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -26,5 +26,9 @@
|
|||
{
|
||||
"name": "NAVIGATION_REFACTORING",
|
||||
"version": "undefined"
|
||||
},
|
||||
{
|
||||
"name": "SWAP_STORIES_ENABLED",
|
||||
"version": "undefined"
|
||||
}
|
||||
]
|
||||
|
|
|
|||
|
|
@ -113,6 +113,8 @@ object PreferencesKeys {
|
|||
|
||||
val WAS_LOG_FILE_CLEARED by lazy { booleanPreferencesKey(name = "wasLogFileCleared") }
|
||||
|
||||
val SHOULD_SHOW_SWAP_STORIES_KEY by lazy { booleanPreferencesKey("shouldShowSwapStories") }
|
||||
|
||||
// region Permission
|
||||
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@ fun StoriesProgressBar(
|
|||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = Modifier
|
||||
.statusBarsPadding()
|
||||
.padding(
|
||||
start = 16.dp,
|
||||
end = 16.dp,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import com.tangem.datasource.local.preferences.PreferencesKeys.ADDED_WALLETS_WIT
|
|||
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_RING_PROMO_KEY
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_SWAP_STORIES_KEY
|
||||
import com.tangem.datasource.local.preferences.utils.get
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
import com.tangem.datasource.local.preferences.utils.store
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.promo.models.PromoBanner
|
||||
|
|
@ -89,6 +91,21 @@ internal class DefaultPromoRepository(
|
|||
appPreferencesStore.store(key = SHOULD_SHOW_RING_PROMO_KEY, value = false)
|
||||
}
|
||||
|
||||
override fun isReadyToShowSwapStories(): Flow<Boolean> {
|
||||
return appPreferencesStore.get(SHOULD_SHOW_SWAP_STORIES_KEY, true)
|
||||
}
|
||||
|
||||
override suspend fun isReadyToShowSwapStoriesSync(): Boolean {
|
||||
return appPreferencesStore.getSyncOrDefault(SHOULD_SHOW_SWAP_STORIES_KEY, true)
|
||||
}
|
||||
|
||||
override suspend fun setNeverToShowSwapStories() {
|
||||
appPreferencesStore.store(
|
||||
key = SHOULD_SHOW_SWAP_STORIES_KEY,
|
||||
value = false,
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val CHANGELLY_NAME = "changelly"
|
||||
private const val OKX = "okx"
|
||||
|
|
|
|||
|
|
@ -23,4 +23,10 @@ interface PromoRepository {
|
|||
fun isReadyToShowRingPromo(userWalletId: UserWalletId): Flow<Boolean>
|
||||
|
||||
suspend fun setNeverToShowRingPromo()
|
||||
|
||||
fun isReadyToShowSwapStories(): Flow<Boolean>
|
||||
|
||||
suspend fun isReadyToShowSwapStoriesSync(): Boolean
|
||||
|
||||
suspend fun setNeverToShowSwapStories()
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.domain.promo
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
class ShouldShowSwapStoriesUseCase(private val promoRepository: PromoRepository) {
|
||||
operator fun invoke(): Flow<Boolean> = promoRepository.isReadyToShowSwapStories()
|
||||
suspend fun invokeSync(): Boolean = promoRepository.isReadyToShowSwapStoriesSync()
|
||||
|
||||
suspend fun neverToShow() = promoRepository.setNeverToShowSwapStories()
|
||||
}
|
||||
|
|
@ -1,18 +1,10 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.kotlin.serialization)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("kotlin-parcelize")
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.feature.swap.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
namespace = "com.tangem.features.swap.api"
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.tangem.features.swap
|
||||
|
||||
interface SwapFeatureToggles {
|
||||
val isPromoStoriesEnabled: Boolean
|
||||
}
|
||||
|
|
@ -38,12 +38,14 @@ dependencies {
|
|||
implementation(projects.domain.settings)
|
||||
implementation(projects.domain.staking)
|
||||
implementation(projects.domain.feedback)
|
||||
implementation(projects.domain.promo)
|
||||
|
||||
/** Feature modules */
|
||||
implementation(projects.features.swap.domain)
|
||||
implementation(projects.features.swap.domain.api)
|
||||
implementation(projects.features.swap.domain.models)
|
||||
implementation(projects.features.wallet.api)
|
||||
implementation(projects.features.swap.api)
|
||||
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package com.tangem.feature.swap
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
|
||||
internal class DefaultSwapFeatureToggles(
|
||||
private val featureToggles: FeatureTogglesManager,
|
||||
) : SwapFeatureToggles {
|
||||
override val isPromoStoriesEnabled: Boolean
|
||||
get() = featureToggles.isFeatureEnabled("SWAP_STORIES_ENABLED")
|
||||
}
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
package com.tangem.feature.swap.di
|
||||
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.feature.swap.DefaultSwapFeatureToggles
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal object SwapFeatureModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSwapFeatureToggles(featureToggles: FeatureTogglesManager): SwapFeatureToggles {
|
||||
return DefaultSwapFeatureToggles(featureToggles)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -67,5 +67,5 @@ internal class SwapRouter(
|
|||
}
|
||||
|
||||
enum class SwapNavScreen {
|
||||
Main, Success, SelectToken
|
||||
Main, Success, SelectToken, PromoStories
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue