Updated on 2026-08-14

This commit is contained in:
Tangem 2026-04-27 21:10:26 +04:00
parent b004f8df6b
commit 1964b3cfad
22 changed files with 204 additions and 173 deletions

View file

@ -16,14 +16,6 @@ object TangemBlogUrlBuilder {
val path: String val path: String
data object SeedNotify : Post {
override val path: String = "seed-notify"
}
data object SeedNotifySecond : Post {
override val path: String = "tangem-resolves-log-issue"
}
data object SeedPhraseRiskySolution : Post { data object SeedPhraseRiskySolution : Post {
override val path: String = "seed-phrase-faq" override val path: String = "seed-phrase-faq"
} }
@ -39,5 +31,21 @@ object TangemBlogUrlBuilder {
data object HowToScan : Post { data object HowToScan : Post {
override val path: String = "scan-tangem-card" override val path: String = "scan-tangem-card"
} }
data object HowToStake : Post {
override val path: String = "how-to-stake-cryptocurrency"
}
data object GiveRevokePermission : Post {
override val path: String = "give-revoke-permission"
}
data object HowYieldModeWorks : Post {
override val path: String = "yield-mode"
}
data object AboutCrossChainBridges : Post {
override val path: String = "an-overview-of-cross-chain-bridges"
}
} }
} }

View file

@ -13,7 +13,7 @@ object SupportedLanguages {
const val CHINESE = "zh" const val CHINESE = "zh"
const val SPANISH = "es" const val SPANISH = "es"
val supportedLangugeCodes = listOf( val supportedLanguageCodes = listOf(
ENGLISH, ENGLISH,
RUSSIAN, RUSSIAN,
GERMAN, GERMAN,
@ -25,10 +25,17 @@ object SupportedLanguages {
SPANISH, SPANISH,
) )
/**
* Returns the ISO 639-1 code of the device's current language when it belongs to
* [supportedLanguageCodes], otherwise falls back to [ENGLISH].
*
* Intended for callers that need a plain two-letter language code (e.g. URL path segments
* like `tangem.com/{en|ru}/...`).
*/
fun getCurrentSupportedLanguageCode(): String { fun getCurrentSupportedLanguageCode(): String {
val locale = Locale.getDefault() val locale = Locale.getDefault()
return if (supportedLangugeCodes.contains(locale.language)) { return if (supportedLanguageCodes.contains(locale.language)) {
locale.language locale.language
} else { } else {
ENGLISH ENGLISH

View file

@ -1,28 +0,0 @@
package com.tangem.utils
import java.util.Locale
@Deprecated("Use TangemBlogUrlBuilder from common module")
object TangemBlogUrlBuilder {
private const val RU_LOCALE = "ru"
private const val EN_LOCALE = "en"
private const val TANGEM_MAIN = "https://tangem.com/"
val FEE_BLOG_LINK: String
get(): String {
val locale = if (Locale.getDefault().language == RU_LOCALE) RU_LOCALE else EN_LOCALE
return buildString {
append(TANGEM_MAIN)
append(locale)
append("/blog/post/what-is-a-transaction-fee-and-why-do-we-need-it/")
}
}
const val RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP = "https://tangem.com/en/blog/post/give-revoke-permission/"
const val YIELD_SUPPLY_HOW_IT_WORKS_URL = "https://tangem.com/en/blog/post/yield-mode"
const val YIELD_SUPPLY_TOS_URL = "https://aave.com/terms-of-service"
const val YIELD_SUPPLY_PRIVACY_URL = "https://aave.com/privacy-policy"
}

View file

@ -0,0 +1,94 @@
package com.tangem.utils
import com.google.common.truth.Truth
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import java.util.Locale
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class SupportedLanguagesTest {
private lateinit var originalLocale: Locale
@BeforeEach
fun setUp() {
originalLocale = Locale.getDefault()
}
@AfterEach
fun tearDown() {
Locale.setDefault(originalLocale)
}
@Test
fun `getCurrentSupportedLanguageCode returns primary language when locale is supported`() {
// Arrange
Locale.setDefault(Locale("en", "US"))
// Act
val actual = SupportedLanguages.getCurrentSupportedLanguageCode()
// Assert
Truth.assertThat(actual).isEqualTo("en")
}
@Test
fun `getCurrentSupportedLanguageCode drops region for supported language`() {
// Arrange
Locale.setDefault(Locale("zh", "CN"))
// Act
val actual = SupportedLanguages.getCurrentSupportedLanguageCode()
// Assert
Truth.assertThat(actual).isEqualTo("zh")
}
@Test
fun `getCurrentSupportedLanguageCode returns ENGLISH when locale is not supported`() {
// Arrange — pt (Portuguese) is not in supportedLanguageCodes
Locale.setDefault(Locale("pt", "BR"))
// Act
val actual = SupportedLanguages.getCurrentSupportedLanguageCode()
// Assert
Truth.assertThat(actual).isEqualTo(SupportedLanguages.ENGLISH)
}
@Test
fun `getCurrentSupportedLanguageCode returns ENGLISH for empty language`() {
// Arrange
Locale.setDefault(Locale("", ""))
// Act
val actual = SupportedLanguages.getCurrentSupportedLanguageCode()
// Assert
Truth.assertThat(actual).isEqualTo(SupportedLanguages.ENGLISH)
}
@Test
fun `getCurrentSupportedLanguageCode supports every code in supportedLanguageCodes`() {
SupportedLanguages.supportedLanguageCodes.forEach { code ->
// Arrange
Locale.setDefault(Locale(code))
// Act
val actual = SupportedLanguages.getCurrentSupportedLanguageCode()
// Assert
Truth.assertThat(actual).isEqualTo(code)
}
}
@Test
fun `supportedLanguageCodes contains the expected nine ISO 639-1 codes`() {
// Assert
Truth.assertThat(SupportedLanguages.supportedLanguageCodes)
.containsExactly("en", "ru", "de", "fr", "it", "ja", "uk", "zh", "es")
.inOrder()
}
}

View file

@ -1,28 +0,0 @@
package com.tangem.data.common.locale
import java.util.Locale
/**
[REDACTED_AUTHOR]
*/
internal class DefaultLocaleProvider : LocaleProvider {
override fun getLocale(): Locale {
return Locale.getDefault()
}
override fun getWebUriLocaleLanguage(): String {
val language = getLocale().language
return if (LOCALE_LANG_RU.equals(language, true) || LOCALE_LANG_BY.equals(language, true)) {
LOCALE_LANG_RU
} else {
LOCALE_LANG_EN
}
}
companion object {
const val LOCALE_LANG_RU = "ru"
const val LOCALE_LANG_BY = "by"
const val LOCALE_LANG_EN = "en"
}
}

View file

@ -1,13 +0,0 @@
package com.tangem.data.common.locale
import java.util.Locale
/**
[REDACTED_AUTHOR]
*/
interface LocaleProvider {
fun getLocale(): Locale
fun getWebUriLocaleLanguage(): String
}

View file

@ -1,20 +0,0 @@
package com.tangem.data.common.locale.di
import com.tangem.data.common.locale.DefaultLocaleProvider
import com.tangem.data.common.locale.LocaleProvider
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object LocaleProviderModule {
@Provides
@Singleton
fun provideCacheRegistry(): LocaleProvider {
return DefaultLocaleProvider()
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.TransactionSender.MultipleTransactionSendMode import com.tangem.blockchain.common.TransactionSender.MultipleTransactionSendMode
import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.ui.bottomsheet.permission.state.ApproveType import com.tangem.common.ui.bottomsheet.permission.state.ApproveType
import com.tangem.common.ui.userwallet.ext.walletInterationIcon import com.tangem.common.ui.userwallet.ext.walletInterationIcon
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
@ -16,10 +17,7 @@ import com.tangem.core.analytics.models.Basic
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.models.currency.CryptoCurrency import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.transaction.error.GetFeeError import com.tangem.domain.transaction.error.GetFeeError
@ -37,7 +35,6 @@ import com.tangem.features.approval.api.GiveApprovalComponent
import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback import com.tangem.features.send.v2.api.callbacks.FeeSelectorModelCallback
import com.tangem.features.send.v2.api.entity.FeeItem import com.tangem.features.send.v2.api.entity.FeeItem
import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.utils.TangemBlogUrlBuilder.RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
@ -61,7 +58,6 @@ internal class GiveApprovalModel @Inject constructor(
private val getFeeForGaslessUseCase: GetFeeForGaslessUseCase, private val getFeeForGaslessUseCase: GetFeeForGaslessUseCase,
private val getFeeForTokenUseCase: GetFeeForTokenUseCase, private val getFeeForTokenUseCase: GetFeeForTokenUseCase,
private val createAndSendGaslessTransactionUseCase: CreateAndSendGaslessTransactionUseCase, private val createAndSendGaslessTransactionUseCase: CreateAndSendGaslessTransactionUseCase,
private val uiMessageSender: UiMessageSender,
private val urlOpener: UrlOpener, private val urlOpener: UrlOpener,
private val getUserWalletUseCase: GetUserWalletUseCase, private val getUserWalletUseCase: GetUserWalletUseCase,
private val analyticsEventHandler: AnalyticsEventHandler, private val analyticsEventHandler: AnalyticsEventHandler,
@ -119,16 +115,9 @@ internal class GiveApprovalModel @Inject constructor(
} }
fun onOpenLearnMoreAboutApproveClick() { fun onOpenLearnMoreAboutApproveClick() {
urlOpener.openUrl(RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP) modelScope.launch {
} urlOpener.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.GiveRevokePermission))
}
fun showPermissionInfoDialog() {
uiMessageSender.send(
DialogMessage(
message = resourceReference(com.tangem.common.ui.R.string.give_permission_staking_footer),
title = resourceReference(com.tangem.common.ui.R.string.common_approve),
),
)
} }
suspend fun loadFee(): Either<GetFeeError, TransactionFee> { suspend fun loadFee(): Either<GetFeeError, TransactionFee> {

View file

@ -1,8 +1,6 @@
package com.tangem.features.details.model package com.tangem.features.details.model
import android.content.res.Resources
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.utils.logging.TangemLogger
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
@ -37,6 +35,7 @@ import com.tangem.features.details.utils.ItemsBuilder
import com.tangem.features.details.utils.SocialsBuilder import com.tangem.features.details.utils.SocialsBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.info.AppInfoProvider import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.logging.TangemLogger
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
@ -44,7 +43,6 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
@ModelScoped @ModelScoped
@ -270,13 +268,4 @@ internal class DetailsModel @Inject constructor(
} }
private fun getAppVersion(): String = "${appInfoProvider.appVersion} (${appInfoProvider.appVersionCode})" private fun getAppVersion(): String = "${appInfoProvider.appVersion} (${appInfoProvider.appVersionCode})"
private companion object {
val SYSTEM_LANGUAGE = runCatching { Resources.getSystem().configuration.locales[0].language }.getOrElse { "" }
val APP_LANGUAGE = Locale.getDefault().language
val UTM_MARKS = "utm_source=tangem-app" +
"&utm_medium=app" +
"&utm_campaign=users-$SYSTEM_LANGUAGE" +
"&utm_content=devicelang-$APP_LANGUAGE"
}
} }

View file

@ -8,6 +8,7 @@ import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.Fee import com.tangem.blockchain.common.transaction.Fee
import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.getValidatorsCount import com.tangem.common.getValidatorsCount
import com.tangem.common.routing.AppRouter import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData import com.tangem.common.ui.amountScreen.converters.AmountReduceByTransformer.ReduceByData
@ -95,7 +96,6 @@ import com.tangem.features.staking.impl.presentation.state.utils.isSingleAction
import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstakeAction import com.tangem.features.staking.impl.presentation.state.utils.withStubUnstakeAction
import com.tangem.lib.crypto.BlockchainUtils.isTon import com.tangem.lib.crypto.BlockchainUtils.isTon
import com.tangem.utils.Provider import com.tangem.utils.Provider
import com.tangem.utils.TangemBlogUrlBuilder.RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP
import com.tangem.utils.coroutines.* import com.tangem.utils.coroutines.*
import com.tangem.utils.extensions.isSingleItem import com.tangem.utils.extensions.isSingleItem
import com.tangem.utils.extensions.orZero import com.tangem.utils.extensions.orZero
@ -603,7 +603,9 @@ internal class StakingModel @Inject constructor(
override fun onInitialInfoBannerClick() { override fun onInitialInfoBannerClick() {
analyticsEventHandler.send(StakingAnalyticsEvent.WhatIsStaking()) analyticsEventHandler.send(StakingAnalyticsEvent.WhatIsStaking())
innerRouter.openUrl(WHAT_IS_STAKING_ARTICLE_URL) modelScope.launch {
innerRouter.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.HowToStake))
}
} }
override fun onInfoClick(infoType: InfoType) { override fun onInfoClick(infoType: InfoType) {
@ -1100,7 +1102,9 @@ internal class StakingModel @Inject constructor(
} }
override fun onOpenLearnMoreAboutApproveClick() { override fun onOpenLearnMoreAboutApproveClick() {
urlOpener.openUrl(RESOURCE_TO_LEARN_ABOUT_APPROVING_IN_SWAP) modelScope.launch {
urlOpener.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.GiveRevokePermission))
}
} }
override fun onActivateTonAccountNotificationClick() { override fun onActivateTonAccountNotificationClick() {
@ -1495,7 +1499,6 @@ internal class StakingModel @Inject constructor(
} }
private companion object { private companion object {
const val WHAT_IS_STAKING_ARTICLE_URL = "https://tangem.com/en/blog/post/how-to-stake-cryptocurrency/"
const val ALLOWANCE_UPDATE_DELAY = 10_000L const val ALLOWANCE_UPDATE_DELAY = 10_000L
} }
} }

View file

@ -1,6 +1,7 @@
package com.tangem.features.staking.impl.presentation.model package com.tangem.features.staking.impl.presentation.model
import arrow.core.Either import arrow.core.Either
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.core.analytics.models.Basic import com.tangem.core.analytics.models.Basic
import com.tangem.core.ui.haptic.TangemHapticEffect import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.message.DialogMessage import com.tangem.core.ui.message.DialogMessage
@ -300,21 +301,24 @@ internal class StakingModelNavigationTest : StakingModelTestBase() {
@Test @Test
fun `WHEN onInitialInfoBannerClick THEN analytics sent and url opened`() = runTest { fun `WHEN onInitialInfoBannerClick THEN analytics sent and url opened`() = runTest {
val expectedUrl = "https://tangem.com/blog/post/how-to-stake-cryptocurrency/?utm_source=tangem-app"
mockkObject(TangemBlogUrlBuilder)
coEvery { TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.HowToStake) } returns expectedUrl
every { innerRouter.openUrl(any()) } just Runs every { innerRouter.openUrl(any()) } just Runs
val model = createModel(testScope = this) val model = createModel(testScope = this)
advanceUntilIdle() advanceUntilIdle()
model.onInitialInfoBannerClick() model.onInitialInfoBannerClick()
advanceUntilIdle()
verify { verify {
analyticsEventHandler.send(match { it is StakingAnalyticsEvent.WhatIsStaking }) analyticsEventHandler.send(match { it is StakingAnalyticsEvent.WhatIsStaking })
} }
verify { verify { innerRouter.openUrl(expectedUrl) }
innerRouter.openUrl("https://tangem.com/en/blog/post/how-to-stake-cryptocurrency/")
}
model.onDestroy() model.onDestroy()
unmockkObject(TangemBlogUrlBuilder)
} }
@Test @Test
@ -478,15 +482,20 @@ internal class StakingModelNavigationTest : StakingModelTestBase() {
@Test @Test
fun `WHEN onOpenLearnMoreAboutApproveClick THEN urlOpener opens approve url`() = runTest { fun `WHEN onOpenLearnMoreAboutApproveClick THEN urlOpener opens approve url`() = runTest {
val expectedUrl = "https://tangem.com/blog/post/give-revoke-permission/?utm_source=tangem-app"
mockkObject(TangemBlogUrlBuilder)
coEvery { TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.GiveRevokePermission) } returns expectedUrl
every { urlOpener.openUrl(any()) } just Runs every { urlOpener.openUrl(any()) } just Runs
val model = createModel(testScope = this) val model = createModel(testScope = this)
advanceUntilIdle() advanceUntilIdle()
model.onOpenLearnMoreAboutApproveClick() model.onOpenLearnMoreAboutApproveClick()
advanceUntilIdle()
verify { urlOpener.openUrl("https://tangem.com/en/blog/post/give-revoke-permission/") } verify { urlOpener.openUrl(expectedUrl) }
unmockkObject(TangemBlogUrlBuilder)
model.onDestroy() model.onDestroy()
} }
} }

View file

@ -96,6 +96,7 @@ import com.tangem.feature.swap.utils.getContractAddress
import com.tangem.features.approval.api.GiveApprovalComponent import com.tangem.features.approval.api.GiveApprovalComponent
import com.tangem.features.send.v2.api.entity.FeeSelectorUM import com.tangem.features.send.v2.api.entity.FeeSelectorUM
import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger import com.tangem.features.send.v2.api.subcomponents.feeSelector.FeeSelectorReloadTrigger
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.features.swap.SwapComponent import com.tangem.features.swap.SwapComponent
import com.tangem.utils.Provider import com.tangem.utils.Provider
import com.tangem.utils.coroutines.* import com.tangem.utils.coroutines.*
@ -1357,12 +1358,16 @@ internal class SwapModel @Inject constructor(
val selectedFee = (getSelectedFee() as? TxFee.Legacy)?.feeType ?: FeeType.NORMAL val selectedFee = (getSelectedFee() as? TxFee.Legacy)?.feeType ?: FeeType.NORMAL
val txFeeState = val txFeeState =
dataState.getCurrentLoadedSwapState()?.txFee as? TxFeeState.MultipleFeeState ?: return@UiActions dataState.getCurrentLoadedSwapState()?.txFee as? TxFeeState.MultipleFeeState ?: return@UiActions
uiState = stateBuilder.showSelectFeeBottomSheet( modelScope.launch {
uiState = uiState, val readMoreUrl = TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.WhatIsTransactionFee)
selectedFee = selectedFee, uiState = stateBuilder.showSelectFeeBottomSheet(
txFeeState = txFeeState, uiState = uiState,
) { selectedFee = selectedFee,
uiState = stateBuilder.dismissBottomSheet(uiState) txFeeState = txFeeState,
readMoreUrl = readMoreUrl,
) {
uiState = stateBuilder.dismissBottomSheet(uiState)
}
} }
}, },
onSelectFeeType = { txFee -> onSelectFeeType = { txFee ->

View file

@ -37,7 +37,6 @@ import com.tangem.utils.Provider
import com.tangem.utils.StringsSigns import com.tangem.utils.StringsSigns
import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.StringsSigns.TILDE_SIGN import com.tangem.utils.StringsSigns.TILDE_SIGN
import com.tangem.utils.TangemBlogUrlBuilder.FEE_BLOG_LINK
import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableList
@ -984,6 +983,7 @@ internal class StateBuilder(
uiState: SwapStateHolder, uiState: SwapStateHolder,
selectedFee: FeeType, selectedFee: FeeType,
txFeeState: TxFeeState.MultipleFeeState, txFeeState: TxFeeState.MultipleFeeState,
readMoreUrl: String,
onDismiss: () -> Unit, onDismiss: () -> Unit,
): SwapStateHolder { ): SwapStateHolder {
val config = ChooseFeeBottomSheetConfig( val config = ChooseFeeBottomSheetConfig(
@ -995,7 +995,7 @@ internal class StateBuilder(
} }
actions.onSelectFeeType.invoke(selectedItem) actions.onSelectFeeType.invoke(selectedItem)
}, },
readMoreUrl = FEE_BLOG_LINK, readMoreUrl = readMoreUrl,
feeItems = txFeeState.toFeeItemState(), feeItems = txFeeState.toFeeItemState(),
readMore = resourceReference(R.string.common_read_more), readMore = resourceReference(R.string.common_read_more),
onReadMoreClick = actions.onLinkClick, onReadMoreClick = actions.onLinkClick,

View file

@ -2,6 +2,7 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.model
import androidx.compose.runtime.Stable import androidx.compose.runtime.Stable
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState import com.tangem.common.ui.expressStatus.state.ExpressTransactionsBlockState
@ -114,6 +115,12 @@ internal class ExpressTransactionsModel @Inject constructor(
router.openUrl(url) router.openUrl(url)
} }
override fun onReadAboutCrossChainBridgesClick() {
modelScope.launch {
router.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.AboutCrossChainBridges))
}
}
override fun onConfirmDisposeExpressStatus() { override fun onConfirmDisposeExpressStatus() {
uiMessageSender.send( uiMessageSender.send(
DialogMessage( DialogMessage(

View file

@ -100,6 +100,8 @@ interface ExpressTransactionsClickIntents {
fun onOpenUrlClick(url: String) fun onOpenUrlClick(url: String)
fun onReadAboutCrossChainBridgesClick()
fun onConfirmDisposeExpressStatus() fun onConfirmDisposeExpressStatus()
fun onDisposeExpressStatus() fun onDisposeExpressStatus()

View file

@ -13,6 +13,7 @@ import com.tangem.domain.dynamicaddresses.DynamicAddressesDerivationChecker
import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles import com.tangem.domain.dynamicaddresses.DynamicAddressesFeatureToggles
import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains import com.tangem.domain.dynamicaddresses.DynamicAddressesSupportedBlockchains
import com.tangem.domain.dynamicaddresses.IsXpubSupportedUseCase import com.tangem.domain.dynamicaddresses.IsXpubSupportedUseCase
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.routing.AppRoute import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter import com.tangem.common.routing.AppRouter
import com.tangem.common.ui.bottomsheet.receive.AddressModel import com.tangem.common.ui.bottomsheet.receive.AddressModel
@ -960,6 +961,12 @@ internal class TokenDetailsModel @Inject constructor(
router.openUrl(url) router.openUrl(url)
} }
override fun onReadAboutCrossChainBridgesClick() {
modelScope.launch {
router.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.AboutCrossChainBridges))
}
}
override fun onSwapPromoDismiss(promoId: PromoId) { override fun onSwapPromoDismiss(promoId: PromoId) {
modelScope.launch(dispatchers.main) { modelScope.launch(dispatchers.main) {
shouldShowPromoTokenUseCase.neverToShow(promoId) shouldShowPromoTokenUseCase.neverToShow(promoId)

View file

@ -39,7 +39,6 @@ import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList import kotlinx.collections.immutable.toPersistentList
import com.tangem.utils.logging.TangemLogger import com.tangem.utils.logging.TangemLogger
import java.math.BigDecimal import java.math.BigDecimal
import java.util.Locale
// Fixme [REDACTED_JIRA] // Fixme [REDACTED_JIRA]
@Suppress("LargeClass") @Suppress("LargeClass")
@ -232,7 +231,7 @@ internal class TokenDetailsSwapTransactionsStateConverter(
} else { } else {
ExchangeStatusNotification.TokenRefunded( ExchangeStatusNotification.TokenRefunded(
cryptoCurrency = refundToken, cryptoCurrency = refundToken,
onReadMoreClick = { clickIntents.onOpenUrlClick(url = getAboutCrossChainBridgesLink()) }, onReadMoreClick = clickIntents::onReadAboutCrossChainBridgesClick,
onGoToTokenClick = { clickIntents.onGoToRefundedTokenClick(refundToken) }, onGoToTokenClick = { clickIntents.onGoToRefundedTokenClick(refundToken) },
) )
} }
@ -424,12 +423,4 @@ internal class TokenDetailsSwapTransactionsStateConverter(
isDone = isSendingDone, isDone = isSendingDone,
) )
} }
private fun getAboutCrossChainBridgesLink(): String {
return if (Locale.getDefault().country == "RU") {
"https://tangem.com/ru/blog/post/an-overview-of-cross-chain-bridges/"
} else {
"https://tangem.com/en/blog/post/an-overview-of-cross-chain-bridges/"
}
}
} }

View file

@ -12,6 +12,7 @@ import com.domain.blockaid.models.transaction.SimulationResult
import com.domain.blockaid.models.transaction.ValidationResult import com.domain.blockaid.models.transaction.ValidationResult
import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model import com.tangem.core.decompose.model.Model
@ -59,7 +60,6 @@ import com.tangem.features.walletconnect.transaction.entity.send.WcSendTransacti
import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes import com.tangem.features.walletconnect.transaction.routes.WcTransactionRoutes
import com.tangem.features.walletconnect.transaction.ui.blockaid.WcSendAndReceiveBlockAidUiConverter import com.tangem.features.walletconnect.transaction.ui.blockaid.WcSendAndReceiveBlockAidUiConverter
import com.tangem.features.walletconnect.utils.WcNotificationsFactory import com.tangem.features.walletconnect.utils.WcNotificationsFactory
import com.tangem.utils.SupportedLanguages
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -301,14 +301,10 @@ internal class WcSendTransactionModel @Inject constructor(
stackNavigation.pop() stackNavigation.pop()
} }
@Deprecated("Use TangemBlockUrlBuilder instead")
private fun onApproveLearnMoreClick() { private fun onApproveLearnMoreClick() {
val code = SupportedLanguages.getCurrentSupportedLanguageCode() modelScope.launch {
.takeIf { it == SupportedLanguages.RUSSIAN } urlOpener.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.GiveRevokePermission))
?: SupportedLanguages.ENGLISH }
val url = "https://tangem.com/$code/blog/post/give-revoke-permission/"
urlOpener.openUrl(url)
} }
private fun isMultipleSignRequired(useCase: WcSignUseCase<*>): Boolean { private fun isMultipleSignRequired(useCase: WcSignUseCase<*>): Boolean {

View file

@ -4,6 +4,7 @@ import arrow.core.getOrElse
import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.activate
import com.arkivanov.decompose.router.slot.dismiss import com.arkivanov.decompose.router.slot.dismiss
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.routing.AppRouter import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
@ -34,13 +35,12 @@ import com.tangem.features.yield.supply.impl.active.model.transformers.YieldSupp
import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyApproveComponent import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyApproveComponent
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent
import com.tangem.utils.StringsSigns.DASH_SIGN import com.tangem.utils.StringsSigns.DASH_SIGN
import com.tangem.utils.TangemBlogUrlBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger
import com.tangem.utils.transformer.update import com.tangem.utils.transformer.update
import kotlinx.collections.immutable.persistentListOf import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.* import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import com.tangem.utils.logging.TangemLogger
import javax.inject.Inject import javax.inject.Inject
@Suppress("LongParameterList", "LargeClass") @Suppress("LongParameterList", "LargeClass")
@ -160,7 +160,9 @@ internal class YieldSupplyActiveModel @Inject constructor(
} }
fun onReadMoreClick() { fun onReadMoreClick() {
urlOpener.openUrl(TangemBlogUrlBuilder.YIELD_SUPPLY_HOW_IT_WORKS_URL) modelScope.launch {
urlOpener.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.HowYieldModeWorks))
}
} }
private fun subscribeOnCurrencyStatusUpdates() { private fun subscribeOnCurrencyStatusUpdates() {

View file

@ -2,6 +2,7 @@ package com.tangem.features.yield.supply.impl.promo.model
import com.arkivanov.decompose.router.slot.SlotNavigation import com.arkivanov.decompose.router.slot.SlotNavigation
import com.arkivanov.decompose.router.slot.activate import com.arkivanov.decompose.router.slot.activate
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.routing.AppRouter import com.tangem.common.routing.AppRouter
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped import com.tangem.core.decompose.di.ModelScoped
@ -11,13 +12,12 @@ import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.extensions.wrappedList
import com.tangem.features.yield.supply.api.YieldSupplyPromoComponent import com.tangem.features.yield.supply.api.YieldSupplyPromoComponent
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics import com.tangem.features.yield.supply.api.analytics.YieldSupplyAnalytics
import com.tangem.features.yield.supply.impl.R
import com.tangem.features.yield.supply.impl.promo.YieldSupplyPromoConfig import com.tangem.features.yield.supply.impl.promo.YieldSupplyPromoConfig
import com.tangem.features.yield.supply.impl.promo.entity.YieldSupplyPromoUM import com.tangem.features.yield.supply.impl.promo.entity.YieldSupplyPromoUM
import com.tangem.utils.TangemBlogUrlBuilder
import com.tangem.utils.TangemBlogUrlBuilder.YIELD_SUPPLY_HOW_IT_WORKS_URL
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ModelScoped @ModelScoped
@ -32,8 +32,8 @@ internal class YieldSupplyPromoModel @Inject constructor(
val params: YieldSupplyPromoComponent.Params = paramsContainer.require() val params: YieldSupplyPromoComponent.Params = paramsContainer.require()
val uiState: YieldSupplyPromoUM = YieldSupplyPromoUM( val uiState: YieldSupplyPromoUM = YieldSupplyPromoUM(
tosLink = TangemBlogUrlBuilder.YIELD_SUPPLY_TOS_URL, tosLink = AAVE_TOS_URL,
policyLink = TangemBlogUrlBuilder.YIELD_SUPPLY_PRIVACY_URL, policyLink = AAVE_PRIVACY_URL,
tokenSymbol = params.currency.symbol, tokenSymbol = params.currency.symbol,
title = resourceReference( title = resourceReference(
R.string.yield_module_promo_screen_title_v2, R.string.yield_module_promo_screen_title_v2,
@ -69,10 +69,17 @@ internal class YieldSupplyPromoModel @Inject constructor(
} }
override fun onHowItWorksClick() { override fun onHowItWorksClick() {
urlOpener.openUrl(YIELD_SUPPLY_HOW_IT_WORKS_URL) modelScope.launch {
urlOpener.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.HowYieldModeWorks))
}
} }
override fun onStartEarningClick() { override fun onStartEarningClick() {
bottomSheetNavigation.activate(YieldSupplyPromoConfig.Action) bottomSheetNavigation.activate(YieldSupplyPromoConfig.Action)
} }
private companion object {
const val AAVE_TOS_URL = "https://aave.com/terms-of-service"
const val AAVE_PRIVACY_URL = "https://aave.com/privacy-policy"
}
} }

View file

@ -3,6 +3,7 @@ package com.tangem.features.yield.supply.impl.subcomponents.approve.model
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.blockchain.common.TransactionData import com.tangem.blockchain.common.TransactionData
import com.tangem.blockchain.common.transaction.TransactionFee import com.tangem.blockchain.common.transaction.TransactionFee
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
@ -39,7 +40,6 @@ import com.tangem.features.yield.supply.impl.subcomponents.approve.YieldSupplyAp
import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsComponent import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsComponent
import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsUpdateTrigger import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSupplyNotificationsUpdateTrigger
import com.tangem.features.yield.supply.impl.subcomponents.notifications.entity.YieldSupplyNotificationData import com.tangem.features.yield.supply.impl.subcomponents.notifications.entity.YieldSupplyNotificationData
import com.tangem.utils.TangemBlogUrlBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.logging.TangemLogger import com.tangem.utils.logging.TangemLogger
import com.tangem.utils.transformer.update import com.tangem.utils.transformer.update
@ -113,7 +113,9 @@ internal class YieldSupplyApproveModel @Inject constructor(
} }
fun onReadMoreClick() { fun onReadMoreClick() {
urlOpener.openUrl(TangemBlogUrlBuilder.FEE_BLOG_LINK) modelScope.launch {
urlOpener.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.WhatIsTransactionFee))
}
} }
override fun onFeeReload() { override fun onFeeReload() {

View file

@ -1,6 +1,7 @@
package com.tangem.features.yield.supply.impl.subcomponents.stopearning.model package com.tangem.features.yield.supply.impl.subcomponents.stopearning.model
import arrow.core.getOrElse import arrow.core.getOrElse
import com.tangem.common.TangemBlogUrlBuilder
import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter import com.tangem.common.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.analytics.models.AnalyticsParam
@ -39,7 +40,6 @@ import com.tangem.features.yield.supply.impl.subcomponents.notifications.YieldSu
import com.tangem.features.yield.supply.impl.subcomponents.notifications.entity.YieldSupplyNotificationData import com.tangem.features.yield.supply.impl.subcomponents.notifications.entity.YieldSupplyNotificationData
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent import com.tangem.features.yield.supply.impl.subcomponents.stopearning.YieldSupplyStopEarningComponent
import com.tangem.features.yield.supply.impl.subcomponents.stopearning.model.transformer.YieldSupplyStopEarningFeeContentTransformer import com.tangem.features.yield.supply.impl.subcomponents.stopearning.model.transformer.YieldSupplyStopEarningFeeContentTransformer
import com.tangem.utils.TangemBlogUrlBuilder
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.extensions.orZero import com.tangem.utils.extensions.orZero
import com.tangem.utils.logging.TangemLogger import com.tangem.utils.logging.TangemLogger
@ -128,7 +128,9 @@ internal class YieldSupplyStopEarningModel @Inject constructor(
} }
fun onReadMoreClick() { fun onReadMoreClick() {
urlOpener.openUrl(TangemBlogUrlBuilder.FEE_BLOG_LINK) modelScope.launch {
urlOpener.openUrl(TangemBlogUrlBuilder.build(TangemBlogUrlBuilder.Post.WhatIsTransactionFee))
}
} }
fun onClick() { fun onClick() {