Updated on 2026-08-14
This commit is contained in:
commit
b03396d7b1
44 changed files with 569 additions and 92 deletions
|
|
@ -177,6 +177,8 @@ dependencies {
|
|||
implementation(projects.features.onramp.impl)
|
||||
implementation(projects.features.onboardingV2.api)
|
||||
implementation(projects.features.onboardingV2.impl)
|
||||
implementation(projects.features.stories.api)
|
||||
implementation(projects.features.stories.impl)
|
||||
|
||||
/** AndroidX libraries */
|
||||
implementation(deps.androidx.core.ktx)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import com.tangem.common.routing.AppRoute
|
|||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.feature.qrscanning.QrScanningRouter
|
||||
import com.tangem.feature.referral.ReferralFragment
|
||||
import com.tangem.feature.stories.api.StoriesComponent
|
||||
import com.tangem.feature.swap.presentation.SwapFragment
|
||||
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
|
||||
import com.tangem.features.details.component.DetailsComponent
|
||||
|
|
@ -56,6 +57,7 @@ internal class ChildFactory @Inject constructor(
|
|||
private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory,
|
||||
private val onboardingEntryComponentFactory: OnboardingEntryComponent.Factory,
|
||||
private val welcomeComponentFactory: WelcomeComponent.Factory,
|
||||
private val storiesComponentFactory: StoriesComponent.Factory,
|
||||
private val sendRouter: SendRouter,
|
||||
private val tokenDetailsRouter: TokenDetailsRouter,
|
||||
private val walletRouter: WalletRouter,
|
||||
|
|
@ -199,6 +201,16 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = onboardingEntryComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.Stories -> {
|
||||
createComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = StoriesComponent.Params(
|
||||
storyId = route.storyId,
|
||||
nextScreen = route.nextScreen,
|
||||
),
|
||||
componentFactory = storiesComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.AccessCodeRecovery,
|
||||
is AppRoute.AppCurrencySelector,
|
||||
is AppRoute.SaveWallet,
|
||||
|
|
@ -407,6 +419,16 @@ internal class ChildFactory @Inject constructor(
|
|||
componentFactory = onboardingEntryComponentFactory,
|
||||
)
|
||||
}
|
||||
is AppRoute.Stories -> {
|
||||
route.asComponentChild(
|
||||
contextProvider = contextProvider(route, contextFactory),
|
||||
params = StoriesComponent.Params(
|
||||
storyId = route.storyId,
|
||||
nextScreen = route.nextScreen,
|
||||
),
|
||||
componentFactory = storiesComponentFactory,
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -341,4 +341,12 @@ sealed class AppRoute(val path: String) : Route {
|
|||
AddBackup, // continue backup process for existing wallet 1
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Stories(
|
||||
val storyId: String,
|
||||
val nextScreen: AppRoute,
|
||||
) : AppRoute(path = "/stories$storyId"), RouteBundleParams {
|
||||
override fun getBundle(): Bundle = bundle(serializer())
|
||||
}
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@ dependencies {
|
|||
implementation(projects.domain.transaction.models)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.onramp.models)
|
||||
implementation(projects.domain.promo.models)
|
||||
|
||||
implementation(deps.tangem.card.core)
|
||||
implementation(deps.tangem.blockchain) {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,48 @@
|
|||
package com.tangem.common.ui.swapStoriesScreen
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.promo.models.StoryContent
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
object SwapStoriesFactory {
|
||||
|
||||
// WARNING! Be careful with indices. Temporary solution.
|
||||
// Use all data from v1/stories api (image url, title, subtitle)
|
||||
@Suppress("MagicNumber")
|
||||
fun createStoriesState(swapStory: StoryContent, onStoriesClose: () -> Unit): SwapStoriesUM {
|
||||
val storyOrderedImageUrls = swapStory.getImageUrls()
|
||||
if (storyOrderedImageUrls.size != 5) return SwapStoriesUM.Empty
|
||||
|
||||
return SwapStoriesUM.Content(
|
||||
stories = persistentListOf(
|
||||
SwapStoriesUM.Content.Config(
|
||||
imageUrl = storyOrderedImageUrls[0],
|
||||
title = resourceReference(R.string.swap_story_first_title),
|
||||
subtitle = resourceReference(R.string.swap_story_first_subtitle),
|
||||
),
|
||||
SwapStoriesUM.Content.Config(
|
||||
imageUrl = storyOrderedImageUrls[1],
|
||||
title = resourceReference(R.string.swap_story_second_title),
|
||||
subtitle = resourceReference(R.string.swap_story_second_subtitle),
|
||||
),
|
||||
SwapStoriesUM.Content.Config(
|
||||
imageUrl = storyOrderedImageUrls[2],
|
||||
title = resourceReference(R.string.swap_story_third_title),
|
||||
subtitle = resourceReference(R.string.swap_story_third_subtitle),
|
||||
),
|
||||
SwapStoriesUM.Content.Config(
|
||||
imageUrl = storyOrderedImageUrls[3],
|
||||
title = resourceReference(R.string.swap_story_forth_title),
|
||||
subtitle = resourceReference(R.string.swap_story_forth_subtitle),
|
||||
),
|
||||
SwapStoriesUM.Content.Config(
|
||||
imageUrl = storyOrderedImageUrls[4],
|
||||
title = resourceReference(R.string.swap_story_fifth_title),
|
||||
subtitle = resourceReference(R.string.swap_story_fifth_subtitle),
|
||||
),
|
||||
),
|
||||
onClose = onStoriesClose,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
package com.tangem.common.ui.swapStoriesScreen
|
||||
|
||||
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
|
||||
|
||||
sealed class SwapStoriesUM {
|
||||
|
||||
data object Empty : SwapStoriesUM()
|
||||
|
||||
data class Content(
|
||||
override val stories: ImmutableList<Config>,
|
||||
override val onClose: () -> Unit,
|
||||
) : SwapStoriesUM(), StoriesContentConfig<Content.Config> {
|
||||
override val isRestartable: Boolean = false
|
||||
|
||||
data class Config(
|
||||
val imageUrl: String,
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
) : StoryConfig {
|
||||
override val duration: Int = 2000
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4,20 +4,23 @@ import android.content.res.Configuration
|
|||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyRow
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButton
|
||||
import com.tangem.core.ui.components.buttons.actions.ActionButtonConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -25,6 +28,7 @@ import kotlinx.collections.immutable.persistentListOf
|
|||
fun HorizontalActionChips(
|
||||
buttons: ImmutableList<ActionButtonConfig>,
|
||||
modifier: Modifier = Modifier,
|
||||
containerColor: Color = TangemTheme.colors.background.secondary,
|
||||
contentPadding: PaddingValues = PaddingValues(TangemTheme.dimens.spacing0),
|
||||
) {
|
||||
LazyRow(
|
||||
|
|
@ -36,7 +40,7 @@ fun HorizontalActionChips(
|
|||
// do not use key cause when change items order, list is scrolled
|
||||
items(
|
||||
items = buttons,
|
||||
itemContent = { ActionButton(config = it) },
|
||||
itemContent = { ActionButton(config = it, containerColor = containerColor) },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -46,22 +50,31 @@ fun HorizontalActionChips(
|
|||
@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun Preview_HorizontalActionChips(
|
||||
@PreviewParameter(ActionButtonConfigProvider::class) buttons: HorizontalActionChips,
|
||||
@PreviewParameter(ActionButtonConfigProvider::class) buttons: HorizontalActionChipsData,
|
||||
) {
|
||||
TangemThemePreview {
|
||||
HorizontalActionChips(buttons = buttons.buttons)
|
||||
HorizontalActionChips(
|
||||
buttons = buttons.buttons,
|
||||
modifier = Modifier.padding(4.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private class ActionButtonConfigProvider : CollectionPreviewParameterProvider<HorizontalActionChips>(
|
||||
private class ActionButtonConfigProvider : CollectionPreviewParameterProvider<HorizontalActionChipsData>(
|
||||
collection = listOf(
|
||||
HorizontalActionChips(
|
||||
HorizontalActionChipsData(
|
||||
buttons = persistentListOf(
|
||||
ActionButtonConfig(
|
||||
text = TextReference.Str(value = "Buy"),
|
||||
iconResId = R.drawable.ic_plus_24,
|
||||
onClick = {},
|
||||
),
|
||||
ActionButtonConfig(
|
||||
text = TextReference.Str(value = "Exchange"),
|
||||
iconResId = R.drawable.ic_exchange_vertical_24,
|
||||
onClick = {},
|
||||
showBadge = true,
|
||||
),
|
||||
ActionButtonConfig(
|
||||
text = TextReference.Str(value = "Send"),
|
||||
iconResId = R.drawable.ic_arrow_up_24,
|
||||
|
|
@ -72,17 +85,12 @@ private class ActionButtonConfigProvider : CollectionPreviewParameterProvider<Ho
|
|||
iconResId = R.drawable.ic_arrow_down_24,
|
||||
onClick = {},
|
||||
),
|
||||
ActionButtonConfig(
|
||||
text = TextReference.Str(value = "Exchange"),
|
||||
iconResId = R.drawable.ic_exchange_vertical_24,
|
||||
onClick = {},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
private data class HorizontalActionChips(
|
||||
private data class HorizontalActionChipsData(
|
||||
val buttons: ImmutableList<ActionButtonConfig>,
|
||||
)
|
||||
// endregion Preview
|
||||
|
|
@ -13,6 +13,8 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
* @property enabled enabled
|
||||
* @property dimContent determines whether the button content will be dimmed. This property will be ignored if [enabled]
|
||||
* is `false`.
|
||||
* @property isInProgress indicates progress state of button
|
||||
* @property showBadge display dot in upper right corner
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
|
|
@ -24,4 +26,5 @@ data class ActionButtonConfig(
|
|||
val enabled: Boolean = true,
|
||||
val dimContent: Boolean = false,
|
||||
val isInProgress: Boolean = false,
|
||||
val showBadge: Boolean = false,
|
||||
)
|
||||
|
|
@ -17,7 +17,10 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.drawWithContent
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.drawscope.DrawScope
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
|
|
@ -29,6 +32,7 @@ import androidx.compose.ui.unit.dp
|
|||
import com.tangem.core.ui.R
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemColorPalette
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
|
||||
|
|
@ -81,6 +85,7 @@ fun ActionButton(
|
|||
config: ActionButtonConfig,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = TangemTheme.colors.button.secondary,
|
||||
containerColor: Color = TangemTheme.colors.background.secondary,
|
||||
) {
|
||||
ActionBaseButton(
|
||||
config = config,
|
||||
|
|
@ -97,6 +102,7 @@ fun ActionButton(
|
|||
},
|
||||
modifier = modifier,
|
||||
color = color,
|
||||
containerColor = containerColor,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -108,6 +114,7 @@ fun ActionBaseButton(
|
|||
content: @Composable (modifier: Modifier) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
color: Color = TangemTheme.colors.button.secondary,
|
||||
containerColor: Color = TangemTheme.colors.background.secondary,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val backgroundColor by animateColorAsState(
|
||||
|
|
@ -119,6 +126,12 @@ fun ActionBaseButton(
|
|||
modifier = modifier
|
||||
.heightIn(min = 36.dp)
|
||||
.widthIn(min = 100.dp)
|
||||
.drawWithContent {
|
||||
drawContent()
|
||||
if (config.showBadge) {
|
||||
drawBadge(containerColor = containerColor)
|
||||
}
|
||||
}
|
||||
.clip(shape)
|
||||
.combinedClickable(
|
||||
enabled = config.enabled,
|
||||
|
|
@ -212,6 +225,20 @@ fun getTextColor(config: ActionButtonConfig): Color {
|
|||
}
|
||||
}
|
||||
|
||||
private fun DrawScope.drawBadge(containerColor: Color) {
|
||||
val width = size.width
|
||||
drawCircle(
|
||||
color = containerColor,
|
||||
center = Offset(x = width - 2.dp.toPx(), y = 2.dp.toPx()),
|
||||
radius = 5.dp.toPx(),
|
||||
)
|
||||
drawCircle(
|
||||
color = TangemColorPalette.Azure,
|
||||
center = Offset(x = width - 2.dp.toPx(), y = 2.dp.toPx()),
|
||||
radius = 3.dp.toPx(),
|
||||
)
|
||||
}
|
||||
|
||||
@Preview(group = "RoundedActionButton", showBackground = true)
|
||||
@Preview(group = "RoundedActionButton", showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
|
|
@ -237,6 +264,7 @@ private class ActionStateProvider : CollectionPreviewParameterProvider<ActionBut
|
|||
iconResId = R.drawable.ic_arrow_up_24,
|
||||
enabled = true,
|
||||
onClick = {},
|
||||
showBadge = true,
|
||||
),
|
||||
ActionButtonConfig(
|
||||
text = TextReference.Str(value = "Dimmed"),
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
1
features/stories/api/.gitignore
vendored
Normal file
1
features/stories/api/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
20
features/stories/api/build.gradle.kts
Normal file
20
features/stories/api/build.gradle.kts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
id("kotlin-parcelize")
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.stories.api"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/* Project - Core */
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/* Compose */
|
||||
implementation(deps.compose.runtime)
|
||||
implementation(projects.common.routing)
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.tangem.feature.stories.api
|
||||
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
|
||||
interface StoriesComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val storyId: String,
|
||||
val nextScreen: AppRoute,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, StoriesComponent>
|
||||
}
|
||||
1
features/stories/impl/.gitignore
vendored
Normal file
1
features/stories/impl/.gitignore
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
43
features/stories/impl/build.gradle.kts
Normal file
43
features/stories/impl/build.gradle.kts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
plugins {
|
||||
alias(deps.plugins.android.library)
|
||||
alias(deps.plugins.kotlin.android)
|
||||
alias(deps.plugins.kotlin.kapt)
|
||||
alias(deps.plugins.hilt.android)
|
||||
id("configuration")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.tangem.features.stories.impl"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
/** Feature modules */
|
||||
implementation(projects.features.stories.api)
|
||||
implementation(projects.features.swap.api)
|
||||
|
||||
/** Domain modules */
|
||||
implementation(projects.domain.promo)
|
||||
implementation(projects.domain.promo.models)
|
||||
|
||||
/** Project - Common */
|
||||
implementation(projects.common.routing)
|
||||
implementation(projects.common.ui)
|
||||
|
||||
/** Project - Core */
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.decompose)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.ui)
|
||||
|
||||
/** AndroidX */
|
||||
implementation(deps.androidx.activity.compose)
|
||||
implementation(deps.lifecycle.compose)
|
||||
|
||||
/** Others */
|
||||
implementation(deps.timber)
|
||||
implementation(deps.arrow.core)
|
||||
|
||||
/** DI */
|
||||
implementation(deps.hilt.android)
|
||||
kapt(deps.hilt.kapt)
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.feature.stories.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.Stable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.tangem.common.ui.swapStoriesScreen.SwapStoriesScreen
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.feature.stories.api.StoriesComponent
|
||||
import com.tangem.feature.stories.impl.model.StoriesModel
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
@Stable
|
||||
internal class DefaultStoriesComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted params: StoriesComponent.Params,
|
||||
) : StoriesComponent, AppComponentContext by context {
|
||||
|
||||
private val model: StoriesModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
val state = model.state.collectAsStateWithLifecycle()
|
||||
SwapStoriesScreen(state.value)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : StoriesComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: StoriesComponent.Params): DefaultStoriesComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.feature.stories.impl.di
|
||||
|
||||
import com.tangem.feature.stories.api.StoriesComponent
|
||||
import com.tangem.feature.stories.impl.DefaultStoriesComponent
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface StoriesComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindSwapStoriesComponentFactory(factory: DefaultStoriesComponent.Factory): StoriesComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.feature.stories.impl.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.feature.stories.impl.model.StoriesModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface StoriesModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(StoriesModel::class)
|
||||
fun bindStoriesModel(model: StoriesModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.tangem.feature.stories.impl.model
|
||||
|
||||
import com.tangem.common.ui.swapStoriesScreen.SwapStoriesFactory
|
||||
import com.tangem.common.ui.swapStoriesScreen.SwapStoriesUM
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.domain.promo.GetStoryContentUseCase
|
||||
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
|
||||
import com.tangem.domain.promo.models.StoryContentIds
|
||||
import com.tangem.feature.stories.api.StoriesComponent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class StoriesModel @Inject constructor(
|
||||
paramsContainer: ParamsContainer,
|
||||
private val router: Router,
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getStoryContentUseCase: GetStoryContentUseCase,
|
||||
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
|
||||
) : Model() {
|
||||
val state: StateFlow<SwapStoriesUM> get() = _state
|
||||
|
||||
private val params = paramsContainer.require<StoriesComponent.Params>()
|
||||
private val _state: MutableStateFlow<SwapStoriesUM> = MutableStateFlow(
|
||||
value = SwapStoriesUM.Empty,
|
||||
)
|
||||
|
||||
init {
|
||||
initStories()
|
||||
}
|
||||
|
||||
private fun openScreen() {
|
||||
modelScope.launch {
|
||||
shouldShowSwapStoriesUseCase.neverToShow()
|
||||
router.pop()
|
||||
router.push(params.nextScreen)
|
||||
}
|
||||
}
|
||||
|
||||
private fun initStories() {
|
||||
modelScope.launch {
|
||||
getStoryContentUseCase(params.storyId).fold(
|
||||
ifLeft = {
|
||||
Timber.e("Unable to load stories for ${StoryContentIds.STORY_FIRST_TIME_SWAP.id}")
|
||||
openScreen() // Fallback to target screen
|
||||
},
|
||||
ifRight = { swapStory ->
|
||||
_state.update {
|
||||
SwapStoriesFactory.createStoriesState(
|
||||
swapStory = swapStory,
|
||||
onStoriesClose = ::openScreen,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,7 @@ internal fun TokenDetailsBalanceBlock(
|
|||
end.linkTo(anchor = parent.end)
|
||||
bottom.linkTo(anchor = parent.bottom, margin = spacing12)
|
||||
},
|
||||
containerColor = TangemTheme.colors.background.primary,
|
||||
contentPadding = PaddingValues(horizontal = TangemTheme.dimens.spacing12),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -105,6 +105,7 @@ dependencies {
|
|||
implementation(projects.features.markets.api)
|
||||
implementation(projects.features.onramp.api)
|
||||
implementation(projects.features.onboardingV2.api)
|
||||
implementation(projects.features.swap.api)
|
||||
|
||||
/** Common modules */
|
||||
implementation(projects.common)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
|||
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
|
||||
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -12,10 +13,12 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletActionButtonsSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletTokenListSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class MultiWalletContentLoader(
|
||||
|
|
@ -31,6 +34,8 @@ internal class MultiWalletContentLoader(
|
|||
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
|
||||
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
|
||||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
|
|
@ -56,6 +61,12 @@ internal class MultiWalletContentLoader(
|
|||
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
|
||||
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
|
||||
),
|
||||
MultiWalletActionButtonsSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
shouldShowSwapStoriesUseCase = shouldShowSwapStoriesUseCase,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
|||
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
|
||||
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
|
|
@ -13,6 +14,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import javax.inject.Inject
|
||||
|
||||
|
|
@ -29,6 +31,8 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
|
||||
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
|
||||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
) {
|
||||
|
||||
|
|
@ -46,6 +50,8 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
|
|||
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
|
||||
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
shouldShowSwapStoriesUseCase = shouldShowSwapStoriesUseCase,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
deepLinksRegistry = deepLinksRegistry,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
|||
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
|
|
@ -11,10 +12,12 @@ import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarnin
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore
|
||||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletActionButtonsSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.SingleWalletWithTokenListSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
internal class SingleWalletWithTokenContentLoader(
|
||||
|
|
@ -29,6 +32,8 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
private val tokenListStore: MultiWalletTokenListStore,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
) : WalletContentLoader(id = userWallet.walletId) {
|
||||
|
||||
|
|
@ -53,6 +58,12 @@ internal class SingleWalletWithTokenContentLoader(
|
|||
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
|
||||
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
|
||||
),
|
||||
MultiWalletActionButtonsSubscriber(
|
||||
userWallet = userWallet,
|
||||
stateHolder = stateHolder,
|
||||
shouldShowSwapStoriesUseCase = shouldShowSwapStoriesUseCase,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
|
|||
|
||||
import com.tangem.core.deeplink.DeepLinksRegistry
|
||||
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
|
||||
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
|
||||
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
|
||||
|
|
@ -12,6 +13,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList
|
|||
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import javax.inject.Inject
|
||||
|
||||
// TODO: Refactor
|
||||
|
|
@ -26,6 +28,8 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
|
||||
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
|
||||
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
|
||||
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
private val deepLinksRegistry: DeepLinksRegistry,
|
||||
) {
|
||||
|
||||
|
|
@ -42,6 +46,8 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
|
|||
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
|
||||
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
|
||||
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
|
||||
shouldShowSwapStoriesUseCase = shouldShowSwapStoriesUseCase,
|
||||
swapFeatureToggles = swapFeatureToggles,
|
||||
deepLinksRegistry = deepLinksRegistry,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -143,6 +143,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
override val dimContent: Boolean,
|
||||
override val onClick: () -> Unit,
|
||||
val isInProgress: Boolean = false,
|
||||
val showBadge: Boolean = false,
|
||||
) : WalletManageButton(
|
||||
config = ActionButtonConfig(
|
||||
text = TextReference.Res(id = R.string.swapping_swap_action),
|
||||
|
|
@ -151,6 +152,7 @@ internal sealed class WalletManageButton(val config: ActionButtonConfig) {
|
|||
enabled = enabled,
|
||||
dimContent = dimContent,
|
||||
isInProgress = isInProgress,
|
||||
showBadge = showBadge,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.showSwapBadge
|
||||
|
||||
internal class UpdateMultiWalletActionButtonBadgeTransformer(
|
||||
private val showSwapBadge: Boolean,
|
||||
userWalletId: UserWalletId,
|
||||
) : WalletStateTransformer(userWalletId) {
|
||||
override fun transform(prevState: WalletState): WalletState {
|
||||
return when (prevState) {
|
||||
is WalletState.MultiCurrency.Content -> {
|
||||
prevState.copy(buttons = prevState.showSwapBadge(showBadge = showSwapBadge))
|
||||
}
|
||||
else -> prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -24,4 +24,15 @@ private fun WalletState.MultiCurrency.Content.changeAvailability(enabled: Boolea
|
|||
}
|
||||
}
|
||||
.toPersistentList()
|
||||
}
|
||||
|
||||
internal fun WalletState.MultiCurrency.Content.showSwapBadge(showBadge: Boolean): PersistentList<WalletManageButton> {
|
||||
return buttons
|
||||
.map { action ->
|
||||
when (action) {
|
||||
is WalletManageButton.Swap -> action.copy(showBadge = showBadge)
|
||||
else -> action
|
||||
}
|
||||
}
|
||||
.toPersistentList()
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.subscribers
|
||||
|
||||
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
|
||||
import com.tangem.domain.wallets.models.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.UpdateMultiWalletActionButtonBadgeTransformer
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
internal class MultiWalletActionButtonsSubscriber(
|
||||
private val userWallet: UserWallet,
|
||||
private val stateHolder: WalletStateController,
|
||||
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
) : WalletSubscriber() {
|
||||
override fun create(coroutineScope: CoroutineScope): Flow<*> = shouldShowSwapStoriesUseCase()
|
||||
.distinctUntilChanged()
|
||||
.map { showSwapBadge ->
|
||||
val showBadge = swapFeatureToggles.isPromoStoriesEnabled && showSwapBadge
|
||||
stateHolder.update(
|
||||
UpdateMultiWalletActionButtonBadgeTransformer(
|
||||
userWalletId = userWallet.walletId,
|
||||
showSwapBadge = showBadge,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -28,6 +28,8 @@ import com.tangem.domain.demo.IsDemoCardUseCase
|
|||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.markets.TokenMarketParams
|
||||
import com.tangem.domain.onramp.model.OnrampSource
|
||||
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
|
||||
import com.tangem.domain.promo.models.StoryContentIds
|
||||
import com.tangem.domain.redux.ReduxStateHolder
|
||||
import com.tangem.domain.staking.model.stakekit.Yield
|
||||
import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase
|
||||
|
|
@ -51,6 +53,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensLis
|
|||
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
|
||||
import com.tangem.features.onramp.OnrampFeatureToggles
|
||||
import com.tangem.features.swap.SwapFeatureToggles
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -110,6 +113,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
|
||||
private val getExploreUrlUseCase: GetExploreUrlUseCase,
|
||||
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
|
||||
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
private val reduxStateHolder: ReduxStateHolder,
|
||||
|
|
@ -119,6 +123,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
private val appRouter: AppRouter,
|
||||
private val rampStateManager: RampStateManager,
|
||||
private val onrampFeatureToggles: OnrampFeatureToggles,
|
||||
private val swapFeatureToggles: SwapFeatureToggles,
|
||||
) : BaseWalletClickIntents(), WalletCurrencyActionsClickIntents {
|
||||
|
||||
override fun onSendClick(
|
||||
|
|
@ -430,11 +435,16 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
return
|
||||
}
|
||||
|
||||
onMultiWalletActionClick(
|
||||
statusFlow = rampStateManager.getExpressInitializationStatus(userWalletId),
|
||||
route = AppRoute.SwapCrypto(userWalletId = userWalletId),
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSwap,
|
||||
)
|
||||
viewModelScope.launch {
|
||||
val swapRoute = getSwapRoute(
|
||||
AppRoute.SwapCrypto(userWalletId = userWalletId),
|
||||
)
|
||||
onMultiWalletActionClick(
|
||||
statusFlow = rampStateManager.getExpressInitializationStatus(userWalletId),
|
||||
route = swapRoute,
|
||||
eventCreator = MainScreenAnalyticsEvent::ButtonSwap,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMultiWalletBuyClick(userWalletId: UserWalletId) {
|
||||
|
|
@ -597,4 +607,19 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
|
|||
event = WalletEvent.ShowAlert(state = WalletAlertState.ProvidersStillLoading),
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getSwapRoute(targetRoute: AppRoute): AppRoute {
|
||||
val isSwapStoriesEnabled = swapFeatureToggles.isPromoStoriesEnabled
|
||||
val shouldShowSwapStories = shouldShowSwapStoriesUseCase.invokeSync()
|
||||
val showSwapStories = shouldShowSwapStories && isSwapStoriesEnabled
|
||||
|
||||
return if (showSwapStories) {
|
||||
AppRoute.Stories(
|
||||
storyId = StoryContentIds.STORY_FIRST_TIME_SWAP.id,
|
||||
nextScreen = targetRoute,
|
||||
)
|
||||
} else {
|
||||
targetRoute
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -205,6 +205,9 @@ include(":features:markets:impl")
|
|||
|
||||
include(":features:onramp:api")
|
||||
include(":features:onramp:impl")
|
||||
|
||||
include(":features:stories:api")
|
||||
include(":features:stories:impl")
|
||||
// endregion Feature modules
|
||||
|
||||
// region Domain modules
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue