Updated on 2026-08-14
This commit is contained in:
parent
cfdc1ea05c
commit
9c6cc9006b
6 changed files with 358 additions and 0 deletions
|
|
@ -1,6 +1,55 @@
|
|||
package com.tangem.features.swap.v2.impl.common
|
||||
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.core.ui.format.bigdecimal.simple
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.features.swap.v2.impl.R
|
||||
|
||||
internal object SwapUtils {
|
||||
const val INCREASE_GAS_LIMIT_FOR_DEX = 112 // 12%
|
||||
const val INCREASE_GAS_LIMIT_FOR_CEX = 105 // 5%
|
||||
|
||||
fun getExpressErrorMessage(expressError: ExpressError): TextReference {
|
||||
return when (expressError) {
|
||||
is ExpressError.InternalError -> resourceReference(
|
||||
id = R.string.express_error_swap_unavailable,
|
||||
formatArgs = wrappedList(expressError.code),
|
||||
)
|
||||
is ExpressError.ExchangeNotPossibleError -> resourceReference(
|
||||
id = R.string.warning_express_pair_unavailable_message,
|
||||
formatArgs = wrappedList(expressError.code),
|
||||
)
|
||||
is ExpressError.UnknownError -> resourceReference(R.string.common_unknown_error)
|
||||
is ExpressError.ProviderNotActiveError,
|
||||
is ExpressError.ProviderNotFoundError,
|
||||
is ExpressError.ProviderNotAvailableError,
|
||||
is ExpressError.ProviderInternalError,
|
||||
-> resourceReference(
|
||||
id = R.string.express_error_swap_pair_unavailable,
|
||||
formatArgs = wrappedList(expressError.code),
|
||||
)
|
||||
is ExpressError.ProviderDifferentAmountError -> resourceReference(
|
||||
R.string.express_error_provider_amount_roundup,
|
||||
formatArgs = wrappedList(
|
||||
expressError.code,
|
||||
expressError.fromProviderAmount.format { simple(decimals = expressError.decimals) },
|
||||
),
|
||||
)
|
||||
else -> resourceReference(R.string.express_error_code, wrappedList(expressError.code.toString()))
|
||||
}
|
||||
}
|
||||
|
||||
internal fun getExpressErrorTitle(expressError: ExpressError): TextReference {
|
||||
return when (expressError) {
|
||||
is ExpressError.ExchangeNotPossibleError -> resourceReference(
|
||||
id = R.string.warning_express_pair_unavailable_title,
|
||||
formatArgs = wrappedList(expressError.code),
|
||||
)
|
||||
is ExpressError.UnknownError -> resourceReference(R.string.common_error)
|
||||
else -> resourceReference(R.string.warning_express_refresh_required_title)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.features.swap.v2.impl.notifications
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.common.ui.notifications.notifications
|
||||
import com.tangem.core.decompose.context.AppComponentContext
|
||||
import com.tangem.core.decompose.model.getOrCreateModel
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.features.swap.v2.impl.notifications.model.SwapNotificationsModel
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
|
||||
internal class SwapNotificationsComponent(
|
||||
appComponentContext: AppComponentContext,
|
||||
params: Params,
|
||||
) : AppComponentContext by appComponentContext {
|
||||
private val model: SwapNotificationsModel = getOrCreateModel(params)
|
||||
|
||||
val state: StateFlow<ImmutableList<NotificationUM>> = model.uiState
|
||||
|
||||
fun LazyListScope.content(
|
||||
state: ImmutableList<NotificationUM>,
|
||||
modifier: Modifier = Modifier,
|
||||
hasPaddingAbove: Boolean = false,
|
||||
isClickDisabled: Boolean = false,
|
||||
) {
|
||||
notifications(
|
||||
notifications = state,
|
||||
modifier = modifier,
|
||||
hasPaddingAbove = hasPaddingAbove,
|
||||
isClickDisabled = isClickDisabled,
|
||||
)
|
||||
}
|
||||
|
||||
data class Params(
|
||||
val swapNotificationData: SwapNotificationData,
|
||||
) {
|
||||
data class SwapNotificationData(
|
||||
val expressError: ExpressError?,
|
||||
val fromCryptoCurrency: CryptoCurrency?,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
package com.tangem.features.swap.v2.impl.notifications
|
||||
|
||||
import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent.Params.SwapNotificationData
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
internal interface SwapNotificationsUpdateListener {
|
||||
|
||||
/** Flow triggers swap notifications update */
|
||||
val updateTriggerFlow: Flow<SwapNotificationData>
|
||||
|
||||
/** Flow returns whether there is swap error notifications */
|
||||
val hasErrorFlow: Flow<Boolean>
|
||||
}
|
||||
|
||||
internal interface SwapNotificationsUpdateTrigger {
|
||||
/** Trigger return callback with check result */
|
||||
suspend fun callbackHasError(hasError: Boolean)
|
||||
|
||||
/** Trigger check swap notifications */
|
||||
suspend fun triggerUpdate(data: SwapNotificationData)
|
||||
}
|
||||
|
||||
@Singleton
|
||||
internal class DefaultSwapNotificationsUpdateTrigger @Inject constructor() :
|
||||
SwapNotificationsUpdateListener,
|
||||
SwapNotificationsUpdateTrigger {
|
||||
|
||||
override val updateTriggerFlow: SharedFlow<SwapNotificationData>
|
||||
field = MutableSharedFlow<SwapNotificationData>()
|
||||
|
||||
override val hasErrorFlow: SharedFlow<Boolean>
|
||||
field = MutableSharedFlow<Boolean>()
|
||||
|
||||
override suspend fun callbackHasError(hasError: Boolean) {
|
||||
hasErrorFlow.emit(hasError)
|
||||
}
|
||||
|
||||
override suspend fun triggerUpdate(data: SwapNotificationData) {
|
||||
updateTriggerFlow.emit(data)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package com.tangem.features.swap.v2.impl.notifications.di
|
||||
|
||||
import com.tangem.core.decompose.di.ModelComponent
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.features.swap.v2.impl.notifications.DefaultSwapNotificationsUpdateTrigger
|
||||
import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsUpdateListener
|
||||
import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsUpdateTrigger
|
||||
import com.tangem.features.swap.v2.impl.notifications.model.SwapNotificationsModel
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import dagger.multibindings.ClassKey
|
||||
import dagger.multibindings.IntoMap
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(ModelComponent::class)
|
||||
internal interface SwapNotificationsModule {
|
||||
|
||||
@Binds
|
||||
@IntoMap
|
||||
@ClassKey(SwapNotificationsModel::class)
|
||||
fun provideSwapNotificationsModel(impl: SwapNotificationsModel): Model
|
||||
}
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
internal interface SwapNotificationsSingletonModule {
|
||||
@Singleton
|
||||
@Binds
|
||||
fun bindsSwapNotificationsUpdateTrigger(impl: DefaultSwapNotificationsUpdateTrigger): SwapNotificationsUpdateTrigger
|
||||
|
||||
@Singleton
|
||||
@Binds
|
||||
fun bindsSwapNotificationsUpdateListener(
|
||||
impl: DefaultSwapNotificationsUpdateTrigger,
|
||||
): SwapNotificationsUpdateListener
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
package com.tangem.features.swap.v2.impl.notifications.entity
|
||||
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.ui.components.notifications.NotificationConfig
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.features.swap.v2.impl.common.SwapUtils.getExpressErrorMessage
|
||||
import com.tangem.features.swap.v2.impl.common.SwapUtils.getExpressErrorTitle
|
||||
|
||||
internal object SwapNotificationUM {
|
||||
|
||||
sealed class Error(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.ic_alert_24,
|
||||
buttonState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : NotificationUM.Error(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonState = buttonState,
|
||||
onCloseClick = onCloseClick,
|
||||
) {
|
||||
data class MinimalAmountError(
|
||||
val amount: String,
|
||||
) : Error(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_minimal_amount_title,
|
||||
formatArgs = wrappedList(amount),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
)
|
||||
|
||||
data class MaximumAmountError(
|
||||
val amount: String,
|
||||
) : Error(
|
||||
title = resourceReference(
|
||||
id = R.string.warning_express_too_maximum_amount_title,
|
||||
formatArgs = wrappedList(amount),
|
||||
),
|
||||
subtitle = resourceReference(R.string.warning_express_wrong_amount_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
)
|
||||
|
||||
data object FCAWarningList : Error(
|
||||
title = resourceReference(R.string.warning_express_providers_fca_warning_title),
|
||||
subtitle = resourceReference(R.string.warning_express_providers_fca_warning_description),
|
||||
iconResId = R.drawable.ic_alert_circle_24,
|
||||
)
|
||||
}
|
||||
|
||||
sealed class Warning(
|
||||
title: TextReference,
|
||||
subtitle: TextReference,
|
||||
iconResId: Int = R.drawable.img_attention_20,
|
||||
buttonsState: NotificationConfig.ButtonsState? = null,
|
||||
onCloseClick: (() -> Unit)? = null,
|
||||
) : NotificationUM.Warning(
|
||||
title = title,
|
||||
subtitle = subtitle,
|
||||
iconResId = iconResId,
|
||||
buttonsState = buttonsState,
|
||||
onCloseClick = onCloseClick,
|
||||
) {
|
||||
data class ExpressGeneralError(
|
||||
val expressError: ExpressError,
|
||||
val onConfirmClick: () -> Unit,
|
||||
) : Warning(
|
||||
title = getExpressErrorTitle(expressError),
|
||||
subtitle = getExpressErrorMessage(expressError),
|
||||
buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig(
|
||||
text = resourceReference(R.string.warning_button_refresh),
|
||||
onClick = onConfirmClick,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package com.tangem.features.swap.v2.impl.notifications.model
|
||||
|
||||
import com.tangem.common.ui.notifications.NotificationUM
|
||||
import com.tangem.core.decompose.di.ModelScoped
|
||||
import com.tangem.core.decompose.model.Model
|
||||
import com.tangem.core.decompose.model.ParamsContainer
|
||||
import com.tangem.core.ui.format.bigdecimal.crypto
|
||||
import com.tangem.core.ui.format.bigdecimal.format
|
||||
import com.tangem.domain.express.models.ExpressError
|
||||
import com.tangem.features.swap.v2.impl.notifications.DefaultSwapNotificationsUpdateTrigger
|
||||
import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent
|
||||
import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsComponent.Params.SwapNotificationData
|
||||
import com.tangem.features.swap.v2.impl.notifications.SwapNotificationsUpdateListener
|
||||
import com.tangem.features.swap.v2.impl.notifications.entity.SwapNotificationUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ModelScoped
|
||||
internal class SwapNotificationsModel @Inject constructor(
|
||||
override val dispatchers: CoroutineDispatcherProvider,
|
||||
private val swapNotificationsUpdateListener: SwapNotificationsUpdateListener,
|
||||
private val swapNotificationsUpdateTrigger: DefaultSwapNotificationsUpdateTrigger,
|
||||
paramsContainer: ParamsContainer,
|
||||
) : Model() {
|
||||
|
||||
private val params: SwapNotificationsComponent.Params = paramsContainer.require()
|
||||
|
||||
private var notificationData = params.swapNotificationData
|
||||
|
||||
val uiState: StateFlow<ImmutableList<NotificationUM>>
|
||||
field = MutableStateFlow<ImmutableList<NotificationUM>>(persistentListOf())
|
||||
|
||||
init {
|
||||
subscribeToNotificationUpdateTrigger()
|
||||
modelScope.launch {
|
||||
buildNotifications()
|
||||
}
|
||||
}
|
||||
|
||||
private fun subscribeToNotificationUpdateTrigger() {
|
||||
swapNotificationsUpdateListener.updateTriggerFlow
|
||||
.onEach { updateState(it) }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
||||
private suspend fun updateState(data: SwapNotificationData) {
|
||||
notificationData = data
|
||||
buildNotifications()
|
||||
}
|
||||
|
||||
private suspend fun buildNotifications() {
|
||||
val notifications = buildList {
|
||||
addExpressErrorNotification()
|
||||
}
|
||||
|
||||
swapNotificationsUpdateTrigger.callbackHasError(notifications.isNotEmpty())
|
||||
uiState.value = notifications.toImmutableList()
|
||||
}
|
||||
|
||||
fun MutableList<NotificationUM>.addExpressErrorNotification() {
|
||||
val expressError = notificationData.expressError ?: return
|
||||
val fromCryptoCurrency = notificationData.fromCryptoCurrency ?: return
|
||||
|
||||
val errorNotification = when (expressError) {
|
||||
is ExpressError.AmountError.TooSmallError -> SwapNotificationUM.Error.MinimalAmountError(
|
||||
expressError.amount.format {
|
||||
crypto(
|
||||
symbol = fromCryptoCurrency.symbol,
|
||||
decimals = fromCryptoCurrency.decimals,
|
||||
)
|
||||
},
|
||||
)
|
||||
is ExpressError.AmountError.TooBigError -> SwapNotificationUM.Error.MaximumAmountError(
|
||||
expressError.amount.format {
|
||||
crypto(
|
||||
symbol = fromCryptoCurrency.symbol,
|
||||
decimals = fromCryptoCurrency.decimals,
|
||||
)
|
||||
},
|
||||
)
|
||||
else -> SwapNotificationUM.Warning.ExpressGeneralError(
|
||||
expressError = expressError,
|
||||
onConfirmClick = {
|
||||
// todo reload quotes
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
add(errorNotification)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue