Updated on 2026-08-14
This commit is contained in:
commit
c46701b9c3
22 changed files with 232 additions and 109 deletions
|
|
@ -30,7 +30,7 @@ internal class LockUserWalletsTimer(
|
|||
lifecycle.addObserver(this)
|
||||
}
|
||||
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
owner.lifecycleScope.launch {
|
||||
val wasApplicationStopped = settingsRepository.wasApplicationStopped()
|
||||
val shouldOpenWelcomeScreenOnResume = settingsRepository.shouldOpenWelcomeScreenOnResume()
|
||||
|
|
@ -45,8 +45,6 @@ internal class LockUserWalletsTimer(
|
|||
|
||||
settingsRepository.setWasApplicationStopped(value = false)
|
||||
|
||||
start()
|
||||
|
||||
if (shouldOpenWelcomeScreenOnResume) {
|
||||
store.dispatchNavigationAction { replaceAll(AppRoute.Welcome()) }
|
||||
settingsRepository.setShouldOpenWelcomeScreenOnResume(value = false)
|
||||
|
|
@ -54,6 +52,10 @@ internal class LockUserWalletsTimer(
|
|||
}
|
||||
}
|
||||
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
start()
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
Timber.i("Owner stopped")
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<string name="action_buttons_sell_navigation_bar_title">Продать</string>
|
||||
<string name="action_buttons_service_loading_alert_message">Это может занять несколько секунд. Пожалуйста, попробуйте позже.</string>
|
||||
<string name="action_buttons_service_loading_alert_title">Данные ещё не загрузились</string>
|
||||
<string name="action_buttons_something_wrong_alert_message">Это действие в данный момент недоступно, пожалуйста попробуйте позже или обновите данные, сделав свайп экрана вниз.</string>
|
||||
<string name="action_buttons_something_wrong_alert_message">Это действие в данный момент недоступно, пожалуйста, попробуйте позже или обновите данные, сделав свайп экрана вниз.</string>
|
||||
<string name="action_buttons_something_wrong_alert_title">Кнопка недоступна</string>
|
||||
<string name="action_buttons_swap_choose_token">Выберите токен</string>
|
||||
<string name="action_buttons_swap_empty_search_message">Не нашли свой токен? Перейдите в раздел «Рынок» на главной странице и добавьте его в свой портфель для обмена.</string>
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
<string name="action_buttons_swap_no_available_pair_notification_message">Немає доступних токенів для обміну з обраним токеном. Будь ласка, оберіть інший.</string>
|
||||
<string name="action_buttons_swap_no_available_pair_notification_title">Немає вільної пари</string>
|
||||
<string name="action_buttons_swap_no_tokens_added_alert_message">Щоб скористатися функцією обміну, у вашому портфелі має бути щонайменше 2 токени.</string>
|
||||
<string name="action_buttons_swap_no_tokens_added_alert_title">Додайте токени</string>
|
||||
<string name="action_buttons_swap_not_enough_tokens_alert_message">У вас додано лише 1 токен. Щоб скористатися функцією обміну, потрібно додати мінімум 2 токени.</string>
|
||||
<string name="action_buttons_swap_not_enough_tokens_alert_title">Додайте токени</string>
|
||||
<string name="action_buttons_you_want_to_receive">Оберіть токен для отримання</string>
|
||||
|
|
@ -324,6 +325,7 @@
|
|||
<string name="feedback_preface_scan_failed">Розкажіть, будь ласка, яку картку або кільце ви маєте?</string>
|
||||
<string name="feedback_preface_support">Привіт, команда підтримки,</string>
|
||||
<string name="feedback_preface_tx_failed">Будь ласка, розкажіть нам більше про вашу проблему. Кожна дрібниця може допомогти.</string>
|
||||
<string name="feedback_subject_pre_activated_wallet">Раніше активований гаманець</string>
|
||||
<string name="feedback_subject_rate_negative">Мої пропозиції</string>
|
||||
<string name="feedback_subject_scan_failed">Не вдається відсканувати картку/кільце</string>
|
||||
<string name="feedback_subject_support">Звернення в підтримку</string>
|
||||
|
|
@ -598,6 +600,7 @@
|
|||
<string name="onramp_country_search">Пошук за країною</string>
|
||||
<string name="onramp_country_unavailable">Недоступно</string>
|
||||
<string name="onramp_currency_search">Пошук по валюті</string>
|
||||
<string name="onramp_legal">Використовуючи сервіс покупки, ви погоджуєтесь з %1$s та %2$s</string>
|
||||
<string name="onramp_max_amount_restriction">Сума покупки не може бути більше ніж %s</string>
|
||||
<string name="onramp_min_amount_restriction">Сума покупки повинна бути не менше %s</string>
|
||||
<string name="onramp_no_available_providers">Для данної валюти немає доступних провайдерів</string>
|
||||
|
|
|
|||
|
|
@ -468,6 +468,7 @@ internal class DefaultOnrampRepository(
|
|||
OnrampQuote.Error(
|
||||
paymentMethod = paymentMethod,
|
||||
provider = provider,
|
||||
fromAmount = fromOnrampAmount,
|
||||
error = onrampError,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,11 +6,12 @@ sealed class OnrampQuote {
|
|||
|
||||
abstract val paymentMethod: OnrampPaymentMethod
|
||||
abstract val provider: OnrampProvider
|
||||
abstract val fromAmount: OnrampAmount
|
||||
|
||||
data class Data(
|
||||
override val paymentMethod: OnrampPaymentMethod,
|
||||
override val provider: OnrampProvider,
|
||||
val fromAmount: OnrampAmount,
|
||||
override val fromAmount: OnrampAmount,
|
||||
val toAmount: OnrampAmount,
|
||||
val minFromAmount: OnrampAmount,
|
||||
val maxFromAmount: OnrampAmount,
|
||||
|
|
@ -19,13 +20,14 @@ sealed class OnrampQuote {
|
|||
data class AmountError(
|
||||
override val paymentMethod: OnrampPaymentMethod,
|
||||
override val provider: OnrampProvider,
|
||||
val fromAmount: OnrampAmount,
|
||||
override val fromAmount: OnrampAmount,
|
||||
val error: OnrampError.AmountError,
|
||||
) : OnrampQuote()
|
||||
|
||||
data class Error(
|
||||
override val paymentMethod: OnrampPaymentMethod,
|
||||
override val provider: OnrampProvider,
|
||||
override val fromAmount: OnrampAmount,
|
||||
val error: OnrampError,
|
||||
) : OnrampQuote()
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ internal interface ConfirmResidencyComponent : ComposableBottomSheetComponent {
|
|||
data class Params(
|
||||
val cryptoCurrency: CryptoCurrency,
|
||||
val country: OnrampCountry,
|
||||
val onDismiss: (OnrampCountry) -> Unit,
|
||||
val onDismiss: () -> Unit,
|
||||
)
|
||||
|
||||
interface Factory : ComponentFactory<Params, ConfirmResidencyComponent>
|
||||
|
|
|
|||
|
|
@ -66,11 +66,17 @@ internal class DefaultConfirmResidencyComponent @AssistedInject constructor(
|
|||
config: ConfirmResidencyBottomSheetConfig,
|
||||
componentContext: ComponentContext,
|
||||
): ComposableBottomSheetComponent = when (config) {
|
||||
ConfirmResidencyBottomSheetConfig.SelectCountry -> selectCountryComponentFactory.create(
|
||||
is ConfirmResidencyBottomSheetConfig.SelectCountry -> selectCountryComponentFactory.create(
|
||||
context = childByContext(componentContext),
|
||||
params = SelectCountryComponent.Params(
|
||||
cryptoCurrency = params.cryptoCurrency,
|
||||
onDismiss = model.bottomSheetNavigation::dismiss,
|
||||
onDismiss = {
|
||||
// Dismiss country select sheet
|
||||
model.bottomSheetNavigation.dismiss()
|
||||
|
||||
// Dismiss confirm residency sheet
|
||||
config.onDismiss()
|
||||
},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,5 +6,5 @@ import kotlinx.serialization.Serializable
|
|||
internal sealed class ConfirmResidencyBottomSheetConfig {
|
||||
|
||||
@Serializable
|
||||
data object SelectCountry : ConfirmResidencyBottomSheetConfig()
|
||||
data class SelectCountry(val onDismiss: () -> Unit) : ConfirmResidencyBottomSheetConfig()
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@ import com.tangem.core.decompose.navigation.Router
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.onramp.OnrampSaveDefaultCountryUseCase
|
||||
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
|
||||
import com.tangem.domain.onramp.model.OnrampCountry
|
||||
import com.tangem.features.onramp.confirmresidency.ConfirmResidencyComponent
|
||||
import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyBottomSheetConfig
|
||||
import com.tangem.features.onramp.confirmresidency.entity.ConfirmResidencyUM
|
||||
|
|
@ -35,7 +36,7 @@ internal class ConfirmResidencyModel @Inject constructor(
|
|||
country = params.country.name,
|
||||
countryFlagUrl = params.country.image,
|
||||
isCountrySupported = params.country.onrampAvailable,
|
||||
primaryButtonConfig = getPrimaryButtonConfig(),
|
||||
primaryButtonConfig = getPrimaryButtonConfig(params.country),
|
||||
secondaryButtonConfig = ConfirmResidencyUM.ActionButtonConfig(
|
||||
onClick = ::onChangeClick,
|
||||
text = resourceReference(R.string.common_change),
|
||||
|
|
@ -47,11 +48,12 @@ internal class ConfirmResidencyModel @Inject constructor(
|
|||
analyticsEventHandler.send(OnrampAnalyticsEvent.ResidenceConfirmScreenOpened(params.country.name))
|
||||
}
|
||||
|
||||
private fun getPrimaryButtonConfig() = if (params.country.onrampAvailable) {
|
||||
private fun getPrimaryButtonConfig(country: OnrampCountry) = if (country.onrampAvailable) {
|
||||
ConfirmResidencyUM.ActionButtonConfig(
|
||||
onClick = {
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceConfirm(params.country.name))
|
||||
modelScope.launch { saveDefaultCountryUseCase.invoke(params.country) }
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceConfirm(country.name))
|
||||
modelScope.launch { saveDefaultCountryUseCase.invoke(country) }
|
||||
params.onDismiss()
|
||||
},
|
||||
text = resourceReference(R.string.common_confirm),
|
||||
)
|
||||
|
|
@ -67,6 +69,6 @@ internal class ConfirmResidencyModel @Inject constructor(
|
|||
|
||||
private fun onChangeClick() {
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceChange)
|
||||
bottomSheetNavigation.activate(ConfirmResidencyBottomSheetConfig.SelectCountry)
|
||||
bottomSheetNavigation.activate(ConfirmResidencyBottomSheetConfig.SelectCountry(params.onDismiss))
|
||||
}
|
||||
}
|
||||
|
|
@ -58,7 +58,7 @@ internal class DefaultOnrampMainComponent @AssistedInject constructor(
|
|||
country = config.country,
|
||||
onDismiss = {
|
||||
model.bottomSheetNavigation.dismiss()
|
||||
model.handleOnrampAvailable(it.defaultCurrency)
|
||||
model.handleOnrampAvailable()
|
||||
},
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ import com.tangem.domain.demo.IsDemoCardUseCase
|
|||
import com.tangem.domain.onramp.*
|
||||
import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent
|
||||
import com.tangem.domain.onramp.model.OnrampAvailability
|
||||
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.onramp.model.error.OnrampError
|
||||
|
|
@ -105,8 +104,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
fun handleOnrampAvailable(currency: OnrampCurrency) {
|
||||
_state.update { stateFactory.getReadyState(currency) }
|
||||
fun handleOnrampAvailable() {
|
||||
subscribeToCountryAndCurrencyUpdates()
|
||||
subscribeToQuotesUpdate()
|
||||
}
|
||||
|
|
@ -125,7 +123,7 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
|
||||
private fun handleOnrampAvailability(availability: OnrampAvailability) {
|
||||
when (availability) {
|
||||
is OnrampAvailability.Available -> handleOnrampAvailable(availability.currency)
|
||||
is OnrampAvailability.Available -> handleOnrampAvailable()
|
||||
is OnrampAvailability.ConfirmResidency,
|
||||
is OnrampAvailability.NotSupported,
|
||||
-> bottomSheetNavigation.activate(OnrampMainBottomSheetConfig.ConfirmResidency(availability.country))
|
||||
|
|
@ -139,7 +137,13 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
ifLeft = ::handleOnrampError,
|
||||
ifRight = { country ->
|
||||
if (country == null) return@onEach
|
||||
_state.update { amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency) }
|
||||
_state.update {
|
||||
if (it is OnrampMainComponentUM.InitialLoading) {
|
||||
stateFactory.getReadyState(country.defaultCurrency)
|
||||
} else {
|
||||
amountStateFactory.getUpdatedCurrencyState(country.defaultCurrency)
|
||||
}
|
||||
}
|
||||
updatePairsAndQuotes()
|
||||
},
|
||||
)
|
||||
|
|
@ -278,10 +282,11 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
private fun handleQuoteResult(quotes: List<OnrampQuote>) {
|
||||
sendOnrampQuotesErrorAnalytic(quotes)
|
||||
|
||||
val quote = quotes.firstOrNull()
|
||||
val quote = quotes.firstOrNull { it !is OnrampQuote.Error }
|
||||
|
||||
if (quote == null) {
|
||||
_state.update { stateFactory.getErrorState() }
|
||||
lastAmount.value = BigDecimal.ZERO
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -295,15 +300,17 @@ internal class OnrampMainComponentModel @Inject constructor(
|
|||
|
||||
val isBestProvider = quote == bestProvider && hasBestProvider
|
||||
|
||||
if (quote is OnrampQuote.Data && lastAmount.value != quote.fromAmount.value) {
|
||||
if (lastAmount.value != quote.fromAmount.value) {
|
||||
lastAmount.value = quote.fromAmount.value
|
||||
analyticsEventHandler.send(
|
||||
OnrampAnalyticsEvent.ProviderCalculated(
|
||||
providerName = quote.provider.info.name,
|
||||
tokenSymbol = params.cryptoCurrency.symbol,
|
||||
paymentMethod = quote.paymentMethod.name,
|
||||
),
|
||||
)
|
||||
if (quote is OnrampQuote.Data) {
|
||||
analyticsEventHandler.send(
|
||||
OnrampAnalyticsEvent.ProviderCalculated(
|
||||
providerName = quote.provider.info.name,
|
||||
tokenSymbol = params.cryptoCurrency.symbol,
|
||||
paymentMethod = quote.paymentMethod.name,
|
||||
),
|
||||
)
|
||||
}
|
||||
_state.update {
|
||||
amountStateFactory.getAmountSecondaryUpdatedState(
|
||||
quote = quote,
|
||||
|
|
|
|||
|
|
@ -6,30 +6,42 @@ internal sealed interface ProviderListItemUM {
|
|||
|
||||
val providerId: String
|
||||
val name: String
|
||||
val imageUrl: String
|
||||
|
||||
data class Available(
|
||||
override val providerId: String,
|
||||
override val name: String,
|
||||
val imageUrl: String,
|
||||
val rate: String,
|
||||
val isBestRate: Boolean,
|
||||
val diffRate: TextReference?,
|
||||
val isSelected: Boolean,
|
||||
val onClick: () -> Unit,
|
||||
) : ProviderListItemUM
|
||||
sealed interface Available : ProviderListItemUM {
|
||||
val onClick: () -> Unit
|
||||
val providerResult: SelectProviderResult
|
||||
val isBestRate: Boolean
|
||||
val isSelected: Boolean
|
||||
|
||||
data class AvailableWithError(
|
||||
override val providerId: String,
|
||||
override val name: String,
|
||||
val imageUrl: String,
|
||||
val subtitle: TextReference,
|
||||
val onClick: () -> Unit,
|
||||
) : ProviderListItemUM
|
||||
data class Content(
|
||||
override val providerId: String,
|
||||
override val name: String,
|
||||
override val onClick: () -> Unit,
|
||||
override val imageUrl: String,
|
||||
override val providerResult: SelectProviderResult,
|
||||
override val isBestRate: Boolean,
|
||||
override val isSelected: Boolean,
|
||||
val rate: String,
|
||||
val diffRate: TextReference?,
|
||||
) : Available
|
||||
|
||||
data class WithError(
|
||||
override val providerId: String,
|
||||
override val name: String,
|
||||
override val onClick: () -> Unit,
|
||||
override val imageUrl: String,
|
||||
override val providerResult: SelectProviderResult,
|
||||
override val isBestRate: Boolean,
|
||||
override val isSelected: Boolean,
|
||||
val subtitle: TextReference,
|
||||
) : Available
|
||||
}
|
||||
|
||||
data class Unavailable(
|
||||
override val providerId: String,
|
||||
override val name: String,
|
||||
val imageUrl: String,
|
||||
override val imageUrl: String,
|
||||
val subtitle: TextReference,
|
||||
) : ProviderListItemUM
|
||||
}
|
||||
|
|
@ -4,13 +4,14 @@ import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
internal data class SelectPaymentAndProviderUM(
|
||||
val paymentMethods: ImmutableList<OnrampPaymentMethod>,
|
||||
val selectedPaymentMethod: SelectProviderUM,
|
||||
val paymentMethods: ImmutableList<PaymentProviderUM>,
|
||||
val selectedPaymentMethod: PaymentProviderUM,
|
||||
val selectedProviderId: String,
|
||||
val isPaymentMethodClickEnabled: Boolean,
|
||||
val onPaymentMethodClick: () -> Unit,
|
||||
)
|
||||
|
||||
internal data class SelectProviderUM(
|
||||
internal data class PaymentProviderUM(
|
||||
val paymentMethod: OnrampPaymentMethod,
|
||||
val providers: ImmutableList<ProviderListItemUM>,
|
||||
)
|
||||
|
|
@ -72,9 +72,21 @@ internal class SelectProviderModel @Inject constructor(
|
|||
},
|
||||
ifRight = { it },
|
||||
)
|
||||
|
||||
val filteredEmptyMethods = methods.mapNotNull { method ->
|
||||
val providers = getOnrampProviderWithQuoteUseCase(method).getOrNull()
|
||||
if (!providers.isNullOrEmpty()) {
|
||||
PaymentProviderUM(
|
||||
paymentMethod = method,
|
||||
providers = providers.toProvidersListItems(),
|
||||
)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
_state.value = state.value.copy(
|
||||
paymentMethods = methods.toPersistentList(),
|
||||
isPaymentMethodClickEnabled = methods.isNotEmpty(),
|
||||
paymentMethods = filteredEmptyMethods.toPersistentList(),
|
||||
isPaymentMethodClickEnabled = filteredEmptyMethods.isNotEmpty(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -111,13 +123,19 @@ internal class SelectProviderModel @Inject constructor(
|
|||
|
||||
private fun getInitialState(): SelectPaymentAndProviderUM {
|
||||
return SelectPaymentAndProviderUM(
|
||||
paymentMethods = persistentListOf(params.selectedPaymentMethod),
|
||||
paymentMethods = persistentListOf(
|
||||
PaymentProviderUM(
|
||||
paymentMethod = params.selectedPaymentMethod,
|
||||
providers = emptyList<ProviderListItemUM>().toImmutableList(),
|
||||
),
|
||||
),
|
||||
isPaymentMethodClickEnabled = false,
|
||||
onPaymentMethodClick = ::openPaymentMethods,
|
||||
selectedPaymentMethod = SelectProviderUM(
|
||||
selectedPaymentMethod = PaymentProviderUM(
|
||||
paymentMethod = params.selectedPaymentMethod,
|
||||
providers = emptyList<ProviderListItemUM>().toImmutableList(),
|
||||
),
|
||||
selectedProviderId = params.selectedProviderId,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -131,16 +149,18 @@ internal class SelectProviderModel @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun ImmutableList<OnrampPaymentMethod>.toPaymentUMList(): ImmutableList<PaymentMethodUM> = map { method ->
|
||||
private fun ImmutableList<PaymentProviderUM>.toPaymentUMList(): ImmutableList<PaymentMethodUM> = map { method ->
|
||||
PaymentMethodUM(
|
||||
id = method.id,
|
||||
imageUrl = method.imageUrl,
|
||||
name = method.name,
|
||||
id = method.paymentMethod.id,
|
||||
imageUrl = method.paymentMethod.imageUrl,
|
||||
name = method.paymentMethod.name,
|
||||
onSelect = { onPaymentMethodSelected(method) },
|
||||
)
|
||||
}.toPersistentList()
|
||||
|
||||
private fun onPaymentMethodSelected(paymentMethod: OnrampPaymentMethod) {
|
||||
private fun onPaymentMethodSelected(methodContainer: PaymentProviderUM) {
|
||||
val paymentMethod = methodContainer.paymentMethod
|
||||
val firstProvider = methodContainer.providers.firstOrNull()
|
||||
analyticsEventHandler.send(OnrampAnalyticsEvent.OnPaymentMethodChosen(paymentMethod = paymentMethod.name))
|
||||
modelScope.launch {
|
||||
saveSelectedPaymentMethod.invoke(paymentMethod)
|
||||
|
|
@ -151,6 +171,10 @@ internal class SelectProviderModel @Inject constructor(
|
|||
),
|
||||
)
|
||||
}
|
||||
if (firstProvider is ProviderListItemUM.Available) {
|
||||
onProviderSelected(firstProvider.providerResult, firstProvider.isBestRate)
|
||||
}
|
||||
|
||||
bottomSheetNavigation.dismiss()
|
||||
}
|
||||
}
|
||||
|
|
@ -165,6 +189,8 @@ internal class SelectProviderModel @Inject constructor(
|
|||
val hasBestProvider = isMultipleQuotes && isOtherQuotesHasData
|
||||
|
||||
return sorted.map { quote ->
|
||||
val isSelectedProvider = state.value.selectedProviderId == quote.provider.id
|
||||
|
||||
when (quote) {
|
||||
is OnrampProviderWithQuote.Data -> {
|
||||
val rate = quote.toAmount.value.format {
|
||||
|
|
@ -173,28 +199,29 @@ internal class SelectProviderModel @Inject constructor(
|
|||
val rateDiff = bestProvider?.toAmount?.value?.let { bestRate ->
|
||||
BigDecimal.ONE - quote.toAmount.value / bestRate
|
||||
}
|
||||
val isSelectedProvider = quote.provider.id == params.selectedProviderId
|
||||
val isSelectedPayment = quote.paymentMethod.id == params.selectedPaymentMethod.id
|
||||
val isBestProvider = quote == bestProvider && hasBestProvider
|
||||
ProviderListItemUM.Available(
|
||||
val providerResult = SelectProviderResult.ProviderWithQuote(
|
||||
paymentMethod = quote.paymentMethod,
|
||||
provider = quote.provider,
|
||||
fromAmount = quote.fromAmount,
|
||||
toAmount = quote.toAmount,
|
||||
)
|
||||
ProviderListItemUM.Available.Content(
|
||||
providerId = quote.provider.id,
|
||||
imageUrl = quote.provider.info.imageLarge,
|
||||
name = quote.provider.info.name,
|
||||
rate = rate,
|
||||
isSelected = isSelectedProvider && isSelectedPayment,
|
||||
isBestRate = isBestProvider,
|
||||
diffRate = stringReference("$MINUS${rateDiff.format { percent() }}")
|
||||
.takeIf { hasBestProvider },
|
||||
providerResult = providerResult,
|
||||
isSelected = isSelectedProvider,
|
||||
onClick = {
|
||||
onProviderSelected(
|
||||
result = SelectProviderResult.ProviderWithQuote(
|
||||
paymentMethod = quote.paymentMethod,
|
||||
provider = quote.provider,
|
||||
fromAmount = quote.fromAmount,
|
||||
toAmount = quote.toAmount,
|
||||
),
|
||||
result = providerResult,
|
||||
isBestRate = isBestProvider,
|
||||
)
|
||||
params.onDismiss()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -207,21 +234,25 @@ internal class SelectProviderModel @Inject constructor(
|
|||
is OnrampError.AmountError.TooBigError -> R.string.express_provider_max_amount
|
||||
is OnrampError.AmountError.TooSmallError -> R.string.express_provider_min_amount
|
||||
}
|
||||
|
||||
ProviderListItemUM.AvailableWithError(
|
||||
val providerResult = SelectProviderResult.ProviderWithError(
|
||||
paymentMethod = quoteError.paymentMethod,
|
||||
provider = quote.provider,
|
||||
quoteError = quote.quoteError,
|
||||
)
|
||||
ProviderListItemUM.Available.WithError(
|
||||
providerId = quote.provider.id,
|
||||
imageUrl = quote.provider.info.imageLarge,
|
||||
name = quote.provider.info.name,
|
||||
subtitle = resourceReference(errorSubtitleRes, wrappedList(amount)),
|
||||
providerResult = providerResult,
|
||||
isBestRate = false,
|
||||
isSelected = isSelectedProvider,
|
||||
onClick = {
|
||||
onProviderSelected(
|
||||
result = SelectProviderResult.ProviderWithError(
|
||||
paymentMethod = quoteError.paymentMethod,
|
||||
provider = quote.provider,
|
||||
quoteError = quote.quoteError,
|
||||
),
|
||||
result = providerResult,
|
||||
isBestRate = bestProvider == quote,
|
||||
)
|
||||
params.onDismiss()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -247,8 +278,10 @@ internal class SelectProviderModel @Inject constructor(
|
|||
tokenSymbol = params.cryptoCurrency.symbol,
|
||||
),
|
||||
)
|
||||
_state.update {
|
||||
it.copy(selectedProviderId = result.provider.id)
|
||||
}
|
||||
params.onProviderClick(result, isBestRate)
|
||||
params.onDismiss()
|
||||
}
|
||||
|
||||
private fun sendOnrampErrorEvent(error: OnrampError) {
|
||||
|
|
|
|||
|
|
@ -2,59 +2,83 @@ package com.tangem.features.onramp.providers.model.previewData
|
|||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.stringReference
|
||||
import com.tangem.domain.onramp.model.OnrampPaymentMethod
|
||||
import com.tangem.domain.onramp.model.PaymentMethodType
|
||||
import com.tangem.domain.onramp.model.*
|
||||
import com.tangem.features.onramp.providers.entity.PaymentProviderUM
|
||||
import com.tangem.features.onramp.providers.entity.ProviderListItemUM
|
||||
import com.tangem.features.onramp.providers.entity.SelectPaymentAndProviderUM
|
||||
import com.tangem.features.onramp.providers.entity.SelectProviderUM
|
||||
import com.tangem.features.onramp.providers.entity.SelectProviderResult
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal object SelectProviderPreviewData {
|
||||
|
||||
private val paymentMethods = persistentListOf(
|
||||
OnrampPaymentMethod(
|
||||
id = "card",
|
||||
name = "Card",
|
||||
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
|
||||
type = PaymentMethodType.CARD,
|
||||
),
|
||||
private val paymentMethod = OnrampPaymentMethod(
|
||||
id = "card",
|
||||
name = "Card",
|
||||
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/PaymentMethods/visa-mc.png",
|
||||
type = PaymentMethodType.CARD,
|
||||
)
|
||||
|
||||
private val providerListUM = SelectProviderUM(
|
||||
paymentMethod = paymentMethods.first(),
|
||||
private val provider = OnrampProvider(
|
||||
id = "mercuryo",
|
||||
info = OnrampProviderInfo(
|
||||
name = "Mercuryo",
|
||||
imageLarge = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/mercuryo1024.png",
|
||||
termsOfUseLink = null,
|
||||
privacyPolicyLink = null,
|
||||
),
|
||||
paymentMethods = listOf(),
|
||||
)
|
||||
|
||||
private val providerListUM = PaymentProviderUM(
|
||||
paymentMethod = paymentMethod,
|
||||
providers = persistentListOf(
|
||||
ProviderListItemUM.Available(
|
||||
providerId = "mercuryo",
|
||||
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/mercuryo1024.png",
|
||||
name = "Mercuryo",
|
||||
ProviderListItemUM.Available.Content(
|
||||
providerId = provider.id,
|
||||
imageUrl = provider.info.imageLarge,
|
||||
name = provider.info.name,
|
||||
rate = "1 BTC",
|
||||
isSelected = true,
|
||||
isBestRate = true,
|
||||
diffRate = TextReference.EMPTY,
|
||||
providerResult = SelectProviderResult.ProviderWithQuote(
|
||||
paymentMethod = paymentMethod,
|
||||
provider = provider,
|
||||
fromAmount = OnrampAmount(symbol = "$", value = BigDecimal.ONE, decimals = 2),
|
||||
toAmount = OnrampAmount(symbol = "BTC", value = BigDecimal.ONE, decimals = 18),
|
||||
|
||||
),
|
||||
onClick = {},
|
||||
),
|
||||
ProviderListItemUM.Available(
|
||||
providerId = "mercuryo",
|
||||
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/mercuryo1024.png",
|
||||
name = "Mercuryo",
|
||||
ProviderListItemUM.Available.Content(
|
||||
providerId = provider.id,
|
||||
imageUrl = provider.info.imageLarge,
|
||||
name = provider.info.name,
|
||||
rate = "1 BTC",
|
||||
isSelected = false,
|
||||
isBestRate = false,
|
||||
diffRate = stringReference("-0.03%"),
|
||||
providerResult = SelectProviderResult.ProviderWithQuote(
|
||||
paymentMethod = paymentMethod,
|
||||
provider = provider,
|
||||
fromAmount = OnrampAmount(symbol = "$", value = BigDecimal.ONE, decimals = 2),
|
||||
toAmount = OnrampAmount(symbol = "BTC", value = BigDecimal.ONE, decimals = 18),
|
||||
),
|
||||
onClick = {},
|
||||
),
|
||||
ProviderListItemUM.Unavailable(
|
||||
providerId = "mercuryo",
|
||||
imageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/mercuryo1024.png",
|
||||
name = "Mercuryo",
|
||||
providerId = provider.id,
|
||||
imageUrl = provider.info.imageLarge,
|
||||
name = provider.info.name,
|
||||
subtitle = stringReference("Avaiable from 0,00413 BTC"),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
val state = SelectPaymentAndProviderUM(
|
||||
paymentMethods = paymentMethods,
|
||||
paymentMethods = persistentListOf(providerListUM),
|
||||
selectedPaymentMethod = providerListUM,
|
||||
selectedProviderId = provider.id,
|
||||
isPaymentMethodClickEnabled = false,
|
||||
onPaymentMethodClick = {},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -124,16 +124,17 @@ private fun PaymentMethodBlock(
|
|||
@Composable
|
||||
private fun ProviderItem(state: ProviderListItemUM, modifier: Modifier = Modifier) {
|
||||
when (state) {
|
||||
is ProviderListItemUM.Available -> AvailableProviderItem(
|
||||
is ProviderListItemUM.Available.Content -> AvailableProviderItem(
|
||||
modifier = modifier,
|
||||
state = state,
|
||||
)
|
||||
is ProviderListItemUM.AvailableWithError -> {
|
||||
is ProviderListItemUM.Available.WithError -> {
|
||||
UnavailableProviderItem(
|
||||
modifier = modifier.clickable(onClick = state.onClick),
|
||||
imageUrl = state.imageUrl,
|
||||
providerName = state.name,
|
||||
subtitle = state.subtitle,
|
||||
isSelected = state.isSelected,
|
||||
)
|
||||
}
|
||||
is ProviderListItemUM.Unavailable -> UnavailableProviderItem(
|
||||
|
|
@ -141,12 +142,13 @@ private fun ProviderItem(state: ProviderListItemUM, modifier: Modifier = Modifie
|
|||
imageUrl = state.imageUrl,
|
||||
providerName = state.name,
|
||||
subtitle = state.subtitle,
|
||||
isSelected = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AvailableProviderItem(state: ProviderListItemUM.Available, modifier: Modifier = Modifier) {
|
||||
private fun AvailableProviderItem(state: ProviderListItemUM.Available.Content, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier
|
||||
.then(
|
||||
|
|
@ -220,10 +222,19 @@ private fun UnavailableProviderItem(
|
|||
imageUrl: String,
|
||||
providerName: String,
|
||||
subtitle: TextReference,
|
||||
isSelected: Boolean,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
modifier = modifier.padding(all = TangemTheme.dimens.spacing12),
|
||||
modifier = modifier
|
||||
.then(
|
||||
if (isSelected) {
|
||||
Modifier.selectedBorder()
|
||||
} else {
|
||||
Modifier.clip(RoundedCornerShape(16.dp))
|
||||
},
|
||||
)
|
||||
.padding(all = TangemTheme.dimens.spacing12),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -185,6 +185,14 @@ internal class SwapViewModel @Inject constructor(
|
|||
}.launchIn(viewModelScope)
|
||||
}
|
||||
|
||||
override fun onStart(owner: LifecycleOwner) {
|
||||
startLoadingQuotesFromLastState(true)
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
singleTaskScheduler.cancelTask()
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
singleTaskScheduler.cancelTask()
|
||||
super.onCleared()
|
||||
|
|
|
|||
|
|
@ -152,6 +152,7 @@ internal object TokenDetailsPreviewData {
|
|||
formatArgs = wrappedList("Solana"),
|
||||
),
|
||||
iconState = iconState,
|
||||
isEnabled = true,
|
||||
onStakeClicked = {},
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ internal sealed interface StakingBlockUM {
|
|||
val iconState: IconState,
|
||||
val titleText: TextReference,
|
||||
val subtitleText: TextReference,
|
||||
val isEnabled: Boolean,
|
||||
val onStakeClicked: () -> Unit,
|
||||
) : StakingBlockUM
|
||||
}
|
||||
|
|
@ -158,7 +158,7 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
return when {
|
||||
stakingCryptoAmount.isNullOrZero() && stakingEntryInfo != null -> {
|
||||
if (pendingBalances.isEmpty()) {
|
||||
getStakeAvailableState(stakingEntryInfo, iconState)
|
||||
getStakeAvailableState(stakingEntryInfo, iconState, isStakingButtonEnabled(status))
|
||||
} else {
|
||||
getStakedBlockWithFiatAmount(status, pendingBalances.sumOf { it.amount }, null)
|
||||
}
|
||||
|
|
@ -170,6 +170,12 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
}
|
||||
}
|
||||
|
||||
private fun isStakingButtonEnabled(status: CryptoCurrencyStatus): Boolean {
|
||||
return status.value is CryptoCurrencyStatus.Loaded ||
|
||||
status.value is CryptoCurrencyStatus.NoQuote ||
|
||||
status.value is CryptoCurrencyStatus.Custom
|
||||
}
|
||||
|
||||
private fun getStakedBlockWithFiatAmount(
|
||||
status: CryptoCurrencyStatus,
|
||||
stakingAmount: BigDecimal?,
|
||||
|
|
@ -210,6 +216,7 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
private fun getStakeAvailableState(
|
||||
stakingEntryInfo: StakingEntryInfo,
|
||||
iconState: IconState,
|
||||
isEnabled: Boolean,
|
||||
): StakingBlockUM.StakeAvailable {
|
||||
val apr = stakingEntryInfo.apr.format { percent() }
|
||||
return StakingBlockUM.StakeAvailable(
|
||||
|
|
@ -222,6 +229,7 @@ internal class TokenDetailsLoadedBalanceConverter(
|
|||
formatArgs = wrappedList(stakingEntryInfo.tokenSymbol),
|
||||
),
|
||||
iconState = iconState,
|
||||
isEnabled = isEnabled,
|
||||
onStakeClicked = clickIntents::onStakeBannerClick,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ private fun StakingAvailableContent(state: StakingBlockUM.StakeAvailable, modifi
|
|||
SecondaryButton(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
text = stringResource(id = R.string.common_stake),
|
||||
enabled = state.isEnabled,
|
||||
onClick = state.onStakeClicked,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ internal enum class Wallet2CobrandImage(
|
|||
Pastel(
|
||||
cards2ResId = R.drawable.ill_pastel_cards3_120_106,
|
||||
cards3ResId = R.drawable.ill_pastel_cards3_120_106,
|
||||
batchIds = setOf("AF43", "AF44", "AF45"),
|
||||
batchIds = setOf("AF43", "AF44", "AF45", "AF78", "AF79", "AF80"),
|
||||
),
|
||||
|
||||
RedPanda(
|
||||
|
|
@ -204,7 +204,7 @@ internal enum class Wallet2CobrandImage(
|
|||
Vivid(
|
||||
cards2ResId = R.drawable.ill_vivid_cards3_120_106,
|
||||
cards3ResId = R.drawable.ill_vivid_cards3_120_106,
|
||||
batchIds = setOf("AF40", "AF41", "AF42"),
|
||||
batchIds = setOf("AF40", "AF41", "AF42", "AF75", "AF76", "AF77"),
|
||||
),
|
||||
|
||||
VoltInu(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue