diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index fd070196c9..f9911c81f8 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -81,6 +81,12 @@ sealed class AnalyticsParam { override val feeType: FeeType, ) : TxSentFrom("Swap"), TxData + data class Staking( + override val blockchain: String, + override val token: String, + override val feeType: FeeType, + ) : TxSentFrom("Staking"), TxData + data class Approve( override val blockchain: String, override val token: String, diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml index a50e97f246..73dbc1635e 100644 --- a/core/res/src/main/res/values-de/strings.xml +++ b/core/res/src/main/res/values-de/strings.xml @@ -84,6 +84,7 @@ D hast keinen Zugang zur Kamera erteilt, bitte passe deine Datenschutzeinstellungen an Abbrechen Aktion wählen + Beanspruchen Stakingbelohnungen beanstpruchen Schließen Weiter @@ -115,7 +116,9 @@ Zum Anbieter gehen Zum Token Importieren + In Arbeit Später + %1$s übrig Gesperrt Hauptnetz Netzgebühr @@ -413,6 +416,9 @@ Die maximale Anzahl von Coins oder Tokens, die jemals für eine bestimmte Kryptowährung existieren können 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 + 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 Du musst einen einzigen Zugangscode einrichten, um alle deine Karten zu schützen Schützen Du kannst später auf jeder Karte einen individuellen Zugangscode einrichten @@ -661,15 +667,18 @@ Der Zeitraum, den du nach der Beantragung der Abhebung von Geldern aus dem Staking warten musst, bevor die Token verfügbar werden. Aufwärmphase Die zugewiesene Zeit für die Aktivierung der Teilnahme am Staking. + Indem Du die Staking-Funktionalität nutzen, stimmst Du den %1$s und %2$s des Anbieters zu. Gesperrt Migrieren Natives Staking + Verdiente Prämien werden direkt an Deine Adresse gesendet. Sobald Du diese eingefordert hast, stehen Dir die Mittel zur Verfügung. Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jeden tag. Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jede Stunde. Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jeden Monat. Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jede Woche. Verdiene Staking-Belohnungen - Die Belohnungen werden sofort nach dem unstaken gestoppt. Der unstakingprozess dauert %s. + Reinvestiert Deine verdienten Prämien in Deinen Einsatzbetrag und erhöht so den potenziellen Gewinn. + Die Belohnungen werden sofort nach dem start des unstaken gestoppt. Der unstakingprozess dauert %s. Dein Guthaben wurde freigegeben. Du kannst es jetzt zusammen mit deinen verdienten Prämien auszahlen. Vorbereitung Bereit zum Abheben @@ -691,17 +700,22 @@ Belohnungen Stake gesperrt Mehr staken + Du setzt %1$s ein und erhältst jährlich ~ %2$s + Zum Entsperren antippen + Zum abheben antippen Stake %s Staking beenden%s Lösen der Bindungen Gelocktes unlocken Unstaken Staking beenden + Unstake Assets %s Validator/ Prüfer Validatoren Abstimmung Abstimmung gesperrt Zurückziehen + Deine Einsätze Bewahre deine Krypto-Assets sicher auf, während die privaten Schlüssel auf deiner Karte bleiben Revolutionäre Hardware-Wallet Bis zu 3 physische Karten pro Wallet @@ -725,7 +739,6 @@ Der Tausch dieser Menge ausgewählter Token hat erhebliche Auswirkungen auf den Preis und verringert dein Ergebnis. Unzureichende Mittel Erlaubnis erteilen - In Arbeit Tauschen Du erhältst Token auswählen diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml index d69ca4e032..667b0cf956 100644 --- a/core/res/src/main/res/values-es/strings.xml +++ b/core/res/src/main/res/values-es/strings.xml @@ -115,10 +115,7 @@ Ir al proveedor Ir al token Importe - - en %d día - en %d días - + En progreso Más tarde Bloqueado Red principal @@ -132,6 +129,7 @@ Tarjeta principal Frase de contraseña Pegar + Política de privacidad %1$s-%2$s %1$s — %2$s Leer más @@ -159,6 +157,7 @@ Soporte Intercambiar términos y condiciones + Condiciones de uso Hoy Transacción fallida Transacciones @@ -264,7 +263,6 @@ Al utilizar la función de intercambio, acepta el %s Al utilizar la función de intercambio, aceptas las condiciones de %1$s y %2$s del proveedor Próximamente habrá más proveedores.\n¡Estén atentos! - Política de privacidad Proveedor Mejor tarifa Disponible hasta %s @@ -272,7 +270,6 @@ No disponible para este par Permiso requerido Recomendado - Condiciones de uso No se encontraron fichas. Por favor intenta con otra solicitud ID : %s ID de transacción copiado @@ -413,6 +410,8 @@ La cantidad máxima de monedas o tokens que pueden existir para una criptomoneda en particular 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. + Tire hacia arriba o toque la barra de búsqueda para agregar tokens directamente desde el mercado + Agregar tokens Deba configurar un único código de acceso para proteger todas sus tarjetas Proteger Puede configurar un código de acceso individual en cada tarjeta más tarde @@ -637,8 +636,6 @@ Olvidar la billetera Esto eliminará la wallet de la aplicación. La wallet en sí puede\nañadirse de nuevo. Nombre - Activo - Para eliminar el staking de sus activos, presione el bloque de arriba El monto del staking debe ser al menos %s Unstaking de la reclamación Porcentaje de rendimiento anual @@ -697,7 +694,6 @@ Desunión Desbloquear Sin staking - Marque activos sin staking para reclamarlos Unstaking Validador Validadores @@ -727,7 +723,6 @@ Hacer swap de esta cantidad de tokens seleccionados causará un impacto significativo en el precio y reducirá su resultado. Fondos insuficientes Dar autorización - En progreso Intercambie Usted recibe Elige token @@ -808,7 +803,7 @@ Hemos encontrado un error desconocido. Mensaje de error: %s. Si el problema persiste, no duda en contactar con nuestro soporte Tarjeta incorrecta seleccionada en Tangem App Error al crear la transacción a partir de los datos de la dApp. Código: %s - Hemos encontrado un error desconocido. Mensaje de error: %s. Si el problema persiste, contacte con nuestro soporte + Hemos encontrado un error desconocido. Mensaje de error: %d. Si el problema persiste, contacte con nuestro soporte No hay sesiones abiertas de WalletConnect ¡Qué lástima! No hay sesiones. Error al emparejar la sesión de WalletConnect: %1$s diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml index dd3a253e57..89862022aa 100644 --- a/core/res/src/main/res/values-fr/strings.xml +++ b/core/res/src/main/res/values-fr/strings.xml @@ -115,6 +115,7 @@ Aller au fournisseur Aller au jeton Importez + En cours Plus tard Verrouillé Réseau principal @@ -409,6 +410,8 @@ Le nombre maximal de pièces ou de jetons pouvant exister pour une crypto-monnaie particulière 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é + Tirez vers le haut ou appuyez sur la barre de recherche pour ajouter des jetons directement depuis le marché + Ajouter des jetons Vous devez définir un seul code d\'accès pour protéger toutes vos cartes Protéger Vous pourrez définir un code d\'accès individuel sur chaque carte plus tard @@ -452,7 +455,7 @@ 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 @@ -720,7 +723,6 @@ Échanger ce montant de jetons sélectionnés aura un impact significatif sur les prix et réduira votre résultat. Fonds insuffisants Donner l\'autorisation - En cours Échangez Vous recevez Choisir le jeton diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml index 5e94e5d5f8..7cb6050f2b 100644 --- a/core/res/src/main/res/values-ja/strings.xml +++ b/core/res/src/main/res/values-ja/strings.xml @@ -114,7 +114,9 @@ プロバイダーへ移動 トークンへ移動 インポート + 進行中 後で + 残り%1$s ロックされています メインネットワーク ネットワーク手数料 @@ -406,6 +408,9 @@ 特定の暗号資産に存在しうるコインまたはトークンの最大数 取引量(24時間) 過去24時間以内に取引された暗号資産の合計額。市場の活発さと流動性を示します。 + これをドラッグするか、検索窓をタップして、マーケットから直接トークンを追加します + トークンを追加 + お使いのデバイスではNFCが使用できません すべてのカードを保護するには、単一のアクセスコードを設定する必要があります 保護する 後で各カードに個別のアクセスコードを設定できます @@ -684,6 +689,8 @@ ステーキングはロックされています もっとステーキングする %1$sをステーキングし、年間 ~ %2$sを受け取ります + タップしてロック解除 + タップして引き出す %sをステーキングする %sのステーキング解除 ステーキング解約中 @@ -696,6 +703,7 @@ 投票する 投票はロックされています 引き出す + あなたのステーキング カード内に秘密鍵を保管しながら暗号資産を安全に保管します 革新的なハードウェアウォレット 1つのウォレットに最大3枚のカード @@ -719,7 +727,6 @@ 選択したトークンをこの量を交換すると、価格に大きな影響が生じ、結果が減少します。 残高不足 許可を与える - 進行中 スワップ 受け取る トークンを選択 diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 9011127a90..3cd80972f4 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 Вы не предоставили доступ к камере, пожалуйста, измените настройки конфиденциальности. Отмена + Получить Вывести награду Закрыть Продолжить @@ -117,7 +118,9 @@ К провайдеру Перейти в токен Импортировать + В процессе Позже + Осталось %1$s Заблокирован Основная сеть Сетевая комиссия @@ -363,7 +366,7 @@ Все - Опытные покупатели + Опытные трейдеры Рейтинг Сортировать по Лидеры роста @@ -377,12 +380,12 @@ На основе %d оценок Веб-сайт - Покупательское предпочтение + Покуп. предпочтение Разница между объемом покупателей и продавцов - Циркулирующее предложение + Циркулир. предл. Общее количество монет, доступных для торговли и находящихся в обращении на рынке Опытные покупкатели - Полностью разбавленная капитализация + Полн. разб. кап. Общая теоретическая стоимость криптовалюты, если все монеты, которые могут существовать, находятся в обращении, включая те, которые в настоящее время не обращаются Дата создания Высокий @@ -394,9 +397,9 @@ Изменение объема ликвидности, доступной для токена в течение указанного периода времени. Индекс ликвидности Низкий - Рыночная капитализация + Рын. кап. Общая рыночная стоимость криптовалюты, рассчитываемая путем умножения текущей цены монеты на общее количество монет в обращении. - Рыночный рейтинг + Рейтинг Максимальный объем Метрики Официальные ссылки @@ -408,6 +411,9 @@ Максимальное количество монет или токенов, которое может когда-либо существовать для выбранной криптовалюты. Объем торгов (24ч) Общая сумма криптовалюты, которая была продана за последние 24 часа, показывающая уровень активности и ликвидности на рынке. + Потяните вверх или коснитесь поисковой строки, чтобы добавить токены напрямую из маркета + Добавить токены + Функция NFC недоступна на вашем устройстве Вам необходимо установить единый код доступа для защиты всех ваших карт Защита Позже вы сможете установить индивидуальный код доступа для каждой карты @@ -652,9 +658,9 @@ Метрики Минимальное количество Нет вознаграждений к получению - Способ возраграждения + Способ вознаграждения Способ получения вознаграждений за стейкинг. Он может быть автоматическим, при котором вознаграждение само зачисляется вам на адрес или в ручную, когда вознаграждение нужно вывести, создав транзакцию на её получение. - Период возрагражения + Период вознагражения Это период, определяющий, когда участники стейкинга получат свои вознаграждения. Вознаграждение для получения: %s Стейкинг %s @@ -662,15 +668,18 @@ Период, который необходимо подождать после запроса на вывод средств из стейкинга, прежде чем токены станут доступны. Период прогрева Время, необходимое для начала процесса стейкинга и активации процесса начисления наград + Пользуясь стейкинг сервисом, вы соглашаетесь с %1$s и %2$s Заблокировано Переместить Нативный стейкинг + Полученная награда будет отправлена на ваш кошелек и будет сразу доступна для использования Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждый день. Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждый час. Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждый месяц. Стейкинг дает возможность вам получать %1$s. Награда будет зачисляться каждую неделю. Получите награду за стейкинг - Награда перестанет начисляться сразу после того, как вы начнете процесс завершения стейкинга. Процесс завершения длится %s. + Ваши средства будут доступны после %s периода отзыва. + Вы можете вывести свои средства из стейкинга, они будут доступны для использования незамедлительно. Сменить валидатора Застейкать вознаграждения Отозвать @@ -688,6 +697,8 @@ Вознаграждения Стейкинг закрыт Застейкать еще + Нажмите для разблокировки + Нажмите для вывода Застейкать %s Вывести %s Отзыв @@ -723,7 +734,6 @@ Обмен этой суммы выбранных токенов может вызвать значительные колебания цены и уменьшить получаемую сумму. Недостаточно средств Дать разрешение - В процессе Обменять Вы получите Выберите токен @@ -805,7 +815,7 @@ Произошла непредвиденная ошибка. Сообщение ошибки: %s Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. Неверная карта выбрана в приложении Tangem Не удалось создать транзакцию из данных Dapp. Код: %s - Произошла непредвиденная ошибка. Код ошибки: %d Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. + Произошла непредвиденная ошибка. Код ошибки: %d. Попробуйте, пожалуйста, позже. Если проблема будет продолжать возникать - обратитесь в службу поддержки. Нет открытых сессий WalletConnect Упс. Нет сессий. Не удалось создать пару WalletConnect: %1$s diff --git a/core/res/src/main/res/values-uk-rUA/strings.xml b/core/res/src/main/res/values-uk-rUA/strings.xml index 96f929750f..d15a97ab14 100644 --- a/core/res/src/main/res/values-uk-rUA/strings.xml +++ b/core/res/src/main/res/values-uk-rUA/strings.xml @@ -116,6 +116,7 @@ Перейти до провайдера Перейти до токену Імпортувати + процесі Пізніше Заблокований Основна мережа @@ -397,7 +398,7 @@ Низький Ринкова капіталізація Загальна ринкова вартість криптовалюти, що розраховується шляхом множення поточної ціни монети на загальну кількість монет в обігу - Рейтинг ринку + Рейтинг Позиція в крипторейтингу між усіма монетами на основі ринкової капіталізації Максимальна пропозиція Метрики @@ -722,7 +723,6 @@ Обмін цієї кількості обраних токенів призведе до значного впливу на ціну і зменшить вашу кінцеву суму. Недостатньо коштів Надати дозвіл - В процесі Обміняти Ви отримаєте Оберіть токен diff --git a/core/res/src/main/res/values-zh-rTW/strings.xml b/core/res/src/main/res/values-zh-rTW/strings.xml index b860623796..f144efa47f 100644 --- a/core/res/src/main/res/values-zh-rTW/strings.xml +++ b/core/res/src/main/res/values-zh-rTW/strings.xml @@ -58,6 +58,7 @@ 啟用 錯誤 導入 + 進行中 網路費 OK @@ -271,7 +272,6 @@ 在此代幣交換的數量將對價格產生重大影響,並降低您收到的數量 餘額不足 賦予權限 - 進行中 交易 選擇代幣 無法使用 diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 3999c3c8e3..d4457dd0f7 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -116,6 +116,7 @@ Go to provider Go to token Import + In progress Later %1$s left Locked @@ -396,7 +397,7 @@ The change in how much liquidity is available for the token during the specified timeframe Liquidity index Low - Market capitalization + 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 rating Position in crypto rating between all coins based on market capitalization @@ -666,15 +667,15 @@ Locked Migrate Native staking - Earned rewards are sent directly to your address. Once claimed, the funds are available for use. + Earned rewards will be sent to your wallet and available for use immediately Staking allows you to earn %1$s. Your staking rewards arrive every day. Staking allows you to earn %1$s. Your staking rewards arrive every hour. Staking allows you to earn %1$s. Your staking rewards arrive every month. Staking allows you to earn %1$s. Your staking rewards arrive every week. Earn staking rewards Reinvests your earned rewards in your staked amount, increasing potential earnings. - Rewards stop accruing immediately after you start unstaking. The unstaking process takes %s. - Your funds have been unstaked. You can now withdraw them along with your earned rewards. + Your funds will be available for use after the %s unbonding period. + You can now withdraw your funds, it will be available to use immediatly Preparing Ready to withdraw Rebond @@ -700,6 +701,7 @@ Tap to withdraw Stake %s Unstake %s + The transaction is being processed! Validation is currently underway in the blockchain. This may take a few minutes. Unbonding Unlock locked Unstaked @@ -734,7 +736,6 @@ Swapping this amount of selected tokens will cause a significant price impact and reduce your outcome. Insufficient funds Give Permission - In progress Swap You receive Choose token diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt index aea7aaa822..0a5cc124d1 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/TransactionDoneTitle.kt @@ -1,7 +1,6 @@ package com.tangem.core.ui.components.transactions import android.content.res.Configuration -import androidx.annotation.StringRes import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -13,22 +12,24 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.R -import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.res.TangemTheme -import com.tangem.core.ui.utils.DateTimeFormatters -import com.tangem.core.ui.utils.toTimeFormat +import com.tangem.core.ui.res.TangemThemePreview /** * Common transaction done screen title * - * @param titleRes title resource - * @param date transaction timestamp in millis + * @param title title resource + * @param subtitle subtitle text */ @Composable -fun TransactionDoneTitle(@StringRes titleRes: Int, date: Long, modifier: Modifier = Modifier) { +fun TransactionDoneTitle(title: TextReference, subtitle: TextReference, modifier: Modifier = Modifier) { Column( modifier = modifier .fillMaxWidth(), @@ -41,20 +42,17 @@ fun TransactionDoneTitle(@StringRes titleRes: Int, date: Long, modifier: Modifie .size(TangemTheme.dimens.size64), ) Text( - text = stringResource(id = titleRes), + text = title.resolveReference(), style = TangemTheme.typography.h3, color = TangemTheme.colors.text.primary1, modifier = Modifier .padding(top = TangemTheme.dimens.spacing16), ) Text( - text = stringResource( - id = R.string.send_date_format, - date.toTimeFormat(DateTimeFormatters.dateFormatter), - date.toTimeFormat(), - ), + text = subtitle.resolveReference(), style = TangemTheme.typography.body2, color = TangemTheme.colors.text.tertiary, + textAlign = TextAlign.Center, modifier = Modifier .padding(top = TangemTheme.dimens.spacing4), ) @@ -68,8 +66,8 @@ fun TransactionDoneTitle(@StringRes titleRes: Int, date: Long, modifier: Modifie private fun TransactionDoneTitlePreview() { TangemThemePreview { TransactionDoneTitle( - titleRes = R.string.sent_transaction_sent_title, - date = 0, + title = resourceReference(R.string.sent_transaction_sent_title), + subtitle = stringReference("0"), modifier = Modifier .background(TangemTheme.colors.background.tertiary) .padding(TangemTheme.dimens.spacing16), diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt index c932b9f1d9..6c93dfccee 100644 --- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt +++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/YieldBalance.kt @@ -25,6 +25,13 @@ sealed class YieldBalance { .filter { it.type == BalanceType.REWARDS } .sumOf { it.amount } } + + fun getValidatorsCount(): Int { + return balance.items + .filterNot { it.validatorAddress.isNullOrBlank() } + .distinctBy { it.validatorAddress } + .size + } } data object Empty : YieldBalance() diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt index 1896d2c250..bea218c179 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/model/analytics/TokenScreenAnalyticsEvent.kt @@ -77,4 +77,9 @@ sealed class TokenScreenAnalyticsEvent( event = "Button - Token Trustline", params = mapOf("Token" to tokenSymbol, "Blockchain" to blockchain), ) + + data class StakingClicked(val token: String) : TokenScreenAnalyticsEvent( + event = "Staking Clicked", + params = mapOf("Token" to token), + ) } \ No newline at end of file diff --git a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt index 2576d7b37f..d65fdea64d 100644 --- a/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt +++ b/features/send/impl/src/main/java/com/tangem/features/send/impl/presentation/ui/send/SendContent.kt @@ -26,8 +26,12 @@ import androidx.compose.ui.tooling.preview.PreviewParameter import androidx.compose.ui.tooling.preview.PreviewParameterProvider import com.tangem.common.ui.amountScreen.ui.AmountBlock import com.tangem.core.ui.components.transactions.TransactionDoneTitle +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.utils.DateTimeFormatters +import com.tangem.core.ui.utils.toTimeFormat import com.tangem.features.send.impl.R import com.tangem.features.send.impl.presentation.state.SendUiState import com.tangem.features.send.impl.presentation.state.previewdata.ConfirmStatePreviewData @@ -69,8 +73,14 @@ private fun LazyListScope.blocks(uiState: SendUiState) { modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12), ) { TransactionDoneTitle( - titleRes = R.string.sent_transaction_sent_title, - date = timestamp, + title = resourceReference(R.string.sent_transaction_sent_title), + subtitle = resourceReference( + R.string.send_date_format, + wrappedList( + timestamp.toTimeFormat(DateTimeFormatters.dateFormatter), + timestamp.toTimeFormat(), + ), + ), ) } RecipientBlock( diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingAnalyticsEvents.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingAnalyticsEvents.kt new file mode 100644 index 0000000000..336486156f --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/StakingAnalyticsEvents.kt @@ -0,0 +1,157 @@ +package com.tangem.features.staking.impl.analytics + +import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.domain.staking.model.stakekit.action.StakingActionType + +internal sealed class StakingAnalyticsEvents( + event: String, + params: Map = mapOf(), +) : AnalyticsEvent( + category = "Staking", + event = event, + params = params, +) { + + data class StakingInfoScreenOpened( + val validatorsCount: Int, + val token: String, + ) : StakingAnalyticsEvents( + event = "Staking Info Screen Opened", + params = mapOf( + "Validators Count" to validatorsCount.toString(), + AnalyticsParam.TOKEN_PARAM to token, + ), + ) + + data class WhatIsStaking(val token: String) : StakingAnalyticsEvents( + event = "Link - What Is Staking", + params = mapOf( + AnalyticsParam.TOKEN_PARAM to token, + ), + ) + + data class AmountScreenOpened(val token: String) : StakingAnalyticsEvents( + event = "Amount Screen Opened", + params = mapOf( + AnalyticsParam.TOKEN_PARAM to token, + ), + ) + + data class ConfirmationScreenOpened( + val token: String, + val validator: String, + val action: StakingActionType, + ) : StakingAnalyticsEvents( + event = "Confirmation Screen Opened", + params = mapOf( + AnalyticsParam.TOKEN_PARAM to token, + "Validator" to validator, + "Action" to action.name, + ), + ) + + data class StakeInProgressScreenOpened( + val validator: String, + val token: String, + ) : StakingAnalyticsEvents( + event = "Stake In Progress Screen Opened", + params = mapOf( + AnalyticsParam.TOKEN_PARAM to token, + "Validator" to validator, + ), + ) + + data class RewardScreenOpened(val token: String) : StakingAnalyticsEvents( + event = "Reward Screen Opened", + params = mapOf( + AnalyticsParam.TOKEN_PARAM to token, + ), + ) + + data class AmountSelectCurrency( + val token: String, + val isAppCurrency: Boolean, + ) : StakingAnalyticsEvents( + event = "Selected Currency", + params = mapOf( + AnalyticsParam.TOKEN_PARAM to token, + AnalyticsParam.TYPE to if (isAppCurrency) "App Currency" else "Token", + ), + ) + + data class ButtonMax(val token: String) : StakingAnalyticsEvents( + event = "Button - Max", + params = mapOf( + AnalyticsParam.TOKEN_PARAM to token, + ), + ) + + data class ButtonCancel( + val source: StakeScreenSource, + val token: String, + ) : StakingAnalyticsEvents( + event = "Button - Cancel", + params = mapOf( + AnalyticsParam.TOKEN_PARAM to token, + AnalyticsParam.SOURCE to source.name, + ), + ) + + data class ButtonValidator( + val source: StakeScreenSource, + val token: String, + ) : StakingAnalyticsEvents( + event = "Button - Validator", + params = mapOf( + AnalyticsParam.TOKEN_PARAM to token, + AnalyticsParam.SOURCE to source.name, + ), + ) + + data class ButtonRewards( + val token: String, + ) : StakingAnalyticsEvents( + event = "Button - Rewards", + params = mapOf( + AnalyticsParam.TOKEN_PARAM to token, + ), + ) + + data class ButtonAction( + val action: String, + val token: String, + val validator: String, + ) : StakingAnalyticsEvents( + event = "Button - $action", + params = mapOf( + AnalyticsParam.TOKEN_PARAM to token, + "Validator" to validator, + ), + ) + + data object ButtonShare : StakingAnalyticsEvents(event = "Button - Share") + + data object ButtonExplore : StakingAnalyticsEvents(event = "Button - Explore") + + data class StakingError( + val token: String, + ) : StakingAnalyticsEvents( + event = "Errors", + params = mapOf(AnalyticsParam.TOKEN_PARAM to token), + ) + + data class TransactionError( + val token: String, + ) : StakingAnalyticsEvents( + event = "Error - Transaction Rejected", + params = mapOf(AnalyticsParam.TOKEN_PARAM to token), + ) +} + +enum class StakeScreenSource { + Info, + Amount, + Confirmation, + Validators, +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt new file mode 100644 index 0000000000..42dc192fbb --- /dev/null +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/analytics/utils/StakingAnalyticSender.kt @@ -0,0 +1,104 @@ +package com.tangem.features.staking.impl.analytics.utils + +import com.tangem.common.ui.bottomsheet.permission.state.ApproveType +import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam +import com.tangem.core.analytics.models.Basic +import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType +import com.tangem.domain.staking.model.stakekit.action.StakingActionType +import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.features.staking.impl.analytics.StakeScreenSource +import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents +import com.tangem.features.staking.impl.presentation.state.* + +internal class StakingAnalyticSender( + private val analyticsEventHandler: AnalyticsEventHandler, +) { + + fun initialInfoScreen(value: StakingUiState) { + val initialInfoState = value.initialInfoState as? StakingStates.InitialInfoState.Data + val validatorState = initialInfoState?.yieldBalance as? InnerYieldBalanceState.Data + val validatorCount = validatorState?.balance + ?.filterNot { it.validator?.address.isNullOrBlank() } + ?.distinctBy { it.validator?.address } + ?.size ?: 0 + + analyticsEventHandler.send( + StakingAnalyticsEvents.StakingInfoScreenOpened( + validatorsCount = validatorCount, + token = value.cryptoCurrencySymbol, + ), + ) + } + + fun confirmationScreen(value: StakingUiState) { + val confirmationState = value.confirmationState as? StakingStates.ConfirmationState.Data + val validatorState = confirmationState?.validatorState as? ValidatorState.Content + val validatorName = validatorState?.chosenValidator?.name ?: return + + if (confirmationState.innerState == InnerConfirmationStakingState.COMPLETED) return + + analyticsEventHandler.send( + StakingAnalyticsEvents.ConfirmationScreenOpened( + token = value.cryptoCurrencySymbol, + validator = validatorName, + action = when (value.actionType) { + StakingActionCommonType.ENTER -> StakingActionType.STAKE + StakingActionCommonType.EXIT -> StakingActionType.UNSTAKE + StakingActionCommonType.PENDING_REWARDS, + StakingActionCommonType.PENDING_OTHER, + -> confirmationState.pendingAction?.type ?: StakingActionType.UNKNOWN + }, + ), + ) + } + + fun screenCancel(value: StakingUiState) { + analyticsEventHandler.send( + StakingAnalyticsEvents.ButtonCancel( + source = when (value.currentStep) { + StakingStep.InitialInfo -> StakeScreenSource.Info + StakingStep.Amount -> StakeScreenSource.Amount + StakingStep.Confirmation -> StakeScreenSource.Confirmation + StakingStep.Validators, + StakingStep.RewardsValidators, + -> StakeScreenSource.Validators + }, + token = value.cryptoCurrencyName, + ), + ) + } + + fun sendTransactionApprovalAnalytics(tokenCryptoCurrency: CryptoCurrency) { + analyticsEventHandler.send( + Basic.TransactionSent( + sentFrom = AnalyticsParam.TxSentFrom.Approve( + blockchain = tokenCryptoCurrency.network.name, + token = tokenCryptoCurrency.symbol, + feeType = AnalyticsParam.FeeType.Normal, + permissionType = ApproveType.LIMITED.name, + ), + memoType = Basic.TransactionSent.MemoType.Null, + ), + ) + } + + fun sendTransactionStakingAnalytics(cryptoCurrency: CryptoCurrency, validator: String) { + analyticsEventHandler.send( + Basic.TransactionSent( + sentFrom = AnalyticsParam.TxSentFrom.Staking( + blockchain = cryptoCurrency.network.name, + token = cryptoCurrency.symbol, + feeType = AnalyticsParam.FeeType.Normal, + ), + memoType = Basic.TransactionSent.MemoType.Null, + ), + ) + analyticsEventHandler.send( + StakingAnalyticsEvents.StakeInProgressScreenOpened( + validator = validator, + token = cryptoCurrency.symbol, + ), + ) + } +} \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/StakingFragment.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/StakingFragment.kt index cb590bcb4c..1740d65084 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/StakingFragment.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/StakingFragment.kt @@ -54,6 +54,7 @@ internal class StakingFragment : ComposeFragment() { StakingStateRouter( appRouter = appRouter, stateController = stateController, + analyticsEventsHandler = analyticsEventsHandler, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt index 4c450c78a2..0b04c60e11 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateController.kt @@ -68,6 +68,7 @@ internal class StakingStateController @Inject constructor( clickIntents = StakingClickIntentsStub, walletName = "", cryptoCurrencyName = "", + cryptoCurrencySymbol = "", currentStep = StakingStep.InitialInfo, initialInfoState = StakingStates.InitialInfoState.Empty(), amountState = AmountState.Empty(), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt index 5dd1f3de27..69cca72194 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/StakingStateRouter.kt @@ -1,14 +1,21 @@ package com.tangem.features.staking.impl.presentation.state import com.tangem.common.routing.AppRouter +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType +import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents +import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender internal class StakingStateRouter( private val appRouter: AppRouter, private val stateController: StakingStateController, + private val analyticsEventsHandler: AnalyticsEventHandler, ) { + private val analyticSender = StakingAnalyticSender(analyticsEventsHandler) + fun onBackClick() { + analyticSender.screenCancel(stateController.value) appRouter.pop() stateController.clear() } @@ -48,14 +55,21 @@ internal class StakingStateRouter( } private fun showInitial() { + analyticSender.initialInfoScreen(stateController.value) stateController.update { it.copy(currentStep = StakingStep.InitialInfo) } } - fun showRewardsValidators() { + private fun showRewardsValidators() { + analyticsEventsHandler.send( + StakingAnalyticsEvents.RewardScreenOpened(stateController.value.cryptoCurrencySymbol), + ) stateController.update { it.copy(currentStep = StakingStep.RewardsValidators) } } - fun showAmount() { + private fun showAmount() { + analyticsEventsHandler.send( + StakingAnalyticsEvents.AmountScreenOpened(stateController.value.cryptoCurrencySymbol), + ) stateController.update { it.copy(currentStep = StakingStep.Amount) } } @@ -63,7 +77,8 @@ internal class StakingStateRouter( stateController.update { it.copy(currentStep = StakingStep.Validators) } } - fun showConfirmation() { + private fun showConfirmation() { + analyticSender.confirmationScreen(stateController.value) stateController.update { it.copy(currentStep = StakingStep.Confirmation) } } } \ 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 1a276f55e0..202219cb3b 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 @@ -27,6 +27,7 @@ internal data class StakingUiState( val clickIntents: StakingClickIntents, val walletName: String, val cryptoCurrencyName: String, + val cryptoCurrencySymbol: String, val currentStep: StakingStep, val initialInfoState: StakingStates.InitialInfoState, val amountState: AmountState, 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 a13675c8bf..4cc05fa7f2 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 @@ -69,4 +69,6 @@ internal object StakingClickIntentsStub : StakingClickIntents { override fun onNotificationCancel(notification: Class) {} override fun openTokenDetails(cryptoCurrency: CryptoCurrency) {} + + override fun onActiveStakeAnalytic() {} } \ No newline at end of file diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt index be78b5321c..55740369f4 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/transformers/AddStakingNotificationsTransformer.kt @@ -104,7 +104,9 @@ internal class AddStakingNotificationsTransformer( return prevState.copy( confirmationState = confirmationState.copy( notifications = notifications.toImmutableList(), - isPrimaryButtonEnabled = notifications.any { it is StakingNotification.Error }, + isPrimaryButtonEnabled = notifications.none { + it is StakingNotification.Error || it is NotificationUM.Error + }, ), ) } 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 13c748e962..8779bf824e 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 @@ -56,16 +56,19 @@ internal class SetInitialDataStateTransformer( } private val yieldBalancesConverter by lazy(LazyThreadSafetyMode.NONE) { - YieldBalancesConverter(cryptoCurrencyStatusProvider, appCurrencyProvider, yield) + YieldBalancesConverter( + cryptoCurrencyStatusProvider, + appCurrencyProvider, + yield, + ) } override fun transform(prevState: StakingUiState): StakingUiState { - val cryptoCurrencyName = cryptoCurrencyStatusProvider().currency.name - + val cryptoCurrency = cryptoCurrencyStatusProvider().currency return prevState.copy( title = TextReference.EMPTY, - walletName = userWalletProvider.invoke().name, - cryptoCurrencyName = cryptoCurrencyName, + cryptoCurrencyName = cryptoCurrency.name, + cryptoCurrencySymbol = cryptoCurrency.symbol, clickIntents = clickIntents, currentStep = StakingStep.InitialInfo, initialInfoState = createInitialInfoState(), diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt index 8071e92164..df10d96663 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/StakingConfirmationContent.kt @@ -9,13 +9,13 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.amountScreen.preview.AmountStatePreviewData import com.tangem.common.ui.amountScreen.ui.AmountBlock import com.tangem.core.ui.components.transactions.TransactionDoneTitle +import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType @@ -52,10 +52,9 @@ internal fun StakingConfirmationContent( visible = doneState is TransactionDoneState.Content, modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12), ) { - val transactionDoneStateContent = remember(this) { doneState as TransactionDoneState.Content } TransactionDoneTitle( - titleRes = R.string.sent_transaction_sent_title, - date = transactionDoneStateContent.timestamp, + title = resourceReference(R.string.common_in_progress), + subtitle = resourceReference(R.string.staking_transaction_in_progress_text), ) } AmountBlock( 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 6ffdee416f..15aeef6efe 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 @@ -173,8 +173,9 @@ private fun LazyListScope.activeStakingBlock( ) { balance -> ActiveStakingBlock( balance = balance, - onClick = clickIntents::onActiveStake, isBalanceHidden = isBalanceHidden, + onClick = clickIntents::onActiveStake, + onAnalytic = clickIntents::onActiveStakeAnalytic, modifier = Modifier .animateItemPlacement() .then( @@ -268,8 +269,9 @@ private fun StakingRewardBlock( @Composable private fun ActiveStakingBlock( balance: BalanceState, - onClick: (BalanceState) -> Unit, isBalanceHidden: Boolean, + onClick: (BalanceState) -> Unit, + onAnalytic: () -> Unit, modifier: Modifier = Modifier, ) { val (icon, iconTint) = balance.type.getIcon() @@ -287,7 +289,10 @@ private fun ActiveStakingBlock( interactionSource = remember { MutableInteractionSource() }, indication = rememberRipple(), enabled = balance.isClickable, - onClick = { onClick(balance) }, + onClick = { + onAnalytic() + onClick(balance) + }, ), ) } diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt index 93802cd66d..cfb15b93e0 100644 --- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt +++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/ui/block/StakingFeeBlock.kt @@ -55,10 +55,9 @@ internal fun StakingFeeBlock(feeState: FeeState, isTransactionSent: Boolean) { when (feeState) { is FeeState.Content -> { val feeAmount = feeState.fee?.amount - val (title, icon) = R.string.common_fee_selector_option_market to R.drawable.ic_bird_24 SelectorRowItem( - titleRes = title, - iconRes = icon, + titleRes = R.string.common_fee_selector_option_market, + iconRes = R.drawable.ic_bird_24, preDot = stringReference( BigDecimalFormatter.formatCryptoFeeAmount( cryptoAmount = feeAmount?.value, @@ -80,9 +79,19 @@ internal fun StakingFeeBlock(feeState: FeeState, isTransactionSent: Boolean) { ) } is FeeState.Loading -> { + SelectorRowItem( + titleRes = R.string.common_fee_selector_option_market, + iconRes = R.drawable.ic_bird_24, + paddingValues = PaddingValues(), + ) FeeLoading(feeState) } is FeeState.Error -> { + SelectorRowItem( + titleRes = R.string.common_fee_selector_option_market, + iconRes = R.drawable.ic_bird_24, + paddingValues = PaddingValues(), + ) FeeError(feeState) } } 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 b9743fb560..ed0cfbffc4 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 @@ -39,6 +39,8 @@ internal interface StakingClickIntents : AmountScreenClickIntents { fun onActiveStake(activeStake: BalanceState) + fun onActiveStakeAnalytic() + fun showApprovalBottomSheet() fun onApprovalClick() 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 1988d8a119..966e5d3693 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 @@ -15,6 +15,7 @@ import com.tangem.common.routing.bundle.unbundle import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer import com.tangem.common.ui.amountScreen.models.AmountState import com.tangem.common.ui.notifications.NotificationUM +import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.ui.haptic.TangemHapticEffect import com.tangem.core.ui.haptic.VibratorHapticManager import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase @@ -29,7 +30,9 @@ import com.tangem.domain.staking.* import com.tangem.domain.staking.model.StakingApproval import com.tangem.domain.staking.model.stakekit.PendingAction import com.tangem.domain.staking.model.stakekit.Yield +import com.tangem.domain.staking.model.stakekit.YieldBalance import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType +import com.tangem.domain.staking.model.stakekit.action.StakingActionType import com.tangem.domain.staking.model.stakekit.transaction.ActionParams import com.tangem.domain.staking.model.stakekit.transaction.StakingTransaction import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType @@ -45,6 +48,9 @@ import com.tangem.domain.utils.convertToSdkAmount import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.usecase.GetUserWalletUseCase +import com.tangem.features.staking.impl.analytics.StakeScreenSource +import com.tangem.features.staking.impl.analytics.StakingAnalyticsEvents +import com.tangem.features.staking.impl.analytics.utils.StakingAnalyticSender import com.tangem.features.staking.impl.navigation.InnerStakingRouter import com.tangem.features.staking.impl.presentation.state.* import com.tangem.features.staking.impl.presentation.state.bottomsheet.InfoType @@ -106,6 +112,7 @@ internal class StakingViewModel @Inject constructor( private val getCurrencyCheckUseCase: GetCurrencyCheckUseCase, private val isAmountSubtractAvailableUseCase: IsAmountSubtractAvailableUseCase, private val isAnyTokenStakedUseCase: IsAnyTokenStakedUseCase, + private val analyticsEventHandler: AnalyticsEventHandler, @DelayedWork private val coroutineScope: CoroutineScope, savedStateHandle: SavedStateHandle, ) : ViewModel(), DefaultLifecycleObserver, StakingClickIntents { @@ -135,6 +142,8 @@ internal class StakingViewModel @Inject constructor( private var userWallet: UserWallet by Delegates.notNull() private var appCurrency: AppCurrency by Delegates.notNull() + private var isInitialInfoAnalyticSent: Boolean = false + private val stakingEventFactory: StakingEventFactory get() = StakingEventFactory( stateController = stateController, @@ -142,6 +151,10 @@ internal class StakingViewModel @Inject constructor( onFailedTxEmailClick = ::onFailedTxEmailClick, ) + private val stakingAnalyticSender = StakingAnalyticSender( + analyticsEventHandler = analyticsEventHandler, + ) + private var stakingApproval: StakingApproval = StakingApproval.Empty private var isAmountSubtractAvailable: Boolean = false private val allowanceTaskScheduler = SingleTaskScheduler() @@ -270,6 +283,7 @@ internal class StakingViewModel @Inject constructor( ), ).getOrElse { Timber.e(it.toString()) + analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName)) stakingEventFactory.createStakingErrorAlert(it) stateController.update(SetConfirmationStateResetAssentTransformer) return@launch @@ -284,6 +298,7 @@ internal class StakingViewModel @Inject constructor( transactionId = transaction.id, ).getOrElse { Timber.e(it.toString()) + analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName)) stakingEventFactory.createStakingErrorAlert(it) stateController.update(SetConfirmationStateResetAssentTransformer) return@launch @@ -301,6 +316,7 @@ internal class StakingViewModel @Inject constructor( fullTransactionsData = fullTransactionsData, fee = fee, pendingAction = confirmationState.pendingAction, + validator = validatorState.chosenValidator.name, ) } } @@ -326,6 +342,7 @@ internal class StakingViewModel @Inject constructor( type = pendingAction?.type, ), ).getOrElse { + analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName)) stateController.update(AddStakingErrorTransformer()) updateNotifications(GetFeeError.UnknownError) return @@ -365,6 +382,7 @@ internal class StakingViewModel @Inject constructor( updateNotifications() }, ifLeft = { + analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencyName)) stateController.update(AddStakingErrorTransformer()) updateNotifications(it) }, @@ -405,6 +423,7 @@ internal class StakingViewModel @Inject constructor( } override fun onInitialInfoBannerClick() { + analyticsEventHandler.send(StakingAnalyticsEvents.WhatIsStaking(cryptoCurrencyStatus.currency.symbol)) innerRouter.openUrl(WHAT_IS_STAKING_ARTICLE_URL) } @@ -425,26 +444,47 @@ internal class StakingViewModel @Inject constructor( } override fun onMaxValueClick() { + analyticsEventHandler.send(StakingAnalyticsEvents.ButtonMax(cryptoCurrencyStatus.currency.symbol)) stateController.update(AmountMaxValueStateTransformer(cryptoCurrencyStatus, yield)) } override fun onCurrencyChangeClick(isFiat: Boolean) { + analyticsEventHandler.send( + StakingAnalyticsEvents.AmountSelectCurrency(cryptoCurrencyStatus.currency.symbol, isFiat), + ) stateController.update(AmountCurrencyChangeStateTransformer(cryptoCurrencyStatus, isFiat)) } - override fun openValidators() = stakingStateRouter.showValidators() + override fun openValidators() { + analyticsEventHandler.send( + StakingAnalyticsEvents.ButtonValidator( + source = StakeScreenSource.Confirmation, + token = cryptoCurrencyStatus.currency.symbol, + ), + ) + stakingStateRouter.showValidators() + } override fun onValidatorSelect(validator: Yield.Validator) { stateController.update(ValidatorSelectChangeTransformer(validator)) } override fun openRewardsValidators() { + analyticsEventHandler.send( + StakingAnalyticsEvents.ButtonRewards(value.cryptoCurrencyName), + ) val rewardsValidators = stateController.value.rewardsValidatorsState as? StakingStates.RewardsValidatorsState.Data val rewards = rewardsValidators?.rewards if (rewards != null && rewards.isSingleItem()) { onActiveStake(rewards.first()) } else { + analyticsEventHandler.send( + StakingAnalyticsEvents.ButtonValidator( + source = StakeScreenSource.Info, + token = cryptoCurrencyStatus.currency.symbol, + ), + ) onNextClick(actionTypeToOverwrite = StakingActionCommonType.PENDING_REWARDS) } } @@ -479,6 +519,15 @@ internal class StakingViewModel @Inject constructor( } } + override fun onActiveStakeAnalytic() { + analyticsEventHandler.send( + StakingAnalyticsEvents.ButtonValidator( + source = StakeScreenSource.Info, + token = cryptoCurrencyStatus.currency.symbol, + ), + ) + } + override fun showApprovalBottomSheet() { stateController.update( ShowApprovalBottomSheetTransformer( @@ -519,6 +568,7 @@ internal class StakingViewModel @Inject constructor( ).fold( ifLeft = { error -> Timber.e(error.toString()) + analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencyName)) stateController.update( SetConfirmationStateAssentApprovalTransformer( appCurrencyProvider = Provider { appCurrency }, @@ -540,6 +590,7 @@ internal class StakingViewModel @Inject constructor( ).fold( ifLeft = { error -> Timber.e(error.toString()) + analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencyName)) stateController.update( SetConfirmationStateAssentApprovalTransformer( appCurrencyProvider = Provider { appCurrency }, @@ -551,6 +602,7 @@ internal class StakingViewModel @Inject constructor( stateController.update(SetConfirmationStateResetAssentTransformer) }, ifRight = { + stakingAnalyticSender.sendTransactionApprovalAnalytics(tokenCryptoCurrency) stateController.update(SetApprovalInProgressTransformer) stateController.update(DismissBottomSheetStateTransformer()) awaitForAllowance(confirmationState.pendingAction) @@ -668,10 +720,10 @@ internal class StakingViewModel @Inject constructor( } override fun onExploreClick() { + analyticsEventHandler.send(StakingAnalyticsEvents.ButtonExplore) val confirmationDataState = uiState.value.confirmationState as? StakingStates.ConfirmationState.Data val transactionDoneState = confirmationDataState?.transactionDoneState as? TransactionDoneState.Content val txUrl = transactionDoneState?.txUrl - if (txUrl != null) { innerRouter.openUrl(txUrl) } @@ -682,12 +734,11 @@ internal class StakingViewModel @Inject constructor( val transactionDoneState = confirmationDataState?.transactionDoneState as? TransactionDoneState.Content val txUrl = transactionDoneState?.txUrl + analyticsEventHandler.send(StakingAnalyticsEvents.ButtonShare) if (txUrl != null) { vibratorHapticManager.performOneTime(TangemHapticEffect.OneTime.Click) stakingEventFactory.createShareDialog(txUrl = txUrl) } - - // TODO staking [REDACTED_TASK_KEY] } override fun onFailedTxEmailClick(errorMessage: String) { @@ -758,6 +809,17 @@ internal class StakingViewModel @Inject constructor( .onEach { maybeStatus -> maybeStatus.fold( ifRight = { status -> + if (!isInitialInfoAnalyticSent) { + isInitialInfoAnalyticSent = true + val balances = status.value.yieldBalance as? YieldBalance.Data + analyticsEventHandler.send( + StakingAnalyticsEvents.StakingInfoScreenOpened( + validatorsCount = balances?.getValidatorsCount() ?: 0, + token = status.currency.symbol, + ), + ) + } + feeCryptoCurrencyStatus = getFeePaidCryptoCurrencyStatusSyncUseCase(userWalletId, status).getOrNull() cryptoCurrencyStatus = status @@ -816,14 +878,33 @@ internal class StakingViewModel @Inject constructor( fullTransactionsData: List, fee: Fee, pendingAction: PendingAction?, + validator: String, ) { + val cryptoCurrency = cryptoCurrencyStatus.currency + val action = when (value.actionType) { + StakingActionCommonType.ENTER -> StakingActionType.STAKE + StakingActionCommonType.EXIT -> StakingActionType.UNSTAKE + StakingActionCommonType.PENDING_REWARDS, + StakingActionCommonType.PENDING_OTHER, + -> pendingAction?.type + } + action?.let { + analyticsEventHandler.send( + StakingAnalyticsEvents.ButtonAction( + action = it.name, + token = value.cryptoCurrencyName, + validator = validator, + ), + ) + } sendMultipleTransactionUseCase( txsData = fullTransactionsData.map { it.tangemTransaction }, userWallet = userWallet, - network = cryptoCurrencyStatus.currency.network, + network = cryptoCurrency.network, ).fold( ifLeft = { error -> Timber.e(error.toString()) + analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName)) stateController.update( SetConfirmationStateAssentTransformer( appCurrencyProvider = Provider { appCurrency }, @@ -836,6 +917,7 @@ internal class StakingViewModel @Inject constructor( stateController.update(SetConfirmationStateResetAssentTransformer) }, ifRight = { transactionHashes -> + stakingAnalyticSender.sendTransactionStakingAnalytics(cryptoCurrency, validator) transactionsInProgress.clear() submitHash( transactionIds = fullTransactionsData.map { it.stakeKitTransaction.id }, @@ -844,9 +926,8 @@ internal class StakingViewModel @Inject constructor( scheduleUpdates() val txUrl = getExplorerTransactionUrlUseCase( txHash = transactionHashes.last(), - networkId = cryptoCurrencyStatus.currency.network.id, + networkId = cryptoCurrency.network.id, ).getOrElse { "" } - stateController.update( SetConfirmationStateCompletedTransformer( appCurrencyProvider = Provider { appCurrency }, @@ -868,6 +949,7 @@ internal class StakingViewModel @Inject constructor( transactionHash = transactionHash, ) .onLeft { + analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName)) saveUnsubmittedHashUseCase.invoke( transactionId = transactionId, transactionHash = transactionHash, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt index c9e3891056..b5c056c643 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt @@ -18,8 +18,12 @@ import com.tangem.core.ui.components.inputrow.InputRowDefault import com.tangem.core.ui.components.inputrow.InputRowImage import com.tangem.core.ui.components.transactions.TransactionDoneTitle import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.utils.DateTimeFormatters +import com.tangem.core.ui.utils.toTimeFormat import com.tangem.feature.swap.domain.models.domain.ExchangeProviderType import com.tangem.feature.swap.models.SwapSuccessStateHolder import com.tangem.feature.swap.presentation.R @@ -60,7 +64,16 @@ private fun SwapSuccessScreenContent(state: SwapSuccessStateHolder, padding: Pad .background(TangemTheme.colors.background.secondary) .padding(horizontal = TangemTheme.dimens.spacing16), ) { - TransactionDoneTitle(titleRes = R.string.swapping_success_view_title, date = state.timestamp) + TransactionDoneTitle( + title = resourceReference(R.string.common_in_progress), + subtitle = resourceReference( + R.string.send_date_format, + wrappedList( + state.timestamp.toTimeFormat(DateTimeFormatters.dateFormatter), + state.timestamp.toTimeFormat(), + ), + ), + ) SpacerH16() InputRowImage( title = TextReference.Res(R.string.swapping_from_title), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index 2157443000..5d65adaf1c 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -466,6 +466,7 @@ internal class TokenDetailsViewModel @Inject constructor( } override fun onStakeBannerClick() { + analyticsEventsHandler.send(TokenScreenAnalyticsEvent.StakingClicked(cryptoCurrency.symbol)) openStaking() }