diff --git a/app/src/main/java/com/tangem/tap/features/home/HomeViewModel.kt b/app/src/main/java/com/tangem/tap/features/home/HomeViewModel.kt
index dd335deba7..0df4db4753 100644
--- a/app/src/main/java/com/tangem/tap/features/home/HomeViewModel.kt
+++ b/app/src/main/java/com/tangem/tap/features/home/HomeViewModel.kt
@@ -5,6 +5,7 @@ import androidx.lifecycle.viewModelScope
import com.google.firebase.analytics.ktx.analytics
import com.google.firebase.ktx.Firebase
import com.tangem.common.routing.AppRoute
+import com.tangem.common.routing.AppRoute.ManageTokens.Source
import com.tangem.core.analytics.Analytics
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
@@ -67,7 +68,7 @@ internal class HomeViewModel @Inject constructor(
analyticsEventHandler.send(IntroductionProcess.ButtonTokensList())
store.dispatch(TokensAction.SetArgs.ReadAccess)
- store.dispatchNavigationAction { push(AppRoute.ManageTokens()) }
+ store.dispatchNavigationAction { push(AppRoute.ManageTokens(Source.STORIES)) }
}
private fun scanCard() {
diff --git a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
index 8934b9801e..ec1b9c0c85 100644
--- a/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
+++ b/app/src/main/java/com/tangem/tap/routing/utils/ChildFactory.kt
@@ -10,6 +10,7 @@ import com.tangem.features.details.component.DetailsComponent
import com.tangem.features.disclaimer.api.components.DisclaimerComponent
import com.tangem.features.managetokens.ManageTokensToggles
import com.tangem.features.managetokens.component.ManageTokensComponent
+import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
import com.tangem.features.send.api.navigation.SendRouter
@@ -112,9 +113,15 @@ internal class ChildFactory @Inject constructor(
}
is AppRoute.ManageTokens -> {
if (manageTokensToggles.isFeatureEnabled) {
+ val source = when (route.source) {
+ AppRoute.ManageTokens.Source.SETTINGS -> ManageTokensSource.SETTINGS
+ AppRoute.ManageTokens.Source.ONBOARDING -> ManageTokensSource.ONBOARDING
+ AppRoute.ManageTokens.Source.STORIES -> ManageTokensSource.STORIES
+ }
+
route.asComponentChild(
contextProvider = contextProvider(route, contextFactory),
- params = ManageTokensComponent.Params(route.userWalletId),
+ params = ManageTokensComponent.Params(route.userWalletId, source),
componentFactory = manageTokensComponentFactory,
)
} else {
diff --git a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
index 2c60258331..b1f4c7a19b 100644
--- a/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
+++ b/common/routing/src/main/kotlin/com/tangem/common/routing/AppRoute.kt
@@ -179,9 +179,16 @@ sealed class AppRoute(val path: String) : Route {
@Serializable
data class ManageTokens(
+ val source: Source,
val userWalletId: UserWalletId? = null,
- ) : AppRoute(path = "/manage_tokens/$userWalletId"), RouteBundleParams {
+ ) : AppRoute(path = "${source.name.lowercase()}/manage_tokens/$userWalletId"), RouteBundleParams {
override fun getBundle(): Bundle = bundle(serializer())
+
+ enum class Source {
+ STORIES,
+ ONBOARDING,
+ SETTINGS,
+ }
}
@Serializable
diff --git a/core/analytics/build.gradle.kts b/core/analytics/build.gradle.kts
index c61c8e7af2..99c29cd55d 100644
--- a/core/analytics/build.gradle.kts
+++ b/core/analytics/build.gradle.kts
@@ -11,7 +11,7 @@ dependencies {
kapt(deps.hilt.kapt)
/** Analytics - Models */
- implementation(projects.core.analytics.models)
+ api(projects.core.analytics.models)
/** Domain */
implementation(projects.domain.analytics)
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 f9911c81f8..5fc6c4e12c 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
@@ -22,9 +22,19 @@ sealed class AnalyticsParam {
data object Closed : RateApp("Close")
}
- sealed class OnOffState(val value: String) {
- data object On : OnOffState("On")
- data object Off : OnOffState("Off")
+ enum class OnOffState(val value: String) {
+ On("On"),
+ Off("Off"),
+ ;
+
+ companion object {
+
+ fun from(enabled: Boolean): String {
+ val state = if (enabled) On else Off
+
+ return state.value
+ }
+ }
}
sealed class OrganizeSortType(val value: String) {
@@ -135,6 +145,22 @@ sealed class AnalyticsParam {
class SingleCurrency(currencyName: String) : WalletType(currencyName)
}
+ enum class Validation(val value: String) {
+
+ OK(value = "Ok"),
+ ERROR(value = "Error"),
+ ;
+
+ companion object {
+
+ fun from(isValid: Boolean): String {
+ val status = if (isValid) OK else ERROR
+
+ return status.value
+ }
+ }
+ }
+
companion object Key {
const val BLOCKCHAIN = "blockchain"
const val TOKEN_PARAM = "Token"
@@ -158,6 +184,9 @@ sealed class AnalyticsParam {
const val VALIDATION = "Validation"
const val BLOCKCHAIN_EXCEPTION_HOST = "exception_host"
const val BLOCKCHAIN_SELECTED_HOST = "selected_host"
+ const val INPUT = "Input"
+ const val COUNT = "Count"
+ const val DERIVATION = "Derivation"
// region swap
const val TOKEN_CATEGORY = "Token"
diff --git a/core/analytics/src/main/java/com/tangem/core/analytics/DummyAnalyticsEventHandler.kt b/core/analytics/src/main/java/com/tangem/core/analytics/DummyAnalyticsEventHandler.kt
new file mode 100644
index 0000000000..170158e0ec
--- /dev/null
+++ b/core/analytics/src/main/java/com/tangem/core/analytics/DummyAnalyticsEventHandler.kt
@@ -0,0 +1,11 @@
+package com.tangem.core.analytics
+
+import com.tangem.core.analytics.api.AnalyticsEventHandler
+import com.tangem.core.analytics.models.AnalyticsEvent
+
+class DummyAnalyticsEventHandler : AnalyticsEventHandler {
+
+ override fun send(event: AnalyticsEvent) {
+ /* no-op */
+ }
+}
\ No newline at end of file
diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt
index 68c54776fa..fb8f19935b 100644
--- a/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt
+++ b/core/datasource/src/main/java/com/tangem/datasource/api/markets/models/response/TokenMarketInfoResponse.kt
@@ -104,8 +104,8 @@ data class TokenMarketInfoResponse(
val marketCap: BigDecimal?,
@Json(name = "volume_24h")
val volume24h: BigDecimal?,
- @Json(name = "total_supply")
- val totalSupply: BigDecimal?,
+ @Json(name = "max_supply")
+ val maxSupply: BigDecimal?,
@Json(name = "fully_diluted_valuation")
val fullyDilutedValuation: BigDecimal?,
)
diff --git a/core/res/src/main/res/values-de/strings.xml b/core/res/src/main/res/values-de/strings.xml
index 05775e44ca..1d3513aa6e 100644
--- a/core/res/src/main/res/values-de/strings.xml
+++ b/core/res/src/main/res/values-de/strings.xml
@@ -414,7 +414,8 @@
Position im Krypto-Rating zwischen allen Coins basierend auf der Marktkapitalisierung
Marktbewertung
Maximale Versorgung
- Leer
+ Die maximale Anzahl von Coins oder Tokens, die jemals für eine bestimmte Kryptowährung existieren können
+ Maximale Versorgung
Metriken
Offizielle Links
Preisleistung
@@ -700,6 +701,7 @@
Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jede Woche.
Sicher staken und wöchentliche Belohnungen verdienen.
Verdiene Staking-Belohnungen
+ Aufgrund von Netzwerkproblemen ist Staking derzeit nicht verfügbar. Bitte versuche es später erneut.
Beim Staking im %1$s -Netzwerk mit einem neuen Validator werden alle zuvor eingesetzten Kryptos automatisch an diesen Validator übertragen
Reinvestiert Deine verdienten Prämien in Deinen Einsatzbetrag und erhöht so den potenziellen Gewinn.
Entsperre dein Geld, um es aus dem Staking-Prozess abzuheben. Das Freischalten nimmt %s.
diff --git a/core/res/src/main/res/values-es/strings.xml b/core/res/src/main/res/values-es/strings.xml
index 25839b4722..ecb1726923 100644
--- a/core/res/src/main/res/values-es/strings.xml
+++ b/core/res/src/main/res/values-es/strings.xml
@@ -75,8 +75,8 @@
Aprobación
Aprobar
Atención
- Soldo: %s
- Soldo
+ Saldo: %s
+ Saldo
autenticación biométrica
biometría
Comprar
@@ -103,7 +103,7 @@
Activar
Activado
Error
- Intercambie
+ Intercambiar
Explore
Explore historial de transacciones
Explorador(a)
@@ -211,7 +211,7 @@
Gire la pantalla de su dispositivo hacia abajo para ocultar y mostrar rápidamente los saldos
%s hashes
ID de la tarjeta
- Contacta con el equipo de soporte
+ Contactar con el equipo de soporte
Vincular más tarjetas
Moneda de la aplicación
Girar para ocultar saldos
@@ -351,7 +351,7 @@
Mi portafolio
Mercado
Para generar direcciones para las redes seleccionadas, debe escanear su tarjeta Tangem
- Para agregar tokens, abra esta página o toque la barra de búsqueda
+ Para agregar tokens, abra esta página o pulse sobre la barra de búsqueda
Los datos de este apartado proceden de las siguientes redes: %s
No se pueden cargar los datos…
Sin datos
@@ -363,11 +363,11 @@
Sin resultado
Seleccione una red
Seleccione una billetera
- 1 mo
- 1 año
+ 1m
+ 1a
24h
- 3mos
- 6mos
+ 3m
+ 6m
7d
Todo
Compradores experimentados
@@ -413,6 +413,8 @@
Posición en la clasificación de criptomonedas entre todas las monedas según la capitalización de mercado
Evaluación de mercado
Suministro máximo
+ La cantidad máxima de monedas o tokens que pueden existir para una criptomoneda en particular
+ Suministro máximo
Métrica
Enlaces oficiales
Rendimiento de precios
diff --git a/core/res/src/main/res/values-fr/strings.xml b/core/res/src/main/res/values-fr/strings.xml
index 2a9e104e07..17c960b9e2 100644
--- a/core/res/src/main/res/values-fr/strings.xml
+++ b/core/res/src/main/res/values-fr/strings.xml
@@ -413,6 +413,8 @@
Position dans le classement des crypto-monnaies entre toutes les pièces en fonction de la capitalisation boursière
Évaluation du marché
Approvisionnement maximal
+ Le nombre maximal de pièces ou de jetons pouvant exister pour une crypto-monnaie particulière
+ Approvisionnement maximal
Métriques
Official links
Performance des prix
diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml
index a46b4b69aa..aeed4388a0 100644
--- a/core/res/src/main/res/values-ja/strings.xml
+++ b/core/res/src/main/res/values-ja/strings.xml
@@ -39,7 +39,7 @@
試行回数が多すぎます
お使いの携帯電話で生体認証が無効になっているため、アプリにウォレットを保存できません。ウォレットを保存するには、携帯電話の設定で生体認証機能を有効にしてください。
バックアップ処理を開始する
- 銀行カードまたは銀行口座から
+ 銀行カードまたはその他の支払い方法を使用する
- %d カード
@@ -101,7 +101,7 @@
有効にする
有効
エラー
- 交換
+ スワップ
移動する
取引履歴を調べる
エクスプローラー
@@ -273,7 +273,7 @@
トークンが見つかりません。別のリクエストをお試しください。
ID: %s
取引IDをコピーしました
- ウォレット内の別の通貨から
+ ポートフォリオ内のアセットをこのトークンと交換する
以下の情報はオプションです。共有したくない場合は消去できます。
どのような機能が不足しているかを教えください。解決できるよう尽力致します。
お持ちのカードについて教えてください
@@ -409,6 +409,8 @@
時価総額に基づくすべてのコイン間の暗号資産評価における位置
市場格付け
最大供給量
+ 特定の暗号資産に存在しうるコインまたはトークンの最大数
+ 最大供給量
指標
公式リンク
値動き
@@ -522,7 +524,7 @@
カメラへのアクセスが拒否されました
%3$sネットワーク上の%1$s ( %2$s )
このアドレスには%sのみを送金してください。他のトークンを送信すると、取り返しのつかない損失が発生します。
- QRコードを表示するか、アドレスを共有してください
+ ウォレットや取引所から資金を送金する
参加する
紹介プログラムに関する情報を読み込めませんでした。しばらくしてからもう一度お試しください。
紹介プログラムに関する情報を読み込めませんでした。エラー コード: %s 。しばらくしてからもう一度お試しください。
@@ -570,7 +572,7 @@
カードをスキャンして設定を変更します。変更はスキャンしたカードにのみ影響し、ウォレットに関連付けられている他のカードには影響しません。
カードを準備してください!
入力したアドレスにすでに含まれています
- 手数料額が推奨額の%s倍となっています。カスタム設定が正しいことを再度確認してください。
+ 手数料額が推奨額の%s倍となっています。設定を再度確認し、調整してください。
推奨手数料を下回る手数料が指定されたため、取引に遅れが生じる可能性があります。続行しますか?
理由: %1$s \nコード: %2$s
取引は完了していません
@@ -605,7 +607,7 @@
合計が残高を超えています
セキュリティリスクを防ぐため、ブロックチェーン上にアカウントを維持するには、少なくとも%s の残高が必要です。この金額は残高に残り、引き出すことはできません。
アカウント維持に必要な最低残高
- 手数料額が推奨額の%s倍となっています。カスタム設定が正しいことを再度確認してください。
+ 手数料額が推奨額の%s倍となっています。設定を再度確認し、調整してください。
カスタム手数料が高くなっています
%1$sネットワークの特殊性により、残高全体を転送する場合の手数料は高くなります。手数料を削減するには、 %2$sを残します。
手数料が高くなっています
@@ -690,6 +692,7 @@
ステーキングにより%1$sを獲得できます。ステーキング報酬は毎週受け取れます。
安全にステーキングして、報酬を毎週獲得しましょう
ステーキング報酬を獲得
+ ネットワークの状態により、現在ステーキングはご利用いただけません。しばらくしてからもう一度お試しください。
新しいバリデーターで%1$sネットワークにステーキングすると、以前にステーキングされた資金はすべてこのバリデーターに自動的に転送されます。
獲得した報酬をステーキングに再投資し、潜在的な収益を増やします。
資金をステーキングから引き出すには、ロックを解除してください。ロック解除には%sかかります。
@@ -706,14 +709,14 @@
再投票
自動
手動
- ブロック
- 日
+ ブロック毎
+ 毎日
毎日
- エポック
- 時代
- 時
- 月
- 週
+ エポック毎
+ 時代毎
+ 毎時間
+ 毎月
+ 毎週
報酬
ステーキングはロックされています
もっとステーキングする
diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index 40de1c3f00..c7e2221f9d 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -15,6 +15,7 @@
Токены в сети %1$s не поддерживаются этой картой из-за ограничений прошивки.
У вас возникли трудности со сканированием карты?
Эта карта не предназначена для работы с этим приложением
+ Комиссия по-умолчанию
Подключите функцию комиссии по умолчанию и при формировании транзакции на отправку средств комиссия будет выставлена автоматически, а экран комиссии пропущен. Вы всегда сможете на него вернуться.
Перейдите в настройки, чтобы включить биометрическую аутентификацию в приложении Tangem
Включите биометрическую аутентификацию
@@ -178,6 +179,7 @@
Адрес контракта
Адрес контракта некорректен
Пожалуйста, выберите сеть
+ Этот токен уже добавлен в список
Токен уже существует
Десятичное число должно быть действительным целым числом, до %d
Своя деривация
@@ -347,6 +349,7 @@
Кошелёк не поддерживает более одной сети
Чтобы купить, обменять или получить данный токен, вам нужно добавить его к себе в портфель
Этот актив недоступен
+ Этот токен не доступен для данного кошелька
Добавить в портфель
Добавить
Доступные сети
@@ -358,7 +361,7 @@
Невозможно загрузить данные
Нет данных
Быстрые действия
- Искать на маркете
+ Поиск на рынке
Результат
Токены с капитализацией меньше 100к USD
Показать токены
@@ -416,7 +419,9 @@
Рейтинг
Позиция в рейтинге криптовалют среди всех монет на основе рыночной капитализации.
Рейтинг
- Максимальный объем
+ Макс. объем
+ Максимальное количество монет или токенов, которое может когда-либо существовать для выбранной криптовалюты
+ Максимальный объем
Метрики
Официальные ссылки
Динамика цены
@@ -424,7 +429,7 @@
Оценка безопасности
Социальные
Общ. предл.
- Максимальное количество монет или токенов, которое может когда-либо существовать для выбранной криптовалюты.
+ Максимальное количество монет или токенов, которое может когда-либо существовать для выбранной криптовалюты
Общее предложение
Объем торгов (24ч)
Общая сумма криптовалюты, которая была продана за последние 24 часа, показывающая уровень активности и ликвидности на рынке.
@@ -536,7 +541,7 @@
Доступ к камере запрещен
%1$s (%2$s) в сети %3$s
Отправляйте только %s на этот адрес. Использование другой сети может привести к утрате средств.
- Переводите средства с другого кошелька или биржи
+ Переводите средства с любого кошелька или биржи
Участвовать
Не удалось загрузить информацию по реферальной программе. Пожалуйста, попробуйте позже.
Не удалось загрузить информацию по реферальной программе. Код ошибки: %s. Пожалуйста, попробуйте позже.
@@ -597,6 +602,7 @@
Сумма
Вы можете установить комиссию за транзакцию, изменив значение в поле Satoshi per vByte.
Комиссия, которая будет взята за вашу транзакцию. Вы можете выставить своё собственное значение.
+ Максимальная комиссия
Это стоимость, которую вы готовы заплатить за каждую единицу газа. Чем выше цена газа, тем быстрее ваша транзакция будет обработана. (Приоритетная комиссия включена)
Приоритетная комиссия
Комиссия, которую пользователь может заплатить майнерам или валидаторам за ускорение включения его транзакции в блок.
@@ -685,7 +691,7 @@
Минимальное количество
Нет вознаграждений к получению
Способ вознаграждения
- Способ получения вознаграждений за стейкинг. Он может быть автоматическим, при котором вознаграждение само зачисляется вам на адрес или в ручную, когда вознаграждение нужно вывести, создав транзакцию на её получение.
+ Способ получения вознаграждений за стейкинг. Оно может зачисляться автоматически на ваш адрес или вручную, когда вознаграждение нужно вывести, создав транзакцию на её получение.
Период вознагражения
Это период, определяющий, когда участники стейкинга получат свои вознаграждения.
Вознаграждение для получения: %s
@@ -725,14 +731,14 @@
Переголосовать
Авто
Вручную
- Блок
- День
+ За блок
+ Ежедневно
Каждый день
- Эпоха
- Эра
- Час
+ За эпоху
+ За эру
+ Ежечасно
Месяц
- Неделя
+ Еженедельно
Вознаграждения
Стейкинг закрыт
Застейкать еще
@@ -815,6 +821,7 @@
Операция
от: %s
на: %s
+ валидатор: %s
Попробовать снова
Вы отсканировали ту же карту. Для создания twin-кошелька вам необходимо отсканировать карту с номером %d
Вы отсканировали не ту twin-карту. Пожалуйста, попробуйте отсканировать другую
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 15a063ac84..ef2696c0e6 100644
--- a/core/res/src/main/res/values-uk-rUA/strings.xml
+++ b/core/res/src/main/res/values-uk-rUA/strings.xml
@@ -418,7 +418,8 @@
Рейтинг
Позиція в крипторейтингу між усіма монетами на основі ринкової капіталізації
Рейтинг
- Максимальна пропозиція
+ Максимальна кількість монет або токенів, яка може коли-небудь існувати для певної криптовалюти
+ Максимальна пропозиція
Метрики
Офіційні посилання
Цінова ефективність
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 93fba71bd2..a75c4fce68 100644
--- a/core/res/src/main/res/values/strings.xml
+++ b/core/res/src/main/res/values/strings.xml
@@ -345,6 +345,7 @@
The wallet doesn\'t support more than one network
To buy, exchange, or receive this asset, add it to your portfolio
This asset is not available
+ This asset is not available for this wallet
Add to portfolio
Add
Available networks
@@ -413,6 +414,8 @@
Position in crypto rating between all coins based on market capitalization
Market rating
Max supply
+ The maximum number of coins or tokens that can ever exist for a particular cryptocurrency
+ Max supply
Metrics
Official links
Price performance
@@ -528,7 +531,7 @@
Camera access denied
%1$s (%2$s) on %3$s network
Send only %s to this address. Sending any other currency will result in its irreversible loss.
- Transfer funds from another wallet or exchange
+ Transfer funds from any wallet or exchange
Participate
Failed to load the information about the referral program. Please try again later.
Failed to load the information about the referral program. Error code: %s. Please try again later.
@@ -715,14 +718,14 @@
Revote
Auto
Manual
- Block
- Day
+ Per Block
+ Daily
Daily
- Epoch
- Era
- Hour
- Month
- Week
+ Per Epoch
+ Per Era
+ Hourly
+ Monthly
+ Weekly
Rewards
Stake locked
Stake more
@@ -805,6 +808,7 @@
Operation
from: %s
to: %s
+ validator: %s
Try again
You\'ve scanned the same card. To create a twin wallet you need to scan the card with number %d
You\'ve scanned wrong twin card. Please try another one
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/TopAppBarButton.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/TopAppBarButton.kt
index 160e0148da..0f2f4b0523 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/TopAppBarButton.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/TopAppBarButton.kt
@@ -13,6 +13,7 @@ import com.tangem.core.ui.res.TangemTheme
@Composable
fun TopAppBarButton(button: TopAppBarButtonUM, tint: Color, modifier: Modifier = Modifier) {
IconButton(
+ enabled = button.enabled,
modifier = modifier.size(TangemTheme.dimens.size32),
onClick = button.onIconClicked,
) {
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/models/TopAppBarButtonUM.kt b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/models/TopAppBarButtonUM.kt
index 61e3f25d39..7104e25cf9 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/appbar/models/TopAppBarButtonUM.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/appbar/models/TopAppBarButtonUM.kt
@@ -6,14 +6,18 @@ import com.tangem.core.ui.R
data class TopAppBarButtonUM(
@DrawableRes val iconRes: Int,
val onIconClicked: () -> Unit,
+ val enabled: Boolean = true,
) {
@Suppress("FunctionName")
companion object {
- fun Back(onBackClicked: () -> Unit) = TopAppBarButtonUM(
+ fun Back(onBackClicked: () -> Unit) = Back(true, onBackClicked)
+
+ fun Back(enabled: Boolean = true, onBackClicked: () -> Unit) = TopAppBarButtonUM(
iconRes = R.drawable.ic_back_24,
onIconClicked = onBackClicked,
+ enabled = enabled,
)
}
}
\ No newline at end of file
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemSheetState.kt b/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemSheetState.kt
index 64dcea2dcb..17988b6b6f 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemSheetState.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/sheetscaffold/TangemSheetState.kt
@@ -207,8 +207,8 @@ class TangemSheetState(
absVelocityThreshold = 0.5f,
),
confirmValueChange = confirmValueChange,
- positionalThreshold = { with(density) { 56.dp.toPx() } },
- velocityThreshold = { with(density) { 125.dp.toPx() } },
+ positionalThreshold = { with(density) { 156.dp.toPx() } },
+ velocityThreshold = { with(density) { 225.dp.toPx() } },
)
internal val offset: Float
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt
index 91efa949e6..af0ff0f89b 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/transactions/Transaction.kt
@@ -20,8 +20,10 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
+import androidx.constraintlayout.compose.ChainStyle
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.Dimension
+import androidx.constraintlayout.compose.Visibility
import com.tangem.core.ui.R
import com.tangem.core.ui.components.CircleShimmer
import com.tangem.core.ui.components.RectangleShimmer
@@ -68,6 +70,9 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod
ConstraintLayout(modifier = Modifier.fillMaxWidth()) {
val (iconItem, titleItem, subtitleItem, amountItem, timestampItem) = createRefs()
+ createVerticalChain(titleItem, subtitleItem, chainStyle = ChainStyle.Spread)
+ createVerticalChain(amountItem, timestampItem, chainStyle = ChainStyle.Spread)
+
Icon(
state = state,
modifier = Modifier
@@ -102,6 +107,7 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod
end = TangemTheme.dimens.spacing4,
)
.constrainAs(subtitleItem) {
+ visibility = state.isGoneIf { subtitle.isNullOrEmpty() }
top.linkTo(titleItem.bottom)
bottom.linkTo(parent.bottom)
start.linkTo(iconItem.end)
@@ -114,6 +120,7 @@ fun Transaction(state: TransactionState, isBalanceHidden: Boolean, modifier: Mod
state = state,
isBalanceHidden = isBalanceHidden,
modifier = Modifier.constrainAs(amountItem) {
+ visibility = state.isGoneIf { amount.isEmpty() }
top.linkTo(parent.top)
bottom.linkTo(timestampItem.top)
start.linkTo(titleItem.end)
@@ -315,6 +322,10 @@ private fun LockedContent(modifier: Modifier = Modifier) {
)
}
+private fun TransactionState.isGoneIf(goneCondition: TransactionState.Content.() -> Boolean): Visibility {
+ return if ((this as? TransactionState.Content)?.goneCondition() == true) Visibility.Gone else Visibility.Visible
+}
+
@Preview(showBackground = true, widthDp = 368)
@Preview(showBackground = true, widthDp = 368, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt
index 999276faf5..372a8c50f9 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemTheme.kt
@@ -25,6 +25,7 @@ fun TangemTheme(
dimens: TangemDimens = TangemTheme.dimens,
vibratorHapticManager: VibratorHapticManager? = null,
snackbarHostState: SnackbarHostState = remember { SnackbarHostState() },
+ overrideSystemBarColors: Boolean = true,
content: @Composable () -> Unit,
) {
val themeColors = if (isDark) darkThemeColors() else lightThemeColors()
@@ -32,14 +33,18 @@ fun TangemTheme(
.also { it.update(themeColors) }
val shapes = remember { TangemShapes(dimens) }
- val systemUiController = rememberSystemUiController()
- SideEffect {
- systemUiController.setSystemBarsColor(
- color = Color.Transparent,
- darkIcons = !isDark,
- isNavigationBarContrastEnforced = false,
- )
+ // we don't want to override system bar colors in case of fragment bottom sheets for example
+ if (overrideSystemBarColors) {
+ val systemUiController = rememberSystemUiController()
+
+ SideEffect {
+ systemUiController.setSystemBarsColor(
+ color = Color.Transparent,
+ darkIcons = !isDark,
+ isNavigationBarContrastEnforced = false,
+ )
+ }
}
val view = LocalView.current
diff --git a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeBottomSheetFragment.kt b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeBottomSheetFragment.kt
index 75dc4e8a98..c5c4285292 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeBottomSheetFragment.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeBottomSheetFragment.kt
@@ -52,7 +52,11 @@ abstract class ComposeBottomSheetFragment : BottomSheetDialogFragment(), Compose
override fun getTheme(): Int = R.style.AppTheme_TransparentBottomSheetDialog
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- return createComposeView(inflater.context, requireActivity())
+ return createComposeView(
+ context = inflater.context,
+ activity = requireActivity(),
+ overrideSystemBarColors = false,
+ )
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
diff --git a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt
index 5557002b53..c403c5036e 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeScreen.kt
@@ -50,9 +50,14 @@ internal interface ComposeScreen {
* Creates a [ComposeView] with the defined content for the Compose screen.
*
* @param context The context.
+ * @param overrideSystemBarColors Whether to override the system bar colors to make them transparent.
* @return A [ComposeView] instance with the defined screen content.
*/
-internal fun ComposeScreen.createComposeView(context: Context, activity: Activity): ComposeView {
+internal fun ComposeScreen.createComposeView(
+ context: Context,
+ activity: Activity,
+ overrideSystemBarColors: Boolean = true,
+): ComposeView {
return ComposeView(context).apply {
setContent {
val appThemeMode by uiDependencies.appThemeModeHolder.appThemeMode
@@ -63,6 +68,7 @@ internal fun ComposeScreen.createComposeView(context: Context, activity: Activit
windowSize = windowSize,
vibratorHapticManager = uiDependencies.vibratorHapticManager,
snackbarHostState = uiDependencies.globalSnackbarHostState,
+ overrideSystemBarColors = overrideSystemBarColors,
) {
ScreenContent(modifier = screenModifier)
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
index e6944920a6..fb78b58f1e 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatter.kt
@@ -5,6 +5,7 @@ import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.StringsSigns.LOWER_SIGN
import com.tangem.utils.StringsSigns.TILDE_SIGN
+import com.tangem.utils.extensions.isNotWhitespace
import timber.log.Timber
import java.math.BigDecimal
import java.math.RoundingMode
@@ -20,8 +21,6 @@ object BigDecimalFormatter {
private const val CAN_BE_LOWER_SIGN = LOWER_SIGN
private val FORMAT_THRESHOLD = BigDecimal("0.01")
- private const val TEMP_CURRENCY_CODE = "USD"
-
private val FIAT_FORMAT_THRESHOLD = BigDecimal("0.01")
private val CRYPTO_FEE_FORMAT_THRESHOLD = BigDecimal("0.000001")
@@ -29,6 +28,12 @@ object BigDecimalFormatter {
private const val FIAT_MARKET_EXTENDED_DIGITS = 6
private const val FRACTIONAL_PART_LENGTH_AFTER_LEADING_ZEROES = 4
+ private val usdCurrency = Currency.getInstance("USD")
+
+ @Deprecated(
+ "Use formatCryptoAmount2",
+ replaceWith = ReplaceWith("formatCryptoAmount2"),
+ )
fun formatCryptoAmount(
cryptoAmount: BigDecimal?,
cryptoCurrency: String,
@@ -53,6 +58,30 @@ object BigDecimalFormatter {
}
}
+ // Migrate to this method from formatCryptoAmount ([REDACTED_TASK_KEY])
+ fun formatCryptoAmount2(
+ cryptoAmount: BigDecimal?,
+ cryptoCurrency: String,
+ decimals: Int,
+ locale: Locale = Locale.getDefault(),
+ ): String {
+ if (cryptoAmount == null) return EMPTY_BALANCE_SIGN
+
+ val formatter = NumberFormat.getCurrencyInstance(locale).apply {
+ currency = usdCurrency
+ maximumFractionDigits = decimals.coerceAtMost(maximumValue = 8)
+ minimumFractionDigits = 2
+ isGroupingUsed = true
+ roundingMode = RoundingMode.HALF_UP
+ }
+
+ return formatter.format(cryptoAmount)
+ .replaceFiatSymbolWithCrypto(
+ fiatCurrencySymbol = usdCurrency.symbol,
+ cryptoCurrencySymbol = cryptoCurrency,
+ )
+ }
+
fun formatCryptoAmountShorted(
cryptoAmount: BigDecimal?,
cryptoCurrency: String,
@@ -305,7 +334,7 @@ object BigDecimalFormatter {
.getOrElse { e ->
// Currency code is not valid ISO 4217 code
if (e is IllegalArgumentException) {
- Currency.getInstance(TEMP_CURRENCY_CODE)
+ usdCurrency
} else {
throw e
}
@@ -316,7 +345,7 @@ object BigDecimalFormatter {
* Adds a proper currency sign for the provided formatted [amount]
* ex. '10.0k" -> "$10.0k", "string" -> "$string"
*/
- fun addCurrencySymbolToStringAmount(
+ private fun addCurrencySymbolToStringAmount(
amount: String,
fiatCurrencyCode: String,
fiatCurrencySymbol: String,
@@ -338,6 +367,32 @@ object BigDecimalFormatter {
return formatted
}
+ /**
+ * Adds a proper currency sign for the provided formatted [amount]
+ * ex. '10.0k" -> "ETH 10.0k", "string" -> "ETH string"
+ */
+ private fun addCryptoCurrencySymbolToStringAmount(
+ amount: String,
+ cryptoCurrencySymbol: String,
+ locale: Locale = Locale.getDefault(),
+ ): String {
+ val sampleAmount = BigDecimal.TEN
+
+ val formatter = NumberFormat.getCurrencyInstance(locale).apply {
+ maximumFractionDigits = 0
+ minimumFractionDigits = 0
+ currency = usdCurrency
+ }
+
+ val formatted = formatter.format(sampleAmount)
+ .replace(sampleAmount.toString(), amount)
+
+ return formatted.replaceFiatSymbolWithCrypto(
+ fiatCurrencySymbol = usdCurrency.symbol,
+ cryptoCurrencySymbol = cryptoCurrencySymbol,
+ )
+ }
+
/**
* "123456.6" -> "$123.457K"
* "12345.6" -> "$123.046K"
@@ -380,6 +435,45 @@ object BigDecimalFormatter {
)
}
+ /**
+ * "123456.6" -> "ETH 123.457K"
+ * "12345.6" -> "123.046K ETH"
+ * Negative amount is not supported
+ * @param threeDigitsMethod if true, will format the amount always with 3 significant digits
+ * @param scale the number of digits to the right of the decimal point
+ */
+ fun formatCompactCryptoAmount(
+ amount: BigDecimal?,
+ cryptoCurrencySymbol: String,
+ threeDigitsMethod: Boolean = false,
+ decimals: Int = 0,
+ locale: Locale = Locale.getDefault(),
+ ): String {
+ if (amount == null) return EMPTY_BALANCE_SIGN
+
+ if (amount < BigDecimal.ONE) {
+ return formatCryptoAmount2(
+ cryptoAmount = amount,
+ cryptoCurrency = cryptoCurrencySymbol,
+ decimals = decimals,
+ locale = locale,
+ )
+ }
+
+ val rawAmount = formatCompactAmount(
+ amount = amount,
+ locale = locale,
+ threeDigitsMethod = threeDigitsMethod,
+ scale = decimals,
+ )
+
+ return addCryptoCurrencySymbolToStringAmount(
+ amount = rawAmount,
+ cryptoCurrencySymbol = cryptoCurrencySymbol,
+ locale = locale,
+ )
+ }
+
/**
* "123456.6" -> "123.457K"
* "12345.6" -> "123.046K"
@@ -413,14 +507,61 @@ object BigDecimalFormatter {
return formatter.format(amount.setScale(scale, RoundingMode.HALF_UP))
} else {
- val value = amount.setScale(scale, RoundingMode.HALF_UP)
+ val scaledAmount = amount.setScale(scale, RoundingMode.HALF_UP)
+ val digitsCount = scaledAmount.longValueExact().toString().count()
+ val digitsToFormat = 5 - when (digitsCount % 3) {
+ 0 -> 0
+ 1 -> 2
+ else -> 1
+ }
val formatter = CompactDecimalFormat.getInstance(
locale,
CompactDecimalFormat.CompactStyle.SHORT,
- )
+ ).apply {
+ minimumSignificantDigits = 2
+ maximumSignificantDigits = digitsToFormat
+ }
- return formatter.format(value)
+ return formatter.format(amount.setScale(scale, RoundingMode.HALF_UP))
+ }
+ }
+
+ // Replaces fiat currency symbol with crypto currency symbol
+ // with respect to the position of the symbol and whitespace
+ private fun String.replaceFiatSymbolWithCrypto(fiatCurrencySymbol: String, cryptoCurrencySymbol: String): String {
+ val str = this
+ if (str.isEmpty()) return str
+
+ return buildString {
+ when {
+ str.endsWith(fiatCurrencySymbol) -> {
+ val withoutSymbol = str.dropLast(fiatCurrencySymbol.length)
+ val last = withoutSymbol.lastOrNull() ?: return cryptoCurrencySymbol
+
+ append(withoutSymbol)
+
+ if (last.isNotWhitespace()) {
+ append("\u2009")
+ }
+
+ append(cryptoCurrencySymbol)
+ }
+ str.startsWith(fiatCurrencySymbol) -> {
+ append(cryptoCurrencySymbol)
+
+ val withoutSymbol = str.drop(fiatCurrencySymbol.length)
+ val first = withoutSymbol.firstOrNull()
+ ?: return cryptoCurrencySymbol
+
+ if (first.isNotWhitespace()) {
+ append("\u2009")
+ }
+
+ append(withoutSymbol)
+ }
+ else -> append(str)
+ }
}
}
diff --git a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatterCompat.kt b/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatterCompat.kt
deleted file mode 100644
index 9218188bef..0000000000
--- a/core/ui/src/main/java/com/tangem/core/ui/utils/BigDecimalFormatterCompat.kt
+++ /dev/null
@@ -1,63 +0,0 @@
-package com.tangem.core.ui.utils
-
-import java.math.BigDecimal
-import java.math.RoundingMode
-import java.util.Locale
-
-internal object BigDecimalFormatterCompat {
-
- /**
- * Formats value as [BigDecimalFormatter.formatCompactFiatAmount] does using only "T","B","M","K" suffixes
- * Used for < API24 compatibility
- */
- @Suppress("MagicNumber", "UnnecessaryParentheses")
- fun formatCompactFiatAmountNoLocaleContext(
- amount: BigDecimal,
- fiatCurrencyCode: String,
- fiatCurrencySymbol: String,
- locale: Locale = Locale.getDefault(),
- ): String {
- val formatted = formatCompactAmountNoLocaleContext(amount)
-
- return BigDecimalFormatter.addCurrencySymbolToStringAmount(
- amount = formatted,
- fiatCurrencyCode = fiatCurrencyCode,
- fiatCurrencySymbol = fiatCurrencySymbol,
- locale = locale,
- )
- }
-
- /**
- * Formats value as [BigDecimalFormatter.formatCompactAmount] does using only "T","B","M","K" suffixes
- * Used for < API24 compatibility
- */
- @Suppress("MagicNumber", "UnnecessaryParentheses")
- fun formatCompactAmountNoLocaleContext(amount: BigDecimal): String {
- val value = amount.setScale(0, RoundingMode.HALF_UP).longValueExact()
-
- val formatted = when {
- value > 1_000_000_000_000L -> {
- val trillion = value / 1_000_000_000_000
- val billion = (value % 1_000_000_000_000) / 1_000_000_000
- "$trillion.${billion}T"
- }
- value > 1_000_000_000L -> {
- val billion = value / 1_000_000_000
- val million = (value % 1_000_000_000) / 1_000_000
- "$billion.${million}B"
- }
- value > 1_000_000L -> {
- val million = value / 1_000_000
- val thousand = (value % 1_000_000) / 1_000
- "$million.${thousand}M"
- }
- value > 1_000L -> {
- val thousand = value / 1_000
- "${thousand}K"
- }
- else -> return value.toString()
- }
-
- return formatted
- }
-}
\ No newline at end of file
diff --git a/core/ui/src/main/res/drawable/ic_transaction_history_claim_rewards_24.xml b/core/ui/src/main/res/drawable/ic_transaction_history_claim_rewards_24.xml
new file mode 100644
index 0000000000..8fb3cae98f
--- /dev/null
+++ b/core/ui/src/main/res/drawable/ic_transaction_history_claim_rewards_24.xml
@@ -0,0 +1,11 @@
+
+
+
+
+
diff --git a/core/ui/src/main/res/drawable/ic_transaction_history_staking.xml b/core/ui/src/main/res/drawable/ic_transaction_history_staking_24.xml
similarity index 100%
rename from core/ui/src/main/res/drawable/ic_transaction_history_staking.xml
rename to core/ui/src/main/res/drawable/ic_transaction_history_staking_24.xml
diff --git a/core/ui/src/main/res/drawable/ic_transaction_history_unstaking.xml b/core/ui/src/main/res/drawable/ic_transaction_history_unstaking_24.xml
similarity index 100%
rename from core/ui/src/main/res/drawable/ic_transaction_history_unstaking.xml
rename to core/ui/src/main/res/drawable/ic_transaction_history_unstaking_24.xml
diff --git a/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt b/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt
index c563b399a8..991f77b883 100644
--- a/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt
+++ b/core/utils/src/main/java/com/tangem/utils/StringsSigns.kt
@@ -8,5 +8,6 @@ object StringsSigns {
const val DASH_SIGN = "—"
const val LOWER_SIGN = "<"
const val TILDE_SIGN = "~"
+ const val INFINITY_SIGN = "∞"
const val NON_BREAKING_SPACE = '\u00A0'
}
\ No newline at end of file
diff --git a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt
index 535b4aca6c..77faa385ef 100644
--- a/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt
+++ b/data/markets/src/main/java/com/tangem/data/markets/converters/TokenMarketInfoConverter.kt
@@ -99,7 +99,7 @@ internal object TokenMarketInfoConverter : Converter TxHistoryItem.AddressType.Contract(address)
is SdkTransactionHistoryItem.AddressType.User -> TxHistoryItem.AddressType.User(address)
+ is SdkTransactionHistoryItem.AddressType.Validator -> TxHistoryItem.AddressType.Validator(address)
}
- private fun SdkTransactionHistoryItem.extractInteractionAddressType(): TxHistoryItem.InteractionAddressType {
- return when (type) {
+ private fun SdkTransactionHistoryItem.extractInteractionAddressType(): TxHistoryItem.InteractionAddressType? {
+ return when (val transactionType = type) {
SdkTransactionHistoryItem.TransactionType.Transfer -> if (isOutgoing) {
mapToInteractionAddressType(destinationType = destinationType)
} else {
mapToInteractionAddressType(sourceType = sourceType)
}
- is SdkTransactionHistoryItem.TransactionType.TronStakingTransactionType -> {
- TxHistoryItem.InteractionAddressType.Staking
- }
is SdkTransactionHistoryItem.TransactionType.ContractMethod,
is SdkTransactionHistoryItem.TransactionType.ContractMethodName,
- -> mapToInteractionAddressType(destinationType)
+ -> mapToInteractionAddressType(destinationType = destinationType)
+
+ is SdkTransactionHistoryItem.TransactionType.TronStakingTransactionType.VoteWitnessContract -> {
+ TxHistoryItem.InteractionAddressType.Validator(address = transactionType.validatorAddress)
+ }
+ else -> null
}
}
@@ -78,6 +81,9 @@ internal class SdkTransactionHistoryItemConverter(
is TransactionHistoryItem.AddressType.User -> TxHistoryItem.InteractionAddressType.User(
destinationType.addressType.address,
)
+ is TransactionHistoryItem.AddressType.Validator -> TxHistoryItem.InteractionAddressType.Validator(
+ destinationType.addressType.address,
+ )
}
}
}
@@ -89,7 +95,9 @@ internal class SdkTransactionHistoryItemConverter(
is TransactionHistoryItem.SourceType.Multiple -> TxHistoryItem.InteractionAddressType.Multiple(
sourceType.addresses,
)
- is TransactionHistoryItem.SourceType.Single -> TxHistoryItem.InteractionAddressType.User(sourceType.address)
+ is TransactionHistoryItem.SourceType.Single -> {
+ TxHistoryItem.InteractionAddressType.User(sourceType.address)
+ }
}
}
}
\ No newline at end of file
diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt
index e62cd5b7eb..306c18cf6a 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionTypeConverter.kt
@@ -27,9 +27,12 @@ internal class SdkTransactionTypeConverter(
TxHistoryItem.TransactionType.TronStakingTransactionType.Unstake
}
is TransactionType.TronStakingTransactionType.VoteWitnessContract -> {
- TxHistoryItem.TransactionType.TronStakingTransactionType.Vote
+ TxHistoryItem.TransactionType.TronStakingTransactionType.Vote(value.validatorAddress)
}
is TransactionType.TronStakingTransactionType.WithdrawBalanceContract -> {
+ TxHistoryItem.TransactionType.TronStakingTransactionType.ClaimRewards
+ }
+ is TransactionType.TronStakingTransactionType.WithdrawExpireUnfreezeContract -> {
TxHistoryItem.TransactionType.TronStakingTransactionType.Withdraw
}
}
diff --git a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetSupportedNetworksUseCase.kt b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetSupportedNetworksUseCase.kt
index 1074a21b02..6a1a96966c 100644
--- a/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetSupportedNetworksUseCase.kt
+++ b/domain/manage-tokens/src/main/kotlin/com/tangem/domain/managetokens/GetSupportedNetworksUseCase.kt
@@ -21,7 +21,7 @@ class GetSupportedNetworksUseCase(
ensureNotNull(networks.takeIf { it.isNotEmpty() }) {
SupportedBlockchainException.EmptyList
- }
+ }.sortedBy(Network::name)
}
}
}
\ No newline at end of file
diff --git a/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt b/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt
index 6f02eb0639..6cac99b488 100644
--- a/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt
+++ b/domain/markets/models/src/main/kotlin/com/tangem/domain/markets/TokenMarketInfo.kt
@@ -46,7 +46,7 @@ data class TokenMarketInfo(
val circulatingSupply: BigDecimal?,
val marketCap: BigDecimal?,
val volume24h: BigDecimal?,
- val totalSupply: BigDecimal?,
+ val maxSupply: BigDecimal?,
val fullyDilutedValuation: BigDecimal?,
)
diff --git a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionType.kt b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionType.kt
index 5098ec0ce1..0c6a45bcc0 100644
--- a/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionType.kt
+++ b/domain/staking/models/src/main/kotlin/com/tangem/domain/staking/model/stakekit/action/StakingActionType.kt
@@ -17,4 +17,27 @@ enum class StakingActionType {
REBOND,
MIGRATE,
UNKNOWN,
+ ;
+
+ companion object {
+ val StakingActionType.asAnalyticName
+ get() = when (this) {
+ STAKE -> "Stake"
+ UNSTAKE -> "Unstake"
+ CLAIM_REWARDS -> "Claim Rewards"
+ RESTAKE_REWARDS -> "Restake Rewards"
+ WITHDRAW -> "Withdraw"
+ RESTAKE -> "Restake"
+ CLAIM_UNSTAKED -> "Claim Unstaked"
+ UNLOCK_LOCKED -> "Unlock Locked"
+ STAKE_LOCKED -> "Stake Locked"
+ VOTE -> "Vote"
+ REVOKE -> "Revoke"
+ VOTE_LOCKED -> "Vote Locked"
+ REVOTE -> "Revote"
+ REBOND -> "Rebond"
+ MIGRATE -> "Migrate"
+ UNKNOWN -> "Unknown"
+ }
+ }
}
\ No newline at end of file
diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt
index bea881092e..ac688c34ef 100644
--- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt
+++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/CurrenciesStatusesOperations.kt
@@ -151,13 +151,12 @@ internal class CurrenciesStatusesOperations(
val currenciesFlow = combine(
getQuotes(currenciesIds),
getNetworksStatuses(networks),
- getYieldBalances(nonEmptyCurrencies),
- ) { maybeQuotes, maybeNetworksStatuses, maybeYieldBalances ->
+ ) { maybeQuotes, maybeNetworksStatuses ->
createCurrenciesStatuses(
currencies = nonEmptyCurrencies,
maybeQuotes = maybeQuotes,
maybeNetworkStatuses = maybeNetworksStatuses,
- maybeYieldBalances = maybeYieldBalances,
+ maybeYieldBalances = null,
)
}
@@ -403,15 +402,6 @@ internal class CurrenciesStatusesOperations(
.onEmpty { emit(Error.EmptyNetworksStatuses.left()) }
}
- private fun getYieldBalances(cryptoCurrencies: List): Flow> {
- return stakingRepository.getMultiYieldBalanceFlow(
- userWalletId = userWalletId,
- cryptoCurrencies = cryptoCurrencies,
- ).map> { it.right() }
- .catch { emit(Error.DataError(it).left()) }
- .onEmpty { emit(Error.EmptyYieldBalances.left()) }
- }
-
private suspend fun getYieldBalancesSync(
cryptoCurrencies: List,
): Either {
diff --git a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt
index 68677bdd63..f96905a1ed 100644
--- a/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt
+++ b/domain/txhistory/models/src/main/kotlin/com/tangem/domain/txhistory/models/TxHistoryItem.kt
@@ -8,7 +8,7 @@ data class TxHistoryItem(
val isOutgoing: Boolean,
val destinationType: DestinationType,
val sourceType: SourceType,
- val interactionAddressType: InteractionAddressType,
+ val interactionAddressType: InteractionAddressType?,
val status: TransactionStatus,
val type: TransactionType,
val amount: BigDecimal,
@@ -30,6 +30,7 @@ data class TxHistoryItem(
data class User(override val address: String) : AddressType()
data class Contract(override val address: String) : AddressType()
+ data class Validator(override val address: String) : AddressType()
}
sealed interface TransactionType {
@@ -40,10 +41,11 @@ data class TxHistoryItem(
data class Operation(val name: String) : TransactionType
sealed interface TronStakingTransactionType : TransactionType {
- data object Vote : TronStakingTransactionType
- data object Withdraw : TronStakingTransactionType
+ data class Vote(val validatorAddress: String) : TronStakingTransactionType
+ data object ClaimRewards : TronStakingTransactionType
data object Stake : TronStakingTransactionType
data object Unstake : TronStakingTransactionType
+ data object Withdraw : TronStakingTransactionType
}
}
@@ -54,7 +56,7 @@ data class TxHistoryItem(
}
sealed class InteractionAddressType {
- data object Staking : InteractionAddressType()
+ data class Validator(val address: String) : InteractionAddressType()
data class User(val address: String) : InteractionAddressType()
data class Contract(val address: String) : InteractionAddressType()
data class Multiple(val addresses: List) : InteractionAddressType()
diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/AddCustomTokenComponent.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/AddCustomTokenComponent.kt
index 4539527ce5..0eebb14aa3 100644
--- a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/AddCustomTokenComponent.kt
+++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/AddCustomTokenComponent.kt
@@ -8,6 +8,7 @@ interface AddCustomTokenComponent : ComposableBottomSheetComponent {
data class Params(
val userWalletId: UserWalletId,
+ val source: ManageTokensSource,
val onDismiss: () -> Unit,
val onCurrencyAdded: () -> Unit,
)
diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensComponent.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensComponent.kt
index c327e0ac05..abb4b91de9 100644
--- a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensComponent.kt
+++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensComponent.kt
@@ -6,7 +6,10 @@ import com.tangem.domain.wallets.models.UserWalletId
interface ManageTokensComponent : ComposableContentComponent {
- data class Params(val userWalletId: UserWalletId?)
+ data class Params(
+ val userWalletId: UserWalletId?,
+ val source: ManageTokensSource,
+ )
interface Factory : ComponentFactory
}
\ No newline at end of file
diff --git a/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt
new file mode 100644
index 0000000000..b55fac8e8c
--- /dev/null
+++ b/features/manage-tokens/api/src/main/kotlin/com/tangem/features/managetokens/component/ManageTokensSource.kt
@@ -0,0 +1,7 @@
+package com.tangem.features.managetokens.component
+
+enum class ManageTokensSource {
+ STORIES,
+ ONBOARDING,
+ SETTINGS,
+}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/build.gradle.kts b/features/manage-tokens/impl/build.gradle.kts
index a506043ad2..f018c97984 100644
--- a/features/manage-tokens/impl/build.gradle.kts
+++ b/features/manage-tokens/impl/build.gradle.kts
@@ -20,6 +20,7 @@ dependencies {
implementation(projects.core.ui)
implementation(projects.common.routing)
implementation(projects.core.featuretoggles)
+ implementation(projects.core.analytics)
/* Project - Domain */
implementation(projects.domain.manageTokens)
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/analytics/CustomTokenAnalyticsEvent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/analytics/CustomTokenAnalyticsEvent.kt
new file mode 100644
index 0000000000..b753960993
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/analytics/CustomTokenAnalyticsEvent.kt
@@ -0,0 +1,77 @@
+package com.tangem.features.managetokens.analytics
+
+import com.tangem.core.analytics.models.AnalyticsEvent
+import com.tangem.core.analytics.models.AnalyticsParam
+import com.tangem.features.managetokens.component.ManageTokensSource
+
+internal sealed class CustomTokenAnalyticsEvent(
+ event: String,
+ params: Map = mapOf(),
+) : AnalyticsEvent(
+ category = "Manage Tokens / Custom",
+ event = event,
+ params = params,
+) {
+
+ class ScreenOpened(source: ManageTokensSource) : CustomTokenAnalyticsEvent(
+ event = "Custom Token Screen Opened",
+ params = mapOf(AnalyticsParam.Key.SOURCE to source.name),
+ )
+
+ class CustomTokenWasAdded(
+ currencySymbol: String,
+ derivationPath: String,
+ source: ManageTokensSource,
+ ) : CustomTokenAnalyticsEvent(
+ event = "Custom Token Was Added",
+ params = mapOf(
+ AnalyticsParam.Key.TOKEN_PARAM to currencySymbol,
+ AnalyticsParam.Key.DERIVATION to derivationPath,
+ AnalyticsParam.Key.SOURCE to source.name,
+ ),
+ )
+
+ class NetworkSelected(networkName: String, source: ManageTokensSource) : CustomTokenAnalyticsEvent(
+ event = "Custom Token Network Selected",
+ params = mapOf(
+ AnalyticsParam.Key.BLOCKCHAIN to networkName,
+ AnalyticsParam.Key.SOURCE to source.name,
+ ),
+ )
+
+ class DerivationSelected(derivationName: String, source: ManageTokensSource) : CustomTokenAnalyticsEvent(
+ event = "Custom Token Derivation Selected",
+ params = mapOf(
+ AnalyticsParam.Key.DERIVATION to derivationName,
+ AnalyticsParam.Key.SOURCE to source.name,
+ ),
+ )
+
+ class Address(isValid: Boolean, source: ManageTokensSource) : CustomTokenAnalyticsEvent(
+ event = "Custom Token Address",
+ params = mapOf(
+ AnalyticsParam.Key.VALIDATION to AnalyticsParam.Validation.from(isValid),
+ AnalyticsParam.Key.SOURCE to source.name,
+ ),
+ )
+
+ class Name(source: ManageTokensSource) : CustomTokenAnalyticsEvent(
+ event = "Custom Token Name",
+ params = mapOf(AnalyticsParam.Key.SOURCE to source.name),
+ )
+
+ class Symbol(source: ManageTokensSource) : CustomTokenAnalyticsEvent(
+ event = "Custom Token Symbol",
+ params = mapOf(AnalyticsParam.Key.SOURCE to source.name),
+ )
+
+ class Decimals(source: ManageTokensSource) : CustomTokenAnalyticsEvent(
+ event = "Custom Token Decimals",
+ params = mapOf(AnalyticsParam.Key.SOURCE to source.name),
+ )
+
+ class ButtonCustomToken(source: ManageTokensSource) : CustomTokenAnalyticsEvent(
+ event = "Button - Custom Token",
+ params = mapOf(AnalyticsParam.Key.SOURCE to source.name),
+ )
+}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/analytics/ManageTokensAnalyticEvent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/analytics/ManageTokensAnalyticEvent.kt
new file mode 100644
index 0000000000..bc70e1e736
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/analytics/ManageTokensAnalyticEvent.kt
@@ -0,0 +1,55 @@
+package com.tangem.features.managetokens.analytics
+
+import com.tangem.core.analytics.models.AnalyticsEvent
+import com.tangem.core.analytics.models.AnalyticsParam
+import com.tangem.features.managetokens.component.ManageTokensSource
+
+internal sealed class ManageTokensAnalyticEvent(
+ event: String,
+ params: Map = mapOf(),
+) : AnalyticsEvent(
+ category = "ManageTokens",
+ event = event,
+ params = params,
+) {
+
+ class ScreenOpened(source: ManageTokensSource) : ManageTokensAnalyticEvent(
+ event = "Manage Tokens Screen Opened",
+ params = mapOf(AnalyticsParam.Key.SOURCE to source.name),
+ )
+
+ class TokensIsNotFound(query: String, source: ManageTokensSource) : ManageTokensAnalyticEvent(
+ event = "Token Is Not Found",
+ params = mapOf(
+ AnalyticsParam.Key.INPUT to query,
+ AnalyticsParam.Key.SOURCE to source.name,
+ ),
+ )
+
+ class TokenSwitcherChanged(
+ tokenSymbol: String,
+ isSelected: Boolean,
+ source: ManageTokensSource,
+ ) : ManageTokensAnalyticEvent(
+ event = "Token Switcher Changed",
+ params = mapOf(
+ AnalyticsParam.Key.TOKEN_PARAM to tokenSymbol,
+ AnalyticsParam.Key.STATE to AnalyticsParam.OnOffState.from(isSelected),
+ AnalyticsParam.Key.SOURCE to source.name,
+ ),
+ )
+
+ class TokenAdded(
+ tokensCount: Int,
+ source: ManageTokensSource,
+ ) : ManageTokensAnalyticEvent(
+ event = "Token Added",
+ params = mapOf(
+ AnalyticsParam.Key.COUNT to tokensCount.toString(),
+ AnalyticsParam.Key.SOURCE to source.name,
+ ),
+ )
+
+ // TODO: Will be used later
+ data object ButtonLater : ManageTokensAnalyticEvent(event = "Button - Later")
+}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenFormComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenFormComponent.kt
index b7c219e560..e2f093a62e 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenFormComponent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/CustomTokenFormComponent.kt
@@ -14,6 +14,7 @@ internal interface CustomTokenFormComponent : ComposableContentComponent {
val network: SelectedNetwork,
val derivationPath: SelectedDerivationPath?,
val formValues: CustomTokenFormValues,
+ val source: ManageTokensSource,
val onSelectNetworkClick: (CustomTokenFormValues) -> Unit,
val onSelectDerivationPathClick: (CustomTokenFormValues) -> Unit,
val onCurrencyAdded: () -> Unit,
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt
index bde22e12c6..e9444a6d3d 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultAddCustomTokenComponent.kt
@@ -8,10 +8,12 @@ import com.arkivanov.decompose.extensions.compose.jetpack.stack.Children
import com.arkivanov.decompose.extensions.compose.jetpack.stack.animation.stackAnimation
import com.arkivanov.decompose.extensions.compose.jetpack.subscribeAsState
import com.arkivanov.decompose.router.stack.*
+import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.decompose.ComposableContentComponent
+import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
import com.tangem.features.managetokens.component.AddCustomTokenComponent
import com.tangem.features.managetokens.component.CustomTokenFormComponent
import com.tangem.features.managetokens.component.CustomTokenSelectorComponent
@@ -29,6 +31,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
@Assisted private val params: AddCustomTokenComponent.Params,
private val selectorComponentFactory: CustomTokenSelectorComponent.Factory,
private val formComponentFactory: CustomTokenFormComponent.Factory,
+ private val analyticsEventHandler: AnalyticsEventHandler,
) : AddCustomTokenComponent, AppComponentContext by context {
private val navigation = StackNavigation()
@@ -45,6 +48,10 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
childFactory = ::contentChild,
)
+ init {
+ analyticsEventHandler.send(CustomTokenAnalyticsEvent.ScreenOpened(params.source))
+ }
+
override fun dismiss() {
params.onDismiss()
}
@@ -85,9 +92,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
params = CustomTokenSelectorComponent.Params.NetworkSelector(
userWalletId = config.userWalletId,
selectedNetwork = null,
- onNetworkSelected = { network ->
- showForm(network = network)
- },
+ onNetworkSelected = ::changeSelectedNetwork,
),
)
}
@@ -97,9 +102,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
params = CustomTokenSelectorComponent.Params.NetworkSelector(
userWalletId = config.userWalletId,
selectedNetwork = config.selectedNetwork,
- onNetworkSelected = { network ->
- showForm(network = network)
- },
+ onNetworkSelected = ::changeSelectedNetwork,
),
)
}
@@ -112,9 +115,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
"Network is not selected"
},
selectedDerivationPath = config.selectedDerivationPath,
- onDerivationPathSelected = { derivationPath ->
- showForm(derivationPath = derivationPath)
- },
+ onDerivationPathSelected = ::changeDerivationPath,
),
)
}
@@ -128,6 +129,7 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
},
derivationPath = config.selectedDerivationPath,
formValues = config.formValues,
+ source = params.source,
onSelectNetworkClick = ::showNetworkSelector,
onSelectDerivationPathClick = ::showDerivationPathSelector,
onCurrencyAdded = ::dismissAndNotify,
@@ -136,6 +138,26 @@ internal class DefaultAddCustomTokenComponent @AssistedInject constructor(
}
}
+ private fun changeSelectedNetwork(network: SelectedNetwork) {
+ val event = CustomTokenAnalyticsEvent.NetworkSelected(
+ networkName = network.name,
+ source = params.source,
+ )
+ analyticsEventHandler.send(event)
+
+ showForm(network = network)
+ }
+
+ private fun changeDerivationPath(derivationPath: SelectedDerivationPath) {
+ val event = CustomTokenAnalyticsEvent.DerivationSelected(
+ derivationName = derivationPath.name,
+ source = params.source,
+ )
+ analyticsEventHandler.send(event)
+
+ showForm(derivationPath = derivationPath)
+ }
+
private fun showDerivationPathSelector(formValues: CustomTokenFormValues) {
val currentConfig = contentStack.value.active.configuration
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultCustomTokenDerivationInputComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultCustomTokenDerivationInputComponent.kt
index 0695290569..7066fa1307 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultCustomTokenDerivationInputComponent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultCustomTokenDerivationInputComponent.kt
@@ -7,7 +7,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import arrow.core.getOrElse
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.extensions.resourceReference
-import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.managetokens.ValidateDerivationPathUseCase
import com.tangem.domain.managetokens.model.exceptoin.DerivationPathValidationException
import com.tangem.domain.tokens.model.Network
@@ -111,7 +110,8 @@ internal class DefaultCustomTokenDerivationInputComponent @AssistedInject constr
val model = SelectedDerivationPath(
id = null,
value = Network.DerivationPath.Custom(value),
- networkName = stringReference(value = value),
+ name = value,
+ isDefault = false,
)
params.onConfirm(model)
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultManageTokensComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultManageTokensComponent.kt
index 2b513d23cb..9a8b81b83a 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultManageTokensComponent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/impl/DefaultManageTokensComponent.kt
@@ -24,7 +24,7 @@ import dagger.assisted.AssistedInject
internal class DefaultManageTokensComponent @AssistedInject constructor(
@Assisted context: AppComponentContext,
- @Assisted params: ManageTokensComponent.Params,
+ @Assisted private val params: ManageTokensComponent.Params,
private val addCustomTokenComponentFactory: AddCustomTokenComponent.Factory,
) : ManageTokensComponent, AppComponentContext by context {
@@ -61,6 +61,7 @@ internal class DefaultManageTokensComponent @AssistedInject constructor(
context = childByContext(componentContext),
params = AddCustomTokenComponent.Params(
userWalletId = config.userWalletId,
+ source = params.source,
onDismiss = model.bottomSheetNavigation::dismiss,
onCurrencyAdded = model::reloadList,
),
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenFormComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenFormComponent.kt
index fa909e5257..5d995e9b1c 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenFormComponent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenFormComponent.kt
@@ -1,5 +1,6 @@
package com.tangem.features.managetokens.component.preview
+import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.notifications.NotificationConfig
@@ -13,6 +14,7 @@ import com.tangem.features.managetokens.impl.R
import com.tangem.features.managetokens.ui.CustomTokenFormContent
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.toPersistentMap
internal class PreviewCustomTokenFormComponent(
networkName: ClickableFieldUM = PreviewCustomTokenFormComponent.networkName,
@@ -48,30 +50,40 @@ internal class PreviewCustomTokenFormComponent(
onClick = {},
)
val tokenForm: CustomTokenFormUM.TokenFormUM = CustomTokenFormUM.TokenFormUM(
- contractAddress = TextInputFieldUM(
- label = resourceReference(R.string.custom_token_contract_address_input_title),
- placeholder = stringReference(value = "0x000000000000000000000000000"),
- value = "",
- onValueChange = {},
- ),
- name = TextInputFieldUM(
- label = resourceReference(R.string.custom_token_name_input_title),
- placeholder = stringReference(value = "E.g. USD Coin"),
- value = "",
- onValueChange = {},
- ),
- symbol = TextInputFieldUM(
- label = resourceReference(R.string.custom_token_token_symbol_input_title),
- placeholder = stringReference(value = "E.g. USDC"),
- value = "",
- onValueChange = {},
- ),
- decimals = TextInputFieldUM(
- label = resourceReference(R.string.custom_token_decimals_input_title),
- placeholder = stringReference(value = "8"),
- value = "",
- onValueChange = {},
- ),
+ fields = mapOf(
+ CustomTokenFormUM.TokenFormUM.Field.CONTRACT_ADDRESS to TextInputFieldUM(
+ label = resourceReference(R.string.custom_token_contract_address_input_title),
+ placeholder = stringReference(value = "0x000000000000000000000000000"),
+ value = "",
+ keyboardOptions = KeyboardOptions(),
+ onValueChange = {},
+ onFocusChange = {},
+ ),
+ CustomTokenFormUM.TokenFormUM.Field.NAME to TextInputFieldUM(
+ label = resourceReference(R.string.custom_token_name_input_title),
+ placeholder = stringReference(value = "E.g. USD Coin"),
+ value = "",
+ keyboardOptions = KeyboardOptions(),
+ onValueChange = {},
+ onFocusChange = {},
+ ),
+ CustomTokenFormUM.TokenFormUM.Field.SYMBOL to TextInputFieldUM(
+ label = resourceReference(R.string.custom_token_token_symbol_input_title),
+ placeholder = stringReference(value = "E.g. USDC"),
+ value = "",
+ keyboardOptions = KeyboardOptions(),
+ onValueChange = {},
+ onFocusChange = {},
+ ),
+ CustomTokenFormUM.TokenFormUM.Field.DECIMALS to TextInputFieldUM(
+ label = resourceReference(R.string.custom_token_decimals_input_title),
+ placeholder = stringReference(value = "8"),
+ value = "",
+ keyboardOptions = KeyboardOptions(),
+ onValueChange = {},
+ onFocusChange = {},
+ ),
+ ).toPersistentMap(),
)
val notifications: PersistentList = persistentListOf(
CustomTokenFormUM.NotificationUM(
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt
index 24b389d850..baef39e2e5 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/component/preview/PreviewCustomTokenSelectorComponent.kt
@@ -31,13 +31,14 @@ internal class PreviewCustomTokenSelectorComponent(
val d = SelectedDerivationPath(
id = Network.ID(index.toString()),
value = Network.DerivationPath.Card("m/44'/0'/0'/0/$index"),
- networkName = stringReference(value = "Network $index"),
+ name = "Network $index",
+ isDefault = false,
)
DerivationPathUM(
id = d.id?.value ?: "",
value = d.value.value.orEmpty(),
- networkName = d.networkName,
+ networkName = stringReference(d.name),
isSelected = d.value == params.selectedDerivationPath?.value,
onSelectedStateChange = { params.onDerivationPathSelected(d) },
)
@@ -45,7 +46,7 @@ internal class PreviewCustomTokenSelectorComponent(
is Params.NetworkSelector -> {
val n = SelectedNetwork(
id = Network.ID(index.toString()),
- name = stringReference(value = "Network $index"),
+ name = "Network $index",
derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/$index"),
canHandleTokens = false,
)
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/AddCustomTokenConfig.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/AddCustomTokenConfig.kt
index 1c5de5c702..96e65e61d5 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/AddCustomTokenConfig.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/AddCustomTokenConfig.kt
@@ -1,7 +1,6 @@
package com.tangem.features.managetokens.entity.customtoken
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
-import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.serialization.Serializable
@@ -27,7 +26,7 @@ internal data class AddCustomTokenConfig(
@Serializable
internal data class SelectedNetwork(
val id: Network.ID,
- val name: TextReference,
+ val name: String,
val derivationPath: Network.DerivationPath,
val canHandleTokens: Boolean,
)
@@ -36,5 +35,6 @@ internal data class SelectedNetwork(
internal data class SelectedDerivationPath(
val id: Network.ID?,
val value: Network.DerivationPath,
- val networkName: TextReference,
+ val name: String,
+ val isDefault: Boolean,
)
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormUM.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormUM.kt
index fe72248f0d..b4fbaea7be 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormUM.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormUM.kt
@@ -1,8 +1,10 @@
package com.tangem.features.managetokens.entity.customtoken
+import androidx.compose.foundation.text.KeyboardOptions
import com.tangem.core.ui.components.notifications.NotificationConfig
import com.tangem.core.ui.extensions.TextReference
import kotlinx.collections.immutable.PersistentList
+import kotlinx.collections.immutable.PersistentMap
import kotlinx.collections.immutable.persistentListOf
internal data class CustomTokenFormUM(
@@ -16,12 +18,17 @@ internal data class CustomTokenFormUM(
) {
data class TokenFormUM(
- val contractAddress: TextInputFieldUM,
- val name: TextInputFieldUM,
- val symbol: TextInputFieldUM,
- val decimals: TextInputFieldUM,
+ val fields: PersistentMap,
val wasFilled: Boolean = false,
- )
+ ) {
+
+ enum class Field {
+ CONTRACT_ADDRESS,
+ NAME,
+ SYMBOL,
+ DECIMALS,
+ }
+ }
data class NotificationUM(
val id: String,
@@ -32,10 +39,13 @@ internal data class CustomTokenFormUM(
internal data class TextInputFieldUM(
val label: TextReference,
val placeholder: TextReference,
+ val keyboardOptions: KeyboardOptions,
val value: String = "",
+ val isFocused: Boolean = false,
val error: TextReference? = null,
val isEnabled: Boolean = true,
val onValueChange: (String) -> Unit,
+ val onFocusChange: (Boolean) -> Unit,
)
internal data class ClickableFieldUM(
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormValues.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormValues.kt
index 983eb8e61a..c391cfdc76 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormValues.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/entity/customtoken/CustomTokenFormValues.kt
@@ -1,45 +1,35 @@
package com.tangem.features.managetokens.entity.customtoken
-import com.tangem.domain.managetokens.model.AddCustomTokenForm
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM
+import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM.Field
+import kotlinx.collections.immutable.toPersistentMap
import kotlinx.serialization.Serializable
-@JvmInline
@Serializable
-internal value class CustomTokenFormValues private constructor(private val values: List) {
-
- constructor() : this(values = emptyList())
+internal class CustomTokenFormValues(
+ private val contractAddress: String = "",
+ private val name: String = "",
+ private val symbol: String = "",
+ private val decimals: String = "",
+) {
constructor(form: TokenFormUM?) : this(
- values = if (form == null) {
- emptyList()
- } else {
- listOf(
- form.contractAddress.value,
- form.name.value,
- form.symbol.value,
- form.decimals.value,
- )
- },
+ contractAddress = form?.fields?.get(Field.CONTRACT_ADDRESS)?.value.orEmpty(),
+ name = form?.fields?.get(Field.NAME)?.value.orEmpty(),
+ symbol = form?.fields?.get(Field.SYMBOL)?.value.orEmpty(),
+ decimals = form?.fields?.get(Field.DECIMALS)?.value.orEmpty(),
)
fun fillValues(to: TokenFormUM): TokenFormUM = to.copy(
- contractAddress = to.contractAddress.copy(value = values.getOrElse(index = 0) { "" }),
- name = to.name.copy(value = values.getOrElse(index = 1) { "" }),
- symbol = to.symbol.copy(value = values.getOrElse(index = 2) { "" }),
- decimals = to.decimals.copy(value = values.getOrElse(index = 3) { "" }),
- )
-
- fun toDomainModel(): AddCustomTokenForm.Raw? {
- return if (values.isEmpty()) {
- null
- } else {
- AddCustomTokenForm.Raw(
- contractAddress = values.getOrElse(index = 0) { "" },
- name = values.getOrElse(index = 1) { "" },
- symbol = values.getOrElse(index = 2) { "" },
- decimals = values.getOrElse(index = 3) { "" },
+ fields = to.fields.mapValues { (key, field) ->
+ field.copy(
+ value = when (key) {
+ Field.CONTRACT_ADDRESS -> contractAddress
+ Field.NAME -> name
+ Field.SYMBOL -> symbol
+ Field.DECIMALS -> decimals
+ },
)
- }
- }
+ }.toPersistentMap(),
+ )
}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt
index 4e678f1bb4..c1c6f9922e 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenFormModel.kt
@@ -2,6 +2,7 @@ package com.tangem.features.managetokens.model
import androidx.compose.ui.res.stringResource
import arrow.core.getOrElse
+import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@@ -15,22 +16,27 @@ import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationE
import com.tangem.domain.tokens.AddCryptoCurrenciesUseCase
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.domain.tokens.model.Network
+import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
import com.tangem.features.managetokens.component.CustomTokenFormComponent
import com.tangem.features.managetokens.entity.customtoken.ClickableFieldUM
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
+import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM.Field
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormValues
import com.tangem.features.managetokens.entity.customtoken.TextInputFieldUM
import com.tangem.features.managetokens.impl.R
+import com.tangem.features.managetokens.utils.CustomCurrencyFormBuilder
import com.tangem.features.managetokens.utils.CustomCurrencyValidator
import com.tangem.features.managetokens.utils.mapper.mapToDomainModel
import com.tangem.features.managetokens.utils.ui.*
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
+import kotlinx.collections.immutable.mutate
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
+@Suppress("LongParameterList")
@ComponentScoped
internal class CustomTokenFormModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
@@ -38,6 +44,8 @@ internal class CustomTokenFormModel @Inject constructor(
private val addCryptoCurrenciesUseCase: AddCryptoCurrenciesUseCase,
private val derivePublicKeysUseCase: DerivePublicKeysUseCase,
private val messageSender: UiMessageSender,
+ private val customTokenFormManager: CustomCurrencyFormBuilder,
+ private val analyticsEventHandler: AnalyticsEventHandler,
paramsContainer: ParamsContainer,
) : Model() {
@@ -68,20 +76,23 @@ internal class CustomTokenFormModel @Inject constructor(
return CustomTokenFormUM(
networkName = ClickableFieldUM(
label = resourceReference(R.string.custom_token_network_input_title),
- value = params.network.name,
+ value = stringReference(params.network.name),
onClick = ::selectNetwork,
),
tokenForm = if (params.network.canHandleTokens) {
- getInitialTokenForm()
+ customTokenFormManager.buildForm(
+ updateFormFieldValue = ::updateFormFieldValue,
+ updateFormFieldFocus = ::updateFormFieldFocus,
+ )
} else {
null
},
derivationPath = ClickableFieldUM(
label = resourceReference(R.string.custom_token_derivation_path),
- value = if (params.derivationPath == null || params.derivationPath.id == params.network.id) {
+ value = if (params.derivationPath == null || params.derivationPath.isDefault) {
resourceReference(R.string.custom_token_derivation_path_default)
} else {
- params.derivationPath.networkName
+ stringReference(params.derivationPath.name)
},
onClick = ::selectDerivationPath,
),
@@ -241,79 +252,52 @@ internal class CustomTokenFormModel @Inject constructor(
}
}
- private fun getInitialTokenForm(): CustomTokenFormUM.TokenFormUM {
- val formValues = params.formValues
-
- val form = CustomTokenFormUM.TokenFormUM(
- contractAddress = TextInputFieldUM(
- label = resourceReference(R.string.custom_token_contract_address_input_title),
- placeholder = stringReference(CONTRACT_ADDRESS_PLACEHOLDER),
- onValueChange = ::updateContractAddress,
- ),
- name = TextInputFieldUM(
- label = resourceReference(R.string.custom_token_name_input_title),
- placeholder = resourceReference(R.string.custom_token_name_input_placeholder),
- onValueChange = ::updateTokenName,
- ),
- symbol = TextInputFieldUM(
- label = resourceReference(R.string.custom_token_token_symbol_input_title),
- placeholder = resourceReference(R.string.custom_token_token_symbol_input_placeholder),
- onValueChange = ::updateTokenSymbol,
- ),
- decimals = TextInputFieldUM(
- label = resourceReference(R.string.custom_token_decimals_input_title),
- placeholder = stringReference(DECIMALS_PLACEHOLDER),
- onValueChange = ::updateDecimals,
- ),
- )
-
- return formValues.fillValues(form)
- }
-
private fun getDerivationPath(): Network.DerivationPath {
return params.derivationPath?.value ?: params.network.derivationPath
}
- private fun updateContractAddress(value: String) {
+ private fun updateFormFieldValue(field: Field, value: String) {
state.update { state ->
state.updateTokenForm {
+ val fieldValue = fields.getValue(field)
+
+ if (!fieldValue.isEnabled) return@updateTokenForm this
+
+ val updatedFieldValue = fieldValue.copy(
+ value = value,
+ )
+ val updatedFields = fields.mutate {
+ it[field] = updatedFieldValue
+ }
+
copy(
- contractAddress = contractAddress.updateValue(value),
+ fields = updatedFields,
wasFilled = false,
)
}
}
}
- private fun updateTokenName(value: String) {
+ private fun updateFormFieldFocus(field: Field, isFocused: Boolean) {
state.update { state ->
state.updateTokenForm {
- copy(
- name = name.updateValue(value),
- wasFilled = false,
- )
- }
- }
- }
+ val fieldValue = fields.getValue(field)
- private fun updateTokenSymbol(value: String) {
- state.update { state ->
- state.updateTokenForm {
- copy(
- symbol = symbol.updateValue(value),
- wasFilled = false,
- )
- }
- }
- }
+ if (!fieldValue.isEnabled) return@updateTokenForm this
- private fun updateDecimals(value: String) {
- state.update { state ->
- state.updateTokenForm {
- copy(
- decimals = decimals.updateValue(value),
- wasFilled = false,
+ val updatedFieldValue = fieldValue.copy(
+ isFocused = isFocused,
)
+ val updatedFields = fields.mutate {
+ it[field] = updatedFieldValue
+ }
+
+ // Checking if a field is out of focus
+ if (fieldValue.isFocused && !isFocused && fieldValue.value.isNotEmpty()) {
+ sendFieldAnalyticsEvent(field, fieldValue)
+ }
+
+ copy(fields = updatedFields)
}
}
}
@@ -337,6 +321,13 @@ internal class CustomTokenFormModel @Inject constructor(
return@resource
}
+ val event = CustomTokenAnalyticsEvent.CustomTokenWasAdded(
+ currencySymbol = currency.symbol,
+ derivationPath = currency.network.derivationPath.value.orEmpty(),
+ source = params.source,
+ )
+ analyticsEventHandler.send(event)
+
derivePublicKeysUseCase(params.userWalletId, listOf(currency)).getOrElse {
Timber.e(it, "Failed to derive public keys")
showErrorDialog()
@@ -360,8 +351,17 @@ internal class CustomTokenFormModel @Inject constructor(
params.onSelectDerivationPathClick(CustomTokenFormValues(state.value.tokenForm))
}
- private companion object {
- const val CONTRACT_ADDRESS_PLACEHOLDER = "0x000000000000000000000000000..."
- const val DECIMALS_PLACEHOLDER = "0"
+ private fun sendFieldAnalyticsEvent(field: Field, fieldValue: TextInputFieldUM) {
+ val event = when (field) {
+ Field.CONTRACT_ADDRESS -> CustomTokenAnalyticsEvent.Address(
+ isValid = fieldValue.error == null,
+ source = params.source,
+ )
+ Field.NAME -> CustomTokenAnalyticsEvent.Name(params.source)
+ Field.SYMBOL -> CustomTokenAnalyticsEvent.Symbol(params.source)
+ Field.DECIMALS -> CustomTokenAnalyticsEvent.Decimals(params.source)
+ }
+
+ analyticsEventHandler.send(event)
}
}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt
index 611c6a1fe9..998307191a 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/CustomTokenSelectorModel.kt
@@ -8,7 +8,6 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.extensions.resourceReference
-import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.managetokens.GetSupportedNetworksUseCase
import com.tangem.domain.tokens.model.Network
@@ -90,7 +89,7 @@ internal class CustomTokenSelectorModel @Inject constructor(
onSelectedStateChange = {
val model = SelectedNetwork(
id = network.id,
- name = stringReference(network.name),
+ name = network.name,
derivationPath = network.derivationPath,
canHandleTokens = network.canHandleTokens,
)
@@ -109,8 +108,9 @@ internal class CustomTokenSelectorModel @Inject constructor(
onSelectedStateChange = {
val model = SelectedDerivationPath(
id = network.id,
- networkName = resourceReference(R.string.custom_token_derivation_path_default),
+ name = network.name,
value = network.derivationPath,
+ isDefault = true,
)
selector.onDerivationPathSelected(model)
@@ -133,8 +133,9 @@ internal class CustomTokenSelectorModel @Inject constructor(
onSelectedStateChange = {
val model = SelectedDerivationPath(
id = network.id,
- networkName = stringReference(network.name),
+ name = network.name,
value = network.derivationPath,
+ isDefault = false,
)
selector.onDerivationPathSelected(model)
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt
index 5c4d924942..b72993de69 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/model/ManageTokensModel.kt
@@ -1,7 +1,9 @@
package com.tangem.features.managetokens.model
+import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate
+import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
@@ -16,6 +18,8 @@ import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.managetokens.SaveManagedTokensUseCase
import com.tangem.domain.wallets.models.UserWalletId
+import com.tangem.features.managetokens.analytics.CustomTokenAnalyticsEvent
+import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
import com.tangem.features.managetokens.component.ManageTokensComponent
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.features.managetokens.entity.managetokens.ManageTokensBottomSheetConfig
@@ -35,6 +39,7 @@ import kotlinx.coroutines.launch
import timber.log.Timber
import javax.inject.Inject
+@Suppress("LongParameterList")
@ComponentScoped
internal class ManageTokensModel @Inject constructor(
override val dispatchers: CoroutineDispatcherProvider,
@@ -42,6 +47,7 @@ internal class ManageTokensModel @Inject constructor(
private val manageTokensListManager: ManageTokensListManager,
private val messageSender: UiMessageSender,
private val saveManagedTokensUseCase: SaveManagedTokensUseCase,
+ private val analyticsEventHandler: AnalyticsEventHandler,
paramsContainer: ParamsContainer,
) : Model() {
@@ -68,7 +74,7 @@ internal class ManageTokensModel @Inject constructor(
observeSearchQueryChanges()
modelScope.launch {
- manageTokensListManager.launchPagination(params.userWalletId)
+ manageTokensListManager.launchPagination(params)
}
}
@@ -79,6 +85,8 @@ internal class ManageTokensModel @Inject constructor(
}
private fun getInitialState(userWalletId: UserWalletId?): ManageTokensUM {
+ analyticsEventHandler.send(ManageTokensAnalyticEvent.ScreenOpened(params.source))
+
return if (userWalletId == null) {
createReadContentModel()
} else {
@@ -140,8 +148,7 @@ internal class ManageTokensModel @Inject constructor(
state
.distinctUntilChanged { old, new ->
// It's also used to skip search activation to avoid searching an empty query
- old.search.query == new.search.query &&
- (old.search.isActive == new.search.isActive || new.search.isActive)
+ old.search.query == new.search.query && new.search.isActive
}
.transform { state ->
val query = state.search.query
@@ -161,11 +168,19 @@ internal class ManageTokensModel @Inject constructor(
}
private fun updateItems(items: ImmutableList) {
- state.update { state ->
+ val updatedState = state.updateAndGet { state ->
state.copySealed(
items = items,
)
}
+
+ if (updatedState.items.isEmpty() && updatedState.search.isActive) {
+ val event = ManageTokensAnalyticEvent.TokensIsNotFound(
+ query = updatedState.search.query,
+ source = params.source,
+ )
+ analyticsEventHandler.send(event)
+ }
}
private fun updatePaginationStatus(status: PaginationStatus<*>) {
@@ -234,7 +249,7 @@ internal class ManageTokensModel @Inject constructor(
}
private fun consumeScrollToTopEvent() {
- this.state.update { state ->
+ state.update { state ->
state.copySealed(
scrollToTop = consumedEvent(),
)
@@ -264,24 +279,33 @@ internal class ManageTokensModel @Inject constructor(
}
private fun navigateToAddCustomToken() {
+ analyticsEventHandler.send(CustomTokenAnalyticsEvent.ButtonCustomToken(params.source))
+
params.userWalletId?.let {
bottomSheetNavigation.activate(ManageTokensBottomSheetConfig.AddCustomToken(it))
}
}
- private fun saveChanges() {
- modelScope.launch {
- state.update { state -> state.copySealed(isSavingInProgress = true) }
- saveManagedTokensUseCase.invoke(
- userWalletId = requireNotNull(params.userWalletId),
- currenciesToAdd = manageTokensListManager.currenciesToAdd.value,
- currenciesToRemove = manageTokensListManager.currenciesToRemove.value,
- ).fold(
- ifLeft = { Timber.e(it, "Failed to save changes") },
- ifRight = { router.pop() },
- )
- state.update { state -> state.copySealed(isSavingInProgress = false) }
+ private fun saveChanges() = resource(
+ acquire = { state.update { state -> state.copySealed(isSavingInProgress = true) } },
+ release = { state.update { state -> state.copySealed(isSavingInProgress = false) } },
+ ) {
+ val event = ManageTokensAnalyticEvent.TokenAdded(
+ tokensCount = manageTokensListManager.currenciesToAdd.value.values.sumOf { it.size },
+ source = params.source,
+ )
+ analyticsEventHandler.send(event)
+
+ saveManagedTokensUseCase(
+ userWalletId = requireNotNull(params.userWalletId),
+ currenciesToAdd = manageTokensListManager.currenciesToAdd.value,
+ currenciesToRemove = manageTokensListManager.currenciesToRemove.value,
+ ).getOrElse {
+ Timber.e(it, "Failed to save changes")
+ return@resource
}
+
+ router.pop()
}
private fun searchCurrencies(query: String) {
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt
index 69ea2418d6..c8fc9b7dd7 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/AddCustomTokenBottomSheet.kt
@@ -16,7 +16,6 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetTitle
import com.tangem.core.ui.extensions.resourceReference
-import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.tokens.model.Network
@@ -102,7 +101,7 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<
popBack = {},
selectedNetwork = SelectedNetwork(
id = Network.ID(value = "1"),
- name = stringReference("Ethereum"),
+ name = "Ethereum",
derivationPath = Network.DerivationPath.None,
canHandleTokens = false,
),
@@ -115,7 +114,7 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<
popBack = {},
selectedNetwork = SelectedNetwork(
id = Network.ID(value = "0"),
- name = stringReference("Ethereum"),
+ name = "Ethereum",
derivationPath = Network.DerivationPath.None,
canHandleTokens = false,
),
@@ -129,7 +128,8 @@ private class AddCustomTokenComponentPreviewProvider : PreviewParameterProvider<
selectedDerivationPath = SelectedDerivationPath(
id = Network.ID(value = "0"),
value = Network.DerivationPath.None,
- networkName = stringReference("Ethereum"),
+ name = "Ethereum",
+ isDefault = false,
),
),
),
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt
index 3565f4c326..04e750f95c 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenFormContent.kt
@@ -7,19 +7,16 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.text.KeyboardActions
-import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.Text
-import androidx.compose.runtime.*
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.input.ImeAction
-import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
@@ -37,9 +34,11 @@ import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.features.managetokens.component.preview.PreviewCustomTokenFormComponent
import com.tangem.features.managetokens.entity.customtoken.ClickableFieldUM
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
+import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM.Field
import com.tangem.features.managetokens.entity.customtoken.TextInputFieldUM
import com.tangem.features.managetokens.impl.R
import com.tangem.features.managetokens.ui.component.AddCustomTokenDescription
+import kotlinx.collections.immutable.mutate
@Composable
internal fun CustomTokenFormContent(model: CustomTokenFormUM, modifier: Modifier = Modifier) {
@@ -125,43 +124,14 @@ private fun TokenForm(tokenForm: CustomTokenFormUM.TokenFormUM, modifier: Modifi
shape = TangemTheme.shapes.roundedCornersXMedium,
),
) {
- TextField(
- model = tokenForm.contractAddress,
- keyboardOptions = KeyboardOptions.Default.copy(
- imeAction = ImeAction.Next,
- ),
- )
- TextField(
- model = tokenForm.name,
- keyboardOptions = KeyboardOptions.Default.copy(
- imeAction = ImeAction.Next,
- ),
- )
- TextField(
- model = tokenForm.symbol,
- keyboardOptions = KeyboardOptions.Default.copy(
- imeAction = ImeAction.Next,
- ),
- )
- TextField(
- model = tokenForm.decimals,
- keyboardOptions = KeyboardOptions.Default.copy(
- keyboardType = KeyboardType.Decimal,
- imeAction = ImeAction.Next,
- ),
- )
+ tokenForm.fields.values.forEach { field ->
+ TextField(model = field)
+ }
}
}
@Composable
-private fun TextField(
- model: TextInputFieldUM,
- modifier: Modifier = Modifier,
- keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
- keyboardActions: KeyboardActions = KeyboardActions.Default,
-) {
- var isFocused by remember { mutableStateOf(value = false) }
-
+private fun TextField(model: TextInputFieldUM, modifier: Modifier = Modifier) {
InformationBlock(
modifier = modifier,
title = {
@@ -173,7 +143,7 @@ private fun TextField(
model.error != null -> {
TangemTheme.colors.text.warning
}
- model.value.isNotBlank() || isFocused -> {
+ model.value.isNotBlank() || model.isFocused -> {
TangemTheme.colors.text.tertiary
}
else -> {
@@ -204,16 +174,15 @@ private fun TextField(
.padding(bottom = TangemTheme.dimens.spacing12)
.fillMaxWidth()
.onFocusChanged {
- isFocused = it.isFocused
+ model.onFocusChange(it.isFocused)
},
value = model.value,
color = color,
onValueChange = model.onValueChange,
placeholder = model.placeholder,
- readOnly = !model.isEnabled && !isFocused,
+ readOnly = !model.isEnabled && !model.isFocused,
singleLine = true,
- keyboardOptions = keyboardOptions,
- keyboardActions = keyboardActions,
+ keyboardOptions = model.keyboardOptions,
)
},
)
@@ -262,25 +231,31 @@ private class PreviewCustomTokenFormComponentProvider :
override val values: Sequence
get() = sequenceOf(
PreviewCustomTokenFormComponent(
- tokenForm = PreviewCustomTokenFormComponent.tokenForm.copy(
- contractAddress = TextInputFieldUM(
- label = stringReference("Contract address"),
- value = "0x1234567890",
- placeholder = stringReference("0x1234567890"),
- onValueChange = {},
- ),
- ),
+ tokenForm = PreviewCustomTokenFormComponent.tokenForm.let { form ->
+ form.copy(
+ fields = form.fields.mutate {
+ it[Field.CONTRACT_ADDRESS] = it[Field.CONTRACT_ADDRESS]!!.copy(
+ label = stringReference("Contract address"),
+ value = "0x1234567890",
+ placeholder = stringReference("0x1234567890"),
+ )
+ },
+ )
+ },
),
PreviewCustomTokenFormComponent(
- tokenForm = PreviewCustomTokenFormComponent.tokenForm.copy(
- contractAddress = TextInputFieldUM(
- label = stringReference("Contract address"),
- value = "0x1234567890",
- error = stringReference("Contract address is invalid"),
- placeholder = stringReference("0x1234567890"),
- onValueChange = {},
- ),
- ),
+ tokenForm = PreviewCustomTokenFormComponent.tokenForm.let { form ->
+ form.copy(
+ fields = form.fields.mutate {
+ it[Field.CONTRACT_ADDRESS] = it[Field.CONTRACT_ADDRESS]!!.copy(
+ label = stringReference("Contract address"),
+ value = "0x1234567890",
+ error = stringReference("Contract address is invalid"),
+ placeholder = stringReference("0x1234567890"),
+ )
+ },
+ )
+ },
),
PreviewCustomTokenFormComponent(
tokenForm = null,
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt
index 476433fb7d..4196a9c491 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/ui/CustomTokenSelectorContent.kt
@@ -26,7 +26,6 @@ import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.rows.ChainRow
import com.tangem.core.ui.components.rows.model.ChainRowUM
import com.tangem.core.ui.extensions.resolveReference
-import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.domain.tokens.model.Network
@@ -271,14 +270,15 @@ private class CustomTokenNetworkSelectorComponentPreviewProvider :
userWalletId = UserWalletId(stringValue = "321"),
selectedNetwork = SelectedNetwork(
id = Network.ID(value = "0"),
- name = stringReference("Ethereum"),
+ name = "Ethereum",
derivationPath = Network.DerivationPath.Card("m/44'/0'/0'/0/0"),
canHandleTokens = true,
),
selectedDerivationPath = SelectedDerivationPath(
id = Network.ID(value = "0"),
value = Network.DerivationPath.Card("m/44'/0'/0'/0/0"),
- networkName = stringReference(""),
+ name = "",
+ isDefault = false,
),
onDerivationPathSelected = {},
),
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyFormBuilder.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyFormBuilder.kt
new file mode 100644
index 0000000000..caf9507cc2
--- /dev/null
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/CustomCurrencyFormBuilder.kt
@@ -0,0 +1,94 @@
+package com.tangem.features.managetokens.utils
+
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.input.KeyboardType
+import com.tangem.core.decompose.di.ComponentScoped
+import com.tangem.core.decompose.model.ParamsContainer
+import com.tangem.core.ui.extensions.resourceReference
+import com.tangem.core.ui.extensions.stringReference
+import com.tangem.features.managetokens.component.CustomTokenFormComponent
+import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
+import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM.Field
+import com.tangem.features.managetokens.entity.customtoken.TextInputFieldUM
+import com.tangem.features.managetokens.impl.R
+import kotlinx.collections.immutable.persistentMapOf
+import javax.inject.Inject
+
+@ComponentScoped
+internal class CustomCurrencyFormBuilder @Inject constructor(
+ paramsContainer: ParamsContainer,
+) {
+
+ private val params: CustomTokenFormComponent.Params = paramsContainer.require()
+
+ fun buildForm(
+ updateFormFieldValue: (Field, String) -> Unit,
+ updateFormFieldFocus: (Field, Boolean) -> Unit,
+ ): CustomTokenFormUM.TokenFormUM {
+ val formValues = params.formValues
+ val fields = persistentMapOf(
+ Field.CONTRACT_ADDRESS to TextInputFieldUM(
+ label = resourceReference(R.string.custom_token_contract_address_input_title),
+ placeholder = stringReference(CONTRACT_ADDRESS_PLACEHOLDER),
+ keyboardOptions = KeyboardOptions(
+ capitalization = KeyboardCapitalization.None,
+ keyboardType = KeyboardType.Text,
+ ),
+ onValueChange = { value ->
+ updateFormFieldValue(Field.CONTRACT_ADDRESS, value)
+ },
+ onFocusChange = { isFocused ->
+ updateFormFieldFocus(Field.CONTRACT_ADDRESS, isFocused)
+ },
+ ),
+ Field.NAME to TextInputFieldUM(
+ label = resourceReference(R.string.custom_token_name_input_title),
+ placeholder = resourceReference(R.string.custom_token_name_input_placeholder),
+ keyboardOptions = KeyboardOptions(
+ capitalization = KeyboardCapitalization.Words,
+ keyboardType = KeyboardType.Text,
+ ),
+ onValueChange = { value ->
+ updateFormFieldValue(Field.NAME, value)
+ },
+ onFocusChange = { isFocused ->
+ updateFormFieldFocus(Field.NAME, isFocused)
+ },
+ ),
+ Field.SYMBOL to TextInputFieldUM(
+ label = resourceReference(R.string.custom_token_token_symbol_input_title),
+ placeholder = resourceReference(R.string.custom_token_token_symbol_input_placeholder),
+ keyboardOptions = KeyboardOptions(
+ capitalization = KeyboardCapitalization.Characters,
+ keyboardType = KeyboardType.Text,
+ ),
+ onValueChange = { value ->
+ updateFormFieldValue(Field.SYMBOL, value)
+ },
+ onFocusChange = { isFocused ->
+ updateFormFieldFocus(Field.SYMBOL, isFocused)
+ },
+ ),
+ Field.DECIMALS to TextInputFieldUM(
+ label = resourceReference(R.string.custom_token_decimals_input_title),
+ placeholder = stringReference(DECIMALS_PLACEHOLDER),
+ keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Decimal),
+ onValueChange = { value ->
+ updateFormFieldValue(Field.DECIMALS, value)
+ },
+ onFocusChange = { isFocused ->
+ updateFormFieldFocus(Field.DECIMALS, isFocused)
+ },
+ ),
+ )
+ val form = CustomTokenFormUM.TokenFormUM(fields)
+
+ return formValues.fillValues(form)
+ }
+
+ private companion object {
+ const val CONTRACT_ADDRESS_PLACEHOLDER = "0x000000000000000000000000000..."
+ const val DECIMALS_PLACEHOLDER = "0"
+ }
+}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt
index da24501ab1..2eea0dc190 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/list/ManageTokensListManager.kt
@@ -1,6 +1,7 @@
package com.tangem.features.managetokens.utils.list
import arrow.core.getOrElse
+import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.clipboard.ClipboardManager
@@ -8,12 +9,15 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.message.SnackbarMessage
import com.tangem.domain.managetokens.CheckCurrencyUnsupportedUseCase
+import com.tangem.domain.managetokens.CheckHasLinkedTokensUseCase
import com.tangem.domain.managetokens.GetManagedTokensUseCase
import com.tangem.domain.managetokens.RemoveCustomManagedCryptoCurrencyUseCase
-import com.tangem.domain.managetokens.CheckHasLinkedTokensUseCase
import com.tangem.domain.managetokens.model.*
import com.tangem.domain.tokens.model.Network
import com.tangem.domain.wallets.models.UserWalletId
+import com.tangem.features.managetokens.analytics.ManageTokensAnalyticEvent
+import com.tangem.features.managetokens.component.ManageTokensComponent
+import com.tangem.features.managetokens.component.ManageTokensSource
import com.tangem.features.managetokens.entity.item.CurrencyItemUM
import com.tangem.features.managetokens.impl.R
import com.tangem.pagination.BatchAction
@@ -42,10 +46,12 @@ internal class ManageTokensListManager @Inject constructor(
private val checkCurrencyUnsupportedUseCase: CheckCurrencyUnsupportedUseCase,
private val messageSender: UiMessageSender,
private val dispatchers: CoroutineDispatcherProvider,
+ private val analyticsEventHandler: AnalyticsEventHandler,
clipboardManager: ClipboardManager,
) : ManageTokensUiActions {
private lateinit var scope: CoroutineScope
+ private lateinit var source: ManageTokensSource
private val jobHolder = JobHolder()
private val actionsFlow: MutableSharedFlow = MutableSharedFlow(
@@ -74,8 +80,9 @@ internal class ManageTokensListManager @Inject constructor(
.distinctUntilChanged()
val uiItems: Flow> = uiManager.items
- suspend fun launchPagination(userWalletId: UserWalletId?) = coroutineScope {
+ suspend fun launchPagination(params: ManageTokensComponent.Params) = coroutineScope {
scope = this
+ source = params.source
val batchFlow = getManagedTokensUseCase(
context = ManageTokensListBatchingContext(
@@ -85,13 +92,13 @@ internal class ManageTokensListManager @Inject constructor(
)
batchFlow.state
- .onEach { state -> updateState(state, userWalletId) }
+ .onEach { state -> updateState(state, params.userWalletId) }
.flowOn(dispatchers.default)
.launchIn(scope = this)
.saveIn(jobHolder)
// Initial load
- reload(userWalletId)
+ reload(params.userWalletId)
}
suspend fun reload(userWalletId: UserWalletId?) {
@@ -159,12 +166,16 @@ internal class ManageTokensListManager @Inject constructor(
changedCurrenciesManager.addCurrency(currency, network)
sendSelectCurrencyAction(batchKey, currency.id, network, isSelected = true)
+
+ sendSelectCurrencyAnalyticsEvent(currency, isSelected = true)
}
override fun removeCurrency(batchKey: Int, currency: ManagedCryptoCurrency.Token, network: Network) {
changedCurrenciesManager.removeCurrency(currency, network)
sendSelectCurrencyAction(batchKey, currency.id, network, isSelected = false)
+
+ sendSelectCurrencyAnalyticsEvent(currency, isSelected = false)
}
override fun removeCustomCurrency(userWalletId: UserWalletId, currency: ManagedCryptoCurrency.Custom) {
@@ -180,6 +191,15 @@ internal class ManageTokensListManager @Inject constructor(
network: Network,
): Boolean = !changedCurrenciesManager.containsCurrency(currency, network)
+ private fun sendSelectCurrencyAnalyticsEvent(currency: ManagedCryptoCurrency.Token, isSelected: Boolean) {
+ val event = ManageTokensAnalyticEvent.TokenSwitcherChanged(
+ tokenSymbol = currency.symbol,
+ isSelected = isSelected,
+ source = source,
+ )
+ analyticsEventHandler.send(event)
+ }
+
private fun sendSelectCurrencyAction(
batchKey: Int,
currencyId: ManagedCryptoCurrency.ID,
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/TokenFormMapper.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/TokenFormMapper.kt
index fd9a7bd140..6e55316dfb 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/TokenFormMapper.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/mapper/TokenFormMapper.kt
@@ -5,9 +5,9 @@ import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
internal fun CustomTokenFormUM.TokenFormUM.mapToDomainModel(): AddCustomTokenForm.Raw {
return AddCustomTokenForm.Raw(
- contractAddress = contractAddress.value,
- symbol = symbol.value,
- name = name.value,
- decimals = decimals.value,
+ contractAddress = fields.getValue(CustomTokenFormUM.TokenFormUM.Field.CONTRACT_ADDRESS).value,
+ symbol = fields.getValue(CustomTokenFormUM.TokenFormUM.Field.SYMBOL).value,
+ name = fields.getValue(CustomTokenFormUM.TokenFormUM.Field.NAME).value,
+ decimals = fields.getValue(CustomTokenFormUM.TokenFormUM.Field.DECIMALS).value,
)
}
\ No newline at end of file
diff --git a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt
index ae05953156..d99bb9473b 100644
--- a/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt
+++ b/features/manage-tokens/impl/src/main/kotlin/com/tangem/features/managetokens/utils/ui/CustomCurrencyFormOperations.kt
@@ -1,17 +1,17 @@
package com.tangem.features.managetokens.utils.ui
import com.tangem.core.ui.components.notifications.NotificationConfig
-import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.domain.managetokens.ValidateTokenFormUseCase
import com.tangem.domain.managetokens.model.exceptoin.CustomTokenFormValidationException
import com.tangem.domain.tokens.model.CryptoCurrency
import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM
-import com.tangem.features.managetokens.entity.customtoken.TextInputFieldUM
+import com.tangem.features.managetokens.entity.customtoken.CustomTokenFormUM.TokenFormUM.Field
import com.tangem.features.managetokens.impl.R
import kotlinx.collections.immutable.mutate
import kotlinx.collections.immutable.persistentListOf
+import kotlinx.collections.immutable.toPersistentMap
internal fun CustomTokenFormUM.updateTokenForm(
block: CustomTokenFormUM.TokenFormUM.() -> CustomTokenFormUM.TokenFormUM,
@@ -23,19 +23,6 @@ internal fun CustomTokenFormUM.updateTokenForm(
return copy(tokenForm = updatedForm)
}
-internal fun TextInputFieldUM.updateValue(
- value: String = this.value,
- error: TextReference? = this.error,
- isEnabled: Boolean = this.isEnabled,
- clearError: Boolean = false,
-): TextInputFieldUM {
- return copy(
- value = value,
- error = if (clearError) null else error,
- isEnabled = isEnabled,
- )
-}
-
internal fun CustomTokenFormUM.updateWithProgress(
showProgress: Boolean,
isWasFilled: Boolean = this.tokenForm?.wasFilled ?: false,
@@ -49,22 +36,21 @@ internal fun CustomTokenFormUM.updateWithProgress(
canAddToken = canAddToken,
notifications = if (clearNotifications) persistentListOf() else notifications,
).updateTokenForm {
+ val updatedFields = fields.mapValues { (key, field) ->
+ field.copy(
+ isEnabled = when (key) {
+ Field.CONTRACT_ADDRESS -> field.isEnabled
+ Field.NAME,
+ Field.SYMBOL,
+ Field.DECIMALS,
+ -> !(showProgress || disableSecondaryFields)
+ },
+ error = if (clearFieldErrors) null else field.error,
+ )
+ }
+
copy(
- contractAddress = contractAddress.updateValue(
- clearError = clearFieldErrors,
- ),
- name = name.updateValue(
- isEnabled = !showProgress && !disableSecondaryFields,
- clearError = clearFieldErrors,
- ),
- symbol = symbol.updateValue(
- isEnabled = !showProgress && !disableSecondaryFields,
- clearError = clearFieldErrors,
- ),
- decimals = decimals.updateValue(
- isEnabled = !showProgress && !disableSecondaryFields,
- clearError = clearFieldErrors,
- ),
+ fields = updatedFields.toPersistentMap(),
wasFilled = isWasFilled,
)
}
@@ -72,11 +58,31 @@ internal fun CustomTokenFormUM.updateWithProgress(
internal fun CustomTokenFormUM.updateWithCurrency(currency: CryptoCurrency): CustomTokenFormUM {
return updateTokenForm {
+ val updatedFields = fields.mapValues { (key, field) ->
+ when (key) {
+ Field.CONTRACT_ADDRESS -> field.copy(
+ error = null,
+ )
+ Field.NAME -> field.copy(
+ value = currency.name,
+ error = null,
+ isEnabled = false,
+ )
+ Field.SYMBOL -> field.copy(
+ value = currency.symbol,
+ error = null,
+ isEnabled = false,
+ )
+ Field.DECIMALS -> field.copy(
+ value = currency.decimals.toString(),
+ error = null,
+ isEnabled = false,
+ )
+ }
+ }
+
copy(
- contractAddress = contractAddress.updateValue(error = null),
- name = name.updateValue(currency.name),
- symbol = symbol.updateValue(currency.symbol),
- decimals = decimals.updateValue(currency.decimals.toString()),
+ fields = updatedFields.toPersistentMap(),
)
}
}
@@ -85,18 +91,20 @@ internal fun CustomTokenFormUM.updateWithContractAddressException(
exception: CustomTokenFormValidationException.ContractAddress,
): CustomTokenFormUM {
return updateTokenForm {
- copy(
- contractAddress = contractAddress.updateValue(
+ val updatedFields = fields.mutate {
+ it[Field.CONTRACT_ADDRESS] = it.getValue(Field.CONTRACT_ADDRESS).copy(
error = when (exception) {
CustomTokenFormValidationException.ContractAddress.Empty -> {
- null
+ null // Should not display this error
}
CustomTokenFormValidationException.ContractAddress.Invalid -> {
resourceReference(R.string.custom_token_creation_error_invalid_contract_address)
}
},
- ),
- )
+ )
+ }
+
+ copy(fields = updatedFields)
}
}
@@ -104,11 +112,11 @@ internal fun CustomTokenFormUM.updateWithDecimalsException(
exception: CustomTokenFormValidationException.Decimals,
): CustomTokenFormUM {
return updateTokenForm {
- copy(
- decimals = decimals.updateValue(
+ val updatedFields = fields.mutate {
+ it[Field.DECIMALS] = it.getValue(Field.DECIMALS).copy(
error = when (exception) {
is CustomTokenFormValidationException.Decimals.Empty -> {
- null
+ null // Should not display this error
}
is CustomTokenFormValidationException.Decimals.Invalid -> {
resourceReference(
@@ -117,8 +125,10 @@ internal fun CustomTokenFormUM.updateWithDecimalsException(
)
}
},
- ),
- )
+ )
+ }
+
+ copy(fields = updatedFields)
}
}
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/DefaultMarketsTokenDetailsComponent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/DefaultMarketsTokenDetailsComponent.kt
index 681d36de1b..2628335d48 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/DefaultMarketsTokenDetailsComponent.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/DefaultMarketsTokenDetailsComponent.kt
@@ -110,11 +110,8 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
backgroundColor = LocalMainBottomSheetColor.current.value,
addTopBarStatusBarPadding = false,
state = state,
- onBackClick = {
- if (bsState == BottomSheetState.EXPANDED) {
- navigateBack()
- }
- },
+ onBackClick = ::navigateBack,
+ backButtonEnabled = bsState == BottomSheetState.EXPANDED,
onHeaderSizeChange = onHeaderSizeChange,
portfolioBlock = portfolioComponent?.let { component ->
{ blockModifier ->
@@ -145,6 +142,7 @@ internal class DefaultMarketsTokenDetailsComponent @AssistedInject constructor(
addTopBarStatusBarPadding = true,
state = state,
onBackClick = ::navigateBack,
+ backButtonEnabled = true,
onHeaderSizeChange = {},
portfolioBlock = portfolioComponent?.let { component ->
{ blockModifier ->
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt
index 3501ff40f5..48b3217b3d 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/MetricsConverter.kt
@@ -18,6 +18,7 @@ import java.math.BigDecimal
@Stable
internal class MetricsConverter(
private val appCurrency: Provider,
+ private val tokenSymbol: String,
private val onInfoClick: (InfoBottomSheetContent) -> Unit,
) : Converter {
@@ -99,12 +100,12 @@ internal class MetricsConverter(
},
),
InfoPointUM(
- title = resourceReference(R.string.markets_token_details_total_supply),
- value = totalSupply.formatAmount(crypto = true),
+ title = resourceReference(R.string.markets_token_details_max_supply),
+ value = maxSupply.formatMaxSupply(),
onInfoClick = {
onInfoClick(
InfoBottomSheetContent(
- title = resourceReference(R.string.markets_token_details_total_supply_full),
+ title = resourceReference(R.string.markets_token_details_max_supply_full),
body = resourceReference(R.string.markets_token_details_total_supply_description),
),
)
@@ -115,13 +116,26 @@ internal class MetricsConverter(
}
}
+ private fun BigDecimal?.formatMaxSupply(): String {
+ when (this) {
+ null -> return StringsSigns.DASH_SIGN
+ BigDecimal.ZERO -> return StringsSigns.INFINITY_SIGN
+ }
+
+ return this.formatAmount(crypto = true)
+ }
+
private fun BigDecimal?.formatAmount(crypto: Boolean = false): String {
if (this == null) return StringsSigns.DASH_SIGN
return if (crypto) {
- BigDecimalFormatter.formatCompactAmount(amount = this)
+ BigDecimalFormatter.formatCompactCryptoAmount(
+ amount = this,
+ cryptoCurrencySymbol = tokenSymbol,
+ )
} else {
val currency = appCurrency()
+
BigDecimalFormatter.formatCompactFiatAmount(
amount = this,
fiatCurrencyCode = currency.code,
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt
index 4f3145408b..8a9b8aa82a 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/TokenMarketInfoConverter.kt
@@ -12,8 +12,8 @@ import com.tangem.utils.converter.Converter
@Stable
internal class TokenMarketInfoConverter(
- appCurrency: Provider,
- onInfoClick: (InfoBottomSheetContent) -> Unit,
+ private val appCurrency: Provider,
+ private val onInfoClick: (InfoBottomSheetContent) -> Unit,
onLinkClick: (LinksUM.Link) -> Unit,
onPricePerformanceIntervalChanged: (PriceChangeInterval) -> Unit,
onInsightsIntervalChanged: (PriceChangeInterval) -> Unit,
@@ -28,7 +28,6 @@ internal class TokenMarketInfoConverter(
@Suppress("UnusedPrivateMember")
// TODO second markets iteration
private val securityScoreConverter = SecurityScoreConverter(onInfoClick = onInfoClick)
- private val metricsConverter = MetricsConverter(appCurrency = appCurrency, onInfoClick = onInfoClick)
private val pricePerformanceConverter = PricePerformanceConverter(
appCurrency = appCurrency,
onIntervalChanged = onPricePerformanceIntervalChanged,
@@ -36,6 +35,12 @@ internal class TokenMarketInfoConverter(
private val linksConverter = LinksConverter(onLinkClick = onLinkClick)
override fun convert(value: TokenMarketInfo): MarketsTokenDetailsUM.InformationBlocks {
+ val metricsConverter = MetricsConverter(
+ tokenSymbol = value.symbol,
+ appCurrency = appCurrency,
+ onInfoClick = onInfoClick,
+ )
+
return MarketsTokenDetailsUM.InformationBlocks(
insights = value.insights?.let { insightsConverter.convert(it) },
securityScore = null,
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt
index bce07d98c0..26639bd472 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/MarketsTokenDetailsContent.kt
@@ -52,6 +52,7 @@ internal fun MarketsTokenDetailsContent(
addTopBarStatusBarPadding: Boolean,
onBackClick: () -> Unit,
onHeaderSizeChange: (Dp) -> Unit,
+ backButtonEnabled: Boolean,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
modifier: Modifier = Modifier,
) {
@@ -61,6 +62,7 @@ internal fun MarketsTokenDetailsContent(
state = state,
onBackClick = onBackClick,
onHeaderSizeChange = onHeaderSizeChange,
+ backButtonEnabled = backButtonEnabled,
portfolioBlock = portfolioBlock,
addTopBarStatusBarInsets = addTopBarStatusBarPadding,
)
@@ -76,6 +78,7 @@ private fun Content(
addTopBarStatusBarInsets: Boolean,
onBackClick: () -> Unit,
onHeaderSizeChange: (Dp) -> Unit,
+ backButtonEnabled: Boolean,
portfolioBlock: @Composable ((Modifier) -> Unit)?,
modifier: Modifier = Modifier,
) {
@@ -98,7 +101,10 @@ private fun Content(
}
},
title = state.tokenName,
- startButton = TopAppBarButtonUM.Back(onBackClick),
+ startButton = TopAppBarButtonUM.Back(
+ onBackClicked = onBackClick,
+ enabled = backButtonEnabled,
+ ),
)
SpacerH4()
@@ -305,6 +311,7 @@ private fun Preview() {
onBackClick = {},
backgroundColor = TangemTheme.colors.background.tertiary,
portfolioBlock = {},
+ backButtonEnabled = true,
addTopBarStatusBarPadding = false,
)
}
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt
index de51bd5a8f..2b02fdb118 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/LinksBlock.kt
@@ -28,6 +28,7 @@ import kotlinx.collections.immutable.persistentListOf
internal fun LinksBlock(state: LinksUM, modifier: Modifier = Modifier) {
InformationBlock(
modifier = modifier,
+ contentHorizontalPadding = 0.dp,
title = {
Text(
text = stringResource(id = R.string.markets_token_details_links),
@@ -81,7 +82,7 @@ private fun SubBlock(
showDivider = !lastBlock,
) {
Column(
- modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12),
+ modifier = Modifier.padding(TangemTheme.dimens.spacing12),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
Text(
@@ -111,6 +112,7 @@ private fun SubBlock(
fun LinksBlockPlaceholder(modifier: Modifier = Modifier) {
InformationBlock(
modifier = modifier,
+ contentHorizontalPadding = 0.dp,
title = {
TextShimmer(
modifier = Modifier.fillMaxWidth(),
@@ -134,7 +136,7 @@ private fun SubBlockPlaceholder(modifier: Modifier = Modifier, lastBlock: Boolea
showDivider = !lastBlock,
) {
Column(
- modifier = Modifier.padding(vertical = TangemTheme.dimens.spacing12),
+ modifier = Modifier.padding(TangemTheme.dimens.spacing12),
verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing12),
) {
TextShimmer(
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt
index e0bd92cbdf..a337b02336 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/AddToPortfolioBSContentUMFactory.kt
@@ -3,6 +3,7 @@ package com.tangem.features.markets.portfolio.impl.model
import com.tangem.common.ui.userwallet.converter.UserWalletItemUMConverter
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
+import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.rows.model.BlockchainRowUM
import com.tangem.domain.markets.TokenMarketInfo
import com.tangem.domain.markets.TokenMarketParams
@@ -45,47 +46,51 @@ internal class AddToPortfolioBSContentUMFactory(
fun create(
portfolioData: PortfolioData,
portfolioUIData: PortfolioUIData,
- selectedWallet: UserWallet,
- alreadyAddedNetworks: Set,
+ selectedWallet: UserWallet?,
+ alreadyAddedNetworks: Set?,
): TangemBottomSheetConfig {
return TangemBottomSheetConfig(
isShow = portfolioUIData.portfolioBSVisibilityModel.addToPortfolioBSVisibility,
onDismissRequest = { onAddToPortfolioVisibilityChange(false) },
- content = AddToPortfolioBSContentUM(
- selectedWallet = selectedWallet.toSelectedUserWalletItemUM(),
- selectNetworkUM = SelectNetworkUMConverter(
- networksWithToggle = portfolioUIData.addToPortfolioData.associateWithToggle(
- userWalletId = selectedWallet.walletId,
- alreadyAddedNetworkIds = alreadyAddedNetworks,
- ),
- alreadyAddedNetworks = alreadyAddedNetworks,
- onNetworkSwitchClick = onNetworkSwitchClick,
- ).convert(value = token),
- isScanCardNotificationVisible = portfolioUIData.hasMissedDerivations,
- continueButtonEnabled = portfolioUIData.addToPortfolioData.isUserAddedNetworks(
- userWalletId = selectedWallet.walletId,
- ),
- onContinueButtonClick = {
- val alreadyAddedNetworkIds = portfolioData.walletsWithCurrencies[selectedWallet].orEmpty()
- .map { it.status.currency.network.backendId }
- .toSet()
-
- onContinueClick(
- selectedWallet.walletId,
- portfolioUIData.addToPortfolioData.getAddedNetworks(
+ content = if (selectedWallet != null && alreadyAddedNetworks != null) {
+ AddToPortfolioBSContentUM(
+ selectedWallet = selectedWallet.toSelectedUserWalletItemUM(),
+ selectNetworkUM = SelectNetworkUMConverter(
+ networksWithToggle = portfolioUIData.addToPortfolioData.associateWithToggle(
userWalletId = selectedWallet.walletId,
- alreadyAddedNetworkIds = alreadyAddedNetworkIds,
+ alreadyAddedNetworkIds = alreadyAddedNetworks,
),
- )
- },
- walletSelectorConfig = crateWalletSelectorBSConfig(
- isShow = portfolioUIData.portfolioBSVisibilityModel.walletSelectorBSVisibility,
- portfolioData = portfolioData,
- selectedWalletId = selectedWallet.walletId,
- ),
- isWalletBlockVisible = portfolioData.walletsWithCurrencies
- .filterKeys(UserWallet::isMultiCurrency).size > 1,
- ),
+ alreadyAddedNetworks = alreadyAddedNetworks,
+ onNetworkSwitchClick = onNetworkSwitchClick,
+ ).convert(value = token),
+ isScanCardNotificationVisible = portfolioUIData.hasMissedDerivations,
+ continueButtonEnabled = portfolioUIData.addToPortfolioData.isUserAddedNetworks(
+ userWalletId = selectedWallet.walletId,
+ ),
+ onContinueButtonClick = {
+ val alreadyAddedNetworkIds = portfolioData.walletsWithCurrencies[selectedWallet].orEmpty()
+ .map { it.status.currency.network.backendId }
+ .toSet()
+
+ onContinueClick(
+ selectedWallet.walletId,
+ portfolioUIData.addToPortfolioData.getAddedNetworks(
+ userWalletId = selectedWallet.walletId,
+ alreadyAddedNetworkIds = alreadyAddedNetworkIds,
+ ),
+ )
+ },
+ walletSelectorConfig = crateWalletSelectorBSConfig(
+ isShow = portfolioUIData.portfolioBSVisibilityModel.walletSelectorBSVisibility,
+ portfolioData = portfolioData,
+ selectedWalletId = selectedWallet.walletId,
+ ),
+ isWalletBlockVisible = portfolioData.walletsWithCurrencies
+ .filterKeys(UserWallet::isMultiCurrency).size > 1,
+ )
+ } else {
+ TangemBottomSheetConfigContent.Empty
+ },
)
}
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt
index 83af1ca3c2..1bc2ecf543 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt
@@ -30,9 +30,8 @@ internal class MyPortfolioUMFactory(
fun create(portfolioData: PortfolioData, portfolioUIData: PortfolioUIData): MyPortfolioUM {
val addToPortfolioData = portfolioUIData.addToPortfolioData
- val hasAvailableNetworks = addToPortfolioData.availableNetworks?.isEmpty() == true
- val isOnlySingleWalletsAdded = portfolioData.walletsWithCurrencies.keys.all { !it.isMultiCurrency }
- if (hasAvailableNetworks || isOnlySingleWalletsAdded) return MyPortfolioUM.Unavailable
+ val isOnlyUnavailableNetworks = addToPortfolioData.availableNetworks?.isEmpty() == true
+ if (isOnlyUnavailableNetworks) return MyPortfolioUM.Unavailable
val walletsWithCurrencies = if (addToPortfolioData.availableNetworks == null) {
portfolioData.walletsWithCurrencies
@@ -53,7 +52,7 @@ internal class MyPortfolioUMFactory(
onAddClick = onAddClick,
)
} else {
- MyPortfolioUM.Unavailable
+ MyPortfolioUM.UnavailableForWallet
}
}
@@ -79,16 +78,13 @@ internal class MyPortfolioUMFactory(
val selectedWallet = portfolioData.walletsWithCurrencies.keys
.firstOrNull { it.walletId == portfolioUIData.selectedWalletId }
?: portfolioData.walletsWithCurrencies.keys.firstOrNull { it.isMultiCurrency }
- ?: error("walletsWithCurrencies don't contain selected wallet or any multi-currency wallet")
val availableNetworks = portfolioUIData.addToPortfolioData.availableNetworks.orEmpty()
- val alreadyAddedNetworks = requireNotNull(
- value = portfolioData.walletsWithCurrencies.filterAvailableNetworks(availableNetworks)[selectedWallet],
- lazyMessage = { "walletsWithCurrencies don't contain ${selectedWallet.walletId}" },
- )
- .map { it.status.currency.network.backendId }
- .toSet()
+ val alreadyAddedNetworks = portfolioData.walletsWithCurrencies
+ .filterAvailableNetworks(availableNetworks)[selectedWallet]
+ ?.map { it.status.currency.network.backendId }
+ ?.toSet()
return addToPortfolioBSContentUMFactory.create(
portfolioData = portfolioData,
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt
index 3637c9ed61..daaa385754 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/MyPortfolio.kt
@@ -1,6 +1,7 @@
package com.tangem.features.markets.portfolio.impl.ui
import android.content.res.Configuration
+import androidx.annotation.StringRes
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
@@ -50,7 +51,8 @@ internal fun MyPortfolio(state: MyPortfolioUM, modifier: Modifier = Modifier) {
is MyPortfolioUM.Tokens -> TokenList(state = state)
is MyPortfolioUM.AddFirstToken -> AddFirstTokenContent(state = state, modifier = contentModifier)
MyPortfolioUM.Loading -> LoadingPlaceholder(modifier = contentModifier)
- MyPortfolioUM.Unavailable -> UnavailableContent(modifier = contentModifier)
+ MyPortfolioUM.Unavailable -> UnavailableAsset(modifier = contentModifier)
+ MyPortfolioUM.UnavailableForWallet -> UnavailableAssetForWallet(modifier = contentModifier)
}
}
@@ -111,10 +113,26 @@ private fun TokenList(state: MyPortfolioUM.Tokens, modifier: Modifier = Modifier
}
@Composable
-private fun UnavailableContent(modifier: Modifier = Modifier) {
+fun UnavailableAsset(modifier: Modifier = Modifier) {
+ UnavailableContent(
+ textId = R.string.markets_add_to_my_portfolio_unavailable_description,
+ modifier = modifier,
+ )
+}
+
+@Composable
+fun UnavailableAssetForWallet(modifier: Modifier = Modifier) {
+ UnavailableContent(
+ textId = R.string.markets_add_to_my_portfolio_unavailable_for_wallet_description,
+ modifier = modifier,
+ )
+}
+
+@Composable
+private fun UnavailableContent(@StringRes textId: Int, modifier: Modifier = Modifier) {
Text(
modifier = modifier,
- text = stringResource(R.string.markets_add_to_my_portfolio_unavailable_description),
+ text = stringResource(textId),
style = TangemTheme.typography.body2,
color = TangemTheme.colors.text.tertiary,
)
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt
index bdff4f722c..fab6e21c48 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/ui/preview/PreviewMyPortfolioUMProvider.kt
@@ -42,6 +42,7 @@ internal class PreviewMyPortfolioUMProvider : PreviewParameterProvider?,
- onFeeError: (GetFeeError) -> Unit,
onStakingFee: (Fee) -> Unit,
+ onStakingFeeError: (StakingError) -> Unit,
onApprovalFee: (TransactionFee) -> Unit,
+ onFeeError: (GetFeeError) -> Unit,
) {
val state = stateController.value
val confirmationState = state.confirmationState as? StakingStates.ConfirmationState.Data
@@ -85,7 +86,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
pendingActions = pendingActions,
amount = amount,
validatorAddress = validatorAddress,
- onFeeError = onFeeError,
+ onStakingFeeError = onStakingFeeError,
onStakingFee = onStakingFee,
)
}
@@ -95,7 +96,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
pendingActions = pendingActions,
amount = amount,
validatorAddress = validatorAddress,
- onFeeError = onFeeError,
+ onStakingFeeError = onStakingFeeError,
onStakingFee = onStakingFee,
)
}
@@ -106,7 +107,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
pendingActions: ImmutableList?,
amount: BigDecimal,
validatorAddress: String,
- onFeeError: (GetFeeError) -> Unit,
+ onStakingFeeError: (StakingError) -> Unit,
onStakingFee: (Fee) -> Unit,
) {
val sourceAddress = cryptoCurrencyStatus.value.networkAddress?.defaultAddress?.value
@@ -125,7 +126,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
action = action,
)
}.getOrElse {
- onFeeError(GetFeeError.DataError(Throwable(it.toString())))
+ onStakingFeeError(it)
null
}
}
@@ -133,7 +134,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
}
if (result.isNullOrEmpty()) {
- onFeeError(GetFeeError.UnknownError)
+ onStakingFeeError(StakingError.UnknownError)
return
}
@@ -151,7 +152,7 @@ internal class StakingFeeTransactionLoader @AssistedInject constructor(
validatorAddress = validatorAddress,
action = pendingAction,
).getOrElse {
- onFeeError(GetFeeError.DataError(Throwable(it.toString())))
+ onStakingFeeError(it)
return
}
}
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt
index 109dafb912..57f98b373b 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/helpers/StakingTransactionSender.kt
@@ -15,6 +15,7 @@ import com.tangem.domain.staking.model.stakekit.Yield
import com.tangem.domain.staking.model.stakekit.action.StakingActionCommonType
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.StakingTransactionStatus
import com.tangem.domain.staking.model.stakekit.transaction.StakingTransactionType
import com.tangem.domain.tokens.model.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.SendTransactionError
@@ -130,7 +131,10 @@ internal class StakingTransactionSender @AssistedInject constructor(
fee: Fee,
onConstructError: (StakingError) -> Unit,
) = coroutineScope {
- stakingTransactions?.filterNot { it.type == StakingTransactionType.APPROVAL }
+ stakingTransactions
+ ?.filterNot {
+ it.type == StakingTransactionType.APPROVAL || it.status == StakingTransactionStatus.SKIPPED
+ }
?.map { transaction ->
async {
getConstructedStakingTransactionUseCase(
@@ -150,7 +154,9 @@ internal class StakingTransactionSender @AssistedInject constructor(
},
)
}
- }?.awaitAll()?.filterNotNull()
+ }
+ ?.awaitAll()
+ ?.filterNotNull()
}
private suspend fun getStakingTransaction(
@@ -185,7 +191,12 @@ internal class StakingTransactionSender @AssistedInject constructor(
type = action?.type,
),
).getOrElse {
- analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(state.cryptoCurrencyName))
+ analyticsEventHandler.send(
+ StakingAnalyticsEvents.StakingError(
+ token = state.cryptoCurrencyName,
+ errorType = it.javaClass.simpleName,
+ ),
+ )
onConstructError(it)
return emptyList()
}
@@ -248,9 +259,12 @@ internal class StakingTransactionSender @AssistedInject constructor(
rawCurrencyId = rawCurrencyId,
),
)
- .onLeft {
+ .onLeft { error ->
analyticsEventHandler.send(
- StakingAnalyticsEvents.StakingError(stateController.value.cryptoCurrencyName),
+ StakingAnalyticsEvents.StakingError(
+ token = stateController.value.cryptoCurrencyName,
+ errorType = error.javaClass.simpleName,
+ ),
)
saveUnsubmittedHashUseCase.invoke(
transactionId = transactionId,
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 181d37a8a2..bd7988cd8f 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
@@ -81,11 +81,11 @@ internal class AddStakingNotificationsTransformer(
amountValue = amountValue,
feeValue = feeValue,
reduceAmountBy = reduceAmountBy,
- )
+ ).max(minimumRequirement)
} else {
// No amount is taken from account balance on exit or pending actions
BigDecimal.ZERO
- }.max(minimumRequirement)
+ }
val notifications = buildList {
// errors
diff --git a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt
index 2822f01758..efda617b73 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/state/utils/StakingPendingActionUtils.kt
@@ -32,5 +32,5 @@ internal fun StakingActionType?.getPendingActionTitle(): TextReference = when (t
internal fun isSolanaWithdraw(networkId: String, pendingActions: ImmutableList?): Boolean {
val isSolana = isSolana(networkId)
val isWithdraw = pendingActions?.all { it.type == StakingActionType.WITHDRAW } == true
- return isSolana && isWithdraw
+ return isSolana && isWithdraw && !pendingActions.isNullOrEmpty()
}
\ No newline at end of file
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 913bf9b07a..a5afd54f97 100644
--- a/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt
+++ b/features/staking/impl/src/main/java/com/tangem/features/staking/impl/presentation/viewmodel/StakingViewModel.kt
@@ -1,7 +1,10 @@
package com.tangem.features.staking.impl.presentation.viewmodel
import android.os.Bundle
-import androidx.lifecycle.*
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
import arrow.core.getOrElse
import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.routing.AppRoute
@@ -256,8 +259,18 @@ internal class StakingViewModel @Inject constructor(
)
updateNotifications()
},
+ onStakingFeeError = { error ->
+ analyticsEventHandler.send(
+ StakingAnalyticsEvents.StakingError(
+ value.cryptoCurrencyName,
+ error.javaClass.simpleName,
+ ),
+ )
+ stateController.update(AddStakingErrorTransformer())
+ updateNotifications(GetFeeError.UnknownError)
+ },
onFeeError = { error ->
- analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
+ analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencyName))
stateController.update(AddStakingErrorTransformer())
updateNotifications(error)
},
@@ -285,7 +298,12 @@ internal class StakingViewModel @Inject constructor(
},
onConstructError = { error ->
Timber.e(error.toString())
- analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
+ analyticsEventHandler.send(
+ StakingAnalyticsEvents.StakingError(
+ token = value.cryptoCurrencyName,
+ errorType = error.javaClass.simpleName,
+ ),
+ )
stakingEventFactory.createStakingErrorAlert(error)
stateController.update(SetConfirmationStateResetAssentTransformer)
},
@@ -296,7 +314,7 @@ internal class StakingViewModel @Inject constructor(
},
onSendError = { error ->
Timber.e(error.toString())
- analyticsEventHandler.send(StakingAnalyticsEvents.StakingError(value.cryptoCurrencyName))
+ analyticsEventHandler.send(StakingAnalyticsEvents.TransactionError(value.cryptoCurrencyName))
stakingEventFactory.createSendTransactionErrorAlert(error)
stateController.update(SetConfirmationStateResetAssentTransformer)
},
diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt
index 5eea986141..0d0b59e987 100644
--- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt
+++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/txhistory/TokenDetailsTxHistoryTransactionStateConverter.kt
@@ -51,10 +51,12 @@ internal class TokenDetailsTxHistoryTransactionStateConverter(
is TransactionType.Approve -> R.drawable.ic_doc_24
is TransactionType.TronStakingTransactionType.Stake,
is TransactionType.TronStakingTransactionType.Vote,
- -> R.drawable.ic_transaction_history_staking
- is TransactionType.TronStakingTransactionType.Withdraw,
+ -> R.drawable.ic_transaction_history_staking_24
+ is TransactionType.TronStakingTransactionType.ClaimRewards,
+ -> R.drawable.ic_transaction_history_claim_rewards_24
is TransactionType.TronStakingTransactionType.Unstake,
- -> R.drawable.ic_transaction_history_unstaking
+ is TransactionType.TronStakingTransactionType.Withdraw,
+ -> R.drawable.ic_transaction_history_unstaking_24
is TransactionType.Operation,
is TransactionType.Swap,
is TransactionType.Transfer,
@@ -72,6 +74,7 @@ internal class TokenDetailsTxHistoryTransactionStateConverter(
is TransactionType.TronStakingTransactionType.Stake -> resourceReference(R.string.common_stake)
is TransactionType.TronStakingTransactionType.Unstake -> resourceReference(R.string.common_unstake)
is TransactionType.TronStakingTransactionType.Vote -> resourceReference(R.string.staking_vote)
+ is TransactionType.TronStakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards)
is TransactionType.TronStakingTransactionType.Withdraw -> resourceReference(R.string.staking_withdraw)
}
@@ -97,9 +100,13 @@ internal class TokenDetailsTxHistoryTransactionStateConverter(
},
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
- is InteractionAddressType.Staking -> resourceReference(
- id = R.string.common_staking,
+ is InteractionAddressType.Validator -> resourceReference(
+ id = R.string.transaction_history_transaction_validator,
+ formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
+ null -> {
+ TextReference.EMPTY
+ }
}
private fun TxHistoryItem.extractDirection() = if (isOutgoing) Direction.OUTGOING else Direction.INCOMING
@@ -111,7 +118,8 @@ internal class TokenDetailsTxHistoryTransactionStateConverter(
}
private fun TxHistoryItem.getAmount(): String {
- if (type == TransactionType.TronStakingTransactionType.Vote ||
+ if (type is TransactionType.TronStakingTransactionType.Vote ||
+ type == TransactionType.TronStakingTransactionType.ClaimRewards ||
type == TransactionType.TronStakingTransactionType.Withdraw
) {
return ""
diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/Settings.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/Settings.kt
index 9a655d973a..99b401cfda 100644
--- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/Settings.kt
+++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/analytics/Settings.kt
@@ -9,5 +9,7 @@ internal sealed class Settings(
error: Throwable? = null,
) : AnalyticsEvent(category, event, params, error) {
- class ButtonCreateBackup : Settings(event = "Button - Create Backup")
+ data object ButtonCreateBackup : Settings(event = "Button - Create Backup")
+
+ data object ButtonManageTokens : Settings(event = "Button - Manage Tokens")
}
\ No newline at end of file
diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt
index c0f134ef22..5844c0eab0 100644
--- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt
+++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/component/preview/PreviewWalletSettingsComponent.kt
@@ -2,6 +2,7 @@ package com.tangem.feature.walletsettings.component.preview
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import com.tangem.core.analytics.DummyAnalyticsEventHandler
import com.tangem.core.decompose.navigation.DummyRouter
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.walletsettings.component.WalletSettingsComponent
@@ -13,7 +14,10 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent {
private val previewState = WalletSettingsUM(
popBack = {},
- items = ItemsBuilder(router = DummyRouter()).buildItems(
+ items = ItemsBuilder(
+ router = DummyRouter(),
+ analyticsEventHandler = DummyAnalyticsEventHandler(),
+ ).buildItems(
userWalletId = UserWalletId("011"),
userWalletName = "My Wallet",
isReferralAvailable = true,
diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt
index ba19734477..1ad797573f 100644
--- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt
+++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/model/WalletSettingsModel.kt
@@ -145,7 +145,7 @@ internal class WalletSettingsModel @Inject constructor(
}
private fun onLinkMoreCardsClick(scanResponse: ScanResponse) {
- analyticsEventHandler.send(Settings.ButtonCreateBackup())
+ analyticsEventHandler.send(Settings.ButtonCreateBackup)
analyticsContextProxy.addContext(scanResponse)
diff --git a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt
index 896d1bbf0c..74ebf2067c 100644
--- a/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt
+++ b/features/wallet-settings/impl/src/main/kotlin/com/tangem/feature/walletsettings/utils/ItemsBuilder.kt
@@ -1,12 +1,15 @@
package com.tangem.feature.walletsettings.utils
import com.tangem.common.routing.AppRoute
+import com.tangem.common.routing.AppRoute.ManageTokens.Source
+import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ComponentScoped
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.ui.components.block.model.BlockUM
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.wallets.models.UserWalletId
+import com.tangem.feature.walletsettings.analytics.Settings
import com.tangem.feature.walletsettings.entity.WalletSettingsItemUM
import com.tangem.feature.walletsettings.impl.R
import kotlinx.collections.immutable.PersistentList
@@ -17,6 +20,7 @@ import javax.inject.Inject
@ComponentScoped
internal class ItemsBuilder @Inject constructor(
private val router: Router,
+ private val analyticsEventHandler: AnalyticsEventHandler,
) {
@Suppress("LongParameterList")
@@ -62,7 +66,10 @@ internal class ItemsBuilder @Inject constructor(
BlockUM(
text = resourceReference(R.string.add_tokens_title),
iconRes = R.drawable.ic_tether_24,
- onClick = { router.push(AppRoute.ManageTokens(userWalletId)) },
+ onClick = {
+ analyticsEventHandler.send(Settings.ButtonManageTokens)
+ router.push(AppRoute.ManageTokens(Source.SETTINGS, userWalletId))
+ },
).let(::add)
}
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt
index 421f33f996..d08f24beca 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/router/DefaultWalletRouter.kt
@@ -14,6 +14,7 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import com.tangem.common.routing.AppRoute
+import com.tangem.common.routing.AppRoute.ManageTokens.Source
import com.tangem.common.routing.AppRouter
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.domain.redux.ReduxStateHolder
@@ -140,7 +141,7 @@ internal class DefaultWalletRouter(
}
override fun openManageTokensScreen(userWalletId: UserWalletId) {
- router.push(AppRoute.ManageTokens(userWalletId = userWalletId))
+ router.push(AppRoute.ManageTokens(Source.SETTINGS, userWalletId))
}
override fun openScanFailedDialog(onTryAgain: () -> Unit) {
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt
index f37407d469..2d162a17c0 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/converter/TxHistoryItemStateConverter.kt
@@ -50,10 +50,12 @@ internal class TxHistoryItemStateConverter(
is TransactionType.Approve -> R.drawable.ic_doc_24
is TransactionType.TronStakingTransactionType.Stake,
is TransactionType.TronStakingTransactionType.Vote,
- -> R.drawable.ic_transaction_history_staking
- is TransactionType.TronStakingTransactionType.Withdraw,
+ -> R.drawable.ic_transaction_history_staking_24
+ is TransactionType.TronStakingTransactionType.ClaimRewards,
+ -> R.drawable.ic_transaction_history_claim_rewards_24
is TransactionType.TronStakingTransactionType.Unstake,
- -> R.drawable.ic_transaction_history_unstaking
+ is TransactionType.TronStakingTransactionType.Withdraw,
+ -> R.drawable.ic_transaction_history_unstaking_24
is TransactionType.Operation,
is TransactionType.Swap,
is TransactionType.Transfer,
@@ -70,7 +72,8 @@ internal class TxHistoryItemStateConverter(
is TransactionType.TronStakingTransactionType.Stake -> resourceReference(R.string.common_stake)
is TransactionType.TronStakingTransactionType.Unstake -> resourceReference(R.string.common_unstake)
is TransactionType.TronStakingTransactionType.Vote -> resourceReference(R.string.staking_vote)
- is TransactionType.TronStakingTransactionType.Withdraw -> resourceReference(R.string.staking_withdraw)
+ is TransactionType.TronStakingTransactionType.ClaimRewards -> resourceReference(R.string.common_claim_rewards)
+ is TransactionType.TronStakingTransactionType.Withdraw -> { resourceReference(R.string.staking_withdraw) }
is TransactionType.UnknownOperation -> resourceReference(R.string.transaction_history_operation)
}
@@ -96,9 +99,13 @@ internal class TxHistoryItemStateConverter(
},
formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
- is InteractionAddressType.Staking -> resourceReference(
- id = R.string.common_staking,
+ is InteractionAddressType.Validator -> resourceReference(
+ id = R.string.transaction_history_transaction_validator,
+ formatArgs = wrappedList(interactionAddress.address.toBriefAddressFormat()),
)
+ null -> {
+ TextReference.EMPTY
+ }
}
private fun TxHistoryItem.extractDirection() =
@@ -111,7 +118,8 @@ internal class TxHistoryItemStateConverter(
}
private fun TxHistoryItem.getAmount(): String {
- if (type == TransactionType.TronStakingTransactionType.Vote ||
+ if (type is TransactionType.TronStakingTransactionType.Vote ||
+ type == TransactionType.TronStakingTransactionType.ClaimRewards ||
type == TransactionType.TronStakingTransactionType.Withdraw
) {
return ""
diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt
index d8b674e7df..d131e8e482 100644
--- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt
+++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/WalletScreen.kt
@@ -8,7 +8,7 @@ import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideIn
import androidx.compose.foundation.Canvas
-import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -19,7 +19,6 @@ import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.*
-import androidx.compose.material3.BottomSheetDefaults
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@@ -46,7 +45,8 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import androidx.paging.compose.collectAsLazyPagingItems
import com.google.accompanist.systemuicontroller.rememberSystemUiController
-import com.tangem.core.ui.components.*
+import com.tangem.core.ui.components.BottomFade
+import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
@@ -54,6 +54,7 @@ import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBot
import com.tangem.core.ui.components.bottomsheets.chooseaddress.ChooseAddressBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheet
import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBottomSheetConfig
+import com.tangem.core.ui.components.rememberIsKeyboardVisible
import com.tangem.core.ui.components.sheetscaffold.*
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
import com.tangem.core.ui.components.snackbar.TangemSnackbar
@@ -425,8 +426,17 @@ private inline fun BaseScaffoldWithMarkets(
modifier = Modifier.sizeIn(maxHeight = maxHeight - statusBarHeight),
) {
Hand(Modifier.drawBehind { drawRect(backgroundColor.value) })
+
Box(
modifier = Modifier
+ // expand bottom sheet when clicked on the header
+ .clickable(
+ enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
+ indication = null,
+ interactionSource = null,
+ ) {
+ coroutineScope.launch { bottomSheetState.expand() }
+ }
.onFocusChanged {
isSearchFieldFocused = it.isFocused
},
@@ -468,7 +478,11 @@ private inline fun BaseScaffoldWithMarkets(
}
BottomSheetScrim(
- color = BottomSheetDefaults.ScrimColor,
+ color = if (state.showMarketsOnboarding) {
+ Color.Black.copy(alpha = .65f)
+ } else {
+ BottomSheetDefaults.ScrimColor
+ },
visible = bottomSheetState.targetValue == TangemSheetValue.Expanded ||
state.showMarketsOnboarding,
onDismissRequest = {
@@ -575,7 +589,7 @@ internal fun MarketsHint(isVisible: Boolean, modifier: Modifier = Modifier) {
@Composable
private fun MarketsTooltipContent(modifier: Modifier = Modifier) {
- val backgroundColor = TangemTheme.colors.background.primary
+ val backgroundColor = TangemTheme.colors.background.action
val cornerRadius = CornerRadius(x = 14.dp.toPx())
val tipDpSize = DpSize(width = 20.dp, height = 8.dp)
diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml
index 7ca1da2b67..72e140db0d 100644
--- a/gradle/dependencies.toml
+++ b/gradle/dependencies.toml
@@ -89,9 +89,9 @@ markdownComposeView = "0.5.4"
# endregion Other libraries
# region Tangem
-tangemBlockchainSdk = "develop-793"
+tangemBlockchainSdk = "develop-797"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
-tangemCardSdk = "develop-381"
+tangemCardSdk = "develop-385"
#tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^
tangemVico = "2.0.0-alpha.25-tangem16"
#tangemVico = "0.0.1" # Keep it! - used for local builds ^