Updated on 2026-08-14
This commit is contained in:
parent
94a4486443
commit
96ce2fc718
22 changed files with 784 additions and 57 deletions
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.onramp.alloffers
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
||||
internal interface AllOffersComponent : ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(
|
||||
val userWallet: UserWallet,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, AllOffersComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package com.tangem.features.onramp.alloffers
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.onramp.alloffers.model.AllOffersModel
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultAllOffersComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted params: AllOffersComponent.Params,
|
||||
) : AllOffersComponent, AppComponentContext by context {
|
||||
|
||||
private val model: AllOffersModel = getOrCreateModel(params)
|
||||
|
||||
override fun dismiss() {
|
||||
model.dismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
// TODO to be continued in [REDACTED_TASK_KEY]
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : AllOffersComponent.Factory {
|
||||
override fun create(context: AppComponentContext, params: AllOffersComponent.Params): DefaultAllOffersComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.alloffers.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onramp.alloffers.model.AllOffersModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface AllOffersComponentModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(AllOffersModel::class)
|
||||
fun bindAllOffersModel(model: AllOffersModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package com.tangem.features.onramp.alloffers.di
|
||||
|
||||
import com.tangem.features.onramp.alloffers.AllOffersComponent
|
||||
import com.tangem.features.onramp.alloffers.DefaultAllOffersComponent
|
||||
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 AllOffersComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindAllOffersComponentFactory(factory: DefaultAllOffersComponent.Factory): AllOffersComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.features.onramp.alloffers.model
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.features.onramp.alloffers.AllOffersComponent
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class AllOffersModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params: AllOffersComponent.Params = paramsContainer.require()
|
||||
|
||||
fun dismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ import com.tangem.core.decompose.context.AppComponentContext
|
|||
import com.tangem.core.decompose.context.childByContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.features.onramp.alloffers.AllOffersComponent
|
||||
import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent
|
||||
import com.tangem.features.onramp.mainv2.entity.OnrampV2MainBottomSheetConfig
|
||||
import com.tangem.features.onramp.mainv2.model.OnrampV2MainComponentModel
|
||||
|
|
@ -26,6 +27,7 @@ internal class DefaultOnrampV2MainComponent @AssistedInject constructor(
|
|||
@Assisted private val params: OnrampV2MainComponent.Params,
|
||||
private val confirmResidencyComponentFactory: ConfirmResidencyComponent.Factory,
|
||||
private val selectCurrencyComponentFactory: SelectCurrencyComponent.Factory,
|
||||
private val allOffersComponentFactory: AllOffersComponent.Factory,
|
||||
) : OnrampV2MainComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnrampV2MainComponentModel = getOrCreateModel(params)
|
||||
|
|
@ -67,6 +69,14 @@ internal class DefaultOnrampV2MainComponent @AssistedInject constructor(
|
|||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
OnrampV2MainBottomSheetConfig.AllOffers -> allOffersComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = AllOffersComponent.Params(
|
||||
userWallet = model.userWallet,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@ class DefaultOnrampV2MainFeatureToggle(
|
|||
private val featureTogglesManager: FeatureTogglesManager,
|
||||
) : OnrampV2MainFeatureToggle {
|
||||
override val isOnrampNewMainEnabled: Boolean
|
||||
get() = featureTogglesManager.isFeatureEnabled("NEW_ONRAMP_RECEIVE_ENABLED")
|
||||
get() = featureTogglesManager.isFeatureEnabled("NEW_ONRAMP_MAIN_ENABLED")
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.onramp.mainv2.entity
|
||||
|
||||
import androidx.compose.runtime.Immutable
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
|
|
@ -20,22 +21,32 @@ internal sealed interface OnrampOffersBlockUM {
|
|||
|
||||
data class Content(
|
||||
override val isBlockVisible: Boolean,
|
||||
val offers: ImmutableList<OnrampOfferUM>,
|
||||
val recentOffer: OnrampOfferUM?,
|
||||
val recommended: ImmutableList<OnrampOfferUM>,
|
||||
val onrampAllOffersButtonConfig: OnrampAllOffersButtonConfig?,
|
||||
) : OnrampOffersBlockUM
|
||||
}
|
||||
|
||||
internal data class OnrampOfferUM(
|
||||
val category: OnrampOfferCategory,
|
||||
val advantages: OnrampOfferAdvantages,
|
||||
val category: OnrampOfferCategoryUM,
|
||||
val advantages: OnrampOfferAdvantagesUM,
|
||||
val paymentMethod: OnrampPaymentMethod,
|
||||
val providerId: String,
|
||||
val providerName: String,
|
||||
val rate: String,
|
||||
val diff: TextReference?,
|
||||
val onBuyClicked: () -> Unit,
|
||||
)
|
||||
|
||||
internal enum class OnrampOfferCategory {
|
||||
internal enum class OnrampOfferCategoryUM {
|
||||
RecentlyUsed, Recommended
|
||||
}
|
||||
|
||||
internal enum class OnrampOfferAdvantages {
|
||||
internal enum class OnrampOfferAdvantagesUM {
|
||||
Default, BestRate, Fastest
|
||||
}
|
||||
}
|
||||
|
||||
internal data class OnrampAllOffersButtonConfig(
|
||||
val title: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -10,4 +10,7 @@ sealed interface OnrampV2MainBottomSheetConfig {
|
|||
|
||||
@Serializable
|
||||
data object CurrenciesList : OnrampV2MainBottomSheetConfig
|
||||
|
||||
@Serializable
|
||||
data object AllOffers : OnrampV2MainBottomSheetConfig
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
package com.tangem.features.onramp.mainv2.entity.factory
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.percent
|
||||
import com.tangem.domain.onramp.model.*
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.main.entity.OnrampIntents
|
||||
import com.tangem.features.onramp.mainv2.entity.*
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.StringsSigns.MINUS
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
|
||||
internal class OnrampOffersStateFactory(
|
||||
private val currentStateProvider: Provider<OnrampV2MainComponentUM>,
|
||||
private val onrampIntents: OnrampIntents,
|
||||
) {
|
||||
|
||||
fun getOnShowOffersState(offers: List<OnrampOffersBlock>): OnrampV2MainComponentUM {
|
||||
val currentState = currentStateProvider.invoke()
|
||||
return when (currentState) {
|
||||
is OnrampV2MainComponentUM.Content -> {
|
||||
currentState.copy(
|
||||
offersBlockState = mapOnrampOffersBlockToUM(offers),
|
||||
)
|
||||
}
|
||||
is OnrampV2MainComponentUM.InitialLoading -> {
|
||||
currentState
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapOnrampOffersBlockToUM(offersBlocks: List<OnrampOffersBlock>): OnrampOffersBlockUM.Content {
|
||||
val allOffersUM = mutableListOf<OnrampOfferUM>()
|
||||
offersBlocks.map { block ->
|
||||
block.offers.forEach { offer ->
|
||||
when (val currentQuote = offer.quote) {
|
||||
is OnrampQuote.AmountError,
|
||||
is OnrampQuote.Error,
|
||||
-> Unit
|
||||
is OnrampQuote.Data -> {
|
||||
allOffersUM.add(
|
||||
OnrampOfferUM(
|
||||
category = mapOfferCategoryDTOtoUM(block.category),
|
||||
advantages = mapOfferAdvantagesDTOtoUM(offer.advantages),
|
||||
paymentMethod = currentQuote.paymentMethod,
|
||||
providerId = currentQuote.provider.id,
|
||||
providerName = currentQuote.provider.info.name,
|
||||
rate = currentQuote.toAmount.value.format {
|
||||
crypto(
|
||||
symbol = currentQuote.toAmount.symbol,
|
||||
decimals = currentQuote.toAmount.decimals,
|
||||
)
|
||||
},
|
||||
diff = offer.rateDif?.let { diff ->
|
||||
stringReference("$MINUS${diff.format { percent() }}")
|
||||
},
|
||||
onBuyClicked = {
|
||||
onrampIntents.onBuyClick(
|
||||
quote = OnrampProviderWithQuote.Data(
|
||||
provider = offer.quote.provider,
|
||||
paymentMethod = currentQuote.paymentMethod,
|
||||
toAmount = currentQuote.toAmount,
|
||||
fromAmount = currentQuote.fromAmount,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return OnrampOffersBlockUM.Content(
|
||||
isBlockVisible = false,
|
||||
recentOffer = allOffersUM.firstOrNull { it.category == OnrampOfferCategoryUM.RecentlyUsed },
|
||||
recommended = allOffersUM.filter { it.category == OnrampOfferCategoryUM.Recommended }.toPersistentList(),
|
||||
onrampAllOffersButtonConfig = if (offersBlocks.any { it.hasMoreOffers }) {
|
||||
OnrampAllOffersButtonConfig(
|
||||
title = TextReference.Res(R.string.onramp_all_offers_button_title),
|
||||
onClick = { onrampIntents.openProviders() },
|
||||
)
|
||||
} else {
|
||||
null
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun mapOfferCategoryDTOtoUM(category: OnrampOfferCategory): OnrampOfferCategoryUM {
|
||||
return when (category) {
|
||||
OnrampOfferCategory.Recent -> OnrampOfferCategoryUM.RecentlyUsed
|
||||
OnrampOfferCategory.Recommended -> OnrampOfferCategoryUM.Recommended
|
||||
}
|
||||
}
|
||||
|
||||
private fun mapOfferAdvantagesDTOtoUM(advantages: OnrampOfferAdvantages): OnrampOfferAdvantagesUM {
|
||||
return when (advantages) {
|
||||
OnrampOfferAdvantages.Default -> OnrampOfferAdvantagesUM.Default
|
||||
OnrampOfferAdvantages.BestRate -> OnrampOfferAdvantagesUM.BestRate
|
||||
OnrampOfferAdvantages.Fastest -> OnrampOfferAdvantagesUM.Fastest
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,6 +20,7 @@ import com.tangem.features.onramp.main.entity.OnrampLastUpdate
|
|||
import com.tangem.features.onramp.mainv2.OnrampV2MainComponent
|
||||
import com.tangem.features.onramp.mainv2.entity.*
|
||||
import com.tangem.features.onramp.mainv2.entity.factory.OnrampAmountButtonUMStateFactory
|
||||
import com.tangem.features.onramp.mainv2.entity.factory.OnrampOffersStateFactory
|
||||
import com.tangem.features.onramp.mainv2.entity.factory.OnrampV2AmountStateFactory
|
||||
import com.tangem.features.onramp.mainv2.entity.factory.OnrampV2StateFactory
|
||||
import com.tangem.features.onramp.utils.sendOnrampErrorEvent
|
||||
|
|
@ -32,7 +33,7 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
internal class OnrampV2MainComponentModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
|
|
@ -44,6 +45,7 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
private val getOnrampQuotesUseCase: GetOnrampQuotesUseCase,
|
||||
private val fetchPairsUseCase: OnrampFetchPairsUseCase,
|
||||
private val amountInputManager: InputManager,
|
||||
private val getOnrampOffersUseCase: GetOnrampOffersUseCase,
|
||||
paramsContainer: ParamsContainer,
|
||||
getWalletsUseCase: GetWalletsUseCase,
|
||||
) : Model(), OnrampIntents {
|
||||
|
|
@ -58,6 +60,13 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
OnrampAmountButtonUMStateFactory()
|
||||
}
|
||||
|
||||
private val onrampOffersStateFactory: OnrampOffersStateFactory by lazy(LazyThreadSafetyMode.NONE) {
|
||||
OnrampOffersStateFactory(
|
||||
currentStateProvider = Provider { _state.value },
|
||||
onrampIntents = this,
|
||||
)
|
||||
}
|
||||
|
||||
private val stateFactory = OnrampV2StateFactory(
|
||||
currentStateProvider = Provider { _state.value },
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
|
|
@ -94,6 +103,7 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
subscribeToAmountChanges()
|
||||
subscribeToCountryAndCurrencyUpdates()
|
||||
subscribeToQuotesUpdate()
|
||||
subscribeOnOffers()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
@ -117,11 +127,19 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun onBuyClick(quote: OnrampProviderWithQuote.Data) {
|
||||
// TODO in [REDACTED_TASK_KEY] to be continued
|
||||
val currentContentState = state.value as? OnrampV2MainComponentUM.Content ?: return
|
||||
analyticsEventHandler.send(
|
||||
OnrampAnalyticsEvent.OnBuyClick(
|
||||
providerName = quote.provider.info.name,
|
||||
currency = currentContentState.amountBlockState.currencyUM.code,
|
||||
tokenSymbol = params.cryptoCurrency.symbol,
|
||||
),
|
||||
)
|
||||
params.openRedirectPage(quote)
|
||||
}
|
||||
|
||||
override fun openProviders() {
|
||||
// TODO in [REDACTED_TASK_KEY] to be continued
|
||||
bottomSheetNavigation.activate(OnrampV2MainBottomSheetConfig.AllOffers)
|
||||
}
|
||||
|
||||
override fun onRefresh() {
|
||||
|
|
@ -172,6 +190,20 @@ internal class OnrampV2MainComponentModel @Inject constructor(
|
|||
router.pop()
|
||||
}
|
||||
|
||||
private fun subscribeOnOffers() = modelScope.launch {
|
||||
getOnrampOffersUseCase.invoke(
|
||||
userWalletId = userWallet.walletId,
|
||||
cryptoCurrencyId = params.cryptoCurrency.id,
|
||||
).collectLatest { maybeOffers ->
|
||||
maybeOffers.fold(
|
||||
ifLeft = ::handleOnrampError,
|
||||
ifRight = { offers ->
|
||||
_state.update { onrampOffersStateFactory.getOnShowOffersState(offers) }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToAmountChanges() = modelScope.launch {
|
||||
amountInputManager.query
|
||||
.filter(String::isNotEmpty)
|
||||
|
|
|
|||
|
|
@ -1,13 +1,42 @@
|
|||
package com.tangem.features.onramp.mainv2.ui
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.slideInVertically
|
||||
import androidx.compose.animation.slideOutVertically
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.*
|
||||
import com.tangem.core.ui.components.buttons.common.TangemButtonSize
|
||||
import com.tangem.core.ui.extensions.*
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.res.TangemThemePreview
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.model.PaymentMethodType
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.mainv2.entity.OnrampOfferAdvantagesUM
|
||||
import com.tangem.features.onramp.mainv2.entity.OnrampOfferCategoryUM
|
||||
import com.tangem.features.onramp.mainv2.entity.OnrampOfferUM
|
||||
import com.tangem.features.onramp.mainv2.entity.OnrampOffersBlockUM
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
@Composable
|
||||
internal fun OnrampOffersContent(state: OnrampOffersBlockUM) {
|
||||
|
|
@ -23,10 +52,330 @@ internal fun OnrampOffersContent(state: OnrampOffersBlockUM) {
|
|||
),
|
||||
label = "Offers block animation",
|
||||
) {
|
||||
Text(
|
||||
text = "Some offers",
|
||||
style = TangemTheme.typography.head,
|
||||
if (state is OnrampOffersBlockUM.Content) {
|
||||
Column(modifier = Modifier.fillMaxWidth()) {
|
||||
state.recentOffer?.let { recentOffer ->
|
||||
Column {
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
text = stringResourceSafe(R.string.onramp_recently_used_title),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
||||
SpacerH(8.dp)
|
||||
|
||||
Offer(recentOffer)
|
||||
|
||||
SpacerH(16.dp)
|
||||
}
|
||||
}
|
||||
|
||||
Text(
|
||||
modifier = Modifier.padding(start = 12.dp),
|
||||
text = stringResourceSafe(R.string.onramp_recommended_title),
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
||||
SpacerH(8.dp)
|
||||
|
||||
state.recommended.fastForEach {
|
||||
Offer(it)
|
||||
SpacerH(8.dp)
|
||||
}
|
||||
|
||||
state.onrampAllOffersButtonConfig?.let {
|
||||
SpacerH(12.dp)
|
||||
SecondaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = it.title.resolveReference(),
|
||||
onClick = it.onClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Offer(onrampOfferUM: OnrampOfferUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
)
|
||||
.padding(12.dp),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Column(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
OfferHeader(advantage = onrampOfferUM.advantages)
|
||||
RateBlock(rate = onrampOfferUM.rate, diff = onrampOfferUM.diff)
|
||||
}
|
||||
SpacerWMax()
|
||||
SecondaryButton(
|
||||
size = TangemButtonSize.RoundedAction,
|
||||
text = stringResourceSafe(R.string.common_buy),
|
||||
onClick = onrampOfferUM.onBuyClicked,
|
||||
)
|
||||
}
|
||||
SpacerH(10.dp)
|
||||
HorizontalDivider(color = TangemTheme.colors.stroke.primary)
|
||||
SpacerH(12.dp)
|
||||
PaymentBlockInOffer(
|
||||
paymentMethod = onrampOfferUM.paymentMethod,
|
||||
providerName = onrampOfferUM.providerName,
|
||||
)
|
||||
// TODO in [REDACTED_TASK_KEY] to be continued
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun OfferHeader(advantage: OnrampOfferAdvantagesUM) {
|
||||
when (advantage) {
|
||||
OnrampOfferAdvantagesUM.Default -> {
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.onramp_title_you_get),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
OnrampOfferAdvantagesUM.BestRate -> {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_best_rate_16),
|
||||
tint = TangemTheme.colors.icon.accent,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.express_provider_best_rate),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.icon.accent,
|
||||
)
|
||||
}
|
||||
}
|
||||
OnrampOfferAdvantagesUM.Fastest -> {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_fastest_16),
|
||||
tint = TangemTheme.colors.icon.attention,
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.onramp_offer_type_fastet),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.icon.attention,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RateBlock(rate: String, diff: TextReference?) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
Text(
|
||||
text = rate,
|
||||
style = TangemTheme.typography.subtitle1,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
diff?.let {
|
||||
Text(
|
||||
modifier = Modifier
|
||||
.background(
|
||||
color = TangemTheme.colors.text.warning.copy(alpha = 0.1f),
|
||||
shape = RoundedCornerShape(4.dp),
|
||||
)
|
||||
.padding(horizontal = 4.dp),
|
||||
text = it.resolveReference(),
|
||||
style = TangemTheme.typography.caption1,
|
||||
color = TangemTheme.colors.text.warning,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PaymentBlockInOffer(paymentMethod: OnrampPaymentMethod, providerName: String) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
Icon(
|
||||
modifier = Modifier.size(16.dp),
|
||||
imageVector = ImageVector.vectorResource(R.drawable.ic_clock_24),
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
contentDescription = null,
|
||||
)
|
||||
|
||||
SpacerW(2.dp)
|
||||
|
||||
TimingBlock(speed = paymentMethod.type.getProcessingSpeed())
|
||||
|
||||
SpacerW(6.dp)
|
||||
|
||||
DrawDot(color = TangemTheme.colors.text.tertiary)
|
||||
|
||||
SpacerW(6.dp)
|
||||
|
||||
Text(
|
||||
text = providerName,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
||||
SpacerWMax()
|
||||
|
||||
Text(
|
||||
text = stringResourceSafe(R.string.onramp_pay_with),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
|
||||
SpacerW(4.dp)
|
||||
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier.sizeIn(maxWidth = 38.dp, maxHeight = 16.dp),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(paymentMethod.imageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(false)
|
||||
.build(),
|
||||
loading = {
|
||||
TextShimmer(
|
||||
style = TangemTheme.typography.body1,
|
||||
modifier = Modifier.width(40.dp),
|
||||
)
|
||||
},
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun TimingBlock(speed: PaymentMethodType.PaymentSpeed) {
|
||||
val timingText = when (speed) {
|
||||
PaymentMethodType.PaymentSpeed.Instant -> {
|
||||
stringResourceSafe(id = R.string.onramp_instant_status)
|
||||
}
|
||||
PaymentMethodType.PaymentSpeed.FewMin -> {
|
||||
stringResourceSafe(
|
||||
id = R.string.onramp_timing_minutes,
|
||||
FEW_MINS_VALUE,
|
||||
)
|
||||
}
|
||||
PaymentMethodType.PaymentSpeed.FewDays -> {
|
||||
pluralStringResourceSafe(
|
||||
id = R.plurals.onramp_timing_days,
|
||||
count = FEW_DAYS_VALUE,
|
||||
FEW_DAYS_VALUE,
|
||||
)
|
||||
}
|
||||
PaymentMethodType.PaymentSpeed.PlentyDays -> {
|
||||
pluralStringResourceSafe(
|
||||
id = R.plurals.onramp_timing_days,
|
||||
count = PLENTY_DAYS_VALUE,
|
||||
PLENTY_DAYS_VALUE,
|
||||
)
|
||||
}
|
||||
PaymentMethodType.PaymentSpeed.Unknown -> ""
|
||||
}
|
||||
|
||||
Text(
|
||||
text = timingText,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DrawDot(color: Color) {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.size(4.dp)
|
||||
.drawWithCache {
|
||||
val radius = size.minDimension / 2f
|
||||
onDrawBehind {
|
||||
drawCircle(
|
||||
color = color,
|
||||
radius = radius,
|
||||
)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
// will be hardcoded till server will be ready to provide this values
|
||||
private const val FEW_MINS_VALUE = "3-5"
|
||||
private const val FEW_DAYS_VALUE = 3
|
||||
private const val PLENTY_DAYS_VALUE = 5
|
||||
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
private fun OnrampOffersContentPreview() {
|
||||
val state = OnrampOffersBlockUM.Content(
|
||||
isBlockVisible = true,
|
||||
recentOffer = OnrampOfferUM(
|
||||
category = OnrampOfferCategoryUM.RecentlyUsed,
|
||||
advantages = OnrampOfferAdvantagesUM.Default,
|
||||
paymentMethod = OnrampPaymentMethod(
|
||||
id = "card",
|
||||
name = "Card",
|
||||
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
|
||||
type = PaymentMethodType.CARD,
|
||||
),
|
||||
providerId = "providerId3",
|
||||
providerName = "Simplex",
|
||||
rate = "0,00045334 BTC",
|
||||
diff = stringReference("–27%"),
|
||||
onBuyClicked = {},
|
||||
),
|
||||
recommended = persistentListOf(
|
||||
OnrampOfferUM(
|
||||
category = OnrampOfferCategoryUM.Recommended,
|
||||
advantages = OnrampOfferAdvantagesUM.BestRate,
|
||||
paymentMethod = OnrampPaymentMethod(
|
||||
id = "card",
|
||||
name = "Card",
|
||||
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
|
||||
type = PaymentMethodType.CARD,
|
||||
),
|
||||
providerId = "providerId1",
|
||||
providerName = "Simplex",
|
||||
rate = "0,0245334 BTC",
|
||||
diff = null,
|
||||
onBuyClicked = {},
|
||||
),
|
||||
OnrampOfferUM(
|
||||
category = OnrampOfferCategoryUM.Recommended,
|
||||
advantages = OnrampOfferAdvantagesUM.Fastest,
|
||||
paymentMethod = OnrampPaymentMethod(
|
||||
id = "card",
|
||||
name = "Card",
|
||||
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
|
||||
type = PaymentMethodType.CARD,
|
||||
),
|
||||
providerId = "providerId2",
|
||||
providerName = "Simplex",
|
||||
rate = "0,00145334 BTC",
|
||||
diff = stringReference("–0.07%"),
|
||||
onBuyClicked = {},
|
||||
),
|
||||
),
|
||||
onrampAllOffersButtonConfig = null,
|
||||
)
|
||||
TangemThemePreview {
|
||||
OnrampOffersContent(state)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.onramp.mainv2.ui
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.background
|
||||
|
|
@ -51,6 +52,7 @@ internal fun OnrampV2AmountContent(state: OnrampV2MainComponentUM.Content, modif
|
|||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.background(
|
||||
color = TangemTheme.colors.background.action,
|
||||
shape = RoundedCornerShape(size = TangemTheme.dimens.radius16),
|
||||
|
|
@ -61,16 +63,14 @@ internal fun OnrampV2AmountContent(state: OnrampV2MainComponentUM.Content, modif
|
|||
) {
|
||||
OnrampHeaderTitle()
|
||||
|
||||
SpacerH(12.dp)
|
||||
|
||||
OnrampAmountField(
|
||||
amountField = state.amountBlockState.amountFieldModel,
|
||||
currencyCode = state.amountBlockState.currencyUM.code,
|
||||
)
|
||||
|
||||
SpacerH(8.dp)
|
||||
|
||||
OnrampAmountSecondary(state = state.amountBlockState.secondaryFieldModel)
|
||||
AnimatedVisibility(!state.offersBlockState.isBlockVisible) {
|
||||
OnrampAmountSecondary(state = state.amountBlockState.secondaryFieldModel)
|
||||
}
|
||||
|
||||
SpacerH(20.dp)
|
||||
|
||||
|
|
@ -115,7 +115,8 @@ private fun OnrampAmountField(amountField: AmountFieldModel, currencyCode: Strin
|
|||
modifier = Modifier
|
||||
.focusRequester(requester)
|
||||
.padding(
|
||||
top = TangemTheme.dimens.spacing24,
|
||||
top = TangemTheme.dimens.spacing8,
|
||||
bottom = TangemTheme.dimens.spacing4,
|
||||
start = TangemTheme.dimens.spacing12,
|
||||
end = TangemTheme.dimens.spacing12,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,8 @@ import com.tangem.core.decompose.context.childByContext
|
|||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.features.onramp.component.OnrampComponent
|
||||
import com.tangem.features.onramp.main.OnrampMainComponent
|
||||
import com.tangem.features.onramp.mainv2.OnrampV2MainComponent
|
||||
import com.tangem.features.onramp.mainv2.OnrampV2MainFeatureToggle
|
||||
import com.tangem.features.onramp.redirect.OnrampRedirectComponent
|
||||
import com.tangem.features.onramp.root.entity.OnrampChild
|
||||
import com.tangem.features.onramp.settings.OnrampSettingsComponent
|
||||
|
|
@ -24,13 +26,15 @@ import dagger.assisted.Assisted
|
|||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
@Suppress("UnusedPrivateMember")
|
||||
@Suppress("LongParameterList")
|
||||
internal class DefaultOnrampComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: OnrampComponent.Params,
|
||||
private val settingsComponentFactory: OnrampSettingsComponent.Factory,
|
||||
private val onrampMainComponentFactory: OnrampMainComponent.Factory,
|
||||
private val onrampMainV2ComponentFactory: OnrampV2MainComponent.Factory,
|
||||
private val onrampRedirectComponentFactory: OnrampRedirectComponent.Factory,
|
||||
private val onrampV2MainFeatureToggle: OnrampV2MainFeatureToggle,
|
||||
) : OnrampComponent, AppComponentContext by context {
|
||||
|
||||
private val navigation = StackNavigation<OnrampChild>()
|
||||
|
|
@ -66,24 +70,44 @@ internal class DefaultOnrampComponent @AssistedInject constructor(
|
|||
onBack = navigation::pop,
|
||||
),
|
||||
)
|
||||
OnrampChild.Main -> onrampMainComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = OnrampMainComponent.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
openSettings = { navigation.push(OnrampChild.Settings) },
|
||||
source = params.source,
|
||||
openRedirectPage = {
|
||||
navigation.push(
|
||||
OnrampChild.RedirectPage(
|
||||
quote = it,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
),
|
||||
)
|
||||
},
|
||||
launchSepa = params.launchSepa,
|
||||
),
|
||||
)
|
||||
OnrampChild.Main -> if (onrampV2MainFeatureToggle.isOnrampNewMainEnabled) {
|
||||
onrampMainV2ComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = OnrampV2MainComponent.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
openSettings = { navigation.push(OnrampChild.Settings) },
|
||||
source = params.source,
|
||||
openRedirectPage = {
|
||||
navigation.push(
|
||||
OnrampChild.RedirectPage(
|
||||
quote = it,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
} else {
|
||||
onrampMainComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = OnrampMainComponent.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
openSettings = { navigation.push(OnrampChild.Settings) },
|
||||
source = params.source,
|
||||
openRedirectPage = {
|
||||
navigation.push(
|
||||
OnrampChild.RedirectPage(
|
||||
quote = it,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
),
|
||||
)
|
||||
},
|
||||
launchSepa = params.launchSepa,
|
||||
),
|
||||
)
|
||||
}
|
||||
is OnrampChild.RedirectPage -> onrampRedirectComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = OnrampRedirectComponent.Params(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue