Updated on 2026-08-14

This commit is contained in:
Tangem 2024-11-15 17:12:02 +05:00
parent 3bac111c2b
commit 854b04624d
24 changed files with 486 additions and 90 deletions

View file

@ -47,4 +47,10 @@ internal object OnrampDomainModule {
fun provideCheckOnrampAvailabilityUseCase(onrampRepository: OnrampRepository): CheckOnrampAvailabilityUseCase {
return CheckOnrampAvailabilityUseCase(onrampRepository)
}
@Provides
@Singleton
fun provideGetOnrampCurrencyUseCase(onrampRepository: OnrampRepository): GetOnrampCurrencyUseCase {
return GetOnrampCurrencyUseCase(onrampRepository)
}
}

View file

@ -199,7 +199,7 @@ internal class ChildFactory @Inject constructor(
is AppRoute.Onramp -> {
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
params = OnrampComponent.Params(route.currency.name),
params = OnrampComponent.Params(route.currency),
componentFactory = onrampComponentFactory,
)
}

View file

@ -2,7 +2,9 @@ package com.tangem.domain.onramp.model
sealed interface OnrampAvailability {
data object Available : OnrampAvailability
data class NotSupported(val country: OnrampCountry) : OnrampAvailability
data class ConfirmResidency(val country: OnrampCountry) : OnrampAvailability
val country: OnrampCountry
data class Available(override val country: OnrampCountry, val currency: OnrampCurrency) : OnrampAvailability
data class NotSupported(override val country: OnrampCountry) : OnrampAvailability
data class ConfirmResidency(override val country: OnrampCountry) : OnrampAvailability
}

View file

@ -8,10 +8,13 @@ class CheckOnrampAvailabilityUseCase(private val repository: OnrampRepository) {
suspend operator fun invoke(): Either<Throwable, OnrampAvailability> {
return Either.catch {
return@catch OnrampAvailability.Available
repository.getDefaultCountrySync()?.let { savedCountry ->
return@catch if (savedCountry.onrampAvailable) {
OnrampAvailability.Available
val currency = repository.getDefaultCurrencySync() ?: run {
repository.saveDefaultCurrency(savedCountry.defaultCurrency)
savedCountry.defaultCurrency
}
OnrampAvailability.Available(country = savedCountry, currency = currency)
} else {
OnrampAvailability.NotSupported(savedCountry)
}

View file

@ -0,0 +1,19 @@
package com.tangem.domain.onramp
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.onramp.repositories.OnrampRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
class GetOnrampCurrencyUseCase(private val repository: OnrampRepository) {
operator fun invoke(): Flow<Either<Throwable, OnrampCurrency?>> {
return repository.getDefaultCurrency()
.map<OnrampCurrency?, Either<Throwable, OnrampCurrency?>> { it.right() }
.catch { emit(it.left()) }
}
}

View file

@ -15,6 +15,7 @@ dependencies {
implementation(projects.core.ui)
/* Project - Domain */
implementation(projects.domain.tokens.models)
implementation(projects.domain.wallets.models)
/* Compose */

View file

@ -2,10 +2,11 @@ package com.tangem.features.onramp.component
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.tokens.model.CryptoCurrency
interface OnrampComponent : ComposableContentComponent {
data class Params(val currency: String)
data class Params(val cryptoCurrency: CryptoCurrency)
interface Factory : ComponentFactory<Params, OnrampComponent>
}

View file

@ -17,7 +17,7 @@ import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.features.onramp.confirmresidency.model.ConfirmResidencyModel
import com.tangem.features.onramp.confirmresidency.ui.ConfirmResidencyBottomSheet
import com.tangem.features.onramp.confirmresidency.ui.ConfirmResidencyBottomSheetContent
import com.tangem.features.onramp.entity.ConfirmResidencyBottomSheetConfig
import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig
import com.tangem.features.onramp.selectcountry.SelectCountryComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory

View file

@ -1,4 +1,4 @@
package com.tangem.features.onramp.entity
package com.tangem.features.onramp.confirmresidency.entity
import kotlinx.serialization.Serializable

View file

@ -11,7 +11,7 @@ import com.tangem.domain.onramp.GetOnrampCountryUseCase
import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent
import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.entity.ConfirmResidencyBottomSheetConfig
import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn

View file

@ -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.selectcurrency.SelectCurrencyComponent
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -24,6 +25,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: OnrampMainComponent.Params,
private val confirmResidencyComponentFactory: ConfirmResidencyComponent.Factory,
private val selectCurrencyComponentFactory: SelectCurrencyComponent.Factory,
) : OnrampMainComponent, AppComponentContext by appComponentContext {
private val model: OnrampMainComponentModel = getOrCreateModel(params)
@ -54,6 +56,10 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
onDismiss = { model.bottomSheetNavigation.dismiss() },
),
)
is OnrampMainBottomSheetConfig.CurrenciesList -> selectCurrencyComponentFactory.create(
context = childByContext(componentContext),
params = SelectCurrencyComponent.Params(model.bottomSheetNavigation::dismiss),
)
}
@AssistedFactory

View file

@ -2,10 +2,11 @@ package com.tangem.features.onramp.main
import com.tangem.core.decompose.factory.ComponentFactory
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.tokens.model.CryptoCurrency
internal interface OnrampMainComponent : ComposableContentComponent {
class Params(val currency: String, val openSettings: () -> Unit)
data class Params(val cryptoCurrency: CryptoCurrency, val openSettings: () -> Unit)
interface Factory : ComponentFactory<Params, OnrampMainComponent>
}

View file

@ -0,0 +1,20 @@
package com.tangem.features.onramp.main.entity
import androidx.compose.runtime.Immutable
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.core.ui.extensions.TextReference
internal data class OnrampAmountBlockUM(
val currencyUM: OnrampCurrencyUM,
val amountFieldModel: AmountFieldModel,
val secondaryFieldModel: OnrampAmountSecondaryFieldUM,
)
internal data class OnrampCurrencyUM(val code: String, val iconUrl: String, val precision: Int, val onClick: () -> Unit)
@Immutable
internal sealed interface OnrampAmountSecondaryFieldUM {
data object Loading : OnrampAmountSecondaryFieldUM
data class Content(val amount: TextReference) : OnrampAmountSecondaryFieldUM
data class Error(val error: TextReference) : OnrampAmountSecondaryFieldUM
}

View file

@ -0,0 +1,8 @@
package com.tangem.features.onramp.main.entity
interface OnrampIntents {
fun onAmountValueChanged(value: String)
fun openSettings()
fun openCurrenciesList()
fun onBuyClick()
}

View file

@ -7,4 +7,7 @@ import kotlinx.serialization.Serializable
internal sealed interface OnrampMainBottomSheetConfig {
@Serializable
data class ConfirmResidency(val country: OnrampCountry) : OnrampMainBottomSheetConfig
@Serializable
data object CurrenciesList : OnrampMainBottomSheetConfig
}

View file

@ -44,6 +44,7 @@ internal sealed interface OnrampMainComponentUM {
data class Content(
override val topBarConfig: OnrampMainTopBarUM,
override val buyButtonConfig: BuyButtonConfig,
val amountBlockState: OnrampAmountBlockUM,
) : OnrampMainComponentUM
}

View file

@ -0,0 +1,85 @@
package com.tangem.features.onramp.main.entity.factory
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.tokens.model.Amount
import com.tangem.domain.tokens.model.AmountType
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.convertToAmount
import com.tangem.features.onramp.main.entity.*
import com.tangem.features.onramp.main.entity.OnrampAmountBlockUM
import com.tangem.features.onramp.main.entity.OnrampAmountSecondaryFieldUM
import com.tangem.features.onramp.main.entity.OnrampCurrencyUM
import com.tangem.features.onramp.main.entity.OnrampMainComponentUM
import com.tangem.utils.Provider
import java.math.BigDecimal
internal class OnrampStateFactory(
private val currentStateProvider: Provider<OnrampMainComponentUM>,
private val cryptoCurrency: CryptoCurrency,
private val onrampIntents: OnrampIntents,
) {
fun getInitialState(currency: String, onClose: () -> Unit): OnrampMainComponentUM.InitialLoading {
return OnrampMainComponentUM.InitialLoading(
currency = currency,
onClose = onClose,
openSettings = onrampIntents::openSettings,
onBuyClick = onrampIntents::onBuyClick,
)
}
fun getReadyState(currency: OnrampCurrency): OnrampMainComponentUM.Content {
val state = currentStateProvider()
val endButton = state.topBarConfig.endButtonUM.copy(enabled = true)
return OnrampMainComponentUM.Content(
topBarConfig = state.topBarConfig.copy(endButtonUM = endButton),
buyButtonConfig = state.buyButtonConfig,
amountBlockState = getInitialAmountBlockState(currency),
)
}
private fun getInitialAmountBlockState(currency: OnrampCurrency): OnrampAmountBlockUM {
return OnrampAmountBlockUM(
currencyUM = OnrampCurrencyUM(
code = currency.code,
iconUrl = currency.image,
precision = currency.precision,
onClick = onrampIntents::openCurrenciesList,
),
amountFieldModel = AmountFieldModel(
value = "",
fiatValue = "",
onValueChange = onrampIntents::onAmountValueChanged,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.None,
keyboardType = KeyboardType.Number,
),
keyboardActions = KeyboardActions(),
isFiatValue = true,
cryptoAmount = BigDecimal.ZERO.convertToAmount(cryptoCurrency),
fiatAmount = BigDecimal.ZERO.convertToFiatAmount(currency),
isError = false,
isWarning = false,
error = TextReference.EMPTY,
isFiatUnavailable = false,
isValuePasted = false,
onValuePastedTriggerDismiss = {},
),
secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading,
)
}
private fun BigDecimal.convertToFiatAmount(currency: OnrampCurrency): Amount = Amount(
currencySymbol = currency.name,
value = this,
decimals = currency.precision,
type = AmountType.FiatType(currency.code),
)
}

View file

@ -0,0 +1,63 @@
package com.tangem.features.onramp.main.entity.factory.amount
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.KeyboardType
import com.tangem.features.onramp.main.entity.OnrampAmountSecondaryFieldUM
import com.tangem.features.onramp.main.entity.OnrampMainComponentUM
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import com.tangem.utils.isNullOrZero
import java.math.BigDecimal
internal class OnrampAmountFieldChangeConverter(
private val currentStateProvider: Provider<OnrampMainComponentUM>,
) : Converter<String, OnrampMainComponentUM> {
override fun convert(value: String): OnrampMainComponentUM {
val state = currentStateProvider()
if (state !is OnrampMainComponentUM.Content) return state
if (value.isEmpty()) return state.emptyState()
val amountState = state.amountBlockState
val amountTextField = amountState.amountFieldModel
val fiatDecimal = value.toBigDecimalOrNull() ?: BigDecimal.ZERO
val isDoneActionEnabled = !fiatDecimal.isNullOrZero()
val amountFieldModel = amountState.amountFieldModel.copy(
fiatValue = value,
fiatAmount = amountTextField.fiatAmount.copy(value = fiatDecimal),
keyboardOptions = KeyboardOptions(
imeAction = if (isDoneActionEnabled) ImeAction.Done else ImeAction.None,
keyboardType = KeyboardType.Number,
),
)
return state.copy(
amountBlockState = amountState.copy(
amountFieldModel = amountFieldModel,
secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading,
),
)
}
private fun OnrampMainComponentUM.Content.emptyState(): OnrampMainComponentUM.Content {
val amountFieldModel = amountBlockState.amountFieldModel.copy(
value = "",
fiatValue = "",
cryptoAmount = amountBlockState.amountFieldModel.cryptoAmount.copy(value = BigDecimal.ZERO),
fiatAmount = amountBlockState.amountFieldModel.fiatAmount.copy(value = BigDecimal.ZERO),
isError = false,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.None,
keyboardType = KeyboardType.Number,
),
)
return copy(
amountBlockState = amountBlockState.copy(
amountFieldModel = amountFieldModel,
secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading,
),
)
}
}

View file

@ -0,0 +1,38 @@
package com.tangem.features.onramp.main.entity.factory.amount
import com.tangem.domain.onramp.model.OnrampCurrency
import com.tangem.domain.tokens.model.AmountType
import com.tangem.features.onramp.main.entity.OnrampMainComponentUM
import com.tangem.utils.Provider
internal class OnrampAmountStateFactory(private val currentStateProvider: Provider<OnrampMainComponentUM>) {
private val onrampAmountFieldChangeConverter = OnrampAmountFieldChangeConverter(
currentStateProvider = currentStateProvider,
)
fun getOnAmountValueChange(value: String) = onrampAmountFieldChangeConverter.convert(value)
fun getUpdatedCurrencyState(currency: OnrampCurrency): OnrampMainComponentUM {
val currentState = currentStateProvider()
if (currentState !is OnrampMainComponentUM.Content) return currentState
val amountState = currentState.amountBlockState
return currentState.copy(
amountBlockState = amountState.copy(
currencyUM = amountState.currencyUM.copy(
code = currency.code,
iconUrl = currency.image,
precision = currency.precision,
),
amountFieldModel = amountState.amountFieldModel.copy(
fiatAmount = amountState.amountFieldModel.fiatAmount.copy(
currencySymbol = currency.code,
decimals = currency.precision,
type = AmountType.FiatType(currency.code),
),
),
),
)
}
}

