diff --git a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt index 81e25125ae..2c276a7117 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/OnrampDomainModule.kt @@ -1,7 +1,9 @@ package com.tangem.tap.di.domain import com.tangem.domain.onramp.* +import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampRepository +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn @@ -48,9 +50,50 @@ internal object OnrampDomainModule { return CheckOnrampAvailabilityUseCase(onrampRepository) } + @Provides + @Singleton + fun provideGetOnrampStatusUseCase( + onrampRepository: OnrampRepository, + onrampErrorResolver: OnrampErrorResolver, + ): GetOnrampStatusUseCase { + return GetOnrampStatusUseCase(onrampRepository, onrampErrorResolver) + } + @Provides @Singleton fun provideGetOnrampCurrencyUseCase(onrampRepository: OnrampRepository): GetOnrampCurrencyUseCase { return GetOnrampCurrencyUseCase(onrampRepository) } + + @Provides + @Singleton + fun provideGetOnrampTransactionsUseCase( + onrampTransactionRepository: OnrampTransactionRepository, + ): GetOnrampTransactionsUseCase { + return GetOnrampTransactionsUseCase(onrampTransactionRepository) + } + + @Provides + @Singleton + fun provideGetOnrampTransactionUseCase( + onrampTransactionRepository: OnrampTransactionRepository, + ): GetOnrampTransactionUseCase { + return GetOnrampTransactionUseCase(onrampTransactionRepository) + } + + @Provides + @Singleton + fun provideOnrampRemoveTransactionUseCase( + onrampTransactionRepository: OnrampTransactionRepository, + ): OnrampRemoveTransactionUseCase { + return OnrampRemoveTransactionUseCase(onrampTransactionRepository) + } + + @Provides + @Singleton + fun provideOnrampSaveTransactionUseCase( + onrampTransactionRepository: OnrampTransactionRepository, + ): OnrampSaveTransactionUseCase { + return OnrampSaveTransactionUseCase(onrampTransactionRepository) + } } \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt index 48042171a4..46a9c70161 100644 --- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt +++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt @@ -12,10 +12,7 @@ import com.tangem.features.managetokens.component.ManageTokensComponent import com.tangem.features.managetokens.component.ManageTokensSource import com.tangem.features.markets.details.MarketsTokenDetailsComponent import com.tangem.features.onboarding.v2.entry.OnboardingEntryComponent -import com.tangem.features.onramp.component.BuyCryptoComponent -import com.tangem.features.onramp.component.OnrampComponent -import com.tangem.features.onramp.component.SellCryptoComponent -import com.tangem.features.onramp.component.SwapSelectTokensComponent +import com.tangem.features.onramp.component.* import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter import com.tangem.features.send.api.navigation.SendRouter import com.tangem.features.staking.api.navigation.StakingRouter @@ -52,6 +49,7 @@ internal class ChildFactory @Inject constructor( private val manageTokensComponentFactory: ManageTokensComponent.Factory, private val marketsTokenDetailsComponentFactory: MarketsTokenDetailsComponent.Factory, private val onrampComponentFactory: OnrampComponent.Factory, + private val onrampSuccessComponentFactory: OnrampSuccessComponent.Factory, private val buyCryptoComponentFactory: BuyCryptoComponent.Factory, private val sellCryptoComponentFactory: SellCryptoComponent.Factory, private val swapSelectTokensComponentFactory: SwapSelectTokensComponent.Factory, @@ -203,6 +201,13 @@ internal class ChildFactory @Inject constructor( componentFactory = onrampComponentFactory, ) } + is AppRoute.OnrampSuccess -> { + route.asComponentChild( + contextProvider = contextProvider(route, contextFactory), + params = OnrampSuccessComponent.Params(route.txId), + componentFactory = onrampSuccessComponentFactory, + ) + } is AppRoute.BuyCrypto -> { route.asComponentChild( contextProvider = contextProvider(route, contextFactory), diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt index 66993b210a..da4f005d32 100644 --- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt +++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt @@ -299,6 +299,13 @@ sealed class AppRoute(val path: String) : Route { override fun getBundle(): Bundle = bundle(serializer()) } + @Serializable + data class OnrampSuccess( + val txId: String, + ) : AppRoute(path = "/onramp/success/$txId"), RouteBundleParams { + override fun getBundle(): Bundle = bundle(serializer()) + } + @Serializable data class BuyCrypto( val userWalletId: UserWalletId, diff --git a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt index 5d8b37fd46..4f4e74d559 100644 --- a/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/bottomsheet/permission/GiveTxPermisssionBottomSheet.kt @@ -31,10 +31,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.containers.FooterContainer import com.tangem.core.ui.components.inputrow.InputRowDefault -import com.tangem.core.ui.extensions.TextReference -import com.tangem.core.ui.extensions.resolveReference -import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import kotlinx.collections.immutable.ImmutableList @@ -118,7 +115,7 @@ private fun GiveTxPermissionBottomSheetContent(content: GiveTxPermissionBottomSh @Composable private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest) { FooterContainer( - footer = stringResource(id = R.string.give_permission_policy_type_footer), + footer = resourceReference(R.string.give_permission_policy_type_footer), modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), ) { AmountItem( @@ -130,7 +127,7 @@ private fun ApprovalBottomSheetInfo(data: GiveTxPermissionState.ReadyForRequest) } SpacerH16() FooterContainer( - footer = data.footerText.resolveReference(), + footer = data.footerText, modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16), ) { FeeItem(fee = data.fee) diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBlock.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBlock.kt new file mode 100644 index 0000000000..cdf3f1f8fb --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBlock.kt @@ -0,0 +1,228 @@ +package com.tangem.common.ui.expressStatus + +import androidx.annotation.DrawableRes +import androidx.compose.animation.* +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +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.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview +import com.tangem.common.ui.R +import com.tangem.common.ui.expressStatus.state.ExpressLinkUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusUM +import com.tangem.core.ui.components.SpacerWMax +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import kotlinx.collections.immutable.persistentListOf + +/** + * Block with express statuses + * + * @param state ui holder + * @param modifier modifier + * @see [Figma](https://www.figma.com/design/Vs6SkVsFnUPsSCNwlnVf5U/Android-%E2%80%93-UI?node-id=18459-26521&t=4jox7bfqUiXnm2h1-4) + */ +@Composable +fun ExpressStatusBlock(state: ExpressStatusUM, modifier: Modifier = Modifier) { + Column( + modifier = modifier + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.action) + .padding( + vertical = TangemTheme.dimens.spacing14, + horizontal = TangemTheme.dimens.spacing12, + ), + ) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding(bottom = TangemTheme.dimens.spacing16), + ) { + Text( + text = state.title.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = TangemTheme.colors.text.tertiary, + ) + SpacerWMax() + AnimatedVisibility(visible = state.link is ExpressLinkUM.Content) { + val link = remember(this) { state.link as ExpressLinkUM.Content } + Row( + modifier = Modifier.clickable { link.onClick() }, + verticalAlignment = Alignment.CenterVertically, + ) { + Icon( + painter = painterResource(id = link.icon), + contentDescription = null, + tint = TangemTheme.colors.icon.informative, + modifier = Modifier + .size(TangemTheme.dimens.spacing16) + .padding(end = TangemTheme.dimens.spacing2), + ) + Text( + text = link.text.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + ) + } + } + } + + Column { + state.statuses.forEachIndexed { index, item -> + ExpressStatusStep(item, index == state.statuses.lastIndex) + } + } + } +} + +@Composable +private fun ExpressStatusStep(status: ExpressStatusItemUM, isLast: Boolean) { + AnimatedContent( + targetState = status, + label = "Exchange Step Change Success", + transitionSpec = { + fadeIn(tween(durationMillis = 220)) togetherWith + fadeOut(tween(durationMillis = 220)) + }, + ) { content -> + Row { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + ) { + when (content.state) { + ExpressStatusItemState.Active -> StepInProgress() + ExpressStatusItemState.Default -> StepDefault() + ExpressStatusItemState.Done -> Step( + iconRes = R.drawable.ic_check_24, + iconColor = TangemTheme.colors.icon.primary1, + borderColor = TangemTheme.colors.field.focused, + ) + ExpressStatusItemState.Error -> Step( + iconRes = R.drawable.ic_close_24, + iconColor = TangemTheme.colors.icon.warning, + ) + ExpressStatusItemState.Warning -> Step( + iconRes = R.drawable.ic_close_24, + iconColor = TangemTheme.colors.icon.attention, + ) + } + if (!isLast) { + StepSeparator() + } + } + val textColor = when (status.state) { + ExpressStatusItemState.Active -> TangemTheme.colors.text.primary1 + ExpressStatusItemState.Default -> TangemTheme.colors.text.disabled + ExpressStatusItemState.Done -> TangemTheme.colors.text.primary1 + ExpressStatusItemState.Error -> TangemTheme.colors.text.warning + ExpressStatusItemState.Warning -> TangemTheme.colors.text.attention + } + Text( + text = content.text.resolveReference(), + style = TangemTheme.typography.body2, + color = textColor, + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing12), + ) + } + } +} + +@Composable +private fun StepDefault() { + Box( + modifier = Modifier + .size(TangemTheme.dimens.size20) + .border( + width = TangemTheme.dimens.size1_5, + color = TangemTheme.colors.field.focused, + shape = CircleShape, + ) + .padding(TangemTheme.dimens.spacing2), + ) +} + +@Composable +private fun Step(iconColor: Color, @DrawableRes iconRes: Int, borderColor: Color = iconColor) { + Icon( + painter = painterResource(id = iconRes), + contentDescription = null, + tint = iconColor, + modifier = Modifier + .size(TangemTheme.dimens.size20) + .border( + width = TangemTheme.dimens.size1_5, + color = borderColor, + shape = CircleShape, + ) + .padding(TangemTheme.dimens.spacing2), + ) +} + +@Composable +private fun StepInProgress() { + CircularProgressIndicator( + color = TangemTheme.colors.icon.primary1, + strokeWidth = TangemTheme.dimens.size2, + modifier = Modifier + .padding(TangemTheme.dimens.spacing2) + .size(TangemTheme.dimens.size14), + ) +} + +@Composable +private fun StepSeparator() { + Box( + modifier = Modifier + .padding(vertical = TangemTheme.dimens.spacing2) + .size( + width = TangemTheme.dimens.size1_5, + height = TangemTheme.dimens.size10, + ) + .background( + color = TangemTheme.colors.field.focused, + shape = CircleShape, + ), + ) +} + +@Preview +@Composable +private fun Preview_ExchangeStatusBlock() { + val state = ExpressStatusUM( + title = resourceReference(R.string.express_exchange_status_title), + link = ExpressLinkUM.Content( + icon = R.drawable.ic_alert_24, + text = resourceReference(R.string.common_go_to_provider), + onClick = {}, + ), + statuses = persistentListOf( + ExpressStatusItemUM(text = stringReference("Done"), state = ExpressStatusItemState.Done), + ExpressStatusItemUM(text = stringReference("Active"), state = ExpressStatusItemState.Active), + ExpressStatusItemUM(text = stringReference("Warning"), state = ExpressStatusItemState.Warning), + ExpressStatusItemUM(text = stringReference("Error"), state = ExpressStatusItemState.Error), + ExpressStatusItemUM(text = stringReference("Default"), state = ExpressStatusItemState.Default), + ), + ) + + TangemThemePreview { + ExpressStatusBlock(state = state) + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusNotificationBlock.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusNotificationBlock.kt new file mode 100644 index 0000000000..b93ad58334 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusNotificationBlock.kt @@ -0,0 +1,32 @@ +package com.tangem.common.ui.expressStatus + +import androidx.compose.animation.AnimatedContent +import androidx.compose.foundation.layout.padding +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import com.tangem.common.ui.notifications.ExpressNotificationsUM +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.components.notifications.Notification +import com.tangem.core.ui.res.TangemTheme + +@Composable +fun ExpressStatusNotificationBlock(state: NotificationUM?) { + AnimatedContent( + targetState = state, + modifier = Modifier.padding(top = 12.dp), + label = "Express Status Notification Change", + ) { notification -> + if (notification?.config != null) { + Notification( + config = notification.config, + iconTint = when (state) { + is ExpressNotificationsUM.NeedVerification -> TangemTheme.colors.icon.attention + is ExpressNotificationsUM.FailedByProvider -> TangemTheme.colors.icon.warning + else -> null + }, + containerColor = TangemTheme.colors.background.action, + ) + } + } +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusUM.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusUM.kt new file mode 100644 index 0000000000..6fcb02cc30 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressStatusUM.kt @@ -0,0 +1,54 @@ +package com.tangem.common.ui.expressStatus.state + +import androidx.annotation.DrawableRes +import androidx.compose.runtime.Stable +import com.tangem.core.ui.extensions.TextReference +import kotlinx.collections.immutable.ImmutableList + +/** + * UI data holder for express status block + * + * @property title block title + * @property link provider web link + * @property statuses list of possible and active statuses + */ +data class ExpressStatusUM( + val title: TextReference, + val link: ExpressLinkUM, + val statuses: ImmutableList, +) + +/** + * Provider web link for express status block. + * [Empty] if no link needed + * [Content] if link is provided and displayed + */ +@Stable +sealed class ExpressLinkUM { + data object Empty : ExpressLinkUM() + data class Content( + @DrawableRes val icon: Int, + val text: TextReference, + val onClick: () -> Unit, + ) : ExpressLinkUM() +} + +/** + * Single status item in express status block + */ +data class ExpressStatusItemUM( + val text: TextReference, + val state: ExpressStatusItemState, +) + +/** + * Available status states for express status block + */ +enum class ExpressStatusItemState { + Active, + Default, + Done, + Warning, + Error, + ; +} \ No newline at end of file diff --git a/common/ui/src/main/java/com/tangem/common/ui/notifications/ExpressNotificationsUM.kt b/common/ui/src/main/java/com/tangem/common/ui/notifications/ExpressNotificationsUM.kt new file mode 100644 index 0000000000..09c66ed8c8 --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/notifications/ExpressNotificationsUM.kt @@ -0,0 +1,28 @@ +package com.tangem.common.ui.notifications + +import com.tangem.common.ui.R +import com.tangem.core.ui.components.notifications.NotificationConfig +import com.tangem.core.ui.extensions.resourceReference + +object ExpressNotificationsUM { + + data class NeedVerification(val onGoToProviderClick: () -> Unit) : NotificationUM.Warning( + title = resourceReference(R.string.express_exchange_notification_verification_title), + subtitle = resourceReference(R.string.express_exchange_notification_verification_text), + iconResId = R.drawable.ic_alert_triangle_20, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.common_go_to_provider), + onClick = onGoToProviderClick, + ), + ) + + data class FailedByProvider(val onGoToProviderClick: () -> Unit) : NotificationUM.Error( + title = resourceReference(R.string.express_exchange_notification_failed_title), + subtitle = resourceReference(R.string.express_exchange_notification_failed_text), + iconResId = R.drawable.ic_alert_circle_24, + buttonState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.common_go_to_provider), + onClick = onGoToProviderClick, + ), + ) +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt index 6ef0416464..301fd2421f 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/onramp/models/response/OnrampStatusResponse.kt @@ -14,8 +14,8 @@ data class OnrampStatusResponse( @Json(name = "payoutAddress") val payoutAddress: String, - // @Json(name = "status") - // val status: ??? + @Json(name = "status") + val status: Status, @Json(name = "failReason") val failReason: String?, @@ -48,14 +48,46 @@ data class OnrampStatusResponse( val toDecimals: String, @Json(name = "toAmount") - val toAmount: String, + val toAmount: String?, @Json(name = "toActualAmount") - val toActualAmount: String, + val toActualAmount: String?, @Json(name = "paymentMethod") val paymentMethod: String, @Json(name = "countryCode") val countryCode: String, -) \ No newline at end of file +) + +enum class Status { + @Json(name = "created") + Created, + + @Json(name = "expired") + Expired, + + @Json(name = "waiting-for-payment") + WaitingForPayment, + + @Json(name = "payment-processing") + PaymentProcessing, + + @Json(name = "verifying") + Verifying, + + @Json(name = "failed") + Failed, + + @Json(name = "paid") + Paid, + + @Json(name = "sending") + Sending, + + @Json(name = "finished") + Finished, + + @Json(name = "paused") + Paused, +} \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index a049a7db22..9f4cf80fea 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -107,6 +107,8 @@ object PreferencesKeys { val ONRAMP_DEFAULT_COUNTRY by lazy { stringPreferencesKey(name = "onrampDefaultCountry") } + val ONRAMP_TRANSACTIONS_STATUSES_KEY by lazy { stringPreferencesKey(name = "onrampTransactionsStatuses") } + // region Permission fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission") diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt index a7fd3cafef..0369cf22cd 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/utils/AppPreferencesStoreExt.kt @@ -150,4 +150,12 @@ suspend inline fun AppPreferencesStore.getObjectSetSync(key: Prefere ?.get(key) ?.let(adapter::fromJson) .orEmpty() +} + +/** Get flow of set of [T] by string [key], or empty if data is not found */ +inline fun AppPreferencesStore.getObjectSet(key: Preferences.Key): Flow> { + val adapter = moshi.adapter>(Types.newParameterizedType(Set::class.java, T::class.java)) + return data.map { + it[key]?.let(adapter::fromJson) ?: emptySet() + } } \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 65a4990969..8fe310b7d9 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -568,7 +568,7 @@ Keine verfügbaren Anbieter für diese Währung Bezahlen mit Du kannst Deine Transaktion beim Drittanbieter %s abschließen. - Umleitung auf %s … + Umleitung auf %s… Unsere Dienstleistungen sind in diesem Land nicht verfügbar Änder oder bestätige bitte Dein Wohnsitz wurde identifiziert als diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index ee04d97a2f..b77f4a6456 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -858,14 +858,14 @@ Saldos mostrados Cancelar Esta operación no está disponible actualmente. Por favor, inténtalo de nuevo más tarde. - Comprar %s no está disponible en este momento. Por favor revise sus actualizaciones. + Comprar %s no está disponible con los proveedores actuales pero estamos trabajando para agregar más opciones. No tiene fondos para vender. Recargue su cuenta para poder vender fondos desde ella. No tienes fondos para enviar. Recarga tu cuenta para poder enviar fondos desde ella. - El servicio de intercambio %s no está disponible en este momento. Por favor consulte nuestras actualizaciones. + El servicio de intercambio %s no está compatible con los proveedores actuales pero estamos trabajando para agregar más opciones. La venta de fondos estará disponible una vez que la(s) transacción(es) pendiente(s) en la red %s se complete El envío de fondos estará disponible una vez que se completen las transacciones pendientes en la red %s. - La venta de %s no está disponible en este momento. Por favor consulte nuestras actualizaciones. - El staking %s no está disponible en este momento. Por favor consulte nuestras actualizaciones. + Vender %s no es compatible con los proveedores actuales, pero estamos trabajando para agregar más opciones. + El staking de %s no es compatible con los proveedores actuales, pero estamos trabajando para agregar más opciones. Generar XPUB Ocultar Está a punto de ocultar este token de la pantalla principal. Puede volver a agregarlo en cualquier momento a través de la página de gestión de tokens. diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index c9983efdb7..346d3081e5 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -850,14 +850,14 @@ Soldes affichés Annuler Cette opération est actuellement indisponible. Veuillez réessayer plus tard. - L\'achat de %s n\'est pas disponible pour le moment. Veuillez vérifier vos mises à jour. + L\'achat de %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter davantage d\'options. Vous n\'avez pas de fonds à vendre. Renflouez votre compte pour pouvoir vendre des fonds à partir de celui-ci. Vous n\'avez pas de fonds à envoyer. Renflouez votre compte pour pouvoir envoyer des fonds à partir de celui-ci. - Le service d\'échange %s n\'est pas disponible pour le moment. Veuillez consulter nos mises à jour. + Le service d\'échange %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter plus d\'options. La vente de fonds sera disponible une fois que la ou les transactions en attente dans le réseau %s seront terminées L\'envoi de fonds sera disponible une fois la ou les transactions en attente dans le réseau %s terminées. - La vente de %s n\'est pas disponible pour le moment. Veuillez consulter nos mises à jour. - Le staking %s n’est pas disponible pour le moment. Veuillez consulter nos mises à jour. + L\'achat de %s n\'est pas pris en charge par les fournisseurs actuels mais nous travaillons à ajouter plus d\'options. + Le staking de %s n\'est pas pris en charge par les fournisseurs actuels, mais nous travaillons à ajouter plus d\'options. Générer XPUB Masquer Vous êtes sur le point de masquer ce jeton de l\'écran principal. Vous pouvez le rajouter à tout moment via la page de gestion des jetons. diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 3d8d8d15d5..f4e7cfccfe 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -830,7 +830,7 @@ 金額には以下が含まれます: \n • サービス プロバイダーの手数料\n • 取引所からユーザーのアドレスに%1$sを送金するためのネットワーク手数料。 \n\nプロバイダーのスリッページは最大%2$sです この金額には、サービスプロバイダーの手数料が含まれています。 金額にはサービスプロバイダーの手数料が含まれます。 \n\nプロバイダーのスリッページは最大%s です - 手数料 + 情報 すべての分散型取引所は、スマートコントラクトがあなたの許可なくウォレットにアクセスするのを防ぐために承認を必要とします。設計上、スマートコントラクトは承認なしでトークンにアクセスできません。トークンを「ロック解除」することで、あなたは1-inchのスマートコントラクトがトークンを使うことを承認します。ネットワークのマイナーは、このアクションをブロックチェーンに記録するためのガス料金(あなたが支払う)を受け取ります。承認後、トークンを交換することができます。 承認 手数料見積りエラーです。サポートにフィードバックをお送りください。 @@ -846,14 +846,14 @@ 残高表示 元に戻す この操作は現在利用できません。しばらくしてからもう一度お試しください。 - 現在、 %sの買付はご利用いただけません。アップデート情報をご確認ください。 + %sの買付は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 売却できる資金がありません。アカウントに入金して、売却できるようにしてください。 送金する資金がありません。アカウントに入金して、送金できるようにしてください。 - 現在、 %sの交換はご利用いただけません。アップデート情報を確認してください。 + %sのスワップは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 ネットワーク%s内の保留中の取引が完了すると、資金の売却が可能になります。 ネットワーク%s内の保留中の取引が完了すると、送金が可能になります。 - 現在、 %sの売却はご利用いただけません。アップデート情報をご確認ください。 - %s のステーキングは現在ご利用いただけません。最新情報をご確認ください。 + %sの売却は、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 + %sのステーキングは、現在のプロバイダーではサポートされていませんが、より多くのオプションを追加できるよう取り組んでいます。 XPUBを生成する 非表示 このトークンをメイン画面から非表示にします。トークンの管理ページからいつでも再度追加できます。 @@ -946,7 +946,7 @@ %sネットワーク アドレスがクリップボードにコピーされました インターネット接続がありません - 今すぐ10 %オフで購入 + 今すぐ10 %%オフで購入 1.3万種類以上の暗号資産にアクセス。ワンタップで買付、売却、スワップ、ステーキングが可能です。\nバックアップ用に最大3枚のカードを連携できます。 Tangemウォレットを見る ウォレット設定 @@ -1019,7 +1019,7 @@ Solanaネットワークが混雑しています。2分以内に取引が完了しない場合は、再度取引を繰り返してください。 Solanaネットワークアラート Solana ネットワークは 2 日ごとに%1$sのレンタル料を請求します。レンタル料を支払えないアカウントはネットワークから削除されます。アカウントに%2$s以上入金すると、無料で使用できます。 - 現在、一部のネットワークにアクセスできません。しばらくしてからもう一度お試しください。 + 下にスワイプして更新するか、後でもう一度お試しください。 一部のネットワークにアクセスできません これはテストネットカードです。取引処理はできませんので、テストおよび開発目的でのみご利用ください。 テスト目的のみ diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 9a43af9607..76ba8cfddf 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -581,7 +581,7 @@ Для данной валюты нет доступных провайдеров Оплата с Вы сможете завершить транзакцию через сервис стороннего провайдера, %s - Переход к %s ... + Переход к %s... Наши сервисы недоступны в данной стране Настройки Через @@ -966,7 +966,7 @@ Нет соединения с интернетом Получить с 10%% скидкой Получите доступ к более чем 13 000 криптовалют. Покупайте, продавайте, обменяйте и стейкайте в один клик. Свяжите до трех карт для резервного копирования. - Откройте Tangem Wallet + Откройте Tangem Wallet Настройки кошелька Tangem Используйте %s или отсканируйте карту/кольцо, чтобы разблокировать доступ к вашему кошельку diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 6978552ae0..6241677828 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -294,6 +294,9 @@ Unavailable for this pair Permission Required Recommended + Bought %s + Buying %s + Buying %s... No tokens found. Please try another request ID: %s Transaction ID copied diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt b/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt index 5296313d28..bebd090e66 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/containers/FooterContainer.kt @@ -5,8 +5,11 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.unit.Dp +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme /** @@ -20,15 +23,16 @@ import com.tangem.core.ui.res.TangemTheme @Composable fun FooterContainer( modifier: Modifier = Modifier, - footer: String? = null, + footer: TextReference? = null, footerTopPadding: Dp = TangemTheme.dimens.spacing8, content: @Composable () -> Unit, ) { Column(modifier = modifier) { content() AnimatedVisibility(visible = footer != null) { + val footerWrapped = remember(this) { requireNotNull(footer) } Text( - text = footer.orEmpty(), + text = footerWrapped.resolveReference(), style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.tertiary, modifier = Modifier diff --git a/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt b/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt index fb9ff22adb..047150c42e 100644 --- a/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt +++ b/data/common/src/main/kotlin/com/tangem/data/common/currency/ResponseCryptoCurrenciesFactory.kt @@ -14,15 +14,11 @@ import com.tangem.blockchain.common.Token as SdkToken class ResponseCryptoCurrenciesFactory { - fun createCurrency( - currencyId: CryptoCurrency.ID, - response: UserTokensResponse, - scanResponse: ScanResponse, - ): CryptoCurrency { + fun createCurrency(currencyId: String, response: UserTokensResponse, scanResponse: ScanResponse): CryptoCurrency { return response.tokens .asSequence() .mapNotNull { createCurrency(it, scanResponse) } - .first { it.id == currencyId } + .first { it.id.value == currencyId } } fun createCurrencies(response: UserTokensResponse, scanResponse: ScanResponse): List { diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt index 0ec508bd89..b03b5c6c69 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampErrorResolver.kt @@ -5,9 +5,9 @@ import com.tangem.datasource.api.common.response.ApiResponseError import com.tangem.domain.onramp.model.OnrampError import com.tangem.domain.onramp.repositories.OnrampErrorResolver -internal class DefaultOnrampErrorResolver( - private val onrampErrorConverter: OnrampErrorConverter, -) : OnrampErrorResolver { +internal class DefaultOnrampErrorResolver : OnrampErrorResolver { + + private val onrampErrorConverter = OnrampErrorConverter() override fun resolve(throwable: Throwable): OnrampError { return if (throwable is ApiResponseError.HttpException) { diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt index 31afbf4c67..ef6cd4523e 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampRepository.kt @@ -2,6 +2,7 @@ package com.tangem.data.onramp import com.tangem.data.onramp.converters.CountryConverter import com.tangem.data.onramp.converters.CurrencyConverter +import com.tangem.data.onramp.converters.StatusConverter import com.tangem.datasource.api.common.response.getOrThrow import com.tangem.datasource.api.onramp.OnrampApi import com.tangem.datasource.api.onramp.models.response.model.OnrampCountryDTO @@ -13,6 +14,7 @@ import com.tangem.datasource.local.preferences.utils.getObjectSyncOrNull import com.tangem.datasource.local.preferences.utils.storeObject import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.domain.onramp.model.OnrampStatus import com.tangem.domain.onramp.repositories.OnrampRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import kotlinx.coroutines.flow.Flow @@ -27,6 +29,7 @@ internal class DefaultOnrampRepository( private val currencyConverter = CurrencyConverter() private val countryConverter = CountryConverter(currencyConverter) + private val statusConverter = StatusConverter() override suspend fun getCurrencies(): List = withContext(dispatchers.io) { onrampApi.getCurrencies() @@ -46,6 +49,12 @@ internal class DefaultOnrampRepository( .let(countryConverter::convert) } + override suspend fun getStatus(txId: String): OnrampStatus = withContext(dispatchers.io) { + onrampApi.getStatus(txId) + .getOrThrow() + .let(statusConverter::convert) + } + override suspend fun saveDefaultCurrency(currency: OnrampCurrency) = withContext(dispatchers.io) { appPreferencesStore.storeObject( key = PreferencesKeys.ONRAMP_DEFAULT_CURRENCY, diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt new file mode 100644 index 0000000000..eaaad04a78 --- /dev/null +++ b/data/onramp/src/main/java/com/tangem/data/onramp/DefaultOnrampTransactionRepository.kt @@ -0,0 +1,77 @@ +package com.tangem.data.onramp + +import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.datasource.local.preferences.PreferencesKeys +import com.tangem.datasource.local.preferences.utils.getObjectSet +import com.tangem.datasource.local.preferences.utils.getObjectSetSync +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import com.tangem.utils.extensions.addOrReplace +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.withContext + +internal class DefaultOnrampTransactionRepository( + private val appPreferencesStore: AppPreferencesStore, + private val dispatchers: CoroutineDispatcherProvider, +) : OnrampTransactionRepository { + + override suspend fun storeTransaction(transaction: OnrampTransaction) { + withContext(dispatchers.io) { + appPreferencesStore.editData { mutablePreferences -> + val stored = mutablePreferences.getObjectSet( + PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + ) + val updated = stored?.toMutableSet() + ?.addOrReplace(transaction) { it.txId == transaction.txId } + ?: mutableSetOf(transaction) + + mutablePreferences.setObjectSet( + key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + value = updated, + ) + } + } + } + + override suspend fun getTransactionById(txId: String): OnrampTransaction? = withContext(dispatchers.io) { + val stored = appPreferencesStore.getObjectSetSync( + PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + ) + + stored.firstOrNull { it.txId == txId } + } + + override fun getTransactions( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + ): Flow> = appPreferencesStore + .getObjectSet(PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY) + .map { transactions -> + transactions.filter { + it.userWalletId == userWalletId && it.toCurrencyId == cryptoCurrencyId.value + } + } + + override suspend fun removeTransaction(txId: String) { + withContext(dispatchers.io) { + appPreferencesStore.editData { mutablePreferences -> + runCatching { + val stored = mutablePreferences.getObjectSet( + PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + )?.toMutableSet() + + stored?.removeIf { it.txId == txId } + + mutablePreferences.setObjectSet( + key = PreferencesKeys.ONRAMP_TRANSACTIONS_STATUSES_KEY, + value = stored ?: emptySet(), + ) + } + } + } + } +} \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/converters/StatusConverter.kt b/data/onramp/src/main/java/com/tangem/data/onramp/converters/StatusConverter.kt new file mode 100644 index 0000000000..70805a77fe --- /dev/null +++ b/data/onramp/src/main/java/com/tangem/data/onramp/converters/StatusConverter.kt @@ -0,0 +1,30 @@ +package com.tangem.data.onramp.converters + +import com.tangem.datasource.api.onramp.models.response.OnrampStatusResponse +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.utils.converter.Converter + +internal class StatusConverter : Converter { + override fun convert(value: OnrampStatusResponse): OnrampStatus { + return OnrampStatus( + txId = value.txId, + providerId = value.providerId, + payoutAddress = value.payoutAddress, + status = OnrampStatus.Status.valueOf(value.status.name), + failReason = value.failReason, + externalTxId = value.externalTxId, + externalTxUrl = value.externalTxUrl, + payoutHash = value.payoutHash, + createdAt = value.createdAt, + fromCurrencyCode = value.fromCurrencyCode, + fromAmount = value.fromAmount, + toContractAddress = value.toContractAddress, + toNetwork = value.toNetwork, + toDecimals = value.toDecimals, + toAmount = value.toAmount, + toActualAmount = value.toActualAmount, + paymentMethod = value.paymentMethod, + countryCode = value.countryCode, + ) + } +} \ No newline at end of file diff --git a/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt b/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt index d47543851a..fed11e54d0 100644 --- a/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt +++ b/data/onramp/src/main/java/com/tangem/data/onramp/di/OnrampDataModule.kt @@ -1,9 +1,13 @@ package com.tangem.data.onramp.di +import com.tangem.data.onramp.DefaultOnrampErrorResolver import com.tangem.data.onramp.DefaultOnrampRepository +import com.tangem.data.onramp.DefaultOnrampTransactionRepository import com.tangem.datasource.api.onramp.OnrampApi import com.tangem.datasource.local.preferences.AppPreferencesStore +import com.tangem.domain.onramp.repositories.OnrampErrorResolver import com.tangem.domain.onramp.repositories.OnrampRepository +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.Module import dagger.Provides @@ -28,4 +32,22 @@ internal object OnrampDataModule { appPreferencesStore = appPreferencesStore, ) } + + @Provides + @Singleton + fun provideOnrampTransactionRepository( + appPreferencesStore: AppPreferencesStore, + dispatchers: CoroutineDispatcherProvider, + ): OnrampTransactionRepository { + return DefaultOnrampTransactionRepository( + appPreferencesStore = appPreferencesStore, + dispatchers = dispatchers, + ) + } + + @Provides + @Singleton + fun provideOnrampErrorResolver(): OnrampErrorResolver { + return DefaultOnrampErrorResolver() + } } \ No newline at end of file diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 31282f6f34..1d25e1ea81 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -293,23 +293,28 @@ internal class DefaultCurrenciesRepository( userWalletId: UserWalletId, id: CryptoCurrency.ID, ): CryptoCurrency = withContext(dispatchers.io) { - val userWallet = getUserWallet(userWalletId) - ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) - - val response = requireNotNull( - value = getSavedUserTokensResponseSync(key = userWalletId), - lazyMessage = { - "Unable to find tokens response for user wallet with provided ID: $userWalletId" - }, - ) - - responseCurrenciesFactory.createCurrency( - currencyId = id, - response = response, - scanResponse = userWallet.scanResponse, - ) + getMultiCurrencyWalletCurrency(userWalletId, id.value) } + override suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: String): CryptoCurrency = + withContext(dispatchers.io) { + val userWallet = getUserWallet(userWalletId) + ensureIsCorrectUserWallet(userWallet, isMultiCurrencyWalletExpected = true) + + val response = requireNotNull( + value = getSavedUserTokensResponseSync(key = userWalletId), + lazyMessage = { + "Unable to find tokens response for user wallet with provided ID: $userWalletId" + }, + ) + + responseCurrenciesFactory.createCurrency( + currencyId = id, + response = response, + scanResponse = userWallet.scanResponse, + ) + } + override suspend fun getNetworkCoin( userWalletId: UserWalletId, networkId: Network.ID, diff --git a/domain/onramp/build.gradle.kts b/domain/onramp/build.gradle.kts index 43956b9497..b676e92120 100644 --- a/domain/onramp/build.gradle.kts +++ b/domain/onramp/build.gradle.kts @@ -6,6 +6,8 @@ plugins { dependencies { api(projects.domain.onramp.models) + api(projects.domain.tokens.models) + api(projects.domain.wallets.models) api(projects.domain.core) implementation(deps.kotlin.serialization) diff --git a/domain/onramp/models/build.gradle.kts b/domain/onramp/models/build.gradle.kts index 7558c5ca61..dc13a99957 100644 --- a/domain/onramp/models/build.gradle.kts +++ b/domain/onramp/models/build.gradle.kts @@ -6,6 +6,8 @@ plugins { dependencies { implementation(projects.domain.core) + implementation(projects.domain.tokens.models) + implementation(projects.domain.wallets.models) implementation(deps.kotlin.serialization) implementation(deps.jodatime) diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt new file mode 100644 index 0000000000..69ecefb695 --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampStatus.kt @@ -0,0 +1,54 @@ +package com.tangem.domain.onramp.model + +import kotlinx.serialization.Serializable + +@Serializable +data class OnrampStatus( + val txId: String, + val providerId: String, + val payoutAddress: String, + val status: Status, + val failReason: String?, + val externalTxId: String, + val externalTxUrl: String?, + val payoutHash: String?, + val createdAt: String, + val fromCurrencyCode: String, + val fromAmount: String, + val toContractAddress: String, + val toNetwork: String, + val toDecimals: String, + val toAmount: String?, + val toActualAmount: String?, + val paymentMethod: String, + val countryCode: String, +) { + enum class Status(val order: Int) { + Created(order = 1), + Expired(order = 2), + Paused(order = 3), + WaitingForPayment(order = 4), + PaymentProcessing(order = 5), + Verifying(order = 6), + Failed(order = 7), + Paid(order = 8), + Sending(order = 9), + Finished(order = 10), + ; + + fun isTerminal(): Boolean = when (this) { + Expired, + Failed, + Paused, + Finished, + -> true + Created, + WaitingForPayment, + PaymentProcessing, + Verifying, + Paid, + Sending, + -> false + } + } +} \ No newline at end of file diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt new file mode 100644 index 0000000000..96f069b02f --- /dev/null +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/cache/OnrampTransaction.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.onramp.model.cache + +import com.tangem.domain.core.serialization.SerializedBigDecimal +import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.serialization.Serializable + +@Serializable +data class OnrampTransaction( + val txId: String, + val userWalletId: UserWalletId, + val fromAmount: SerializedBigDecimal, + val fromCurrency: OnrampCurrency, + val toAmount: SerializedBigDecimal, + val toCurrencyId: String, + val providerName: String, + val providerImageUrl: String, +) \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt new file mode 100644 index 0000000000..4692c6d6b7 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampError +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.onramp.repositories.OnrampErrorResolver +import com.tangem.domain.onramp.repositories.OnrampRepository + +class GetOnrampStatusUseCase( + private val onrampRepository: OnrampRepository, + private val errorResolver: OnrampErrorResolver, +) { + + suspend operator fun invoke(txId: String): Either { + return Either.catch { + onrampRepository.getStatus(txId) + }.mapLeft { + errorResolver.resolve(it) + } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionUseCase.kt new file mode 100644 index 0000000000..0e33b2e91a --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionUseCase.kt @@ -0,0 +1,21 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampError +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository + +class GetOnrampTransactionUseCase( + private val onrampTransactionRepository: OnrampTransactionRepository, +) { + + suspend operator fun invoke(txId: String): Either { + return Either.catch { + requireNotNull( + onrampTransactionRepository.getTransactionById(txId), + ) + }.mapLeft { + OnrampError.UnknownError + } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionsUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionsUseCase.kt new file mode 100644 index 0000000000..af51320013 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampTransactionsUseCase.kt @@ -0,0 +1,28 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampError +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +class GetOnrampTransactionsUseCase( + private val onrampTransactionRepository: OnrampTransactionRepository, +) { + + operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrencyId: CryptoCurrency.ID, + ): Either>> { + return Either.catch { + onrampTransactionRepository.getTransactions( + userWalletId = userWalletId, + cryptoCurrencyId = cryptoCurrencyId, + ) + }.mapLeft { + OnrampError.UnknownError + } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampRemoveTransactionUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampRemoveTransactionUseCase.kt new file mode 100644 index 0000000000..4a6062ece8 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampRemoveTransactionUseCase.kt @@ -0,0 +1,18 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampError +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository + +class OnrampRemoveTransactionUseCase( + private val onrampTransactionRepository: OnrampTransactionRepository, +) { + + suspend operator fun invoke(txId: String): Either { + return Either.catch { + onrampTransactionRepository.removeTransaction(txId) + }.mapLeft { + OnrampError.UnknownError + } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveTransactionUseCase.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveTransactionUseCase.kt new file mode 100644 index 0000000000..d939933d9c --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/OnrampSaveTransactionUseCase.kt @@ -0,0 +1,16 @@ +package com.tangem.domain.onramp + +import arrow.core.Either +import com.tangem.domain.onramp.model.OnrampError +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.onramp.repositories.OnrampTransactionRepository + +class OnrampSaveTransactionUseCase( + private val onrampTransactionRepository: OnrampTransactionRepository, +) { + + suspend operator fun invoke(transaction: OnrampTransaction): Either { + return Either.catch { onrampTransactionRepository.storeTransaction(transaction) } + .mapLeft { OnrampError.UnknownError } + } +} \ No newline at end of file diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt index 85b4e27f69..7e6dc7df12 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampRepository.kt @@ -2,12 +2,17 @@ package com.tangem.domain.onramp.repositories import com.tangem.domain.onramp.model.OnrampCountry import com.tangem.domain.onramp.model.OnrampCurrency +import com.tangem.domain.onramp.model.OnrampStatus import kotlinx.coroutines.flow.Flow interface OnrampRepository { + // api suspend fun getCurrencies(): List suspend fun getCountries(): List suspend fun getCountryByIp(): OnrampCountry + suspend fun getStatus(txId: String): OnrampStatus + + // cache suspend fun saveDefaultCurrency(currency: OnrampCurrency) suspend fun getDefaultCurrencySync(): OnrampCurrency? fun getDefaultCurrency(): Flow diff --git a/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt new file mode 100644 index 0000000000..24105314b1 --- /dev/null +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/repositories/OnrampTransactionRepository.kt @@ -0,0 +1,17 @@ +package com.tangem.domain.onramp.repositories + +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.wallets.models.UserWalletId +import kotlinx.coroutines.flow.Flow + +interface OnrampTransactionRepository { + + suspend fun storeTransaction(transaction: OnrampTransaction) + + suspend fun getTransactionById(txId: String): OnrampTransaction? + + fun getTransactions(userWalletId: UserWalletId, cryptoCurrencyId: CryptoCurrency.ID): Flow> + + suspend fun removeTransaction(txId: String) +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyUseCase.kt index d0e47c2b62..b24eb525ae 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCryptoCurrencyUseCase.kt @@ -27,6 +27,22 @@ class GetCryptoCurrencyUseCase( return either { getCurrency(userWalletId, id) } } + /** + * Returns specific cryptocurrency for a given user wallet. + * + * !!! Important Use only [CryptoCurrency.ID.value] as cryptoCurrencyId + * + * @param userWalletId The ID of the user's wallet. + * @param cryptoCurrencyId String representation of the [CryptoCurrency.ID.value] of the cryptocurrency. + * @return An [Either] representing success (Right) or an error (Left) in fetching the status. + */ + suspend operator fun invoke( + userWalletId: UserWalletId, + cryptoCurrencyId: String, + ): Either { + return either { getCurrency(userWalletId, cryptoCurrencyId) } + } + /** * Returns the primary cryptocurrency for a given user wallet. * @@ -49,6 +65,18 @@ class GetCryptoCurrencyUseCase( ) } + private suspend fun Raise.getCurrency( + userWalletId: UserWalletId, + id: String, + ): CryptoCurrency { + return catch( + block = { + currenciesRepository.getMultiCurrencyWalletCurrency(userWalletId, id) + }, + catch = { raise(CurrencyStatusError.DataError(it)) }, + ) + } + private suspend fun Raise.getPrimaryCurrency(userWalletId: UserWalletId): CryptoCurrency { return catch( block = { currenciesRepository.getSingleCurrencyWalletPrimaryCurrency(userWalletId) }, diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt index bda0e8fdbd..27691a8495 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/CurrenciesRepository.kt @@ -167,6 +167,17 @@ interface CurrenciesRepository { */ suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: CryptoCurrency.ID): CryptoCurrency + /** + * Retrieves the cryptocurrency for a specific multi-currency user wallet. + * + * @param userWalletId The unique identifier of the user wallet. + * @param id The unique identifier of the cryptocurrency to be retrieved. + * @return The cryptocurrency associated with the user wallet and ID. + * @throws DataError.UserWalletError.WrongUserWallet If single-currency user wallet + * ID provided. + */ + suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: String): CryptoCurrency + /** * Get the coin for a specific network. * diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt index f68979c6f4..b23e4a8726 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/repository/MockCurrenciesRepository.kt @@ -100,6 +100,14 @@ internal class MockCurrenciesRepository( return token } + override suspend fun getMultiCurrencyWalletCurrency(userWalletId: UserWalletId, id: String): CryptoCurrency { + val token = token.getOrElse { e -> throw e } + + require(token.id.value == id) + + return token + } + override suspend fun getNetworkCoin( userWalletId: UserWalletId, networkId: Network.ID, diff --git a/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampSuccessComponent.kt b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampSuccessComponent.kt new file mode 100644 index 0000000000..ceed88f9c5 --- /dev/null +++ b/features/onramp/api/src/main/kotlin/com/tangem/features/onramp/component/OnrampSuccessComponent.kt @@ -0,0 +1,11 @@ +package com.tangem.features.onramp.component + +import com.tangem.core.decompose.factory.ComponentFactory +import com.tangem.core.ui.decompose.ComposableContentComponent + +interface OnrampSuccessComponent : ComposableContentComponent { + + data class Params(val txId: String) + + interface Factory : ComponentFactory +} \ No newline at end of file diff --git a/features/onramp/impl/build.gradle.kts b/features/onramp/impl/build.gradle.kts index 314c0f9529..c97691d146 100644 --- a/features/onramp/impl/build.gradle.kts +++ b/features/onramp/impl/build.gradle.kts @@ -22,6 +22,7 @@ dependencies { implementation(projects.core.decompose) implementation(projects.core.ui) implementation(projects.core.featuretoggles) + implementation(projects.core.navigation) /** Project - Common */ implementation(projects.common.routing) @@ -42,7 +43,6 @@ dependencies { /** DI */ implementation(deps.hilt.android) - implementation(project(":common:ui")) kapt(deps.hilt.kapt) /** AndroidX */ diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/DefaultOnrampSuccessComponent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/DefaultOnrampSuccessComponent.kt new file mode 100644 index 0000000000..d1b329d10b --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/DefaultOnrampSuccessComponent.kt @@ -0,0 +1,42 @@ +package com.tangem.features.onramp.success + +import androidx.activity.compose.BackHandler +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import com.tangem.core.decompose.context.AppComponentContext +import com.tangem.core.decompose.model.getOrCreateModel +import com.tangem.features.onramp.component.OnrampSuccessComponent +import com.tangem.features.onramp.success.model.OnrampSuccessComponentModel +import com.tangem.features.onramp.success.ui.OnrampSuccessComponentContent +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject + +internal class DefaultOnrampSuccessComponent @AssistedInject constructor( + @Assisted appComponentContext: AppComponentContext, + @Assisted params: OnrampSuccessComponent.Params, +) : OnrampSuccessComponent, AppComponentContext by appComponentContext { + + private val model: OnrampSuccessComponentModel = getOrCreateModel(params) + + @Composable + override fun Content(modifier: Modifier) { + val state by model.state.collectAsState() + + BackHandler(onBack = router::pop) + OnrampSuccessComponentContent( + state = state, + onBackClick = router::pop, + ) + } + + @AssistedFactory + interface Factory : OnrampSuccessComponent.Factory { + override fun create( + context: AppComponentContext, + params: OnrampSuccessComponent.Params, + ): DefaultOnrampSuccessComponent + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModelModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModelModule.kt new file mode 100644 index 0000000000..ba6f6664fd --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModelModule.kt @@ -0,0 +1,19 @@ +package com.tangem.features.onramp.success.di + +import com.tangem.core.decompose.di.DecomposeComponent +import com.tangem.core.decompose.model.Model +import com.tangem.features.onramp.success.model.OnrampSuccessComponentModel +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.multibindings.ClassKey +import dagger.multibindings.IntoMap + +@Module +@InstallIn(DecomposeComponent::class) +internal interface OnrampSuccessComponentModelModule { + @Binds + @IntoMap + @ClassKey(OnrampSuccessComponentModel::class) + fun bindOnrampSuccessComponentModel(model: OnrampSuccessComponentModel): Model +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModule.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModule.kt new file mode 100644 index 0000000000..fe2545877b --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/di/OnrampSuccessComponentModule.kt @@ -0,0 +1,19 @@ +package com.tangem.features.onramp.success.di + +import com.tangem.features.onramp.component.OnrampSuccessComponent +import com.tangem.features.onramp.success.DefaultOnrampSuccessComponent +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 OnrampSuccessComponentModule { + @Binds + @Singleton + fun bindOnrampSuccessComponentFactory( + factory: DefaultOnrampSuccessComponent.Factory, + ): OnrampSuccessComponent.Factory +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessClickIntents.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessClickIntents.kt new file mode 100644 index 0000000000..7c7565aef5 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessClickIntents.kt @@ -0,0 +1,5 @@ +package com.tangem.features.onramp.success.entity + +interface OnrampSuccessClickIntents { + fun goToProviderClick(providerLink: String) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt new file mode 100644 index 0000000000..71f80e2f05 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/OnrampSuccessComponentUM.kt @@ -0,0 +1,22 @@ +package com.tangem.features.onramp.success.entity + +import com.tangem.common.ui.expressStatus.state.ExpressStatusUM +import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.ui.extensions.TextReference + +sealed class OnrampSuccessComponentUM { + + data object Loading : OnrampSuccessComponentUM() + + data class Content( + val txId: String, + val timestamp: Long, + val currencyImageUrl: String, + val fromAmount: TextReference, + val toAmount: TextReference, + val statusBlock: ExpressStatusUM, + val providerName: TextReference, + val providerImageUrl: String, + val notification: NotificationUM?, + ) : OnrampSuccessComponentUM() +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/conterter/SetOnrampSuccessContentConverter.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/conterter/SetOnrampSuccessContentConverter.kt new file mode 100644 index 0000000000..435ced42ba --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/conterter/SetOnrampSuccessContentConverter.kt @@ -0,0 +1,194 @@ +package com.tangem.features.onramp.success.entity.conterter + +import com.tangem.common.ui.expressStatus.state.ExpressLinkUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusUM +import com.tangem.common.ui.notifications.ExpressNotificationsUM +import com.tangem.common.ui.notifications.NotificationUM +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.fiat +import com.tangem.core.ui.format.bigdecimal.format +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.persistentListOf +import org.joda.time.DateTime + +internal class SetOnrampSuccessContentConverter( + private val cryptoCurrency: CryptoCurrency, + private val transaction: OnrampTransaction, + private val goToProviderClick: (String) -> Unit, +) : Converter { + override fun convert(value: OnrampStatus): OnrampSuccessComponentUM { + return OnrampSuccessComponentUM.Content( + txId = value.txId, + timestamp = DateTime.parse(value.createdAt).millis, + currencyImageUrl = transaction.fromCurrency.image, + fromAmount = stringReference( + transaction.fromAmount.format { + fiat( + fiatCurrencyCode = transaction.fromCurrency.name, + fiatCurrencySymbol = transaction.fromCurrency.code, + ) + }, + ), + toAmount = stringReference( + transaction.toAmount.format { + crypto(cryptoCurrency) + }, + ), + providerName = stringReference(transaction.providerName), + providerImageUrl = transaction.providerImageUrl, + statusBlock = convertStatuses(value.status, value.externalTxUrl), + notification = getNotification(value.status, value.externalTxUrl), + ) + } + + private fun getNotification(status: OnrampStatus.Status, externalTxUrl: String?): NotificationUM? { + if (externalTxUrl == null) return null + return when (status) { + OnrampStatus.Status.Verifying -> { + ExpressNotificationsUM.NeedVerification { + goToProviderClick(externalTxUrl) + } + } + OnrampStatus.Status.Failed -> { + ExpressNotificationsUM.FailedByProvider { + goToProviderClick(externalTxUrl) + } + } + else -> null + } + } + + private fun convertStatuses(status: OnrampStatus.Status, externalTxUrl: String?): ExpressStatusUM { + val statuses = with(status) { + persistentListOf( + getAwaitingDepositItem(), + getPaymentProcessingItem(), + getBuyingItem(), + getSendingItem(), + ) + } + + return ExpressStatusUM( + title = resourceReference(R.string.common_transaction_status), + link = getStatusLink(status, externalTxUrl), + statuses = statuses, + ) + } + + private fun OnrampStatus.Status.getAwaitingDepositItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.WaitingForPayment.order -> { + resourceReference(R.string.express_exchange_status_receiving) + } + this == OnrampStatus.Status.WaitingForPayment -> { + resourceReference(R.string.express_exchange_status_receiving_active) + } + else -> { + resourceReference(R.string.express_exchange_status_received) + } + }, + state = getStatusState(OnrampStatus.Status.WaitingForPayment), + ) + + private fun OnrampStatus.Status.getPaymentProcessingItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.PaymentProcessing.order -> { + resourceReference(R.string.express_exchange_status_confirming) + } + this == OnrampStatus.Status.PaymentProcessing -> { + resourceReference(R.string.express_exchange_status_confirming_active) + } + this == OnrampStatus.Status.Verifying -> { + resourceReference(R.string.express_exchange_status_verifying) + } + this == OnrampStatus.Status.Failed -> { + resourceReference(R.string.express_exchange_status_failed) + } + else -> resourceReference(R.string.express_exchange_status_confirmed) + }, + state = when { + order < OnrampStatus.Status.PaymentProcessing.order -> { + ExpressStatusItemState.Default + } + this == OnrampStatus.Status.PaymentProcessing -> { + ExpressStatusItemState.Active + } + this == OnrampStatus.Status.Verifying -> { + ExpressStatusItemState.Warning + } + this == OnrampStatus.Status.Failed -> { + ExpressStatusItemState.Error + } + else -> { + ExpressStatusItemState.Done + } + }, + ) + + private fun OnrampStatus.Status.getBuyingItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.Paid.order -> { + resourceReference(R.string.express_status_buying, wrappedList(cryptoCurrency.name)) + } + this == OnrampStatus.Status.Paid -> { + resourceReference(R.string.express_status_buying_active, wrappedList(cryptoCurrency.name)) + } + else -> { + resourceReference(R.string.express_status_bought, wrappedList(cryptoCurrency.name)) + } + }, + state = getStatusState(OnrampStatus.Status.Paid), + ) + + private fun OnrampStatus.Status.getSendingItem() = ExpressStatusItemUM( + text = when { + order < OnrampStatus.Status.Sending.order -> { + resourceReference(R.string.express_exchange_status_sending, wrappedList(cryptoCurrency.name)) + } + this == OnrampStatus.Status.Sending -> { + resourceReference( + R.string.express_exchange_status_sending_active, + wrappedList(cryptoCurrency.name), + ) + } + else -> { + resourceReference(R.string.express_exchange_status_sent, wrappedList(cryptoCurrency.name)) + } + }, + state = getStatusState(OnrampStatus.Status.Sending), + ) + + private fun getStatusLink(status: OnrampStatus.Status, externalTxUrl: String?): ExpressLinkUM { + if (externalTxUrl == null) return ExpressLinkUM.Empty + return when (status) { + OnrampStatus.Status.Verifying, + OnrampStatus.Status.Failed, + -> { + ExpressLinkUM.Content( + icon = R.drawable.ic_arrow_top_right_24, + text = resourceReference(R.string.common_go_to_provider), + onClick = { + goToProviderClick(externalTxUrl) + }, + ) + } + else -> ExpressLinkUM.Empty + } + } + + private fun OnrampStatus.Status.getStatusState(targetState: OnrampStatus.Status) = when { + order < targetState.order -> ExpressStatusItemState.Default + this == targetState -> ExpressStatusItemState.Active + else -> ExpressStatusItemState.Done + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/previewdata/OnrampSuccessComponentUMPreviewData.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/previewdata/OnrampSuccessComponentUMPreviewData.kt new file mode 100644 index 0000000000..d7e355ba63 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/entity/previewdata/OnrampSuccessComponentUMPreviewData.kt @@ -0,0 +1,50 @@ +package com.tangem.features.onramp.success.entity.previewdata + +import com.tangem.common.ui.expressStatus.state.ExpressLinkUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemState +import com.tangem.common.ui.expressStatus.state.ExpressStatusItemUM +import com.tangem.common.ui.expressStatus.state.ExpressStatusUM +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM +import kotlinx.collections.immutable.persistentListOf +import org.joda.time.DateTime + +internal data object OnrampSuccessComponentUMPreviewData { + + val loadingState = OnrampSuccessComponentUM.Loading + + val contentState = OnrampSuccessComponentUM.Content( + txId = "b2894851-7b63-4f56-bd75-e408f1dcba31", + timestamp = DateTime.parse("2023-09-20T13:45:10.868Z").millis, + providerName = stringReference("1Inch"), + providerImageUrl = "https://s3.eu-central-1.amazonaws.com/tangem.api/express/1INCH1024.png", + fromAmount = stringReference("100 USDT"), + toAmount = stringReference("99.99 $"), + currencyImageUrl = "", + notification = null, + statusBlock = ExpressStatusUM( + title = resourceReference(R.string.express_exchange_status_title), + link = ExpressLinkUM.Empty, + statuses = persistentListOf( + ExpressStatusItemUM( + text = stringReference("Deposit received"), + state = ExpressStatusItemState.Done, + ), + ExpressStatusItemUM( + text = stringReference("Confirmed"), + state = ExpressStatusItemState.Done, + ), + ExpressStatusItemUM( + text = stringReference("Buying Bitcoin..."), + state = ExpressStatusItemState.Active, + ), + ExpressStatusItemUM( + text = stringReference("Sending to you"), + state = ExpressStatusItemState.Default, + ), + ), + ), + ) +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt new file mode 100644 index 0000000000..6e925f143c --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/model/OnrampSuccessComponentModel.kt @@ -0,0 +1,97 @@ +package com.tangem.features.onramp.success.model + +import arrow.core.getOrElse +import com.tangem.core.decompose.model.Model +import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.navigation.url.UrlOpener +import com.tangem.domain.onramp.GetOnrampStatusUseCase +import com.tangem.domain.onramp.GetOnrampTransactionUseCase +import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.GetCryptoCurrencyUseCase +import com.tangem.features.onramp.component.OnrampSuccessComponent +import com.tangem.features.onramp.success.entity.OnrampSuccessClickIntents +import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM +import com.tangem.features.onramp.success.entity.conterter.SetOnrampSuccessContentConverter +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import timber.log.Timber +import javax.inject.Inject + +@Suppress("LongParameterList") +internal class OnrampSuccessComponentModel @Inject constructor( + override val dispatchers: CoroutineDispatcherProvider, + private val urlOpener: UrlOpener, + private val getOnrampTransactionUseCase: GetOnrampTransactionUseCase, + private val getOnrampStatusUseCase: GetOnrampStatusUseCase, + private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, + private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, + paramsContainer: ParamsContainer, +) : Model(), OnrampSuccessClickIntents { + + private val params: OnrampSuccessComponent.Params = paramsContainer.require() + private val _state: MutableStateFlow = MutableStateFlow( + value = OnrampSuccessComponentUM.Loading, + ) + + val state: StateFlow get() = _state.asStateFlow() + + init { + loadData() + } + + override fun goToProviderClick(providerLink: String) { + urlOpener.openUrl(providerLink) + } + + private fun loadData() { + modelScope.launch { + getOnrampTransactionUseCase(txId = params.txId) + .fold( + ifLeft = { + Timber.e(it.toString()) + }, + ifRight = { transaction -> + loadTransactionStatus(transaction) + }, + ) + } + } + + private suspend fun loadTransactionStatus(transaction: OnrampTransaction) { + val cryptoCurrencies = getCryptoCurrencyUseCase( + transaction.userWalletId, + transaction.toCurrencyId, + ).getOrElse { error("Crypto currency not found") } + + getOnrampStatusUseCase(txId = params.txId) + .fold( + ifLeft = { + Timber.e(it.toString()) + }, + ifRight = { status -> + _state.update { + SetOnrampSuccessContentConverter( + cryptoCurrency = cryptoCurrencies, + transaction = transaction, + goToProviderClick = ::goToProviderClick, + ).convert(status) + } + removeTransactionIfTerminalStatus(status) + }, + ) + } + + private fun removeTransactionIfTerminalStatus(status: OnrampStatus) { + modelScope.launch { + if (status.status.isTerminal()) { + onrampRemoveTransactionUseCase(status.txId) + } + } + } +} \ No newline at end of file diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/ui/OnrampSuccessComponentContent.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/ui/OnrampSuccessComponentContent.kt new file mode 100644 index 0000000000..e021c732e7 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/success/ui/OnrampSuccessComponentContent.kt @@ -0,0 +1,220 @@ +package com.tangem.features.onramp.success.ui + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +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.stringResource +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import androidx.compose.ui.unit.dp +import coil.compose.SubcomposeAsyncImage +import coil.request.ImageRequest +import com.tangem.common.ui.expressStatus.ExpressStatusBlock +import com.tangem.common.ui.expressStatus.ExpressStatusNotificationBlock +import com.tangem.core.ui.components.* +import com.tangem.core.ui.components.appbar.TangemTopAppBar +import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM +import com.tangem.core.ui.components.containers.FooterContainer +import com.tangem.core.ui.components.transactions.TransactionDoneTitle +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.utils.DateTimeFormatters +import com.tangem.core.ui.utils.WindowInsetsZero +import com.tangem.core.ui.utils.toTimeFormat +import com.tangem.features.onramp.impl.R +import com.tangem.features.onramp.success.entity.OnrampSuccessComponentUM +import com.tangem.features.onramp.success.entity.previewdata.OnrampSuccessComponentUMPreviewData + +@Composable +internal fun OnrampSuccessComponentContent( + state: OnrampSuccessComponentUM, + onBackClick: () -> Unit, + modifier: Modifier = Modifier, +) { + if (state !is OnrampSuccessComponentUM.Content) { + Box(modifier.background(TangemTheme.colors.background.secondary)) + } else { + Content( + state = state, + onBackClick = onBackClick, + modifier = modifier, + ) + } +} + +@Composable +private fun Content(state: OnrampSuccessComponentUM.Content, onBackClick: () -> Unit, modifier: Modifier = Modifier) { + Scaffold( + modifier = modifier + .background(TangemTheme.colors.background.secondary) + .systemBarsPadding(), + topBar = { + TangemTopAppBar( + startButton = TopAppBarButtonUM( + iconRes = R.drawable.ic_close_24, + onIconClicked = onBackClick, + ), + ) + }, + bottomBar = { + PrimaryButton( + text = stringResource(R.string.common_close), + onClick = onBackClick, + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), + ) + }, + contentWindowInsets = WindowInsetsZero, + ) { scaffoldPaddings -> + Column( + modifier = Modifier + .background(TangemTheme.colors.background.secondary) + .padding(scaffoldPaddings) + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .padding(16.dp), + ) { + TransactionDoneTitle( + title = resourceReference(R.string.common_in_progress), + subtitle = resourceReference( + R.string.send_date_format, + wrappedList( + state.timestamp.toTimeFormat(DateTimeFormatters.dateFormatter), + state.timestamp.toTimeFormat(), + ), + ), + ) + SpacerH24() + AmountBlock(state) + SpacerH12() + FooterContainer( + footer = resourceReference(R.string.onramp_transaction_status_footer_text), + ) { + ExpressStatusBlock(state.statusBlock) + } + ExpressStatusNotificationBlock( + state.notification, + ) + } + } +} + +@Composable +private fun AmountBlock(state: OnrampSuccessComponentUM.Content) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(TangemTheme.colors.background.action) + .padding(16.dp), + ) { + val iconModifier = Modifier.size(size = 40.dp) + SubcomposeAsyncImage( + modifier = iconModifier, + model = ImageRequest.Builder(context = LocalContext.current) + .data(state.currencyImageUrl) + .crossfade(enable = true) + .allowHardware(false) + .build(), + loading = { CircleShimmer(modifier = iconModifier) }, + error = { }, + contentDescription = null, + ) + ResizableText( + text = state.fromAmount.resolveReference(), + style = TangemTheme.typography.h2, + color = TangemTheme.colors.text.primary1, + textAlign = TextAlign.Center, + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .padding(top = 16.dp), + ) + Text( + text = state.toAmount.resolveReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(top = 8.dp), + ) + SpacerH12() + AmountProviderBlock( + state.providerName, + state.providerImageUrl, + ) + } +} + +@Composable +private fun AmountProviderBlock(providerName: TextReference, providerImageUrl: String, modifier: Modifier = Modifier) { + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(6.dp), + ) { + Text( + text = stringResource(R.string.common_with), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.tertiary, + ) + SubcomposeAsyncImage( + modifier = Modifier.size(size = 16.dp), + model = ImageRequest.Builder(context = LocalContext.current) + .data(providerImageUrl) + .crossfade(enable = true) + .allowHardware(false) + .build(), + loading = { CircleShimmer() }, + error = { }, + contentDescription = null, + ) + Text( + text = providerName.resolveReference(), + style = TangemTheme.typography.body2, + color = TangemTheme.colors.text.primary1, + ) + } +} + +// region Preview +@Preview(showBackground = true, widthDp = 360, heightDp = 720) +@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun OnrampSuccessComponentContent_Preview( + @PreviewParameter(OnrampSuccessComponentContentPreviewProvider::class) + data: OnrampSuccessComponentUM, +) { + TangemThemePreview { + OnrampSuccessComponentContent( + state = data, + onBackClick = {}, + ) + } +} + +private class OnrampSuccessComponentContentPreviewProvider : + PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + OnrampSuccessComponentUMPreviewData.contentState, + ) +} +// endregion \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt index 1af5a5154a..65ad8d5140 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/fee/SendCustomFee.kt @@ -9,7 +9,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import com.tangem.core.ui.components.inputrow.InputRowEnterAmount import com.tangem.core.ui.components.inputrow.InputRowEnterInfoAmount -import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme import com.tangem.features.send.impl.presentation.state.fee.FeeType import com.tangem.features.send.impl.presentation.state.fields.SendTextField @@ -41,7 +40,7 @@ internal fun SendCustomFee( repeat(customValues.size) { index -> val value = customValues[index] FooterContainer( - footer = value.footer.resolveReference(), + footer = value.footer, ) { if (value.label != null) { InputRowEnterInfoAmount( diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt index 013b9e7258..ea78244e14 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/SendRecipientContent.kt @@ -21,8 +21,12 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.tangem.core.ui.components.containers.FooterContainer import com.tangem.core.ui.components.inputrow.InputRowRecipient +import com.tangem.core.ui.extensions.orMaskWithStars import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.features.send.impl.R @@ -32,8 +36,6 @@ import com.tangem.features.send.impl.presentation.state.SendStates import com.tangem.features.send.impl.presentation.state.fields.SendTextField import com.tangem.features.send.impl.presentation.state.previewdata.RecipientStatePreviewData import com.tangem.features.send.impl.presentation.state.previewdata.SendClickIntentsStub -import com.tangem.core.ui.components.containers.FooterContainer -import com.tangem.core.ui.extensions.orMaskWithStars import com.tangem.features.send.impl.presentation.viewmodel.SendClickIntents import kotlinx.collections.immutable.ImmutableList @@ -107,7 +109,7 @@ private fun LazyListScope.addressItem( ) { item(key = ADDRESS_FIELD_KEY) { FooterContainer( - footer = stringResource(R.string.send_recipient_address_footer, network), + footer = resourceReference(R.string.send_recipient_address_footer, wrappedList(network)), ) { InputRowRecipient( value = address.value, @@ -137,7 +139,7 @@ private fun LazyListScope.memoField(memoField: SendTextField.RecipientMemo?, onM value = memoField.value, label = memoField.label, placeholder = placeholder, - footer = stringResource(R.string.send_recipient_memo_footer), + footer = resourceReference(R.string.send_recipient_memo_footer), onValueChange = memoField.onValueChange, onPasteClick = onMemoChange, modifier = Modifier.padding(top = TangemTheme.dimens.spacing20), diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt index 762dc7f7eb..cc33643eab 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/recipient/TextFieldWithPaste.kt @@ -27,7 +27,7 @@ internal fun TextFieldWithPaste( onValueChange: (String) -> Unit, onPasteClick: (String) -> Unit, modifier: Modifier = Modifier, - footer: String? = null, + footer: TextReference? = null, labelStyle: TextStyle = TangemTheme.typography.body2, error: TextReference? = null, isError: Boolean = false, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt index 47508d351e..8d262a4334 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/ExchangeStatusNotifications.kt @@ -19,6 +19,7 @@ internal sealed interface ExchangeStatusNotifications { sealed class CommonNotification(val config: NotificationConfig) : ExchangeStatusNotifications + @Deprecated("Use one in ExpressNotificationsUM") data class NeedVerification(val onGoToProviderClick: () -> Unit) : CommonNotification( config = NotificationConfig( title = resourceReference(R.string.express_exchange_notification_verification_title), @@ -31,6 +32,7 @@ internal sealed interface ExchangeStatusNotifications { ), ) + @Deprecated("Use one in ExpressNotificationsUM") data class Failed(val onGoToProviderClick: () -> Unit) : CommonNotification( config = NotificationConfig( title = resourceReference(R.string.express_exchange_notification_failed_title), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt index ec2a2b3cfb..7831a8aba3 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBlock.kt @@ -30,6 +30,7 @@ import com.tangem.features.tokendetails.impl.R import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList +@Deprecated("Use ExpressStatusBlock from common") @Composable internal fun ExchangeStatusBlock( statuses: ImmutableList,