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 6d8ea8f746..0996998748 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
@@ -11,6 +11,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.featuretoggles.PushNotificationsFeatureToggles
import com.tangem.features.pushnotifications.api.navigation.PushNotificationsRouter
@@ -126,9 +127,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 0d8cee2df5..88a0c3f804 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/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml
index dbbe6ece73..c7e2221f9d 100644
--- a/core/res/src/main/res/values-ru/strings.xml
+++ b/core/res/src/main/res/values-ru/strings.xml
@@ -349,6 +349,7 @@
Кошелёк не поддерживает более одной сети
Чтобы купить, обменять или получить данный токен, вам нужно добавить его к себе в портфель
Этот актив недоступен
+ Этот токен не доступен для данного кошелька
Добавить в портфель
Добавить
Доступные сети
diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml
index 210c100c90..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
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/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/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/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt
index 0df50f5899..220172ebc0 100644
--- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt
+++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/utils/SdkTransactionHistoryItemConverter.kt
@@ -45,22 +45,25 @@ internal class SdkTransactionHistoryItemConverter(
private fun SdkTransactionHistoryItem.AddressType.toDomain(): TxHistoryItem.AddressType = when (this) {
is SdkTransactionHistoryItem.AddressType.Contract -> 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/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/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/portfolio/impl/model/MyPortfolioUMFactory.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/portfolio/impl/model/MyPortfolioUMFactory.kt
index 3fbf58975e..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
@@ -52,7 +52,7 @@ internal class MyPortfolioUMFactory(
onAddClick = onAddClick,
)
} else {
- MyPortfolioUM.Unavailable
+ MyPortfolioUM.UnavailableForWallet
}
}
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 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 aff4cf252f..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,6 +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.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
@@ -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
},
diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml
index 49b3c92007..aaf2b34885 100644
--- a/gradle/dependencies.toml
+++ b/gradle/dependencies.toml
@@ -89,9 +89,9 @@ markdownComposeView = "0.5.4"
# endregion Other libraries
# region Tangem
-tangemBlockchainSdk = "release-app_5.15-781"
+tangemBlockchainSdk = "release-app_5.15-796"
#tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds
-tangemCardSdk = "release-app_5.15-382"
+tangemCardSdk = "release-app_5.15-384"
#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 ^