View file

@ -1,53 +0,0 @@
package com.tangem.features.onramp.main.entity.transformer
import arrow.core.Either
import com.tangem.domain.onramp.model.OnrampAvailability
import com.tangem.domain.onramp.model.OnrampCountry
import com.tangem.features.onramp.main.entity.OnrampMainBottomSheetConfig
import com.tangem.features.onramp.main.entity.OnrampMainComponentUM
import com.tangem.utils.transformer.Transformer
import timber.log.Timber
internal class OnrampAvailabilityTransformer(
private val maybeAvailabilityStatus: Either<Throwable, OnrampAvailability>,
private val openBottomSheet: (OnrampMainBottomSheetConfig) -> Unit,
) : Transformer<OnrampMainComponentUM> {
override fun transform(prevState: OnrampMainComponentUM): OnrampMainComponentUM {
return maybeAvailabilityStatus.fold(
ifLeft = {
Timber.e(it)
prevState
},
ifRight = { availability ->
when (availability) {
OnrampAvailability.Available -> handleAvailableStatus(prevState)
is OnrampAvailability.ConfirmResidency -> handleShowingConfirmResidency(
prevState = prevState,
country = availability.country,
)
is OnrampAvailability.NotSupported -> handleShowingConfirmResidency(
prevState = prevState,
country = availability.country,
)
}
},
)
}
private fun handleAvailableStatus(prevState: OnrampMainComponentUM): OnrampMainComponentUM {
val endButton = prevState.topBarConfig.endButtonUM.copy(enabled = true)
return OnrampMainComponentUM.Content(
topBarConfig = prevState.topBarConfig.copy(endButtonUM = endButton),
buyButtonConfig = prevState.buyButtonConfig,
)
}
private fun handleShowingConfirmResidency(
prevState: OnrampMainComponentUM,
country: OnrampCountry,
): OnrampMainComponentUM {
openBottomSheet.invoke(OnrampMainBottomSheetConfig.ConfirmResidency(country))
return prevState
}
}

View file

@ -6,33 +6,42 @@ 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.onramp.CheckOnrampAvailabilityUseCase
import com.tangem.domain.onramp.GetOnrampCurrencyUseCase
import com.tangem.domain.onramp.model.OnrampAvailability
import com.tangem.domain.onramp.model.OnrampCurrency
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.transformer.OnrampAvailabilityTransformer
import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory
import com.tangem.features.onramp.main.entity.factory.amount.OnrampAmountStateFactory
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.transformer.Transformer
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
internal class OnrampMainComponentModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
private val router: Router,
private val checkOnrampAvailabilityUseCase: CheckOnrampAvailabilityUseCase,
private val getOnrampCurrencyUseCase: GetOnrampCurrencyUseCase,
paramsContainer: ParamsContainer,
) : Model() {
) : Model(), OnrampIntents {
private val params: OnrampMainComponent.Params = paramsContainer.require()
private val stateFactory = OnrampStateFactory(
currentStateProvider = Provider { _state.value },
cryptoCurrency = params.cryptoCurrency,
onrampIntents = this,
)
private val amountStateFactory = OnrampAmountStateFactory(currentStateProvider = Provider { _state.value })
private val _state: MutableStateFlow<OnrampMainComponentUM> = MutableStateFlow(
value = OnrampMainComponentUM.InitialLoading(
currency = params.currency,
value = stateFactory.getInitialState(
currency = params.cryptoCurrency.name,
onClose = router::pop,
openSettings = params.openSettings,
onBuyClick = ::onBuyClick,
),
)
val state: StateFlow<OnrampMainComponentUM> get() = _state.asStateFlow()
@ -44,22 +53,50 @@ internal class OnrampMainComponentModel @Inject constructor(
private fun checkResidenceCountry() {
modelScope.launch {
OnrampAvailabilityTransformer(
maybeAvailabilityStatus = checkOnrampAvailabilityUseCase.invoke(),
openBottomSheet = ::openBottomSheet,
).let(::updateState)
checkOnrampAvailabilityUseCase.invoke()
.onRight(::handleOnrampAvailability)
.onLeft { Timber.e(it) }
}
}
@Suppress("EmptyFunctionBlock")
private fun onBuyClick() {
private fun handleOnrampAvailability(availability: OnrampAvailability) {
when (availability) {
is OnrampAvailability.Available -> handleOnrampAvailable(availability.currency)
is OnrampAvailability.ConfirmResidency,
is OnrampAvailability.NotSupported,
-> bottomSheetNavigation.activate(OnrampMainBottomSheetConfig.ConfirmResidency(availability.country))
}
}
private fun openBottomSheet(config: OnrampMainBottomSheetConfig) {
bottomSheetNavigation.activate(config)
private fun handleOnrampAvailable(currency: OnrampCurrency) {
_state.update { stateFactory.getReadyState(currency) }
subscribeToCurrencyUpdates()
}
private fun updateState(transformer: Transformer<OnrampMainComponentUM>) {
_state.update(transformer::transform)
private fun subscribeToCurrencyUpdates() {
getOnrampCurrencyUseCase.invoke()
.onEach { maybeCurrency ->
val currency = maybeCurrency.getOrNull()
if (currency != null) {
_state.update { amountStateFactory.getUpdatedCurrencyState(currency) }
}
}
.launchIn(modelScope)
}
override fun onAmountValueChanged(value: String) {
_state.update { amountStateFactory.getOnAmountValueChange(value) }
}
override fun openSettings() {
params.openSettings()
}
override fun onBuyClick() {
TODO("Not yet implemented")
}
override fun openCurrenciesList() {
bottomSheetNavigation.activate(OnrampMainBottomSheetConfig.CurrenciesList)
}
}

View file

@ -0,0 +1,146 @@
package com.tangem.features.onramp.main.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDirection
import coil.compose.AsyncImage
import com.tangem.common.ui.amountScreen.models.AmountFieldModel
import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.components.fields.AmountTextField
import com.tangem.core.ui.components.fields.visualtransformations.AmountVisualTransformation
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.utils.rememberDecimalFormat
import com.tangem.features.onramp.impl.R
import com.tangem.features.onramp.main.entity.OnrampAmountBlockUM
import com.tangem.features.onramp.main.entity.OnrampAmountSecondaryFieldUM
import com.tangem.features.onramp.main.entity.OnrampCurrencyUM
import kotlinx.coroutines.delay
@Composable
internal fun OnrampAmountContent(state: OnrampAmountBlockUM, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.clip(shape = RoundedCornerShape(size = TangemTheme.dimens.radius16))
.background(TangemTheme.colors.background.action)
.padding(vertical = TangemTheme.dimens.spacing28),
horizontalAlignment = Alignment.CenterHorizontally,
) {
OnrampCurrencyIcon(
modifier = Modifier.padding(start = TangemTheme.dimens.spacing24),
currencyUM = state.currencyUM,
)
OnrampAmountField(amountField = state.amountFieldModel)
OnrampAmountSecondary(state = state.secondaryFieldModel)
}
}
@Composable
private fun OnrampAmountField(amountField: AmountFieldModel) {
val decimalFormat = rememberDecimalFormat()
val requester = remember { FocusRequester() }
AmountTextField(
value = amountField.fiatValue,
decimals = amountField.fiatAmount.decimals,
visualTransformation = AmountVisualTransformation(
decimals = amountField.fiatAmount.decimals,
symbol = amountField.fiatAmount.currencySymbol,
decimalFormat = decimalFormat,
),
onValueChange = amountField.onValueChange,
keyboardOptions = amountField.keyboardOptions,
keyboardActions = amountField.keyboardActions,
textStyle = TangemTheme.typography.h2.copy(
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
),
isEnabled = !amountField.isError,
isAutoResize = true,
isValuePasted = amountField.isValuePasted,
onValuePastedTriggerDismiss = amountField.onValuePastedTriggerDismiss,
modifier = Modifier
.focusRequester(requester)
.padding(
top = TangemTheme.dimens.spacing24,
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
)
.requiredHeightIn(min = TangemTheme.dimens.size32),
)
LaunchedEffect(key1 = Unit) {
delay(timeMillis = 200)
requester.requestFocus()
}
}
@Composable
private fun OnrampAmountSecondary(state: OnrampAmountSecondaryFieldUM) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(
top = TangemTheme.dimens.spacing8,
start = TangemTheme.dimens.spacing12,
end = TangemTheme.dimens.spacing12,
),
contentAlignment = Alignment.Center,
) {
when (state) {
is OnrampAmountSecondaryFieldUM.Content -> Text(
text = state.amount.resolveReference(),
style = TangemTheme.typography.caption2.copy(textDirection = TextDirection.ContentOrLtr),
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
is OnrampAmountSecondaryFieldUM.Error -> Text(
text = state.error.resolveReference(),
color = TangemTheme.colors.text.warning,
style = TangemTheme.typography.caption2,
textAlign = TextAlign.Center,
)
is OnrampAmountSecondaryFieldUM.Loading -> TextShimmer(
style = TangemTheme.typography.caption2,
modifier = Modifier.width(TangemTheme.dimens.size62),
)
}
}
}
@Composable
private fun OnrampCurrencyIcon(currencyUM: OnrampCurrencyUM, modifier: Modifier = Modifier) {
Row(
modifier = modifier.clickable(onClick = currencyUM.onClick),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing8),
) {
AsyncImage(
modifier = Modifier
.size(TangemTheme.dimens.size40)
.clip(CircleShape),
model = currencyUM.iconUrl,
contentDescription = null,
)
Icon(
modifier = Modifier.size(TangemTheme.dimens.size16),
painter = painterResource(id = R.drawable.ic_chevron_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}

View file

@ -2,7 +2,9 @@ package com.tangem.features.onramp.main.ui
import androidx.compose.foundation.background
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.FabPosition
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
@ -23,7 +25,7 @@ import com.tangem.features.onramp.main.entity.OnrampMainComponentUM
@Composable
internal fun OnrampMainComponentContent(state: OnrampMainComponentUM, modifier: Modifier = Modifier) {
Scaffold(
modifier = modifier,
modifier = modifier.imePadding(),
contentWindowInsets = WindowInsetsZero,
containerColor = TangemTheme.colors.background.secondary,
topBar = {
@ -42,7 +44,7 @@ internal fun OnrampMainComponentContent(state: OnrampMainComponentUM, modifier:
.wrapContentHeight()
when (state) {
is OnrampMainComponentUM.InitialLoading -> InitialLoading(modifier = contentModifier)
is OnrampMainComponentUM.Content -> Content(modifier = contentModifier)
is OnrampMainComponentUM.Content -> Content(modifier = contentModifier, state = state)
}
},
floatingActionButton = {
@ -85,8 +87,15 @@ private fun InitialLoading(modifier: Modifier = Modifier) {
}
@Composable
private fun Content(modifier: Modifier = Modifier) {
Box(modifier = modifier)
private fun Content(state: OnrampMainComponentUM.Content, modifier: Modifier = Modifier) {
Column(
modifier = modifier
.verticalScroll(rememberScrollState())
.navigationBarsPadding()
.padding(bottom = TangemTheme.dimens.spacing76),
) {
OnrampAmountContent(state = state.amountBlockState)
}
}
@Composable

View file

@ -63,7 +63,7 @@ internal class DefaultOnrampComponent @AssistedInject constructor(
OnrampChild.Main -> onrampMainComponentFactory.create(
context = childByContext(componentContext),
params = OnrampMainComponent.Params(
currency = params.currency,
cryptoCurrency = params.cryptoCurrency,
openSettings = { navigation.push(OnrampChild.Settings) },
),
)