diff --git a/core/res/src/main/res/values-ja/strings.xml b/core/res/src/main/res/values-ja/strings.xml
index d138f32aa7..a9b9f295a9 100644
--- a/core/res/src/main/res/values-ja/strings.xml
+++ b/core/res/src/main/res/values-ja/strings.xml
@@ -287,6 +287,7 @@
フィードバック
Tangemへのフィードバック
取引を送信できません
+ コインの説明エラー
取引
ネットワークは、あなたがトークンのスワップを承認していることを確認するために、トークン承認手数料を請求します。
選択したトークンの承認制限を指定します
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt
index 7a77b4cb67..41c22e6cbd 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/Notification.kt
@@ -71,7 +71,7 @@ fun Notification(
title = config.title,
subtitle = config.subtitle,
subtitleColor = subtitleColor,
- isClickableComponent = isEnabled && config.onClick != null,
+ showArrowIcon = isEnabled && config.showArrowIcon,
)
}
}
@@ -130,7 +130,7 @@ private fun MainContent(
title: TextReference?,
subtitle: TextReference,
subtitleColor: Color,
- isClickableComponent: Boolean,
+ showArrowIcon: Boolean,
) {
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
@@ -145,7 +145,7 @@ private fun MainContent(
TextsBlock(title = title, subtitle = subtitle, subtitleColor = subtitleColor)
- if (isClickableComponent) {
+ if (showArrowIcon) {
SpacerWMax()
Icon(
diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt
index 8241017b2b..aad1efde67 100644
--- a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt
+++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/NotificationConfig.kt
@@ -24,6 +24,7 @@ data class NotificationConfig(
val buttonsState: ButtonsState? = null,
val onClick: (() -> Unit)? = null,
val onCloseClick: (() -> Unit)? = null,
+ val showArrowIcon: Boolean = onClick != null,
) {
sealed class ButtonsState {
diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt
index 654e910d2d..c2726a604f 100644
--- a/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt
+++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/FeedbackDataBuilder.kt
@@ -37,8 +37,7 @@ internal class FeedbackDataBuilder {
if (tokens.isNotEmpty()) {
builder.breakLine()
tokens.forEach { token ->
- builder.appendKeyValue("Token ID", token.id ?: "[custom token]")
- builder.appendKeyValue("Name", token.name)
+ addTokenShortInfo(id = token.id, name = token.name)
builder.appendKeyValue("Contract address", token.contractAddress)
builder.appendKeyValue("Decimals", token.decimals)
@@ -100,6 +99,11 @@ internal class FeedbackDataBuilder {
builder.appendKeyValue("Transaction ID", txId)
}
+ fun addTokenShortInfo(id: String?, name: String) {
+ builder.appendKeyValue("Token ID", id ?: "[custom token]")
+ builder.appendKeyValue("Name", name)
+ }
+
fun addDelimiter(): StringBuilder = builder.appendDelimiter()
fun build(): String = builder.trimEnd().toString()
diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt
index 9f11e6ed73..2f9578ed40 100644
--- a/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt
+++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/SendFeedbackEmailUseCase.kt
@@ -54,11 +54,19 @@ class SendFeedbackEmailUseCase(
}
private fun StringBuilder.appendDisclaimerIfNeeded(type: FeedbackEmailType): StringBuilder {
- return if (type is FeedbackEmailType.ScanningProblem) {
- this
- } else {
- append(resources.getString(R.string.feedback_data_collection_message))
- skipLine()
+ return when (type) {
+ is FeedbackEmailType.ScanningProblem,
+ is FeedbackEmailType.CurrencyDescriptionError,
+ -> this
+ is FeedbackEmailType.DirectUserRequest,
+ is FeedbackEmailType.RateCanBeBetter,
+ is FeedbackEmailType.StakingProblem,
+ is FeedbackEmailType.SwapProblem,
+ is FeedbackEmailType.TransactionSendingProblem,
+ -> {
+ append(resources.getString(R.string.feedback_data_collection_message))
+ skipLine()
+ }
}
}
}
\ No newline at end of file
diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt
index 6abec03efc..70d4017993 100644
--- a/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt
+++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/models/FeedbackEmailType.kt
@@ -36,4 +36,14 @@ sealed interface FeedbackEmailType {
val providerName: String,
val txId: String,
) : FeedbackEmailType
+
+ /**
+ * Error in currency description
+ *
+ * @property currencyId currency id
+ * @property currencyName currency name
+ */
+ data class CurrencyDescriptionError(val currencyId: String, val currencyName: String) : FeedbackEmailType {
+ override val cardInfo: CardInfo? = null
+ }
}
\ No newline at end of file
diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt
index 5f9e52a0e2..c6d5bd52eb 100644
--- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt
+++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageBodyResolver.kt
@@ -23,13 +23,9 @@ internal class EmailMessageBodyResolver(
is FeedbackEmailType.RateCanBeBetter -> addCardAndPhoneInfo(type.cardInfo)
is FeedbackEmailType.ScanningProblem -> addScanningProblemBody()
is FeedbackEmailType.TransactionSendingProblem -> addTransactionSendingProblemBody(type.cardInfo)
- is FeedbackEmailType.StakingProblem -> addStakingProblemBody(
- type.cardInfo,
- type.validatorName,
- type.transactionTypes,
- type.unsignedTransactions,
- )
- is FeedbackEmailType.SwapProblem -> addSwapProblemBody(type.cardInfo, type.providerName, type.txId)
+ is FeedbackEmailType.StakingProblem -> addStakingProblemBody(type)
+ is FeedbackEmailType.SwapProblem -> addSwapProblemBody(type)
+ is FeedbackEmailType.CurrencyDescriptionError -> addTokenInfo(type)
}
return build()
@@ -79,16 +75,11 @@ internal class EmailMessageBodyResolver(
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
}
- private suspend fun FeedbackDataBuilder.addStakingProblemBody(
- cardInfo: CardInfo,
- validatorName: String?,
- transactionTypes: List,
- unsignedTransactions: List,
- ) {
- addCardInfo(cardInfo)
+ private suspend fun FeedbackDataBuilder.addStakingProblemBody(type: FeedbackEmailType.StakingProblem) {
+ addCardInfo(type.cardInfo)
addDelimiter()
- val userWalletId = requireNotNull(cardInfo.userWalletId) { "UserWalletId must be not null" }
+ val userWalletId = requireNotNull(type.cardInfo.userWalletId) { "UserWalletId must be not null" }
val blockchainError = feedbackRepository.getBlockchainErrorInfo(userWalletId = userWalletId)
val blockchainInfo = blockchainError?.let {
feedbackRepository.getBlockchainInfo(
@@ -103,21 +94,21 @@ internal class EmailMessageBodyResolver(
addDelimiter()
}
- addStakingInfo(validatorName, transactionTypes, unsignedTransactions)
+ addStakingInfo(
+ validatorName = type.validatorName,
+ transactionTypes = type.transactionTypes,
+ unsignedTransactions = type.unsignedTransactions,
+ )
addDelimiter()
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
}
- private suspend fun FeedbackDataBuilder.addSwapProblemBody(
- cardInfo: CardInfo,
- providerName: String,
- txId: String,
- ) {
- addCardInfo(cardInfo)
+ private suspend fun FeedbackDataBuilder.addSwapProblemBody(type: FeedbackEmailType.SwapProblem) {
+ addCardInfo(type.cardInfo)
addDelimiter()
- val userWalletId = requireNotNull(cardInfo.userWalletId) { "UserWalletId must be not null" }
+ val userWalletId = requireNotNull(type.cardInfo.userWalletId) { "UserWalletId must be not null" }
val blockchainError = feedbackRepository.getBlockchainErrorInfo(userWalletId = userWalletId)
val blockchainInfo = blockchainError?.let {
feedbackRepository.getBlockchainInfo(
@@ -132,7 +123,7 @@ internal class EmailMessageBodyResolver(
addDelimiter()
}
- addSwapInfo(providerName, txId)
+ addSwapInfo(providerName = type.providerName, txId = type.txId)
addDelimiter()
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
@@ -143,4 +134,8 @@ internal class EmailMessageBodyResolver(
addDelimiter()
addPhoneInfo(phoneInfo = feedbackRepository.getPhoneInfo())
}
+
+ private fun FeedbackDataBuilder.addTokenInfo(type: FeedbackEmailType.CurrencyDescriptionError) {
+ addTokenShortInfo(id = type.currencyId, name = type.currencyName)
+ }
}
\ No newline at end of file
diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt
index 5f052face7..d0768aff1f 100644
--- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt
+++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailMessageTitleResolver.kt
@@ -16,7 +16,9 @@ internal class EmailMessageTitleResolver(private val resources: Resources) {
/** Resolve email message title by [type] */
fun resolve(type: FeedbackEmailType): String {
return when (type) {
- is FeedbackEmailType.DirectUserRequest -> R.string.feedback_preface_support
+ is FeedbackEmailType.DirectUserRequest,
+ is FeedbackEmailType.CurrencyDescriptionError,
+ -> R.string.feedback_preface_support
is FeedbackEmailType.RateCanBeBetter -> R.string.feedback_preface_rate_negative
is FeedbackEmailType.ScanningProblem -> R.string.feedback_preface_scan_failed
is FeedbackEmailType.TransactionSendingProblem,
diff --git a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt
index ba9c710413..a833d1b6d3 100644
--- a/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt
+++ b/domain/feedback/src/main/java/com/tangem/domain/feedback/utils/EmailSubjectResolver.kt
@@ -29,6 +29,7 @@ internal class EmailSubjectResolver(private val resources: Resources) {
is FeedbackEmailType.StakingProblem,
is FeedbackEmailType.SwapProblem,
-> R.string.feedback_subject_tx_failed
+ is FeedbackEmailType.CurrencyDescriptionError -> R.string.feedback_token_description_error
}
.let(resources::getString)
}
diff --git a/features/markets/impl/build.gradle.kts b/features/markets/impl/build.gradle.kts
index bd7451e801..94012bfcb8 100644
--- a/features/markets/impl/build.gradle.kts
+++ b/features/markets/impl/build.gradle.kts
@@ -23,6 +23,7 @@ dependencies {
implementation(projects.domain.balanceHiding.models)
implementation(projects.domain.card)
implementation(projects.domain.demo)
+ implementation(projects.domain.feedback)
implementation(projects.domain.manageTokens)
implementation(projects.domain.markets)
implementation(projects.domain.staking.models)
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt
index 08813a2998..b956de8357 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/MarketsTokenDetailsModel.kt
@@ -20,6 +20,8 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.utils.BigDecimalFormatter
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
+import com.tangem.domain.feedback.SendFeedbackEmailUseCase
+import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.markets.*
import com.tangem.features.markets.details.MarketsTokenDetailsComponent
import com.tangem.features.markets.details.impl.analytics.MarketDetailsAnalyticsEvent
@@ -57,6 +59,7 @@ internal class MarketsTokenDetailsModel @Inject constructor(
private val getTokenMarketInfoUseCase: GetTokenMarketInfoUseCase,
private val getTokenFullQuotesUseCase: GetTokenFullQuotesUseCase,
private val getTokenExchangesUseCase: GetTokenExchangesUseCase,
+ private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val urlOpener: UrlOpener,
private val analyticsEventHandler: AnalyticsEventHandler,
) : Model() {
@@ -109,6 +112,16 @@ internal class MarketsTokenDetailsModel @Inject constructor(
// === Analytics ===
analyticsEventHandler.send(analyticsEventBuilder.readMoreClicked())
},
+ onGeneratedAINotificationClick = {
+ modelScope.launch {
+ sendFeedbackEmailUseCase(
+ type = FeedbackEmailType.CurrencyDescriptionError(
+ currencyId = params.token.id,
+ currencyName = params.token.name,
+ ),
+ )
+ }
+ },
)
private val chartDataProducer = MarketChartDataProducer.build(dispatcher = dispatchers.default) {
@@ -268,52 +281,56 @@ internal class MarketsTokenDetailsModel @Inject constructor(
)
}
- val xAxisFormatter = MarketsDateTimeFormatters.getChartXFormatterByInterval(state.value.selectedInterval)
-
- chart.onRight {
- chartDataProducer.runTransactionSuspend {
- chartData = MarketChartData.Data(
- y = it.priceY.toImmutableList(),
- x = it.timeStamps.map { it.toBigDecimal() }.toImmutableList(),
- ).sorted()
-
- updateLook {
+ chart
+ .onRight { updateTokenChart(it) }
+ .onLeft {
+ state.update {
it.copy(
- xAxisFormatter = xAxisFormatter,
- type = state.value.priceChangeType.toChartType(),
+ chartState = it.chartState.copy(
+ status = MarketsTokenDetailsUM.ChartState.Status.ERROR,
+ ),
+ body = if (it.body is MarketsTokenDetailsUM.Body.Error) {
+ MarketsTokenDetailsUM.Body.Nothing
+ } else {
+ it.body
+ },
)
}
}
-
- state.update {
- it.copy(
- chartState = it.chartState.copy(
- status = MarketsTokenDetailsUM.ChartState.Status.DATA,
- ),
- body = if (it.body is MarketsTokenDetailsUM.Body.Nothing) {
- MarketsTokenDetailsUM.Body.Error(onLoadRetryClick = ::onLoadRetryClicked)
- } else {
- it.body
- },
- )
- }
- }.onLeft {
- state.update {
- it.copy(
- chartState = it.chartState.copy(
- status = MarketsTokenDetailsUM.ChartState.Status.ERROR,
- ),
- body = if (it.body is MarketsTokenDetailsUM.Body.Error) {
- MarketsTokenDetailsUM.Body.Nothing
- } else {
- it.body
- },
- )
- }
- }
}.saveIn(loadChartJobHolder)
}
+ private suspend fun updateTokenChart(tokenChart: TokenChart) {
+ val xAxisFormatter = MarketsDateTimeFormatters.getChartXFormatterByInterval(state.value.selectedInterval)
+
+ chartDataProducer.runTransactionSuspend {
+ chartData = MarketChartData.Data(
+ y = tokenChart.priceY.toImmutableList(),
+ x = tokenChart.timeStamps.map { it.toBigDecimal() }.toImmutableList(),
+ ).sorted()
+
+ updateLook {
+ it.copy(
+ xAxisFormatter = xAxisFormatter,
+ type = state.value.priceChangeType.toChartType(),
+ )
+ }
+ }
+
+ state.update {
+ it.copy(
+ chartState = it.chartState.copy(
+ status = MarketsTokenDetailsUM.ChartState.Status.DATA,
+ ),
+ body = if (it.body is MarketsTokenDetailsUM.Body.Nothing) {
+ MarketsTokenDetailsUM.Body.Error(onLoadRetryClick = ::onLoadRetryClicked)
+ } else {
+ it.body
+ },
+ )
+ }
+ }
+
private fun loadInfo() {
state.update {
it.copy(
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt
index 7b16182d58..e2e8e6f985 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/model/converters/DescriptionConverter.kt
@@ -12,7 +12,8 @@ import com.tangem.utils.converter.Converter
@Stable
internal class DescriptionConverter(
- val onReadModeClicked: (InfoBottomSheetContent) -> Unit,
+ private val onReadModeClicked: (InfoBottomSheetContent) -> Unit,
+ private val onGeneratedAINotificationClick: () -> Unit,
) : Converter {
override fun convert(value: TokenMarketInfo): MarketsTokenDetailsUM.Description? {
@@ -32,7 +33,9 @@ internal class DescriptionConverter(
),
),
body = stringReference(value.fullDescription ?: ""),
- showGeneratedAINotification = true,
+ generatedAINotificationUM = InfoBottomSheetContent.GeneratedAINotificationUM(
+ onClick = onGeneratedAINotificationClick,
+ ),
),
)
},
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt
index 594d0f81df..23e16539b1 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/components/InfoBottomSheet.kt
@@ -27,14 +27,14 @@ internal fun InfoBottomSheet(config: TangemBottomSheetConfig) {
config = config,
addBottomInsets = false,
title = { TangemBottomSheetTitle(title = it.title) },
- content = {
+ content = { content ->
Column(
modifier = Modifier
.verticalScroll(rememberScrollState())
.padding(horizontal = TangemTheme.dimens.spacing16),
) {
MarkdownText(
- markdown = it.body.resolveReference(),
+ markdown = content.body.resolveReference(),
disableLinkMovementMethod = true,
linkifyMask = 0,
syntaxHighlightColor = TangemTheme.colors.text.secondary,
@@ -43,8 +43,9 @@ internal fun InfoBottomSheet(config: TangemBottomSheetConfig) {
),
)
- if (it.showGeneratedAINotification) {
+ if (content.generatedAINotificationUM != null) {
AdditionalInfoNotification(
+ onClick = content.generatedAINotificationUM.onClick,
modifier = Modifier
.padding(top = TangemTheme.dimens.spacing12, bottom = TangemTheme.dimens.spacing16)
.fillMaxWidth(),
@@ -58,11 +59,13 @@ internal fun InfoBottomSheet(config: TangemBottomSheetConfig) {
}
@Composable
-private fun AdditionalInfoNotification(modifier: Modifier = Modifier) {
+private fun AdditionalInfoNotification(onClick: () -> Unit, modifier: Modifier = Modifier) {
Notification(
config = NotificationConfig(
subtitle = TextReference.Res(id = R.string.information_generated_with_ai),
iconResId = R.drawable.ic_magic_28,
+ onClick = onClick,
+ showArrowIcon = false,
),
modifier = modifier,
subtitleColor = TangemTheme.colors.text.primary1,
diff --git a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt
index c68995460f..d6af118609 100644
--- a/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt
+++ b/features/markets/impl/src/main/kotlin/com/tangem/features/markets/details/impl/ui/state/InfoBottomSheetContent.kt
@@ -6,5 +6,8 @@ import com.tangem.core.ui.extensions.TextReference
internal data class InfoBottomSheetContent(
val title: TextReference,
val body: TextReference,
- val showGeneratedAINotification: Boolean = false,
-) : TangemBottomSheetConfigContent
\ No newline at end of file
+ val generatedAINotificationUM: GeneratedAINotificationUM? = null,
+) : TangemBottomSheetConfigContent {
+
+ data class GeneratedAINotificationUM(val onClick: () -> Unit)
+}
\ No newline at end of file