diff --git a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt index e1312703a7..e6b2d310c8 100644 --- a/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/features/wallet/redux/middlewares/TradeCryptoMiddleware.kt @@ -49,8 +49,6 @@ object TradeCryptoMiddleware { is TradeCryptoAction.FinishSelling -> openReceiptUrl(action.transactionId) is TradeCryptoAction.Buy -> proceedBuyAction(state, action) is TradeCryptoAction.Sell -> proceedSellAction(action) - is TradeCryptoAction.SendToken -> handleNewSendToken(action = action) - is TradeCryptoAction.SendCoin -> handleNewSendCoin(action = action) } } @@ -166,37 +164,4 @@ object TradeCryptoMiddleware { transactionId = transactionId, )?.let { store.dispatchOpenUrl(it) } } - - private fun handleNewSendToken(action: TradeCryptoAction.SendToken) { - handleNewSend( - userWalletId = action.userWallet.walletId, - txInfo = action.transactionInfo, - currency = action.tokenCurrency, - ) - } - - private fun handleNewSendCoin(action: TradeCryptoAction.SendCoin) { - handleNewSend( - userWalletId = action.userWallet.walletId, - txInfo = action.transactionInfo, - currency = action.coinStatus.currency, - ) - } - - private fun handleNewSend( - userWalletId: UserWalletId, - txInfo: TradeCryptoAction.TransactionInfo?, - currency: CryptoCurrency, - ) { - val route = AppRoute.Send( - currency = currency, - userWalletId = userWalletId, - transactionId = txInfo?.transactionId, - destinationAddress = txInfo?.destinationAddress, - amount = txInfo?.amount, - tag = txInfo?.tag, - ) - - store.dispatchNavigationAction { push(route) } - } } diff --git a/common/ui/build.gradle.kts b/common/ui/build.gradle.kts index 315574334e..28e0387959 100644 --- a/common/ui/build.gradle.kts +++ b/common/ui/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { implementation(deps.compose.navigation) implementation(deps.compose.navigation.hilt) implementation(deps.compose.coil) + implementation(deps.compose.constraintLayout) /** Deps */ implementation(deps.kotlin.immutable.collections) @@ -34,6 +35,7 @@ dependencies { implementation(projects.domain.tokens.models) implementation(projects.domain.transaction.models) implementation(projects.domain.wallets.models) + implementation(projects.domain.onramp.models) implementation(deps.tangem.card.core) implementation(deps.tangem.blockchain) { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressEstimate.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressEstimate.kt similarity index 93% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressEstimate.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressEstimate.kt index 6f913e75fb..81164716b5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressEstimate.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressEstimate.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express +package com.tangem.common.ui.expressStatus import androidx.compose.foundation.background import androidx.compose.foundation.layout.* @@ -8,16 +8,16 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.stringResource +import com.tangem.common.ui.R import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.components.inputrow.InputRowApprox import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.res.TangemTheme -import com.tangem.features.tokendetails.impl.R @Suppress("LongParameterList") @Composable -internal fun ExpressEstimate( +fun ExpressEstimate( timestamp: TextReference, fromTokenIconState: CurrencyIconState, toTokenIconState: CurrencyIconState, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressProvider.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressProvider.kt similarity index 97% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressProvider.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressProvider.kt index 9112d98458..1b82c1a7e7 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressProvider.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressProvider.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express +package com.tangem.common.ui.expressStatus import android.content.res.Configuration import android.widget.Toast @@ -29,7 +29,7 @@ import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @Composable -internal fun ExpressProvider( +fun ExpressProvider( providerName: TextReference, providerType: TextReference, providerTxId: String?, diff --git a/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBottomSheet.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBottomSheet.kt new file mode 100644 index 0000000000..92bd1822aa --- /dev/null +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusBottomSheet.kt @@ -0,0 +1,24 @@ +package com.tangem.common.ui.expressStatus + +import androidx.compose.runtime.Composable +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent +import com.tangem.core.ui.res.TangemTheme + +data class ExpressStatusBottomSheetConfig( + val value: ExpressTransactionStateUM, +) : TangemBottomSheetConfigContent + +@Composable +fun ExpressStatusBottomSheet(config: TangemBottomSheetConfig) { + TangemBottomSheet( + config = config, + containerColor = TangemTheme.colors.background.tertiary, + ) { content: ExpressStatusBottomSheetConfig -> + when (val state = content.value) { + is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state) + } + } +} diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItem.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItem.kt similarity index 98% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItem.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItem.kt index 91cf9ed9be..050f76cd26 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItem.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItem.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express +package com.tangem.common.ui.expressStatus import android.content.res.Configuration import androidx.annotation.DrawableRes @@ -18,6 +18,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import androidx.constraintlayout.compose.* +import com.tangem.common.ui.R import com.tangem.core.ui.components.atoms.text.EllipsisText import com.tangem.core.ui.components.atoms.text.TextEllipsis import com.tangem.core.ui.components.currency.icon.CurrencyIcon @@ -27,7 +28,6 @@ import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview -import com.tangem.features.tokendetails.impl.R @Suppress("DestructuringDeclarationWithTooManyEntries", "LongMethod", "LongParameterList") @Composable diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItems.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItems.kt similarity index 78% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItems.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItems.kt index a0bf44cbc2..6caa46c290 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusItems.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/ExpressStatusItems.kt @@ -1,14 +1,14 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express +package com.tangem.common.ui.expressStatus import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.ui.Modifier +import com.tangem.common.ui.R +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateIconUM -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM -import com.tangem.features.tokendetails.impl.R import kotlinx.collections.immutable.PersistentList -internal fun LazyListScope.expressTransactionsItems( +fun LazyListScope.expressTransactionsItems( expressTxs: PersistentList, modifier: Modifier = Modifier, ) { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/onramp/OnrampStatusBottomSheetContent.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt similarity index 79% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/onramp/OnrampStatusBottomSheetContent.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt index 9b528ddd06..0022d1a262 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/onramp/OnrampStatusBottomSheetContent.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/OnrampStatusBottomSheetContent.kt @@ -1,4 +1,4 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.onramp +package com.tangem.common.ui.expressStatus import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.padding @@ -8,8 +8,7 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign -import com.tangem.common.ui.expressStatus.ExpressStatusBlock -import com.tangem.common.ui.expressStatus.ExpressStatusNotificationBlock +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerH10 import com.tangem.core.ui.components.SpacerH12 @@ -17,12 +16,9 @@ import com.tangem.core.ui.components.SpacerH16 import com.tangem.core.ui.components.SpacerH24 import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressEstimate -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressProvider @Composable -internal fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) { +fun OnrampStatusBottomSheetContent(state: ExpressTransactionStateUM.OnrampUM) { Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16)) { SpacerH10() Text( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExpressTransactionStateUM.kt b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt similarity index 53% rename from features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExpressTransactionStateUM.kt rename to common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt index 928047ea55..7061fe2b27 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExpressTransactionStateUM.kt +++ b/common/ui/src/main/java/com/tangem/common/ui/expressStatus/state/ExpressTransactionStateUM.kt @@ -1,31 +1,13 @@ -package com.tangem.feature.tokendetails.presentation.tokendetails.state.express +package com.tangem.common.ui.expressStatus.state -import com.tangem.common.ui.expressStatus.state.ExpressStatusUM import com.tangem.common.ui.notifications.NotificationUM import com.tangem.core.ui.components.currency.icon.CurrencyIconState import com.tangem.core.ui.extensions.TextReference import com.tangem.domain.onramp.model.OnrampStatus -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.feature.swap.domain.models.domain.ExchangeStatus -import com.tangem.feature.swap.domain.models.domain.SwapProvider -import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications -import kotlinx.collections.immutable.ImmutableList -internal sealed class ExpressTransactionStateUM { +interface ExpressTransactionStateUM { - abstract val info: ExpressTransactionStateInfoUM - - data class ExchangeUM( - override val info: ExpressTransactionStateInfoUM, - val provider: SwapProvider, - val activeStatus: ExchangeStatus?, - val statuses: ImmutableList, - val notification: ExchangeStatusNotifications? = null, - val showProviderLink: Boolean, - val isRefundTerminalStatus: Boolean, - val fromCryptoCurrency: CryptoCurrency, - val toCryptoCurrency: CryptoCurrency, - ) : ExpressTransactionStateUM() + val info: ExpressTransactionStateInfoUM data class OnrampUM( override val info: ExpressTransactionStateInfoUM, @@ -34,10 +16,10 @@ internal sealed class ExpressTransactionStateUM { val providerType: String, // todo onramp fix after SwapProvider moved to own module val activeStatus: OnrampStatus.Status, val fromCurrencyCode: String, - ) : ExpressTransactionStateUM() + ) : ExpressTransactionStateUM } -internal data class ExpressTransactionStateInfoUM( +data class ExpressTransactionStateInfoUM( val title: TextReference, val status: ExpressStatusUM, val notification: NotificationUM?, @@ -49,7 +31,6 @@ internal data class ExpressTransactionStateInfoUM( val onGoToProviderClick: (String) -> Unit, val onClick: () -> Unit, val iconState: ExpressTransactionStateIconUM, - val toAmount: TextReference, val toFiatAmount: TextReference?, val toAmountSymbol: String, @@ -61,7 +42,7 @@ internal data class ExpressTransactionStateInfoUM( val fromCurrencyIcon: CurrencyIconState, ) -internal enum class ExpressTransactionStateIconUM { +enum class ExpressTransactionStateIconUM { Warning, Error, None, diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index 3cc2368a82..aa37b93bdc 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -583,6 +583,8 @@ Der Kauf von Kryptowährungen bei Tangem wird von Drittanbietern zu deren Bedingungen durchgeführt. Suche nach Land Nicht verfügbar + Andere Währungen + Beliebte Fiats Suche nach Währung Durch die Nutzung der Onramp-Funktionalität stimmst Du den %1$s und %2$s des Anbieters zu. Der Kaufbetrag sollte nicht höher sein als %s diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 683983950c..1bd3de114c 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -6,10 +6,15 @@ Esto puede tardar unos segundos. Inténtelo de nuevo más tarde. Los datos aún no se han cargado. La acción no está disponible actualmente. Inténtalo de nuevo más tarde o actualiza los datos deslizando el dedo hacia abajo en la pantalla. + No disponible Elige el token - ¿No encuentras el token en tu billetera? Consulte los mercados para encontrarlo y agregarlo al intercambio + ¿No encuentra el token en tu billetera? Consulte los mercados para encontrarlo y agregarlo al intercambio No hay tokens disponibles para intercambiar con el token seleccionado. Por favor elige otro. No hay par disponible + Para utilizar la función de intercambio, su billetera debe contener al menos 2 tokens + Añada tokens + Sólo tienes 1 token añadido a tu portafolio. Para utilizar la función de intercambio, necesita añadir al menos 2 tokens + Añada tokens Seleccione el token que desea recibir Seleccione el token que desea intercambiar Elige red @@ -25,7 +30,7 @@ El seleccionado no admite la red %1$s Para activar el %1$s cifrado criptográfico de la blockchain, necesitarás reiniciar la billetera a los ajustes de fábrica. Por favor, retire sus fondos antes de hacerlo para asegurarte de no perderlos, y luego complete el proceso de reinicio. El acceso a la billetera actual no será posible después del reinicio. Esta tarjeta no admite %1$s tokens de red debido a una limitación del firmware. - ¿Tiene dificultades para escanear su tarjeta? + ¿Tiene dificultades para escanear su tarjeta/anillo? Esta tarjeta no está diseñada para funcionar con Tangem Tarifa por defecto Habilite las Tarifas predeterminadas para establecer automáticamente las tarifas de transacción y omitir la página de Tarifas al enviar fondos. Siempre puede volver a esta página si es necesario. @@ -65,10 +70,10 @@ El código de acceso se cambiará solo en esta tarjeta o este anillo Todas las tarjetas de la billetera seleccionada se han restablecido a la configuración de fábrica. Ahora puedes crear una nueva billetera. Reinicio completado - ¿Quiere restablecer la siguiente tarjeta en esta billetera? + ¿Quiere restablecer el siguiente dispositivo en esta billetera? Reinicio de tarjeta Recomendamos completar el proceso de reinicio para todas las tarjetas en esta billetera. - No ha reseteado todas sus tarjetas + No ha reseteado todas sus dispositivos Tangem Restablecer a ajustes de fábrica Modo de seguridad Ajustes del dispositivo @@ -266,6 +271,9 @@ El monto fue reembolsado en %1$s (red %2$s) Visite el sitio web del proveedor para la verificación Verificación KYC requerida por el proveedor + Comprado + Comprando + Comprando... Cancelado Confirmado Esperando confirmación @@ -305,15 +313,15 @@ No se encontraron fichas. Por favor intenta con otra solicitud ID : %s ID de transacción copiado - Convierta una de sus monedas a otra + Convierta una de sus activos de su portafolio por este token La siguiente información es opcional. Puede borrarla si no quiere compartirla. Cuéntenos qué funciones echa de menos y trataremos de ayudarle. - Por favor, díganos qué tarjeta tiene + Por favor, díganos qué tarjeta o anillo tiene Hola equipo de soporte, Por favor, cuéntenos más sobre tu problema. Cada pequeño detalle puede ayudar. Billetera previamente activada Mis recomendaciones - No se puede escanear una tarjeta + No se puede escanear una tarjeta/anillo Comentarios Comentarios en Tangem No se puede completar una transacción @@ -329,14 +337,14 @@ Ordenar Escanee Esta información fue generada con IA.\nPulse aquí si encuentra algún error. - Toque para cambiar la contraseña + Para cambiar el código de acceso coloque la tarjeta o el anillo como se muestra arriba y no lo retire hasta el fin de la operación Toque para cambiar la contraseña - Para crear la billetera, toque la tarjeta como se muestra arriba y no la retire hasta el final de la operación + Para crear la billetera, coloque la tarjeta o el anillo como se muestra arriba y no la retire hasta el fin de la operación Para crear la billetera, ponga el anillo como se muestra arriba y manténgala hasta que termine la operación. Toque la tarjeta no. %s de la billetera Coloque para escanear Toque para firmar - Coloque la tarjeta + Coloque la tarjeta o el anillo Ha actualizado la biometría, escanee su tarjeta o anillo para entrar Su saldo debe ser mayor que el valor de la tarifa para hacer una transferencia Saldo insuficiente @@ -379,7 +387,7 @@ Este activo no está disponible en la billetera Este activo no está disponible para esta billetera Añadir al portafolio - Agregue un token + Añadir Redes disponibles Mi portafolio Mercado @@ -391,7 +399,7 @@ Acciones rápidas Busque en el mercado Resultado - Ver tokens con una capitalización de mercado de menos de $100,000 + Ver tokens con una capitalización de mercado inferior a 100,000$ Mostrar tokens Sin resultado Seleccione una red @@ -478,9 +486,9 @@ NFC no está disponible en su dispositivo Deba configurar un único código de acceso para proteger todss sus dispositivos. Proteger - Puede configurar un código de acceso individual en cada tarjeta más tarde + Puede configurar un código de acceso individual en cada tarjeta más adelante Personalizar - El código de acceso se puede recuperar con una tarjeta o un anillo vinculada(o). No guarde todas las tarjetas en un mismo lugar. + El código de acceso se puede recuperar con una tarjeta o un anillo vinculada(o). No guarde todos los dispositivos en el mismo lugar. Restaurar Elige cualquier palabra, frase o número que quieras como su código de acceso Crear un código de acceso @@ -491,12 +499,12 @@ Por favor repita la operación. La tarjeta se restablecerá a la configuración de fábrica. Error de activación Agregar tokens - Ha agregado una tarjeta de backup. Cuando el proceso de backup finalice, no podrá añadir más tarjetas de backup. Si tiene otra tarjeta, agréguela al backup. ¿Quiere continuar el proceso de backup? + Ha agregado una tarjeta/anillo de backup. Cuando el proceso de backup finalice, no podrá añadir más dispositivos de backup. Si tiene otra tarjeta o anillo, agréguela al backup. ¿Quiere continuar el proceso de backup? El proceso de backup está parcialmente completo. No puede salir ahora. La frase de contraseña es una característica de seguridad avanzada utilizada por las billeteras criptográficas. Agrega una palabra o frase adicional de su elección a su frase de recuperación ya existente para desbloquear un conjunto completamente nuevo de direcciones. Agregar una tarjeta o un anillo de backup Escanee la tarjeta - Escanee la tarjeta no. %d + Escanee la tarjeta núm. %d Hacer backup ahora Escanee la tarjeta principal Escanee el anillo @@ -506,7 +514,7 @@ Escanee la tarjeta principal Finalizar más tarde ¿Cómo funciona? - Vamos a generar todas las claves en su tarjeta y crear una billetera segura + Vamos a generar todas las claves en su tarjeta o anillos y crear una billetera segura Crear una billetera Crear una billetera Otras opciones @@ -539,23 +547,23 @@ Legado Para comprobar si has escrito correctamente su seed phrase, por favor introduce las palabras 2ª, 7ª y 11ª Bien, vamos a comprobar - Para iniciar el proceso de backup, agregue hasta dos tarjetas de backup. - Puede agregar una tarjeta más o finalizar el proceso de backup + Para iniciar el proceso de backup, agregue hasta dos tarjetas/anillos de backup. + Puede agregar una tarjeta/anillo más o finalizar el proceso de backup Prepare su tarjeta de backup con el no.%s Escanee la tarjeta principal o el anillo para iniciar el proceso de backup. Prepare la tarjeta principal con el número %s Prepare el anillo y presione el botón de escaneo a continuación - Su tarjeta billetera está configurada y lista para usar. - Número máximo de tarjetas añadido. Finalice el proceso de backup. - Activando tarjeta + Su billetera está configurada y lista para usar. + Número máximo de dispositivos añadido. Finalice el proceso de backup. + Activando billetera Tarjeta de backup Tarjeta de backup no.%d Anillo de backup Ningunos dispositivos de backup Notificaciones - Una tarjeta de backup agregada + Un dispositivo de backup agregado Prepare su tarjeta - Dos tarjetas de backup agregadas + Dos dispositivos de backup agregados Para empezar, simplemente recargue la billetera con cualquier cantidad Para empezar, simplemente recargue la billetera con más de %1$s %2$s Comprar cripto @@ -563,7 +571,7 @@ Activar una billetera El proceso de emparejamiento está parcialmente completo. No puede salir ahora. Si el proceso de creación del wallet se interrumpe de alguna manera, tendrá que empezar de nuevo - Puede hacer backup de sus claves en hasta dos tarjetas Tangem Wallet en blanco. + Puede hacer backup de sus claves en hasta dos tarjetas o anillos Tangem no activadas. El código de acceso se puede restaurar con una de las tarjetas de backup. Todas las tarjetas de backup se pueden usar como funcionales con las claves idénticas. Podrá establecer un código de acceso para proteger sus billeteras. @@ -572,8 +580,11 @@ Tarjetas idénticas Código de acceso Disponible con %s + La compra de criptomonedas en Tangem se lleva a cabo utilizando proveedores externos usando sus propios términos. Buscar por país Indisponible + Otras monedas + Monedas Fiats Populares Buscar por moneda Al utilizar la funcionalidad onramp, acepta %1$s y %2$s del proveedor. El monto de la compra no debe ser mayor a %s @@ -604,7 +615,8 @@ %1$s (%2$s) en la red %3$s El envío de cualquier otra moneda supondrá una pérdida irreversible. Envíe solo %s a esta dirección. Enviar cualquier otra moneda resultará en su pérdida irreversible. - Transfiera fondos desde otra billetera o intercambio + Envíe solo %1$s en la red %2$s + Transfiera fondos desde otra billetera o exchange Participar Error al cargar la información sobre el programa de referidos. Por favor, inténtelo de nuevo más tarde. Error al cargar la información sobre el programa de referidos. Código de error: %s. Por favor, inténtelo de nuevo más tarde. @@ -806,17 +818,17 @@ Revotar Auto Manual - Bloquear - Día + Por Bloque + Diario Cada %s Cada día - Cada %s + Cada %s días Cada - Época + Por Época Era - Hora - Mes - Semana + Cada Hora + Mensual + Semanal Recompensas El stake está bloqueado Hacer más staking @@ -843,7 +855,7 @@ Votar Retirar Sus stakes - Almacene sus activos crypto de forma segura manteniendo las claves privadas contenidas en su tarjeta + Almacene sus criptomonedas de forma segura manteniendo las claves privadas almacenadas en su tarjeta Billetera de hardware revolucionaria Hasta 3 tarjetas físicas o anillos por billetera Backup ultra seguro @@ -857,7 +869,7 @@ La red cobrará una tarifa de aprobación de token para verificar que está autorizando el uso de su token para el swap. Intercambie más tokens a mejores tasas directamente en su billetera. ¡Nuevo proveedor de intercambio disponible! - El monto incluye:\n• Tarifas del proveedor de servicios\n• Tarifas de red por enviar %s desde el intercambio a la dirección del usuario. + La cantidad incluye:\n• Tarifas del proveedor de servicios\n• Tarifas de red por enviar %s desde el intercambio a la dirección del usuario. El monto incluye:\n• tarifas del proveedor de servicios\n• tarifas de red por enviar %1$s desde el intercambio a la dirección del usuario. \n\nEl slippage del proveedor puede alcanzar el %2$s El importe incluye los honorarios del proveedor de servicios. El importe incluye las tarifas del proveedor de servicios. \n\nEl slippage del proveedor puede alcanzar el %s @@ -920,7 +932,7 @@ Ha escaneado la tarjeta gemela incorrecta. Por favor, intente con otra Esta que tiene en sus manos y la otra con el número %s.\n\nAmbas tarjetas pueden usarse para extraer fondos de esta billetera. Una billetera. Dos tarjetas. - Escanee la tarjeta no. %s + Escanee la tarjeta núm. %s Creando la billetera Escanear tarjeta gemela no. %s Preparando la tarjeta diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 55d428f3d3..4972952622 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -582,6 +582,8 @@ Disponible avec %s Recherche par pays Indisponible + Autres devises + Devises Fiats Populaires Recherche par devise En utilisant la fonctionnalité onramp, vous acceptez %1$s et %2$s du fournisseur Le montant de l\'achat ne doit pas dépasser %s diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index bd018bcf92..66cd763ddb 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -575,6 +575,8 @@ Tangemでの暗号通貨の買付は、サードパーティプロバイダーの条件に基づいて行われます。 国で検索 利用不可 + その他の通貨 + 人気の法定通貨 通貨で検索 オンランプ機能を使用することにより、プロバイダの%1$sおよび%2$sに同意するものとします 買付金額は%s以下にしてください diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index f6655e8627..4bd80de49f 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -597,6 +597,8 @@ Tangem предоставляет доступ к покупке через сторонних провайдеров в соответствии с их правилами Поиск по стране Недоступно + Другие валюты + Популярные фиаты Поиск по валюте Пользуясь сервисом покупки, вы соглашаетесь с %1$s и %2$s Сумма покупки не может быть больше, чем %s diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index e93c5799e5..9b615a149f 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -599,6 +599,8 @@ Tangem забезпечує доступ к покупці через сторонніх провайдерів згідно з їхніми умовами Пошук за країною Недоступно + Інші валюти + Популярні фіати Пошук по валюті Використовуючи сервіс покупки, ви погоджуєтесь з %1$s та %2$s Сума покупки не може бути більше ніж %s diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 12d15e5487..ede0aab5f3 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -583,6 +583,8 @@ Buying crypto in Tangem is powered by third-party providers on their terms. Search by country Unavailable + Other currencies + Popular Fiats Search by currency By using onramp functionality, you agree with provider’s %1$s and %2$s The purchase amount should be no more than %s 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 bd8d58977c..44d534e528 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 @@ -249,6 +249,7 @@ internal class DefaultOnrampRepository( maxFromAmount = convertToAmount(response.maxFromAmount, cryptoCurrency), paymentMethod = paymentMethod, provider = provider, + countryCode = response.countryCode, ) }, onError = { error -> @@ -257,6 +258,7 @@ internal class DefaultOnrampRepository( paymentMethod = paymentMethod, provider = provider, fromOnrampAmount = fromOnrampAmount, + countryCode = country.code, ) }, ) @@ -454,6 +456,7 @@ internal class DefaultOnrampRepository( paymentMethod: OnrampPaymentMethod, provider: OnrampProvider, fromOnrampAmount: OnrampAmount, + countryCode: String, ) = if (error is ApiResponseError.HttpException) { val onrampError = onrampErrorConverter.convert(value = error.errorBody.orEmpty()) if (onrampError is OnrampError.AmountError) { @@ -462,6 +465,7 @@ internal class DefaultOnrampRepository( provider = provider, fromAmount = fromOnrampAmount, error = onrampError, + countryCode = countryCode, ) } else { Timber.w(error, "Unable to fetch onramp quotes for ${provider.id}. $error") @@ -470,6 +474,7 @@ internal class DefaultOnrampRepository( provider = provider, fromAmount = fromOnrampAmount, error = onrampError, + countryCode = countryCode, ) } } else { diff --git a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt index e413d42433..869d487ff4 100644 --- a/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt +++ b/domain/onramp/models/src/main/kotlin/com/tangem/domain/onramp/model/OnrampQuote.kt @@ -7,11 +7,13 @@ sealed class OnrampQuote { abstract val paymentMethod: OnrampPaymentMethod abstract val provider: OnrampProvider abstract val fromAmount: OnrampAmount + abstract val countryCode: String data class Data( override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, override val fromAmount: OnrampAmount, + override val countryCode: String, val toAmount: OnrampAmount, val minFromAmount: OnrampAmount, val maxFromAmount: OnrampAmount, @@ -21,6 +23,7 @@ sealed class OnrampQuote { override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, override val fromAmount: OnrampAmount, + override val countryCode: String, val error: OnrampError.AmountError, ) : OnrampQuote() @@ -28,6 +31,7 @@ sealed class OnrampQuote { override val paymentMethod: OnrampPaymentMethod, override val provider: OnrampProvider, override val fromAmount: OnrampAmount, + override val countryCode: String, val error: OnrampError, ) : OnrampQuote() } 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 index 3e1e30f3b0..44b1881b47 100644 --- a/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt +++ b/domain/onramp/src/main/java/com/tangem/domain/onramp/GetOnrampStatusUseCase.kt @@ -11,9 +11,9 @@ class GetOnrampStatusUseCase( private val errorResolver: OnrampErrorResolver, ) { - suspend operator fun invoke(externalTxId: String): Either { + suspend operator fun invoke(txId: String): Either { return Either.catch { - onrampRepository.getStatus(externalTxId) + onrampRepository.getStatus(txId) }.mapLeft(errorResolver::resolve) } } diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt index e75aeb85c5..a6fda29f01 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/legacy/TradeCryptoAction.kt @@ -1,11 +1,9 @@ package com.tangem.domain.tokens.legacy import com.tangem.domain.onramp.model.OnrampSource -import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet import org.rekotlin.Action -import java.math.BigDecimal sealed class TradeCryptoAction : Action { @@ -23,29 +21,4 @@ sealed class TradeCryptoAction : Action { val cryptoCurrencyStatus: CryptoCurrencyStatus, val appCurrencyCode: String, ) : TradeCryptoAction() - - @Deprecated("Use AppRoute instead") - data class SendToken( - val userWallet: UserWallet, - val tokenCurrency: CryptoCurrency.Token, - val tokenFiatRate: BigDecimal?, - val coinFiatRate: BigDecimal?, - val feeCurrencyStatus: CryptoCurrencyStatus?, - val transactionInfo: TransactionInfo? = null, - ) : TradeCryptoAction() - - @Deprecated("Use AppRoute instead") - data class SendCoin( - val userWallet: UserWallet, - val coinStatus: CryptoCurrencyStatus, - val feeCurrencyStatus: CryptoCurrencyStatus?, - val transactionInfo: TransactionInfo? = null, - ) : TradeCryptoAction() - - data class TransactionInfo( - val transactionId: String, - val destinationAddress: String, - val amount: String, - val tag: String? = null, - ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt index ae4f2efc46..04021d82cc 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/confirmresidency/model/ConfirmResidencyModel.kt @@ -52,8 +52,10 @@ internal class ConfirmResidencyModel @Inject constructor( ConfirmResidencyUM.ActionButtonConfig( onClick = { analyticsEventHandler.send(OnrampAnalyticsEvent.OnResidenceConfirm(country.name)) - modelScope.launch { saveDefaultCountryUseCase.invoke(country) } - params.onDismiss() + modelScope.launch { + saveDefaultCountryUseCase.invoke(country) + params.onDismiss() + } }, text = resourceReference(R.string.common_confirm), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampLastUpdate.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampLastUpdate.kt new file mode 100644 index 0000000000..af9aef6482 --- /dev/null +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/OnrampLastUpdate.kt @@ -0,0 +1,8 @@ +package com.tangem.features.onramp.main.entity + +import com.tangem.domain.onramp.model.OnrampAmount + +data class OnrampLastUpdate( + val lastAmount: OnrampAmount, + val lastCountryString: String, +) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt index 015923aafc..08f8763f41 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/OnrampStateFactory.kt @@ -49,8 +49,11 @@ internal class OnrampStateFactory( fun getOnrampErrorState(onrampError: OnrampError): OnrampMainComponentUM { return when (onrampError) { OnrampError.PairsNotFound -> getNoPairsErrorState() - is OnrampError.DataError -> getErrorState(onrampError.code) - is OnrampError.DomainError -> getErrorState() + is OnrampError.DataError -> getErrorState( + errorCode = onrampError.code, + onRefresh = onrampIntents::onRefresh, + ) + is OnrampError.DomainError -> getErrorState(onRefresh = onrampIntents::onRefresh) is OnrampError.AmountError.TooBigError, is OnrampError.AmountError.TooSmallError, OnrampError.RedirectError.VerificationFailed, @@ -74,7 +77,7 @@ internal class OnrampStateFactory( ) } - fun getErrorState(errorCode: String? = null): OnrampMainComponentUM { + fun getErrorState(errorCode: String? = null, onRefresh: () -> Unit): OnrampMainComponentUM { val state = currentStateProvider() val endButton = state.topBarConfig.endButtonUM.copy(enabled = true) @@ -89,13 +92,13 @@ internal class OnrampStateFactory( providerBlockState = OnrampProviderBlockUM.Empty, errorNotification = NotificationUM.Warning.OnrampErrorNotification( errorCode = errorCode, - onRefresh = onrampIntents::onRefresh, + onRefresh = onRefresh, ), ) is OnrampMainComponentUM.InitialLoading -> state.copy( errorNotification = NotificationUM.Warning.OnrampErrorNotification( errorCode = errorCode, - onRefresh = onrampIntents::onRefresh, + onRefresh = onRefresh, ), ) } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt index 61a038c15d..9ea81b1618 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/entity/factory/amount/OnrampAmountStateFactory.kt @@ -14,15 +14,18 @@ import com.tangem.domain.onramp.model.OnrampProviderWithQuote import com.tangem.domain.onramp.model.OnrampQuote import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.tokens.model.AmountType +import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.main.entity.* import com.tangem.features.onramp.providers.entity.SelectProviderResult import com.tangem.utils.Provider +import com.tangem.utils.extensions.isSingleItem internal class OnrampAmountStateFactory( private val currentStateProvider: Provider, private val analyticsEventHandler: AnalyticsEventHandler, private val onrampIntents: OnrampIntents, + private val cryptoCurrency: CryptoCurrency, ) { private val onrampAmountFieldChangeConverter = OnrampAmountFieldChangeConverter( @@ -62,11 +65,12 @@ internal class OnrampAmountStateFactory( return currentState.copy( amountBlockState = amountState.copy(secondaryFieldModel = OnrampAmountSecondaryFieldUM.Loading), + providerBlockState = OnrampProviderBlockUM.Loading, buyButtonConfig = currentState.buyButtonConfig.copy(enabled = false), ) } - fun getAmountSecondaryUpdatedState(quote: OnrampQuote, isBestRate: Boolean): OnrampMainComponentUM { + fun getAmountSecondaryUpdatedState(quote: OnrampQuote): OnrampMainComponentUM { val currentState = currentStateProvider() if (currentState !is OnrampMainComponentUM.Content) return currentState @@ -75,9 +79,9 @@ internal class OnrampAmountStateFactory( return currentState.copy( amountBlockState = amountState.copy( + amountFieldModel = amountState.amountFieldModel.copy(isError = false), secondaryFieldModel = quote.toSecondaryFieldUiModel(amountState) ?: amountState.secondaryFieldModel, ), - providerBlockState = quote.toProviderBlockState(isBestRate), buyButtonConfig = currentState.buyButtonConfig.copy( enabled = quote is OnrampQuote.Data, onClick = { @@ -96,6 +100,32 @@ internal class OnrampAmountStateFactory( ) } + fun getUpdatedProviderState(selectedQuote: OnrampQuote, quotes: List): OnrampMainComponentUM { + val currentState = currentStateProvider() + if (currentState !is OnrampMainComponentUM.Content) return currentState + + analyticsEventHandler.send( + OnrampAnalyticsEvent.ProviderCalculated( + providerName = selectedQuote.provider.info.name, + tokenSymbol = cryptoCurrency.symbol, + paymentMethod = selectedQuote.paymentMethod.name, + ), + ) + + val bestProvider = selectedQuote as? OnrampQuote.Data + val isMultipleQuotes = !quotes.isSingleItem() + val isOtherQuotesHasData = quotes + .filter { it.paymentMethod == selectedQuote.paymentMethod } + .filterNot { it == bestProvider } + .any { it is OnrampQuote.Data } + + val isBestProvider = selectedQuote == bestProvider && isMultipleQuotes && isOtherQuotesHasData + + return currentState.copy( + providerBlockState = selectedQuote.toProviderBlockState(isBestProvider), + ) + } + fun getAmountSecondaryUpdatedState( providerResult: SelectProviderResult, isBestRate: Boolean, diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt index b58364c41d..116dcecb9d 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/main/model/OnrampMainComponentModel.kt @@ -25,10 +25,7 @@ import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.wallets.usecase.GetWalletsUseCase import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.main.OnrampMainComponent -import com.tangem.features.onramp.main.entity.OnrampIntents -import com.tangem.features.onramp.main.entity.OnrampMainBottomSheetConfig -import com.tangem.features.onramp.main.entity.OnrampMainComponentUM -import com.tangem.features.onramp.main.entity.OnrampProviderBlockUM +import com.tangem.features.onramp.main.entity.* import com.tangem.features.onramp.main.entity.factory.OnrampStateFactory import com.tangem.features.onramp.main.entity.factory.amount.OnrampAmountStateFactory import com.tangem.features.onramp.providers.entity.SelectProviderResult @@ -38,15 +35,13 @@ import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.PeriodicTask import com.tangem.utils.coroutines.SingleTaskScheduler -import com.tangem.utils.extensions.isSingleItem import com.tangem.utils.isNullOrZero import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber -import java.math.BigDecimal import javax.inject.Inject -@Suppress("LongParameterList") +@Suppress("LongParameterList", "LargeClass") internal class OnrampMainComponentModel @Inject constructor( override val dispatchers: CoroutineDispatcherProvider, private val analyticsEventHandler: AnalyticsEventHandler, @@ -75,6 +70,7 @@ internal class OnrampMainComponentModel @Inject constructor( currentStateProvider = Provider { _state.value }, analyticsEventHandler = analyticsEventHandler, onrampIntents = this, + cryptoCurrency = params.cryptoCurrency, ) private val selectedUserWallet = getWalletsUseCase.invokeSync().first { it.walletId == params.userWalletId } private val _state: MutableStateFlow = MutableStateFlow( @@ -87,7 +83,7 @@ internal class OnrampMainComponentModel @Inject constructor( val state: StateFlow get() = _state.asStateFlow() val bottomSheetNavigation: SlotNavigation = SlotNavigation() - private val lastAmount = mutableStateOf(BigDecimal.ZERO) + private val lastUpdateState = mutableStateOf(null) init { sendScreenOpenAnalytics() @@ -161,7 +157,11 @@ internal class OnrampMainComponentModel @Inject constructor( } private suspend fun updatePairsAndQuotes() { - _state.update { amountStateFactory.getAmountSecondaryLoadingState() } + val state = state.value as? OnrampMainComponentUM.Content + + if (!state?.amountBlockState?.amountFieldModel?.fiatValue.isNullOrEmpty()) { + _state.update { amountStateFactory.getAmountSecondaryLoadingState() } + } fetchPairsUseCase.invoke(params.cryptoCurrency).fold( ifLeft = ::handleOnrampError, ifRight = { _state.update { amountStateFactory.getAmountSecondaryResetState() } }, @@ -200,7 +200,6 @@ internal class OnrampMainComponentModel @Inject constructor( private fun subscribeToQuotesUpdate() { getOnrampQuotesUseCase.invoke() - .distinctUntilChanged() .conflate() .onEach { maybeQuotes -> maybeQuotes.fold( @@ -282,42 +281,67 @@ internal class OnrampMainComponentModel @Inject constructor( private fun handleQuoteResult(quotes: List) { sendOnrampQuotesErrorAnalytic(quotes) - val quote = quotes.firstOrNull { it !is OnrampQuote.Error } + val quote = selectOrUpdateQuote(quotes) if (quote == null) { - _state.update { stateFactory.getErrorState() } - lastAmount.value = BigDecimal.ZERO + _state.update { stateFactory.getErrorState(onRefresh = ::onRetryQuotes) } + lastUpdateState.value = null return } - val bestProvider = quote as? OnrampQuote.Data - val isMultipleQuotes = !quotes.isSingleItem() - val isOtherQuotesHasData = quotes - .filter { it.paymentMethod == quote.paymentMethod } - .filterNot { it == bestProvider } - .any { it is OnrampQuote.Data } - val hasBestProvider = isMultipleQuotes && isOtherQuotesHasData - - val isBestProvider = quote == bestProvider && hasBestProvider - - if (lastAmount.value != quote.fromAmount.value) { - lastAmount.value = quote.fromAmount.value - if (quote is OnrampQuote.Data) { - analyticsEventHandler.send( - OnrampAnalyticsEvent.ProviderCalculated( - providerName = quote.provider.info.name, - tokenSymbol = params.cryptoCurrency.symbol, - paymentMethod = quote.paymentMethod.name, - ), - ) - } + if (checkLastInputState(quote)) { + lastUpdateState.value = OnrampLastUpdate( + quote.fromAmount, + quote.countryCode, + ) + _state.update { - amountStateFactory.getAmountSecondaryUpdatedState( - quote = quote, - isBestRate = isBestProvider, - ) + amountStateFactory.getUpdatedProviderState(selectedQuote = quote, quotes = quotes) } } + _state.update { amountStateFactory.getAmountSecondaryUpdatedState(quote = quote) } + } + + /** + * !!! Important quote selection logic !!! + * Selects or updated quote based on input data (amount, country, currency). + * If input data has changed select new best quote, otherwise last selected quote. + * If last selected quote on same input data is in an error state, select next best quote + * If new best quote or next best quote does not exist (i.e. Error state) select nothing. + */ + private fun selectOrUpdateQuote(quotes: List): OnrampQuote? { + val quoteToCheck = quotes.firstOrNull { it !is OnrampQuote.Error } + + // Check if amount, country or currency has changed + return if (checkLastInputState(quoteToCheck)) { + quoteToCheck + } else { + val state = state.value as? OnrampMainComponentUM.Content + val providerState = state?.providerBlockState as? OnrampProviderBlockUM.Content + + // Get current selected quote to update + val lastSelectedQuote = quotes.firstOrNull { + it.provider.id == providerState?.providerId && + it.paymentMethod.id == providerState.paymentMethod.id + } + + // Check if selected updated quote is not error + if (lastSelectedQuote is OnrampQuote.Error) { + quoteToCheck + } else { + lastSelectedQuote + } + } + } + + private fun onRetryQuotes() { + _state.update { + (it as? OnrampMainComponentUM.Content)?.copy( + errorNotification = null, + providerBlockState = OnrampProviderBlockUM.Loading, + ) ?: it + } + startLoadingQuotes() } private fun showDemoWarning() { @@ -380,6 +404,11 @@ internal class OnrampMainComponentModel @Inject constructor( } } + private fun checkLastInputState(quote: OnrampQuote?): Boolean { + return lastUpdateState.value?.lastAmount != quote?.fromAmount || + lastUpdateState.value?.lastCountryString != quote?.countryCode + } + private companion object { const val UPDATE_DELAY = 10_000L } diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt index 92e246a26a..2bc10f2804 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/entity/transformer/UpdateCurrencyItemsTransformer.kt @@ -1,10 +1,11 @@ package com.tangem.features.onramp.selectcurrency.entity.transformer import arrow.core.Either -import com.tangem.core.ui.extensions.stringReference +import com.tangem.core.ui.extensions.resourceReference import com.tangem.domain.onramp.model.OnrampCurrencies import com.tangem.domain.onramp.model.OnrampCurrency import com.tangem.domain.onramp.model.error.OnrampError +import com.tangem.features.onramp.impl.R import com.tangem.features.onramp.selectcurrency.entity.CurrenciesListUM import com.tangem.features.onramp.selectcurrency.entity.CurrenciesSection import com.tangem.features.onramp.selectcurrency.entity.CurrencyItemState @@ -28,7 +29,7 @@ internal class UpdateCurrencyItemsTransformer( if (populars.isNotEmpty()) { add( CurrenciesSection( - title = stringReference("Popular fiats"), + title = resourceReference(R.string.onramp_currency_popular), items = populars.map(::convertCurrencyToUiModel).toImmutableList(), ), ) @@ -36,7 +37,7 @@ internal class UpdateCurrencyItemsTransformer( if (others.isNotEmpty()) { add( CurrenciesSection( - title = stringReference("Other currencies"), + title = resourceReference(R.string.onramp_currency_other), items = others.map(::convertCurrencyToUiModel).toImmutableList(), ), ) diff --git a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt index 9e57ad4d2d..ab2abc08b2 100644 --- a/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt +++ b/features/onramp/impl/src/main/kotlin/com/tangem/features/onramp/selectcurrency/model/OnrampSelectCurrencyModel.kt @@ -6,7 +6,6 @@ import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.ui.components.fields.entity.SearchBarUM import com.tangem.core.ui.extensions.resourceReference -import com.tangem.core.ui.extensions.stringReference import com.tangem.domain.onramp.FetchOnrampCurrenciesUseCase import com.tangem.domain.onramp.GetOnrampCurrenciesUseCase import com.tangem.domain.onramp.OnrampSaveDefaultCurrencyUseCase @@ -136,8 +135,11 @@ internal class OnrampSelectCurrencyModel @Inject constructor( private companion object { val loadingSections = listOf( - CurrenciesSection(stringReference("Popular fiats"), items = createLoadingItems("popular")), - CurrenciesSection(stringReference("Other currencies"), items = createLoadingItems("other")), + CurrenciesSection( + resourceReference(R.string.onramp_currency_popular), + items = createLoadingItems("popular"), + ), + CurrenciesSection(resourceReference(R.string.onramp_currency_other), items = createLoadingItems("other")), ).toImmutableList() private fun createLoadingItems(prefix: String, size: Int = 5): ImmutableList = 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 index a86f6fecce..3324551fbb 100644 --- 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 @@ -1,19 +1,28 @@ package com.tangem.features.onramp.success.model +import androidx.compose.ui.res.stringResource import arrow.core.getOrElse import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.ParamsContainer +import com.tangem.core.decompose.navigation.Router +import com.tangem.core.decompose.ui.UiMessageSender import com.tangem.core.navigation.url.UrlOpener +import com.tangem.core.ui.components.BasicDialog +import com.tangem.core.ui.components.DialogButtonUM +import com.tangem.core.ui.extensions.wrappedList +import com.tangem.core.ui.message.ContentMessage import com.tangem.domain.onramp.GetOnrampStatusUseCase import com.tangem.domain.onramp.GetOnrampTransactionUseCase import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase import com.tangem.domain.onramp.analytics.OnrampAnalyticsEvent import com.tangem.domain.onramp.model.OnrampStatus import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.onramp.model.error.OnrampError import com.tangem.domain.tokens.GetCryptoCurrencyUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.features.onramp.component.OnrampSuccessComponent +import com.tangem.features.onramp.impl.R 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 @@ -36,6 +45,8 @@ internal class OnrampSuccessComponentModel @Inject constructor( private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, private val analyticsEventHandler: AnalyticsEventHandler, + private val messageSender: UiMessageSender, + private val router: Router, paramsContainer: ParamsContainer, ) : Model(), OnrampSuccessClickIntents { @@ -58,8 +69,9 @@ internal class OnrampSuccessComponentModel @Inject constructor( modelScope.launch { getOnrampTransactionUseCase(externalTxId = params.externalTxId) .fold( - ifLeft = { - Timber.e(it.toString()) + ifLeft = { error -> + Timber.e(error.toString()) + showErrorAlert(error) }, ifRight = { transaction -> loadTransactionStatus(transaction) @@ -74,7 +86,7 @@ internal class OnrampSuccessComponentModel @Inject constructor( transaction.toCurrencyId, ).getOrElse { error("Crypto currency not found") } - getOnrampStatusUseCase(externalTxId = params.externalTxId) + getOnrampStatusUseCase(txId = transaction.txId) .fold( ifLeft = { error -> analyticsEventHandler.sendOnrampErrorEvent( @@ -84,6 +96,7 @@ internal class OnrampSuccessComponentModel @Inject constructor( paymentMethod = transaction.paymentMethod, ) Timber.e(error.toString()) + showErrorAlert(error) }, ifRight = { status -> analyticsEventHandler.send( @@ -112,6 +125,32 @@ internal class OnrampSuccessComponentModel @Inject constructor( ) } + private fun showErrorAlert(error: OnrampError) { + val contentMessage = ContentMessage { onDismiss -> + val errorCode = (error as? OnrampError.DataError)?.code + val message = if (errorCode.isNullOrBlank()) { + stringResource(R.string.common_unknown_error) + } else { + stringResource(R.string.express_error_code, wrappedList(errorCode)) + } + BasicDialog( + message = message, + confirmButton = DialogButtonUM( + title = stringResource(id = R.string.common_ok), + onClick = { + router.pop() + onDismiss() + }, + ), + onDismissDialog = { + router.pop() + onDismiss() + }, + ) + } + messageSender.send(contentMessage) + } + private fun removeTransactionIfTerminalStatus( cryptoCurrency: CryptoCurrency, providerName: String, diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt index 225538aaa1..83ff67b854 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/viewmodel/SendViewModel.kt @@ -378,10 +378,9 @@ internal class SendViewModel @Inject constructor( when { uiState.value.sendState?.isSuccess == true -> return transactionId != null && amount != null && destinationAddress != null -> { - loadFee() uiState.value = stateFactory.getReadyState(amount, destinationAddress, memo) stateRouter.showSend() - updateNotifications() + loadFee() } else -> { uiState.value = stateFactory.getReadyState() diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt index 368b693b50..f9923fedcc 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/TokenDetailsState.kt @@ -9,7 +9,7 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsDialogConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.PersistentList diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt new file mode 100644 index 0000000000..1829bec66b --- /dev/null +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/express/ExchangeUM.kt @@ -0,0 +1,21 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.state.express + +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.feature.swap.domain.models.domain.ExchangeStatus +import com.tangem.feature.swap.domain.models.domain.SwapProvider +import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications +import kotlinx.collections.immutable.ImmutableList + +internal data class ExchangeUM( + override val info: ExpressTransactionStateInfoUM, + val provider: SwapProvider, + val activeStatus: ExchangeStatus?, + val statuses: ImmutableList, + val notification: ExchangeStatusNotifications? = null, + val showProviderLink: Boolean, + val isRefundTerminalStatus: Boolean, + val fromCryptoCurrency: CryptoCurrency, + val toCryptoCurrency: CryptoCurrency, +) : ExpressTransactionStateUM diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt index 8a1677cfd6..c3eb82b653 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsOnrampTransactionStateConverter.kt @@ -23,9 +23,9 @@ import com.tangem.domain.onramp.model.cache.OnrampTransaction import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateIconUM -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateInfoUM -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index 771b967b95..94035e10e5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -23,9 +23,9 @@ import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionListMode import com.tangem.feature.swap.domain.models.domain.SavedSwapTransactionModel import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeStatusState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateIconUM -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateInfoUM -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider @@ -45,20 +45,20 @@ internal class TokenDetailsSwapTransactionsStateConverter( private val cryptoCurrency: CryptoCurrency, private val analyticsEventsHandler: AnalyticsEventHandler, appCurrencyProvider: Provider, -) : Converter> { +) : Converter> { private val iconStateConverter = CryptoCurrencyToIconStateConverter() private val appCurrency = appCurrencyProvider() - override fun convert(value: Unit): PersistentList { + override fun convert(value: Unit): PersistentList { return persistentListOf() } fun convert( savedTransactions: List, quotes: Set, - ): PersistentList { - val result = mutableListOf() + ): PersistentList { + val result = mutableListOf() savedTransactions .forEach { swapTransaction -> @@ -84,7 +84,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( getNotification(transaction.status?.status, transaction.status?.txExternalUrl, null) val showProviderLink = getShowProviderLink(notifications, transaction.status) result.add( - ExpressTransactionStateUM.ExchangeUM( + ExchangeUM( provider = transaction.provider, statuses = getStatuses(transaction.status?.status), notification = notifications, @@ -108,11 +108,11 @@ internal class TokenDetailsSwapTransactionsStateConverter( } fun updateTxStatus( - tx: ExpressTransactionStateUM.ExchangeUM, + tx: ExchangeUM, statusModel: ExchangeStatusModel?, refundToken: CryptoCurrency?, isRefundTerminalStatus: Boolean, - ): ExpressTransactionStateUM.ExchangeUM { + ): ExchangeUM { if (statusModel == null || tx.activeStatus == statusModel.status) { Timber.e("UpdateTxStatus isn't required. Current status isn't changed") return tx diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt index 534cbc94b4..fd8bfca8da 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExchangeStatusFactory.kt @@ -1,5 +1,6 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.datasource.local.swaptx.ExpressAnalyticsStatus import com.tangem.datasource.local.swaptx.SwapTransactionStatusStore @@ -15,9 +16,8 @@ import com.tangem.feature.swap.domain.SwapTransactionRepository import com.tangem.feature.swap.domain.api.SwapRepository import com.tangem.feature.swap.domain.models.domain.* import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsSwapTransactionsStateConverter -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheetConfig import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.utils.Provider import dagger.assisted.Assisted @@ -55,7 +55,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor( ) } - suspend operator fun invoke(): Flow> { + suspend operator fun invoke(): Flow> { val selectedWallet = getSelectedWalletSyncUseCase().fold( ifLeft = { return emptyFlow() }, ifRight = { it }, @@ -82,7 +82,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor( suspend fun removeTransactionOnBottomSheetClosed(isForceTerminal: Boolean = false) { val state = currentStateProvider() val bottomSheetConfig = state.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return - val selectedTx = bottomSheetConfig.value as? ExpressTransactionStateUM.ExchangeUM ?: return + val selectedTx = bottomSheetConfig.value as? ExchangeUM ?: return val shouldTerminate = selectedTx.activeStatus.isTerminal(selectedTx.isRefundTerminalStatus) || isForceTerminal if (shouldTerminate) { @@ -95,7 +95,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor( } } - suspend fun updateSwapTxStatus(swapTx: ExpressTransactionStateUM.ExchangeUM): ExpressTransactionStateUM.ExchangeUM { + suspend fun updateSwapTxStatus(swapTx: ExchangeUM): ExchangeUM { return if (swapTx.activeStatus.isTerminal(swapTx.isRefundTerminalStatus)) { swapTx } else { @@ -163,7 +163,7 @@ internal class ExchangeStatusFactory @AssistedInject constructor( private fun getExchangeStatusState( savedTransactions: List?, quotes: Set, - ): PersistentList { + ): PersistentList { if (savedTransactions == null) { return persistentListOf() } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt index 476e3a67e3..663e0fd70d 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/ExpressStatusFactory.kt @@ -1,5 +1,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.domain.appcurrency.model.AppCurrency @@ -10,8 +12,7 @@ import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheetConfig +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.utils.Provider import com.tangem.utils.coroutines.CoroutineDispatcherProvider @@ -79,11 +80,13 @@ internal class ExpressStatusFactory @AssistedInject constructor( expressTxs.map { tx -> async { when (tx) { - is ExpressTransactionStateUM.ExchangeUM -> exchangeStatusFactory.updateSwapTxStatus(tx) + is ExchangeUM -> exchangeStatusFactory.updateSwapTxStatus(tx) is ExpressTransactionStateUM.OnrampUM -> onrampStatusFactory.updateOnrmapTxStatus(tx) + else -> null } } }.awaitAll() + .filterNotNull() .toPersistentList() } @@ -95,13 +98,13 @@ internal class ExpressStatusFactory @AssistedInject constructor( val config = state.bottomSheetConfig val expressBottomSheet = config?.content as? ExpressStatusBottomSheetConfig val currentTx = expressTxs.firstOrNull { it.info.txId == expressBottomSheet?.value?.info?.txId } - if (currentTx is ExpressTransactionStateUM.ExchangeUM && currentTx.activeStatus == ExchangeStatus.Finished) { + if (currentTx is ExchangeUM && currentTx.activeStatus == ExchangeStatus.Finished) { updateBalance(currentTx.toCryptoCurrency) } val expressTxsToDisplay = expressTxs.filterNot { when (it) { - is ExpressTransactionStateUM.ExchangeUM -> false is ExpressTransactionStateUM.OnrampUM -> it.activeStatus.isHidden + else -> false } }.toPersistentList() return state.copy( @@ -115,7 +118,7 @@ internal class ExpressStatusFactory @AssistedInject constructor( fun getStateWithExpressStatusBottomSheet(expressState: ExpressTransactionStateUM): TokenDetailsState { val analyticEvent = when (expressState) { - is ExpressTransactionStateUM.ExchangeUM -> TokenExchangeAnalyticsEvent.CexTxStatusOpened( + is ExchangeUM -> TokenExchangeAnalyticsEvent.CexTxStatusOpened( cryptoCurrency.symbol, ) is ExpressTransactionStateUM.OnrampUM -> TokenOnrampAnalyticsEvent.OnrampStatusOpened( @@ -123,6 +126,7 @@ internal class ExpressStatusFactory @AssistedInject constructor( provider = expressState.providerName, fiatCurrency = expressState.fromCurrencyCode, ) + else -> return currentStateProvider() } analyticsEventsHandler.send(analyticEvent) @@ -156,9 +160,7 @@ internal class ExpressStatusFactory @AssistedInject constructor( isForceTerminal: Boolean = false, ) { when (expressState) { - is ExpressTransactionStateUM.ExchangeUM -> exchangeStatusFactory.removeTransactionOnBottomSheetClosed( - isForceTerminal, - ) + is ExchangeUM -> exchangeStatusFactory.removeTransactionOnBottomSheetClosed(isForceTerminal) is ExpressTransactionStateUM.OnrampUM -> onrampStatusFactory.removeTransactionOnBottomSheetClosed() } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt index 15395783f0..ea8a1d95af 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/express/OnrampStatusFactory.kt @@ -1,5 +1,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.datasource.local.swaptx.ExpressAnalyticsStatus import com.tangem.domain.appcurrency.model.AppCurrency @@ -14,9 +16,7 @@ import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsOnrampTransactionStateConverter -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheetConfig import com.tangem.feature.tokendetails.presentation.tokendetails.viewmodels.TokenDetailsClickIntents import com.tangem.utils.Provider import dagger.assisted.Assisted diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index 7037d01f45..9854c7c61e 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -22,6 +22,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider import androidx.paging.compose.collectAsLazyPagingItems +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.expressTransactionsItems import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheet import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheet @@ -49,8 +51,6 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.T import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenDetailsTopAppBar import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.TokenInfoBlock import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheet -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheetConfig -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.expressTransactionsItems import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.staking.TokenStakingBlock import com.tangem.features.markets.token.block.TokenMarketBlockComponent diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt index c0378ede7c..9fb66bfb57 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/ExpressStatusBottomSheet.kt @@ -1,17 +1,14 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express import androidx.compose.runtime.Composable +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.OnrampStatusBottomSheetContent +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig -import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent import com.tangem.core.ui.res.TangemTheme -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.exchange.ExchangeStatusBottomSheetContent -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.onramp.OnrampStatusBottomSheetContent - -internal data class ExpressStatusBottomSheetConfig( - val value: ExpressTransactionStateUM, -) : TangemBottomSheetConfigContent @Composable internal fun ExpressStatusBottomSheet(config: TangemBottomSheetConfig) { @@ -21,7 +18,7 @@ internal fun ExpressStatusBottomSheet(config: TangemBottomSheetConfig) { ) { content: ExpressStatusBottomSheetConfig -> when (val state = content.value) { is ExpressTransactionStateUM.OnrampUM -> OnrampStatusBottomSheetContent(state) - is ExpressTransactionStateUM.ExchangeUM -> ExchangeStatusBottomSheetContent(state) + is ExchangeUM -> ExchangeStatusBottomSheetContent(state) } } } diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt index 493c53c1e8..ab6bfb28b6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/express/exchange/ExchangeStatusBottomSheetContent.kt @@ -9,6 +9,8 @@ import androidx.compose.ui.Alignment.Companion.CenterHorizontally import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign +import com.tangem.common.ui.expressStatus.ExpressEstimate +import com.tangem.common.ui.expressStatus.ExpressProvider import com.tangem.core.ui.R import com.tangem.core.ui.components.SpacerH10 import com.tangem.core.ui.components.SpacerH12 @@ -19,12 +21,10 @@ import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.swap.domain.models.domain.ExchangeStatus import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.ExchangeStatusNotifications -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressEstimate -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressProvider +import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExchangeUM @Composable -internal fun ExchangeStatusBottomSheetContent(state: ExpressTransactionStateUM.ExchangeUM) { +internal fun ExchangeStatusBottomSheetContent(state: ExchangeUM) { Column(modifier = Modifier.padding(horizontal = TangemTheme.dimens.spacing16)) { SpacerH10() Text( diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index f3edf58432..54f5f07983 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -5,7 +5,6 @@ import androidx.lifecycle.* import androidx.paging.cachedIn import arrow.core.getOrElse import com.tangem.blockchain.common.address.AddressType -import com.tangem.common.core.TangemSdkError import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRouter import com.tangem.common.routing.bundle.unbundle @@ -41,7 +40,6 @@ import com.tangem.domain.staking.model.StakingAvailability import com.tangem.domain.staking.model.StakingEntryInfo import com.tangem.domain.tokens.* import com.tangem.domain.tokens.legacy.TradeCryptoAction -import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.tokens.model.NetworkAddress @@ -68,10 +66,10 @@ import com.tangem.feature.tokendetails.presentation.tokendetails.analytics.Token import com.tangem.feature.tokendetails.presentation.tokendetails.analytics.TokenDetailsNotificationsAnalyticsSender import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenBalanceSegmentedButtonConfig import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState -import com.tangem.feature.tokendetails.presentation.tokendetails.state.express.ExpressTransactionStateUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.TokenDetailsStateFactory import com.tangem.feature.tokendetails.presentation.tokendetails.state.factory.express.ExpressStatusFactory -import com.tangem.feature.tokendetails.presentation.tokendetails.ui.components.express.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.features.onramp.OnrampFeatureToggles import com.tangem.features.tokendetails.impl.R import com.tangem.utils.Provider @@ -84,7 +82,6 @@ import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import timber.log.Timber -import java.math.BigDecimal import javax.inject.Inject @Suppress("LongParameterList", "LargeClass", "TooManyFunctions") @@ -99,8 +96,6 @@ internal class TokenDetailsViewModel @Inject constructor( private val getExploreUrlUseCase: GetExploreUrlUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, private val removeCurrencyUseCase: RemoveCurrencyUseCase, - private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, - private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase, private val getCurrencyWarningsUseCase: GetCurrencyWarningsUseCase, private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, @@ -518,68 +513,16 @@ internal class TokenDetailsViewModel @Inject constructor( return } - sendCurrency(status = cryptoCurrencyStatus ?: return) + sendCurrency() } - private fun sendCurrency(status: CryptoCurrencyStatus, transactionInfo: TransactionInfo? = null) { - viewModelScope.launch(dispatchers.main) { - val maybeFeeCurrencyStatus = - getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, status).getOrNull() - - when (val currency = status.currency) { - is CryptoCurrency.Coin -> { - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendCoin( - userWallet = userWallet, - coinStatus = status, - feeCurrencyStatus = maybeFeeCurrencyStatus, - transactionInfo = transactionInfo, - ), - ) - } - is CryptoCurrency.Token -> { - sendToken( - tokenCurrency = currency, - tokenFiatRate = status.value.fiatRate, - feeCurrencyStatus = maybeFeeCurrencyStatus, - transactionInfo = transactionInfo, - ) - } - } - } - } + private fun sendCurrency() { + val route = AppRoute.Send( + currency = cryptoCurrency, + userWalletId = userWallet.walletId, + ) - private fun sendToken( - tokenCurrency: CryptoCurrency.Token, - tokenFiatRate: BigDecimal?, - feeCurrencyStatus: CryptoCurrencyStatus?, - transactionInfo: TransactionInfo?, - ) { - viewModelScope.launch(dispatchers.main) { - val maybeCoinStatus = getNetworkCoinStatusUseCase( - userWalletId = userWalletId, - networkId = tokenCurrency.network.id, - derivationPath = tokenCurrency.network.derivationPath, - isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), - ) - .conflate() - .distinctUntilChanged() - .firstOrNull() - - reduxStateHolder.dispatchWithMain( - action = TradeCryptoAction.SendToken( - userWallet = userWallet, - tokenCurrency = tokenCurrency, - tokenFiatRate = tokenFiatRate, - coinFiatRate = maybeCoinStatus?.fold( - ifLeft = { null }, - ifRight = { it.value.fiatRate }, - ), - feeCurrencyStatus = feeCurrencyStatus, - transactionInfo = transactionInfo, - ), - ) - } + appRouter.push(route) } override fun onReceiveClick(unavailabilityReason: ScenarioUnavailabilityReason) { diff --git a/features/wallet/impl/build.gradle.kts b/features/wallet/impl/build.gradle.kts index 2048d4f108..a1f44712ae 100644 --- a/features/wallet/impl/build.gradle.kts +++ b/features/wallet/impl/build.gradle.kts @@ -52,18 +52,19 @@ dependencies { implementation(projects.core.utils) implementation(projects.core.analytics) implementation(projects.core.analytics.models) - implementation(projects.common.routing) implementation(projects.core.deepLinks) implementation(projects.core.deepLinks.global) implementation(projects.core.decompose) + implementation(projects.core.datasource) + implementation(projects.libs.crypto) + implementation(projects.libs.blockchainSdk) /** Domain modules */ implementation(projects.domain.card) implementation(projects.domain.demo) implementation(projects.domain.legacy) - implementation(projects.libs.blockchainSdk) implementation(projects.domain.models) implementation(projects.domain.settings) implementation(projects.domain.tokens) @@ -82,6 +83,7 @@ dependencies { implementation(projects.domain.markets.models) implementation(projects.domain.feedback) implementation(projects.domain.onramp.models) + implementation(projects.domain.onramp) //TODO: Create api/impl modules for onboarding https://tangem.atlassian.net/browse/AND-4841 implementation(projects.features.onboarding) @@ -100,6 +102,7 @@ dependencies { /** Common modules */ implementation(projects.common.ui) + implementation(projects.common.routing) /** Test libraries */ implementation(deps.test.junit) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt index 6bcf21dadd..108a48acfd 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/deeplink/WalletDeepLinksHandler.kt @@ -2,25 +2,22 @@ package com.tangem.feature.wallet.presentation.deeplink import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.tangem.common.routing.AppRoute +import com.tangem.common.routing.AppRouter import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.deeplink.DeepLink import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.global.BuyCurrencyDeepLink import com.tangem.core.deeplink.global.SellCurrencyDeepLink -import com.tangem.domain.redux.ReduxStateHolder -import com.tangem.domain.tokens.* -import com.tangem.domain.tokens.legacy.TradeCryptoAction -import com.tangem.domain.tokens.legacy.TradeCryptoAction.TransactionInfo -import com.tangem.domain.tokens.model.CryptoCurrency -import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.GetCryptoCurrencyUseCase import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents import com.tangem.features.onramp.OnrampFeatureToggles import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @Suppress("LongParameterList") @@ -28,13 +25,9 @@ internal class WalletDeepLinksHandler @Inject constructor( private val deepLinksRegistry: DeepLinksRegistry, private val analyticsEventHandler: AnalyticsEventHandler, private val getCryptoCurrencyUseCase: GetCryptoCurrencyUseCase, - private val getCryptoCurrencyStatusSyncUseCase: GetCryptoCurrencyStatusSyncUseCase, - private val getCryptoCurrencyStatusesSyncUseCase: GetCryptoCurrencyStatusesSyncUseCase, - private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, - private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, - private val reduxStateHolder: ReduxStateHolder, private val clickIntents: WalletClickIntents, private val onrampFeatureToggles: OnrampFeatureToggles, + private val router: AppRouter, ) { private var deepLinksMap = mutableMapOf>() @@ -75,35 +68,23 @@ internal class WalletDeepLinksHandler @Inject constructor( } private suspend fun onSellCurrencyDeepLink(userWallet: UserWallet, data: SellCurrencyDeepLink.Data) { - val cryptoCurrencyStatus = findCryptoCurrencyStatus(userWallet, data.currencyId) ?: return - val feeCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase( - userWallet.walletId, - cryptoCurrencyStatus, - ).getOrNull() + val cryptoCurrency = getCryptoCurrencyUseCase(userWallet.walletId, data.currencyId).getOrNull() - val transactionInfo = data.let { - TransactionInfo( - amount = it.baseCurrencyAmount, - destinationAddress = it.depositWalletAddress, - transactionId = it.transactionId, - tag = it.depositWalletAddressTag, - ) + if (cryptoCurrency == null) { + Timber.e("onSellCurrencyDeepLink cryptoCurrency is null") + return } - when (cryptoCurrencyStatus.currency) { - is CryptoCurrency.Coin -> sendCoin( - userWallet = userWallet, - cryptoCurrencyStatus = cryptoCurrencyStatus, - feeCurrencyStatus = feeCurrencyStatus, - transactionInfo = transactionInfo, - ) - is CryptoCurrency.Token -> sendToken( - userWallet = userWallet, - cryptoCurrencyStatus = cryptoCurrencyStatus, - feeCurrencyStatus = feeCurrencyStatus, - transactionInfo = transactionInfo, - ) - } + val route = AppRoute.Send( + currency = cryptoCurrency, + userWalletId = userWallet.walletId, + transactionId = data.transactionId, + destinationAddress = data.depositWalletAddress, + amount = data.baseCurrencyAmount, + tag = data.depositWalletAddressTag, + ) + + router.push(route) } private suspend fun onBuyCurrencyDeepLink(externalTxId: String, userWallet: UserWallet) { @@ -114,62 +95,4 @@ internal class WalletDeepLinksHandler @Inject constructor( analyticsEventHandler.send(TokenScreenAnalyticsEvent.Bought(cryptoCurrency.symbol)) } } - - private suspend fun findCryptoCurrencyStatus( - userWallet: UserWallet, - currencyIdValue: String, - ): CryptoCurrencyStatus? { - return if (userWallet.isMultiCurrency) { - getCryptoCurrencyStatusesSyncUseCase(userWallet.walletId).getOrNull()?.let { currencies -> - currencies.find { currencyIdValue == it.currency.id.value } - } - } else { - getCryptoCurrencyStatusSyncUseCase(userWallet.walletId).getOrNull() - } - } - - private fun sendCoin( - userWallet: UserWallet, - cryptoCurrencyStatus: CryptoCurrencyStatus, - feeCurrencyStatus: CryptoCurrencyStatus?, - transactionInfo: TransactionInfo, - ) { - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendCoin( - userWallet = userWallet, - coinStatus = cryptoCurrencyStatus, - feeCurrencyStatus = feeCurrencyStatus, - transactionInfo = transactionInfo, - ), - ) - } - - private suspend fun sendToken( - userWallet: UserWallet, - cryptoCurrencyStatus: CryptoCurrencyStatus, - feeCurrencyStatus: CryptoCurrencyStatus?, - transactionInfo: TransactionInfo, - ) { - val cryptoCurrency = cryptoCurrencyStatus.currency as? CryptoCurrency.Token ?: return - val coinStatus = getNetworkCoinStatusUseCase( - userWalletId = userWallet.walletId, - networkId = cryptoCurrency.network.id, - derivationPath = cryptoCurrency.network.derivationPath, - isSingleWalletWithTokens = false, - ) - .firstOrNull() - ?.getOrNull() - ?: return - - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendToken( - userWallet = userWallet, - tokenCurrency = cryptoCurrency, - tokenFiatRate = cryptoCurrencyStatus.value.fiatRate, - coinFiatRate = coinStatus.value.fiatRate, - feeCurrencyStatus = feeCurrencyStatus, - transactionInfo = transactionInfo, - ), - ) - } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt new file mode 100644 index 0000000000..457d123b1d --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/OnrampStatusFactory.kt @@ -0,0 +1,99 @@ +package com.tangem.feature.wallet.presentation.wallet.domain + +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.datasource.local.swaptx.ExpressAnalyticsStatus +import com.tangem.domain.onramp.GetOnrampStatusUseCase +import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase +import com.tangem.domain.onramp.OnrampUpdateTransactionStatusUseCase +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.onramp.model.OnrampStatus.Status.* +import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import dagger.hilt.android.scopes.ViewModelScoped +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext +import timber.log.Timber +import javax.inject.Inject + +@ViewModelScoped +internal class OnrampStatusFactory @Inject constructor( + private val stateHolder: WalletStateController, + private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, + private val getOnrampStatusUseCase: GetOnrampStatusUseCase, + private val onrampUpdateTransactionStatusUseCase: OnrampUpdateTransactionStatusUseCase, + private val analyticsEventHandler: AnalyticsEventHandler, + private val dispatchers: CoroutineDispatcherProvider, +) { + + suspend fun removeTransactionOnBottomSheetClosed() { + val state = stateHolder.getSelectedWallet() + val bottomSheetConfig = state.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig ?: return + val selectedTx = bottomSheetConfig.value as? ExpressTransactionStateUM.OnrampUM ?: return + + if (selectedTx.activeStatus.isTerminal) { + onrampRemoveTransactionUseCase(externalTxId = selectedTx.info.txExternalId) + } + } + + suspend fun updateOnrmapTransactionStatuses() = withContext(dispatchers.io) { + val singleWalletState = stateHolder.getSelectedWallet() as? WalletState.SingleCurrency.Content + ?: return@withContext + + singleWalletState.expressTxs.map { tx -> + async { + if (tx is ExpressTransactionStateUM.OnrampUM) { + updateOnrampTxStatus(tx) + } + } + }.awaitAll() + } + + private suspend fun updateOnrampTxStatus(onrampTx: ExpressTransactionStateUM.OnrampUM) { + if (!onrampTx.activeStatus.isTerminal) { + getOnrampStatusUseCase(onrampTx.info.txId).fold( + ifLeft = { + Timber.e("Couldn't update onramp status. $it") + }, + ifRight = { statusModel -> + val externalTxId = statusModel.externalTxId + val status = toAnalyticStatus(statusModel.status) ?: return + + if (statusModel.status != onrampTx.activeStatus) { + analyticsEventHandler.send( + TokenOnrampAnalyticsEvent.OnrampStatusChanged( + tokenSymbol = onrampTx.info.toAmountSymbol, + status = status.name, + provider = onrampTx.providerName, + fiatCurrency = onrampTx.fromCurrencyCode, + ), + ) + onrampUpdateTransactionStatusUseCase(externalTxId = externalTxId, statusModel.status) + } + }, + ) + } + } + + private fun toAnalyticStatus(status: OnrampStatus.Status?): ExpressAnalyticsStatus? { + return when (status) { + Expired, + Paused, + -> ExpressAnalyticsStatus.Cancelled + Created, + WaitingForPayment, + PaymentProcessing, + Paid, + Sending, + -> ExpressAnalyticsStatus.InProgress + Verifying -> ExpressAnalyticsStatus.KYC + Failed -> ExpressAnalyticsStatus.Fail + Finished -> ExpressAnalyticsStatus.Done + null -> null + } + } +} diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt index 0145f41f99..a81e5b732f 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoader.kt @@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.onramp.GetOnrampTransactionsUseCase +import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase @@ -27,6 +29,8 @@ internal class SingleWalletContentLoader( private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase, + private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, ) : WalletContentLoader(id = userWallet.walletId) { @@ -55,6 +59,16 @@ internal class SingleWalletContentLoader( getSingleWalletWarningsFactory = getSingleWalletWarningsFactory, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, ), + SingleWalletExpressStatusesSubscriber( + userWallet = userWallet, + stateHolder = stateHolder, + clickIntents = clickIntents, + getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, + analyticsEventHandler = analyticsEventHandler, + getPrimaryCurrencyStatusUpdatesUseCase = getPrimaryCurrencyStatusUpdatesUseCase, + getOnrampTransactionsUseCase = getOnrampTransactionsUseCase, + onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase, + ), TxHistorySubscriber( userWallet = userWallet, isRefresh = isRefresh, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt index 5bdf92ac28..34d91fa2cb 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/loaders/implementors/SingleWalletContentLoaderFactory.kt @@ -2,6 +2,8 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.onramp.GetOnrampTransactionsUseCase +import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase import com.tangem.domain.settings.SetWalletWithFundsFoundUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase @@ -26,6 +28,8 @@ internal class SingleWalletContentLoaderFactory @Inject constructor( private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase, private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase, + private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, private val analyticsEventHandler: AnalyticsEventHandler, private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender, ) { @@ -45,6 +49,8 @@ internal class SingleWalletContentLoaderFactory @Inject constructor( getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase, analyticsEventHandler = analyticsEventHandler, walletWarningsAnalyticsSender = walletWarningsAnalyticsSender, + getOnrampTransactionsUseCase = getOnrampTransactionsUseCase, + onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase, ) } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt index 3190d6f286..d09f324c26 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletState.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.transactions.state.TxHistoryState @@ -59,6 +60,8 @@ internal sealed interface WalletState : WalletStateHolder { override val buttons: PersistentList, override val marketPriceBlockState: MarketPriceBlockState, override val txHistoryState: TxHistoryState, + val expressTxsToDisplay: PersistentList, + val expressTxs: PersistentList, ) : SingleCurrency() data class Locked( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetExpressStatusesTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetExpressStatusesTransformer.kt new file mode 100644 index 0000000000..00a849fced --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetExpressStatusesTransformer.kt @@ -0,0 +1,76 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers + +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.SingleWalletOnrampTransactionConverter +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import kotlinx.collections.immutable.toPersistentList +import timber.log.Timber + +internal class SetExpressStatusesTransformer( + userWalletId: UserWalletId, + private val onrampTxs: List, + private val clickIntents: WalletClickIntents, + private val cryptoCurrencyStatus: CryptoCurrencyStatus, + private val appCurrency: AppCurrency, + private val analyticsEventHandler: AnalyticsEventHandler, +) : WalletStateTransformer(userWalletId) { + override fun transform(prevState: WalletState): WalletState { + return when (prevState) { + is WalletState.SingleCurrency.Content -> { + val expressTxs = SingleWalletOnrampTransactionConverter( + clickIntents = clickIntents, + cryptoCurrencyStatus = cryptoCurrencyStatus, + appCurrency = appCurrency, + analyticsEventHandler = analyticsEventHandler, + ).convertList(onrampTxs).toPersistentList() + + val expressTxsToDisplay = expressTxs.filterNot { + it.activeStatus.isHidden + }.toPersistentList() + + val expressBottomSheet = prevState.bottomSheetConfig?.content as? ExpressStatusBottomSheetConfig + val currentTx = expressTxs.firstOrNull { it.info.txId == expressBottomSheet?.value?.info?.txId } + + prevState.copy( + expressTxs = expressTxs, + expressTxsToDisplay = expressTxsToDisplay, + bottomSheetConfig = prevState.bottomSheetConfig?.updateStateWithExpressStatusBottomSheet(currentTx), + ) + } + is WalletState.SingleCurrency.Locked -> { + Timber.w("Impossible to load express statuses for locked wallet") + prevState + } + is WalletState.Visa -> { + Timber.w("Impossible to load express statuses for visa wallet") + prevState + } + is WalletState.MultiCurrency -> { + Timber.w("Impossible to load express statuses for multi-currency wallet") + prevState + } + } + } + + private fun TangemBottomSheetConfig.updateStateWithExpressStatusBottomSheet( + expressState: ExpressTransactionStateUM?, + ): TangemBottomSheetConfig { + val currentConfig = this.content as? ExpressStatusBottomSheetConfig ?: return this + if (expressState == null) return this + return copy( + content = if (currentConfig.value != expressState) { + ExpressStatusBottomSheetConfig(expressState) + } else { + currentConfig + }, + ) + } +} diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt new file mode 100644 index 0000000000..b7b97ca7e7 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/SingleWalletOnrampTransactionConverter.kt @@ -0,0 +1,279 @@ +package com.tangem.feature.wallet.presentation.wallet.state.transformers.converter + +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.analytics.api.AnalyticsEventHandler +import com.tangem.core.ui.components.currency.icon.CurrencyIconState +import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter +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.core.ui.utils.toDateFormatWithTodayYesterday +import com.tangem.core.ui.utils.toTimeFormat +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.onramp.model.OnrampStatus +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.tokens.model.analytics.TokenOnrampAnalyticsEvent +import com.tangem.feature.wallet.impl.R +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateIconUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateInfoUM +import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import com.tangem.utils.converter.Converter +import kotlinx.collections.immutable.persistentListOf + +internal class SingleWalletOnrampTransactionConverter( + private val clickIntents: WalletClickIntents, + cryptoCurrencyStatus: CryptoCurrencyStatus, + private val appCurrency: AppCurrency, + private val analyticsEventHandler: AnalyticsEventHandler, +) : Converter { + + private val iconStateConverter = CryptoCurrencyToIconStateConverter() + + private val currency = cryptoCurrencyStatus.currency + private val status = cryptoCurrencyStatus.value + + override fun convert(value: OnrampTransaction): ExpressTransactionStateUM.OnrampUM { + return ExpressTransactionStateUM.OnrampUM( + info = ExpressTransactionStateInfoUM( + title = resourceReference( + id = R.string.express_status_buying, + wrappedList(currency.name), + ), + status = convertStatuses(value.status, value.externalTxUrl), + notification = getNotification(value.status, value.externalTxUrl, value.providerName), + txId = value.txId, + txExternalId = value.externalTxId, + txExternalUrl = value.externalTxUrl, + timestamp = value.timestamp, + timestampFormatted = resourceReference( + R.string.send_date_format, + wrappedList( + value.timestamp.toDateFormatWithTodayYesterday(), + value.timestamp.toTimeFormat(), + ), + ), + toAmount = stringReference( + value.toAmount.format { crypto(currency) }, + ), + toFiatAmount = stringReference( + status.fiatRate?.multiply(value.toAmount).format { + fiat( + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ) + }, + ), + toAmountSymbol = currency.symbol, + toCurrencyIcon = iconStateConverter.convert(currency), + fromAmount = stringReference( + value.fromAmount.format { + fiat( + fiatCurrencyCode = value.fromCurrency.name, + fiatCurrencySymbol = value.fromCurrency.code, + ) + }, + ), + fromFiatAmount = null, + fromAmountSymbol = value.fromCurrency.code, + fromCurrencyIcon = CurrencyIconState.FiatIcon( + url = value.fromCurrency.image, + fallbackResId = R.drawable.ic_currency_24, + ), + iconState = getIconState(value.status), + onGoToProviderClick = { + analyticsEventHandler.send(TokenOnrampAnalyticsEvent.GoToProvider) + clickIntents.onGoToProviderClick(it) + }, + onClick = { + val analyticEvent = TokenOnrampAnalyticsEvent.OnrampStatusOpened( + tokenSymbol = currency.symbol, + provider = value.providerName, + fiatCurrency = value.fromCurrency.code, + ) + analyticsEventHandler.send(analyticEvent) + clickIntents.onExpressTransactionClick(value.txId) + }, + ), + providerName = value.providerName, + providerImageUrl = value.providerImageUrl, + providerType = value.providerType, + activeStatus = value.status, + fromCurrencyCode = value.fromCurrency.code, + ) + } + + private fun getNotification( + status: OnrampStatus.Status, + externalTxUrl: String?, + providerName: String, + ): NotificationUM? { + if (externalTxUrl == null) return null + return when (status) { + OnrampStatus.Status.Verifying -> { + analyticsEventHandler.send( + TokenOnrampAnalyticsEvent.NoticeKYC(currency.symbol, providerName), + ) + ExpressNotificationsUM.NeedVerification { + clickIntents.onGoToProviderClick(externalTxUrl) + } + } + OnrampStatus.Status.Failed -> { + ExpressNotificationsUM.FailedByProvider { + clickIntents.onGoToProviderClick(externalTxUrl) + } + } + else -> null + } + } + + private fun getIconState(status: OnrampStatus.Status): ExpressTransactionStateIconUM { + return when (status) { + OnrampStatus.Status.Verifying -> ExpressTransactionStateIconUM.Warning + OnrampStatus.Status.Failed -> ExpressTransactionStateIconUM.Error + else -> ExpressTransactionStateIconUM.None + } + } + + 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(currency.name)) + } + this == OnrampStatus.Status.Paid -> { + resourceReference( + R.string.express_status_buying_active, + wrappedList(currency.name), + ) + } + else -> { + resourceReference(R.string.express_status_bought, wrappedList(currency.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(currency.name), + ) + } + this == OnrampStatus.Status.Sending -> { + resourceReference( + R.string.express_exchange_status_sending_active, + wrappedList(currency.name), + ) + } + else -> { + resourceReference( + R.string.express_exchange_status_sent, + wrappedList(currency.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 = { + clickIntents.onGoToProviderClick(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 + } +} diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt index 38dba7abb1..987b8c6bce 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/utils/WalletLoadingStateFactory.kt @@ -58,6 +58,8 @@ internal class WalletLoadingStateFactory( value = TxHistoryState.getDefaultLoadingTransactions(clickIntents::onExploreClick), ), ), + expressTxsToDisplay = persistentListOf(), + expressTxs = persistentListOf(), ) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt new file mode 100644 index 0000000000..6c83d80975 --- /dev/null +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/SingleWalletExpressStatusesSubscriber.kt @@ -0,0 +1,92 @@ +package com.tangem.feature.wallet.presentation.wallet.subscribers + +import arrow.core.Either +import arrow.core.getOrElse +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.onramp.GetOnrampTransactionsUseCase +import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase +import com.tangem.domain.onramp.model.cache.OnrampTransaction +import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.error.CurrencyStatusError +import com.tangem.domain.tokens.model.CryptoCurrencyStatus +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController +import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetExpressStatusesTransformer +import com.tangem.feature.wallet.presentation.wallet.viewmodels.intents.WalletClickIntents +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.* +import timber.log.Timber + +@Suppress("LongParameterList") +internal class SingleWalletExpressStatusesSubscriber( + private val userWallet: UserWallet, + private val stateHolder: WalletStateController, + private val clickIntents: WalletClickIntents, + private val analyticsEventHandler: AnalyticsEventHandler, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, + private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, + private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase, + private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase, +) : WalletSubscriber() { + + override fun create( + coroutineScope: CoroutineScope, + ): Flow, AppCurrency>> { + return combine( + flow = getPrimaryCurrencyStatusUpdatesUseCase(userWalletId = userWallet.walletId) + .conflate() + .distinctUntilChanged(), + flow2 = getSelectedAppCurrencyUseCase() + .conflate() + .distinctUntilChanged() + .map { maybeAppCurrency -> maybeAppCurrency.getOrElse { AppCurrency.Default } }, + transform = { maybeCurrencyStatus, appCurrency -> maybeCurrencyStatus to appCurrency }, + ).onEach { maybeCurrencyStatusAndAppCurrency -> + val status = maybeCurrencyStatusAndAppCurrency.first.getOrElse { + Timber.e("Unable to get primary currency status: $it") + return@onEach + } + + getOnrampTransactionsUseCase( + userWalletId = userWallet.walletId, + cryptoCurrencyId = status.currency.id, + ).onEach { maybeTransaction -> + maybeTransaction.fold( + ifRight = { onrampTxs -> + onrampTxs.clearHiddenTerminal() + stateHolder.update( + SetExpressStatusesTransformer( + userWalletId = userWallet.walletId, + onrampTxs = onrampTxs, + clickIntents = clickIntents, + cryptoCurrencyStatus = status, + appCurrency = maybeCurrencyStatusAndAppCurrency.second, + analyticsEventHandler = analyticsEventHandler, + ), + ) + }, + ifLeft = { + stateHolder.update( + SetExpressStatusesTransformer( + userWalletId = userWallet.walletId, + onrampTxs = listOf(), + clickIntents = clickIntents, + cryptoCurrencyStatus = status, + appCurrency = maybeCurrencyStatusAndAppCurrency.second, + analyticsEventHandler = analyticsEventHandler, + ), + ) + }, + ) + } + .launchIn(coroutineScope) + } + } + + private suspend fun List.clearHiddenTerminal() { + this.filter { it.status.isHidden && it.status.isTerminal } + .forEach { onrampRemoveTransactionUseCase(externalTxId = it.externalTxId) } + } +} diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt index 8a47b88c02..d6797374c5 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt @@ -45,6 +45,9 @@ import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import androidx.paging.compose.collectAsLazyPagingItems import com.google.accompanist.systemuicontroller.rememberSystemUiController +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheet +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig +import com.tangem.common.ui.expressStatus.expressTransactionsItems import com.tangem.core.ui.components.atoms.Hand import com.tangem.core.ui.components.atoms.handComposableComponentHeight import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig @@ -216,6 +219,12 @@ private fun WalletContent( walletState.marketPriceBlockState?.let { marketPriceBlockState -> marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier) } + if (walletState is WalletState.SingleCurrency.Content) { + expressTransactionsItems( + expressTxs = walletState.expressTxsToDisplay, + modifier = itemModifier, + ) + } } (selectedWallet as? WalletState.Visa.Content)?.let { @@ -716,6 +725,7 @@ private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) { is BalancesAndLimitsBottomSheetConfig -> BalancesAndLimitsBottomSheet(config = bottomSheetConfig) is VisaTxDetailsBottomSheetConfig -> VisaTxDetailsBottomSheet(config = bottomSheetConfig) is PushNotificationsBottomSheetConfig -> PushNotificationsBottomSheet(config = bottomSheetConfig) + is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(config = bottomSheetConfig) } } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index 84de9f0644..4697089b5e 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -17,6 +17,7 @@ import com.tangem.feature.wallet.presentation.router.InnerWalletRouter import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenListStore +import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader @@ -33,9 +34,7 @@ import com.tangem.features.pushnotifications.api.utils.PUSH_PERMISSION import com.tangem.features.pushnotifications.api.utils.getPushPermissionOrNull import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles import com.tangem.utils.Provider -import com.tangem.utils.coroutines.CoroutineDispatcherProvider -import com.tangem.utils.coroutines.JobHolder -import com.tangem.utils.coroutines.saveIn +import com.tangem.utils.coroutines.* import com.tangem.utils.extensions.indexOfFirstOrNull import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.delay @@ -70,6 +69,7 @@ internal class WalletViewModel @Inject constructor( private val shouldAskPermissionUseCase: ShouldAskPermissionUseCase, private val walletImageResolver: WalletImageResolver, private val tokenListStore: MultiWalletTokenListStore, + private val onrampStatusFactory: OnrampStatusFactory, private val walletFeatureToggles: WalletFeatureToggles, analyticsEventsHandler: AnalyticsEventHandler, ) : ViewModel() { @@ -81,6 +81,8 @@ internal class WalletViewModel @Inject constructor( private val refreshWalletJobHolder = JobHolder() private var needToRefreshWallet = false + private var expressTxStatusTaskScheduler = SingleTaskScheduler() + init { analyticsEventsHandler.send(WalletScreenAnalyticsEvent.MainScreen.ScreenOpened) @@ -93,6 +95,7 @@ internal class WalletViewModel @Inject constructor( subscribeOnSelectedWalletFlow() subscribeToScreenBackgroundState() subscribeOnPushNotificationsPermission() + subscribeOnExpressTransactionsUpdates() } private fun maybeMigrateNames() { @@ -239,6 +242,24 @@ internal class WalletViewModel @Inject constructor( .launchIn(viewModelScope) } + private fun subscribeOnExpressTransactionsUpdates() { + viewModelScope.launch(dispatchers.main) { + expressTxStatusTaskScheduler.cancelTask() + expressTxStatusTaskScheduler.scheduleTask( + viewModelScope, + PeriodicTask( + isDelayFirst = false, + delay = EXPRESS_STATUS_UPDATE_DELAY, + task = { + runCatching { onrampStatusFactory.updateOnrmapTransactionStatuses() } + }, + onSuccess = { /* no-op */ }, + onError = { /* no-op */ }, + ), + ) + } + } + private fun needToRefreshTimer() { viewModelScope.launch { delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS) @@ -430,5 +451,6 @@ internal class WalletViewModel @Inject constructor( private companion object { const val REFRESH_WALLET_BACKGROUND_TIMER_MILLIS = 10000L + const val EXPRESS_STATUS_UPDATE_DELAY = 10000L } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt index e34a2aa9d6..81f767c6b0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletContentClickIntents.kt @@ -1,6 +1,7 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import arrow.core.getOrElse +import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.ShouldShowMarketsTooltipUseCase import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase @@ -11,10 +12,14 @@ import com.tangem.domain.tokens.model.TokenActionsState import com.tangem.domain.txhistory.usecase.GetExplorerTransactionUrlUseCase import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory import com.tangem.feature.wallet.presentation.wallet.domain.unwrap import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController import com.tangem.feature.wallet.presentation.wallet.state.model.ActionsBottomSheetConfig import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState +import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer +import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCurrencyActionsConverter import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped @@ -43,6 +48,12 @@ internal interface WalletContentClickIntents { fun onTokenItemLongClick(cryptoCurrencyStatus: CryptoCurrencyStatus) fun onTransactionClick(txHash: String) + + fun onDissmissBottomSheet() + + fun onGoToProviderClick(externalTxId: String) + + fun onExpressTransactionClick(txId: String) } @Suppress("LongParameterList") @@ -51,6 +62,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( private val stateHolder: WalletStateController, private val currencyActionsClickIntents: WalletCurrencyActionsClickIntentsImplementor, private val walletWarningsClickIntents: WalletWarningsClickIntentsImplementor, + private val onrampStatusFactory: OnrampStatusFactory, private val getUserWalletUseCase: GetUserWalletUseCase, private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase, @@ -164,4 +176,39 @@ internal class WalletContentClickIntentsImplementor @Inject constructor( ) } } + + override fun onGoToProviderClick(externalTxUrl: String) { + router.openUrl(externalTxUrl) + } + + override fun onDissmissBottomSheet() { + val userWalletId = stateHolder.getSelectedWalletId() + if (stateHolder.getSelectedWallet().bottomSheetConfig?.content is ExpressStatusBottomSheetConfig) { + viewModelScope.launch(dispatchers.main) { + onrampStatusFactory.removeTransactionOnBottomSheetClosed() + } + } + stateHolder.update(CloseBottomSheetTransformer(userWalletId)) + } + + override fun onExpressTransactionClick(txId: String) { + viewModelScope.launch { + val userWalletId = stateHolder.getSelectedWalletId() + val singleWalletState = stateHolder.getSelectedWallet() as? WalletState.SingleCurrency.Content + ?: return@launch + + val expressTransaction = singleWalletState.expressTxsToDisplay.firstOrNull { it.info.txId == txId } + ?: return@launch + + stateHolder.update( + OpenBottomSheetTransformer( + userWalletId = userWalletId, + content = ExpressStatusBottomSheetConfig( + value = expressTransaction, + ), + onDismissBottomSheet = ::onDissmissBottomSheet, + ), + ) + } + } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt index 59be78d8f4..c5cb0e0286 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletCurrencyActionsClickIntents.kt @@ -21,7 +21,6 @@ import com.tangem.core.ui.haptic.TangemHapticEffect import com.tangem.core.ui.haptic.VibratorHapticManager import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap -import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.core.lce.Lce import com.tangem.domain.core.utils.lceError import com.tangem.domain.demo.IsDemoCardUseCase @@ -32,13 +31,14 @@ import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.settings.usercountry.GetUserCountryUseCase import com.tangem.domain.settings.usercountry.models.UserCountry import com.tangem.domain.staking.model.stakekit.Yield -import com.tangem.domain.tokens.* +import com.tangem.domain.tokens.GetPrimaryCurrencyStatusUpdatesUseCase +import com.tangem.domain.tokens.IsCryptoCurrencyCoinCouldHideUseCase +import com.tangem.domain.tokens.RemoveCurrencyUseCase import com.tangem.domain.tokens.legacy.TradeCryptoAction import com.tangem.domain.tokens.model.* import com.tangem.domain.tokens.model.analytics.TokenReceiveAnalyticsEvent import com.tangem.domain.tokens.model.analytics.TokenScreenAnalyticsEvent import com.tangem.domain.walletmanager.WalletManagersFacade -import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetExploreUrlUseCase import com.tangem.domain.wallets.usecase.GetSelectedWalletSyncUseCase @@ -53,9 +53,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider import dagger.hilt.android.scopes.ViewModelScoped import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.firstOrNull -import kotlinx.coroutines.flow.take import kotlinx.coroutines.launch import java.math.BigDecimal import javax.inject.Inject @@ -108,8 +106,6 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( private val getPrimaryCurrencyStatusUpdatesUseCase: GetPrimaryCurrencyStatusUpdatesUseCase, private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase, private val removeCurrencyUseCase: RemoveCurrencyUseCase, - private val getNetworkCoinStatusUseCase: GetNetworkCoinStatusUseCase, - private val getFeePaidCryptoCurrencyStatusSyncUseCase: GetFeePaidCryptoCurrencyStatusSyncUseCase, private val getExploreUrlUseCase: GetExploreUrlUseCase, private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val analyticsEventHandler: AnalyticsEventHandler, @@ -136,63 +132,12 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor( if (handleUnavailabilityReason(unavailabilityReason)) return stateHolder.update(CloseBottomSheetTransformer(userWalletId = userWallet.walletId)) - viewModelScope.launch(dispatchers.main) { - val maybeFeeCurrencyStatus = - getFeePaidCryptoCurrencyStatusSyncUseCase(userWallet.walletId, cryptoCurrencyStatus).getOrNull() - when (val currency = cryptoCurrencyStatus.currency) { - is CryptoCurrency.Coin -> sendCoin(cryptoCurrencyStatus, userWallet, maybeFeeCurrencyStatus) - is CryptoCurrency.Token -> sendToken( - cryptoCurrency = currency, - cryptoCurrencyStatus = cryptoCurrencyStatus.value, - feeCurrencyStatus = maybeFeeCurrencyStatus, - userWallet = userWallet, - ) - } - } - } - - private fun sendCoin( - cryptoCurrencyStatus: CryptoCurrencyStatus, - userWallet: UserWallet, - feeCurrencyStatus: CryptoCurrencyStatus?, - ) { - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendCoin( - userWallet = userWallet, - coinStatus = cryptoCurrencyStatus, - feeCurrencyStatus = feeCurrencyStatus, - ), + val route = AppRoute.Send( + currency = cryptoCurrencyStatus.currency, + userWalletId = userWallet.walletId, ) - } - private fun sendToken( - cryptoCurrency: CryptoCurrency.Token, - cryptoCurrencyStatus: CryptoCurrencyStatus.Value, - feeCurrencyStatus: CryptoCurrencyStatus?, - userWallet: UserWallet, - ) { - viewModelScope.launch(dispatchers.main) { - getNetworkCoinStatusUseCase( - userWalletId = userWallet.walletId, - networkId = cryptoCurrency.network.id, - derivationPath = cryptoCurrency.network.derivationPath, - isSingleWalletWithTokens = userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken(), - ) - .take(count = 1) - .collectLatest { - it.onRight { coinStatus -> - reduxStateHolder.dispatch( - action = TradeCryptoAction.SendToken( - userWallet = userWallet, - tokenCurrency = cryptoCurrency, - tokenFiatRate = cryptoCurrencyStatus.fiatRate, - coinFiatRate = coinStatus.value.fiatRate, - feeCurrencyStatus = feeCurrencyStatus, - ), - ) - } - } - } + appRouter.push(route) } override fun onReceiveClick(cryptoCurrencyStatus: CryptoCurrencyStatus) {