Updated on 2026-08-14

This commit is contained in:
Tangem 2025-02-12 17:35:12 +05:00
parent 69754e4159
commit 34cfc843ab
31 changed files with 322 additions and 170 deletions

View file

@ -4,6 +4,7 @@ import android.content.Context
import android.util.Log
import coil.ImageLoader
import coil.memory.MemoryCache
import coil.request.CachePolicy
import coil.util.Logger
import com.tangem.datasource.api.common.createNetworkLoggingInterceptor
import okhttp3.OkHttpClient
@ -24,6 +25,7 @@ fun createCoilImageLoader(context: Context, logEnabled: Boolean = false): ImageL
.build()
}
}
.memoryCachePolicy(CachePolicy.ENABLED)
.memoryCache {
MemoryCache.Builder(context)
.maxSizePercent(COIL_MEMORY_CACHE_SIZE)

View file

@ -2,6 +2,7 @@ package com.tangem.tap.common.images
import android.content.Context
import coil.imageLoader
import coil.request.CachePolicy
import coil.request.ImageRequest
import com.tangem.core.ui.coil.ImagePreloader
@ -12,6 +13,8 @@ internal class DefaultImagePreloader(
appContext.imageLoader.enqueue(
ImageRequest.Builder(appContext)
.data(url)
.memoryCacheKey(url)
.memoryCachePolicy(CachePolicy.ENABLED)
.build(),
)
}

View file

@ -27,8 +27,8 @@ internal object PromoDomainModule {
@Provides
@Singleton
fun provideShouldShowSwapStoriesUseCase(promoRepository: PromoRepository): ShouldShowSwapStoriesUseCase {
return ShouldShowSwapStoriesUseCase(promoRepository)
fun provideShouldShowSwapStoriesUseCase(promoRepository: PromoRepository): ShouldShowStoriesUseCase {
return ShouldShowStoriesUseCase(promoRepository)
}
@Provides

View file

@ -30,6 +30,7 @@ import com.tangem.domain.settings.usercountry.FetchUserCountryUseCase
import com.tangem.domain.staking.FetchStakingTokensUseCase
import com.tangem.domain.wallets.legacy.UserWalletsListManager
import com.tangem.features.onramp.OnrampFeatureToggles
import com.tangem.features.swap.SwapFeatureToggles
import com.tangem.tap.common.extensions.setContext
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.hilt.android.lifecycle.HiltViewModel
@ -58,6 +59,7 @@ internal class MainViewModel @Inject constructor(
private val getStoryContentUseCase: GetStoryContentUseCase,
private val imagePreloader: ImagePreloader,
private val fetchHotCryptoUseCase: FetchHotCryptoUseCase,
private val swapFeatureToggles: SwapFeatureToggles,
onrampFeatureToggles: OnrampFeatureToggles,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
) : ViewModel() {
@ -298,12 +300,16 @@ internal class MainViewModel @Inject constructor(
private fun preloadImages() {
try {
viewModelScope.launch {
val swapStories = getStoryContentUseCase(StoryContentIds.STORY_FIRST_TIME_SWAP.id).getOrNull()
?: return@launch
val storiesImages = swapStories.getImageUrls()
val swapStories = if (swapFeatureToggles.isPromoStoriesEnabled) {
getStoryContentUseCase.invokeSync(StoryContentIds.STORY_FIRST_TIME_SWAP.id)
.getOrNull()
} else {
null
}
val storiesImages = swapStories?.getImageUrls()
// try to preload images for stories
storiesImages.forEach(imagePreloader::preload)
storiesImages?.forEach(imagePreloader::preload)
}
} catch (ex: Exception) {
Timber.e(ex.message)

View file

@ -3,10 +3,7 @@ package com.tangem.common.ui.swapStoriesScreen
import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
@ -23,8 +20,8 @@ import androidx.compose.ui.unit.TextUnitType
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.SubcomposeAsyncImage
import coil.request.CachePolicy
import coil.request.ImageRequest
import com.tangem.core.ui.components.SpacerH16
import com.tangem.core.ui.components.SystemBarsIconsDisposable
import com.tangem.core.ui.components.stories.StoriesContainer
import com.tangem.core.ui.extensions.resolveReference
@ -35,7 +32,7 @@ import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import kotlinx.collections.immutable.persistentListOf
private val SubtitleColor = Color(0xFF868692)
private val SubtitleColor = Color(0xFFB0B0B0)
private const val STORIES_RELATIVE_PADDING = 0.7
@Composable
@ -60,45 +57,57 @@ fun SwapStoriesScreen(config: SwapStoriesUM) {
.data(current.imageUrl)
.crossfade(enable = false)
.allowHardware(true)
.memoryCacheKey(current.imageUrl)
.memoryCachePolicy(CachePolicy.ENABLED)
.build(),
loading = { },
error = { },
contentDescription = null,
)
val height = LocalWindowSize.current.height.value
val textAlign = (height.dp.value * STORIES_RELATIVE_PADDING).dp
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(
top = textAlign,
start = 56.dp,
end = 56.dp,
),
) {
Text(
text = current.title.resolveReference(),
style = TangemTheme.typography.head,
color = TangemColorPalette.SoftWhite,
textAlign = TextAlign.Center,
)
SpacerH16()
Text(
text = current.subtitle.resolveReference(),
style = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
letterSpacing = TextUnit(value = 0.1f, type = TextUnitType.Sp),
lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp),
),
color = SubtitleColor,
textAlign = TextAlign.Center,
)
}
SwapStoriesText(current)
}
}
}
@Composable
private fun SwapStoriesText(current: SwapStoriesUM.Content.Config) {
val height = LocalWindowSize.current.height.value
val textAlign = (height.dp.value * STORIES_RELATIVE_PADDING).dp
Column(
verticalArrangement = Arrangement.spacedBy(14.dp),
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.padding(
top = textAlign,
start = 44.dp,
end = 44.dp,
),
) {
Text(
text = current.title.resolveReference(),
style = TextStyle(
fontSize = 28.sp,
fontWeight = FontWeight.Bold,
letterSpacing = TextUnit(value = 0.1f, type = TextUnitType.Sp),
lineHeight = TextUnit(value = 34f, type = TextUnitType.Sp),
),
color = TangemTheme.colors.text.constantWhite,
textAlign = TextAlign.Center,
)
Text(
text = current.subtitle.resolveReference(),
style = TextStyle(
fontSize = 16.sp,
fontWeight = FontWeight.Normal,
letterSpacing = TextUnit(value = 0.1f, type = TextUnitType.Sp),
lineHeight = TextUnit(value = 20f, type = TextUnitType.Sp),
),
color = SubtitleColor,
textAlign = TextAlign.Center,
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360, heightDp = 720)
@Preview(showBackground = true, widthDp = 360, heightDp = 720, uiMode = Configuration.UI_MODE_NIGHT_YES)

View file

@ -127,10 +127,7 @@ interface TangemTechApi {
suspend fun getHotCrypto(@Query("currency") currencyId: String): ApiResponse<HotCryptoResponse>
@GET("stories/{story_id}")
suspend fun getStoryById(
@Path("story_id") storyId: String,
@Query("language") language: String,
): ApiResponse<StoryContentResponse>
suspend fun getStoryById(@Path("story_id") storyId: String): ApiResponse<StoryContentResponse>
companion object {
val marketsQuoteFields = listOf(

View file

@ -119,7 +119,7 @@ object PreferencesKeys {
val WAS_LOG_FILE_CLEARED by lazy { booleanPreferencesKey(name = "wasLogFileCleared") }
val SHOULD_SHOW_SWAP_STORIES_KEY by lazy { booleanPreferencesKey("shouldShowSwapStories") }
fun getShouldShowStoriesKey(storyId: String) = booleanPreferencesKey("shouldShowStories_$storyId")
// region Permission
fun getShouldShowPermission(permission: String) = booleanPreferencesKey("shouldShowPushPermission_$permission")

View file

@ -2,6 +2,7 @@ package com.tangem.datasource.local.promo
import com.tangem.datasource.api.promotion.models.StoryContentResponse
import com.tangem.datasource.local.datastore.core.StringKeyDataStore
import kotlinx.coroutines.flow.Flow
internal class DefaultPromoStoriesStore(
private val dataStore: StringKeyDataStore<StoryContentResponse>,
@ -10,6 +11,10 @@ internal class DefaultPromoStoriesStore(
return dataStore.getSyncOrNull(storyId)
}
override fun get(storyId: String): Flow<StoryContentResponse?> {
return dataStore.get(storyId)
}
override suspend fun store(storyId: String, item: StoryContentResponse) {
dataStore.store(storyId, item)
}

View file

@ -1,9 +1,12 @@
package com.tangem.datasource.local.promo
import com.tangem.datasource.api.promotion.models.StoryContentResponse
import kotlinx.coroutines.flow.Flow
interface PromoStoriesStore {
suspend fun getSyncOrNull(storyId: String): StoryContentResponse?
fun get(storyId: String): Flow<StoryContentResponse?>
suspend fun store(storyId: String, item: StoryContentResponse)
}

View file

@ -344,7 +344,7 @@
<string name="home_button_order">Karte oder Ring bestellen</string>
<string name="home_button_scan">Karte oder Ring scannen</string>
<string name="hot_crypto_add_token_subtitle">An %s</string>
<string name="hot_crypto_token_network">In %s</string>
<string name="hot_crypto_token_network">Im %s Netzwerk</string>
<string name="information_generated_with_ai">Diese Informationen wurden mithilfe von KI generiert.\nTippe hier, wenn Du Fehler finden.</string>
<string name="initial_message_change_access_code_body">Um den Zugangscode zu ändern, halte die Karte oder Ring wie oben gezeigt an das Gerät und entferne sie erst am Ende des Vorgangs.</string>
<string name="initial_message_change_passcode_body">Um den Passcode zu ändern, halte die Karte oder Ring wie oben gezeigt an das Gerät und entferne sie erst am Ende des Vorgangs.</string>
@ -571,7 +571,7 @@
<string name="onboarding_title_notifications">Benachrichtigungen</string>
<string name="onboarding_title_one_backup_card">Eine Backupgerät hinzugefügt</string>
<string name="onboarding_title_scan_origin_card">Bereite deine Karte oder Ring vor</string>
<string name="onboarding_title_two_backup_cards">Zweis Backupgerät hinzugefügt</string>
<string name="onboarding_title_two_backup_cards">Zweites Backupgerät hinzugefügt</string>
<string name="onboarding_top_up_body">Um zu beginnen, lade einfach die wallet mit einem beliebigen Betrag auf</string>
<string name="onboarding_top_up_body_no_account_error">Um zu beginnen, lade die Wallet einfach mit mehr als %1$s %2$s auf.</string>
<string name="onboarding_top_up_button_but_crypto">Krypto kaufen</string>
@ -786,13 +786,15 @@
<string name="staking_details_unbonding_period_info">Der Zeitraum, den du nach der Beantragung der Abhebung von Geldern aus dem Staking warten musst, bevor die Token verfügbar werden.</string>
<string name="staking_details_warmup_period">Aufwärmphase</string>
<string name="staking_details_warmup_period_info">Die zugewiesene Zeit für die Aktivierung der Teilnahme am Staking.</string>
<string name="staking_error_no_validators_message">Zurzeit sind keine Validierer verfügbar. Bitte versuche es später noch einmal.</string>
<string name="staking_error_no_validators_title">Staking nicht verfügbar</string>
<string name="staking_give_permission_fee_footer">Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Du die Verwendung Deines Tokens für das Staking genehmigst.</string>
<string name="staking_legal">Indem Du die Staking-Funktionalität nutzt, stimmst Du den %1$s und %2$s des Anbieters zu.</string>
<string name="staking_locked">Gesperrt</string>
<string name="staking_migrate">Migrieren</string>
<string name="staking_native">Natives Staking</string>
<string name="staking_no_validators_error_message">Derzeit sind keine aktiven Validatoren für das Staking verfügbar. Bitte versuchen Sie es später erneut.</string>
<string name="staking_notification_additional_ada_deposit_text">Beim Staking im Cardano Netzwerk wird das gesamte Guthaben genutzt. Zusätzlich werden 2 ADA reserviert und nach dem Unstaking zurückgegeben. Deine ADA sind während des Stakings nicht gesperrt.</string>
<string name="staking_notification_additional_ada_deposit_text">Beim Staking im Cardano-Netzwerk wird Dein gesamtes Guthaben verwendet. Weitere 2 ADA werden reserviert und nach dem Unstaking zurückgegeben. Deine ADA bleiben während des Stakings freigeschaltet.</string>
<string name="staking_notification_additional_ada_deposit_title">Einzelheiten zum ADA-Staking</string>
<string name="staking_notification_claim_rewards_text">Verdiente Belohnungen werden an Deine Wallet gesendet und stehen Dir sofort zur Verfügung</string>
<string name="staking_notification_earn_rewards_text">Stake sicher und verdienen Belohnungen</string>
@ -810,7 +812,7 @@
<string name="staking_notification_minimum_balance_error_text">Für erneutes Staking sind mindestens %1$s %2$s erforderlich. Bitte lade Dein Guthaben auf.</string>
<string name="staking_notification_minimum_balance_error_title">Nicht genug %s</string>
<string name="staking_notification_network_error_text">Aufgrund von Netzwerkproblemen ist Staking derzeit nicht verfügbar. Bitte versuche es später erneut.</string>
<string name="staking_notification_new_validator_funds_transfer">Beim Staking im %1$s -Netzwerk mit einem neuen Validator werden alle zuvor eingesetzten Kryptos automatisch an diesen Validator übertragen</string>
<string name="staking_notification_new_validator_funds_transfer">Wenn Du im %1$s -Netzwerk mit einem neuen Validator stakest, werden alle zuvor eingesetzten Gelder automatisch an diesen Validator übertragen.</string>
<string name="staking_notification_restake_rewards_text">Reinvestiert Deine verdienten Prämien in Deinen Einsatzbetrag und erhöht so den potenziellen Gewinn.</string>
<string name="staking_notification_restake_text">Mit Restake kannst Du Dein Guthaben von einem Validator zu einem anderen verschieben, ohne dass Du den Stake aufheben musst.</string>
<string name="staking_notification_stake_entire_balance_text">Du bist dabei, Dein gesamtes Guthaben zu staken. Wir empfehlen, einen kleinen Betrag übrig zu lassen, um die Netzwerkgebühren für die Aufhebung des Stakes oder das Einfordern von Prämien abzudecken.</string>
@ -879,6 +881,16 @@
<string name="swap_give_permission_fee_footer">Das Netzwerk erhebt eine Token-Genehmigungsgebühr, um zu überprüfen, ob Sie die Verwendung Ihres Tokens für den Swap genehmigen.</string>
<string name="swap_promo_text">Tausche mehr Token zu besseren Kursen direkt in deiner Brieftasche.</string>
<string name="swap_promo_title">Neuer Swap-Anbieter verfügbar!</string>
<string name="swap_story_fifth_subtitle">Du kannst Dich darauf verlassen, dass Deine Transaktionen jederzeit reibungslos ablaufen, da Sie ständig geprüft werden</string>
<string name="swap_story_fifth_title">Hilfe rund um die Uhr</string>
<string name="swap_story_first_subtitle">Mit vertrauenswürdigen Börsenanbietern kannst Du mühelos Vermögenswerte tauschen, wobei alles sicher in Deiner Wallet verbleibt</string>
<string name="swap_story_first_title">Tausche sich mit uns</string>
<string name="swap_story_forth_subtitle">Höchste Sicherheit und geprüfte Anbieter gewährleisten, dass Deine Vermögenswerte bei jedem Swap geschützt sind</string>
<string name="swap_story_forth_title">Sicherer denn je</string>
<string name="swap_story_second_subtitle">Maximiere Deine Wert mit Tarifen aus einem breiten Netzwerk vertrauenswürdiger Anbieter und wähle immer den Besten aus</string>
<string name="swap_story_second_title">Bester Preis</string>
<string name="swap_story_third_subtitle">Problemlos und intuitiv, sodass Deine Token mit nur wenigen Handgriffen getauscht werden können</string>
<string name="swap_story_third_title">Einfacher als je zuvor</string>
<string name="swapping_alert_cex_description">Der Betrag umfasst:\n- Gebühr des Dienstanbieters\n- Netzgebühr für die Rücksendung von %s von der Vermittlungsstelle an die Adresse des Nutzers.</string>
<string name="swapping_alert_cex_description_with_slippage">Der Betrag beinhaltet:\n• Honorar des Dienstleisters\n• Netzwerkgebühr für das Senden %1$s von der Börse zurück an die Adresse des Benutzers. \n\n Provider-Slippage kann bis zu %2$s</string>
<string name="swapping_alert_dex_description">Der Betrag enthält die Gebühren des Dienstleisters.</string>
@ -904,7 +916,7 @@
<string name="token_button_unavailability_reason_empty_balance_sell">Du hast kein Guthaben um dies zu Verkaufen. Lade dein Konto auf, um Geld von dort zu verkaufen.</string>
<string name="token_button_unavailability_reason_empty_balance_send">Du hast kein Guthaben zum Versenden. Lade dein Konto auf, um Geld von dort aus senden zu können.</string>
<string name="token_button_unavailability_reason_not_exchangeable">Das Tauschen von %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen.</string>
<string name="token_button_unavailability_reason_pending_transaction_sell">Das Geld für den Verkauf steht zur Verfügung, sobald die ausstehende(n) Transaktion(en) im Netzwerk %s abgeschlossen ist/sind.</string>
<string name="token_button_unavailability_reason_pending_transaction_sell">Der Verkauf von Geldmitteln ist möglich, sobald die ausstehende(n) Transaktion(en) im %s -Netzwerk abgeschlossen ist/sind.</string>
<string name="token_button_unavailability_reason_pending_transaction_send">Die Geldsendung wird verfügbar, sobald die ausstehende(n) Transaktion(en) im Netzwerk %s abgeschlossen ist/sind.</string>
<string name="token_button_unavailability_reason_sell_unavailable">Der Verkauf von %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen.</string>
<string name="token_button_unavailability_reason_staking_unavailable">Staking %s wird von aktuellen Anbietern nicht unterstützt, aber wir arbeiten daran, weitere Optionen hinzuzufügen.</string>

View file

@ -87,6 +87,8 @@
<string name="cardano_max_amount_has_token_title">ADA insuficiente</string>
<string name="common_accept">Aceptar</string>
<string name="common_access_denied">Acceso denegado</string>
<string name="common_add_to_portfolio">Añadir al portafolio</string>
<string name="common_add_token">Agregar token</string>
<string name="common_all">Todos</string>
<string name="common_allow">Autorizar</string>
<string name="common_analytics">Analítica</string>
@ -140,6 +142,7 @@
<string name="common_generate_addresses">Sincronizar direcciones</string>
<string name="common_go_to_provider">Ir al proveedor</string>
<string name="common_go_to_token">Ir al token</string>
<string name="common_hide">Ocultar</string>
<string name="common_hour">hora</string>
<string name="common_import">Importe</string>
<string name="common_in_progress">En progreso</string>
@ -311,6 +314,9 @@
<string name="express_status_bought">Comprado %s</string>
<string name="express_status_buying">Comprando %s</string>
<string name="express_status_buying_active">Comprando %s...</string>
<string name="express_status_hide_button_text">Ocultar esta transacción</string>
<string name="express_status_hide_dialog_text">Una vez oculto, el estado de la transacción no se puede volver a ver. En su lugar, basta con deslizar el dedo para descartarla.</string>
<string name="express_status_hide_dialog_title">¿Ocultar el estado de la transacción?</string>
<string name="express_token_list_empty_search">No se encontraron fichas. Por favor intenta con otra solicitud</string>
<string name="express_transaction_id">ID : %s</string>
<string name="express_transaction_id_copied">ID de transacción copiado</string>
@ -337,6 +343,8 @@
<string name="give_permission_unlimited">Ilimitado</string>
<string name="home_button_order">Pedir Tangem</string>
<string name="home_button_scan">Escanee</string>
<string name="hot_crypto_add_token_subtitle">a %s</string>
<string name="hot_crypto_token_network">En la red %s</string>
<string name="information_generated_with_ai">Esta información fue generada con IA.\nPulse aquí si encuentra algún error.</string>
<string name="initial_message_change_access_code_body">Para cambiar el código de acceso coloque la tarjeta o el anillo como se muestra arriba y no lo retire hasta el fin de la operación</string>
<string name="initial_message_change_passcode_body">Toque para cambiar la contraseña</string>
@ -778,12 +786,16 @@
<string name="staking_details_unbonding_period_info">El período que debes esperar después de solicitar el retiro de fondos del staking antes de que los tokens estén disponibles.</string>
<string name="staking_details_warmup_period">Periodo de calentamiento</string>
<string name="staking_details_warmup_period_info">El tiempo permitido para activar la participación en la apuesta.</string>
<string name="staking_error_no_validators_message">No hay validadores disponibles en este momento. Por favor, inténtelo de nuevo más tarde.</string>
<string name="staking_error_no_validators_title">Staking no disponible</string>
<string name="staking_give_permission_fee_footer">La red cobrará una tarifa de aprobación de token para verificar que usted está autorizando el uso de su token para el staking.</string>
<string name="staking_legal">Al utilizar la función de staking, usted acepta %1$s y %2$s del proveedor</string>
<string name="staking_locked">Bloqueado</string>
<string name="staking_migrate">Migrar</string>
<string name="staking_native">Native staking</string>
<string name="staking_no_validators_error_message">No hay validadores activos disponibles para hacer staking en este momento. Por favor, inténtalo de nuevo más tarde.</string>
<string name="staking_notification_additional_ada_deposit_text">Al hacer staking en la red Cardano, se utiliza todo su saldo. Se reservarán 2 ADA adicionales y se devolverán después del staking. Su ADA permanece desbloqueado mientras hace staking.</string>
<string name="staking_notification_additional_ada_deposit_title">Detalles del staking de ADA</string>
<string name="staking_notification_claim_rewards_text">Recomp. won se le enviará y estará disponible para su uso inmediato</string>
<string name="staking_notification_earn_rewards_text">Haga staking de forma segura y comience a ganar recompensas</string>
<string name="staking_notification_earn_rewards_text_daily">Haga de forma segura y comience a ganar recompensas diarias</string>
@ -797,6 +809,8 @@
<string name="staking_notification_earn_rewards_title">Gane recompensas por staking</string>
<string name="staking_notification_low_staked_balance_text">Su saldo de staking será demasiado bajo para cancelar el staking. Necesitará hacer staking más para alcanzar la cantidad mínima para cancelarlo.</string>
<string name="staking_notification_low_staked_balance_title">Saldo de staking bajo</string>
<string name="staking_notification_minimum_balance_error_text">Un mínimo de %1$s %2$s es necesario para añadir más staking. Por favor recargue su saldo.</string>
<string name="staking_notification_minimum_balance_error_title">No tiene suficiente %s</string>
<string name="staking_notification_network_error_text">El staking no está disponible actualmente debido a las condiciones de la red. Inténtelo de nuevo más tarde.</string>
<string name="staking_notification_new_validator_funds_transfer">El staking en la red %1$s con un nuevo validador transferirá automáticamente todos los fondos previamente en staking a este validador.</string>
<string name="staking_notification_restake_rewards_text">Reinvierta las recompensas obtenidas en el monto apostado, aumentando las ganancias potenciales.</string>
@ -867,6 +881,16 @@
<string name="swap_give_permission_fee_footer">La red cobrará una tarifa de aprobación de token para verificar que está autorizando el uso de su token para el swap.</string>
<string name="swap_promo_text">Intercambie más tokens a mejores tasas directamente en su billetera.</string>
<string name="swap_promo_title">¡Nuevo proveedor de intercambio disponible!</string>
<string name="swap_story_fifth_subtitle">Siéntase seguro con un soporte constante, lo que garantiza que sus transacciones se realicen sin problemas en cualquier momento</string>
<string name="swap_story_fifth_title">Asistencia 24 horas al día</string>
<string name="swap_story_first_subtitle">Los proveedores de intercambio de confianza le permiten intercambiar activos sin esfuerzo, manteniendo todo seguro en su billetera</string>
<string name="swap_story_first_title">Intercambie con nosotros</string>
<string name="swap_story_forth_subtitle">La seguridad de alto nivel y los proveedores verificados garantizan la protección de sus activos en cada intercambio</string>
<string name="swap_story_forth_title">Más seguro que nunca</string>
<string name="swap_story_second_subtitle">Maximice su valor con tarifas de una amplia red de proveedores de confianza, eligiendo siempre la mejor</string>
<string name="swap_story_second_title">Las mejores tarifas</string>
<string name="swap_story_third_subtitle">Sencillo e intuitivo, permite cambiar tokens con solo unos cuantos toques</string>
<string name="swap_story_third_title">Más fácil que nunca</string>
<string name="swapping_alert_cex_description">La cantidad incluye:\n• Tarifas del proveedor de servicios\n• Tarifas de red por enviar %s desde el intercambio a la dirección del usuario.</string>
<string name="swapping_alert_cex_description_with_slippage">El monto incluye:\n• tarifas del proveedor de servicios\n• tarifas de red por enviar %1$s desde el intercambio a la dirección del usuario. \n\nEl slippage del proveedor puede alcanzar el %2$s</string>
<string name="swapping_alert_dex_description">El importe incluye los honorarios del proveedor de servicios.</string>
@ -1043,6 +1067,7 @@
<string name="warning_kaspa_unfinished_token_transaction_discard_message">¿Está seguro(a) de que desea cancelar la transacción? No podrá volver a intentar realizarla.</string>
<string name="warning_kaspa_unfinished_token_transaction_message">Su transacción con un importe de %1$s %2$s no se ha completado. Puede volver a intentarlo para completarla.</string>
<string name="warning_kaspa_unfinished_token_transaction_title">Tiene una transacción sin terminar</string>
<string name="warning_last_balance_updated_time">Última actualización hace %s</string>
<string name="warning_low_signatures_message">Solo quedan %s firmas en esta tarjeta. Deba retirar todos sus fondos.</string>
<string name="warning_low_signatures_title">Recuento de firmas bajo</string>
<string name="warning_manage_tokens_legacy_derivation_message">Los tokens en diferentes redes pueden tener direcciones diferentes. Verifique que su dirección coincida con la red cuando transfieras fondos.</string>
@ -1075,6 +1100,7 @@
<string name="warning_solana_rent_fee_message">La red Solana cobra un alquiler de %1$s cada %2$s días. Las cuentas que no pueden pagar el alquiler son eliminadas de la red. Deposite en su cuenta más de 2 para usarla de forma gratuita.</string>
<string name="warning_some_networks_unreachable_message">Deslice hacia abajo para refrescar o inténtelo de nuevo más tarde.</string>
<string name="warning_some_networks_unreachable_title">Algunas redes no están disponibles</string>
<string name="warning_some_token_balances_not_updated">No se han podido actualizar los saldos de algunos tokens</string>
<string name="warning_testnet_card_message">Esta es una tarjeta de Testnet. No puede procesar transacciones y solo deba usarse para pruebas y desarrollo.</string>
<string name="warning_testnet_card_title">Solo para fines de prueba</string>
<string name="welcome_interrupted_backup_alert_discard">Ignorar</string>

View file

@ -786,6 +786,8 @@
<string name="staking_details_unbonding_period_info">La période que vous devez attendre après avoir demandé le retrait des fonds du staking avant que les jetons ne soient disponibles.</string>
<string name="staking_details_warmup_period">Période d\'échauffement</string>
<string name="staking_details_warmup_period_info">Le temps imparti pour activer la participation au staking.</string>
<string name="staking_error_no_validators_message">Aucun validateur disponible pour le moment. Veuillez réessayer plus tard.</string>
<string name="staking_error_no_validators_title">Staking indisponible</string>
<string name="staking_give_permission_fee_footer">Le réseau facturera des frais dapprobation de jeton pour vérifier que vous autorisez lutilisation de votre jeton pour le jalonnement.</string>
<string name="staking_legal">En utilisant la fonctionnalité de staking, vous acceptez les %1$s et %2$s du fournisseur</string>
<string name="staking_locked">Bloqué</string>
@ -879,6 +881,16 @@
<string name="swap_give_permission_fee_footer">Le réseau facturera des frais d\'approbation de jeton pour vérifier que vous autorisez l\'utilisation de votre jeton pour l\'échange.</string>
<string name="swap_promo_text">Échangez plus de jetons à de meilleurs taux directement dans votre portefeuille.</string>
<string name="swap_promo_title">Nouveau fournisseur d\'échange disponible !</string>
<string name="swap_story_fifth_subtitle">Ayez confiance en bénéficiant d\'un support constant, garantissant que vos transactions se déroulent sans problème à tout moment</string>
<string name="swap_story_fifth_title">Assistance 24 heures sur 24</string>
<string name="swap_story_first_subtitle">Les fournisseurs d\'échange de confiance vous permettent d\'échanger des actifs sans effort, en gardant tout en sécurité dans votre portefeuille</string>
<string name="swap_story_first_title">Échangez Avec Nous</string>
<string name="swap_story_forth_subtitle">Une sécurité de haut niveau et des fournisseurs vérifiés garantissant que vos actifs sont protégés à chaque échange</string>
<string name="swap_story_forth_title">Plus Sûr Que Jamais</string>
<string name="swap_story_second_subtitle">Maximisez votre valeur avec des tarifs provenant d\'un large réseau de fournisseurs de confiance, en choisissant toujours le meilleur</string>
<string name="swap_story_second_title">Meilleurs Tarifs</string>
<string name="swap_story_third_subtitle">Simple et intuitif, vous permettant d\'échanger des jetons en quelques clics</string>
<string name="swap_story_third_title">Plus Simple Que Jamais</string>
<string name="swapping_alert_cex_description">Le montant comprend :\n• les frais du fournisseur de services\n• les frais de réseau pour l\'envoi de %s depuis l\'échange vers l\'adresse de l\'utilisateur.</string>
<string name="swapping_alert_cex_description_with_slippage">Le montant comprend :\n• les frais du fournisseur de services\n• les frais de réseau pour l\'envoi de %1$s depuis l\'échange vers l\'adresse de l\'utilisateur. \n\nLe slippage du fournisseur peut atteindre %2$s</string>
<string name="swapping_alert_dex_description">Le montant comprend les frais du fournisseur de services.</string>

View file

@ -89,6 +89,8 @@
<string name="cardano_max_amount_has_token_title">Недостаточно ADA</string>
<string name="common_accept">Принять</string>
<string name="common_access_denied">Доступ запрещен</string>
<string name="common_add_to_portfolio">Добавить в портфель</string>
<string name="common_add_token">Добавить токен</string>
<string name="common_all">Все</string>
<string name="common_allow">Разрешить</string>
<string name="common_analytics">Аналитика</string>
@ -146,6 +148,7 @@
<string name="common_generate_addresses">Синхронизировать адреса</string>
<string name="common_go_to_provider">К провайдеру</string>
<string name="common_go_to_token">Перейти в токен</string>
<string name="common_hide">Скрыть</string>
<string name="common_hour">час</string>
<string name="common_import">Импортировать</string>
<string name="common_in_progress">В процессе</string>
@ -317,6 +320,9 @@
<string name="express_status_bought">Куплено %s</string>
<string name="express_status_buying">Покупка %s</string>
<string name="express_status_buying_active">Покупка %s...</string>
<string name="express_status_hide_button_text">Скрыть эту транзакцию</string>
<string name="express_status_hide_dialog_text">После скрытия статус транзакции нельзя будет просмотреть снова. Вместо этого вы можете просто свайпнуть плашку вниз, чтобы закрыть её.</string>
<string name="express_status_hide_dialog_title">Скрыть статус транзакции?</string>
<string name="express_token_list_empty_search">Токены не найдены. Пожалуйста, попробуйте другой запрос</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">ID транзакции скопирован</string>
@ -343,6 +349,7 @@
<string name="give_permission_unlimited">Безлимитно</string>
<string name="home_button_order">Купить</string>
<string name="home_button_scan">Сканировать</string>
<string name="hot_crypto_add_token_subtitle">в %s</string>
<string name="hot_crypto_token_network">В сети %s</string>
<string name="information_generated_with_ai">Эта информация была сгенерирована ИИ.\nНажмите здесь, если обнаружили ошибку.</string>
<string name="initial_message_change_access_code_body">Чтобы изменить код доступа, приложите карту или кольцо как показано выше и не убирайте до окончания операции</string>
@ -374,6 +381,7 @@
<string name="manage_tokens_list_header_title">Рыночная капитализация</string>
<string name="manage_tokens_network_selector_native_subtitle">Блокчейн, в котором криптовалюта изначально была создана</string>
<string name="manage_tokens_network_selector_native_title">Основная сеть</string>
<string name="manage_tokens_network_selector_non_native_info">Использование ненативных сетей для токенов обеспечивает кросс-блокчейн взаимодействие, позволяя использовать активы в различных децентрализованных приложениях и смарт-контрактах на разных платформах. Однако для безопасного хранения исходного актива часто требуется хранитель или смарт-контракт, что приводит к централизации и риску контрагента.</string>
<string name="manage_tokens_network_selector_non_native_subtitle">Не основной или основной блокчейн, на котором размещен токен</string>
<string name="manage_tokens_network_selector_non_native_title">Не основные сети</string>
<string name="manage_tokens_network_selector_title">Выберите сети</string>
@ -484,6 +492,7 @@
<string name="markets_token_details_price_performance">Динамика цены</string>
<string name="markets_token_details_repository">Репозиторий</string>
<string name="markets_token_details_security_score">Оценка безопасности</string>
<string name="markets_token_details_security_score_description">Показатель безопасности токена — это метрика, которая оценивает уровень безопасности блокчейна или токена на основе различных факторов и составляется из перечисленных ниже источников.</string>
<string name="markets_token_details_social">Социальные</string>
<string name="markets_token_details_total_supply">Общ. предл.</string>
<string name="markets_token_details_total_supply_description">Максимальное количество монет или токенов, которое может когда-либо существовать для выбранной криптовалюты</string>
@ -797,12 +806,16 @@
<string name="staking_details_unbonding_period_info">Период, который необходимо подождать после запроса на вывод средств из стейкинга, прежде чем токены станут доступны.</string>
<string name="staking_details_warmup_period">Период прогрева</string>
<string name="staking_details_warmup_period_info">Время, необходимое для начала процесса стейкинга и активации процесса начисления наград</string>
<string name="staking_error_no_validators_message">В данный момент нет доступных валидаторов. Попробуйте позже.</string>
<string name="staking_error_no_validators_title">Стейкинг недоступен</string>
<string name="staking_give_permission_fee_footer">Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для стейкинга.</string>
<string name="staking_legal">Пользуясь стейкинг сервисом, вы соглашаетесь с %1$s и %2$s</string>
<string name="staking_locked">Заблокировано</string>
<string name="staking_migrate">Переместить</string>
<string name="staking_native">Нативный стейкинг</string>
<string name="staking_no_validators_error_message">В данный момент нет доступных активных валидаторов для стейкинга. Пожалуйста, попробуйте позже.</string>
<string name="staking_notification_additional_ada_deposit_text">При стейкинге в сети Cardano используется весь ваш баланс. Дополнительные 2 ADA будут зарезервированы и возвращены после отмены стейкинга. Ваши ADA остаются разблокированными во время стейкинга.</string>
<string name="staking_notification_additional_ada_deposit_title">Подробности стейкинга ADA</string>
<string name="staking_notification_claim_rewards_text">Полученная награда отправится на ваш кошелек и сразу станет доступна для использования</string>
<string name="staking_notification_earn_rewards_text">Стейкайте безопасно и начинайте получать ваши награды.</string>
<string name="staking_notification_earn_rewards_text_daily">Стейкайте безопасно и начинайте получать ежедневные награды.</string>
@ -816,6 +829,8 @@
<string name="staking_notification_earn_rewards_title">Получите награду за стейкинг</string>
<string name="staking_notification_low_staked_balance_text">Оставшийся застейканный баланс будет слишком мал для вывода. Вам потребуется застейкать больше средств, чтобы достичь минимальной суммы для вывода.</string>
<string name="staking_notification_low_staked_balance_title">Низкий баланс стейкинга</string>
<string name="staking_notification_minimum_balance_error_text">Для рестейкинга необходимо %1$s%2$s. Пожалуйста, пополните баланс.</string>
<string name="staking_notification_minimum_balance_error_title">Недостаточно %s</string>
<string name="staking_notification_network_error_text">Стейкинг временно недоступен из-за проблем в сети. Пожалуйста, попробуйте позже.</string>
<string name="staking_notification_new_validator_funds_transfer">Стейкинг в сети %1$s с новым валидатором автоматически переведет ваши текущие застейканные средства на него.</string>
<string name="staking_notification_restake_rewards_text">Реинвестируйте свои заработанные награды в вашу застейканную сумму, увеличивая потенциальный доход</string>
@ -885,16 +900,16 @@
<string name="swap_give_permission_fee_footer">Комиссия сети за одобрение токена будет взиматься за подтверждение того, что именно вы разрешаете использовать ваш токен для обмена.</string>
<string name="swap_promo_text">Обменивайте больше токенов по лучшим курсам прямо в вашем кошельке.</string>
<string name="swap_promo_title">Новый провайдер обмена!</string>
<string name="swap_story_fifth_subtitle">Надёжная поддержка 24/7, чтобы ваши финансовые операции были быстрыми и безопасными!</string>
<string name="swap_story_fifth_subtitle">Чувствуйте уверенность с круглосуточной поддержкой, готовой помочь в любой ситуации!</string>
<string name="swap_story_fifth_title">Круглосуточная поддержка</string>
<string name="swap_story_first_subtitle">Надёжные провайдеры позволяют легко обменивать активы, обеспечивая полную безопасность в вашем кошельке</string>
<string name="swap_story_first_subtitle">Надежные провайдеры в одном месте — обменивайте любые активы легко и быстро прямо в своем кошельке!</string>
<string name="swap_story_first_title">Обменивайте с нами</string>
<string name="swap_story_forth_subtitle">Высший уровень безопасности и проверенные поставщики гарантируют защиту ваших активов при каждой сделке</string>
<string name="swap_story_forth_title">Безопаснее, чем когда-либо</string>
<string name="swap_story_second_subtitle">Получайте максимальную выгоду с лучшими курсами от надёжных поставщиков</string>
<string name="swap_story_forth_subtitle">Высший уровень безопасности и проверенные партнёры защищают каждую транзакцию</string>
<string name="swap_story_forth_title">Полная защита</string>
<string name="swap_story_second_subtitle">Автоматически находим самые выгодные предложения среди проверенных провайдеров</string>
<string name="swap_story_second_title">Лучшие курсы</string>
<string name="swap_story_third_subtitle">Просто и удобно — обмен токенов всего в несколько касаний</string>
<string name="swap_story_third_title">Проще, чем когда-либо</string>
<string name="swap_story_third_subtitle">Интуитивный обмен в пару касаний — без сложностей и ожидания</string>
<string name="swap_story_third_title">Проще простого</string>
<string name="swapping_alert_cex_description">В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя.</string>
<string name="swapping_alert_cex_description_with_slippage">В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %1$s от биржи обратно на адрес пользователя \n\nПроскальзывание провайдера составляет до %2$s</string>
<string name="swapping_alert_dex_description">В сумму включена комиссия провайдера сервиса.</string>
@ -1071,6 +1086,7 @@
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Вы уверены что хотите отменить эту транзакцию? Вы не сможете отправить ее еще раз</string>
<string name="warning_kaspa_unfinished_token_transaction_message">Ваша транзакция %1$s %2$s не была завершена. Вы можете попробовать отправить ее еще раз.</string>
<string name="warning_kaspa_unfinished_token_transaction_title">У вас есть незавершенная транзакция</string>
<string name="warning_last_balance_updated_time">Последнее обновление %s</string>
<string name="warning_low_signatures_message">На этой карте осталось всего %s подписей. Вам следует вывести все ваши средства.</string>
<string name="warning_low_signatures_title">Малое количество подписей</string>
<string name="warning_manage_tokens_legacy_derivation_message">Токены на разных сетях могут иметь разные адреса. Пожалуйста, убедитесь при переводе средств, что ваш адрес соответствует сети.</string>
@ -1105,6 +1121,7 @@
<string name="warning_solana_rent_fee_message">Сеть Solana взимает арендную плату в размере %1$s каждые 2 дня. Аккаунты, которые не могут позволить себе арендную плату, удаляются из сети. Пополните свой счет более чем на %2$s, чтобы не платить арендную плату.</string>
<string name="warning_some_networks_unreachable_message">Свайпните вниз для обновления или попробуйте позже.</string>
<string name="warning_some_networks_unreachable_title">Некоторые сети недоступны</string>
<string name="warning_some_token_balances_not_updated">Некоторые балансы токенов не удалось обновить</string>
<string name="warning_testnet_card_message">Это Testnet карта. Он не может обрабатывать транзакции и используется только в целях тестирования и разработки.</string>
<string name="warning_testnet_card_title">Только для целей тестирования</string>
<string name="welcome_interrupted_backup_alert_discard">Отказаться</string>

View file

@ -89,6 +89,8 @@
<string name="cardano_max_amount_has_token_title">Недостатньо ADA</string>
<string name="common_accept">Прийняти</string>
<string name="common_access_denied">Доступ заборонено</string>
<string name="common_add_to_portfolio">Додати у портфель</string>
<string name="common_add_token">Додати токен</string>
<string name="common_all">Усе</string>
<string name="common_allow">Дозволити</string>
<string name="common_analytics">Аналітика</string>
@ -146,6 +148,7 @@
<string name="common_generate_addresses">Синхронізувати адреси</string>
<string name="common_go_to_provider">Перейти до провайдера</string>
<string name="common_go_to_token">Перейти до токену</string>
<string name="common_hide">Приховати</string>
<string name="common_hour">година</string>
<string name="common_import">Імпортувати</string>
<string name="common_in_progress">В процесі</string>
@ -317,6 +320,9 @@
<string name="express_status_bought">Купив %s</string>
<string name="express_status_buying">Купівля %s</string>
<string name="express_status_buying_active">Купівля %s...</string>
<string name="express_status_hide_button_text">Приховати цю транзакцію</string>
<string name="express_status_hide_dialog_text">Після приховування статус транзакції більше не можна буде переглянути. Замість цього ви можете просто провести пальцем, щоб закрити.</string>
<string name="express_status_hide_dialog_title">Приховати статус транзакції?</string>
<string name="express_token_list_empty_search">Токенів не знайдено. Будь ласка, спробуйте інший запит</string>
<string name="express_transaction_id">ID: %s</string>
<string name="express_transaction_id_copied">ID транзакції скопійовано</string>
@ -343,6 +349,8 @@
<string name="give_permission_unlimited">Необмежено</string>
<string name="home_button_order">Купити</string>
<string name="home_button_scan">Сканувати</string>
<string name="hot_crypto_add_token_subtitle">в %s</string>
<string name="hot_crypto_token_network">В мережі %s</string>
<string name="information_generated_with_ai">Ця інформація була створена за допомогою ШІ.\nНатисніть тут, якщо знайшли помилку.</string>
<string name="initial_message_change_access_code_body">Щоб змінити код доступу, прикладіть картку або кільце, як показано вище, і не прибирайте її до закінчення операції</string>
<string name="initial_message_change_passcode_body">Щоб змінити пароль, прикладіть картку, як показано вище, і не прибирайте її до закінчення операції</string>
@ -798,12 +806,16 @@
<string name="staking_details_unbonding_period_info">Період, який ви повинні чекати після запиту на виведення коштів зі стейкінгу, перш ніж токени стануть доступними.</string>
<string name="staking_details_warmup_period">Період блокування</string>
<string name="staking_details_warmup_period_info">Відведений час для активації участі в стейкінгу.</string>
<string name="staking_error_no_validators_message">Наразі немає вільних валідаторів. Повторіть спробу пізніше.</string>
<string name="staking_error_no_validators_title">Стейкінг недоступний</string>
<string name="staking_give_permission_fee_footer">Комісія за схвалення токену, щоб верифікувати, що саме ви дозволяєте використання токена для стейкінгу.</string>
<string name="staking_legal">Використовуючи функцію стейкінгу, ви погоджуєтесь з %1$s та %2$s</string>
<string name="staking_locked">Заблоковано</string>
<string name="staking_migrate">Перемістити</string>
<string name="staking_native">Нативний стейкінг</string>
<string name="staking_no_validators_error_message">На даний момент немає доступних активних валідаторів для стейкінгу. Будь ласка, спробуйте пізніше.</string>
<string name="staking_notification_additional_ada_deposit_text">Під час стейкінгу в мережі Cardano використовується весь ваш баланс. Додаткові 2 ADA будуть зарезервовані та повернуті після скасування стейкінгу. Ваша ADA залишається розблокованою під час стейкінгу.</string>
<string name="staking_notification_additional_ada_deposit_title">Деталі стейкінгу ADA</string>
<string name="staking_notification_claim_rewards_text">Зароблені винагороди будуть надіслані на ваш гаманець і доступні для використання відразу</string>
<string name="staking_notification_earn_rewards_text">Стейкайте безпечно та почніть отримувати ваші винагороди</string>
<string name="staking_notification_earn_rewards_text_daily">Стейкайте безпечно та почніть отримувати винагороди щоденно</string>
@ -817,6 +829,8 @@
<string name="staking_notification_earn_rewards_title">Отримуйте винагороду за стейкінг</string>
<string name="staking_notification_low_staked_balance_text">Ваш баланс стейкінгу, що залишився, буде занадто низьким, щоб вивести його зі стейкінгу. Вам потрібно буде застейкати більше, щоб досягти мінімальної суми для виводу.</string>
<string name="staking_notification_low_staked_balance_title">Низький баланс стейкінгу</string>
<string name="staking_notification_minimum_balance_error_text">Мінімум %1$s %2$s потрібно для рестейкінгу. Будь ласка, поповніть баланс.</string>
<string name="staking_notification_minimum_balance_error_title">Недостатньо %s</string>
<string name="staking_notification_network_error_text">Стейкінг тимчасово недоступний через проблеми в мережі. Будь ласка, спробуйте пізніше.</string>
<string name="staking_notification_new_validator_funds_transfer">Стейкінг в мережі %1$s з новим валідатором автоматично переведе всі раніше застейкані кошти до цього валідатора</string>
<string name="staking_notification_restake_rewards_text">Реінвестуйте зароблені винагороди у суму стейкінгу, щоб збільшити потенційний прибуток.</string>
@ -887,6 +901,16 @@
<string name="swap_give_permission_fee_footer">Мережа стягує комісію за схвалення токену за підтвердження, що саме ви дозволяєте використовувати ваш токен для обміну.</string>
<string name="swap_promo_text">Обмінюйте більше токенів за вигіднішим курсом прямо у своєму гаманці.</string>
<string name="swap_promo_title">З\'явився новий провайдер обмінів!</string>
<string name="swap_story_fifth_subtitle">Надійна підтримка 24/7, щоб ваші фінансові операції були швидкими та надійними!</string>
<string name="swap_story_fifth_title">Цілодобова підтримка</string>
<string name="swap_story_first_subtitle">Надійні провайдери дозволяють легко обмінювати активи, забезпечуючи повну безпеку у вашому гаманці</string>
<string name="swap_story_first_title">Обмінюйте з нами</string>
<string name="swap_story_forth_subtitle">Найвищий рівень безпеки та перевірені провайдери гарантують захист ваших активів при кожному обміні.</string>
<string name="swap_story_forth_title">Безпечніше, ніж будь-коли</string>
<string name="swap_story_second_subtitle">Отримуйте максимальну вигоду з кращими курсами від надійних провайдерів</string>
<string name="swap_story_second_title">Найкращі тарифи</string>
<string name="swap_story_third_subtitle">Просто та зручно — обмін токенів в декілька дотиків</string>
<string name="swap_story_third_title">Легше, ніж будь-коли</string>
<string name="swapping_alert_cex_description">Сума включає: \n• комісію постачальника послуг\n• комісію мережі за відправлення %s з біржі назад на адресу користувача.</string>
<string name="swapping_alert_cex_description_with_slippage">У суму входить:\n- комісія провайдера\n- мережева комісія за відправку %1$s з біржі назад на адресу користувача. \n\nПроскакування провайдера становить до %2$s</string>
<string name="swapping_alert_dex_description">Сума включає комісію постачальника послуг.</string>
@ -1063,6 +1087,7 @@
<string name="warning_kaspa_unfinished_token_transaction_discard_message">Ви впевнені, що хочете скасувати цю транзакцію? Ви не зможете відправити її ще раз</string>
<string name="warning_kaspa_unfinished_token_transaction_message">Ваша транзакція %1$s %2$s не була завершена. Ви можете спробувати відправити її ще раз.</string>
<string name="warning_kaspa_unfinished_token_transaction_title">У вас є незавершена транзакція</string>
<string name="warning_last_balance_updated_time">Останнє оновлення було %s</string>
<string name="warning_low_signatures_message">На цій картці залишається лише %s підписів. Ви повинні вивести всі свої кошти.</string>
<string name="warning_low_signatures_title">Низька кількість підписів</string>
<string name="warning_manage_tokens_legacy_derivation_message">Токени в різних мережах можуть мати різні адреси. Переконайтеся, що ваша адреса відповідає мережі, коли переказуєте кошти.</string>
@ -1097,6 +1122,7 @@
<string name="warning_solana_rent_fee_message">Мережа Solana стягує орендну плату у розмірі %1$s кожні 2 дні. Акаунти, які не можуть дозволити собі орендну плату, видаляються з мережі. Поповніть свій рахунок на суму понад %2$s, щоб не платити орендну плату.</string>
<string name="warning_some_networks_unreachable_message">Проведіть пальцем вниз для оновлення або спробуйте пізніше.</string>
<string name="warning_some_networks_unreachable_title">Деякі мережі недоступні</string>
<string name="warning_some_token_balances_not_updated">Деякі баланси токенів не вдалося оновити</string>
<string name="warning_testnet_card_message">Це картка Testnet. Вона не може обробляти транзакції і повинна використовуватися лише для тестування та розробки.</string>
<string name="warning_testnet_card_title">Лише для цілей тестування</string>
<string name="welcome_interrupted_backup_alert_discard">Відмовитися</string>

View file

@ -881,16 +881,16 @@
<string name="swap_give_permission_fee_footer">The network will charge a token approval fee to verify that you are authorizing the use of your token for the swap.</string>
<string name="swap_promo_text">Exchange more tokens at better rates directly in your wallet.</string>
<string name="swap_promo_title">New Swap Provider Available!</string>
<string name="swap_story_fifth_subtitle">Feel confident with constant support, ensuring your transactions go smoothly anytime</string>
<string name="swap_story_fifth_title">Around-the-Clock Assistance</string>
<string name="swap_story_first_subtitle">Trusted exchange providers let you swap assets effortlessly, keeping everything safe in your wallet</string>
<string name="swap_story_first_title">Exchange With Us</string>
<string name="swap_story_forth_subtitle">Top-level security and verified providers ensure your assets are protected during every swap</string>
<string name="swap_story_forth_title">Safer Then Ever</string>
<string name="swap_story_fifth_subtitle">Feel confident with round-the-clock support to help with any issues</string>
<string name="swap_story_fifth_title">Always Here</string>
<string name="swap_story_first_subtitle">Multiple trusted providers in one place—swap any asset effortlessly in your wallet</string>
<string name="swap_story_first_title">Swap With Us</string>
<string name="swap_story_forth_subtitle">No fumbles, no turnovers, no blind spots—your transaction is always protected</string>
<string name="swap_story_forth_title">Impenetrable Defense</string>
<string name="swap_story_second_subtitle">Maximize your value with rates sourced from a wide network of trusted providers, always choosing the best one</string>
<string name="swap_story_second_title">Best Rates</string>
<string name="swap_story_second_title">Unbeatable Rates</string>
<string name="swap_story_third_subtitle">Hassle-free and intuitive, allowing you to swap tokens in just a few taps</string>
<string name="swap_story_third_title">Easier Than Ever</string>
<string name="swap_story_third_title">Simply Convenient</string>
<string name="swapping_alert_cex_description">The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address.</string>
<string name="swapping_alert_cex_description_with_slippage">The amount includes:\n• service provider\'s fee\n• network fee for sending %1$s from the exchange back to the user\'s address. \n\nProvider slippage is up to %2$s</string>
<string name="swapping_alert_dex_description">The amount includes the service provider\'s fee.</string>

View file

@ -7,7 +7,6 @@ object TangemColorPalette {
// region Base
val Black = Color(0xFF000000)
val White = Color(0xFFFFFFFF)
val SoftWhite = Color(0xFFF1F1F1)
// endregion Base
// region Dark

View file

@ -14,6 +14,8 @@ dependencies {
implementation(deps.androidx.datastore)
implementation(deps.jodatime)
implementation(deps.timber)
implementation(deps.hilt.android)
kapt(deps.hilt.kapt)

View file

@ -4,18 +4,19 @@ import com.tangem.data.promo.converters.StoryContentResponseConverter
import com.tangem.datasource.api.common.response.getOrThrow
import com.tangem.datasource.api.tangemTech.TangemTechApi
import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.local.preferences.PreferencesKeys.SHOULD_SHOW_SWAP_STORIES_KEY
import com.tangem.datasource.local.preferences.PreferencesKeys.getShouldShowStoriesKey
import com.tangem.datasource.local.preferences.utils.get
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
import com.tangem.datasource.local.preferences.utils.store
import com.tangem.datasource.local.promo.PromoStoriesStore
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.promo.models.StoryContent
import com.tangem.utils.SupportedLanguages
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.mapLatest
import kotlinx.coroutines.withContext
import kotlinx.coroutines.withTimeoutOrNull
internal class DefaultPromoRepository(
private val tangemApi: TangemTechApi,
@ -42,35 +43,49 @@ internal class DefaultPromoRepository(
// Use it on new promo action
}
override suspend fun getStoryById(id: String): StoryContent = withContext(dispatchers.io) {
override fun getStoryById(id: String): Flow<StoryContent?> = isReadyToShowStories(id).mapLatest {
getStoryByIdSync(id)
}
override suspend fun getStoryByIdSync(id: String): StoryContent? = withContext(dispatchers.io) {
if (!isReadyToShowStoriesSync(id)) return@withContext null
val storedPromo = promoStoriesStore.getSyncOrNull(storyId = id)
// Get last stored promo by id if possible or get from network
val story = if (storedPromo == null) {
val storyContent = tangemApi.getStoryById(
storyId = id,
language = SupportedLanguages.getCurrentSupportedLanguageCode(),
).getOrThrow()
promoStoriesStore.store(storyId = id, item = storyContent)
val storyContent = runCatching {
// Important to return
withTimeoutOrNull(STORIES_LOAD_DELAY) {
tangemApi.getStoryById(storyId = id).getOrThrow()
}
}.getOrNull()
if (storyContent != null) {
promoStoriesStore.store(id, storyContent)
}
storyContent
} else {
storedPromo
}
storyContentConverter.convert(story)
story?.let { storyContentConverter.convert(it) }
}
override fun isReadyToShowSwapStories(): Flow<Boolean> {
return appPreferencesStore.get(SHOULD_SHOW_SWAP_STORIES_KEY, true)
override fun isReadyToShowStories(storyId: String): Flow<Boolean> {
return appPreferencesStore.get(getShouldShowStoriesKey(storyId), true)
}
override suspend fun isReadyToShowSwapStoriesSync(): Boolean {
return appPreferencesStore.getSyncOrDefault(SHOULD_SHOW_SWAP_STORIES_KEY, true)
override suspend fun isReadyToShowStoriesSync(storyId: String): Boolean {
return appPreferencesStore.getSyncOrDefault(getShouldShowStoriesKey(storyId), true)
}
override suspend fun setNeverToShowSwapStories() {
override suspend fun setNeverToShowStories(storyId: String) {
appPreferencesStore.store(
key = SHOULD_SHOW_SWAP_STORIES_KEY,
key = getShouldShowStoriesKey(storyId),
value = false,
)
}
private companion object {
const val STORIES_LOAD_DELAY = 1000L
}
}

View file

@ -1,13 +1,24 @@
package com.tangem.domain.promo
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import com.tangem.domain.promo.models.StoryContent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEmpty
class GetStoryContentUseCase(
private val promoRepository: PromoRepository,
) {
suspend operator fun invoke(id: String): Either<Throwable, StoryContent> = Either.catch {
promoRepository.getStoryById(id)
operator fun invoke(id: String): Flow<Either<Throwable, StoryContent?>> = promoRepository.getStoryById(id)
.map<StoryContent?, Either<Throwable, StoryContent?>> { it.right() }
.catch { emit(it.left()) }
.onEmpty { emit(null.right()) }
suspend fun invokeSync(id: String): Either<Throwable, StoryContent?> = Either.catch {
promoRepository.getStoryByIdSync(id)
}
}

View file

@ -16,12 +16,14 @@ interface PromoRepository {
// endregion
// region Stories
suspend fun getStoryById(id: String): StoryContent
fun getStoryById(id: String): Flow<StoryContent?>
fun isReadyToShowSwapStories(): Flow<Boolean>
suspend fun getStoryByIdSync(id: String): StoryContent?
suspend fun isReadyToShowSwapStoriesSync(): Boolean
fun isReadyToShowStories(storyId: String): Flow<Boolean>
suspend fun setNeverToShowSwapStories()
suspend fun isReadyToShowStoriesSync(storyId: String): Boolean
suspend fun setNeverToShowStories(storyId: String)
// endregion
}

View file

@ -0,0 +1,10 @@
package com.tangem.domain.promo
import kotlinx.coroutines.flow.Flow
class ShouldShowStoriesUseCase(private val promoRepository: PromoRepository) {
operator fun invoke(storyId: String): Flow<Boolean> = promoRepository.isReadyToShowStories(storyId)
suspend fun invokeSync(storyId: String): Boolean = promoRepository.isReadyToShowStoriesSync(storyId)
suspend fun neverToShow(storyId: String) = promoRepository.setNeverToShowStories(storyId)
}

View file

@ -1,10 +0,0 @@
package com.tangem.domain.promo
import kotlinx.coroutines.flow.Flow
class ShouldShowSwapStoriesUseCase(private val promoRepository: PromoRepository) {
operator fun invoke(): Flow<Boolean> = promoRepository.isReadyToShowSwapStories()
suspend fun invokeSync(): Boolean = promoRepository.isReadyToShowSwapStoriesSync()
suspend fun neverToShow() = promoRepository.setNeverToShowSwapStories()
}

View file

@ -3,6 +3,7 @@ package com.tangem.domain.tokens
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.staking.repositories.StakingRepository
import com.tangem.domain.tokens.model.*
@ -66,14 +67,14 @@ class GetCryptoCurrencyActionsUseCase(
}
val flow = combine(
flow = networkFlow,
flow2 = promoRepository.isReadyToShowSwapStories().distinctUntilChanged(),
) { maybeCoinStatus, shouldShowSwapStories ->
flow2 = promoRepository.getStoryById(StoryContentIds.STORY_FIRST_TIME_SWAP.id).conflate(),
) { maybeCoinStatus, maybeSwapStories ->
createTokenActionsState(
userWallet = userWallet,
coinStatus = maybeCoinStatus.getOrNull(),
cryptoCurrencyStatus = cryptoCurrencyStatus,
requirements = requirements,
shouldShowSwapStories = shouldShowSwapStories,
shouldShowSwapStories = maybeSwapStories != null,
)
}

View file

@ -6,7 +6,7 @@ import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.core.decompose.navigation.Router
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
import com.tangem.domain.promo.ShouldShowStoriesUseCase
import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.feature.stories.api.StoriesComponent
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
@ -22,7 +22,7 @@ internal class StoriesModel @Inject constructor(
private val router: Router,
override val dispatchers: CoroutineDispatcherProvider,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
private val shouldShowStoriesUseCase: ShouldShowStoriesUseCase,
) : Model() {
val state: StateFlow<SwapStoriesUM> get() = _state
@ -38,7 +38,7 @@ internal class StoriesModel @Inject constructor(
private fun openScreen(hideStories: Boolean = true) {
modelScope.launch {
if (hideStories) {
shouldShowSwapStoriesUseCase.neverToShow()
shouldShowStoriesUseCase.neverToShow(StoryContentIds.STORY_FIRST_TIME_SWAP.id)
}
router.pop()
router.push(params.nextScreen)
@ -47,17 +47,21 @@ internal class StoriesModel @Inject constructor(
private fun initStories() {
modelScope.launch {
getStoryContentUseCase(params.storyId).fold(
getStoryContentUseCase.invokeSync(params.storyId).fold(
ifLeft = {
Timber.e("Unable to load stories for ${StoryContentIds.STORY_FIRST_TIME_SWAP.id}")
openScreen(hideStories = false) // Fallback to target screen
},
ifRight = { swapStory ->
_state.update {
SwapStoriesFactory.createStoriesState(
swapStory = swapStory,
onStoriesClose = ::openScreen,
)
if (swapStory == null) {
openScreen(hideStories = false) // Fallback to target screen
} else {
_state.update {
SwapStoriesFactory.createStoriesState(
swapStory = swapStory,
onStoriesClose = ::openScreen,
)
}
}
},
)

View file

@ -28,7 +28,7 @@ import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.BlockchainErrorInfo
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
import com.tangem.domain.promo.ShouldShowStoriesUseCase
import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.model.CryptoCurrency
@ -90,7 +90,7 @@ internal class SwapViewModel @Inject constructor(
private val saveBlockchainErrorUseCase: SaveBlockchainErrorUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val getMinimumTransactionAmountSyncUseCase: GetMinimumTransactionAmountSyncUseCase,
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
private val shouldShowStoriesUseCase: ShouldShowStoriesUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val featureToggles: SwapFeatureToggles,
swapInteractorFactory: SwapInteractor.Factory,
@ -172,9 +172,7 @@ internal class SwapViewModel @Inject constructor(
init {
viewModelScope.launch {
val isShowPromoStories = shouldShowSwapStoriesUseCase.invokeSync() && featureToggles.isPromoStoriesEnabled
if (isShowPromoStories) {
if (featureToggles.isPromoStoriesEnabled) {
initStories()
swapRouter.openScreen(SwapNavScreen.PromoStories)
}
@ -289,12 +287,14 @@ internal class SwapViewModel @Inject constructor(
private fun initStories() {
viewModelScope.launch {
getStoryContentUseCase(StoryContentIds.STORY_FIRST_TIME_SWAP.id).fold(
getStoryContentUseCase.invokeSync(StoryContentIds.STORY_FIRST_TIME_SWAP.id).fold(
ifLeft = {
Timber.e("Unable to load stories for ${StoryContentIds.STORY_FIRST_TIME_SWAP.id}")
},
ifRight = {
uiState = stateBuilder.createStoriesState(uiState, it)
ifRight = { story ->
story?.let {
uiState = stateBuilder.createStoriesState(uiState, story)
}
},
)
}
@ -1145,7 +1145,7 @@ internal class SwapViewModel @Inject constructor(
swapRouter.openScreen(SwapNavScreen.Success)
},
onStoriesClose = {
viewModelScope.launch { shouldShowSwapStoriesUseCase.neverToShow() }
viewModelScope.launch { shouldShowStoriesUseCase.neverToShow(StoryContentIds.STORY_FIRST_TIME_SWAP.id) }
swapRouter.openScreen(SwapNavScreen.Main)
},
)

View file

@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.models.UserWallet
@ -34,13 +34,13 @@ internal class MultiWalletContentLoader(
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val swapFeatureToggles: SwapFeatureToggles,
private val deepLinksRegistry: DeepLinksRegistry,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
return listOfNotNull(
return buildList {
MultiWalletTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -52,7 +52,7 @@ internal class MultiWalletContentLoader(
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
),
).let(::add)
MultiWalletWarningsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -60,13 +60,14 @@ internal class MultiWalletContentLoader(
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
),
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
shouldShowSwapStoriesUseCase = shouldShowSwapStoriesUseCase,
swapFeatureToggles = swapFeatureToggles,
),
)
).let(::add)
if (swapFeatureToggles.isPromoStoriesEnabled) {
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
}
}
}
}

View file

@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.models.UserWallet
@ -31,7 +31,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val swapFeatureToggles: SwapFeatureToggles,
private val deepLinksRegistry: DeepLinksRegistry,
) {
@ -50,7 +50,7 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
shouldShowSwapStoriesUseCase = shouldShowSwapStoriesUseCase,
getStoryContentUseCase = getStoryContentUseCase,
swapFeatureToggles = swapFeatureToggles,
deepLinksRegistry = deepLinksRegistry,
)

View file

@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
@ -32,13 +32,13 @@ internal class SingleWalletWithTokenContentLoader(
private val tokenListStore: MultiWalletTokenListStore,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val swapFeatureToggles: SwapFeatureToggles,
private val deepLinksRegistry: DeepLinksRegistry,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
return listOf(
return buildList {
SingleWalletWithTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -49,7 +49,7 @@ internal class SingleWalletWithTokenContentLoader(
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
deepLinksRegistry = deepLinksRegistry,
),
).let(::add)
MultiWalletWarningsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
@ -57,13 +57,14 @@ internal class SingleWalletWithTokenContentLoader(
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
),
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
shouldShowSwapStoriesUseCase = shouldShowSwapStoriesUseCase,
swapFeatureToggles = swapFeatureToggles,
),
)
).let(::add)
if (swapFeatureToggles.isPromoStoriesEnabled) {
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
}
}
}
}

View file

@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.deeplink.DeepLinksRegistry
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.tokens.RunPolkadotAccountHealthCheckUseCase
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
@ -28,7 +28,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val runPolkadotAccountHealthCheckUseCase: RunPolkadotAccountHealthCheckUseCase,
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val swapFeatureToggles: SwapFeatureToggles,
private val deepLinksRegistry: DeepLinksRegistry,
) {
@ -46,7 +46,7 @@ internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
runPolkadotAccountHealthCheckUseCase = runPolkadotAccountHealthCheckUseCase,
shouldShowSwapStoriesUseCase = shouldShowSwapStoriesUseCase,
getStoryContentUseCase = getStoryContentUseCase,
swapFeatureToggles = swapFeatureToggles,
deepLinksRegistry = deepLinksRegistry,
)

View file

@ -1,30 +1,28 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.UpdateMultiWalletActionButtonBadgeTransformer
import com.tangem.features.swap.SwapFeatureToggles
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
internal class MultiWalletActionButtonsSubscriber(
private val userWallet: UserWallet,
private val stateHolder: WalletStateController,
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
private val swapFeatureToggles: SwapFeatureToggles,
private val getStoryContentUseCase: GetStoryContentUseCase,
) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<*> = shouldShowSwapStoriesUseCase()
.distinctUntilChanged()
.map { showSwapBadge ->
val showBadge = swapFeatureToggles.isPromoStoriesEnabled && showSwapBadge
stateHolder.update(
UpdateMultiWalletActionButtonBadgeTransformer(
userWalletId = userWallet.walletId,
showSwapBadge = showBadge,
),
)
}
override fun create(coroutineScope: CoroutineScope): Flow<*> = getStoryContentUseCase(
id = StoryContentIds.STORY_FIRST_TIME_SWAP.id,
).map { maybeSwapStories ->
val isSwapStoriesNotNull = maybeSwapStories.getOrNull() != null
stateHolder.update(
UpdateMultiWalletActionButtonBadgeTransformer(
userWalletId = userWallet.walletId,
showSwapBadge = isSwapStoriesNotNull,
),
)
}
}

View file

@ -28,7 +28,7 @@ import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.markets.TokenMarketParams
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.promo.ShouldShowSwapStoriesUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.staking.model.stakekit.Yield
@ -113,7 +113,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
private val getExploreUrlUseCase: GetExploreUrlUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val shouldShowSwapStoriesUseCase: ShouldShowSwapStoriesUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val dispatchers: CoroutineDispatcherProvider,
private val reduxStateHolder: ReduxStateHolder,
@ -610,8 +610,8 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private suspend fun getSwapRoute(targetRoute: AppRoute): AppRoute {
val isSwapStoriesEnabled = swapFeatureToggles.isPromoStoriesEnabled
val shouldShowSwapStories = shouldShowSwapStoriesUseCase.invokeSync()
val showSwapStories = shouldShowSwapStories && isSwapStoriesEnabled
val maybeSwapStories = getStoryContentUseCase.invokeSync(StoryContentIds.STORY_FIRST_TIME_SWAP.id)
val showSwapStories = maybeSwapStories.getOrNull() != null && isSwapStoriesEnabled
return if (showSwapStories) {
AppRoute.Stories(