Updated on 2026-08-14

This commit is contained in:
Tangem 2025-05-26 16:04:26 +05:00
parent 1485eddbc7
commit 8f9b356307
27 changed files with 326 additions and 115 deletions

View file

@ -1,6 +1,7 @@
package com.tangem.tap.di.domain package com.tangem.tap.di.domain
import com.tangem.domain.redux.ReduxStateHolder import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.transaction.WalletAddressServiceRepository import com.tangem.domain.transaction.WalletAddressServiceRepository
import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase import com.tangem.domain.transaction.usecase.ParseSharedAddressUseCase
import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase import com.tangem.domain.transaction.usecase.ValidateWalletAddressUseCase
@ -245,4 +246,26 @@ internal object WalletsDomainModule {
walletsRepository = walletsRepository, walletsRepository = walletsRepository,
) )
} }
@Provides
@Singleton
fun providesSetNotificationsEnabledUseCase(
walletsRepository: WalletsRepository,
currenciesRepository: CurrenciesRepository,
): SetNotificationsEnabledUseCase {
return SetNotificationsEnabledUseCase(
walletsRepository = walletsRepository,
currenciesRepository = currenciesRepository,
)
}
@Provides
@Singleton
fun providesGetWalletNotificationsEnabledUseCase(
walletsRepository: WalletsRepository,
): GetWalletNotificationsEnabledUseCase {
return GetWalletNotificationsEnabledUseCase(
walletsRepository = walletsRepository,
)
}
} }

View file

@ -5,6 +5,7 @@ import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.datasource.utils.RequestHeader import com.tangem.datasource.utils.RequestHeader
import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.utils.ProviderSuspend import com.tangem.utils.ProviderSuspend
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider import com.tangem.utils.version.AppVersionProvider
/** /**
@ -18,6 +19,7 @@ internal class Express(
private val environmentConfigStorage: EnvironmentConfigStorage, private val environmentConfigStorage: EnvironmentConfigStorage,
private val expressAuthProvider: ExpressAuthProvider, private val expressAuthProvider: ExpressAuthProvider,
private val appVersionProvider: AppVersionProvider, private val appVersionProvider: AppVersionProvider,
private val appInfoProvider: AppInfoProvider,
) : ApiConfig() { ) : ApiConfig() {
override val defaultEnvironment: ApiEnvironment = getInitialEnvironment() override val defaultEnvironment: ApiEnvironment = getInitialEnvironment()
@ -49,7 +51,7 @@ internal class Express(
private fun createHeaders(isProd: Boolean) = buildMap { private fun createHeaders(isProd: Boolean) = buildMap {
put(key = "api-key", value = ProviderSuspend { getApiKey(isProd) }) put(key = "api-key", value = ProviderSuspend { getApiKey(isProd) })
put(key = "session-id", value = ProviderSuspend(expressAuthProvider::getSessionId)) put(key = "session-id", value = ProviderSuspend(expressAuthProvider::getSessionId))
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider).values) putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
} }
private fun getApiKey(isProd: Boolean): String { private fun getApiKey(isProd: Boolean): String {

View file

@ -2,12 +2,14 @@ package com.tangem.datasource.api.common.config
import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.utils.RequestHeader import com.tangem.datasource.utils.RequestHeader
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider import com.tangem.utils.version.AppVersionProvider
/** TangemTech [ApiConfig] */ /** TangemTech [ApiConfig] */
internal class TangemTech( internal class TangemTech(
private val appVersionProvider: AppVersionProvider, private val appVersionProvider: AppVersionProvider,
private val authProvider: AuthProvider, private val authProvider: AuthProvider,
private val appInfoProvider: AppInfoProvider,
) : ApiConfig() { ) : ApiConfig() {
override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD override val defaultEnvironment: ApiEnvironment = ApiEnvironment.PROD
@ -30,7 +32,7 @@ internal class TangemTech(
) )
private fun createHeaders() = buildMap { private fun createHeaders() = buildMap {
putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider).values) putAll(from = RequestHeader.AppVersionPlatformHeaders(appVersionProvider, appInfoProvider).values)
putAll(from = RequestHeader.AuthenticationHeader(authProvider).values) putAll(from = RequestHeader.AuthenticationHeader(authProvider).values)
} }
} }

View file

@ -5,6 +5,7 @@ import com.tangem.datasource.api.common.config.*
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider import com.tangem.lib.auth.StakeKitAuthProvider
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider import com.tangem.utils.version.AppVersionProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -22,8 +23,14 @@ internal object ApiConfigsModule {
environmentConfigStorage: EnvironmentConfigStorage, environmentConfigStorage: EnvironmentConfigStorage,
expressAuthProvider: ExpressAuthProvider, expressAuthProvider: ExpressAuthProvider,
appVersionProvider: AppVersionProvider, appVersionProvider: AppVersionProvider,
appInfoProvider: AppInfoProvider,
): ApiConfig { ): ApiConfig {
return Express(environmentConfigStorage, expressAuthProvider, appVersionProvider) return Express(
environmentConfigStorage = environmentConfigStorage,
expressAuthProvider = expressAuthProvider,
appVersionProvider = appVersionProvider,
appInfoProvider = appInfoProvider,
)
} }
@Provides @Provides
@ -34,8 +41,15 @@ internal object ApiConfigsModule {
@Provides @Provides
@IntoSet @IntoSet
fun provideTangemTechConfig(appVersionProvider: AppVersionProvider, authProvider: AuthProvider): ApiConfig = fun provideTangemTechConfig(
TangemTech(appVersionProvider, authProvider) appVersionProvider: AppVersionProvider,
authProvider: AuthProvider,
appInfoProvider: AppInfoProvider,
): ApiConfig = TangemTech(
appVersionProvider = appVersionProvider,
authProvider = authProvider,
appInfoProvider = appInfoProvider,
)
@Provides @Provides
@IntoSet @IntoSet

View file

@ -23,6 +23,7 @@ import com.tangem.datasource.local.preferences.AppPreferencesStore
import com.tangem.datasource.utils.* import com.tangem.datasource.utils.*
import com.tangem.datasource.utils.RequestHeader.AppVersionPlatformHeaders import com.tangem.datasource.utils.RequestHeader.AppVersionPlatformHeaders
import com.tangem.utils.coroutines.CoroutineDispatcherProvider import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider import com.tangem.utils.version.AppVersionProvider
import dagger.Module import dagger.Module
import dagger.Provides import dagger.Provides
@ -162,6 +163,7 @@ internal object NetworkModule {
@ApplicationContext context: Context, @ApplicationContext context: Context,
analyticsErrorHandler: AnalyticsErrorHandler, analyticsErrorHandler: AnalyticsErrorHandler,
appVersionProvider: AppVersionProvider, appVersionProvider: AppVersionProvider,
appInfoProvider: AppInfoProvider,
): TangemTechApiV2 { ): TangemTechApiV2 {
return provideTangemTechApiInternal( return provideTangemTechApiInternal(
moshi = moshi, moshi = moshi,
@ -169,6 +171,7 @@ internal object NetworkModule {
appVersionProvider = appVersionProvider, appVersionProvider = appVersionProvider,
baseUrl = PROD_V2_TANGEM_TECH_BASE_URL, baseUrl = PROD_V2_TANGEM_TECH_BASE_URL,
analyticsErrorHandler = analyticsErrorHandler, analyticsErrorHandler = analyticsErrorHandler,
appInfoProvider = appInfoProvider,
) )
} }
@ -218,15 +221,17 @@ internal object NetworkModule {
) )
} }
@Suppress("LongParameterList")
@Deprecated("use createApi instead") @Deprecated("use createApi instead")
private inline fun <reified T> provideTangemTechApiInternal( private inline fun <reified T> provideTangemTechApiInternal(
moshi: Moshi, moshi: Moshi,
context: Context, context: Context,
appVersionProvider: AppVersionProvider, appVersionProvider: AppVersionProvider,
appInfoProvider: AppInfoProvider,
baseUrl: String, baseUrl: String,
analyticsErrorHandler: AnalyticsErrorHandler, analyticsErrorHandler: AnalyticsErrorHandler,
timeouts: Timeouts = Timeouts(), timeouts: Timeouts = Timeouts(),
requestHeaders: List<RequestHeader> = listOf(AppVersionPlatformHeaders(appVersionProvider)), requestHeaders: List<RequestHeader> = listOf(AppVersionPlatformHeaders(appVersionProvider, appInfoProvider)),
): T { ): T {
val client = OkHttpClient.Builder() val client = OkHttpClient.Builder()
.applyTimeoutAnnotations() .applyTimeoutAnnotations()

View file

@ -4,6 +4,7 @@ import android.os.Build
import com.tangem.datasource.api.common.AuthProvider import com.tangem.datasource.api.common.AuthProvider
import com.tangem.datasource.utils.RequestHeader.CacheControlHeader.checkHeaderValueOrEmpty import com.tangem.datasource.utils.RequestHeader.CacheControlHeader.checkHeaderValueOrEmpty
import com.tangem.utils.ProviderSuspend import com.tangem.utils.ProviderSuspend
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider import com.tangem.utils.version.AppVersionProvider
import java.util.Locale import java.util.Locale
import java.util.TimeZone import java.util.TimeZone
@ -25,7 +26,11 @@ sealed class RequestHeader(vararg pairs: Pair<String, ProviderSuspend<String>>)
"card_public_key" to ProviderSuspend(authProvider::getCardPublicKey), "card_public_key" to ProviderSuspend(authProvider::getCardPublicKey),
) )
class AppVersionPlatformHeaders(appVersionProvider: AppVersionProvider) : RequestHeader( class AppVersionPlatformHeaders(
appVersionProvider: AppVersionProvider,
appInfoProvider: AppInfoProvider,
) : RequestHeader(
"system_version" to ProviderSuspend { appInfoProvider.osVersion },
"version" to ProviderSuspend { appVersionProvider.versionName }, "version" to ProviderSuspend { appVersionProvider.versionName },
"platform" to ProviderSuspend { "android" }, "platform" to ProviderSuspend { "android" },
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() }, "language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },

View file

@ -13,6 +13,7 @@ import com.tangem.datasource.api.common.config.ApiConfig.Companion.RELEASE_BUILD
import com.tangem.lib.auth.ExpressAuthProvider import com.tangem.lib.auth.ExpressAuthProvider
import com.tangem.lib.auth.StakeKitAuthProvider import com.tangem.lib.auth.StakeKitAuthProvider
import com.tangem.utils.ProviderSuspend import com.tangem.utils.ProviderSuspend
import com.tangem.utils.info.AppInfoProvider
import com.tangem.utils.version.AppVersionProvider import com.tangem.utils.version.AppVersionProvider
import io.mockk.every import io.mockk.every
import io.mockk.mockk import io.mockk.mockk
@ -29,11 +30,12 @@ private val appVersionProvider = mockk<AppVersionProvider>()
private val expressAuthProvider = mockk<ExpressAuthProvider>() private val expressAuthProvider = mockk<ExpressAuthProvider>()
private val stakeKitAuthProvider = mockk<StakeKitAuthProvider>() private val stakeKitAuthProvider = mockk<StakeKitAuthProvider>()
private val appAuthProvider = mockk<AuthProvider>() private val appAuthProvider = mockk<AuthProvider>()
private val appInfoProvider = mockk<AppInfoProvider>()
// Don't forget to add new config !!! // Don't forget to add new config !!!
private val API_CONFIGS = setOf( private val API_CONFIGS = setOf(
Express(configManager, expressAuthProvider, appVersionProvider), Express(configManager, expressAuthProvider, appVersionProvider, appInfoProvider),
TangemTech(appVersionProvider, appAuthProvider), TangemTech(appVersionProvider, appAuthProvider, appInfoProvider),
StakeKit(stakeKitAuthProvider), StakeKit(stakeKitAuthProvider),
TangemVisa(appVersionProvider), TangemVisa(appVersionProvider),
) )
@ -53,6 +55,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
every { stakeKitAuthProvider.getApiKey() } returns STAKE_KIT_API_KEY every { stakeKitAuthProvider.getApiKey() } returns STAKE_KIT_API_KEY
every { appAuthProvider.getCardId() } returns APP_CARD_ID every { appAuthProvider.getCardId() } returns APP_CARD_ID
every { appAuthProvider.getCardPublicKey() } returns APP_CARD_PUBLIC_KEY every { appAuthProvider.getCardPublicKey() } returns APP_CARD_PUBLIC_KEY
every { appInfoProvider.osVersion } returns "Android 16"
} }
@Test @Test
@ -129,6 +132,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
}, },
"session-id" to ProviderSuspend { EXPRESS_SESSION_ID }, "session-id" to ProviderSuspend { EXPRESS_SESSION_ID },
"version" to ProviderSuspend { VERSION_NAME }, "version" to ProviderSuspend { VERSION_NAME },
"system_version" to ProviderSuspend { "Android 16" },
"platform" to ProviderSuspend { "android" }, "platform" to ProviderSuspend { "android" },
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() }, "language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
"timezone" to ProviderSuspend { "timezone" to ProviderSuspend {
@ -151,6 +155,7 @@ internal class ProdApiConfigsManagerTest(private val model: Model) {
"card_public_key" to ProviderSuspend { APP_CARD_PUBLIC_KEY }, "card_public_key" to ProviderSuspend { APP_CARD_PUBLIC_KEY },
"version" to ProviderSuspend { VERSION_NAME }, "version" to ProviderSuspend { VERSION_NAME },
"platform" to ProviderSuspend { "android" }, "platform" to ProviderSuspend { "android" },
"system_version" to ProviderSuspend { "Android 16" },
"language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() }, "language" to ProviderSuspend { Locale.getDefault().language.checkHeaderValueOrEmpty() },
"timezone" to ProviderSuspend { "timezone" to ProviderSuspend {
TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty() TimeZone.getDefault().getDisplayName(false, TimeZone.SHORT).checkHeaderValueOrEmpty()

View file

@ -669,7 +669,7 @@
<string name="onboarding_wallet_info_title_second">Identische Karten oder Ring</string> <string name="onboarding_wallet_info_title_second">Identische Karten oder Ring</string>
<string name="onboarding_wallet_info_title_third">Zugangscode</string> <string name="onboarding_wallet_info_title_third">Zugangscode</string>
<string name="onramp_avaiable_with_payment_methods">Verfügbar mit %s</string> <string name="onramp_avaiable_with_payment_methods">Verfügbar mit %s</string>
<string name="onramp_choose_provider_title_hint">Der Kauf von Kryptowährungen bei Tangem wird von Drittanbietern zu deren Bedingungen durchgeführt.</string> <string name="onramp_choose_provider_title_hint">Anbieter erleichtern Transaktionen</string>
<string name="onramp_country_search">Suche nach Land</string> <string name="onramp_country_search">Suche nach Land</string>
<string name="onramp_country_unavailable">Nicht verfügbar</string> <string name="onramp_country_unavailable">Nicht verfügbar</string>
<string name="onramp_currency_other">Andere Währungen</string> <string name="onramp_currency_other">Andere Währungen</string>
@ -696,6 +696,10 @@
<string name="organize_tokens_sort_by_balance">Nach Guthaben</string> <string name="organize_tokens_sort_by_balance">Nach Guthaben</string>
<string name="organize_tokens_title">Token organisieren</string> <string name="organize_tokens_title">Token organisieren</string>
<string name="organize_tokens_ungroup">Gruppe löschen</string> <string name="organize_tokens_ungroup">Gruppe löschen</string>
<string name="push_notifications_permission_alert_description">Du kannst Benachrichtigungen für Tangem in den Einstellungen aktivieren.</string>
<string name="push_notifications_permission_alert_negative_button">Später aktivieren</string>
<string name="push_notifications_permission_alert_positive_button">Einstellungen</string>
<string name="push_notifications_permission_alert_title">Benachrichtigungen aktivieren</string>
<string name="qr_scanner_camera_denied_gallery_button">Wählen aus der Galerie aus</string> <string name="qr_scanner_camera_denied_gallery_button">Wählen aus der Galerie aus</string>
<string name="qr_scanner_camera_denied_settings_button">Einstellungen</string> <string name="qr_scanner_camera_denied_settings_button">Einstellungen</string>
<string name="qr_scanner_camera_denied_text">Du hast keinen Zugriff auf deine Kamera gewährt</string> <string name="qr_scanner_camera_denied_text">Du hast keinen Zugriff auf deine Kamera gewährt</string>
@ -867,7 +871,7 @@
<string name="staking_details_auto_claiming_rewards_daily_text">Belohnungen sammeln sich täglich automatisch in deinem Staking-Konto an.</string> <string name="staking_details_auto_claiming_rewards_daily_text">Belohnungen sammeln sich täglich automatisch in deinem Staking-Konto an.</string>
<string name="staking_details_available">Verfügbar</string> <string name="staking_details_available">Verfügbar</string>
<string name="staking_details_average_reward_rate">Durchschnittliche Belohnungsquote</string> <string name="staking_details_average_reward_rate">Durchschnittliche Belohnungsquote</string>
<string name="staking_details_banner_text">Was ist Staking?</string> <string name="staking_details_banner_text">Wie funktioniert Staking?</string>
<string name="staking_details_estimated_profit">%s geschätzter Profit</string> <string name="staking_details_estimated_profit">%s geschätzter Profit</string>
<string name="staking_details_market_rating">Marktposition</string> <string name="staking_details_market_rating">Marktposition</string>
<string name="staking_details_metrics_block_header">Metriken</string> <string name="staking_details_metrics_block_header">Metriken</string>
@ -899,10 +903,10 @@
<string name="staking_notification_earn_rewards_text_daily">Tangem ermöglicht es Benutzern, ihre Kryptowährungen zu Staken</string> <string name="staking_notification_earn_rewards_text_daily">Tangem ermöglicht es Benutzern, ihre Kryptowährungen zu Staken</string>
<string name="staking_notification_earn_rewards_text_hourly">Tangem ermöglicht es Benutzern, ihre Kryptowährungen zu Staken</string> <string name="staking_notification_earn_rewards_text_hourly">Tangem ermöglicht es Benutzern, ihre Kryptowährungen zu Staken</string>
<string name="staking_notification_earn_rewards_text_monthly">Tangem ermöglicht es Benutzern, ihre Kryptowährungen zu Staken</string> <string name="staking_notification_earn_rewards_text_monthly">Tangem ermöglicht es Benutzern, ihre Kryptowährungen zu Staken</string>
<string name="staking_notification_earn_rewards_text_period_day">Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jeden tag.</string> <string name="staking_notification_earn_rewards_text_period_day">Durch Staking kannst Du %1$s erhalten. Deine Staking-Belohnungen kommen täglich an.</string>
<string name="staking_notification_earn_rewards_text_period_hour">Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jede Stunde.</string> <string name="staking_notification_earn_rewards_text_period_hour">Durch Staking kannst Du %1$s erhalten. Deine Staking-Belohnungen werden stündlich gutgeschrieben.</string>
<string name="staking_notification_earn_rewards_text_period_month">Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jeden Monat.</string> <string name="staking_notification_earn_rewards_text_period_month">Durch Staking kannst Du %1$s erhalten. Deine Staking-Belohnungen werden Dir jeden Monat gutgeschrieben.</string>
<string name="staking_notification_earn_rewards_text_period_week">Mit dem Staking kannst Du %1$s verdienen. Deine Staking-Belohnungen erhältst Du jede Woche.</string> <string name="staking_notification_earn_rewards_text_period_week">Durch Staking kannst Du %1$s erhalten. Deine Staking-Belohnungen kommen jede Woche.</string>
<string name="staking_notification_earn_rewards_text_weekly">Tangem ermöglicht es Benutzern, ihre Kryptowährungen zu Staken</string> <string name="staking_notification_earn_rewards_text_weekly">Tangem ermöglicht es Benutzern, ihre Kryptowährungen zu Staken</string>
<string name="staking_notification_earn_rewards_title">Verdiene Staking-Belohnungen</string> <string name="staking_notification_earn_rewards_title">Verdiene Staking-Belohnungen</string>
<string name="staking_notification_low_staked_balance_text">Dein verbleibender Stakingbetrag ist zu niedrig, um den Einsatz aufzuheben. Du musst mehr Staken, um den Mindestbetrag zum Aufheben des Einsatzes zu erreichen.</string> <string name="staking_notification_low_staked_balance_text">Dein verbleibender Stakingbetrag ist zu niedrig, um den Einsatz aufzuheben. Du musst mehr Staken, um den Mindestbetrag zum Aufheben des Einsatzes zu erreichen.</string>
@ -1258,6 +1262,8 @@
<string name="wc_alert_session_disconnected_title">WalletConnect-Sitzung wurde getrennt</string> <string name="wc_alert_session_disconnected_title">WalletConnect-Sitzung wurde getrennt</string>
<string name="wc_alert_unknown_error_description">Fehlercode: %s. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support.</string> <string name="wc_alert_unknown_error_description">Fehlercode: %s. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support.</string>
<string name="wc_alert_unknown_error_title">Wir haben einen unbekannten Fehler festgestellt.</string> <string name="wc_alert_unknown_error_title">Wir haben einen unbekannten Fehler festgestellt.</string>
<string name="wc_alert_unsupported_method_description">Fehlercode: 8 005. Wenn das Problem weiterhin besteht, wende Dich bitte an unseren Support.</string>
<string name="wc_alert_unsupported_method_title">Wir haben einen unbekannten Fehler festgestellt.</string>
<string name="wc_alert_unsupported_networks_description">Tangem unterstützt derzeit das erforderliches Netzwerk von %s nicht.</string> <string name="wc_alert_unsupported_networks_description">Tangem unterstützt derzeit das erforderliches Netzwerk von %s nicht.</string>
<string name="wc_alert_unsupported_networks_title">Nicht unterstützte Netzwerke</string> <string name="wc_alert_unsupported_networks_title">Nicht unterstützte Netzwerke</string>
<string name="wc_alert_verified_domain_description">Tangem unterstützt ein erforderliches Netzwerk um %s.</string> <string name="wc_alert_verified_domain_description">Tangem unterstützt ein erforderliches Netzwerk um %s.</string>

View file

@ -518,7 +518,7 @@
<string name="nft_about_title">NFTについて</string> <string name="nft_about_title">NFTについて</string>
<string name="nft_asset">NFTアセット</string> <string name="nft_asset">NFTアセット</string>
<plurals name="nft_collections_count"> <plurals name="nft_collections_count">
<item quantity="other">アイテム</item> <item quantity="other">%d アイテム</item>
</plurals> </plurals>
<string name="nft_collections_empty_description">ウォレットに送信されたNFTがここに表示されます。</string> <string name="nft_collections_empty_description">ウォレットに送信されたNFTがここに表示されます。</string>
<string name="nft_collections_empty_title">コレクションはまだありません</string> <string name="nft_collections_empty_title">コレクションはまだありません</string>
@ -687,6 +687,10 @@
<string name="organize_tokens_sort_by_balance">残高順</string> <string name="organize_tokens_sort_by_balance">残高順</string>
<string name="organize_tokens_title">トークンを整理する</string> <string name="organize_tokens_title">トークンを整理する</string>
<string name="organize_tokens_ungroup">グループ解除</string> <string name="organize_tokens_ungroup">グループ解除</string>
<string name="push_notifications_permission_alert_description">Tangemの通知は設定で有効にできます。</string>
<string name="push_notifications_permission_alert_negative_button">後で有効にする</string>
<string name="push_notifications_permission_alert_positive_button">設定</string>
<string name="push_notifications_permission_alert_title">通知を有効にする</string>
<string name="qr_scanner_camera_denied_gallery_button">ギャラリーから選択</string> <string name="qr_scanner_camera_denied_gallery_button">ギャラリーから選択</string>
<string name="qr_scanner_camera_denied_settings_button">設定</string> <string name="qr_scanner_camera_denied_settings_button">設定</string>
<string name="qr_scanner_camera_denied_text">カメラへのアクセスを許可していません。</string> <string name="qr_scanner_camera_denied_text">カメラへのアクセスを許可していません。</string>
@ -1242,6 +1246,8 @@
<string name="wc_alert_session_disconnected_title">WalletConnectセッションが接続解除されました</string> <string name="wc_alert_session_disconnected_title">WalletConnectセッションが接続解除されました</string>
<string name="wc_alert_unknown_error_description">エラーコード: %s 。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。</string> <string name="wc_alert_unknown_error_description">エラーコード: %s 。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。</string>
<string name="wc_alert_unknown_error_title">不明なエラーが発生しました</string> <string name="wc_alert_unknown_error_title">不明なエラーが発生しました</string>
<string name="wc_alert_unsupported_method_description">エラーコード: 8 005。問題が解決しない場合は、お気軽にサポートまでお問い合わせください。</string>
<string name="wc_alert_unsupported_method_title">不明なエラーが発生しました</string>
<string name="wc_alert_unsupported_networks_description">Tangemは現在%sで必要なネットワークをサポートしていません。</string> <string name="wc_alert_unsupported_networks_description">Tangemは現在%sで必要なネットワークをサポートしていません。</string>
<string name="wc_alert_unsupported_networks_title">サポートされていないネットワーク</string> <string name="wc_alert_unsupported_networks_title">サポートされていないネットワーク</string>
<string name="wc_alert_verified_domain_description">Tangemは%sで必要なネットワークをサポートします。</string> <string name="wc_alert_verified_domain_description">Tangemは%sで必要なネットワークをサポートします。</string>

View file

@ -701,6 +701,11 @@
<string name="organize_tokens_sort_by_balance">By balance</string> <string name="organize_tokens_sort_by_balance">By balance</string>
<string name="organize_tokens_title">Organize tokens</string> <string name="organize_tokens_title">Organize tokens</string>
<string name="organize_tokens_ungroup">Ungroup</string> <string name="organize_tokens_ungroup">Ungroup</string>
<string name="push_notifications_more_info">More info</string>
<string name="push_notifications_permission_alert_description">You can enable Notifications for Tangem in Settings.</string>
<string name="push_notifications_permission_alert_negative_button">Enable Later</string>
<string name="push_notifications_permission_alert_positive_button">Settings</string>
<string name="push_notifications_permission_alert_title">Enable Notifications</string>
<string name="qr_scanner_camera_denied_gallery_button">Select from the gallery</string> <string name="qr_scanner_camera_denied_gallery_button">Select from the gallery</string>
<string name="qr_scanner_camera_denied_settings_button">Settings</string> <string name="qr_scanner_camera_denied_settings_button">Settings</string>
<string name="qr_scanner_camera_denied_text">You have not given access to your camera</string> <string name="qr_scanner_camera_denied_text">You have not given access to your camera</string>

View file

@ -1,4 +1,4 @@
package com.tangem.features.markets.details.impl.ui.components package com.tangem.core.ui.components.items
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -7,9 +7,11 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.withStyle import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.R
import com.tangem.core.ui.components.TextShimmer import com.tangem.core.ui.components.TextShimmer
import com.tangem.core.ui.extensions.TextReference import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
@ -18,15 +20,15 @@ import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.utils.PreviewShimmerContainer import com.tangem.core.ui.utils.PreviewShimmerContainer
import com.tangem.features.markets.impl.R
import com.tangem.utils.StringsSigns import com.tangem.utils.StringsSigns
@Composable @Composable
internal fun Description( fun DescriptionItem(
description: TextReference, description: TextReference,
hasFullDescription: Boolean, hasFullDescription: Boolean,
onReadMoreClick: () -> Unit, onReadMoreClick: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
textStyle: TextStyle = TangemTheme.typography.body2,
) { ) {
if (hasFullDescription) { if (hasFullDescription) {
val text = buildAnnotatedString { val text = buildAnnotatedString {
@ -51,20 +53,20 @@ internal fun Description(
onClick = onReadMoreClick, onClick = onReadMoreClick,
), ),
text = text, text = text,
style = TangemTheme.typography.body2, style = textStyle,
) )
} else { } else {
Text( Text(
modifier = modifier, modifier = modifier,
text = description.resolveReference(), text = description.resolveReference(),
style = TangemTheme.typography.body2, style = textStyle,
color = TangemTheme.colors.text.secondary, color = TangemTheme.colors.text.secondary,
) )
} }
} }
@Composable @Composable
internal fun DescriptionPlaceholder(modifier: Modifier = Modifier) { public fun DescriptionPlaceholder(modifier: Modifier = Modifier) {
Column( Column(
modifier = modifier, modifier = modifier,
) { ) {
@ -90,7 +92,7 @@ internal fun DescriptionPlaceholder(modifier: Modifier = Modifier) {
@Composable @Composable
private fun ContentPreview() { private fun ContentPreview() {
TangemThemePreview { TangemThemePreview {
Description( DescriptionItem(
description = stringReference( description = stringReference(
"XRP (XRP) is a cryptocurrency launched in January 2009, where the first " + "XRP (XRP) is a cryptocurrency launched in January 2009, where the first " +
"genesis block was mined on 9th January 2009", "genesis block was mined on 9th January 2009",

View file

@ -46,18 +46,23 @@ class UserTokensSaver constructor(
push(userWalletId, enrichedUserTokensResponse, false) push(userWalletId, enrichedUserTokensResponse, false)
} }
suspend fun push(userWalletId: UserWalletId, response: UserTokensResponse, useEnricher: Boolean = true) = suspend fun push(
withContext(dispatchers.io) { userWalletId: UserWalletId,
val enrichedUserTokensResponse = if (useEnricher) { response: UserTokensResponse,
userTokensResponseAddressesEnricher( useEnricher: Boolean = true,
userWalletId = userWalletId, onFailSend: () -> Unit = {},
response = response, ) = withContext(dispatchers.io) {
) val enrichedUserTokensResponse = if (useEnricher) {
} else { userTokensResponseAddressesEnricher(
response userWalletId = userWalletId,
} response = response,
safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedUserTokensResponse).bind() }) { )
Timber.e(it, "Unable to push user tokens for: ${userWalletId.stringValue}") } else {
} response
} }
safeApiCall({ tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedUserTokensResponse).bind() }) {
Timber.e(it, "Unable to push user tokens for: ${userWalletId.stringValue}")
onFailSend()
}
}
} }

View file

@ -110,66 +110,42 @@ class UserTokensSaverTest {
} }
@Test @Test
fun `GIVEN user wallet id and response WHEN push THEN should push enriched response`() = runTest { fun `GIVEN user wallet id and response WHEN push AND api call fails THEN should log error and call onFailSend`() =
// GIVEN runTest {
val userWalletId = UserWalletId("1234567890abcdef") // GIVEN
val response = UserTokensResponse( val userWalletId = UserWalletId("1234567890abcdef")
version = 0, val response = UserTokensResponse(
group = UserTokensResponse.GroupType.NETWORK, version = 0,
sort = UserTokensResponse.SortType.BALANCE, group = UserTokensResponse.GroupType.NETWORK,
tokens = emptyList(), sort = UserTokensResponse.SortType.BALANCE,
) tokens = emptyList(),
val enrichedResponse = UserTokensResponse( )
version = 0, val enrichedResponse = UserTokensResponse(
group = UserTokensResponse.GroupType.NETWORK, version = 0,
sort = UserTokensResponse.SortType.BALANCE, group = UserTokensResponse.GroupType.NETWORK,
tokens = emptyList(), sort = UserTokensResponse.SortType.BALANCE,
) tokens = emptyList(),
coEvery { userTokensResponseAddressesEnricher(userWalletId, response) } returns enrichedResponse )
coEvery { tangemTechApi.saveUserTokens(any(), any()) } returns ApiResponse.Success(Unit) val error = ApiResponseError.UnknownException(Exception("API Error"))
var onFailSendCalled = false
coEvery { userTokensResponseAddressesEnricher(userWalletId, response) } returns enrichedResponse
coEvery { tangemTechApi.saveUserTokens(any(), any()) } returns
ApiResponse.Error(error) as ApiResponse<Unit>
// WHEN // WHEN
userTokensSaver.push(userWalletId, response) userTokensSaver.push(
userWalletId = userWalletId,
response = response,
onFailSend = { onFailSendCalled = true },
)
// THEN // THEN
coVerify { coVerify {
tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedResponse) tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedResponse)
}
coVerify(exactly = 0) {
dataStore.updateData(any())
}
assert(onFailSendCalled) { "onFailSend callback should be called when API call fails" }
} }
coVerify(exactly = 0) {
dataStore.updateData(any())
}
}
@Test
fun `GIVEN user wallet id and response WHEN push AND api call fails THEN should log error`() = runTest {
// GIVEN
val userWalletId = UserWalletId("1234567890abcdef")
val response = UserTokensResponse(
version = 0,
group = UserTokensResponse.GroupType.NETWORK,
sort = UserTokensResponse.SortType.BALANCE,
tokens = emptyList(),
)
val enrichedResponse = UserTokensResponse(
version = 0,
group = UserTokensResponse.GroupType.NETWORK,
sort = UserTokensResponse.SortType.BALANCE,
tokens = emptyList(),
)
val error = ApiResponseError.UnknownException(Exception("API Error"))
coEvery { userTokensResponseAddressesEnricher(userWalletId, response) } returns enrichedResponse
coEvery { tangemTechApi.saveUserTokens(any(), any()) } returns
ApiResponse.Error(error) as ApiResponse<Unit>
// WHEN
userTokensSaver.push(userWalletId, response)
// THEN
coVerify {
tangemTechApi.saveUserTokens(userWalletId.stringValue, enrichedResponse)
}
coVerify(exactly = 0) {
dataStore.updateData(any())
}
}
} }

View file

@ -548,6 +548,16 @@ internal class DefaultCurrenciesRepository(
return blockchain?.isNetworkFeeZero() ?: false return blockchain?.isNetworkFeeZero() ?: false
} }
override suspend fun syncTokens(userWalletId: UserWalletId) {
val savedCurrencies = requireNotNull(
value = getSavedUserTokensResponseSync(key = userWalletId),
lazyMessage = { "Saved tokens empty. Can not perform add currencies action" },
)
userTokensSaver.push(userWalletId, savedCurrencies, onFailSend = {
throw IllegalStateException("Unable to push tokens")
},)
}
private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow<List<CryptoCurrency>> { private fun getMultiCurrencyWalletCurrencies(userWallet: UserWallet): Flow<List<CryptoCurrency>> {
return getSavedUserTokensResponse(userWallet.walletId).map { storedTokens -> return getSavedUserTokensResponse(userWallet.walletId).map { storedTokens ->
responseCurrenciesFactory.createCurrencies( responseCurrenciesFactory.createCurrencies(

View file

@ -238,6 +238,10 @@ internal class DefaultWalletsRepository(
} }
} }
override fun notificationsEnabledStatus(userWalletId: UserWalletId): Flow<Boolean> = appPreferencesStore
.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY)
.map { it[userWalletId.stringValue] == true }
override suspend fun isNotificationsEnabled(userWalletId: UserWalletId): Boolean = override suspend fun isNotificationsEnabled(userWalletId: UserWalletId): Boolean =
appPreferencesStore.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY) appPreferencesStore.getObjectMap<Boolean>(PreferencesKeys.NOTIFICATIONS_ENABLED_STATES_KEY)
.map { it[userWalletId.stringValue] } .map { it[userWalletId.stringValue] }

View file

@ -8,6 +8,7 @@ import com.tangem.domain.tokens.model.FeePaidCurrency
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlin.jvm.Throws
/** /**
* Repository for everything related to the tokens of user wallet * Repository for everything related to the tokens of user wallet
@ -254,4 +255,15 @@ interface CurrenciesRepository {
): Flow<Map<UserWallet, List<CryptoCurrency>>> ): Flow<Map<UserWallet, List<CryptoCurrency>>>
fun isNetworkFeeZero(userWalletId: UserWalletId, network: Network): Boolean fun isNetworkFeeZero(userWalletId: UserWalletId, network: Network): Boolean
/**
* Synchronizes local tokens with remote data for a specific user wallet.
* This method ensures that the local token list matches the remote state by fetching
* the token data from the local cache and push it to backend.
*
* @param userWalletId The unique identifier of the user wallet to sync tokens for.
* @throws Exception if the sync request to the backend fails
*/
@Throws
suspend fun syncTokens(userWalletId: UserWalletId)
} }

View file

@ -163,4 +163,8 @@ internal class MockCurrenciesRepository(
override fun isNetworkFeeZero(userWalletId: UserWalletId, network: Network): Boolean { override fun isNetworkFeeZero(userWalletId: UserWalletId, network: Network): Boolean {
return false return false
} }
override suspend fun syncTokens(userWalletId: UserWalletId) {
return Unit
}
} }

View file

@ -41,6 +41,8 @@ interface WalletsRepository {
suspend fun disableNFT(userWalletId: UserWalletId) suspend fun disableNFT(userWalletId: UserWalletId)
fun notificationsEnabledStatus(userWalletId: UserWalletId): Flow<Boolean>
suspend fun isNotificationsEnabled(userWalletId: UserWalletId): Boolean suspend fun isNotificationsEnabled(userWalletId: UserWalletId): Boolean
suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean) suspend fun setNotificationsEnabled(userWalletId: UserWalletId, isEnabled: Boolean)

View file

@ -0,0 +1,13 @@
package com.tangem.domain.wallets.usecase
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository
import kotlinx.coroutines.flow.Flow
class GetWalletNotificationsEnabledUseCase(
private val walletsRepository: WalletsRepository,
) {
operator fun invoke(userWalletId: UserWalletId): Flow<Boolean> = walletsRepository
.notificationsEnabledStatus(userWalletId)
}

View file

@ -1,11 +1,13 @@
package com.tangem.domain.wallets.usecase package com.tangem.domain.wallets.usecase
import arrow.core.Either import arrow.core.Either
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.models.UserWalletId import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.domain.wallets.repository.WalletsRepository import com.tangem.domain.wallets.repository.WalletsRepository
class SetNotificationsEnabledUseCase( class SetNotificationsEnabledUseCase(
private val walletsRepository: WalletsRepository, private val walletsRepository: WalletsRepository,
private val currenciesRepository: CurrenciesRepository,
) { ) {
suspend operator fun invoke(userWalletId: UserWalletId, isEnabled: Boolean): Either<Throwable, Unit> = suspend operator fun invoke(userWalletId: UserWalletId, isEnabled: Boolean): Either<Throwable, Unit> =
@ -14,5 +16,11 @@ class SetNotificationsEnabledUseCase(
userWalletId = userWalletId, userWalletId = userWalletId,
isEnabled = isEnabled, isEnabled = isEnabled,
) )
currenciesRepository.syncTokens(userWalletId)
}.onLeft {
walletsRepository.setNotificationsEnabled(
userWalletId = userWalletId,
isEnabled = !isEnabled,
)
} }
} }

View file

@ -10,6 +10,8 @@ import androidx.compose.ui.Modifier
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM import com.tangem.features.markets.details.impl.ui.state.MarketsTokenDetailsUM
import com.tangem.core.ui.components.UnableToLoadData import com.tangem.core.ui.components.UnableToLoadData
import com.tangem.core.ui.components.items.DescriptionItem
import com.tangem.core.ui.components.items.DescriptionPlaceholder
internal fun LazyListScope.tokenMarketDetailsBody( internal fun LazyListScope.tokenMarketDetailsBody(
state: MarketsTokenDetailsUM.Body, state: MarketsTokenDetailsUM.Body,
@ -69,7 +71,7 @@ private fun LazyListScope.error(state: MarketsTokenDetailsUM.Body.Error) {
private fun LazyListScope.description(description: MarketsTokenDetailsUM.Description) { private fun LazyListScope.description(description: MarketsTokenDetailsUM.Description) {
item("description") { item("description") {
Description( DescriptionItem(
modifier = Modifier.blockPaddings(), modifier = Modifier.blockPaddings(),
description = description.shortDescription, description = description.shortDescription,
hasFullDescription = description.fullDescription != null, hasFullDescription = description.fullDescription != null,

View file

@ -35,6 +35,7 @@ dependencies {
implementation(projects.domain.wallets.models) implementation(projects.domain.wallets.models)
implementation(projects.domain.demo) implementation(projects.domain.demo)
implementation(projects.domain.nft) implementation(projects.domain.nft)
implementation(projects.domain.notifications.toggles)
/* AndroidX */ /* AndroidX */
implementation(deps.androidx.fragment.ktx) implementation(deps.androidx.fragment.ktx)

View file

@ -31,6 +31,10 @@ internal class PreviewWalletSettingsComponent : WalletSettingsComponent {
onLinkMoreCardsClick = {}, onLinkMoreCardsClick = {},
onReferralClick = {}, onReferralClick = {},
isManageTokensAvailable = true, isManageTokensAvailable = true,
isNotificationsEnabled = true,
isNotificationsFeatureEnabled = true,
onCheckedNotificationsChanged = {},
onNotificationsDescriptionClick = {},
), ),
) )

View file

@ -30,4 +30,11 @@ internal sealed class WalletSettingsItemUM {
val isEnabled: Boolean, val isEnabled: Boolean,
val onClick: () -> Unit, val onClick: () -> Unit,
) : WalletSettingsItemUM() ) : WalletSettingsItemUM()
data class DescriptionWithMore(
override val id: String,
val text: TextReference,
val more: TextReference,
val onClick: () -> Unit,
) : WalletSettingsItemUM()
} }

View file

@ -22,10 +22,9 @@ import com.tangem.domain.models.scan.ScanResponse
import com.tangem.domain.nft.DisableWalletNFTUseCase import com.tangem.domain.nft.DisableWalletNFTUseCase
import com.tangem.domain.nft.EnableWalletNFTUseCase import com.tangem.domain.nft.EnableWalletNFTUseCase
import com.tangem.domain.nft.GetWalletNFTEnabledUseCase import com.tangem.domain.nft.GetWalletNFTEnabledUseCase
import com.tangem.domain.notifications.toggles.NotificationsFeatureToggles
import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.models.UserWallet
import com.tangem.domain.wallets.usecase.DeleteWalletUseCase import com.tangem.domain.wallets.usecase.*
import com.tangem.domain.wallets.usecase.GetUserWalletUseCase
import com.tangem.domain.wallets.usecase.ShouldSaveUserWalletsSyncUseCase
import com.tangem.feature.walletsettings.analytics.Settings import com.tangem.feature.walletsettings.analytics.Settings
import com.tangem.feature.walletsettings.component.WalletSettingsComponent import com.tangem.feature.walletsettings.component.WalletSettingsComponent
import com.tangem.feature.walletsettings.entity.DialogConfig import com.tangem.feature.walletsettings.entity.DialogConfig
@ -60,6 +59,9 @@ internal class WalletSettingsModel @Inject constructor(
private val getWalletNFTEnabledUseCase: GetWalletNFTEnabledUseCase, private val getWalletNFTEnabledUseCase: GetWalletNFTEnabledUseCase,
private val enableWalletNFTUseCase: EnableWalletNFTUseCase, private val enableWalletNFTUseCase: EnableWalletNFTUseCase,
private val disableWalletNFTUseCase: DisableWalletNFTUseCase, private val disableWalletNFTUseCase: DisableWalletNFTUseCase,
private val notificationsToggles: NotificationsFeatureToggles,
private val getWalletNotificationsEnabledUseCase: GetWalletNotificationsEnabledUseCase,
private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase,
) : Model() { ) : Model() {
val params: WalletSettingsComponent.Params = paramsContainer.require() val params: WalletSettingsComponent.Params = paramsContainer.require()
@ -73,23 +75,27 @@ internal class WalletSettingsModel @Inject constructor(
) )
init { init {
getWalletUseCase.invokeFlow(params.userWalletId) combine(
.distinctUntilChanged() getWalletUseCase.invokeFlow(params.userWalletId).distinctUntilChanged(),
.combine(getWalletNFTEnabledUseCase.invoke(params.userWalletId)) { maybeWallet, nftEnabled -> getWalletNFTEnabledUseCase.invoke(params.userWalletId),
val wallet = maybeWallet.getOrNull() ?: return@combine getWalletNotificationsEnabledUseCase(params.userWalletId),
val isRenameWalletAvailable = getShouldSaveUserWalletsSyncUseCase() ) { maybeWallet, nftEnabled, notificationsEnabled ->
state.update { value -> val wallet = maybeWallet.getOrNull() ?: return@combine
value.copy( val isRenameWalletAvailable = getShouldSaveUserWalletsSyncUseCase()
items = buildItems( state.update { value ->
userWallet = wallet, value.copy(
dialogNavigation = dialogNavigation, items = buildItems(
isRenameWalletAvailable = isRenameWalletAvailable, userWallet = wallet,
isNFTFeatureEnabled = nftFeatureToggles.isNFTEnabled, dialogNavigation = dialogNavigation,
isNFTEnabled = nftEnabled, isRenameWalletAvailable = isRenameWalletAvailable,
), isNFTFeatureEnabled = nftFeatureToggles.isNFTEnabled,
) isNFTEnabled = nftEnabled,
} isNotificationsEnabled = notificationsEnabled,
isNotificationsFeatureEnabled = notificationsToggles.isNotificationsEnabled,
),
)
} }
}
.launchIn(modelScope) .launchIn(modelScope)
} }
@ -99,6 +105,8 @@ internal class WalletSettingsModel @Inject constructor(
isRenameWalletAvailable: Boolean, isRenameWalletAvailable: Boolean,
isNFTFeatureEnabled: Boolean, isNFTFeatureEnabled: Boolean,
isNFTEnabled: Boolean, isNFTEnabled: Boolean,
isNotificationsFeatureEnabled: Boolean,
isNotificationsEnabled: Boolean,
): PersistentList<WalletSettingsItemUM> = itemsBuilder.buildItems( ): PersistentList<WalletSettingsItemUM> = itemsBuilder.buildItems(
userWalletId = userWallet.walletId, userWalletId = userWallet.walletId,
userWalletName = userWallet.name, userWalletName = userWallet.name,
@ -129,6 +137,10 @@ internal class WalletSettingsModel @Inject constructor(
onLinkMoreCardsClick(scanResponse = userWallet.scanResponse) onLinkMoreCardsClick(scanResponse = userWallet.scanResponse)
}, },
onReferralClick = { onReferralClick(userWallet) }, onReferralClick = { onReferralClick(userWallet) },
isNotificationsEnabled = isNotificationsEnabled,
isNotificationsFeatureEnabled = isNotificationsFeatureEnabled,
onCheckedNotificationsChanged = ::onCheckedNotificationsChange,
onNotificationsDescriptionClick = ::onNotificationsDescriptionClick,
) )
private fun openRenameWalletDialog(userWallet: UserWallet, dialogNavigation: SlotNavigation<DialogConfig>) { private fun openRenameWalletDialog(userWallet: UserWallet, dialogNavigation: SlotNavigation<DialogConfig>) {
@ -180,6 +192,17 @@ internal class WalletSettingsModel @Inject constructor(
} }
} }
private fun onCheckedNotificationsChange(isChecked: Boolean) {
// TODO [REDACTED_JIRA]
modelScope.launch {
setNotificationsEnabledUseCase(params.userWalletId, isChecked)
}
}
private fun onNotificationsDescriptionClick() {
// TODO [REDACTED_JIRA]
}
private fun onReferralClick(userWallet: UserWallet) { private fun onReferralClick(userWallet: UserWallet) {
if (isDemoCardUseCase(userWallet.cardId)) { if (isDemoCardUseCase(userWallet.cardId)) {
messageSender.send( messageSender.send(

View file

@ -13,11 +13,14 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag import androidx.compose.ui.platform.testTag
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.components.TangemSwitch import com.tangem.core.ui.components.TangemSwitch
import com.tangem.core.ui.components.appbar.TangemTopAppBar import com.tangem.core.ui.components.appbar.TangemTopAppBar
import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM import com.tangem.core.ui.components.appbar.models.TopAppBarButtonUM
import com.tangem.core.ui.components.block.BlockCard import com.tangem.core.ui.components.block.BlockCard
import com.tangem.core.ui.components.block.BlockItem import com.tangem.core.ui.components.block.BlockItem
import com.tangem.core.ui.components.items.DescriptionItem
import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemTheme
@ -98,6 +101,12 @@ private fun Content(state: WalletSettingsUM, modifier: Modifier = Modifier) {
modifier = itemModifier, modifier = itemModifier,
model = item, model = item,
) )
is WalletSettingsItemUM.DescriptionWithMore -> DescriptionWithMoreBlock(
modifier = itemModifier,
model = item,
yOffset = DESCRIPTION_OFFSET,
onDescriptionClick = item.onClick,
)
} }
} }
} }
@ -195,6 +204,26 @@ private fun SwitchBlock(model: WalletSettingsItemUM.WithSwitch, modifier: Modifi
} }
} }
@Composable
private fun DescriptionWithMoreBlock(
model: WalletSettingsItemUM.DescriptionWithMore,
onDescriptionClick: () -> Unit,
modifier: Modifier = Modifier,
yOffset: Dp = 0.dp,
) {
DescriptionItem(
modifier = modifier
.offset(y = yOffset)
.padding(horizontal = 12.dp),
description = model.text,
hasFullDescription = true,
textStyle = TangemTheme.typography.caption2,
onReadMoreClick = onDescriptionClick,
)
}
private val DESCRIPTION_OFFSET = -8.dp
// region Preview // region Preview
@Composable @Composable
@Preview(showBackground = true, widthDp = 360) @Preview(showBackground = true, widthDp = 360)

View file

@ -34,6 +34,10 @@ internal class ItemsBuilder @Inject constructor(
isNFTFeatureEnabled: Boolean, isNFTFeatureEnabled: Boolean,
isNFTEnabled: Boolean, isNFTEnabled: Boolean,
onCheckedNFTChange: (Boolean) -> Unit, onCheckedNFTChange: (Boolean) -> Unit,
isNotificationsFeatureEnabled: Boolean,
isNotificationsEnabled: Boolean,
onCheckedNotificationsChanged: (Boolean) -> Unit,
onNotificationsDescriptionClick: () -> Unit,
forgetWallet: () -> Unit, forgetWallet: () -> Unit,
renameWallet: () -> Unit, renameWallet: () -> Unit,
onLinkMoreCardsClick: () -> Unit, onLinkMoreCardsClick: () -> Unit,
@ -57,6 +61,22 @@ internal class ItemsBuilder @Inject constructor(
onReferralClick = onReferralClick, onReferralClick = onReferralClick,
), ),
) )
.run {
if (isNotificationsFeatureEnabled) {
add(
buildNotificationsSwitchItem(
isNotificationsEnabled,
onCheckedNotificationsChanged,
),
).add(
buildNotificationsDescriptionItem(
onDescriptionClick = onNotificationsDescriptionClick,
),
)
} else {
this
}
}
.add(buildForgetItem(forgetWallet)) .add(buildForgetItem(forgetWallet))
private fun buildNameItem(walletName: String, isRenameWalletAvailable: Boolean, renameWallet: () -> Unit) = private fun buildNameItem(walletName: String, isRenameWalletAvailable: Boolean, renameWallet: () -> Unit) =
@ -76,6 +96,22 @@ internal class ItemsBuilder @Inject constructor(
onCheckedChange = onCheckedNFTChange, onCheckedChange = onCheckedNFTChange,
) )
private fun buildNotificationsSwitchItem(isNFTEnabled: Boolean, onCheckedNFTChange: (Boolean) -> Unit) =
WalletSettingsItemUM.WithSwitch(
id = "notifications",
title = resourceReference(id = R.string.wallet_settings_push_notifications_title),
isChecked = isNFTEnabled,
onCheckedChange = onCheckedNFTChange,
)
private fun buildNotificationsDescriptionItem(onDescriptionClick: () -> Unit) =
WalletSettingsItemUM.DescriptionWithMore(
id = "notifications_description",
text = resourceReference(id = R.string.wallet_settings_push_notifications_description),
more = resourceReference(id = R.string.push_notifications_more_info),
onClick = onDescriptionClick,
)
@Suppress("LongParameterList") @Suppress("LongParameterList")
private fun buildCardItem( private fun buildCardItem(
userWalletId: UserWalletId, userWalletId: UserWalletId,