Updated on 2026-08-14
This commit is contained in:
parent
380e5c19e0
commit
0cbf7b74e2
14 changed files with 64 additions and 75 deletions
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.repository.*
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -227,6 +229,8 @@ internal object TokensDomainModule {
|
|||
quotesRepository: QuotesRepository,
|
||||
networksRepository: NetworksRepository,
|
||||
stakingRepository: StakingRepository,
|
||||
promoRepository: PromoRepository,
|
||||
swapFeatureToggles: SwapFeatureToggles,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): GetCryptoCurrencyActionsUseCase {
|
||||
return GetCryptoCurrencyActionsUseCase(
|
||||
|
|
@ -236,6 +240,8 @@ internal object TokensDomainModule {
|
|||
quotesRepository = quotesRepository,
|
||||
networksRepository = networksRepository,
|
||||
stakingRepository = stakingRepository,
|
||||
promoRepository = promoRepository,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
package com.tangem.feature.swap.ui
|
||||
package com.tangem.common.ui.swapStoriesScreen
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.activity.compose.BackHandler
|
||||
|
|
@ -33,15 +33,15 @@ import com.tangem.core.ui.res.LocalWindowSize
|
|||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.feature.swap.models.SwapStoriesContentConfig
|
||||
import com.tangem.feature.swap.models.SwapStoryConfig
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
private val SubtitleColor = Color(0xFF868692)
|
||||
private const val STORIES_RELATIVE_PADDING = 0.7
|
||||
|
||||
@Composable
|
||||
internal fun SwapStoriesScreen(config: SwapStoriesContentConfig) {
|
||||
fun SwapStoriesScreen(config: SwapStoriesUM) {
|
||||
if (config !is SwapStoriesUM.Content) return
|
||||
|
||||
BackHandler(onBack = config.onClose)
|
||||
SystemBarsIconsDisposable(darkIcons = false)
|
||||
|
||||
|
|
@ -106,9 +106,9 @@ internal fun SwapStoriesScreen(config: SwapStoriesContentConfig) {
|
|||
private fun SwapStoriesScreen_Preview() {
|
||||
TangemThemePreview {
|
||||
SwapStoriesScreen(
|
||||
SwapStoriesContentConfig(
|
||||
SwapStoriesUM.Content(
|
||||
stories = persistentListOf(
|
||||
SwapStoryConfig(
|
||||
SwapStoriesUM.Content.Config(
|
||||
imageUrl = "https://devweb.tangem.com/images/stories/swap/image1.png",
|
||||
title = stringReference("Exchange With Us"),
|
||||
subtitle = stringReference(
|
||||
|
|
@ -25,11 +25,14 @@ dependencies {
|
|||
implementation(projects.domain.settings)
|
||||
implementation(projects.features.swap.domain.api)
|
||||
implementation(projects.features.swap.domain.models)
|
||||
implementation(projects.domain.promo.models)
|
||||
implementation(projects.domain.promo)
|
||||
|
||||
/** Project - Api */
|
||||
implementation(projects.features.send.api)
|
||||
implementation(projects.features.staking.api)
|
||||
implementation(projects.features.markets.api)
|
||||
implementation(projects.features.swap.api)
|
||||
|
||||
/** Project - Other */
|
||||
implementation(projects.core.utils)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.domain.tokens
|
|||
|
||||
import com.tangem.domain.common.util.cardTypesResolver
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.staking.model.StakingAvailability
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.tokens.model.*
|
||||
|
|
@ -12,9 +13,9 @@ import com.tangem.domain.tokens.repository.QuotesRepository
|
|||
import com.tangem.domain.transaction.models.AssetRequirementsCondition
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.isNullOrZero
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
|
||||
|
|
@ -31,10 +32,11 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
private val quotesRepository: QuotesRepository,
|
||||
private val networksRepository: NetworksRepository,
|
||||
private val stakingRepository: StakingRepository,
|
||||
private val promoRepository: PromoRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
) {
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
suspend operator fun invoke(
|
||||
userWallet: UserWallet,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
|
|
@ -62,13 +64,16 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
includeQuotes = false,
|
||||
)
|
||||
}
|
||||
|
||||
val flow = networkFlow.mapLatest { maybeCoinStatus ->
|
||||
val flow = combine(
|
||||
flow = networkFlow,
|
||||
flow2 = promoRepository.isReadyToShowSwapStories().distinctUntilChanged(),
|
||||
) { maybeCoinStatus, shouldShowSwapStories ->
|
||||
createTokenActionsState(
|
||||
userWallet = userWallet,
|
||||
coinStatus = maybeCoinStatus.getOrNull(),
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
requirements = requirements,
|
||||
shouldShowSwapStories = shouldShowSwapStories,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -81,6 +86,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
coinStatus: CryptoCurrencyStatus?,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
requirements: AssetRequirementsCondition?,
|
||||
shouldShowSwapStories: Boolean,
|
||||
): TokenActionsState {
|
||||
return TokenActionsState(
|
||||
walletId = userWallet.walletId,
|
||||
|
|
@ -90,6 +96,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
coinStatus = coinStatus,
|
||||
cryptoCurrencyStatus = cryptoCurrencyStatus,
|
||||
requirements = requirements,
|
||||
shouldShowSwapStories = shouldShowSwapStories,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -104,6 +111,7 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
coinStatus: CryptoCurrencyStatus?,
|
||||
cryptoCurrencyStatus: CryptoCurrencyStatus,
|
||||
requirements: AssetRequirementsCondition?,
|
||||
shouldShowSwapStories: Boolean,
|
||||
): List<TokenActionsState.ActionState> {
|
||||
val cryptoCurrency = cryptoCurrencyStatus.currency
|
||||
if (cryptoCurrencyStatus.value is CryptoCurrencyStatus.MissedDerivation) {
|
||||
|
|
@ -173,11 +181,18 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
rampManager.availableForSwap(userWallet.walletId, cryptoCurrency)
|
||||
} ?: false
|
||||
if (isExchangeable && cryptoCurrencyStatus.value !is CryptoCurrencyStatus.NoQuote) {
|
||||
activeList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.None))
|
||||
val swapStoriesEnabled = swapFeatureToggles.isPromoStoriesEnabled
|
||||
activeList.add(
|
||||
TokenActionsState.ActionState.Swap(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.None,
|
||||
showBadge = shouldShowSwapStories && swapStoriesEnabled,
|
||||
),
|
||||
)
|
||||
} else {
|
||||
disabledList.add(
|
||||
TokenActionsState.ActionState.Swap(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.NotExchangeable(cryptoCurrency.name),
|
||||
showBadge = false,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -233,7 +248,12 @@ class GetCryptoCurrencyActionsUseCase(
|
|||
)
|
||||
}
|
||||
actionsList.add(TokenActionsState.ActionState.Send(ScenarioUnavailabilityReason.Unreachable))
|
||||
actionsList.add(TokenActionsState.ActionState.Swap(ScenarioUnavailabilityReason.Unreachable))
|
||||
actionsList.add(
|
||||
TokenActionsState.ActionState.Swap(
|
||||
unavailabilityReason = ScenarioUnavailabilityReason.Unreachable,
|
||||
showBadge = false,
|
||||
),
|
||||
)
|
||||
actionsList.add(TokenActionsState.ActionState.Sell(ScenarioUnavailabilityReason.Unreachable))
|
||||
if (isAddressAvailable(cryptoCurrencyStatus.value.networkAddress)) {
|
||||
val scenario = getReceiveScenario(requirements)
|
||||
|
|
|
|||
|
|
@ -26,7 +26,10 @@ data class TokenActionsState(
|
|||
val yield: Yield?,
|
||||
) : ActionState()
|
||||
|
||||
data class Swap(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
data class Swap(
|
||||
override val unavailabilityReason: ScenarioUnavailabilityReason,
|
||||
val showBadge: Boolean,
|
||||
) : ActionState()
|
||||
|
||||
data class Send(override val unavailabilityReason: ScenarioUnavailabilityReason) : ActionState()
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import androidx.annotation.DrawableRes
|
|||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.GiveTxPermissionState
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.swapStoriesScreen.SwapStoriesUM
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.event.StateEvent
|
||||
|
|
@ -34,7 +35,7 @@ internal data class SwapStateHolder(
|
|||
val successState: SwapSuccessStateHolder? = null,
|
||||
val selectTokenState: SwapSelectTokenStateHolder? = null,
|
||||
val bottomSheetConfig: TangemBottomSheetConfig? = null,
|
||||
val storiesConfig: SwapStoriesContentConfig? = null,
|
||||
val storiesConfig: SwapStoriesUM? = null,
|
||||
|
||||
val swapButton: SwapButton,
|
||||
val shouldShowMaxAmount: Boolean,
|
||||
|
|
|
|||
|
|
@ -1,21 +0,0 @@
|
|||
package com.tangem.feature.swap.models
|
||||
|
||||
import com.tangem.core.ui.components.stories.model.StoriesContentConfig
|
||||
import com.tangem.core.ui.components.stories.model.StoryConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
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(
|
||||
val imageUrl: String,
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
) : StoryConfig {
|
||||
override val duration: Int = 2000
|
||||
}
|
||||
|
|
@ -6,13 +6,13 @@ import androidx.compose.foundation.background
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.tangem.common.ui.swapStoriesScreen.SwapStoriesScreen
|
||||
import com.tangem.core.ui.UiDependencies
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
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
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import androidx.compose.ui.text.input.TextFieldValue
|
|||
import com.tangem.common.ui.alerts.models.AlertDemoModeUM
|
||||
import com.tangem.common.ui.bottomsheet.permission.state.*
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.swapStoriesScreen.SwapStoriesFactory
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
|
||||
import com.tangem.core.ui.event.consumedEvent
|
||||
|
|
@ -126,43 +127,11 @@ internal class StateBuilder(
|
|||
)
|
||||
}
|
||||
|
||||
// WARNING! Be careful with indices. Temporary solution.
|
||||
// Use all data from v1/stories api (image url, title, subtitle)
|
||||
@Suppress("MagicNumber")
|
||||
fun createStoriesState(uiStateHolder: SwapStateHolder, swapStory: StoryContent): SwapStateHolder {
|
||||
val storyOrderedImageUrls = swapStory.getImageUrls()
|
||||
if (storyOrderedImageUrls.size != 5) return uiStateHolder
|
||||
|
||||
return uiStateHolder.copy(
|
||||
storiesConfig = SwapStoriesContentConfig(
|
||||
stories = persistentListOf(
|
||||
SwapStoryConfig(
|
||||
imageUrl = storyOrderedImageUrls[0],
|
||||
title = resourceReference(R.string.swap_story_first_title),
|
||||
subtitle = resourceReference(R.string.swap_story_first_subtitle),
|
||||
),
|
||||
SwapStoryConfig(
|
||||
imageUrl = storyOrderedImageUrls[1],
|
||||
title = resourceReference(R.string.swap_story_second_title),
|
||||
subtitle = resourceReference(R.string.swap_story_second_subtitle),
|
||||
),
|
||||
SwapStoryConfig(
|
||||
imageUrl = storyOrderedImageUrls[2],
|
||||
title = resourceReference(R.string.swap_story_third_title),
|
||||
subtitle = resourceReference(R.string.swap_story_third_subtitle),
|
||||
),
|
||||
SwapStoryConfig(
|
||||
imageUrl = storyOrderedImageUrls[3],
|
||||
title = resourceReference(R.string.swap_story_forth_title),
|
||||
subtitle = resourceReference(R.string.swap_story_forth_subtitle),
|
||||
),
|
||||
SwapStoryConfig(
|
||||
imageUrl = storyOrderedImageUrls[4],
|
||||
title = resourceReference(R.string.swap_story_fifth_title),
|
||||
subtitle = resourceReference(R.string.swap_story_fifth_subtitle),
|
||||
),
|
||||
),
|
||||
onClose = actions.onStoriesClose,
|
||||
storiesConfig = SwapStoriesFactory.createStoriesState(
|
||||
swapStory = swapStory,
|
||||
onStoriesClose = actions.onStoriesClose,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -85,6 +85,7 @@ dependencies {
|
|||
implementation(projects.domain.onramp)
|
||||
implementation(projects.domain.onramp.models)
|
||||
implementation(projects.domain.promo)
|
||||
implementation(projects.domain.promo.models)
|
||||
|
||||
/** Temp dependency to swap domain */
|
||||
implementation(projects.features.swap.domain)
|
||||
|
|
@ -97,5 +98,6 @@ dependencies {
|
|||
implementation(projects.features.staking.api)
|
||||
implementation(projects.features.markets.api)
|
||||
implementation(projects.features.onramp.api)
|
||||
implementation(projects.features.swap.api)
|
||||
|
||||
}
|
||||
|
|
@ -100,7 +100,7 @@ internal object TokenDetailsPreviewData {
|
|||
TokenDetailsActionButton.Buy(dimContent = false, onClick = {}),
|
||||
TokenDetailsActionButton.Send(dimContent = false, onClick = {}),
|
||||
TokenDetailsActionButton.Receive(onClick = {}, onLongClick = null),
|
||||
TokenDetailsActionButton.Swap(dimContent = false, onClick = {}),
|
||||
TokenDetailsActionButton.Swap(dimContent = false, onClick = {}, showBadge = true),
|
||||
)
|
||||
|
||||
private val balanceSegmentedButtonConfig = persistentListOf(
|
||||
|
|
|
|||
|
|
@ -83,12 +83,17 @@ internal sealed class TokenDetailsActionButton(val config: ActionButtonConfig) {
|
|||
* @property dimContent determines whether the button content will be dimmed
|
||||
* @property onClick lambda be invoked when Swap button is clicked
|
||||
*/
|
||||
data class Swap(val dimContent: Boolean, override val onClick: () -> Unit) : TokenDetailsActionButton(
|
||||
data class Swap(
|
||||
val dimContent: Boolean,
|
||||
val showBadge: Boolean,
|
||||
override val onClick: () -> Unit,
|
||||
) : TokenDetailsActionButton(
|
||||
config = ActionButtonConfig(
|
||||
text = TextReference.Res(id = R.string.swapping_swap_action),
|
||||
iconResId = R.drawable.ic_exchange_vertical_24,
|
||||
onClick = onClick,
|
||||
dimContent = dimContent,
|
||||
showBadge = showBadge,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -54,6 +54,7 @@ internal class TokenDetailsActionButtonsConverter(
|
|||
TokenDetailsActionButton.Swap(
|
||||
dimContent = action.unavailabilityReason != ScenarioUnavailabilityReason.None,
|
||||
onClick = { clickIntents.onSwapClick(action.unavailabilityReason) },
|
||||
showBadge = action.showBadge,
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory
|
||||
|
||||
import arrow.core.getOrElse
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
|
||||
import com.tangem.core.ui.components.transactions.state.TxHistoryState
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.networkIconResId
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.domain.card.NetworkHasDerivationUseCase
|
||||
import com.tangem.domain.staking.GetStakingIntegrationIdUseCase
|
||||
|
|
@ -122,7 +122,7 @@ internal class TokenDetailsSkeletonStateConverter(
|
|||
TokenDetailsActionButton.Send(dimContent = false, onClick = {}),
|
||||
TokenDetailsActionButton.Receive(onClick = {}, onLongClick = null),
|
||||
TokenDetailsActionButton.Sell(dimContent = false, onClick = {}),
|
||||
TokenDetailsActionButton.Swap(dimContent = false, onClick = {}),
|
||||
TokenDetailsActionButton.Swap(dimContent = false, onClick = {}, showBadge = false),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue