diff --git a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt index e4bf1086bd..2712e8375d 100644 --- a/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt +++ b/app/src/main/java/com/tangem/tap/common/redux/legacy/LegacyMiddleware.kt @@ -4,6 +4,7 @@ import com.tangem.blockchain.common.AmountType import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.redux.LegacyAction import com.tangem.domain.utils.convertToSdkAmount +import com.tangem.tap.common.extensions.dispatchWithMain import com.tangem.tap.common.extensions.inject import com.tangem.tap.common.extensions.stripZeroPlainString import com.tangem.tap.common.feedback.FeedbackEmail @@ -11,13 +12,23 @@ import com.tangem.tap.common.feedback.RateCanBeBetterEmail import com.tangem.tap.common.feedback.SendTransactionFailedEmail import com.tangem.tap.common.redux.AppState import com.tangem.tap.common.redux.global.GlobalAction +import com.tangem.tap.features.details.redux.DetailsAction import com.tangem.tap.proxy.redux.DaggerGraphState import com.tangem.tap.scope import com.tangem.tap.store +import com.tangem.utils.coroutines.JobHolder +import com.tangem.utils.coroutines.saveIn +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.distinctUntilChanged +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.launch import org.rekotlin.Middleware internal object LegacyMiddleware { + private val prepareDetailsScreenJobHolder = JobHolder() + val legacyMiddleware: Middleware = { _, _ -> { next -> { action -> @@ -85,6 +96,24 @@ internal object LegacyMiddleware { } } } + is LegacyAction.PrepareDetailsScreen -> { + val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager) + val walletsRepository = store.inject(DaggerGraphState::walletsRepository) + + userWalletsListManager.selectedUserWallet + .distinctUntilChanged() + .onEach { selectedUserWallet -> + store.dispatchWithMain( + DetailsAction.PrepareScreen( + scanResponse = selectedUserWallet.scanResponse, + shouldSaveUserWallets = walletsRepository.shouldSaveUserWalletsSync(), + ), + ) + } + .flowOn(Dispatchers.IO) + .launchIn(scope) + .saveIn(prepareDetailsScreenJobHolder) + } } next(action) } diff --git a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt index c6d4c83b91..0807a628f1 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/StakingDomainModule.kt @@ -2,6 +2,7 @@ package com.tangem.tap.di.domain import com.tangem.domain.staking.* import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository import com.tangem.domain.staking.repositories.StakingRepository import com.tangem.domain.staking.repositories.StakingTransactionHashRepository import dagger.Module @@ -122,6 +123,42 @@ internal object StakingDomainModule { ) } + @Provides + @Singleton + fun provideSavePendingTransactionUseCase( + stakingPendingTransactionRepository: StakingPendingTransactionRepository, + stakingErrorResolver: StakingErrorResolver, + ): SavePendingTransactionUseCase { + return SavePendingTransactionUseCase( + stakingPendingTransactionRepository = stakingPendingTransactionRepository, + stakingErrorResolver = stakingErrorResolver, + ) + } + + @Provides + @Singleton + fun provideInvalidatePendingTransactionsUseCase( + stakingPendingTransactionRepository: StakingPendingTransactionRepository, + stakingErrorResolver: StakingErrorResolver, + ): InvalidatePendingTransactionsUseCase { + return InvalidatePendingTransactionsUseCase( + stakingPendingTransactionRepository = stakingPendingTransactionRepository, + stakingErrorResolver = stakingErrorResolver, + ) + } + + @Provides + @Singleton + fun provideGetPendingTransactionsUseCase( + stakingPendingTransactionRepository: StakingPendingTransactionRepository, + stakingErrorResolver: StakingErrorResolver, + ): GetPendingTransactionsUseCase { + return GetPendingTransactionsUseCase( + stakingPendingTransactionRepository = stakingPendingTransactionRepository, + stakingErrorResolver = stakingErrorResolver, + ) + } + @Provides @Singleton fun provideSendUnsubmittedHashesUseCase( diff --git a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt index 6df235a481..6ba358cced 100644 --- a/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/main/MainViewModel.kt @@ -224,6 +224,7 @@ internal class MainViewModel @Inject constructor( override fun onDismissBottomSheet() { listenToFlipsUseCase.changeUpdateEnabled(true) + router.pop() stateHolder.updateWithoutModalNotification() stateHolder.updateWithHiddenBalancesToast(true) } diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt index fbe921d20b..ea5be79096 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketChartResponse.kt @@ -6,6 +6,8 @@ import java.math.BigDecimal @JsonClass(generateAdapter = true) data class TokenMarketChartResponse( + // There is a bug in the API, it returns null values. + // We need to filter them out. @Json(name = "prices") - val prices: Map, + val prices: Map, ) \ No newline at end of file diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index a72160333d..05775e44ca 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -39,7 +39,7 @@ Zu viele Versuche Du hast die biometrische Authentifizierung auf deinem Telefon deaktiviert und kannst keine Wallets in der App speichern. Um Wallets zu speichern, aktiviere bitte die biometrische Authentifizierung in deinen Telefoneinstellungen. Backup-Prozess starten - Von deiner Fiat-Karte oder deinem Bankkonto + Verwende eine Bankkarte oder eine andere Zahlungsmethode %d Karte %d Karten @@ -275,7 +275,7 @@ Keine Token gefunden. Bitte versuche eine andere Anfrage ID: %s Transaktions-ID kopiert - Aus einer anderen Währung in deiner Wallet + Wechsel eine Ihrer Währungen in eine andere Die folgenden Angaben sind freiwillig. Du kannst diese löschen, wenn du sie nicht weitergeben möchtest. Teile uns mit, welche Funktionen du vermisst, und wir werden versuchen, dir zu helfen. Bitte sag uns, welche Karte du hast @@ -356,6 +356,7 @@ Die Daten konnten nicht geladen werden... Keine Daten Schnelle Aktionen + Markt durchsuchen Ergebnis Token unter 100k USD Marktkapitalisierung anzeigen Token anzeigen @@ -386,25 +387,32 @@ Kaufdruck Umlaufmenge Die Gesamtzahl der Coins, die für den Handel verfügbar sind und auf dem Markt zirkulieren + Umlaufmenge Erfahrene Käufer Nettokäufer mit der zusätzlichen Anforderung, mindestens 100 ausgehende Transaktionen zu haben + Erfahrene Käufer Vollständig verwässerte Bewertung Der theoretische Gesamtwert einer Kryptowährung, wenn alle Coins, die existieren könnten, im Umlauf sind, einschließlich derjenigen, die derzeit nicht im Umlauf sind + Vollständig verwässerte Bewertung Entstehungsdatum Hoch Inhaber/ Halter Die Änderung der Anzahl der Token-Inhaber innerhalb eines bestimmten Zeitraums + Inhaber/ Halter Einblicke Links Liquidität Die Änderung der Liquidität, die dem Token während des angegebenen Zeitraums zur Verfügung steht + Liquidität Liquiditätsindex Leer Niedrig MarketCap - Der Gesamtmarktwert einer Kryptowährung, berechnet durch Multiplikation des aktuellen Preises des Coins mit der Gesamtzahl der im Umlauf befindlichen Coins. + Der Gesamtmarktwert einer Kryptowährung, berechnet sich durch Multiplikation des aktuellen Preises des Coins mit der Gesamtzahl der im Umlauf befindlichen Coins + MarketCap Marktbewertung Position im Krypto-Rating zwischen allen Coins basierend auf der Marktkapitalisierung + Marktbewertung Maximale Versorgung Leer Metriken @@ -415,8 +423,10 @@ Soziales Gesamtangebot Die maximale Anzahl von Coins oder Tokens, die jemals für eine bestimmte Kryptowährung existieren können + Gesamtangebot Handelsvolumen (24h) Der Gesamtbetrag einer Kryptowährung, der innerhalb der letzten 24 Stunden gehandelt wurde, wobei das Aktivitäts- und Liquiditätsniveau auf dem Markt angegeben wird + Handelsvolumen (24h) Rufe dies auf oder tippe auf die Suchleiste, um Token direkt vom Markt hinzuzufügen Token hinzufügen NFC ist auf deinem Gerät nicht verfügbar @@ -520,7 +530,7 @@ Kamerazugriff verweigert %1$s ( %2$s ) im %3$s Netzwerk Sende nur %s an diese Adresse. Der Versand einer anderen Währung führt zu ihrem unwiderruflichen Verlust. - QR-Code anzeigen oder Adresse teilen + Überweise Geld von einem anderen Wallet oder einer anderen Börse Teilnehmen Die Informationen zum Empfehlungsprogramm konnten nicht geladen werden. Bitte versuche es später noch einmal. Die Informationen über das Empfehlungsprogramm konnten nicht geladen werden. Fehlercode: %s. Bitte versuche es später noch einmal. @@ -605,7 +615,7 @@ Gesamtbetrag übersteigt den Saldo Ein Guthaben von mindestens %s ist erforderlich, um dein Konto in der Blockchain aktiv zu halten und Sicherheitsrisiken zu vermeiden. Dieser Betrag verbleibt auf deinem Guthaben und kann nicht abgehoben werden. Mindesteinlage - Der Kommissionsbetrag ist %s mal der empfohlene Betrag. Stelle sicher, dass die benutzerdefinierten Einstellungen korrekt sind. + Dein Provisionsbetrag ist %s -mal höher als der empfohlene Betrag. Bitte überprüfe benutzerdefinierten Einstellungen und pass diese an. Die individuelle Gebühr ist hoch Aufgrund der Besonderheiten des Netzes %1$s ist die Gebühr für die Überweisung des gesamten Guthabens höher. Um die Kommission zu reduzieren, Kannst du %2$s verlassen. Die Gebühr ist höher @@ -639,7 +649,7 @@ Du sendest **%1$s** und %2$s Du sendest **%1$s** Die Netzwerkgebühr wird durch die Nutzung von %1$s Energieträgern gedeckt. - Die Netzwerkgebühr wird durch das Ausgeben von %1$s Energieträgern reduziert. + Die Netzwerkgebühr wird durch den Verbrauch von %1$s Energie reduziert inklusive einer Netzgebühr von %1$s Die Transaktion wurde erfolgreich signiert und an den Blockchain-Knoten gesendet. Die Walletbilanz wird aktualisiert %1$s ist ein Vermögenswert im Tron-Netzwerk. Um die Gebühr zu berechnen und eine Transaktion durchzuführen, musst du etwas Tron (TRX) auf deinem Konto einzahlen. @@ -659,7 +669,7 @@ Belohnungen sammeln sich täglich automatisch in deinem Staking-Konto an. Verfügbar Durchschnittliche Belohnungsquote - Was sit Staking? + Was ist Staking? %s geschätzter Profit Marktbewertung Metriken @@ -679,7 +689,8 @@ Gesperrt Migrieren Natives Staking - Verdiente Belohnungen werden an deine Wallet gesendet und stehen sofort zur Verwendung zur Verfügung + Verdiente Belohnungen werden an Deine Wallet gesendet und stehen Dir sofort zur Verfügung + Stake sicher und verdienen Belohnungen Sicher staken und tägliche Belohnungen verdienen. Sicher staken und stündliche Belohnungen verdienen. Sicher staken und monatliche Belohnungen verdienen. diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index 0bfcf8de3d..25839b4722 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -39,7 +39,7 @@ Demasiados intentos Ha desactivado la autenticación biométrica en su teléfono y no podrá guardar billeteras en la aplicación. Para guardar billeteras, active la función de autenticación biométrica en los ajustes de su teléfono. Iniciar proceso de backup - Con su tarjeta bancaria o cuenta bancaria + Utilice una tarjeta bancaria u otros métodos de pago %d tarjeta %d tarjetas @@ -84,6 +84,7 @@ No ha otorgado acceso a su cámara, cambie su configuración de privacidad Cancelar Elige una acción + Reclamar Reclame recompensas Cerrar Continuar @@ -274,7 +275,7 @@ No se encontraron fichas. Por favor intenta con otra solicitud ID : %s ID de transacción copiado - Con otra moneda en su billetera + Convierta una de sus monedas a otra 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 @@ -355,6 +356,7 @@ No se pueden cargar los datos… Sin datos Acciones rápidas + Busque en el mercado Resultado Ver tokens con una capitalización de mercado de menos de $100,000 Mostrar tokens @@ -385,24 +387,31 @@ Presión de compra Suministro circulante El número total de monedas que están disponibles para el comercio y que circulan en el mercado. + Suministro circulante Compradores experimentados Compradores netos con el requisito adicional de tener al menos 100 transacciones salientes + Compradores experimentados Valoración totalmente diluida El valor teórico total de una criptomoneda si todas las monedas que podrían existir estuvieran en circulación, incluidas las que actualmente no circulan. + Valoración totalmente diluida Dato de Genesis Alto Titulares El cambio en el número de poseedores de tokens dentro de un período de tiempo específico + Titulares Ideas Enlaces Liquidez El cambio en la cantidad de liquidez disponible para el token durante el período de tiempo especificado + Liquidez Índice de liquidez Bajo Capital. de mercado El valor total de una criptomoneda calculado multiplicando su precio por la cantidad de monedas en circulación + Capital. de mercado Evaluación de mercado Posición en la clasificación de criptomonedas entre todas las monedas según la capitalización de mercado + Evaluación de mercado Suministro máximo Métrica Enlaces oficiales @@ -412,8 +421,10 @@ Social Suministro total La cantidad máxima de monedas o tokens que pueden existir para una criptomoneda en particular + Suministro total Volumen de operaciones (24 horas) La cantidad total de una criptomoneda que se ha negociado en las últimas 24 horas, lo que indica el nivel de actividad y liquidez en el mercado. + Volumen de operaciones (24 horas) Tire hacia arriba o toque la barra de búsqueda para agregar tokens directamente desde el mercado Agregar tokens NFC no está disponible en su dispositivo @@ -517,7 +528,7 @@ Acceso a la cámara denegado %1$s (%2$s) en la red %3$s Envíe solo %s a esta dirección. Enviar cualquier otra moneda resultará en su pérdida irreversible. - Muestra un código QR o comparte tu dirección + Transfiera fondos desde otra billetera o intercambio 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. @@ -634,6 +645,10 @@ Enviar %s Está enviando **%1$s** incluida una tarifa de red de %2$s Está enviando**%1$s** y %2$s + Está enviando **%1$s** + La tarifa de red se cubrirá al usar %1$s energía + La tarifa de red se reducirá al usar %1$s energía + incluida una tarifa de red de %1$s La transacción se firmó con éxito y se envió al nodo blockchain. El saldo de la billetera se actualizará después de un tiempo. %1$s es un activo en la red Tron. Para calcular la tarifa y realizar una transacción, deba depositar algo de Tron (TRX) en su cuenta. Transacción enviada @@ -644,6 +659,8 @@ El monto del staking debe ser al menos %s El monto del staking se redondeará a %1$s TRX debido a las reglas de la red. Unstaking de la reclamación + Tarifa de staking de la cuenta + Una cuenta de staking es una cuenta especial donde se almacenan los tokens SOL de staking. Se crea cuando delegas sus tokens a un validador para participar en la validación de transacciones y ganar recompensas. Se cobra una pequeña tarifa por crear la cuenta de staking, que se devuelve una vez que se completa el staking. Porcentaje de rendimiento anual El porcentaje de rendimiento anual que podrá ganar como participante en el staking. APR @@ -671,14 +688,25 @@ Migrar Native staking Recomp. won se le enviará y estará disponible para su uso inmediato + Haga staking de forma segura y comience a ganar recompensas + Haga de forma segura y comience a ganar recompensas diarias + Haga staking de forma segura y comience a ganar recompensas por hora + Haga staking de forma segura y comience a ganar recompensas mensuales El staking le permite ganar %1$s. Sus recompensas por apostar llegan todos los días. El staking le permite ganar %1$s. Sus recompensas del staking llegan cada hora. El staking le permite ganar %1$s. Sus recompensas del staking llegan todos los meses. El staking le permite ganar %1$s. Sus recompensas por apostar llegan todas las semanas. + Haga staking de forma segura y comience a ganar recompensas semanales Gane recompensas por staking + El staking no está disponible actualmente debido a las condiciones de la red. Inténtelo de nuevo más tarde. + El staking en la red %1$s con un nuevo validador transferirá automáticamente todos los fondos previamente en staking a este validador. + Reinvierta las recompensas obtenidas en el monto apostado, aumentando las ganancias potenciales. + Desbloquee su dinero para retirarlo del proceso de staking. El desbloqueo demora %s minuto. + Sus fondos estarán disponibles para su uso después del período de desvinculación de 21 días. La recompensa se retirará junto con los fondos de desvinculación. Sus fondos estarán disponibles para su uso después del período de desvinculación de %s. Ya puede retirar sus fondos, estarán disponibles para usar de inmediato. Hacer staking en la red Tron con un nuevo validador transferirá automáticamente todos los fondos de staking a este validador. + Preparando Listo para retirar Reunir Haga el staking de nuevo @@ -698,6 +726,7 @@ Recompensas El stake está bloqueado Hacer más staking + Haga staking de %1$s y recibirá %2$s anualmente Toque para desbloquear Toque para retirar Stake %s @@ -705,8 +734,10 @@ ¡La transacción se está procesando! La validación está en curso en la cadena de bloques. Esto puede tardar unos minutos. Desunión Desbloquear + Desbloqueando Sin staking Unstaking + Detenando el staking de activos %s Validador Validadores Votar @@ -846,6 +877,8 @@ Error de activación Según los desarrolladores de la red BNB, el soporte para el estándar BEP-2\nfinalizará en junio de 2024. Para evitar perder activos con este estándar, por favor conviértalos al estándar BEP-20. Usa nuestro servicio de swap para transferirlos a la red BNB Smart Chain. BNB Beacon Chain se cerrará + Por favor deposite %1$s para cubrir la tarifa de red + Fondos insuficientes para cubrir la tarifa de red Podría ser mejor Me gusta Entendido diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index 0d3cb6b2bc..2a9e104e07 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -39,7 +39,7 @@ Trop de tentatives Vous avez désactivé l\'authentification biométrique sur votre téléphone et ne pourrez pas enregistrer de portefeuilles dans l\'application. Pour enregistrer des portefeuilles, veuillez activer la fonction d\'authentification biométrique dans les paramètres de votre téléphone. Démarrer le processus de sauvegarde - Avec votre carte bancaire ou votre compte bancaire + Utilisez une carte bancaire ou d\'autres moyens de paiement %d carte %d cartes @@ -84,6 +84,7 @@ Vous n\'avez pas octroyé l\'accès à votre caméra, veuillez modifier vos paramètres de confidentialité Annuler Choisissez une action + Réclamer Réclamez des récompenses Fermer Continuer @@ -274,7 +275,7 @@ Aucun jeton trouvé. Veuillez essayer une autre demande ID : %s ID de transaction copié - Avec une autre devise dans votre portefeuille + Convertissez une de vos devises en une autre Les informations suivantes sont facultatives. Vous pouvez les effacer si vous ne souhaitez pas les partager. Dites-nous quelles fonctions vous manquent, et nous essaierons de vous aider. Veuillez nous dire quelle carte vous avez @@ -355,6 +356,7 @@ Impossible de charger les données… Aucune donnée Actions rapides + Rechercher sur le marché Résultat Voir les jetons de moins de 100 000 $ de capitalisation boursière Afficher les jetons @@ -385,24 +387,31 @@ Pression d\'achat Approvisionnement en circulation Le nombre total de pièces disponibles pour le trading et en circulation sur le marché + Approvisionnement en circulation Acheteurs expérimentés Acheteurs nets avec l\'exigence supplémentaire d\'avoir au moins 100 transactions sortantes + Acheteurs expérimentés Valorisation entièrement diluée La valeur théorique totale d\'une crypto-monnaie si toutes les pièces qui pourraient exister étaient en circulation, y compris celles qui ne circulent pas actuellement + Valorisation entièrement diluée Date de la Genesis Haut Détenteurs L\'évolution du nombre de détenteurs de jetons au cours d\'une période donnée + Détenteurs Idées Liens Liquidité Le changement dans la quantité de liquidité disponible pour le jeton pendant la période spécifiée + Liquidité Indice de liquidité Faible Cap. boursière La valeur d\'une crypto-monnaie est calculée en multipliant son prix par le nombre de pièces en circulation + Cap. boursière Évaluation du marché Position dans le classement des crypto-monnaies entre toutes les pièces en fonction de la capitalisation boursière + Évaluation du marché Approvisionnement maximal Métriques Official links @@ -412,8 +421,10 @@ Social Approvisionnement total Le nombre maximal de pièces ou de jetons pouvant exister pour une crypto-monnaie particulière + Approvisionnement total Volume des échanges (24h) Le montant total d\'une crypto-monnaie qui a été échangé au cours des dernières 24 heures, indiquant le niveau d\'activité et de liquidité du marché + Volume des échanges (24h) Tirez vers le haut ou appuyez sur la barre de recherche pour ajouter des jetons directement depuis le marché Ajouter des jetons NFC n\'est pas disponible sur votre appareil @@ -460,12 +471,12 @@ Sauvegarde en cours En savoir plus sur les seed phrases - + Vide Écrivez ces %d mots dans l\'ordre indiqué ci-dessous et conservez-les dans un endroit sûr et secret. Votre seed phrase - + Vide %d mots Pour importer votre portefeuille, entrez votre seed phrase dans le champ ci-dessous @@ -517,7 +528,7 @@ Accès à la caméra refusé %1$s (%2$s) sur le réseau %3$s Envoyez uniquement %s à cette adresse. L\'envoi de toute autre devise entraînera sa perte irréversible. - Affichez un code QR ou partagez votre adresse + Transférez des fonds depuis un autre portefeuille ou une autre bourse Participer Échec du chargement des informations sur le programme de parrainage. Veuillez réessayer plus tard. Échec du chargement des informations sur le programme de parrainage. Code d\'erreur : %s. Veuillez réessayer plus tard. @@ -634,6 +645,10 @@ Envoyer %s Vous envoyez **%1$s** incluant des frais de réseau de %2$s Vous envoyez **%1$s** et %2$s + Vous envoyez **%1$s** + les frais de réseau seront couverts en utilisant %1$s énergie + les frais de réseau seront réduits en utilisant %1$s énergie + y compris des frais de réseau de %1$s La transaction a été signée avec succès et envoyée au nœud de blockchain. Le solde du portefeuille sera mis à jour après un certain temps %1$s est un actif du réseau Tron. Pour calculer les frais et effectuer une transaction, déposez du Tron (TRX) sur votre compte. Transaction envoyée @@ -644,6 +659,8 @@ Le montant à staker doit être au moins %s Le montant du staking sera arrondi à %1$s TRX en raison des règles du réseau. Réclamation déstakée + Frais de staking du compte + Un compte de staking est un compte spécial où sont stockés les jetons SOL stakés. Il est créé lorsque vous déléguez vos jetons à un validateur pour participer à la validation des transactions et gagner des récompenses. Des frais minimes sont facturés pour la création du compte de staking, qui sont restitués une fois le staking terminé. Pourcentage de rendement annuel Le pourcentage de rendement annuel que vous pouvez gagner en participant au staking. APR @@ -671,14 +688,25 @@ Migrer Native staking Récomp. gagnées vous seront envoyées et dispo pour utilisation immédiate + Stakez et commencez à gagner vos récompenses + Stakez en toute sécurité et commencez à gagner des récompenses quotidiennes + Stakez en toute sécurité et commencez à gagner des récompenses horaires + Stakez en toute sécurité et commencez à gagner des récompenses mensuelles Le staking vous permet de gagner %1$s. Vos récompenses de staking arrivent tous les jours. Le staking vous permet de gagner %1$s. Vos récompenses de staking arrivent toutes les heures. Le staking vous permet de gagner %1$s. Vos récompenses de staking arrivent tous les mois. Le staking vous permet de gagner %1$s. Vos récompenses de staking arrivent toutes les semaines. + Stakez en toute sécurité et commencez à gagner des récompenses hebdomadaires Gagnez des récompenses de staking + L\'option de staking n\'est actuellement pas disponible en raison des conditions du réseau. Veuillez réessayer plus tard. + Le staking dans le réseau %1$s avec un nouveau validateur transférera automatiquement tous les fonds précédemment stakés vers ce validateur + Réinvestissez vos récompenses gagnées dans le montant que vous avez staké, augmentant ainsi vos gains potentiels. + Débloquez votre argent pour le retirer du processus de staking. Le déverrouillage prend %s. + Vos fonds seront disponibles à l\'utilisation après la période de déblocage de 21 jours. La récompense sera retirée en même temps que vos fonds de déblocage. Vos fonds seront disponibles pour utilisation après la période de désengagement %s. Vous pouvez désormais retirer vos fonds, ils seront disponibles à l\'utilisation immédiatement Le staking dans le réseau Tron avec un nouveau validateur transférera automatiquement tous les fonds précédemment stakés vers ce validateur + En cours de préparation Prêt à retirer Réassocier Restakez @@ -698,6 +726,7 @@ Récompenses Stake verrouillé Staker plus + Vous stakez %1$s et recevrez %2$s par an Appuyez pour déverrouiller Appuyez pour retirer Stake %s @@ -705,8 +734,10 @@ La transaction est en cours de traitement ! La validation est actuellement en cours dans la blockchain. Cela peut prendre quelques minutes. Dissociation Débloquer + Déverrouillage Non-staké Unstaking + En train de destaker les actifs %s Validateur Validateurs Voter @@ -846,6 +877,8 @@ Erreur d\'activation Selon les développeurs du réseau BNB, le support de la norme BEP-2\nprendra fin en juin 2024. Pour éviter de perdre des actifs avec cette norme, veuillez les convertir à la norme BEP-20. Utilisez notre service de d\'échange pour les transférer sur le réseau BNB Smart Chain. BNB Beacon Chain va s\'arrêter de fonctionner + Veuillez déposer environ %1$s pour couvrir les frais de réseau + Fonds insuffisants pour couvrir les frais de réseau Pas terrible J\'aime Ok, compris! diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index cff94ccc70..a46b4b69aa 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -383,24 +383,31 @@ 買い圧力 循環供給量 取引可能で市場に流通しているコインの総数 + 循環供給量 経験豊富な買い手 少なくとも100の発信取引を持つネット・バイヤー + 経験豊富な買い手 完全希薄化後評価額 現在流通していないものも含め、存在する可能性のあるすべてのコインが流通している場合の暗号資産の理論上の合計価値 + 完全希薄化後評価額 ジェネシスの日付 高い ホルダー 特定の期間内のトークンホルダー数の変化 + ホルダー インサイト リンク 流動性 指定された期間中のトークンに利用可能な流動性の変化 + 流動性 流動性指数 低い 時価総額 暗号資産の市場価値の合計。コインの現在の価格と、流通しているコインの総数を掛けて計算されます。 + 時価総額 市場格付け 時価総額に基づくすべてのコイン間の暗号資産評価における位置 + 市場格付け 最大供給量 指標 公式リンク @@ -410,8 +417,10 @@ ソーシャル 総供給量 特定の暗号資産に存在しうるコインまたはトークンの最大数 + 総供給量 取引量(24時間) 過去24時間以内に取引された暗号資産の合計額。市場の活発さと流動性を示します。 + 取引量(24時間) これをドラッグするか、検索窓をタップして、マーケットから直接トークンを追加します トークンを追加 お使いのデバイスではNFCが使用できません @@ -671,6 +680,7 @@ 移行 ネイティブステーキング 獲得した報酬はあなたのアドレスに直接送られ、すぐ使用可能です。 + 安全にステーキングして報酬を獲得しましょう 安全にステーキングして、報酬を毎日獲得しましょう 安全にステーキングして、報酬を毎時間獲得しましょう 安全にステーキングして、報酬を毎月獲得しましょう diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index fe50c2b14d..40de1c3f00 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -84,6 +84,7 @@ Перейти на %1$s Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. Отмена + Выберите действие Получить Вывести награду Закрыть @@ -276,7 +277,7 @@ Токены не найдены. Пожалуйста, попробуйте другой запрос ID: %s ID транзакции скопирован - Конвертируйте одну из ваших валют в другую + Обменяйте любой актив в вашем портфеле на этот токен Информация ниже не является обязательной. Вы можете стереть её, если хотите. Расскажите, каких функций вам не хватает, и мы постараемся вам помочь. Скажите, пожалуйста, какая у вас карта? @@ -344,7 +345,7 @@ Голосовать Выберите кошелек Кошелёк не поддерживает более одной сети - Чтобы купить, обменять или получить данный токен вам нужно добавить его к себе в портфель + Чтобы купить, обменять или получить данный токен, вам нужно добавить его к себе в портфель Этот актив недоступен Добавить в портфель Добавить @@ -387,27 +388,34 @@ Веб-сайт Покуп. предпоч. Разница между объемом покупателей и продавцов - Покуп. предпоч. + Покупательское предпочтение Циркулир. предл. Общее количество монет, доступных для торговли и находящихся в обращении на рынке + Циркулирующее предложение Опытные трейдеры Покупатели, у которых было как минимум 100 исходящих транзакций. + Опытные трейдеры Полн. разб. кап. Общая теоретическая стоимость криптовалюты, если все монеты, которые могут существовать, находятся в обращении, включая те, которые в настоящее время не обращаются + Полностью разбавленная капитализация Дата создания Высокий Держатели Изменение количества держателей токенов в течение выбранного периода времени + Держатели Инсайты Ссылки Ликвидность Изменение объема ликвидности, доступной для токена в течение указанного периода времени. + Ликвидность Индекс ликвидности Низкий Рын. кап. Общая рыночная стоимость криптовалюты, рассчитываемая путем умножения текущей цены монеты на общее количество монет в обращении. + Рыночная капитализация Рейтинг Позиция в рейтинге криптовалют среди всех монет на основе рыночной капитализации. + Рейтинг Максимальный объем Метрики Официальные ссылки @@ -415,10 +423,12 @@ Репозиторий Оценка безопасности Социальные - Общее предложение + Общ. предл. Максимальное количество монет или токенов, которое может когда-либо существовать для выбранной криптовалюты. + Общее предложение Объем торгов (24ч) Общая сумма криптовалюты, которая была продана за последние 24 часа, показывающая уровень активности и ликвидности на рынке. + Объем торгов (24ч) Потяните вверх или коснитесь поисковой строки, чтобы добавить токены напрямую из маркета Добавить токены Функция NFC недоступна на вашем устройстве @@ -647,6 +657,8 @@ Вы отправляете **%1$s**, включая комиссию сети %2$s Вы отправляете **%1$s** и %2$s Вы отправляете **%1$s** + Комиссия сети будет покрыта за счет использования %1$s энергии + Комиссия сети будет снижена за счет использования %1$s энергии включая комиссию сети в размере %1$s Транзакция успешно подписана и отправлена в блокчейн. Баланс будет обновлен через некоторое время %1$s — это монета в сети Tron. Чтобы рассчитать комиссию и совершить транзакцию, вам необходимо внести немного Tron (TRX) на свой адрес. @@ -658,10 +670,12 @@ Сумма для стейкинга должна быть не менее %s Согласно правилам сети, сумма стейкинга будет округлена до %1$sTRX. Забрать средства + Комиссия за стейкинг аккаунт Стейкинг аккаунт — это специальный счет, на котором хранятся застейканные токены SOL. Он создается при делегировании ваших токенов валидатору для участия в подтверждении транзакций и получении наград. За создание стейкинг аккаунта взимается небольшая комиссия, которая возвращается после завершения стейкинга. Процентная ставка Годовой процентный доход, который вы можете получить от участия в стейкинге. APR + Награда автоматически аккумулируется на вашем стейкинг балансе. Доступно Средння ставка вознаграждения Что такое Стейкинг? @@ -685,14 +699,26 @@ Переместить Нативный стейкинг Полученная награда отправится на ваш кошелек и сразу станет доступна для использования + Стейкайте безопасно и начинайте получать ваши награды. + Стейкайте безопасно и начинайте получать ежедневные награды. + Стейкайте безопасно и начинайте получать награды каждый час. + Стейкайте безопасно и начинайте получать ежемесячные награды. Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждый день. Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждый час. Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждый месяц. Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждую неделю. + Стейкайте безопасно и начинайте получать еженедельные награды. Получите награду за стейкинг + Стейкинг временно недоступен из-за проблем в сети. Пожалуйста, попробуйте позже. + Стейкинг в сети %1$s с новым валидатором автоматически переведет ваши текущие застейканные средства на него. + Реинвестируйте свои заработанные награды в вашу застейканную сумму, увеличивая потенциальный доход Разблокируйте свои средства, чтобы вывести их из стейкинга. Разблокировка займёт %s. + Ваши средства будут доступны для использования после 21-дневного периода отзыва. Награда будет выведена вместе с вашими выводими средствами. Ваши средства будут доступны после %s периода отзыва. Вы можете вывести свои средства из стейкинга, они будут доступны для использования незамедлительно. + Стейкинг в сети Tron с новым валидатором автоматически переведет ваши текущие застейканные средства на него. + Подготовка + Доступно для вывода Сменить валидатора Застейкать вознаграждения Отозвать @@ -702,7 +728,6 @@ Блок День Каждый день - Каждую минуту Эпоха Эра Час @@ -711,12 +736,15 @@ Вознаграждения Стейкинг закрыт Застейкать еще + Вы стейкаете %1$s и будете получать %2$s ежегодно Нажмите для разблокировки Нажмите для вывода Застейкать %s Вывести %s + Транзакция обрабатывается! В настоящее время идет проверка в блокчейне. Это может занять несколько минут. Отзыв Разблокировать + Разблокировка Вывод из стейкинга Завершение стейкинга Завершение стейкинга %s @@ -725,6 +753,7 @@ Проголосовать Голосование заблокировано Вывод + Застейканные средства Держите свои криптосбережения в безопасности. Приватные ключи надежно хранятся на карте. Революционный аппаратный кошелек До трех карт с одним кошельком 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 c032f68459..15a063ac84 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -39,7 +39,7 @@ Забагато спроб Ви вимкнули біометричну автентифікацію на своєму телефоні і не зможете зберігати гаманці в додатку. Щоб зберегти гаманці, будь ласка, увімкніть функцію біометричної автентифікації в налаштуваннях телефону. Почніть процес резервного копіювання - За допомогою банківської картки або банківського рахунку + Використовуйте банківську картку або інші способи оплати %d картка %d картки @@ -145,7 +145,7 @@ Перейменувати Зберегти Зберегти зміни - Шукати + Пошук Пошук токенів Seed-фраза Оберіть дію @@ -279,7 +279,7 @@ Токенів не знайдено. Будь ласка, спробуйте інший запит ID: %s ID транзакції скопійовано - З іншої валюти у вашому гаманці + Конвертуйте одну з ваших валют в іншу Інформація нижче не є обов\'язковою. Ви можете стерти її, якщо бажаєте. Розкажіть, яких функцій вам не вистачає, і ми спробуємо вам допомогти. Розкажіть, будь ласка, яку картку ви маєте? @@ -362,6 +362,7 @@ Не вдалося завантажити дані... Немає даних Швидкі дії + Шукати на маркеті Результат Переглянути токени до 100к USD ринкової капіталізації Показати токени @@ -394,24 +395,29 @@ Давлення покупця Циркуляційний запас Загальна кількість монет, які доступні для торгівлі та перебувають в обігу на ринку + Циркуляційний запас Досвідчені покупці Мережеві покупці з додатковою вимогою мати не менше 100 вихідних транзакцій + Досвідчені покупці Повн. розведена кап. Загальна теоретична вартість криптовалюти, якщо всі монети, які могли б існувати, перебувають в обігу, включаючи ті, що не перебувають в обігу в даний час Дата створення Високий Тримачі Зміна кількості власників токенів протягом певного періоду часу + Тримачі Інсайти Посилання Ліквідність Зміна того, скільки ліквідності доступно для токена протягом зазначеного періоду часу + Ліквідність Індекс ліквідності Низький Рин. кап. Загальна ринкова вартість криптовалюти, що розраховується шляхом множення поточної ціни монети на загальну кількість монет в обігу Рейтинг Позиція в крипторейтингу між усіма монетами на основі ринкової капіталізації + Рейтинг Максимальна пропозиція Метрики Офіційні посилання @@ -419,10 +425,12 @@ Репозиторій Оцінка безпеки Соцмережі - Загальна пропозиція + Загальна проп. Максимальна кількість монет або токенів, яка може коли-небудь існувати для певної криптовалюти + Загальна пропозиція Обсяг торгів (24г) Загальна сума криптовалюти, якою торгували протягом останніх 24 годин, що вказує на рівень активності та ліквідності на ринку + Обсяг торгів (24г) Потягніть вгору або торкніться панелі пошуку, щоб додати токени безпосередньо з маркету Додати токени Функція NFC недоступна на вашому пристрої @@ -530,7 +538,7 @@ Доступ до камери заборонено %1$s (%2$s) у мережі %3$s Надсилайте лише %s на цю адресу. Надсилання будь-якої іншої валюти призведе до її незворотної втрати. - Покажіть QR-код або поділіться своєю адресою + Перекажіть кошти з іншого гаманця або біржі Взяти участь Не вдалося завантажити інформацію по реферальній програмі. Будь ласка, спробуйте пізніше. Не вдалося завантажити інформацію по реферальній програмі. Код помилки: %s. Будь ласка, спробуйте пізніше. @@ -665,6 +673,8 @@ Сума для стейкінгу має бути не менше %s Сума стейкінгу буде округлена до %1$s TRX відповідно до правил мережі. Зняти кошти + Комісія за стейкінг-акаунт + Стейкінг-акаунт - це спеціальний рахунок, на якому зберігаються застейкані SOL токени. Він створюється, коли ви делегуєте свої токени валідатору для участі у перевірці транзакцій та отримання винагород. За створення стейкінг-акаунту стягується невелика комісія, яка повертається після завершення стейкінгу. Процентна ставка Річний відсоток, який ви можете отримати, беручи участь у стейкінгу. APR @@ -692,6 +702,7 @@ Перемістити Нативний стейкінг Зароблені винагороди будуть надіслані на ваш гаманець і доступні для використання відразу + Стейкайте безпечно та почніть отримувати ваші винагороди Стейкайте безпечно та почніть отримувати винагороди щоденно Стейкайте безпечно та почніть отримувати винагороди щогодини Стейкайте безпечно та почніть отримувати винагороди щомісяця diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index e95c0bc2b7..93fba71bd2 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -103,7 +103,7 @@ Enable Enabled Error - Exchange + Swap Explore Explore transaction history Explorer @@ -275,7 +275,7 @@ No tokens found. Please try another request ID: %s Transaction ID copied - Convert one of your currencies to another + Swap any asset in your portfolio for this token The following information is optional. You can erase it if you don\'t want to share it. Tell us what functions you are missing, and we will try to help you. Please tell us what card do you have @@ -387,24 +387,31 @@ Buy pressure Circulating supply The total number of coins that are available for trading and are circulating in the market + Circulating supply Experienced buyers Net buyers with the additional requirement of having at least 100 outgoing transactions + Experienced buyers Fully diluted valuation The total theoretical value of a cryptocurrency if all coins that could exist are in circulation, including those not currently circulating + Fully diluted valuation Genesis date High Holders The change in the number of token holders within a specific timeframe + Holders Insights Links Liquidity The change in how much liquidity is available for the token during the specified timeframe + Liquidity Liquidity index Low Market cap The total market value of a cryptocurrency, calculated by multiplying the current price of the coin by the total number of coins in circulation + Market cap Market rating Position in crypto rating between all coins based on market capitalization + Market rating Max supply Metrics Official links @@ -414,8 +421,10 @@ Social Total supply The maximum number of coins or tokens that can ever exist for a particular cryptocurrency + Total supply Trading volume (24h) The total amount of a cryptocurrency that has been traded within the last 24 hours, indicating the level of activity and liquidity in the market + Trading volume (24h) Pull this up or tap the search bar to add tokens directly from the market Add tokens NFC is not available on your device @@ -689,6 +698,7 @@ Staking allows you to earn %1$s. Your staking rewards arrive every week. Stake securely and start earning weekly rewards Earn staking rewards + Staking is currently unavailable due to network conditions. Please try again later. Staking in the %1$s network with a new validator will automatically transfer all previously staked funds to this validator Reinvests your earned rewards in your staked amount, increasing potential earnings. Unlock your money to withdraw it from staking process. Unlocking takes %s. @@ -707,8 +717,7 @@ Manual Block Day - Each day - Each minute + Daily Epoch Era Hour diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt index 3033f2221a..0dad99ad18 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButton.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.tangem.core.ui.components.ResizableText import com.tangem.core.ui.res.TangemTheme @@ -85,7 +86,9 @@ fun TangemButton( }, icon = { iconResId -> Icon( - modifier = Modifier.buttonContentSize(maxContentSize), + modifier = Modifier + .buttonContentSize(maxContentSize) + .padding(vertical = 2.dp), painter = painterResource(id = iconResId), tint = colors.contentColor(enabled = enabled).value, contentDescription = null, diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt index bfae5cb313..961bca8869 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/buttons/common/TangemButtonSize.kt @@ -33,9 +33,8 @@ internal fun TangemButtonSize.toHeightDp(): Dp = when (this) { @Composable @ReadOnlyComposable internal fun TangemButtonSize.toShape(): Shape = when (this) { - TangemButtonSize.Default, - TangemButtonSize.WideAction, - -> TangemTheme.shapes.roundedCornersMedium + TangemButtonSize.Default -> TangemTheme.shapes.roundedCornersXMedium + TangemButtonSize.WideAction -> TangemTheme.shapes.roundedCornersMedium TangemButtonSize.Text -> TangemTheme.shapes.roundedCornersSmall TangemButtonSize.Selector -> TangemTheme.shapes.roundedCornersSmall TangemButtonSize.Action -> TangemTheme.shapes.roundedCornersMedium @@ -64,8 +63,8 @@ internal fun TangemButtonSize.toContentPadding(icon: TangemButtonIconPosition): return when (this) { TangemButtonSize.Default -> PaddingValues( - top = TangemTheme.dimens.spacing14, - bottom = TangemTheme.dimens.spacing14, + top = TangemTheme.dimens.spacing12, + bottom = TangemTheme.dimens.spacing12, start = horizontalPadding.first, end = horizontalPadding.second, ) diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt index 6db1972160..bd65f92ab5 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageBase.kt @@ -24,6 +24,7 @@ import com.tangem.core.ui.components.inputrow.inner.InputRowAsyncImage import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.resolveAnnotatedReference import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.res.TangemColorPalette import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview @@ -38,10 +39,12 @@ import com.tangem.core.ui.res.TangemThemePreview * @param subtitleColor subtitle text color * @param captionColor caption text color * @param isGrayscaleImage whether to display grayscale image + * @param subtitleEndIconRes icon to show after subtitle * @param iconEndRes icon to end of row * @param onImageError composable to show if image loading failed * @param extraContent extra content */ +@Suppress("LongMethod") @Composable internal fun InputRowImageBase( subtitle: TextReference, @@ -53,7 +56,9 @@ internal fun InputRowImageBase( captionColor: Color = TangemTheme.colors.text.tertiary, iconTint: Color = TangemTheme.colors.icon.informative, isGrayscaleImage: Boolean = false, - iconEndRes: Int? = null, + @DrawableRes subtitleEndIconRes: Int? = null, + subtitleEndIconTint: Color = TangemColorPalette.Azure, + @DrawableRes iconEndRes: Int? = null, onImageError: (@Composable () -> Unit)? = null, extraContent: (@Composable RowScope.() -> Unit)? = null, ) { @@ -89,11 +94,17 @@ internal fun InputRowImageBase( SpacerW12() } Column { - Text( - text = subtitle.resolveReference(), - style = TangemTheme.typography.subtitle2, - color = subtitleColor, - ) + Row { + Text( + text = subtitle.resolveReference(), + style = TangemTheme.typography.subtitle2, + color = subtitleColor, + ) + SubtitleEndIconRes( + subtitleEndIconRes = subtitleEndIconRes, + subtitleEndIconTint = subtitleEndIconTint, + ) + } if (caption != null) { Text( text = caption.resolveAnnotatedReference(), @@ -113,11 +124,28 @@ internal fun InputRowImageBase( } } +@Composable +private fun RowScope.SubtitleEndIconRes(subtitleEndIconRes: Int?, subtitleEndIconTint: Color) { + AnimatedVisibility( + visible = subtitleEndIconRes != null, + label = "Subtitle end icon visibility animation", + modifier = Modifier.align(Alignment.CenterVertically), + ) { + val icon = remember(this) { requireNotNull(subtitleEndIconRes) } + Icon( + painter = rememberVectorPainter(image = ImageVector.vectorResource(id = icon)), + tint = subtitleEndIconTint, + contentDescription = null, + modifier = Modifier.padding(start = TangemTheme.dimens.spacing4), + ) + } +} + @Composable private fun RowScope.InputRowEndIcon(iconRes: Int?) { AnimatedVisibility( visible = iconRes != null, - label = "Icon visibility animation", + label = "End icon visibility animation", ) { val icon = remember(this) { requireNotNull(iconRes) } Icon( diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt index 2905e007b7..366bd4e48d 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/inputrow/InputRowImageInfo.kt @@ -34,6 +34,7 @@ import com.tangem.core.ui.res.TangemThemePreview * @param subtitleColor subtitle text color * @param captionColor caption text color * @param isGrayscaleImage whether to display grayscale image + * @param showPendingIcon whether to show pending icon * @param iconEndRes icon to end of row * @param onImageError composable to show if image loading failed */ @@ -52,6 +53,7 @@ fun InputRowImageInfo( captionColor: Color = TangemTheme.colors.text.tertiary, iconTint: Color = TangemTheme.colors.icon.informative, isGrayscaleImage: Boolean = false, + @DrawableRes subtitleEndIconRes: Int? = null, @DrawableRes iconEndRes: Int? = null, onImageError: (@Composable () -> Unit)? = null, ) { @@ -76,6 +78,7 @@ fun InputRowImageInfo( subtitleColor = subtitleColor, captionColor = captionColor, isGrayscaleImage = isGrayscaleImage, + subtitleEndIconRes = subtitleEndIconRes, iconEndRes = iconEndRes, onImageError = onImageError, ) { @@ -132,6 +135,7 @@ private fun InputRowImageInfo_Preview( infoTitle = data.infoTitle, infoSubtitle = data.infoSubtitle, imageUrl = "", + subtitleEndIconRes = data.subtitleEndIconRes, iconEndRes = R.drawable.ic_chevron_right_24, ) } @@ -162,6 +166,22 @@ private class InputRowImageInfoPreviewDataProvider : PreviewParameterProvider + + + + diff --git a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt index 79576d75c3..20ca3a308c 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/DefaultMarketsTokenRepository.kt @@ -103,6 +103,7 @@ internal class DefaultMarketsTokenRepository( val tokenMarketsUpdateFetcher = MarketsBatchUpdateFetcher( tangemTechApi = tangemTechApi, marketsApi = marketsApi, + analyticsEventHandler = analyticsEventHandler, onApiError = { analyticsEventHandler.send(MarketsDataAnalyticsEvent.List.Error.toEvent()) }, @@ -141,7 +142,19 @@ internal class DefaultMarketsTokenRepository( response.getOrThrow() } - return TokenChartConverter.convert(interval, result) + return TokenChartConverter.convert( + interval = interval, + value = result, + + // === Analytics === + onNullPresented = { + analyticsEventHandler.send( + MarketsDataAnalyticsEvent.ChartNullValuesError( + requestPath = "coins/history", + ), + ) + }, + ) } override suspend fun getChartPreview( @@ -163,7 +176,19 @@ internal class DefaultMarketsTokenRepository( ) } - return TokenChartConverter.convert(interval, chart) + return TokenChartConverter.convert( + interval = interval, + value = chart, + + // === Analytics === + onNullPresented = { + analyticsEventHandler.send( + MarketsDataAnalyticsEvent.ChartNullValuesError( + requestPath = "coins/history_preview", + ), + ) + }, + ) } override suspend fun getTokenInfo( diff --git a/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt b/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt index 18c7155b03..f1a92028fb 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/MarketsBatchUpdateFetcher.kt @@ -1,6 +1,8 @@ package com.tangem.data.markets +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.data.common.utils.retryOnError +import com.tangem.data.markets.analytics.MarketsDataAnalyticsEvent import com.tangem.data.markets.converters.TokenMarketChartsConverter import com.tangem.data.markets.converters.TokenQuotesShortConverter import com.tangem.data.markets.converters.toRequestParam @@ -22,6 +24,7 @@ import kotlinx.coroutines.launch internal class MarketsBatchUpdateFetcher( private val marketsApi: TangemTechMarketsApi, private val tangemTechApi: TangemTechApi, + private val analyticsEventHandler: AnalyticsEventHandler, private val onApiError: () -> Unit, ) : BatchUpdateFetcher, TokenMarketUpdateRequest> { @@ -52,6 +55,7 @@ internal class MarketsBatchUpdateFetcher( updateTasks.forEachIndexed { index, deferred -> launch { val res = deferred.await() + checkForNulls(res) val batchToUpdate = toUpdate[index] update { @@ -113,6 +117,22 @@ internal class MarketsBatchUpdateFetcher( ) } + private fun checkForNulls(response: TokenMarketChartListResponse) { + response.values.forEach { chart -> + chart.prices.forEach { (_, price) -> + if (price == null) { + analyticsEventHandler.send( + MarketsDataAnalyticsEvent.ChartNullValuesError( + requestPath = "coins/history_preview", + ), + ) + + return + } + } + } + } + private inline fun catchApiError(onError: () -> Unit, block: () -> T): T { return try { block() diff --git a/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt b/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt index b58f04cb33..1c131a3cd4 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/analytics/MarketsDataAnalyticsEvent.kt @@ -36,7 +36,20 @@ sealed interface MarketsDataAnalyticsEvent { } fun toEvent(): AnalyticsEvent = when (this) { + is ChartNullValuesError -> this is List -> this is Details -> this } + + data class ChartNullValuesError( + val requestPath: String, + ) : AnalyticsEvent( + category = "Markets / Chart", + event = "Data Error", + params = mapOf("Request path" to requestPath), + error = IllegalStateException( + "Chart data contains null values from the API", + ), + ), + MarketsDataAnalyticsEvent } \ No newline at end of file diff --git a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenChartConverter.kt b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenChartConverter.kt index 744e6fef48..a036ed9768 100644 --- a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenChartConverter.kt +++ b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenChartConverter.kt @@ -6,11 +6,21 @@ import com.tangem.domain.markets.TokenChart internal object TokenChartConverter { - fun convert(interval: PriceChangeInterval, value: TokenMarketChartResponse): TokenChart { + fun convert( + interval: PriceChangeInterval, + value: TokenMarketChartResponse, + onNullPresented: () -> Unit = {}, + ): TokenChart { + val points = value.prices.mapNotNull { p -> p.value?.let { p.key to it } }.toMap() + + if (points.size < points.values.size) { + onNullPresented() + } + return TokenChart( interval = interval, - priceY = value.prices.values.toList(), - timeStamps = value.prices.keys.toList(), + priceY = points.values.toList(), + timeStamps = points.keys.toList(), ) } } \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt new file mode 100644 index 0000000000..0b637744ab --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/DefaultStakingPendingTransactionRepository.kt @@ -0,0 +1,27 @@ +package com.tangem.data.staking + +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.stakekit.BalanceItem +import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository +import java.util.concurrent.CopyOnWriteArrayList + +internal class DefaultStakingPendingTransactionRepository : StakingPendingTransactionRepository { + + private val pendingTransactions = CopyOnWriteArrayList() + + override fun saveTransaction(transaction: PendingTransaction) { + pendingTransactions.add(transaction) + } + + override fun removeTransactions(transactions: Set) { + pendingTransactions.removeAll(transactions) + } + + override fun getTransactionsWithBalanceItems(): List> { + return pendingTransactions.mapNotNull { pendingTransaction -> + PendingTransactionItemConverter.convert(pendingTransaction)?.let { balanceItem -> + pendingTransaction to balanceItem + } + } + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt new file mode 100644 index 0000000000..a18b61f591 --- /dev/null +++ b/data/staking/src/main/java/com/tangem/data/staking/PendingTransactionItemConverter.kt @@ -0,0 +1,22 @@ +package com.tangem.data.staking + +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.stakekit.BalanceItem +import com.tangem.utils.converter.Converter +import org.joda.time.DateTime + +internal object PendingTransactionItemConverter : Converter { + + override fun convert(value: PendingTransaction): BalanceItem? { + return BalanceItem( + groupId = value.groupId ?: return null, + type = value.type ?: return null, + amount = value.amount ?: return null, + rawCurrencyId = value.rawCurrencyId, + validatorAddress = value.validator?.address, + date = DateTime.now(), + pendingActions = emptyList(), + isPending = true, + ) + } +} \ No newline at end of file diff --git a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt index 90b35354b7..5a7f56b25e 100644 --- a/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt +++ b/data/staking/src/main/java/com/tangem/data/staking/converters/YieldBalanceConverter.kt @@ -19,12 +19,10 @@ internal class YieldBalanceConverter : Converter BalanceItem( - id = item.groupId, + groupId = item.groupId, type = BalanceType.valueOf(item.type.name), amount = item.amount, - pricePerShare = item.pricePerShare, rawCurrencyId = item.tokenDTO.coinGeckoId, - rawNetworkId = item.tokenDTO.network.name, // tron-specific. operates validatorAddresses instead of validatorAddress validatorAddress = item.validatorAddress ?: item.validatorAddresses?.get(0), date = item.date?.toDateTime(), @@ -32,6 +30,7 @@ internal class YieldBalanceConverter : Converter, + val isPending: Boolean, ) data class PendingAction( diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/GetPendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/GetPendingTransactionsUseCase.kt new file mode 100644 index 0000000000..1f4be9dea2 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/GetPendingTransactionsUseCase.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.model.stakekit.BalanceItem +import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository + +/** + * Use case for getting saved staking pending transactions. + */ +class GetPendingTransactionsUseCase( + private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, + private val stakingErrorResolver: StakingErrorResolver, +) { + + operator fun invoke(): Either> { + return Either.catch { + stakingPendingTransactionRepository.getTransactionsWithBalanceItems().map { it.second } + }.mapLeft { + stakingErrorResolver.resolve(it) + } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt new file mode 100644 index 0000000000..daddce0ad9 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/InvalidatePendingTransactionsUseCase.kt @@ -0,0 +1,54 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.stakekit.BalanceItem +import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.model.stakekit.YieldBalance +import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository + +class InvalidatePendingTransactionsUseCase( + private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, + private val stakingErrorResolver: StakingErrorResolver, +) { + + operator fun invoke(yieldBalance: YieldBalance): Either> { + return Either.catch { + if (yieldBalance is YieldBalance.Data) { + val (balancesToDisplay, transactionsToRemove) = mergeRealAndPendingTransactions( + real = yieldBalance.balance.items, + pending = stakingPendingTransactionRepository.getTransactionsWithBalanceItems(), + ) + + stakingPendingTransactionRepository.removeTransactions(transactionsToRemove.toSet()) + + balancesToDisplay + } else { + emptyList() + } + }.mapLeft { + stakingErrorResolver.resolve(it) + } + } + + private fun mergeRealAndPendingTransactions( + real: List, + pending: List>, + ): Pair, List> { + val map = real.associateBy { Triple(it.groupId, it.type, it.amount) }.toMutableMap() + + val toRemove = mutableListOf() + + pending.forEach { (pendingTransaction, balanceItem) -> + val key = Triple(balanceItem.groupId, balanceItem.type, balanceItem.amount) + if (map.containsKey(key)) { + map[key] = balanceItem + } else { + toRemove.add(pendingTransaction) + } + } + + return map.values.toList() to toRemove + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt new file mode 100644 index 0000000000..64ce855ae5 --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/SavePendingTransactionUseCase.kt @@ -0,0 +1,24 @@ +package com.tangem.domain.staking + +import arrow.core.Either +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.stakekit.StakingError +import com.tangem.domain.staking.repositories.StakingErrorResolver +import com.tangem.domain.staking.repositories.StakingPendingTransactionRepository + +/** + * Use case for saving hash that failed to submit during staking confirmation + */ +class SavePendingTransactionUseCase( + private val stakingPendingTransactionRepository: StakingPendingTransactionRepository, + private val stakingErrorResolver: StakingErrorResolver, +) { + + operator fun invoke(pendingTransaction: PendingTransaction): Either { + return Either.catch { + stakingPendingTransactionRepository.saveTransaction(pendingTransaction) + }.mapLeft { + stakingErrorResolver.resolve(it) + } + } +} \ No newline at end of file diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt b/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt index 83d1b151b7..fba8daa29b 100644 --- a/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt +++ b/domain/staking/src/main/java/com/tangem/domain/staking/SubmitHashUseCase.kt @@ -1,6 +1,7 @@ package com.tangem.domain.staking import arrow.core.Either +import com.tangem.domain.staking.model.SubmitHashData import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.repositories.StakingErrorResolver import com.tangem.domain.staking.repositories.StakingTransactionHashRepository @@ -13,12 +14,12 @@ class SubmitHashUseCase( private val stakingErrorResolver: StakingErrorResolver, ) { - suspend fun submitHash(transactionId: String, transactionHash: String): Either { + suspend operator fun invoke(submitHashData: SubmitHashData): Either { return Either .catch { stakingTransactionHashRepository.submitHash( - transactionId = transactionId, - transactionHash = transactionHash, + transactionId = submitHashData.transactionId, + transactionHash = submitHashData.transactionHash, ) }.mapLeft { stakingErrorResolver.resolve(it) diff --git a/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt new file mode 100644 index 0000000000..02bb96806b --- /dev/null +++ b/domain/staking/src/main/java/com/tangem/domain/staking/repositories/StakingPendingTransactionRepository.kt @@ -0,0 +1,13 @@ +package com.tangem.domain.staking.repositories + +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.stakekit.BalanceItem + +interface StakingPendingTransactionRepository { + + fun getTransactionsWithBalanceItems(): List> + + fun saveTransaction(transaction: PendingTransaction) + + fun removeTransactions(transactions: Set) +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt index c8990cebf4..abdf3f94fd 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrencyStatusOperations.kt @@ -61,7 +61,7 @@ internal class CurrencyStatusOperations( (yieldBalance as? YieldBalance.Data)?.address == status.address.defaultAddress.value val currentYieldBalance = yieldBalance.takeIf { isCurrentAddressStaking } return when { - ignoreQuote || quote == null -> CryptoCurrencyStatus.NoQuote( + ignoreQuote -> CryptoCurrencyStatus.NoQuote( amount = amount, hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, @@ -70,14 +70,15 @@ internal class CurrencyStatusOperations( ) currency is CryptoCurrency.Token && currency.isCustom -> CryptoCurrencyStatus.Custom( amount = amount, - fiatAmount = calculateFiatAmountOrNull(amount, quote.fiatRate), - fiatRate = quote.fiatRate, - priceChange = quote.priceChange, + fiatAmount = calculateFiatAmountOrNull(amount, quote?.fiatRate), + fiatRate = quote?.fiatRate, + priceChange = quote?.priceChange, hasCurrentNetworkTransactions = hasCurrentNetworkTransactions, pendingTransactions = currentTransactions, networkAddress = status.address, yieldBalance = currentYieldBalance, ) + quote == null -> CryptoCurrencyStatus.Loading else -> CryptoCurrencyStatus.Loaded( amount = amount, fiatAmount = calculateFiatAmount(amount, quote.fiatRate), diff --git a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt index 884ee4f78a..9903c588e9 100644 --- a/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt +++ b/features/details/impl/src/main/kotlin/com/tangem/features/details/model/DetailsModel.kt @@ -61,6 +61,9 @@ internal class DetailsModel @Inject constructor( ) init { + // Use to save compatibility with screens that using Redux states + bootstrapScreenState() + items .onEach(::updateState) .launchIn(modelScope) @@ -82,6 +85,10 @@ internal class DetailsModel @Inject constructor( ) } + private fun bootstrapScreenState() { + appStateHolder.dispatch(LegacyAction.PrepareDetailsScreen) + } + private fun sendFeedback() { modelScope.launch { val scanResponse = getSelectedWalletSyncUseCase().getOrNull()?.scanResponse diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt index adb9f89f72..fb93bd86a2 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/InsightsConverter.kt @@ -77,7 +77,7 @@ internal class InsightsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_experienced_buyers), + title = resourceReference(R.string.markets_token_details_experienced_buyers_full), body = resourceReference(R.string.markets_token_details_experienced_buyers_description), ), ) @@ -92,7 +92,7 @@ internal class InsightsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_buy_pressure), + title = resourceReference(R.string.markets_token_details_buy_pressure_full), body = resourceReference(R.string.markets_token_details_buy_pressure_description), ), ) @@ -107,7 +107,7 @@ internal class InsightsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_holders), + title = resourceReference(R.string.markets_token_details_holders_full), body = resourceReference(R.string.markets_token_details_holders_description), ), ) @@ -122,7 +122,7 @@ internal class InsightsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_liquidity), + title = resourceReference(R.string.markets_token_details_liquidity_full), body = resourceReference(R.string.markets_token_details_liquidity_description), ), ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt index 5cd11e6cd3..3501ff40f5 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt @@ -32,7 +32,9 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_market_capitalization), + title = resourceReference( + R.string.markets_token_details_market_capitalization_full, + ), body = resourceReference( R.string.markets_token_details_market_capitalization_description, ), @@ -46,7 +48,7 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_market_rating), + title = resourceReference(R.string.markets_token_details_market_rating_full), body = resourceReference(R.string.markets_token_details_market_rating_description), ), ) @@ -58,7 +60,7 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_trading_volume), + title = resourceReference(R.string.markets_token_details_trading_volume_full), body = resourceReference( R.string.markets_token_details_trading_volume_24h_description, ), @@ -72,7 +74,9 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_fully_diluted_valuation), + title = resourceReference( + R.string.markets_token_details_fully_diluted_valuation_full, + ), body = resourceReference( R.string.markets_token_details_fully_diluted_valuation_description, ), @@ -86,7 +90,7 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_circulating_supply), + title = resourceReference(R.string.markets_token_details_circulating_supply_full), body = resourceReference( R.string.markets_token_details_circulating_supply_description, ), @@ -100,7 +104,7 @@ internal class MetricsConverter( onInfoClick = { onInfoClick( InfoBottomSheetContent( - title = resourceReference(R.string.markets_token_details_total_supply), + title = resourceReference(R.string.markets_token_details_total_supply_full), body = resourceReference(R.string.markets_token_details_total_supply_description), ), ) diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt index aca7dd32ae..987689d400 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/Description.kt @@ -1,8 +1,8 @@ package com.tangem.features.markets.details.impl.ui.components +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.text.ClickableText import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier @@ -38,17 +38,16 @@ internal fun Description( } } - ClickableText( - modifier = modifier, + Text( + modifier = modifier + .clickable( + interactionSource = null, + indication = null, + onClick = onReadMoreClick, + ), text = text, style = TangemTheme.typography.body2, - ) { - text.spanStyles.getOrNull(1)?.let { spanStyle -> - if (it in spanStyle.start..spanStyle.end) { - onReadMoreClick() - } - } - } + ) } else { Text( modifier = modifier, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt index f0d31afa7b..594d0f81df 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt @@ -25,7 +25,6 @@ internal fun InfoBottomSheet(config: TangemBottomSheetConfig) { TangemBottomSheet( config = config, - skipPartiallyExpanded = false, addBottomInsets = false, title = { TangemBottomSheetTitle(title = it.title) }, content = { diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt index 3ce401a818..e0bd92cbdf 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt @@ -20,7 +20,7 @@ import kotlinx.collections.immutable.toImmutableList * @property onAddToPortfolioVisibilityChange callback is invoked when add to portfolio visibility is changed * @property onWalletSelectorVisibilityChange callback is invoked when wallet selector visibility is changed * @property onNetworkSwitchClick callback is invoked when network switch is clicked - * @property onWalletSelect callback is invoked when wallet is selected + * @property onAnotherWalletSelect callback is invoked when wallet is selected * @property onContinueClick callback is invoked when continue button is clicked * [REDACTED_AUTHOR] @@ -30,7 +30,7 @@ internal class AddToPortfolioBSContentUMFactory( private val onAddToPortfolioVisibilityChange: (Boolean) -> Unit, private val onWalletSelectorVisibilityChange: (Boolean) -> Unit, private val onNetworkSwitchClick: (BlockchainRowUM, Boolean) -> Unit, - private val onWalletSelect: (UserWalletId) -> Unit, + private val onAnotherWalletSelect: (UserWalletId) -> Unit, private val onContinueClick: (selectedWalletId: UserWalletId, addedNetworks: Set) -> Unit, ) { @@ -62,7 +62,7 @@ internal class AddToPortfolioBSContentUMFactory( onNetworkSwitchClick = onNetworkSwitchClick, ).convert(value = token), isScanCardNotificationVisible = portfolioUIData.hasMissedDerivations, - continueButtonEnabled = portfolioUIData.addToPortfolioData.isUserChangedNetworks( + continueButtonEnabled = portfolioUIData.addToPortfolioData.isUserAddedNetworks( userWalletId = selectedWallet.walletId, ), onContinueButtonClick = { @@ -112,7 +112,12 @@ internal class AddToPortfolioBSContentUMFactory( val balance = portfolioData.walletsWithBalance[userWallet.walletId] UserWalletItemUMConverter( - onClick = onWalletSelect, + onClick = { + if (it != selectedWalletId) { + onAnotherWalletSelect(it) + onWalletSelectorVisibilityChange(false) + } + }, appCurrency = portfolioData.appCurrency, balance = balance?.getOrNull(), isLoading = balance?.isLoading() == true, diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt index be5ee4d9ed..74f85480e8 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioManager.kt @@ -152,6 +152,10 @@ internal class AddToPortfolioManager @Inject constructor() { .orEmpty() } + fun isUserAddedNetworks(userWalletId: UserWalletId): Boolean { + return addedNetworks[userWalletId].orEmpty().isNotEmpty() + } + fun isUserChangedNetworks(userWalletId: UserWalletId): Boolean { return addedNetworks[userWalletId].orEmpty().isNotEmpty() || removedNetworks[userWalletId].orEmpty().isNotEmpty() diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt index 9c6c4bcba1..aff1bf94a8 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MarketsPortfolioModel.kt @@ -92,7 +92,7 @@ internal class MarketsPortfolioModel @Inject constructor( onAddToPortfolioVisibilityChange = ::onAddToPortfolioBSVisibilityChange, onWalletSelectorVisibilityChange = ::onWalletSelectorVisibilityChange, onNetworkSwitchClick = ::onNetworkSwitchClick, - onWalletSelect = { + onAnotherWalletSelect = { onWalletSelect(it) // === Analytics === analyticsEventHandler.send( diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt index 58247416e1..3637c9ed61 100644 --- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt +++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt @@ -127,7 +127,7 @@ private fun AddFirstTokenContent(state: MyPortfolioUM.AddFirstToken, modifier: M verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12), ) { Text( - text = "To start buying, exchanging or receiving this asset, add this token to at least 1 network", // FIXME + text = stringResource(R.string.markets_add_to_my_portfolio_description), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, ) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt index c989de7fb1..92c74bd946 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/InnerYieldBalanceState.kt @@ -22,7 +22,7 @@ internal sealed class InnerYieldBalanceState { @Immutable internal data class BalanceState( - val id: String, + val groupId: String, val title: TextReference, val type: BalanceType, val subtitle: TextReference?, @@ -34,4 +34,5 @@ internal data class BalanceState( val rawCurrencyId: String?, val validator: Yield.Validator?, val pendingActions: ImmutableList, + val isPending: Boolean, ) \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt index 9e4db06b4e..c490284116 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingUiState.kt @@ -98,6 +98,7 @@ internal sealed class StakingStates { val transactionDoneState: TransactionDoneState, val isApprovalNeeded: Boolean, val reduceAmountBy: BigDecimal?, + val balanceState: BalanceState?, ) : ConfirmationState() data class Empty( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt index 9fd91d3cb8..141759adfb 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/BalanceItemConverter.kt @@ -22,6 +22,7 @@ internal class BalanceItemConverter( private val appCurrencyProvider: Provider, private val yield: Yield, ) : Converter { + override fun convert(value: BalanceItem): BalanceState? { val cryptoCurrencyStatus = cryptoCurrencyStatusProvider() val appCurrency = appCurrencyProvider() @@ -37,7 +38,7 @@ internal class BalanceItemConverter( val title = value.type.getTitle(validator?.name) return title?.let { BalanceState( - id = value.id, + groupId = value.groupId, validator = validator, title = title, subtitle = getSubtitle(value), @@ -59,7 +60,8 @@ internal class BalanceItemConverter( ), rawCurrencyId = value.rawCurrencyId, pendingActions = value.pendingActions.toPersistentList(), - isClickable = value.type.isClickable(), + isClickable = value.type.isClickable() && !value.isPending, + isPending = value.isPending, ) } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt index e3cc2f8e07..295f07c032 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/RewardsValidatorStateConverter.kt @@ -78,7 +78,7 @@ internal class RewardsValidatorStateConverter( ) return BalanceState( - id = balance.id, + groupId = balance.groupId, validator = this, title = stringReference(this.name), subtitle = null, @@ -90,6 +90,7 @@ internal class RewardsValidatorStateConverter( pendingActions = balance.pendingActions.toPersistentList(), isClickable = true, type = balance.type, + isPending = balance.isPending, ) } } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt index ae45ca8d1f..5c82de46c3 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/converters/YieldBalancesConverter.kt @@ -14,6 +14,7 @@ import kotlinx.collections.immutable.toPersistentList internal class YieldBalancesConverter( private val cryptoCurrencyStatusProvider: Provider, private val appCurrencyProvider: Provider, + private val balancesToShowProvider: Provider>, private val yield: Yield, ) : Converter { @@ -32,6 +33,8 @@ internal class YieldBalancesConverter( val cryptoRewardsValue = yieldBalance.getRewardStakingBalance() val fiatRewardsValue = cryptoCurrencyStatus.value.fiatRate?.times(cryptoRewardsValue) + val balanceToShowItems = balancesToShowProvider() + InnerYieldBalanceState.Data( rewardsCrypto = BigDecimalFormatter.formatCryptoAmount( cryptoAmount = cryptoRewardsValue, @@ -43,7 +46,7 @@ internal class YieldBalancesConverter( fiatCurrencySymbol = appCurrency.symbol, ), rewardBlockType = getRewardBlockType(), - balance = yieldBalance.balance.items.mapBalances(), + balance = balanceToShowItems.mapBalances(), ) } else { InnerYieldBalanceState.Empty diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt index e6e0b2b6e0..109dafb912 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt @@ -5,10 +5,10 @@ import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.transaction.Fee import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.core.analytics.api.AnalyticsEventHandler -import com.tangem.domain.staking.GetConstructedStakingTransactionUseCase -import com.tangem.domain.staking.GetStakingTransactionUseCase -import com.tangem.domain.staking.SaveUnsubmittedHashUseCase -import com.tangem.domain.staking.SubmitHashUseCase +import com.tangem.domain.staking.* +import com.tangem.domain.staking.model.PendingTransaction +import com.tangem.domain.staking.model.SubmitHashData +import com.tangem.domain.staking.model.stakekit.BalanceType import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.StakingError import com.tangem.domain.staking.model.stakekit.Yield @@ -45,6 +45,7 @@ internal class StakingTransactionSender @AssistedInject constructor( private val getExplorerTransactionUrlUseCase: GetExplorerTransactionUrlUseCase, private val submitHashUseCase: SubmitHashUseCase, private val saveUnsubmittedHashUseCase: SaveUnsubmittedHashUseCase, + private val savePendingTransactionUseCase: SavePendingTransactionUseCase, private val analyticsEventHandler: AnalyticsEventHandler, @Assisted private val cryptoCurrencyStatus: CryptoCurrencyStatus, @Assisted private val userWallet: UserWallet, @@ -89,6 +90,7 @@ internal class StakingTransactionSender @AssistedInject constructor( sendStakingTransaction( fullTransactionsData = fullTransactionsData, + balanceState = confirmationState.balanceState, onSendSuccess = onSendSuccess, onSendError = onSendError, ) @@ -191,6 +193,7 @@ internal class StakingTransactionSender @AssistedInject constructor( private suspend fun sendStakingTransaction( fullTransactionsData: List, + balanceState: BalanceState?, onSendSuccess: (txUrl: String) -> Unit, onSendError: (SendTransactionError?) -> Unit, ) { @@ -206,6 +209,11 @@ internal class StakingTransactionSender @AssistedInject constructor( submitHash( transactionIds = fullTransactionsData.map { it.stakeKitTransaction.id }, transactionHashes = transactionHashes, + groupId = balanceState?.groupId, + validator = balanceState?.validator, + amount = balanceState?.cryptoDecimal, + balanceType = balanceState?.type, + rawCurrencyId = balanceState?.rawCurrencyId, ) val txUrl = getExplorerTransactionUrlUseCase( txHash = transactionHashes.last(), @@ -218,13 +226,27 @@ internal class StakingTransactionSender @AssistedInject constructor( ) } - private suspend fun submitHash(transactionIds: List, transactionHashes: List) { + private suspend fun submitHash( + transactionIds: List, + transactionHashes: List, + groupId: String?, + validator: Yield.Validator?, + amount: BigDecimal?, + balanceType: BalanceType?, + rawCurrencyId: String?, + ) { transactionIds .zip(transactionHashes) .forEach { (transactionId, transactionHash) -> - submitHashUseCase.submitHash( - transactionId = transactionId, - transactionHash = transactionHash, + submitHashUseCase( + SubmitHashData( + transactionId = transactionId, + transactionHash = transactionHash, + validator = validator, + amount = amount, + balanceType = balanceType, + rawCurrencyId = rawCurrencyId, + ), ) .onLeft { analyticsEventHandler.send( @@ -236,6 +258,15 @@ internal class StakingTransactionSender @AssistedInject constructor( ) }.onRight { Timber.d("Successful hash submission") + savePendingTransactionUseCase.invoke( + PendingTransaction( + groupId = groupId, + type = balanceType, + amount = amount, + rawCurrencyId = rawCurrencyId, + validator = validator, + ), + ) } } } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt index b5b6e9061f..51b7ec69a2 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/ConfirmationStatePreviewData.kt @@ -93,5 +93,6 @@ internal object ConfirmationStatePreviewData { isApprovalNeeded = false, reduceAmountBy = null, pendingActions = null, + balanceState = null, ) } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt index 6ad1e50645..e42df87aba 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/previewdata/InitialStakingStatePreview.kt @@ -68,7 +68,7 @@ internal object InitialStakingStatePreview { rewardBlockType = RewardBlockType.RewardUnavailable, balance = persistentListOf( BalanceState( - id = "id", + groupId = "groupId", title = stringReference("Binance"), cryptoValue = "100", cryptoAmount = stringReference("100 SOL"), @@ -91,6 +91,7 @@ internal object InitialStakingStatePreview { isClickable = true, type = BalanceType.STAKED, subtitle = null, + isPending = false, ), ), ), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt index b4c0e2a299..66ec803e8a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/stub/StakingClickIntentsStub.kt @@ -20,8 +20,8 @@ internal object StakingClickIntentsStub : StakingClickIntents { actionTypeToOverwrite: StakingActionCommonType?, pendingAction: PendingAction?, pendingActions: ImmutableList?, - ) { - } + balanceState: BalanceState?, + ) { } override fun onActionClick() {} diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetBalanceStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetBalanceStateTransformer.kt new file mode 100644 index 0000000000..d6fae7cd7f --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetBalanceStateTransformer.kt @@ -0,0 +1,18 @@ +package com.tangem.features.staking.impl.presentation.state.transformers + +import com.tangem.features.staking.impl.presentation.state.* +import com.tangem.utils.transformer.Transformer + +internal class SetBalanceStateTransformer( + private val balanceState: BalanceState? = null, +) : Transformer { + + override fun transform(prevState: StakingUiState): StakingUiState { + val possibleConfirmationState = prevState.confirmationState as? StakingStates.ConfirmationState.Data + ?: return prevState + + return prevState.copy( + confirmationState = possibleConfirmationState.copy(balanceState = balanceState), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt index 27d5d30086..c2df5db972 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetConfirmationStateLoadingTransformer.kt @@ -41,6 +41,7 @@ internal class SetConfirmationStateLoadingTransformer( pendingActions = possibleConfirmationState?.pendingActions, isApprovalNeeded = false, reduceAmountBy = null, + balanceState = null, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt index 517cbba094..66b56ea7aa 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/SetInitialDataStateTransformer.kt @@ -10,6 +10,7 @@ import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.core.serialization.SerializedBigDecimal +import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.tokens.model.CryptoCurrencyStatus import com.tangem.domain.wallets.models.UserWallet @@ -19,7 +20,6 @@ import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType import com.tangem.features.staking.impl.presentation.state.converters.RewardsValidatorStateConverter import com.tangem.features.staking.impl.presentation.state.converters.YieldBalancesConverter import com.tangem.features.staking.impl.presentation.viewmodel.StakingClickIntents -import com.tangem.lib.crypto.BlockchainUtils.isCosmos import com.tangem.lib.crypto.BlockchainUtils.isPolkadot import com.tangem.utils.Provider import com.tangem.utils.isNullOrZero @@ -38,6 +38,7 @@ internal class SetInitialDataStateTransformer( private val cryptoCurrencyStatusProvider: Provider, private val userWalletProvider: Provider, private val appCurrencyProvider: Provider, + private val balancesToShowProvider: Provider>, ) : Transformer { private val iconStateConverter by lazy(::CryptoCurrencyToIconStateConverter) @@ -60,6 +61,7 @@ internal class SetInitialDataStateTransformer( YieldBalancesConverter( cryptoCurrencyStatusProvider, appCurrencyProvider, + balancesToShowProvider, yield, ) } @@ -229,6 +231,7 @@ internal class SetInitialDataStateTransformer( pendingActions = null, isApprovalNeeded = isApprovalNeeded, reduceAmountBy = null, + balanceState = null, ) } @@ -255,13 +258,7 @@ internal class SetInitialDataStateTransformer( private fun getRewardScheduleText(rewardSchedule: Yield.Metadata.RewardSchedule): TextReference? { return when (rewardSchedule) { - Yield.Metadata.RewardSchedule.BLOCK -> { - val networkId = cryptoCurrencyStatusProvider().currency.network.id.value - when { - isCosmos(networkId) -> resourceReference(R.string.staking_reward_schedule_each_minute) - else -> resourceReference(R.string.staking_reward_schedule_each_day) - } - } + Yield.Metadata.RewardSchedule.BLOCK -> resourceReference(R.string.staking_reward_schedule_block) Yield.Metadata.RewardSchedule.WEEK -> resourceReference(R.string.staking_reward_schedule_week) Yield.Metadata.RewardSchedule.HOUR -> resourceReference(R.string.staking_reward_schedule_hour) Yield.Metadata.RewardSchedule.DAY -> resourceReference(R.string.staking_reward_schedule_each_day) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt index 679dd0675d..589c784974 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingInitialInfoContent.kt @@ -89,7 +89,7 @@ internal fun StakingInitialInfoContent( if (state.showBanner) { item(key = BANNER_BLOCK_KEY) { Column( - modifier = Modifier.animateItemPlacement(), + modifier = Modifier.animateItem(), ) { BannerBlock(onClick = clickIntents::onInitialInfoBannerClick) SpacerH12() @@ -105,7 +105,7 @@ internal fun StakingInitialInfoContent( if (state.yieldBalance is InnerYieldBalanceState.Data) { item(key = STAKING_REWARD_BLOCK_KEY) { - Column(modifier = Modifier.animateItemPlacement()) { + Column(modifier = Modifier.animateItem()) { StakingRewardBlock( rewardCrypto = state.yieldBalance.rewardsCrypto, rewardFiat = state.yieldBalance.rewardsFiat, @@ -138,7 +138,6 @@ internal fun StakingInitialInfoContent( } } -@OptIn(ExperimentalFoundationApi::class) private fun LazyListScope.activeStakingBlock( state: StakingStates.InitialInfoState.Data, clickIntents: StakingClickIntents, @@ -175,7 +174,7 @@ private fun LazyListScope.activeStakingBlock( onClick = clickIntents::onActiveStake, onAnalytic = clickIntents::onActiveStakeAnalytic, modifier = Modifier - .animateItemPlacement() + .animateItem() .then( if (state.yieldBalance.balance.last() == balance) { Modifier.clip(CornersToRound.BOTTOM_2.getShape()) @@ -279,6 +278,7 @@ private fun ActiveStakingBlock( imageUrl = balance.getImage(), iconRes = icon, iconTint = iconTint, + subtitleEndIconRes = R.drawable.ic_staking_pending_transaction.takeIf { balance.isPending }, onImageError = { ValidatorImagePlaceholder() }, modifier = modifier .background(TangemTheme.colors.background.action) diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt index c6fe6235d4..4cad63c4c7 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingClickIntents.kt @@ -20,6 +20,7 @@ internal interface StakingClickIntents : AmountScreenClickIntents { actionTypeToOverwrite: StakingActionCommonType? = null, pendingAction: PendingAction? = null, pendingActions: ImmutableList? = null, + balanceState: BalanceState? = null, ) fun onActionClick() diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt index cebace0c6e..913bf9b07a 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt @@ -1,10 +1,7 @@ package com.tangem.features.staking.impl.presentation.viewmodel import android.os.Bundle -import androidx.lifecycle.DefaultLifecycleObserver -import androidx.lifecycle.SavedStateHandle -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope +import androidx.lifecycle.* import arrow.core.getOrElse import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.common.routing.AppRoute @@ -23,9 +20,11 @@ import com.tangem.domain.feedback.GetCardInfoUseCase import com.tangem.domain.feedback.SaveBlockchainErrorUseCase import com.tangem.domain.feedback.models.BlockchainErrorInfo import com.tangem.domain.feedback.models.FeedbackEmailType +import com.tangem.domain.staking.InvalidatePendingTransactionsUseCase import com.tangem.domain.staking.IsAnyTokenStakedUseCase import com.tangem.domain.staking.IsApproveNeededUseCase import com.tangem.domain.staking.model.StakingApproval +import com.tangem.domain.staking.model.stakekit.BalanceItem import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.Yield import com.tangem.domain.staking.model.stakekit.YieldBalance @@ -98,6 +97,7 @@ internal class StakingViewModel @Inject constructor( private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase, private val isAnyTokenStakedUseCase: IsAnyTokenStakedUseCase, + private val invalidatePendingTransactionsUseCase: InvalidatePendingTransactionsUseCase, private val stakingTransactionLoader: StakingTransactionSender.Factory, private val stakingFeeTransactionLoader: StakingFeeTransactionLoader.Factory, private val stakingBalanceUpdater: StakingBalanceUpdater.Factory, @@ -131,6 +131,13 @@ internal class StakingViewModel @Inject constructor( private var userWallet: UserWallet by Delegates.notNull() private var appCurrency: AppCurrency by Delegates.notNull() + private val balancesToShow: List + get() { + return cryptoCurrencyStatus.value.yieldBalance?.let { + invalidatePendingTransactionsUseCase(it).getOrElse { emptyList() } + } ?: emptyList() + } + private var isInitialInfoAnalyticSent: Boolean = false private val balanceUpdater by lazy(LazyThreadSafetyMode.NONE) { @@ -161,7 +168,7 @@ internal class StakingViewModel @Inject constructor( private val stakingEventFactory: StakingEventFactory get() = StakingEventFactory( stateController = stateController, - popBackStack = stakingStateRouter::onBackClick, + popBackStack = ::onBackClick, onFailedTxEmailClick = ::onFailedTxEmailClick, ) @@ -200,6 +207,7 @@ internal class StakingViewModel @Inject constructor( actionTypeToOverwrite: StakingActionCommonType?, pendingAction: PendingAction?, pendingActions: ImmutableList?, + balanceState: BalanceState?, ) { if (actionTypeToOverwrite != null) { stateController.update(SetActionToExecuteTransformer(actionTypeToOverwrite, pendingAction, pendingActions)) @@ -208,10 +216,12 @@ internal class StakingViewModel @Inject constructor( when { isInitState() -> { stateController.update(SetConfirmationStateLoadingTransformer(yield, appCurrency)) + stateController.update(SetBalanceStateTransformer(balanceState)) onRefreshSwipe(isRefreshing = false) } isAssentState() -> { getFee(pendingAction, pendingActions) + stateController.update(SetBalanceStateTransformer(balanceState)) val amountState = value.amountState as? AmountState.Data if (amountState?.amountTextField?.isWarning == true) { stateController.update( @@ -413,6 +423,7 @@ internal class StakingViewModel @Inject constructor( onNextClick( actionTypeToOverwrite = StakingActionCommonType.PENDING_OTHER, pendingAction = action, + balanceState = activeStake, ) stateController.update(DismissBottomSheetStateTransformer) }, @@ -428,6 +439,7 @@ internal class StakingViewModel @Inject constructor( actionTypeToOverwrite = null, pendingAction = activeStake.pendingActions.firstOrNull(), pendingActions = activeStake.pendingActions.takeIf { isAllWithdrawActions }, + balanceState = activeStake, ) } } @@ -749,6 +761,7 @@ internal class StakingViewModel @Inject constructor( cryptoCurrencyStatusProvider = Provider { cryptoCurrencyStatus }, userWalletProvider = Provider { userWallet }, appCurrencyProvider = Provider { appCurrency }, + balancesToShowProvider = Provider { balancesToShow }, ), ) }, diff --git a/features/wallet/impl/src/main/res/drawable/ill_pastel_cards3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_pastel_cards3_120_106.webp index 1b024df94d..ecc36428ee 100644 Binary files a/features/wallet/impl/src/main/res/drawable/ill_pastel_cards3_120_106.webp and b/features/wallet/impl/src/main/res/drawable/ill_pastel_cards3_120_106.webp differ diff --git a/features/wallet/impl/src/main/res/drawable/ill_vivid_cards3_120_106.webp b/features/wallet/impl/src/main/res/drawable/ill_vivid_cards3_120_106.webp index ecc36428ee..1b024df94d 100644 Binary files a/features/wallet/impl/src/main/res/drawable/ill_vivid_cards3_120_106.webp and b/features/wallet/impl/src/main/res/drawable/ill_vivid_cards3_120_106.webp differ