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 @@
6м
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()
}