Updated on 2026-08-14
This commit is contained in:
parent
38276b01ea
commit
9378e4fa31
63 changed files with 1434 additions and 75 deletions
|
|
@ -16,6 +16,7 @@ import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent
|
|||
import com.tangem.features.onramp.main.entity.OnrampMainBottomSheetConfig
|
||||
import com.tangem.features.onramp.main.model.OnrampMainComponentModel
|
||||
import com.tangem.features.onramp.main.ui.OnrampMainComponentContent
|
||||
import com.tangem.features.onramp.providers.SelectProviderComponent
|
||||
import com.tangem.features.onramp.selectcurrency.SelectCurrencyComponent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
|
|
@ -26,6 +27,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
|
|||
@Assisted params: OnrampMainComponent.Params,
|
||||
private val confirmResidencyComponentFactory: ConfirmResidencyComponent.Factory,
|
||||
private val selectCurrencyComponentFactory: SelectCurrencyComponent.Factory,
|
||||
private val selectProviderComponentFactory: SelectProviderComponent.Factory,
|
||||
) : OnrampMainComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnrampMainComponentModel = getOrCreateModel(params)
|
||||
|
|
@ -60,6 +62,14 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
|
|||
context = childByContext(componentContext),
|
||||
params = SelectCurrencyComponent.Params(model.bottomSheetNavigation::dismiss),
|
||||
)
|
||||
is OnrampMainBottomSheetConfig.ProvidersList -> selectProviderComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = SelectProviderComponent.Params(
|
||||
onProviderClick = model::onProviderSelected,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
selectedPaymentMethod = config.selectedPaymentMethod,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
|
|
|
|||
|
|
@ -2,11 +2,16 @@ package com.tangem.features.onramp.main
|
|||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
|
||||
internal interface OnrampMainComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(val cryptoCurrency: CryptoCurrency, val openSettings: () -> Unit)
|
||||
data class Params(
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val openSettings: () -> Unit,
|
||||
val openRedirectPage: (quote: OnrampProviderWithQuote.Data) -> Unit,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnrampMainComponent>
|
||||
}
|
||||
|
|
@ -1,9 +1,11 @@
|
|||
package com.tangem.features.onramp.main.entity
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
|
||||
interface OnrampIntents {
|
||||
fun onAmountValueChanged(value: String)
|
||||
fun openSettings()
|
||||
fun openCurrenciesList()
|
||||
fun onBuyClick()
|
||||
fun onBuyClick(quote: OnrampProviderWithQuote.Data)
|
||||
fun openProviders()
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
package com.tangem.features.onramp.main.entity
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
|
@ -10,4 +11,7 @@ internal sealed interface OnrampMainBottomSheetConfig {
|
|||
|
||||
@Serializable
|
||||
data object CurrenciesList : OnrampMainBottomSheetConfig
|
||||
|
||||
@Serializable
|
||||
data class ProvidersList(val selectedPaymentMethod: OnrampPaymentMethod) : OnrampMainBottomSheetConfig
|
||||
}
|
||||
|
|
@ -18,7 +18,6 @@ internal sealed interface OnrampMainComponentUM {
|
|||
val currency: String,
|
||||
val onClose: () -> Unit,
|
||||
val openSettings: () -> Unit,
|
||||
val onBuyClick: () -> Unit,
|
||||
) : OnrampMainComponentUM {
|
||||
override val topBarConfig: OnrampMainTopBarUM = OnrampMainTopBarUM(
|
||||
title = combinedReference(resourceReference(R.string.common_buy), stringReference(" $currency")),
|
||||
|
|
@ -36,7 +35,7 @@ internal sealed interface OnrampMainComponentUM {
|
|||
|
||||
override val buyButtonConfig: BuyButtonConfig = BuyButtonConfig(
|
||||
text = resourceReference(R.string.common_buy),
|
||||
onClick = onBuyClick,
|
||||
onClick = {},
|
||||
enabled = false,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ package com.tangem.features.onramp.main.entity
|
|||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
data class OnrampMainTopBarUM(
|
||||
internal data class OnrampMainTopBarUM(
|
||||
val title: TextReference,
|
||||
val startButtonUM: TopAppBarButtonUM,
|
||||
val endButtonUM: TopAppBarButtonUM,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
package com.tangem.features.onramp.main.entity
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
|
||||
sealed class OnrampProviderBlockUM {
|
||||
data object Empty : OnrampProviderBlockUM()
|
||||
data object Loading : OnrampProviderBlockUM()
|
||||
data class Content(
|
||||
val paymentMethodIconUrl: String,
|
||||
val paymentMethodName: String,
|
||||
val paymentMethod: OnrampPaymentMethod,
|
||||
val providerName: String,
|
||||
val isBestRate: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ internal class OnrampStateFactory(
|
|||
currency = currency,
|
||||
onClose = onClose,
|
||||
openSettings = onrampIntents::openSettings,
|
||||
onBuyClick = onrampIntents::onBuyClick,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.core.ui.format.bigdecimal.crypto
|
|||
import com.tangem.core.ui.format.bigdecimal.fiat
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.domain.onramp.model.OnrampQuote
|
||||
import com.tangem.domain.tokens.model.AmountType
|
||||
import com.tangem.features.onramp.impl.R
|
||||
|
|
@ -55,6 +56,7 @@ internal class OnrampAmountStateFactory(
|
|||
|
||||
return currentState.copy(
|
||||
amountBlockState = amountState.copy(secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading),
|
||||
buyButtonConfig = currentState.buyButtonConfig.copy(enabled = false),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -68,13 +70,52 @@ internal class OnrampAmountStateFactory(
|
|||
secondaryFieldModel = quote.toSecondaryFieldUiModel(amountState),
|
||||
),
|
||||
providerBlockState = quote.toProviderBlockState(),
|
||||
buyButtonConfig = currentState.buyButtonConfig.copy(
|
||||
enabled = quote is OnrampQuote.Data,
|
||||
onClick = {
|
||||
if (quote is OnrampQuote.Data) {
|
||||
onrampIntents.onBuyClick(
|
||||
OnrampProviderWithQuote.Data(
|
||||
provider = quote.provider,
|
||||
paymentMethod = quote.paymentMethod,
|
||||
toAmount = quote.toAmount,
|
||||
fromAmount = quote.fromAmount,
|
||||
),
|
||||
)
|
||||
}
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun getAmountSecondaryUpdatedState(quoteWithProvider: OnrampProviderWithQuote.Data): OnrampMainComponentUM {
|
||||
val currentState = currentStateProvider()
|
||||
if (currentState !is OnrampMainComponentUM.Content) return currentState
|
||||
|
||||
val amountState = currentState.amountBlockState
|
||||
val amount = quoteWithProvider.toAmount.value.format {
|
||||
crypto(symbol = quoteWithProvider.toAmount.symbol, decimals = quoteWithProvider.toAmount.decimals)
|
||||
}
|
||||
return currentState.copy(
|
||||
amountBlockState = amountState.copy(
|
||||
secondaryFieldModel = OnrampAmountSecondaryFieldUM.Content(stringReference(amount)),
|
||||
),
|
||||
providerBlockState = OnrampProviderBlockUM.Content(
|
||||
paymentMethod = quoteWithProvider.paymentMethod,
|
||||
providerName = quoteWithProvider.provider.info.name,
|
||||
isBestRate = true,
|
||||
onClick = onrampIntents::openProviders,
|
||||
),
|
||||
buyButtonConfig = currentState.buyButtonConfig.copy(
|
||||
enabled = true,
|
||||
onClick = { onrampIntents.onBuyClick(quoteWithProvider) },
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun OnrampQuote.toProviderBlockState(): OnrampProviderBlockUM {
|
||||
return OnrampProviderBlockUM.Content(
|
||||
paymentMethodIconUrl = paymentMethod.imageUrl,
|
||||
paymentMethodName = paymentMethod.name,
|
||||
paymentMethod = paymentMethod,
|
||||
providerName = provider.info.name,
|
||||
isBestRate = true,
|
||||
onClick = onrampIntents::openProviders,
|
||||
|
|
@ -84,11 +125,8 @@ internal class OnrampAmountStateFactory(
|
|||
private fun OnrampQuote.toSecondaryFieldUiModel(amountState: OnrampAmountBlockUM): OnrampAmountSecondaryFieldUM {
|
||||
return when (this) {
|
||||
is OnrampQuote.Data -> {
|
||||
val amount = toAmount.format {
|
||||
crypto(
|
||||
symbol = amountState.amountFieldModel.cryptoAmount.currencySymbol,
|
||||
decimals = amountState.amountFieldModel.cryptoAmount.decimals,
|
||||
)
|
||||
val amount = toAmount.value.format {
|
||||
crypto(symbol = toAmount.symbol, decimals = toAmount.decimals)
|
||||
}
|
||||
OnrampAmountSecondaryFieldUM.Content(stringReference(amount))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,10 +8,12 @@ import com.tangem.core.decompose.navigation.Router
|
|||
import com.tangem.domain.onramp.*
|
||||
import com.tangem.domain.onramp.model.OnrampAvailability
|
||||
import com.tangem.domain.onramp.model.OnrampCurrency
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.features.onramp.main.OnrampMainComponent
|
||||
import com.tangem.features.onramp.main.entity.OnrampIntents
|
||||
import com.tangem.features.onramp.main.entity.OnrampMainBottomSheetConfig
|
||||
import com.tangem.features.onramp.main.entity.OnrampMainComponentUM
|
||||
import com.tangem.features.onramp.main.entity.OnrampProviderBlockUM
|
||||
import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory
|
||||
import com.tangem.features.onramp.main.entity.factory.amount.OnrampAmountStateFactory
|
||||
import com.tangem.features.onramp.utils.InputManager
|
||||
|
|
@ -60,6 +62,10 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
subscribeToAmountChanges()
|
||||
}
|
||||
|
||||
fun onProviderSelected(providerWithQuote: OnrampProviderWithQuote.Data) {
|
||||
_state.update { amountStateFactory.getAmountSecondaryUpdatedState(providerWithQuote) }
|
||||
}
|
||||
|
||||
private fun checkResidenceCountry() {
|
||||
modelScope.launch {
|
||||
checkOnrampAvailabilityUseCase.invoke(params.cryptoCurrency)
|
||||
|
|
@ -125,8 +131,8 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
params.openSettings()
|
||||
}
|
||||
|
||||
override fun onBuyClick() {
|
||||
TODO("Not yet implemented")
|
||||
override fun onBuyClick(quote: OnrampProviderWithQuote.Data) {
|
||||
params.openRedirectPage(quote)
|
||||
}
|
||||
|
||||
override fun openCurrenciesList() {
|
||||
|
|
@ -134,6 +140,13 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
}
|
||||
|
||||
override fun openProviders() {
|
||||
val providerState = (state.value as? OnrampMainComponentUM.Content)?.providerBlockState ?: return
|
||||
val providerContentState = providerState as? OnrampProviderBlockUM.Content ?: return
|
||||
bottomSheetNavigation.activate(
|
||||
OnrampMainBottomSheetConfig.ProvidersList(
|
||||
selectedPaymentMethod = providerContentState.paymentMethod,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ private fun OnrampProviderBlock(state: OnrampProviderBlockUM.Content, modifier:
|
|||
.size(size = TangemTheme.dimens.size40)
|
||||
.clip(TangemTheme.shapes.roundedCorners8),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(state.paymentMethodIconUrl)
|
||||
.data(state.paymentMethod.imageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(false)
|
||||
.build(),
|
||||
|
|
@ -67,7 +67,7 @@ private fun OnrampProviderBlock(state: OnrampProviderBlockUM.Content, modifier:
|
|||
color = TangemTheme.colors.text.primary1,
|
||||
),
|
||||
) {
|
||||
append(state.paymentMethodName)
|
||||
append(state.paymentMethod.name)
|
||||
}
|
||||
},
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package com.tangem.features.onramp.paymentmethod.entity
|
||||
|
||||
data class PaymentMethodUM(
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal data class PaymentMethodUM(
|
||||
val id: String,
|
||||
val imageUrl: String,
|
||||
val name: String,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,95 @@
|
|||
package com.tangem.features.onramp.providers
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.arkivanov.decompose.ComponentContext
|
||||
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
|
||||
import com.arkivanov.decompose.router.slot.childSlot
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
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.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.onramp.paymentmethod.SelectPaymentMethodComponent
|
||||
import com.tangem.features.onramp.providers.entity.ProviderListBottomSheetConfig
|
||||
import com.tangem.features.onramp.providers.model.SelectProviderModel
|
||||
import com.tangem.features.onramp.providers.ui.SelectProviderBottomSheet
|
||||
import com.tangem.features.onramp.providers.ui.SelectProviderBottomSheetContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultSelectProviderComponent @AssistedInject constructor(
|
||||
@Assisted context: AppComponentContext,
|
||||
@Assisted private val params: SelectProviderComponent.Params,
|
||||
private val selectPaymentMethodComponentFactory: SelectPaymentMethodComponent.Factory,
|
||||
) : SelectProviderComponent, AppComponentContext by context {
|
||||
|
||||
private val model: SelectProviderModel = getOrCreateModel(params)
|
||||
private val bottomSheetSlot = childSlot(
|
||||
source = model.bottomSheetNavigation,
|
||||
serializer = null,
|
||||
handleBackButton = false,
|
||||
childFactory = ::bottomSheetChild,
|
||||
)
|
||||
|
||||
override fun dismiss() {
|
||||
params.onDismiss()
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun BottomSheet() {
|
||||
val state by model.state.collectAsStateWithLifecycle()
|
||||
val bottomSheet by bottomSheetSlot.subscribeAsState()
|
||||
|
||||
val bottomSheetConfig = remember(key1 = this) {
|
||||
TangemBottomSheetConfig(
|
||||
isShow = true,
|
||||
onDismissRequest = ::dismiss,
|
||||
content = TangemBottomSheetConfigContent.Empty,
|
||||
)
|
||||
}
|
||||
SelectProviderBottomSheet(
|
||||
config = bottomSheetConfig,
|
||||
content = {
|
||||
SelectProviderBottomSheetContent(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
state = state,
|
||||
)
|
||||
},
|
||||
)
|
||||
bottomSheet.child?.instance?.BottomSheet()
|
||||
}
|
||||
|
||||
private fun bottomSheetChild(
|
||||
config: ProviderListBottomSheetConfig,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent = when (config) {
|
||||
is ProviderListBottomSheetConfig.PaymentMethods -> selectPaymentMethodComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = SelectPaymentMethodComponent.Params(
|
||||
selectedMethodId = config.selectedMethodId,
|
||||
paymentMethods = config.paymentMethodsUM,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : SelectProviderComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: SelectProviderComponent.Params,
|
||||
): DefaultSelectProviderComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.tangem.features.onramp.providers
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
|
||||
internal interface SelectProviderComponent : ComposableBottomSheetComponent {
|
||||
|
||||
data class Params(
|
||||
val onProviderClick: (OnrampProviderWithQuote.Data) -> Unit,
|
||||
val onDismiss: () -> Unit,
|
||||
val selectedPaymentMethod: OnrampPaymentMethod,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, SelectProviderComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.providers.di
|
||||
|
||||
import com.tangem.features.onramp.providers.DefaultSelectProviderComponent
|
||||
import com.tangem.features.onramp.providers.SelectProviderComponent
|
||||
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 SelectProviderComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindSelectProviderComponentFactory(
|
||||
factory: DefaultSelectProviderComponent.Factory,
|
||||
): SelectProviderComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.providers.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onramp.providers.model.SelectProviderModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface SelectProviderModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SelectProviderModel::class)
|
||||
fun bindSelectProviderModel(model: SelectProviderModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.features.onramp.providers.entity
|
||||
|
||||
import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodUM
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
internal sealed interface ProviderListBottomSheetConfig {
|
||||
|
||||
@Serializable
|
||||
data class PaymentMethods(
|
||||
val selectedMethodId: String,
|
||||
val paymentMethodsUM: List<PaymentMethodUM>,
|
||||
) : ProviderListBottomSheetConfig
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
package com.tangem.features.onramp.providers.entity
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
internal sealed interface ProviderListItemUM {
|
||||
|
||||
val providerId: String
|
||||
|
||||
data class Available(
|
||||
override val providerId: String,
|
||||
val imageUrl: String,
|
||||
val name: String,
|
||||
val rate: String,
|
||||
val isSelected: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
) : ProviderListItemUM
|
||||
|
||||
data class Unavailable(
|
||||
override val providerId: String,
|
||||
val imageUrl: String,
|
||||
val name: String,
|
||||
val subtitle: TextReference,
|
||||
) : ProviderListItemUM
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.onramp.providers.entity
|
||||
|
||||
internal data class ProviderListPaymentMethodUM(
|
||||
val id: String,
|
||||
val name: String,
|
||||
val imageUrl: String,
|
||||
val onClick: () -> Unit,
|
||||
)
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
package com.tangem.features.onramp.providers.entity
|
||||
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class ProviderListUM(
|
||||
val paymentMethod: ProviderListPaymentMethodUM,
|
||||
val providers: ImmutableList<ProviderListItemUM>,
|
||||
)
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
package com.tangem.features.onramp.providers.model
|
||||
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.arkivanov.decompose.router.slot.dismiss
|
||||
import com.tangem.core.decompose.di.ComponentScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.onramp.GetOnrampPaymentMethodsUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampProviderWithQuoteUseCase
|
||||
import com.tangem.domain.onramp.GetOnrampSelectedPaymentMethodUseCase
|
||||
import com.tangem.domain.onramp.OnrampSaveSelectedPaymentMethod
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.paymentmethod.entity.PaymentMethodUM
|
||||
import com.tangem.features.onramp.providers.SelectProviderComponent
|
||||
import com.tangem.features.onramp.providers.entity.ProviderListBottomSheetConfig
|
||||
import com.tangem.features.onramp.providers.entity.ProviderListItemUM
|
||||
import com.tangem.features.onramp.providers.entity.ProviderListPaymentMethodUM
|
||||
import com.tangem.features.onramp.providers.entity.ProviderListUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ComponentScoped
|
||||
internal class SelectProviderModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val getSelectedPaymentMethodsUseCase: GetOnrampPaymentMethodsUseCase,
|
||||
private val getOnrampSelectedPaymentMethodUseCase: GetOnrampSelectedPaymentMethodUseCase,
|
||||
private val getOnrampProviderWithQuoteUseCase: GetOnrampProviderWithQuoteUseCase,
|
||||
private val saveSelectedPaymentMethod: OnrampSaveSelectedPaymentMethod,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
val state: StateFlow<ProviderListUM> get() = _state.asStateFlow()
|
||||
val bottomSheetNavigation: SlotNavigation<ProviderListBottomSheetConfig> = SlotNavigation()
|
||||
private val params: SelectProviderComponent.Params = paramsContainer.require()
|
||||
private val _state = MutableStateFlow(getInitialState())
|
||||
|
||||
init {
|
||||
getProviders(params.selectedPaymentMethod)
|
||||
subscribeToPaymentMethodUpdates()
|
||||
}
|
||||
|
||||
private fun subscribeToPaymentMethodUpdates() {
|
||||
getOnrampSelectedPaymentMethodUseCase.invoke()
|
||||
.onEach { it.getOrNull()?.let(::getProviders) }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private fun getProviders(paymentMethod: OnrampPaymentMethod) {
|
||||
modelScope.launch {
|
||||
getOnrampProviderWithQuoteUseCase.invoke(paymentMethod)
|
||||
.onRight { quotes ->
|
||||
_state.update { state -> state.copy(providers = quotes.toProvidersListItems()) }
|
||||
}
|
||||
.onLeft { Timber.e(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getInitialState(): ProviderListUM {
|
||||
return ProviderListUM(
|
||||
paymentMethod = ProviderListPaymentMethodUM(
|
||||
id = params.selectedPaymentMethod.id,
|
||||
name = params.selectedPaymentMethod.name,
|
||||
imageUrl = params.selectedPaymentMethod.imageUrl,
|
||||
onClick = ::openPaymentMethods,
|
||||
),
|
||||
providers = emptyList<ProviderListItemUM>().toImmutableList(),
|
||||
)
|
||||
}
|
||||
|
||||
private fun openPaymentMethods() {
|
||||
modelScope.launch {
|
||||
val methods = getSelectedPaymentMethodsUseCase.invoke().getOrNull().orEmpty()
|
||||
bottomSheetNavigation.activate(
|
||||
ProviderListBottomSheetConfig.PaymentMethods(
|
||||
selectedMethodId = _state.value.paymentMethod.id,
|
||||
paymentMethodsUM = methods.toPaymentUMList(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<OnrampPaymentMethod>.toPaymentUMList(): List<PaymentMethodUM> = map { method ->
|
||||
PaymentMethodUM(
|
||||
id = method.id,
|
||||
imageUrl = method.imageUrl,
|
||||
name = method.name,
|
||||
onSelect = { onPaymentMethodSelected(method) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun onPaymentMethodSelected(paymentMethod: OnrampPaymentMethod) {
|
||||
modelScope.launch {
|
||||
saveSelectedPaymentMethod.invoke(paymentMethod)
|
||||
_state.update { state ->
|
||||
val paymentMethodUM = state.paymentMethod.copy(
|
||||
id = paymentMethod.id,
|
||||
name = paymentMethod.name,
|
||||
imageUrl = paymentMethod.imageUrl,
|
||||
)
|
||||
state.copy(paymentMethod = paymentMethodUM)
|
||||
}
|
||||
bottomSheetNavigation.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
private fun List<OnrampProviderWithQuote>.toProvidersListItems() = mapIndexed { index, quote ->
|
||||
when (quote) {
|
||||
is OnrampProviderWithQuote.Data -> {
|
||||
val rate = quote.toAmount.value.format {
|
||||
crypto(symbol = quote.toAmount.symbol, decimals = quote.toAmount.decimals)
|
||||
}
|
||||
ProviderListItemUM.Available(
|
||||
providerId = quote.provider.id,
|
||||
imageUrl = quote.provider.info.imageLarge,
|
||||
name = quote.provider.info.name,
|
||||
rate = rate,
|
||||
isSelected = index == 0,
|
||||
onClick = {
|
||||
params.onProviderClick(quote)
|
||||
params.onDismiss()
|
||||
},
|
||||
)
|
||||
}
|
||||
is OnrampProviderWithQuote.Unavailable.AvailableFrom -> {
|
||||
val amount = quote.amount.value.format {
|
||||
crypto(symbol = quote.amount.symbol, decimals = quote.amount.decimals)
|
||||
}
|
||||
ProviderListItemUM.Unavailable(
|
||||
providerId = quote.provider.id,
|
||||
imageUrl = quote.provider.info.imageLarge,
|
||||
name = quote.provider.info.name,
|
||||
subtitle = resourceReference(R.string.express_provider_min_amount, wrappedList(amount)),
|
||||
)
|
||||
}
|
||||
is OnrampProviderWithQuote.Unavailable.AvailableUpTo -> {
|
||||
val amount = quote.amount.value.format {
|
||||
crypto(symbol = quote.amount.symbol, decimals = quote.amount.decimals)
|
||||
}
|
||||
ProviderListItemUM.Unavailable(
|
||||
providerId = quote.provider.id,
|
||||
imageUrl = quote.provider.info.imageLarge,
|
||||
name = quote.provider.info.name,
|
||||
subtitle = resourceReference(R.string.express_provider_max_amount, wrappedList(amount)),
|
||||
)
|
||||
}
|
||||
is OnrampProviderWithQuote.Unavailable.NotSupportedPaymentMethod -> {
|
||||
ProviderListItemUM.Unavailable(
|
||||
providerId = quote.provider.id,
|
||||
imageUrl = quote.provider.info.imageLarge,
|
||||
name = quote.provider.info.name,
|
||||
subtitle = stringReference(
|
||||
"Available with ${quote.availablePaymentMethods.joinToString { it.name }}",
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
}.toImmutableList()
|
||||
}
|
||||
|
|
@ -0,0 +1,199 @@
|
|||
package com.tangem.features.onramp.providers.ui
|
||||
|
||||
import androidx.compose.foundation.border
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.alpha
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.util.fastForEach
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
|
||||
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.providers.entity.ProviderListItemUM
|
||||
import com.tangem.features.onramp.providers.entity.ProviderListPaymentMethodUM
|
||||
import com.tangem.features.onramp.providers.entity.ProviderListUM
|
||||
|
||||
@Composable
|
||||
internal fun SelectProviderBottomSheet(config: TangemBottomSheetConfig, content: @Composable () -> Unit) {
|
||||
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
|
||||
config = config,
|
||||
titleText = resourceReference(R.string.express_choose_providers_title),
|
||||
content = { content() },
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun SelectProviderBottomSheetContent(state: ProviderListUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier.verticalScroll(rememberScrollState()),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.onramp_choose_provider_title_hint),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
PaymentMethodBlock(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing20, bottom = TangemTheme.dimens.spacing16),
|
||||
state = state.paymentMethod,
|
||||
)
|
||||
state.providers.fastForEach { provider ->
|
||||
key(provider.providerId) { ProviderItem(state = provider) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PaymentMethodBlock(state: ProviderListPaymentMethodUM, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(TangemTheme.dimens.radius16))
|
||||
.border(
|
||||
width = TangemTheme.dimens.size1,
|
||||
color = TangemTheme.colors.stroke.secondary,
|
||||
shape = RoundedCornerShape(TangemTheme.dimens.radius16),
|
||||
)
|
||||
.clickable(onClick = state.onClick)
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size40)
|
||||
.clip(TangemTheme.shapes.roundedCorners8),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(state.imageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(false)
|
||||
.build(),
|
||||
loading = { RectangleShimmer(radius = TangemTheme.dimens.radius8) },
|
||||
contentDescription = null,
|
||||
)
|
||||
Column(modifier = Modifier.weight(1F)) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.onramp_pay_with),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
Text(
|
||||
text = state.name,
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
Icon(
|
||||
modifier = Modifier.size(TangemTheme.dimens.size24),
|
||||
painter = painterResource(id = R.drawable.ic_chevron_24),
|
||||
contentDescription = null,
|
||||
tint = TangemTheme.colors.icon.informative,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ProviderItem(state: ProviderListItemUM, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is ProviderListItemUM.Available -> AvailableProviderItem(
|
||||
modifier = modifier
|
||||
.clickable(onClick = state.onClick)
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
state = state,
|
||||
)
|
||||
is ProviderListItemUM.Unavailable -> UnavailableProviderItem(
|
||||
modifier = modifier.padding(all = TangemTheme.dimens.spacing12),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AvailableProviderItem(state: ProviderListItemUM.Available, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size40)
|
||||
.clip(TangemTheme.shapes.roundedCorners8),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(state.imageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(false)
|
||||
.build(),
|
||||
loading = { RectangleShimmer(radius = TangemTheme.dimens.radius8) },
|
||||
contentDescription = null,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.weight(1F),
|
||||
text = state.name,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = if (state.isSelected) TangemTheme.colors.text.primary1 else TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Text(
|
||||
text = state.rate,
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private const val UNAVAILABLE_ALPHA = 0.4F
|
||||
|
||||
@Composable
|
||||
private fun UnavailableProviderItem(state: ProviderListItemUM.Unavailable, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size40)
|
||||
.clip(TangemTheme.shapes.roundedCorners8)
|
||||
.alpha(UNAVAILABLE_ALPHA),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(state.imageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(false)
|
||||
.build(),
|
||||
loading = { RectangleShimmer(radius = TangemTheme.dimens.radius8) },
|
||||
contentDescription = null,
|
||||
)
|
||||
Column(modifier = Modifier.weight(1F)) {
|
||||
Text(
|
||||
text = state.name,
|
||||
style = TangemTheme.typography.subtitle2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
)
|
||||
Text(
|
||||
text = state.subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.caption2,
|
||||
color = TangemTheme.colors.text.tertiary,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.tangem.features.onramp.redirect
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.features.onramp.redirect.model.OnrampRedirectModel
|
||||
import com.tangem.features.onramp.redirect.ui.OnrampRedirectContent
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
|
||||
internal class DefaultOnrampRedirectComponent @AssistedInject constructor(
|
||||
@Assisted appComponentContext: AppComponentContext,
|
||||
@Assisted private val params: OnrampRedirectComponent.Params,
|
||||
) : OnrampRedirectComponent, AppComponentContext by appComponentContext {
|
||||
|
||||
private val model: OnrampRedirectModel = getOrCreateModel(params)
|
||||
|
||||
@Composable
|
||||
override fun Content(modifier: Modifier) {
|
||||
BackHandler(onBack = params.onBack)
|
||||
OnrampRedirectContent(modifier = modifier, state = model.state)
|
||||
}
|
||||
|
||||
@AssistedFactory
|
||||
interface Factory : OnrampRedirectComponent.Factory {
|
||||
override fun create(
|
||||
context: AppComponentContext,
|
||||
params: OnrampRedirectComponent.Params,
|
||||
): DefaultOnrampRedirectComponent
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.tangem.features.onramp.redirect
|
||||
|
||||
import com.tangem.core.decompose.factory.ComponentFactory
|
||||
import com.tangem.core.ui.decompose.ComposableContentComponent
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import com.tangem.domain.wallets.models.UserWalletId
|
||||
|
||||
internal interface OnrampRedirectComponent : ComposableContentComponent {
|
||||
|
||||
data class Params(
|
||||
val userWalletId: UserWalletId,
|
||||
val onBack: () -> Unit,
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val onrampProviderWithQuote: OnrampProviderWithQuote.Data,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, OnrampRedirectComponent>
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.redirect.di
|
||||
|
||||
import com.tangem.features.onramp.redirect.DefaultOnrampRedirectComponent
|
||||
import com.tangem.features.onramp.redirect.OnrampRedirectComponent
|
||||
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 OnrampRedirectComponentModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindOnrampRedirectComponentFactory(
|
||||
factory: DefaultOnrampRedirectComponent.Factory,
|
||||
): OnrampRedirectComponent.Factory
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.tangem.features.onramp.redirect.di
|
||||
|
||||
import com.tangem.core.decompose.di.DecomposeComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.onramp.redirect.model.OnrampRedirectModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
|
||||
@Module
|
||||
@InstallIn(DecomposeComponent::class)
|
||||
internal interface OnrampRedirectModelModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(OnrampRedirectModel::class)
|
||||
fun bindOnrampRedirectModel(model: OnrampRedirectModel): Model
|
||||
}
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.tangem.features.onramp.redirect.entity
|
||||
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
data class OnrampRedirectTopBarUM(val title: TextReference, val startButtonUM: TopAppBarButtonUM)
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package com.tangem.features.onramp.redirect.entity
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
|
||||
internal data class OnrampRedirectUM(
|
||||
val topBarConfig: OnrampRedirectTopBarUM,
|
||||
val providerImageUrl: String,
|
||||
val title: TextReference,
|
||||
val subtitle: TextReference,
|
||||
)
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
package com.tangem.features.onramp.redirect.model
|
||||
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.decompose.navigation.Router
|
||||
import com.tangem.core.navigation.url.UrlOpener
|
||||
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
|
||||
import com.tangem.core.ui.extensions.combinedReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.onramp.GetOnrampRedirectUrlUseCase
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.redirect.OnrampRedirectComponent
|
||||
import com.tangem.features.onramp.redirect.entity.OnrampRedirectTopBarUM
|
||||
import com.tangem.features.onramp.redirect.entity.OnrampRedirectUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
internal class OnrampRedirectModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val urlOpener: UrlOpener,
|
||||
private val getOnrampRedirectUrlUseCase: GetOnrampRedirectUrlUseCase,
|
||||
router: Router,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params: OnrampRedirectComponent.Params = paramsContainer.require()
|
||||
val state = OnrampRedirectUM(
|
||||
topBarConfig = OnrampRedirectTopBarUM(
|
||||
title = combinedReference(
|
||||
resourceReference(R.string.common_buy),
|
||||
stringReference(" ${params.cryptoCurrency.name}"),
|
||||
),
|
||||
startButtonUM = TopAppBarButtonUM(
|
||||
iconRes = R.drawable.ic_close_24,
|
||||
onIconClicked = router::pop,
|
||||
enabled = true,
|
||||
),
|
||||
),
|
||||
providerImageUrl = params.onrampProviderWithQuote.provider.info.imageLarge,
|
||||
title = resourceReference(
|
||||
R.string.onramp_redirecting_to_provider_title,
|
||||
wrappedList(params.onrampProviderWithQuote.provider.info.name),
|
||||
),
|
||||
subtitle = resourceReference(
|
||||
R.string.onramp_redirecting_to_provider_subtitle,
|
||||
wrappedList(params.onrampProviderWithQuote.provider.info.name),
|
||||
),
|
||||
)
|
||||
|
||||
init {
|
||||
getRedirectUrl()
|
||||
}
|
||||
|
||||
private fun getRedirectUrl() {
|
||||
modelScope.launch {
|
||||
getOnrampRedirectUrlUseCase.invoke(
|
||||
userWalletId = params.userWalletId,
|
||||
quote = params.onrampProviderWithQuote,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
)
|
||||
.onLeft { Timber.e(it) }
|
||||
.onRight { urlOpener.openUrl(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,133 @@
|
|||
package com.tangem.features.onramp.redirect.ui
|
||||
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.Scaffold
|
||||
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.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import coil.compose.SubcomposeAsyncImage
|
||||
import coil.request.ImageRequest
|
||||
import com.tangem.core.ui.components.RectangleShimmer
|
||||
import com.tangem.core.ui.components.appbar.TangemTopAppBar
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
import com.tangem.core.ui.res.TangemTheme
|
||||
import com.tangem.core.ui.utils.WindowInsetsZero
|
||||
import com.tangem.features.onramp.impl.R
|
||||
import com.tangem.features.onramp.redirect.entity.OnrampRedirectUM
|
||||
|
||||
@Composable
|
||||
internal fun OnrampRedirectContent(state: OnrampRedirectUM, modifier: Modifier = Modifier) {
|
||||
Scaffold(
|
||||
modifier = modifier.imePadding(),
|
||||
contentWindowInsets = WindowInsetsZero,
|
||||
containerColor = TangemTheme.colors.background.secondary,
|
||||
topBar = {
|
||||
TangemTopAppBar(
|
||||
modifier = Modifier.statusBarsPadding(),
|
||||
startButton = state.topBarConfig.startButtonUM,
|
||||
title = state.topBarConfig.title.resolveReference(),
|
||||
)
|
||||
},
|
||||
content = { innerPadding ->
|
||||
Content(
|
||||
modifier = Modifier
|
||||
.padding(innerPadding)
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = TangemTheme.dimens.spacing16),
|
||||
state = state,
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun Content(state: OnrampRedirectUM, modifier: Modifier = Modifier) {
|
||||
Column(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.Center,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
LogoBlock(providerImageUrl = state.providerImageUrl)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing24),
|
||||
text = state.title.resolveReference(),
|
||||
style = TangemTheme.typography.h3,
|
||||
color = TangemTheme.colors.text.primary1,
|
||||
)
|
||||
Text(
|
||||
modifier = Modifier.padding(top = TangemTheme.dimens.spacing12),
|
||||
text = state.subtitle.resolveReference(),
|
||||
style = TangemTheme.typography.body2,
|
||||
color = TangemTheme.colors.text.secondary,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LogoBlock(providerImageUrl: String, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Image(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size64)
|
||||
.clip(TangemTheme.shapes.roundedCorners8)
|
||||
.background(TangemTheme.colors.background.primary)
|
||||
.padding(vertical = TangemTheme.dimens.spacing8, horizontal = TangemTheme.dimens.spacing12),
|
||||
painter = painterResource(id = R.drawable.ic_tangem_24),
|
||||
contentDescription = null,
|
||||
)
|
||||
LoadingDots()
|
||||
SubcomposeAsyncImage(
|
||||
modifier = Modifier
|
||||
.size(size = TangemTheme.dimens.size64)
|
||||
.clip(TangemTheme.shapes.roundedCorners8),
|
||||
model = ImageRequest.Builder(context = LocalContext.current)
|
||||
.data(providerImageUrl)
|
||||
.crossfade(enable = true)
|
||||
.allowHardware(false)
|
||||
.build(),
|
||||
loading = { RectangleShimmer(radius = TangemTheme.dimens.radius8) },
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun LoadingDots(modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing6),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size6)
|
||||
.clip(CircleShape)
|
||||
.background(color = TangemTheme.colors.icon.accent.copy(alpha = 0.75F)),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size6)
|
||||
.clip(CircleShape)
|
||||
.background(color = TangemTheme.colors.icon.accent.copy(alpha = 0.45F)),
|
||||
)
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.size(TangemTheme.dimens.size8)
|
||||
.clip(CircleShape)
|
||||
.background(color = TangemTheme.colors.icon.accent),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -17,6 +17,7 @@ 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.redirect.OnrampRedirectComponent
|
||||
import com.tangem.features.onramp.root.entity.OnrampChild
|
||||
import com.tangem.features.onramp.settings.OnrampSettingsComponent
|
||||
import dagger.assisted.Assisted
|
||||
|
|
@ -29,6 +30,7 @@ internal class DefaultOnrampComponent @AssistedInject constructor(
|
|||
@Assisted private val params: OnrampComponent.Params,
|
||||
private val settingsComponentFactory: OnrampSettingsComponent.Factory,
|
||||
private val onrampMainComponentFactory: OnrampMainComponent.Factory,
|
||||
private val onrampRedirectComponentFactory: OnrampRedirectComponent.Factory,
|
||||
) : OnrampComponent, AppComponentContext by context {
|
||||
|
||||
private val navigation = StackNavigation<OnrampChild>()
|
||||
|
|
@ -65,6 +67,23 @@ internal class DefaultOnrampComponent @AssistedInject constructor(
|
|||
params = OnrampMainComponent.Params(
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
openSettings = { navigation.push(OnrampChild.Settings) },
|
||||
openRedirectPage = {
|
||||
navigation.push(
|
||||
OnrampChild.RedirectPage(
|
||||
quote = it,
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
),
|
||||
)
|
||||
},
|
||||
),
|
||||
)
|
||||
is OnrampChild.RedirectPage -> onrampRedirectComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = OnrampRedirectComponent.Params(
|
||||
userWalletId = params.userWalletId,
|
||||
onBack = navigation::pop,
|
||||
cryptoCurrency = config.cryptoCurrency,
|
||||
onrampProviderWithQuote = config.quote,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
package com.tangem.features.onramp.root.entity
|
||||
|
||||
import com.tangem.domain.onramp.model.OnrampProviderWithQuote
|
||||
import com.tangem.domain.tokens.model.CryptoCurrency
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
|
|
@ -9,4 +11,7 @@ internal sealed class OnrampChild {
|
|||
|
||||
@Serializable
|
||||
data object Settings : OnrampChild()
|
||||
|
||||
@Serializable
|
||||
data class RedirectPage(val quote: OnrampProviderWithQuote.Data, val cryptoCurrency: CryptoCurrency) : OnrampChild()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue