diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1d52735ce7..536e770195 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -55,6 +55,7 @@ + private lateinit var appThemeModeFlow: SharedFlow // TODO: fixme: inject through DI private val intentProcessor: IntentProcessor = IntentProcessor() @@ -172,9 +175,10 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac private val onActivityResultCallbacks = mutableListOf() override fun onCreate(savedInstanceState: Bundle?) { - val splashScreen = installSplashScreen() + // We need to call it before onCreate to prevent unnecessary activity recreation + installAppTheme() - installAppTheme() // We need to call it before onCreate to prevent unnecessary activity recreation + val splashScreen = installSplashScreen() super.onCreate(savedInstanceState) @@ -218,7 +222,7 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac private fun installActivityDependencies() { store.dispatch(NavigationAction.ActivityCreated(WeakReference(this))) - cardSdkLifecycleObserver.onCreate(context = this) + cardSdkOwner.register(activity = this) tangemSdkManager = injectedTangemSdkManager appStateHolder.tangemSdkManager = tangemSdkManager backupService = BackupService.init(cardSdkConfigRepository.sdk, this) @@ -248,14 +252,13 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac private fun installAppTheme() { appThemeModeFlow = createAppThemeModeFlow() - val mode = runBlocking { appThemeModeFlow.filterNotNull().first() } + val mode = runBlocking { appThemeModeFlow.first() } updateAppTheme(mode) } private fun observeAppThemeModeUpdates() { appThemeModeFlow - .filterNotNull() .flowWithLifecycle(lifecycle) .onEach(::updateAppTheme) .launchIn(lifecycleScope) @@ -273,15 +276,17 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } - private fun createAppThemeModeFlow(): SharedFlow { + private fun createAppThemeModeFlow(): SharedFlow { val tangemApplication = application as TangemApplication return tangemApplication.getAppThemeModeUseCase() + .filterNotNull() + .distinctUntilChanged() .map { maybeMode -> maybeMode.getOrElse { AppThemeMode.DEFAULT } } .shareIn( - scope = lifecycleScope + Dispatchers.IO, + scope = lifecycleScope, started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5_000), ) } @@ -327,7 +332,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac override fun onDestroy() { store.dispatch(NavigationAction.ActivityDestroyed(WeakReference(this))) intentProcessor.removeAll() - cardSdkLifecycleObserver.onDestroy(this) super.onDestroy() } @@ -338,9 +342,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } private fun updateAppTheme(appThemeMode: AppThemeMode) { - MutableAppThemeModeHolder.value = appThemeMode - MutableAppThemeModeHolder.isDarkThemeActive = isDarkTheme() - val mode = when (appThemeMode) { AppThemeMode.FORCE_DARK -> AppCompatDelegate.MODE_NIGHT_YES AppThemeMode.FORCE_LIGHT -> AppCompatDelegate.MODE_NIGHT_NO @@ -348,7 +349,32 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } setDefaultNightMode(mode) - delegate.localNightMode = mode + + MutableAppThemeModeHolder.value = appThemeMode + MutableAppThemeModeHolder.isDarkThemeActive = isDarkTheme() + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + + /* + * We need to manually change the background color of the activity when the UI mode changes to prevent + * flickering when navigating between fragments. + * + + * `android:configChanges="uiMode"` is set in the manifest. + * */ + updateAppBackground() + } + + private fun updateAppBackground() { + val backgroundColor = if (isDarkTheme()) { + TangemColorPalette.Dark6 + } else { + TangemColorPalette.White + } + + findViewById(R.id.fragment_container).setBackgroundColor(backgroundColor.toArgb()) } private fun isDarkTheme(): Boolean { @@ -363,12 +389,6 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac override fun onNewIntent(intent: Intent?) { super.onNewIntent(intent) - /* - * FIXME: Test functionality. TangemSdk is null on some devices when HomeAction.Read is called - * inside IntentHandler. - */ - cardSdkLifecycleObserver.onCreate(context = this) - lifecycleScope.launch { intentProcessor.handleIntent(intent, true) } @@ -450,15 +470,16 @@ class MainActivity : AppCompatActivity(), SnackbarHandler, ActivityResultCallbac } private fun navigateToInitialScreenIfNeeded(intentWhichStartedActivity: Intent?) { - val backStackIsEmpty = supportFragmentManager.backStackEntryCount == 0 + val backStack = store.state.navigationState.backStack + val isOnInitialScreen = backStack.all { it == AppScreen.Welcome || it == AppScreen.Home } val isNotScannedBefore = store.state.globalState.scanResponse == null val isOnboardingServiceNotActive = !store.state.globalState.onboardingState.onboardingStarted when { - !backStackIsEmpty && isNotScannedBefore && isOnboardingServiceNotActive -> { + !isOnInitialScreen && isNotScannedBefore && isOnboardingServiceNotActive -> { navigateToInitialScreen(intentWhichStartedActivity) } - backStackIsEmpty -> { + backStack.isEmpty() -> { navigateToInitialScreen(intentWhichStartedActivity) } else -> Unit diff --git a/app/src/main/java/com/tangem/tap/TangemApplication.kt b/app/src/main/java/com/tangem/tap/TangemApplication.kt index 73e4bcd76f..d9b90a7bb9 100644 --- a/app/src/main/java/com/tangem/tap/TangemApplication.kt +++ b/app/src/main/java/com/tangem/tap/TangemApplication.kt @@ -353,6 +353,7 @@ abstract class TangemApplication : Application(), ImageLoaderFactory { Log.Level.Network, Log.Level.Error, Log.Level.Biometric, + Log.Level.Info, ) return TangemLogCollector(logLevels, LogFormat.StairsFormatter()) } diff --git a/app/src/main/java/com/tangem/tap/di/data/CardSDKLoggerModule.kt b/app/src/main/java/com/tangem/tap/di/data/CardSDKLoggerModule.kt index 6da93efeb4..9b326b64c7 100644 --- a/app/src/main/java/com/tangem/tap/di/data/CardSDKLoggerModule.kt +++ b/app/src/main/java/com/tangem/tap/di/data/CardSDKLoggerModule.kt @@ -33,6 +33,7 @@ internal object CardSDKLoggerModule { Log.Level.Network, Log.Level.Error, Log.Level.Biometric, + Log.Level.Info, ) return TangemCardSDKLogger( diff --git a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt index 1a6c5ff684..3d1b711ccf 100644 --- a/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt +++ b/app/src/main/java/com/tangem/tap/di/domain/CardDomainModule.kt @@ -14,15 +14,15 @@ import com.tangem.tap.domain.sdk.TangemSdkManager import dagger.Module import dagger.Provides import dagger.hilt.InstallIn -import dagger.hilt.android.components.ViewModelComponent -import dagger.hilt.android.scopes.ViewModelScoped +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton @Module -@InstallIn(ViewModelComponent::class) +@InstallIn(SingletonComponent::class) internal object CardDomainModule { @Provides - @ViewModelScoped + @Singleton fun provideGetBiometricsStatusUseCase( cardSdkConfigRepository: CardSdkConfigRepository, ): GetBiometricsStatusUseCase { @@ -30,7 +30,7 @@ internal object CardDomainModule { } @Provides - @ViewModelScoped + @Singleton fun provideSetAccessCodeRequestPolicyUseCase( cardSdkConfigRepository: CardSdkConfigRepository, ): SetAccessCodeRequestPolicyUseCase { @@ -38,35 +38,35 @@ internal object CardDomainModule { } @Provides - @ViewModelScoped + @Singleton fun provideWasWalletAlreadySignedHashesConfirmedUseCase(cardRepository: CardRepository): WasCardScannedUseCase { return WasCardScannedUseCase(cardRepository = cardRepository) } @Provides - @ViewModelScoped + @Singleton fun provideSetCardWasScannedUseCase(cardRepository: CardRepository): SetCardWasScannedUseCase { return SetCardWasScannedUseCase(cardRepository = cardRepository) } @Provides - @ViewModelScoped + @Singleton fun provideIsDemoCardUseCase(): IsDemoCardUseCase = IsDemoCardUseCase(config = DemoConfig()) @Provides - @ViewModelScoped + @Singleton fun provideDerivePublicKeysUseCase(derivationsRepository: DerivationsRepository): DerivePublicKeysUseCase { return DerivePublicKeysUseCase(derivationsRepository = derivationsRepository) } @Provides - @ViewModelScoped + @Singleton fun provideIsNeedToBackupUseCase(userWalletsListManager: UserWalletsListManager): IsNeedToBackupUseCase { return IsNeedToBackupUseCase(userWalletsListManager = userWalletsListManager) } @Provides - @ViewModelScoped + @Singleton fun provideGetExtendedPublicKeyForCurrencyUseCase( derivationsRepository: DerivationsRepository, ): GetExtendedPublicKeyForCurrencyUseCase { @@ -74,13 +74,13 @@ internal object CardDomainModule { } @Provides - @ViewModelScoped + @Singleton fun provideDeleteSavedAccessCodesUseCase(tangemSdkManager: TangemSdkManager): DeleteSavedAccessCodesUseCase { return DefaultDeleteSavedAccessCodesUseCase(tangemSdkManager) } @Provides - @ViewModelScoped + @Singleton fun provideResetCardUseCase(tangemSdkManager: TangemSdkManager): ResetCardUseCase { return DefaultResetCardUseCase(tangemSdkManager) } diff --git a/app/src/main/java/com/tangem/tap/domain/tasks/UserWalletIdPreflightReadFilter.kt b/app/src/main/java/com/tangem/tap/domain/tasks/UserWalletIdPreflightReadFilter.kt index b0d2d24119..d0f1c97dda 100644 --- a/app/src/main/java/com/tangem/tap/domain/tasks/UserWalletIdPreflightReadFilter.kt +++ b/app/src/main/java/com/tangem/tap/domain/tasks/UserWalletIdPreflightReadFilter.kt @@ -18,7 +18,7 @@ class UserWalletIdPreflightReadFilter(private val expectedUserWalletId: UserWall override fun onCardRead(card: Card, environment: SessionEnvironment) = Unit override fun onFullCardRead(card: Card, environment: SessionEnvironment) { - val actualUserWalletId = UserWalletIdBuilder.card(card = CardDTO(card)).build() + val actualUserWalletId = UserWalletIdBuilder.card(card = CardDTO(card)).build() ?: return if (expectedUserWalletId != actualUserWalletId) throw TangemSdkError.WalletNotFound() } diff --git a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt index 52e9be2c6d..199b92bf71 100644 --- a/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt +++ b/app/src/main/java/com/tangem/tap/domain/userWalletList/implementation/BiometricUserWalletsListManager.kt @@ -152,19 +152,29 @@ internal class BiometricUserWalletsListManager( return CompletionResult.Success(Unit) } - changeSelectedUserWalletIdIfNeeded(idsToRemove) + if (idsToRemove.size == state.value.userWallets.size) { + return clear() + } return sensitiveInformationRepository.delete(idsToRemove) .flatMap { publicInformationRepository.delete(idsToRemove) } .map { keysRepository.delete(idsToRemove) } .map { state.update { prevState -> - val newUserWallets = prevState.userWallets.filter { it.walletId !in idsToRemove } + val remainingWallets = prevState.userWallets.filter { it.walletId !in idsToRemove } + + val isSelectedWalletDeleted = prevState.selectedUserWalletId in idsToRemove + val newSelectedUserWallet = findOrSetSelectedWallet( + prevSelectedWalletId = prevState.selectedUserWalletId, + userWallets = remainingWallets, + ignorePrevSelectedWallet = isSelectedWalletDeleted, + ) prevState.copy( encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in idsToRemove }, - userWallets = newUserWallets, - isLocked = newUserWallets.any { it.isLocked }, + userWallets = remainingWallets, + isLocked = remainingWallets.any { it.isLocked }, + selectedUserWalletId = newSelectedUserWallet?.walletId, ) } } @@ -173,10 +183,10 @@ internal class BiometricUserWalletsListManager( override suspend fun clear(): CompletionResult { return sensitiveInformationRepository.clear() .flatMap { publicInformationRepository.clear() } - .map { keysRepository.clear() } .map { + keysRepository.clear() selectedUserWalletRepository.set(null) - lock() + state.value = State() } } @@ -301,47 +311,24 @@ internal class BiometricUserWalletsListManager( private fun findOrSetSelectedWallet( prevSelectedWalletId: UserWalletId?, userWallets: List, + ignorePrevSelectedWallet: Boolean = false, ): UserWallet? { - val selectedWalletId = prevSelectedWalletId ?: selectedUserWalletRepository.get() - var possibleSelectedUserWallet = findSelectedUserWallet(userWallets, selectedWalletId) + var possibleSelectedUserWallet: UserWallet? = null + + if (!ignorePrevSelectedWallet) { + val selectedWalletId = prevSelectedWalletId ?: selectedUserWalletRepository.get() + possibleSelectedUserWallet = findSelectedUserWallet(userWallets, selectedWalletId) + } if (possibleSelectedUserWallet == null || possibleSelectedUserWallet.isLocked) { possibleSelectedUserWallet = userWallets.firstOrNull { !it.isLocked } ?: userWallets.firstOrNull() - - if (possibleSelectedUserWallet != null) { - selectedUserWalletRepository.set(possibleSelectedUserWallet.walletId) - } } + selectedUserWalletRepository.set(possibleSelectedUserWallet?.walletId) + return possibleSelectedUserWallet } - private fun changeSelectedUserWalletIdIfNeeded(walletsIdsToRemove: List) { - val remainingWallets = state.value.userWallets.filter { - it.walletId !in walletsIdsToRemove - } - val selectedWallet = findSelectedUserWallet() - when { - remainingWallets.isEmpty() -> { - state.update { prevState -> - prevState.copy( - selectedUserWalletId = null, - ) - } - selectedUserWalletRepository.set(null) - } - !remainingWallets.contains(selectedWallet) -> { - val newSelectedWallet = remainingWallets.firstOrNull { !it.isLocked } - state.update { prevState -> - prevState.copy( - selectedUserWalletId = newSelectedWallet?.walletId, - ) - } - selectedUserWalletRepository.set(newSelectedWallet?.walletId) - } - } - } - private fun findSelectedUserWallet( userWallets: List = state.value.userWallets, selectedUserWalletId: UserWalletId? = state.value.selectedUserWalletId, diff --git a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt index fd32427821..07f7857126 100644 --- a/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt +++ b/app/src/main/java/com/tangem/tap/features/details/ui/resetcard/ResetCardViewModel.kt @@ -214,9 +214,10 @@ internal class ResetCardViewModel @Inject constructor( return when (val status = card.backupStatus) { is CardDTO.BackupStatus.Active -> status.cardCount - is CardDTO.BackupStatus.CardLinked -> status.cardCount - is CardDTO.BackupStatus.NoBackup -> 0 - null -> 0 // Multi-currency wallet without backup function. Example, 4.12 + is CardDTO.BackupStatus.CardLinked, + is CardDTO.BackupStatus.NoBackup, + null, + -> 0 } } diff --git a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt index 5aca214e93..7fb08b3681 100644 --- a/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt +++ b/app/src/main/java/com/tangem/tap/features/welcome/ui/components/WelcomeScreenContent.kt @@ -13,8 +13,8 @@ import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import com.tangem.core.ui.components.* -import com.tangem.core.ui.res.TangemThemePreview import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview import com.tangem.wallet.R @Suppress("LongMethod") diff --git a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt index b77078a426..c7c73612f8 100644 --- a/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt +++ b/core/analytics/models/src/main/java/com/tangem/core/analytics/models/AnalyticsParam.kt @@ -64,6 +64,7 @@ sealed class AnalyticsParam { data object Send : ScreensSources("Send") data object Intro : ScreensSources("Introduction") data object MyWallets : ScreensSources("My Wallets") + data object Token : ScreensSources("Token") } sealed class TxSentFrom(val value: String) { diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt index acbd56ec03..8b224858c1 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeProvider.kt @@ -31,4 +31,7 @@ enum class ExchangeProviderType { @Json(name = "cex") CEX, + + @Json(name = "dex-bridge") + DEX_BRIDGE, } \ No newline at end of file diff --git a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt index a269f45c23..dc786b02de 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/api/express/models/response/ExchangeStatusResponse.kt @@ -23,34 +23,43 @@ data class ExchangeStatusResponse( enum class ExchangeStatus { @Json(name = "new") - NEW, + New, @Json(name = "waiting") - WAITING, + Waiting, @Json(name = "confirming") - CONFIRMING, + Confirming, @Json(name = "exchanging") - EXCHANGING, + Exchanging, @Json(name = "sending") - SENDING, + Sending, @Json(name = "finished") - FINISHED, + Finished, @Json(name = "failed") - FAILED, + Failed, @Json(name = "refunded") - REFUNDED, + Refunded, @Json(name = "verifying") - VERIFYING, + Verifying, @Json(name = "expired") - CANCELLED, + Cancelled, + + @Json(name = "waiting-tx-hash") + WaitingTxHash, + + @Json(name = "tx-failed") + TxFailed, + + @Json(name = "unknown") + Unknown, } data class ExchangeStatusError( diff --git a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt index e069422752..52b037fd6d 100644 --- a/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt +++ b/core/datasource/src/main/java/com/tangem/datasource/local/preferences/PreferencesKeys.kt @@ -53,14 +53,6 @@ object PreferencesKeys { val LAST_SWAPPED_CRYPTOCURRENCY_ID_KEY by lazy { stringPreferencesKey(name = "lastSwappedCryptoCurrency") } - val IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY by lazy { - booleanPreferencesKey(name = "isWalletSwapPromoChangellyShown") - } - - val IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY by lazy { - booleanPreferencesKey(name = "isTokenSwapPromoChangellyShown") - } - val IS_WALLET_TRAVALA_PROMO_SHOWN_KEY by lazy { booleanPreferencesKey(name = "isWalletTravalaPromoShown") } @@ -93,6 +85,14 @@ object PreferencesKeys { val IS_WALLET_NAMES_MIGRATION_DONE_KEY by lazy { booleanPreferencesKey(name = "isWalletNamesMigrationDone") } + val IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY by lazy { + booleanPreferencesKey(name = "isWalletSwapPromoOkxShown") + } + + val IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY by lazy { + booleanPreferencesKey(name = "isTokenSwapPromoOkxShown") + } + fun getStart2CoinTOSAcceptedKey(region: String?) = booleanPreferencesKey(name = "start2Coin_tos_accepted_$region") } diff --git a/core/res/src/main/res/values-ru/strings.xml b/core/res/src/main/res/values-ru/strings.xml index 0c2f9e38e2..453bf3dd9b 100644 --- a/core/res/src/main/res/values-ru/strings.xml +++ b/core/res/src/main/res/values-ru/strings.xml @@ -225,6 +225,7 @@ Данные провайдера. Сумма к получению может измениться в зависимости от рыночных условий. Статус обмена Требуется верификация + Ожидание хеша транзакции Список токенов в вашем кошельке Получение наилучших курсов... Плавающая ставка @@ -549,6 +550,8 @@ Встречайте Tangem Обменивайте, покупайте NFT, получайте займы и делайте вклады в более чем 100 различных децентрализованных сервисах Поддержка Web 3.0 + Обменивайте больше токенов по лучшим курсам прямо в вашем кошельке. + Новый провайдер обмена! В сумму включено: \n• комиссия провайдера сервиса\n• комиссия сети за отправку %s от биржи обратно на адрес пользователя В сумму включена комиссия провайдера сервиса. Комиссии diff --git a/core/res/src/main/res/values/strings.xml b/core/res/src/main/res/values/strings.xml index 0e761dde1b..c712fdab72 100644 --- a/core/res/src/main/res/values/strings.xml +++ b/core/res/src/main/res/values/strings.xml @@ -224,6 +224,7 @@ Provider-sourced data. Estimated amount subject to change due to market conditions. Exchange status Verification required + Awaiting transaction hash List of all tokens added to your wallet Fetching best rates... Floating rate @@ -542,6 +543,8 @@ Meet Tangem Exchange, buy NFT\'s, make loans and deposits in more than 100 different decentralized services Web 3.0 Compatible + Exchange more tokens at better rates directly in your wallet. + New Swap Provider Available! The amount includes:\n• service provider\'s fee\n• network fee for sending %s from the exchange back to the user\'s address. The amount includes the service provider\'s fee. Fees diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/notifications/OkxPromoNotification.kt b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/OkxPromoNotification.kt new file mode 100644 index 0000000000..9d6d403946 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/components/notifications/OkxPromoNotification.kt @@ -0,0 +1,163 @@ +package com.tangem.core.ui.components.notifications + +import android.content.res.Configuration +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.material.ripple.rememberRipple +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.graphics.vector.rememberVectorPainter +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.tooling.preview.PreviewParameter +import androidx.compose.ui.tooling.preview.PreviewParameterProvider +import com.tangem.core.ui.R +import com.tangem.core.ui.components.buttons.common.TangemButton +import com.tangem.core.ui.components.buttons.common.TangemButtonColors +import com.tangem.core.ui.components.buttons.common.TangemButtonIconPosition +import com.tangem.core.ui.extensions.resolveReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.res.LocalIsInDarkTheme +import com.tangem.core.ui.res.TangemColorPalette.Dark6 +import com.tangem.core.ui.res.TangemColorPalette.Light4 +import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.res.TangemThemePreview + +private val OxkPromoColor = Color(0xFFBCFF2F) + +@Composable +fun OkxPromoNotification(config: NotificationConfig, modifier: Modifier = Modifier) { + Column( + modifier + .clip(TangemTheme.shapes.roundedCornersXMedium) + .background(Dark6), + ) { + Content(config = config) + Button(config = config) + } +} + +@Composable +private fun Content(config: NotificationConfig) { + Row { + Icon( + painter = rememberVectorPainter(image = ImageVector.vectorResource(id = R.drawable.img_okx_dex_logo)), + contentDescription = null, + tint = TangemTheme.colors.icon.constant, + modifier = Modifier + .padding(start = TangemTheme.dimens.spacing12) + .align(Alignment.CenterVertically), + ) + Column( + verticalArrangement = Arrangement.spacedBy(TangemTheme.dimens.spacing4), + modifier = Modifier + .weight(1f) + .padding(TangemTheme.dimens.spacing12), + ) { + Text( + text = config.title.resolveReference(), + style = TangemTheme.typography.button, + color = OxkPromoColor, + ) + Text( + text = config.subtitle.resolveReference(), + style = TangemTheme.typography.caption2, + color = TangemTheme.colors.text.constantWhite, + ) + } + config.onCloseClick?.let { + Icon( + painter = rememberVectorPainter( + image = ImageVector.vectorResource(R.drawable.ic_close_24), + ), + contentDescription = null, + tint = TangemTheme.colors.icon.constant, + modifier = + Modifier + .padding( + top = TangemTheme.dimens.spacing8, + end = TangemTheme.dimens.spacing8, + ) + .size(TangemTheme.dimens.size20) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(radius = TangemTheme.dimens.radius10), + onClick = it, + ), + ) + } + } +} + +@Composable +private fun Button(config: NotificationConfig) { + val button = config.buttonsState as? NotificationConfig.ButtonsState.SecondaryButtonConfig + + button?.let { + val isDarkMode = LocalIsInDarkTheme.current + TangemButton( + text = button.text.resolveReference(), + icon = TangemButtonIconPosition.Start(button.iconResId ?: R.drawable.ic_exchange_vertical_24), + onClick = button.onClick, + colors = TangemButtonColors( + backgroundColor = if (isDarkMode) Light4 else TangemTheme.colors.button.secondary, + contentColor = Dark6, + disabledBackgroundColor = TangemTheme.colors.button.disabled, + disabledContentColor = TangemTheme.colors.text.disabled, + ), + enabled = true, + showProgress = false, + modifier = Modifier + .fillMaxWidth() + .padding( + start = TangemTheme.dimens.spacing12, + end = TangemTheme.dimens.spacing12, + bottom = TangemTheme.dimens.spacing12, + ), + ) + } +} + +// region Preview +@Preview(widthDp = 360) +@Preview(widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES) +@Composable +private fun OkxPromoNotification_Preview( + @PreviewParameter(OkxPromoNotificationPreviewProvider::class) data: NotificationConfig, +) { + TangemThemePreview { + OkxPromoNotification(data) + } +} + +private class OkxPromoNotificationPreviewProvider : PreviewParameterProvider { + override val values: Sequence + get() = sequenceOf( + NotificationConfig( + title = resourceReference(R.string.swap_promo_title), + subtitle = resourceReference(R.string.swap_promo_text), + iconResId = R.drawable.img_okx_dex_logo, + onCloseClick = {}, + ), + NotificationConfig( + title = resourceReference(R.string.swap_promo_title), + subtitle = resourceReference(R.string.swap_promo_text), + iconResId = R.drawable.img_okx_dex_logo, + onCloseClick = {}, + buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( + text = resourceReference(R.string.token_swap_promotion_button), + onClick = {}, + ), + ), + ) +} +// endregion \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt index 451207be18..2a78787608 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/res/TangemColors.kt @@ -101,6 +101,7 @@ class TangemColors internal constructor( private set var attention by mutableStateOf(attention) private set + var constant by mutableStateOf(constant) fun update(other: Icon) { primary1 = other.primary1 @@ -111,6 +112,7 @@ class TangemColors internal constructor( accent = other.accent warning = other.warning attention = other.attention + constant = other.constant } } diff --git a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeFragment.kt b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeFragment.kt index fb811871af..c60bf1ba7b 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeFragment.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/screen/ComposeFragment.kt @@ -1,5 +1,6 @@ package com.tangem.core.ui.screen +import android.content.res.Configuration import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -22,6 +23,18 @@ abstract class ComposeFragment : Fragment(), ComposeScreen { } } + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + + /* + * We need to manually dispatch configuration changes to the Compose view. + * + + * `android:configChanges="uiMode"` is set in the manifest. + * */ + view?.dispatchConfigurationChanged(newConfig) + } + /** * Inflates transitions for the fragment. Override this method to customize * enter and exit transitions for the fragment. diff --git a/core/ui/src/main/res/drawable/img_okx_dex_logo.xml b/core/ui/src/main/res/drawable/img_okx_dex_logo.xml new file mode 100644 index 0000000000..3a4ba80759 --- /dev/null +++ b/core/ui/src/main/res/drawable/img_okx_dex_logo.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/data/card/src/main/java/com/tangem/data/card/di/CardSdkModule.kt b/data/card/src/main/java/com/tangem/data/card/di/CardSdkModule.kt index c88f2dbfc2..891c418cb1 100644 --- a/data/card/src/main/java/com/tangem/data/card/di/CardSdkModule.kt +++ b/data/card/src/main/java/com/tangem/data/card/di/CardSdkModule.kt @@ -1,6 +1,6 @@ package com.tangem.data.card.di -import com.tangem.data.card.sdk.CardSdkLifecycleObserver +import com.tangem.data.card.sdk.CardSdkOwner import com.tangem.data.card.sdk.CardSdkProvider import com.tangem.data.card.sdk.DefaultCardSdkProvider import dagger.Binds @@ -19,5 +19,5 @@ internal interface CardSdkModule { @Binds @Singleton - fun providerCardSdkLifecycleObserver(defaultCardSdkProvider: DefaultCardSdkProvider): CardSdkLifecycleObserver + fun providerCardSdkLifecycleObserver(defaultCardSdkProvider: DefaultCardSdkProvider): CardSdkOwner } \ No newline at end of file diff --git a/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkLifecycleObserver.kt b/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkLifecycleObserver.kt deleted file mode 100644 index 212ff3a8bf..0000000000 --- a/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkLifecycleObserver.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.tangem.data.card.sdk - -import android.content.Context - -/** - * Lifecycle observer for creating Card SDK instance - * -[REDACTED_AUTHOR] - */ -interface CardSdkLifecycleObserver { - - /** Callback of creating activity [context] */ - fun onCreate(context: Context) - - /** Callback of destroying activity [context] */ - fun onDestroy(context: Context) -} \ No newline at end of file diff --git a/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkOwner.kt b/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkOwner.kt new file mode 100644 index 0000000000..a5654ef6ec --- /dev/null +++ b/data/card/src/main/java/com/tangem/data/card/sdk/CardSdkOwner.kt @@ -0,0 +1,13 @@ +package com.tangem.data.card.sdk + +import androidx.fragment.app.FragmentActivity + +/** + * Lifecycle observer for creating Card SDK instance + * +[REDACTED_AUTHOR] + */ +interface CardSdkOwner { + + fun register(activity: FragmentActivity) +} \ No newline at end of file diff --git a/data/card/src/main/java/com/tangem/data/card/sdk/DefaultCardSdkProvider.kt b/data/card/src/main/java/com/tangem/data/card/sdk/DefaultCardSdkProvider.kt index d8427a907f..cdc3c2e305 100644 --- a/data/card/src/main/java/com/tangem/data/card/sdk/DefaultCardSdkProvider.kt +++ b/data/card/src/main/java/com/tangem/data/card/sdk/DefaultCardSdkProvider.kt @@ -1,12 +1,21 @@ package com.tangem.data.card.sdk -import android.content.Context import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.DefaultLifecycleObserver +import androidx.lifecycle.LifecycleObserver +import androidx.lifecycle.LifecycleOwner +import com.tangem.Log import com.tangem.TangemSdk import com.tangem.common.CardFilter +import com.tangem.common.authentication.AuthenticationManager import com.tangem.common.card.FirmwareVersion import com.tangem.common.core.Config -import com.tangem.sdk.extensions.initWithBiometrics +import com.tangem.common.services.secure.SecureStorage +import com.tangem.crypto.bip39.Wordlist +import com.tangem.sdk.DefaultSessionViewDelegate +import com.tangem.sdk.extensions.* +import com.tangem.sdk.nfc.NfcManager +import com.tangem.sdk.storage.create import javax.inject.Inject import javax.inject.Singleton @@ -16,31 +25,94 @@ import javax.inject.Singleton [REDACTED_AUTHOR] */ @Singleton -internal class DefaultCardSdkProvider @Inject constructor() : CardSdkProvider, CardSdkLifecycleObserver { +internal class DefaultCardSdkProvider @Inject constructor() : CardSdkProvider, CardSdkOwner { override val sdk: TangemSdk - get() = requireNotNull(value = _sdk) { "Impossible to get the TangemSdk when activity is destroyed" } + get() = requireNotNull(value = holder?.sdk) { + "Impossible to get the TangemSdk when activity is destroyed" + } - private var _sdk: TangemSdk? = null + private val observer: LifecycleObserver = Observer() - override fun onCreate(context: Context) { - _sdk = TangemSdk.initWithBiometrics(activity = context as FragmentActivity, config = config) + private var holder: Holder? = null + + override fun register(activity: FragmentActivity) { + Log.info { "Tangem SDK owner registered" } + + if (holder != null) { + unsubscribeAndCleanup() + } + + initialize(activity) + + activity.lifecycle.addObserver(observer) } - override fun onDestroy(context: Context) { - // Commented out to prevent crash on getting sdk when it's null. - // FIXME: We still should find the real cause and fix it properly. - // idea: pass everywhere DefaultCardSdkProvider instead sdk to reach lazy access to sdk property - // _sdk = null + private fun initialize(activity: FragmentActivity) { + val secureStorage = SecureStorage.create(activity) + val nfcManager = TangemSdk.initNfcManager(activity) + val authenticationManager = TangemSdk.initAuthenticationManager(activity) + val keystoreManager = TangemSdk.initKeystoreManager(authenticationManager, secureStorage) + + val viewDelegate = DefaultSessionViewDelegate(nfcManager, activity) + viewDelegate.sdkConfig = config + + val sdk = TangemSdk( + reader = nfcManager.reader, + viewDelegate = viewDelegate, + secureStorage = secureStorage, + authenticationManager = authenticationManager, + keystoreManager = keystoreManager, + wordlist = Wordlist.getWordlist(activity), + config = config, + ) + + holder = Holder( + activity = activity, + nfcManager = nfcManager, + authenticationManager = authenticationManager, + sdk = sdk, + ) + + Log.info { "Tangem SDK initialized" } } + private fun unsubscribeAndCleanup() { + with(receiver = holder ?: return) { + nfcManager.unsubscribe(activity) + authenticationManager.unsubscribe(activity) + + activity.lifecycle.removeObserver(observer) + } + + holder = null + + Log.info { "Tangem SDK unsubscribed and cleaned up" } + } + + inner class Observer : DefaultLifecycleObserver { + + override fun onDestroy(owner: LifecycleOwner) { + Log.info { "Tangem SDK owner destroyed" } + + unsubscribeAndCleanup() + } + } + + data class Holder( + val activity: FragmentActivity, + val sdk: TangemSdk, + val nfcManager: NfcManager, + val authenticationManager: AuthenticationManager, + ) + private companion object { val config = Config( linkedTerminal = true, allowUntrustedCards = true, filter = CardFilter( - allowedCardTypes = FirmwareVersion.FirmwareType.values().toList(), + allowedCardTypes = FirmwareVersion.FirmwareType.entries.toList(), maxFirmwareVersion = FirmwareVersion(major = 6, minor = 33), batchIdFilter = CardFilter.Companion.ItemFilter.Deny( items = setOf("0027", "0030", "0031", "0035"), diff --git a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt index a92a4f5eb9..94a184fb73 100644 --- a/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt +++ b/data/promo/src/main/java/com/tangem/data/promo/DefaultPromoRepository.kt @@ -32,8 +32,18 @@ internal class DefaultPromoRepository( }.getOrNull() } + override suspend fun getOkxPromoBanner(): PromoBanner? { + return runCatching(dispatchers.io) { + promoResponseConverter.convert( + tangemApi.getPromotionInfo(OKX) + .getOrThrow(), + ) + }.getOrNull() + } + private companion object { private const val CHANGELLY_NAME = "changelly" private const val TRAVALA = "travala" + private const val OKX = "okx" } } \ No newline at end of file diff --git a/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt b/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt index 80ba44157d..ce6d3fa236 100644 --- a/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt +++ b/data/settings/src/main/java/com/tangem/data/settings/DefaultPromoSettingsRepository.kt @@ -1,8 +1,8 @@ package com.tangem.data.settings import com.tangem.datasource.local.preferences.AppPreferencesStore -import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY -import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY +import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY import com.tangem.datasource.local.preferences.PreferencesKeys.IS_WALLET_TRAVALA_PROMO_SHOWN_KEY import com.tangem.datasource.local.preferences.utils.get import com.tangem.datasource.local.preferences.utils.store @@ -16,23 +16,23 @@ class DefaultPromoSettingsRepository( private val appPreferencesStore: AppPreferencesStore, ) : PromoSettingsRepository { override fun isReadyToShowWalletSwapPromo(): Flow { - return appPreferencesStore.get(IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY, true) + return appPreferencesStore.get(IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY, true) } override fun isReadyToShowTokenSwapPromo(): Flow { - return appPreferencesStore.get(IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY, true) + return appPreferencesStore.get(IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY, true) } override suspend fun setNeverToShowWalletSwapPromo() { appPreferencesStore.store( - key = IS_WALLET_SWAP_PROMO_CHANGELLY_SHOW_KEY, + key = IS_WALLET_SWAP_PROMO_OKX_SHOW_KEY, value = false, ) } override suspend fun setNeverToShowTokenSwapPromo() { appPreferencesStore.store( - key = IS_TOKEN_SWAP_PROMO_CHANGELLY_SHOW_KEY, + key = IS_TOKEN_SWAP_PROMO_OKX_SHOW_KEY, value = false, ) } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt index 062692cd06..f31d0e4b5f 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/di/TokensDataModule.kt @@ -87,8 +87,11 @@ internal object TokensDataModule { @Provides @Singleton - fun provideDefaultMarketCoinsRepository(assetsStore: AssetsStore): MarketCryptoCurrencyRepository { - return DefaultMarketCryptoCurrencyRepository(assetsStore) + fun provideDefaultMarketCoinsRepository( + assetsStore: AssetsStore, + coroutineDispatcherProvider: CoroutineDispatcherProvider, + ): MarketCryptoCurrencyRepository { + return DefaultMarketCryptoCurrencyRepository(assetsStore, coroutineDispatcherProvider) } @Provides diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt index 89bb9aca17..9459984032 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultCurrenciesRepository.kt @@ -302,26 +302,28 @@ internal class DefaultCurrenciesRepository( networkId: Network.ID, derivationPath: Network.DerivationPath, ): CryptoCurrency.Coin { - val userWallet = getUserWallet(userWalletId) - ensureIsCorrectUserWallet(userWallet = userWallet, isMultiCurrencyWalletExpected = true) + return withContext(dispatchers.io) { + val userWallet = getUserWallet(userWalletId) + ensureIsCorrectUserWallet(userWallet = userWallet, isMultiCurrencyWalletExpected = true) - fetchTokensIfCacheExpired(userWallet = userWallet, refresh = false) + fetchTokensIfCacheExpired(userWallet = userWallet, refresh = false) - val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) { - "Unable to find tokens response for user wallet with provided ID: $userWalletId" + val storedTokens = requireNotNull(userTokensStore.getSyncOrNull(userWallet.walletId)) { + "Unable to find tokens response for user wallet with provided ID: $userWalletId" + } + val blockchain = Blockchain.fromId(networkId.value) + val blockchainNetworkId = blockchain.toNetworkId() + val coinId = blockchain.toCoinId() + + val storedCoin = storedTokens.tokens + .find { + it.networkId == blockchainNetworkId && it.id == coinId && it.derivationPath == derivationPath.value + } ?: error("Coin in this network $networkId not found") + + val coin = responseCurrenciesFactory.createCurrency(storedCoin, userWallet.scanResponse) + + coin as? CryptoCurrency.Coin ?: error("Unable to create currency") } - val blockchain = Blockchain.fromId(networkId.value) - val blockchainNetworkId = blockchain.toNetworkId() - val coinId = blockchain.toCoinId() - - val storedCoin = storedTokens.tokens - .find { - it.networkId == blockchainNetworkId && it.id == coinId && it.derivationPath == derivationPath.value - } ?: error("Coin in this network $networkId not found") - - val coin = responseCurrenciesFactory.createCurrency(storedCoin, userWallet.scanResponse) - - return coin as? CryptoCurrency.Coin ?: error("Unable to create currency") } override fun isTokensGrouped(userWalletId: UserWalletId): Flow { @@ -382,29 +384,31 @@ internal class DefaultCurrenciesRepository( } override suspend fun getFeePaidCurrency(userWalletId: UserWalletId, currency: CryptoCurrency): FeePaidCurrency { - val blockchain = Blockchain.fromId(currency.network.id.value) - return when (val feePaidCurrency = blockchain.feePaidCurrency()) { - FeePaidSdkCurrency.Coin -> FeePaidCurrency.Coin - FeePaidSdkCurrency.SameCurrency -> FeePaidCurrency.SameCurrency - is FeePaidSdkCurrency.Token -> { - val balance = walletManagersFacade.tokenBalance( - userWalletId = userWalletId, - network = currency.network, - name = feePaidCurrency.token.name, - symbol = feePaidCurrency.token.symbol, - contractAddress = feePaidCurrency.token.contractAddress, - decimals = feePaidCurrency.token.decimals, - id = feePaidCurrency.token.id, - ) - FeePaidCurrency.Token( - tokenId = getTokenId(network = currency.network, sdkToken = feePaidCurrency.token), - name = feePaidCurrency.token.name, - symbol = feePaidCurrency.token.symbol, - contractAddress = feePaidCurrency.token.contractAddress, - balance = balance, - ) + return withContext(dispatchers.io) { + val blockchain = Blockchain.fromId(currency.network.id.value) + when (val feePaidCurrency = blockchain.feePaidCurrency()) { + FeePaidSdkCurrency.Coin -> FeePaidCurrency.Coin + FeePaidSdkCurrency.SameCurrency -> FeePaidCurrency.SameCurrency + is FeePaidSdkCurrency.Token -> { + val balance = walletManagersFacade.tokenBalance( + userWalletId = userWalletId, + network = currency.network, + name = feePaidCurrency.token.name, + symbol = feePaidCurrency.token.symbol, + contractAddress = feePaidCurrency.token.contractAddress, + decimals = feePaidCurrency.token.decimals, + id = feePaidCurrency.token.id, + ) + FeePaidCurrency.Token( + tokenId = getTokenId(network = currency.network, sdkToken = feePaidCurrency.token), + name = feePaidCurrency.token.name, + symbol = feePaidCurrency.token.symbol, + contractAddress = feePaidCurrency.token.contractAddress, + balance = balance, + ) + } + is FeePaidSdkCurrency.FeeResource -> FeePaidCurrency.FeeResource(currency = feePaidCurrency.currency) } - is FeePaidSdkCurrency.FeeResource -> FeePaidCurrency.FeeResource(currency = feePaidCurrency.currency) } } diff --git a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt index a210d96273..d4a0f8fd29 100644 --- a/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt +++ b/data/tokens/src/main/kotlin/com/tangem/data/tokens/repository/DefaultMarketCryptoCurrencyRepository.kt @@ -5,9 +5,12 @@ import com.tangem.datasource.local.token.AssetsStore import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.repository.MarketCryptoCurrencyRepository import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.utils.coroutines.CoroutineDispatcherProvider +import kotlinx.coroutines.withContext class DefaultMarketCryptoCurrencyRepository( private val assetsStore: AssetsStore, + private val dispatchers: CoroutineDispatcherProvider, ) : MarketCryptoCurrencyRepository { override suspend fun isExchangeable(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean { @@ -15,11 +18,16 @@ class DefaultMarketCryptoCurrencyRepository( } private suspend fun getExchangeableFlag(userWalletId: UserWalletId, cryptoCurrency: CryptoCurrency): Boolean { - val contractAddress = (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE + return withContext(dispatchers.io) { + val contractAddress = + (cryptoCurrency as? CryptoCurrency.Token)?.contractAddress ?: EMPTY_CONTRACT_ADDRESS_VALUE - return assetsStore.getSyncOrNull(userWalletId)?.find { - it.network == cryptoCurrency.network.backendId && - it.contractAddress.equals(contractAddress, ignoreCase = true) - }?.exchangeAvailable ?: false + val asset = assetsStore.getSyncOrNull(userWalletId)?.find { + it.network == cryptoCurrency.network.backendId && + it.contractAddress.equals(contractAddress, ignoreCase = true) + } + + asset?.exchangeAvailable ?: false + } } } \ No newline at end of file diff --git a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt index fb35baa42f..7d447a5359 100644 --- a/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt +++ b/data/txhistory/src/main/kotlin/com/tangem/data/txhistory/repository/DefaultTxHistoryRepository.kt @@ -35,16 +35,19 @@ class DefaultTxHistoryRepository( private val sdkPageConverter by lazy { SdkPageConverter() } override suspend fun getTxHistoryItemsCount(userWalletId: UserWalletId, currency: CryptoCurrency): Int { - val userWallet = getUserWallet(userWalletId) - val state = walletManagersFacade.getTxHistoryState( - userWalletId = userWallet.walletId, - currency = currency, - ) - return when (state) { - is TxHistoryState.Failed.FetchError -> throw TxHistoryStateError.DataError(state.exception) - is TxHistoryState.NotImplemented -> throw TxHistoryStateError.TxHistoryNotImplemented - is TxHistoryState.Success.Empty -> throw TxHistoryStateError.EmptyTxHistories - is TxHistoryState.Success.HasTransactions -> state.txCount + return withContext(dispatchers.io) { + val userWallet = getUserWallet(userWalletId) + val state = walletManagersFacade.getTxHistoryState( + userWalletId = userWallet.walletId, + currency = currency, + ) + + when (state) { + is TxHistoryState.Failed.FetchError -> throw TxHistoryStateError.DataError(state.exception) + is TxHistoryState.NotImplemented -> throw TxHistoryStateError.TxHistoryNotImplemented + is TxHistoryState.Success.Empty -> throw TxHistoryStateError.EmptyTxHistories + is TxHistoryState.Success.HasTransactions -> state.txCount + } } } diff --git a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt index 9be30e9699..6204c49430 100644 --- a/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt +++ b/domain/core/src/main/kotlin/com/tangem/domain/core/lce/LceFlow.kt @@ -5,11 +5,11 @@ import com.tangem.domain.core.utils.lceContent import com.tangem.domain.core.utils.lceError import com.tangem.domain.core.utils.lceLoading import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.NonCancellable +import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.channels.ProducerScope +import kotlinx.coroutines.channels.trySendBlocking import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.launch import kotlin.experimental.ExperimentalTypeInference /** @@ -25,35 +25,43 @@ typealias LceFlow = Flow> * It provides methods to handle [Lce] instances and raise errors within a [Flow]. * * @property raise The [LceRaise] instance that this class wraps. - * @property scope The [ProducerScope] that this class operates within. + * @property producerScope The [ProducerScope] instance that this class wraps. * @property ifLoading The function to call if a loading state is raised. */ class LceFlowScope @PublishedApi internal constructor( private val raise: LceRaise, - private val scope: ProducerScope>, + private val producerScope: ProducerScope>, private val ifLoading: suspend LceFlowScope.(C?) -> Unit, -) : Raise, CoroutineScope by scope { +) : Raise, CoroutineScope by producerScope { /** - * Raises an [Lce] instance within the [ProducerScope]. - * It closes the [ProducerScope] after raise. + * Sends a error of type [E] within the [ProducerScope] and then closes it for send. + * All subsequent sends will be ignored. * - * @param r The [Lce] instance to raise. + * This method blocks the coroutine until a error is handled by the receiver. + * + * If the [ProducerScope] is already closed for send (e.g. after rising another error), it just raises [r] + * without closing. + * + * @param r Error to raise. */ override fun raise(r: E): Nothing { - scope.launch(NonCancellable) { - scope.send(r.lceError()) - scope.close() - } + producerScope.trySendBlocking(r.lceError()) + producerScope.close() raise.raise(r.lceError()) } /** - * Sends a content value within the [ProducerScope]. + * Sends a [content] value within the [ProducerScope]. + * * If the content is still loading, it calls [ifLoading] lambda to retrieve a state. * Otherwise, it wraps the content in a [Lce.Content] state. * + * This method suspends until the [Lce] instance is handled by the receiver. + * + * If the [ProducerScope] is closed for send (e.g. after rising a error), it does nothing. + * * @param content The content value to send. * @param isStillLoading A flag indicating whether the content is still loading. */ @@ -65,11 +73,23 @@ class LceFlowScope @PublishedApi internal constructor( content.lceContent() } - scope.send(value) + send(value) } + /** + * Sends an [Lce] instance within the [ProducerScope]. + * + * This method suspends until the [Lce] instance is handled by the receiver. + * + * If the [ProducerScope] is closed for send (e.g. after rising a error), it does nothing. + * + * @param value The [Lce] instance to send. + */ + @OptIn(DelicateCoroutinesApi::class) suspend fun send(value: Lce) { - scope.send(value) + if (producerScope.isClosedForSend) return + + producerScope.send(value) } } @@ -94,7 +114,7 @@ fun lceFlow( lce { val scope = LceFlowScope( raise = this@lce, - scope = this@channelFlow, + producerScope = this@channelFlow, ifLoading = ifLoading, ) diff --git a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt index 5592329e84..25dae1f2dd 100644 --- a/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt +++ b/domain/legacy/src/main/java/com/tangem/domain/walletmanager/DefaultWalletManagersFacade.kt @@ -87,11 +87,13 @@ class DefaultWalletManagersFacade( Blockchain.fromId(it.id.value) to it.derivationPath.value } - walletManagersStore.remove(userWalletId) { walletManager -> - val wallet = walletManager.wallet - val blockchainToDerivationPath = wallet.blockchain to wallet.publicKey.derivationPath?.rawPath + withContext(dispatchers.io) { + walletManagersStore.remove(userWalletId) { walletManager -> + val wallet = walletManager.wallet + val blockchainToDerivationPath = wallet.blockchain to wallet.publicKey.derivationPath?.rawPath - blockchainToDerivationPath in blockchainsToDerivationPaths + blockchainToDerivationPath in blockchainsToDerivationPaths + } } } @@ -110,18 +112,20 @@ class DefaultWalletManagersFacade( network: Network, networkTokens: List, ) { - val walletManager = walletManagersStore.getSyncOrNull( - userWalletId = userWalletId, - blockchain = Blockchain.fromId(network.id.value), - derivationPath = network.derivationPath.value, - ) ?: return - val tokensToRemove = sdkTokenConverter.convertList(networkTokens) + withContext(dispatchers.io) { + val walletManager = walletManagersStore.getSyncOrNull( + userWalletId = userWalletId, + blockchain = Blockchain.fromId(network.id.value), + derivationPath = network.derivationPath.value, + ) ?: return@withContext + val tokensToRemove = sdkTokenConverter.convertList(networkTokens) - tokensToRemove.forEach { token -> - walletManager.removeToken(token) + tokensToRemove.forEach { token -> + walletManager.removeToken(token) + } + + walletManagersStore.store(userWalletId, walletManager) } - - walletManagersStore.store(userWalletId, walletManager) } override suspend fun updatePendingTransactions( @@ -576,12 +580,16 @@ class DefaultWalletManagersFacade( userWalletId: UserWalletId, currency: CryptoCurrency, ): AssetRequirementsCondition? { - val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network) - val currencyType = cryptoCurrencyTypeConverter.convert(currency) - if (walletManager !is AssetRequirementsManager || !walletManager.hasRequirements(currencyType)) return null + return withContext(dispatchers.io) { + val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network) + val currencyType = cryptoCurrencyTypeConverter.convert(currency) + if (walletManager !is AssetRequirementsManager || !walletManager.hasRequirements(currencyType)) { + return@withContext null + } - val condition = walletManager.requirementsCondition(currencyType) ?: return null - return requirementsConditionConverter.convert(condition) + val condition = walletManager.requirementsCondition(currencyType) ?: return@withContext null + requirementsConditionConverter.convert(condition) + } } override suspend fun associateAsset( @@ -589,15 +597,18 @@ class DefaultWalletManagersFacade( currency: CryptoCurrency, signer: CommonSigner, ): SimpleResult { - val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network) - val currencyType = cryptoCurrencyTypeConverter.convert(currency) + return withContext(dispatchers.io) { + val walletManager = getOrCreateWalletManager(userWalletId = userWalletId, network = currency.network) + val currencyType = cryptoCurrencyTypeConverter.convert(currency) - if (walletManager !is AssetRequirementsManager) { - return SimpleResult.Failure( - BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"), - ) + if (walletManager !is AssetRequirementsManager) { + return@withContext SimpleResult.Failure( + BlockchainSdkError.CustomError("WalletManager is not implemented AssetRequirementsManager"), + ) + } + + walletManager.fulfillRequirements(currencyType, signer) } - return walletManager.fulfillRequirements(currencyType, signer) } override suspend fun checkUtxoConsolidationAvailability(userWalletId: UserWalletId, network: Network): Boolean { diff --git a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/analytics/TokenSwapPromoAnalyticsEvent.kt b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/analytics/TokenSwapPromoAnalyticsEvent.kt index cc39985633..9e9156c366 100644 --- a/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/analytics/TokenSwapPromoAnalyticsEvent.kt +++ b/domain/tokens/models/src/main/java/com/tangem/domain/tokens/models/analytics/TokenSwapPromoAnalyticsEvent.kt @@ -1,18 +1,43 @@ package com.tangem.domain.tokens.models.analytics import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam sealed class TokenSwapPromoAnalyticsEvent( event: String, params: Map = mapOf(), -) : AnalyticsEvent("Swap Promo", event, params, null) { - - object Close : TokenSwapPromoAnalyticsEvent(event = "Button - Close") - - class Exchange( - token: String, +) : AnalyticsEvent(category = "Promotion", event = event, params = params) { + class NoticePromotionBanner( + source: AnalyticsParam.ScreensSources, + programName: ProgramName, ) : TokenSwapPromoAnalyticsEvent( - event = "Button - Exchange Now", - params = mapOf("Token" to token), + event = "Notice - Promotion Banner", + params = mapOf( + AnalyticsParam.SOURCE to source.value, + "Program Name" to programName.name, + ), ) + + class PromotionBannerClicked( + source: AnalyticsParam.ScreensSources, + programName: ProgramName, + action: BannerAction, + ) : TokenSwapPromoAnalyticsEvent( + event = "Promo Banner Clicked", + params = mapOf( + AnalyticsParam.SOURCE to source.value, + "Program Name" to programName.name, + "Action" to action.action, + ), + ) { + sealed class BannerAction(val action: String) { + data object Clicked : BannerAction(action = "Clicked") + data object Closed : BannerAction(action = "Closed") + } + } + + enum class ProgramName { + Travala, + OKX, + } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt index eb7296acea..93ff397011 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/GetCurrencyWarningsUseCase.kt @@ -84,7 +84,7 @@ class GetCurrencyWarningsUseCase( ): Flow { val currency = currencyStatus.currency val cryptoStatuses = operations.getCurrenciesStatusesSync() - val promoBanner = promoRepository.getChangellyPromoBanner() + val promoBanner = promoRepository.getOkxPromoBanner() return combine( showSwapPromoTokenUseCase().conflate(), flowOf(marketCryptoCurrencyRepository.isExchangeable(userWalletId, currency)).conflate(), diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt index ed42ab1cfc..51ba0a57f9 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/repository/PromoRepository.kt @@ -7,4 +7,6 @@ interface PromoRepository { suspend fun getChangellyPromoBanner(): PromoBanner? suspend fun getTravalaPromoBanner(): PromoBanner? + + suspend fun getOkxPromoBanner(): PromoBanner? } \ No newline at end of file diff --git a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt index 5bb5079f95..cdaa277dac 100644 --- a/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt +++ b/features/swap/data/src/main/java/com/tangem/feature/swap/converters/SwapPairInfoConverter.kt @@ -68,6 +68,7 @@ class SwapPairInfoConverter : Converter ExchangeProviderTypeDomain.DEX ExchangeProviderType.CEX -> ExchangeProviderTypeDomain.CEX + ExchangeProviderType.DEX_BRIDGE -> ExchangeProviderTypeDomain.DEX_BRIDGE } } } \ No newline at end of file diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt index a422c02284..c52f37ce6c 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/ExchangeStatus.kt @@ -11,6 +11,7 @@ data class ExchangeStatusModel( enum class ExchangeStatus { New, Waiting, + WaitingTxHash, Confirming, Verifying, Exchanging, @@ -19,4 +20,5 @@ enum class ExchangeStatus { Finished, Refunded, Cancelled, + Unknown, } \ No newline at end of file diff --git a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt index 9df61116fa..1cbcdf41d1 100644 --- a/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt +++ b/features/swap/domain/models/src/main/java/com/tangem/feature/swap/domain/models/domain/SwapPairLeast.kt @@ -36,9 +36,10 @@ data class SwapProvider( val privacyPolicy: String?, ) -enum class ExchangeProviderType { - DEX, - CEX, +enum class ExchangeProviderType(val providerName: String) { + DEX("DEX"), + CEX("CEX"), + DEX_BRIDGE("DEX/Bridge"), } /** diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt index a91c30859e..eb1639b60d 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/SwapInteractorImpl.kt @@ -13,7 +13,7 @@ import com.tangem.core.ui.utils.parseBigDecimal import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.appcurrency.repository.AppCurrencyRepository -import com.tangem.domain.demo.DemoConfig +import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.tokens.GetCryptoCurrencyStatusesSyncUseCase import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.CryptoCurrencyStatus @@ -65,6 +65,7 @@ internal class SwapInteractorImpl @Inject constructor( private val walletManagersFacade: WalletManagersFacade, private val sendTransactionUseCase: SendTransactionUseCase, private val createTransactionUseCase: CreateTransactionUseCase, + private val isDemoCardUseCase: IsDemoCardUseCase, private val quotesRepository: QuotesRepository, private val dispatcher: CoroutineDispatcherProvider, private val swapTransactionRepository: SwapTransactionRepository, @@ -72,7 +73,6 @@ internal class SwapInteractorImpl @Inject constructor( private val appCurrencyRepository: AppCurrencyRepository, private val currenciesRepository: CurrenciesRepository, private val initialToCurrencyResolver: InitialToCurrencyResolver, - private val demoConfig: DemoConfig, private val transactionRepository: TransactionRepository, ) : SwapInteractor { @@ -268,7 +268,7 @@ internal class SwapInteractorImpl @Inject constructor( val isBalanceWithoutFeeEnough = isBalanceEnough(fromToken, amount, null) val networkId = fromToken.currency.network.backendId when (provider.type) { - ExchangeProviderType.DEX -> { + ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> { manageDex( networkId = networkId, fromToken = fromToken, @@ -342,7 +342,7 @@ internal class SwapInteractorImpl @Inject constructor( ) } else { provider to getQuotesState( - exchangeProviderType = ExchangeProviderType.DEX, + exchangeProviderType = provider.type, quoteDataModel = quotes, amount = amount, fromToken = fromToken, @@ -568,7 +568,7 @@ internal class SwapInteractorImpl @Inject constructor( fee: TxFee, ): SwapTransactionState { val cardId = getSelectedWallet()?.scanResponse?.card?.cardId ?: return SwapTransactionState.UnknownError - if (demoConfig.isDemoCardId(cardId)) return SwapTransactionState.DemoMode + if (isDemoCardUseCase(cardId)) return SwapTransactionState.DemoMode return when (swapProvider.type) { ExchangeProviderType.CEX -> { @@ -588,8 +588,9 @@ internal class SwapInteractorImpl @Inject constructor( userWalletId = requireNotNull(getSelectedWallet()).walletId, ) } - ExchangeProviderType.DEX -> { + ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> { onSwapDex( + provider = swapProvider, networkId = currencyToSend.currency.network.backendId, swapData = requireNotNull(swapData), currencyToSendStatus = currencyToSend, @@ -641,6 +642,7 @@ internal class SwapInteractorImpl @Inject constructor( } private suspend fun onSwapDex( + provider: SwapProvider, networkId: String, swapData: SwapDataModel, currencyToSendStatus: CryptoCurrencyStatus, @@ -652,7 +654,8 @@ internal class SwapInteractorImpl @Inject constructor( val amountDecimal = requireNotNull(toBigDecimalOrNull(amountToSwap)) { "wrong amount format" } val amount = SwapAmount(amountDecimal, currencyToSendStatus.currency.decimals) val derivationPath = currencyToSendStatus.currency.network.derivationPath.value - val dataToSign = (swapData.transaction as ExpressTransactionModel.DEX).txData + val dexTransaction = swapData.transaction as ExpressTransactionModel.DEX + val dataToSign = dexTransaction.txData val txData = createTransactionUseCase( amount = amount.value.convertToAmount(currencyToSendStatus.currency), fee = getFeeForTransaction( @@ -686,6 +689,17 @@ internal class SwapInteractorImpl @Inject constructor( txHash = txHash, payInExtraId = swapData.transaction.txExtraId, ) + if (provider.type == ExchangeProviderType.DEX_BRIDGE) { + val timestamp = System.currentTimeMillis() + storeSwapTransaction( + currencyToSend = currencyToSendStatus, + currencyToGet = currencyToGetStatus, + amount = amount, + swapProvider = provider, + swapDataModel = swapData, + timestamp = timestamp, + ) + } storeLastCryptoCurrencyId(currencyToGetStatus.currency) SwapTransactionState.TxSent( fromAmount = amountFormatter.formatSwapAmountToUI( @@ -702,16 +716,7 @@ internal class SwapInteractorImpl @Inject constructor( timestamp = System.currentTimeMillis(), ) }, - ifLeft = { - when (it) { - SendTransactionError.UserCancelledError -> SwapTransactionState.UserCancelled - is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError - is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError - is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError - is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode - else -> SwapTransactionState.UnknownError - } - }, + ifLeft = { handleSendTxError(it) }, ) } @@ -783,14 +788,7 @@ internal class SwapInteractorImpl @Inject constructor( val derivationPath = currencyToSend.currency.network.derivationPath.value return result.fold( ifLeft = { - when (it) { - SendTransactionError.UserCancelledError -> SwapTransactionState.UserCancelled - is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError - is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError - is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError - is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode - else -> SwapTransactionState.UnknownError - } + handleSendTxError(it) }, ifRight = { txHash -> repository.exchangeSent( @@ -836,6 +834,17 @@ internal class SwapInteractorImpl @Inject constructor( ) } + private fun handleSendTxError(txError: SendTransactionError?): SwapTransactionState { + return when (txError) { + SendTransactionError.UserCancelledError -> SwapTransactionState.UserCancelled + is SendTransactionError.BlockchainSdkError -> SwapTransactionState.BlockchainError + is SendTransactionError.TangemSdkError -> SwapTransactionState.TangemSdkError + is SendTransactionError.NetworkError -> SwapTransactionState.NetworkError + is SendTransactionError.DemoCardError -> SwapTransactionState.DemoMode + else -> SwapTransactionState.UnknownError + } + } + private fun getFeeForTransaction(fee: TxFee, blockchain: Blockchain): Fee { val feeAmountValue = fee.feeValue val feeAmount = Amount( @@ -884,8 +893,8 @@ internal class SwapInteractorImpl @Inject constructor( swapProvider: SwapProvider, swapDataModel: SwapDataModel, timestamp: Long, - txExternalUrl: String, - txExternalId: String, + txExternalUrl: String? = null, + txExternalId: String? = null, ) { swapTransactionRepository.storeTransaction( userWalletId = UserWalletId(userWalletManager.getWalletId()), @@ -1074,7 +1083,7 @@ internal class SwapInteractorImpl @Inject constructor( ) when (exchangeProviderType) { - ExchangeProviderType.DEX -> { + ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> { val state = updatePermissionState( networkId = networkId, fromTokenStatus = fromToken, @@ -1409,19 +1418,25 @@ internal class SwapInteractorImpl @Inject constructor( swapAmount = swapAmount, spenderAddress = requireNotNull(spenderAddress) { "Spender address is null" }, ) - val feeData = try { - transactionManager.getFee( - networkId = networkId, - amountToSend = BigDecimal.ZERO, - currencyToSend = swapCurrencyConverter.convert(repository.getNativeTokenForNetwork(networkId)), - destinationAddress = fromToken.getContractAddress(), - increaseBy = INCREASE_GAS_LIMIT_BY, - data = transactionData, - derivationPath = derivationPath, - ) - } catch (e: Exception) { - Timber.e(e, "Failed to get fee") - null + val userWallet = getSelectedWallet() + val cardId = userWallet?.scanResponse?.card?.cardId + val feeData = if (cardId != null && isDemoCardUseCase(cardId)) { + getDemoFees(fromTokenStatus.currency) + } else { + try { + transactionManager.getFee( + networkId = networkId, + amountToSend = BigDecimal.ZERO, + currencyToSend = swapCurrencyConverter.convert(repository.getNativeTokenForNetwork(networkId)), + destinationAddress = fromToken.getContractAddress(), + increaseBy = INCREASE_GAS_LIMIT_BY, + data = transactionData, + derivationPath = derivationPath, + ) + } catch (e: Exception) { + Timber.e(e, "Failed to get fee") + null + } } val feeState = feeData?.let { when (feeData) { @@ -1805,10 +1820,37 @@ internal class SwapInteractorImpl @Inject constructor( .toMap() } + private fun getDemoFees(cryptoCurrency: CryptoCurrency): ProxyFees.MultipleFees { + val demoFee = ProxyAmount( + currencySymbol = cryptoCurrency.symbol, + value = minDemoFee, + decimals = cryptoCurrency.decimals, + ) + return ProxyFees.MultipleFees( + minFee = ProxyFee.Common( + gasLimit = 1.toBigInteger(), + fee = demoFee, + ), + normalFee = ProxyFee.Common( + gasLimit = 1.toBigInteger(), + fee = demoFee.copy(value = normalDemoFee), + + ), + priorityFee = ProxyFee.Common( + gasLimit = 1.toBigInteger(), + fee = demoFee.copy(value = priorityDemoFee), + ), + ) + } + companion object { @Suppress("UnusedPrivateMember") private const val INCREASE_GAS_LIMIT_BY = 112 // 12% private const val INCREASE_GAS_LIMIT_FOR_SEND = 105 // 5% private const val INFINITY_SYMBOL = "∞" + + private val minDemoFee = "0.0001".toBigDecimal() + private val normalDemoFee = "0.0002".toBigDecimal() + private val priorityDemoFee = "0.0003".toBigDecimal() } } \ No newline at end of file diff --git a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt index 1a6a6e8b22..a99ef1d6d7 100644 --- a/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt +++ b/features/swap/domain/src/main/java/com/tangem/feature/swap/domain/di/SwapDomainModule.kt @@ -42,6 +42,7 @@ class SwapDomainModule { getCryptoCurrencyStatusUseCase: GetCryptoCurrencyStatusesSyncUseCase, @SwapScope sendTransactionUseCase: SendTransactionUseCase, @SwapScope createTransactionUseCase: CreateTransactionUseCase, + isDemoCardUseCase: IsDemoCardUseCase, quotesRepository: QuotesRepository, swapTransactionRepository: SwapTransactionRepository, appCurrencyRepository: AppCurrencyRepository, @@ -61,6 +62,7 @@ class SwapDomainModule { getMultiCryptoCurrencyStatusUseCase = getCryptoCurrencyStatusUseCase, sendTransactionUseCase = sendTransactionUseCase, createTransactionUseCase = createTransactionUseCase, + isDemoCardUseCase = isDemoCardUseCase, quotesRepository = quotesRepository, walletManagersFacade = walletManagersFacade, dispatcher = coroutineDispatcherProvider, @@ -69,7 +71,6 @@ class SwapDomainModule { currencyChecksRepository = currencyChecksRepository, currenciesRepository = currenciesRepository, initialToCurrencyResolver = initialToCurrencyResolver, - demoConfig = DemoConfig(), transactionRepository = transactionRepository, ) } diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt index c902406819..880518dcdb 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/StateBuilder.kt @@ -1004,13 +1004,15 @@ internal class StateBuilder( val fromFiatAmount = getFormattedFiatAmount(fromCryptoCurrency.value.fiatRate?.multiply(fromAmount)) val toFiatAmount = getFormattedFiatAmount(toCryptoCurrency.value.fiatRate?.multiply(toAmount)) + val shouldShowStatus = providerState.type == ExchangeProviderType.CEX.providerName || + providerState.type == ExchangeProviderType.DEX_BRIDGE.providerName return uiState.copy( successState = SwapSuccessStateHolder( timestamp = swapTransactionState.timestamp, txUrl = txUrl, providerName = stringReference(providerState.name), providerType = stringReference(providerState.type), - showStatusButton = providerState.type == ExchangeProviderType.CEX.name, + showStatusButton = shouldShowStatus, providerIcon = providerState.iconUrl, rate = providerState.subtitle, fee = stringReference("${fee.feeCryptoFormatted} (${fee.feeFiatFormatted})"), @@ -1092,7 +1094,7 @@ internal class StateBuilder( ): SwapStateHolder { val message = when (providerType) { ExchangeProviderType.CEX -> resourceReference(R.string.swapping_alert_cex_description, wrappedList(token)) - ExchangeProviderType.DEX -> { + ExchangeProviderType.DEX, ExchangeProviderType.DEX_BRIDGE -> { val refs = buildList { if (isPriceImpact) { add(resourceReference(R.string.swapping_high_price_impact_description)) @@ -1548,7 +1550,7 @@ internal class StateBuilder( id = this.providerId, name = this.name, iconUrl = this.imageLarge, - type = this.type.toString(), + type = this.type.providerName, subtitle = stringReference(rateString), additionalBadge = badge, selectionType = selectionType, @@ -1575,7 +1577,7 @@ internal class StateBuilder( id = this.providerId, name = this.name, iconUrl = this.imageLarge, - type = this.type.toString(), + type = this.type.providerName, subtitle = stringReference(rateString), additionalBadge = additionalBadge, selectionType = selectionType, @@ -1612,7 +1614,7 @@ internal class StateBuilder( id = this.providerId, name = this.name, iconUrl = this.imageLarge, - type = this.type.toString(), + type = this.type.providerName, selectionType = selectionType, subtitle = alertText, additionalBadge = ProviderState.AdditionalBadge.Empty, diff --git a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt index 11ade5db09..6f1b0517ff 100644 --- a/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt +++ b/features/swap/presentation/src/main/java/com/tangem/feature/swap/ui/SwapSuccessScreen.kt @@ -154,7 +154,7 @@ private val state = SwapSuccessStateHolder( txUrl = "https://www.google.com/#q=nam", fee = TextReference.Str("1 000 DAI ~ 1 000 MATIC"), providerName = TextReference.Str("1inch"), - providerType = TextReference.Str(ExchangeProviderType.DEX.name), + providerType = TextReference.Str(ExchangeProviderType.DEX.providerName), showStatusButton = false, providerIcon = "", fromTokenAmount = TextReference.Str("1 000 DAI"), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt index a69442cd51..878dc66047 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/analytics/TokenDetailsNotificationsAnalyticsSender.kt @@ -2,7 +2,9 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.analytics import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsEvent +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.models.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsState import com.tangem.feature.tokendetails.presentation.tokendetails.state.components.TokenDetailsNotification @@ -35,12 +37,15 @@ internal class TokenDetailsNotificationsAnalyticsSender( -> TokenDetailsAnalyticsEvent.Notice.NotEnoughFee( currency = cryptoCurrency, ) + is TokenDetailsNotification.SwapPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner( + programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX, + source = AnalyticsParam.ScreensSources.Token, + ) is TokenDetailsNotification.NetworksUnreachable, is TokenDetailsNotification.ExistentialDeposit, is TokenDetailsNotification.NetworksNoAccount, is TokenDetailsNotification.TopUpWithoutReserve, is TokenDetailsNotification.RentInfo, - is TokenDetailsNotification.SwapPromo, is TokenDetailsNotification.NetworkShutdown, is TokenDetailsNotification.HederaAssociateWarning, is TokenDetailsNotification.KoinosMana, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt index 950f24a218..a09065820b 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/components/TokenDetailsNotification.kt @@ -2,7 +2,10 @@ package com.tangem.feature.tokendetails.presentation.tokendetails.state.componen import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.networkIconResId +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.domain.tokens.model.CryptoCurrency import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning import com.tangem.features.tokendetails.impl.R @@ -45,16 +48,9 @@ internal sealed class TokenDetailsNotification(val config: NotificationConfig) { val onCloseClick: () -> Unit, ) : TokenDetailsNotification( config = NotificationConfig( - title = resourceReference( - id = R.string.token_swap_changelly_promotion_title, - formatArgs = wrappedList("0%"), - ), - subtitle = resourceReference( - id = R.string.token_swap_changelly_promotion_message, - formatArgs = wrappedList("0%", startDateTime.dayOfMonth, endDateTime.dayOfMonth), - ), - iconResId = R.drawable.img_swap_promo, - backgroundResId = R.drawable.img_swap_promo_green_banner_background, + title = resourceReference(id = R.string.swap_promo_title), + subtitle = resourceReference(id = R.string.swap_promo_text), + iconResId = R.drawable.img_okx_dex_logo, onCloseClick = onCloseClick, buttonsState = NotificationConfig.ButtonsState.SecondaryButtonConfig( text = resourceReference(id = com.tangem.core.ui.R.string.token_swap_promotion_button), diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt index 55e6cba9b0..6c1a71f7af 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/factory/TokenDetailsSwapTransactionsStateConverter.kt @@ -162,6 +162,7 @@ internal class TokenDetailsSwapTransactionsStateConverter( private fun getStatuses(status: ExchangeStatus?, hasFailed: Boolean = false): ImmutableList { if (status == null) return persistentListOf() val isWaiting = status == ExchangeStatus.New || status == ExchangeStatus.Waiting + val isWaitingTxHash = status == ExchangeStatus.WaitingTxHash val isConfirming = status == ExchangeStatus.Confirming val isVerifying = status == ExchangeStatus.Verifying val isExchanging = status == ExchangeStatus.Exchanging @@ -174,27 +175,35 @@ internal class TokenDetailsSwapTransactionsStateConverter( val isExchangingDone = !isExchanging && isConfirmingDone val isSendingDone = !isSending && !isVerifying && !isFailed && isExchangingDone - return if (status == ExchangeStatus.Cancelled) { - listOf(cancelledStep()) - } else { - listOf( - waitStep(isWaiting, isWaitingDone), - confirmStep(isConfirming, isConfirmingDone), - exchangeStep( - isExchanging = isExchanging, - isExchangingDone = isExchangingDone, - isRefunded = isRefunded, - hasFailed = hasFailed, - isVerifying = isVerifying, - isFailed = isFailed, - ), - sendStep( - isSending = isSending, - isSendingDone = isSendingDone, - isRefunded = isRefunded, - hasFailed = hasFailed, - ), - ) + return buildList { + when { + status == ExchangeStatus.Cancelled -> add(cancelledStep()) + isWaitingTxHash -> add(waitTxStep()) + // ExchangeStatus.Unknown is temporary added for 1inch + status == ExchangeStatus.Unknown -> add(unknownStateStep()) + else -> { + add(waitStep(isWaiting, isWaitingDone)) + add(confirmStep(isConfirming, isConfirmingDone)) + add( + exchangeStep( + isExchanging = isExchanging, + isExchangingDone = isExchangingDone, + isRefunded = isRefunded, + hasFailed = hasFailed, + isVerifying = isVerifying, + isFailed = isFailed, + ), + ) + add( + sendStep( + isSending = isSending, + isSendingDone = isSendingDone, + isRefunded = isRefunded, + hasFailed = hasFailed, + ), + ) + } + } }.toPersistentList() } @@ -216,6 +225,20 @@ internal class TokenDetailsSwapTransactionsStateConverter( isDone = isNewDone, ) + private fun waitTxStep() = ExchangeStatusState( + status = ExchangeStatus.Verifying, + text = TextReference.Res(R.string.express_exchange_status_waiting_tx_hash), + isActive = false, + isDone = false, + ) + + private fun unknownStateStep() = ExchangeStatusState( + status = ExchangeStatus.Failed, + text = TextReference.Res(R.string.express_exchange_status_failed), + isActive = false, + isDone = true, + ) + private fun confirmStep(isConfirming: Boolean, isConfirmingDone: Boolean) = ExchangeStatusState( status = ExchangeStatus.Confirming, text = when { diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt index d9eb9ec18d..c24ca651d8 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/TokenDetailsScreen.kt @@ -32,7 +32,7 @@ import com.tangem.core.ui.components.bottomsheets.tokenreceive.TokenReceiveBotto import com.tangem.core.ui.components.marketprice.MarketPriceBlock import com.tangem.core.ui.components.marketprice.MarketPriceBlockState import com.tangem.core.ui.components.notifications.Notification -import com.tangem.core.ui.components.notifications.NotificationWithBackground +import com.tangem.core.ui.components.notifications.OkxPromoNotification import com.tangem.core.ui.components.transactions.state.TxHistoryState import com.tangem.core.ui.components.transactions.txHistoryItems import com.tangem.core.ui.event.EventEffect @@ -111,7 +111,7 @@ internal fun TokenDetailsScreen(state: TokenDetailsState) { contentType = { it.config::class.java }, itemContent = { if (it is TokenDetailsNotification.SwapPromo) { - NotificationWithBackground( + OkxPromoNotification( config = it.config, modifier = itemModifier.animateItemPlacement(), ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt index 1df5e537c1..a1a7c595a5 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/ui/components/exchange/ExchangeStatusBottomSheet.kt @@ -70,7 +70,7 @@ private fun ExchangeStatusBottomSheetContent(content: ExchangeStatusBottomSheetC SpacerH12() ExchangeProvider( providerName = TextReference.Str(config.provider.name), - providerType = TextReference.Str(config.provider.type.name), + providerType = TextReference.Str(config.provider.type.providerName), providerTxId = config.txExternalId, imageUrl = config.provider.imageLarge, ) diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt index 4bc8e7fb37..e77952c869 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/ExchangeStatusFactory.kt @@ -163,6 +163,7 @@ internal class ExchangeStatusFactory( return when (status) { ExchangeStatus.New, ExchangeStatus.Waiting, + ExchangeStatus.WaitingTxHash, ExchangeStatus.Sending, ExchangeStatus.Confirming, ExchangeStatus.Exchanging, diff --git a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt index d4e58af373..c2ff51d2d6 100644 --- a/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt +++ b/features/tokendetails/impl/src/main/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/viewmodels/TokenDetailsViewModel.kt @@ -5,6 +5,7 @@ import androidx.paging.cachedIn import arrow.core.getOrElse import com.tangem.blockchain.common.address.AddressType import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.core.analytics.models.AnalyticsParam import com.tangem.core.deeplink.DeepLinksRegistry import com.tangem.core.deeplink.global.BuyCurrencyDeepLink import com.tangem.core.ui.clipboard.ClipboardManager @@ -709,14 +710,26 @@ internal class TokenDetailsViewModel @Inject constructor( override fun onSwapPromoDismiss() { viewModelScope.launch(dispatchers.main) { shouldShowSwapPromoTokenUseCase.neverToShow() - analyticsEventsHandler.send(TokenSwapPromoAnalyticsEvent.Close) + analyticsEventsHandler.send( + TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( + source = AnalyticsParam.ScreensSources.Token, + programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX, + action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed, + ), + ) } } override fun onSwapPromoClick() { viewModelScope.launch(dispatchers.main) { shouldShowSwapPromoTokenUseCase.neverToShow() - analyticsEventsHandler.send(TokenSwapPromoAnalyticsEvent.Exchange(cryptoCurrency.symbol)) + analyticsEventsHandler.send( + TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( + source = AnalyticsParam.ScreensSources.Token, + programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX, + action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Clicked, + ), + ) } onSwapClick(ScenarioUnavailabilityReason.None) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt index f39a38511b..de6ddf8b1b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/WalletScreenAnalyticsEvent.kt @@ -121,38 +121,4 @@ sealed class WalletScreenAnalyticsEvent { data object DeleteWalletTapped : MainScreen(event = "Button - Delete Wallet Tapped") } - - sealed class Promotion( - event: String, - params: Map = mapOf(), - ) : AnalyticsEvent(category = "Promotion", event = event, params = params) { - class NoticePromotionBanner( - source: AnalyticsParam.ScreensSources, - programName: String, - ) : Promotion( - event = "Notice - Promotion Banner", - params = mapOf( - AnalyticsParam.SOURCE to source.value, - "Program Name" to programName, - ), - ) - - class PromotionBannerClicked( - source: AnalyticsParam.ScreensSources, - programName: String, - action: BannerAction, - ) : Promotion( - event = "Promo Banner Clicked", - params = mapOf( - AnalyticsParam.SOURCE to source.value, - "Program Name" to programName, - "Action" to action.action, - ), - ) { - sealed class BannerAction(val action: String) { - data object Clicked : BannerAction(action = "Clicked") - data object Closed : BannerAction(action = "Closed") - } - } - } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt index 59d4331387..398372e208 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/analytics/utils/WalletWarningsAnalyticsSender.kt @@ -3,7 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils import com.tangem.core.analytics.api.AnalyticsEventHandler import com.tangem.core.analytics.models.AnalyticsEvent import com.tangem.core.analytics.models.AnalyticsParam -import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent +import com.tangem.domain.tokens.models.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState @@ -46,16 +46,19 @@ internal class WalletWarningsAnalyticsSender @Inject constructor( is WalletNotification.Informational.MissingAddresses -> MainScreen.MissingAddresses is WalletNotification.RateApp -> MainScreen.HowDoYouLikeTangem is WalletNotification.Critical.BackupError -> MainScreen.BackupError - is WalletNotification.TravalaPromo -> WalletScreenAnalyticsEvent.Promotion.NoticePromotionBanner( + is WalletNotification.TravalaPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner( source = AnalyticsParam.ScreensSources.Main, - programName = "Travala", + programName = TokenSwapPromoAnalyticsEvent.ProgramName.Travala, + ) + is WalletNotification.SwapPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner( + source = AnalyticsParam.ScreensSources.Main, + programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX, ) is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender] is WalletNotification.Informational.NoAccount, is WalletNotification.Warning.LowSignatures, is WalletNotification.Warning.SomeNetworksUnreachable, is WalletNotification.Warning.NetworksUnreachable, - is WalletNotification.SwapPromo, -> null } } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt index a2adf6b292..b0513c7656 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/GetMultiWalletWarningsFactory.kt @@ -6,7 +6,7 @@ import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.demo.IsDemoCardUseCase import com.tangem.domain.promo.PromoBanner import com.tangem.domain.settings.IsReadyToShowRateAppUseCase -import com.tangem.domain.settings.ShouldShowTravalaPromoWalletUseCase +import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase import com.tangem.domain.tokens.GetTokenListUseCase import com.tangem.domain.tokens.error.TokenListError import com.tangem.domain.tokens.model.CryptoCurrency @@ -34,7 +34,7 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( private val getTokenListUseCase: GetTokenListUseCase, private val isDemoCardUseCase: IsDemoCardUseCase, private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase, - private val shouldShowTravalaPromoWalletUseCase: ShouldShowTravalaPromoWalletUseCase, + private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase, private val promoRepository: PromoRepository, private val isNeedToBackupUseCase: IsNeedToBackupUseCase, private val backupValidator: BackupValidator, @@ -45,18 +45,18 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow> { val cardTypesResolver = userWallet.scanResponse.cardTypesResolver - val travalaPromoFlow = flow { emit(promoRepository.getTravalaPromoBanner()) } + val promoFlow = flow { emit(promoRepository.getOkxPromoBanner()) } return combine( flow = getTokenListUseCase.launch(userWallet.walletId).conflate(), flow2 = isReadyToShowRateAppUseCase().conflate(), flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(), - flow4 = shouldShowTravalaPromoWalletUseCase().conflate(), - flow5 = travalaPromoFlow.conflate(), - ) { maybeTokenList, isReadyToShowRating, isNeedToBackup, shouldShowTravalaPromo, promoBanner -> + flow4 = shouldShowSwapPromoWalletUseCase().conflate(), + flow5 = promoFlow.conflate(), + ) { maybeTokenList, isReadyToShowRating, isNeedToBackup, shouldShowPromo, promoBanner -> readyForRateAppNotification = true buildList { - addTravalaPromoNotification(shouldShowTravalaPromo, promoBanner, clickIntents) + addSwapPromoNotification(shouldShowPromo, promoBanner, clickIntents) addCriticalNotifications(userWallet) @@ -69,18 +69,16 @@ internal class GetMultiWalletWarningsFactory @Inject constructor( } } - private fun MutableList.addTravalaPromoNotification( + private fun MutableList.addSwapPromoNotification( shouldShowPromo: Boolean, promoBanner: PromoBanner?, clickIntents: WalletClickIntents, ) { promoBanner ?: return - val promoNotification = WalletNotification.TravalaPromo( + val promoNotification = WalletNotification.SwapPromo( startDateTime = promoBanner.bannerState.timeline.start, endDateTime = promoBanner.bannerState.timeline.end, - bannerLink = promoBanner.bannerState.link, - onBookNowButtonClick = clickIntents::onTravalaPromoClick, - onCloseClick = clickIntents::onCloseTravalaPromoClick, + onCloseClick = clickIntents::onCloseSwapPromoClick, ) addIf( element = promoNotification, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt index 1f34e460f2..3c61413f63 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/domain/WalletAdditionalInfoFactory.kt @@ -6,6 +6,7 @@ import com.tangem.core.ui.extensions.stringReference import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.utils.BigDecimalFormatter import com.tangem.domain.common.util.cardTypesResolver +import com.tangem.domain.models.scan.CardDTO import com.tangem.domain.wallets.models.UserWallet import com.tangem.feature.wallet.impl.R import com.tangem.feature.wallet.presentation.wallet.state.model.WalletAdditionalInfo @@ -107,4 +108,18 @@ internal object WalletAdditionalInfoFactory { WalletAdditionalInfo(hideable = true, content = TextReference.Str(value = amount.orEmpty())) } } + + private fun UserWallet.getCardsCount(): Int? { + return if (isMultiCurrency) { + when (val status = scanResponse.card.backupStatus) { + is CardDTO.BackupStatus.Active -> status.cardCount + 1 + is CardDTO.BackupStatus.CardLinked, + is CardDTO.BackupStatus.NoBackup, + null, + -> 1 + } + } else { + null + } + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateController.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateController.kt index e8c3c9114c..895c894ab0 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateController.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/WalletStateController.kt @@ -17,6 +17,7 @@ import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update +import timber.log.Timber import javax.inject.Inject import javax.inject.Singleton @@ -41,6 +42,7 @@ internal class WalletStateController @Inject constructor( } fun update(transformer: WalletScreenStateTransformer) { + Timber.d("Applying: ${transformer::class.simpleName}") mutableUiState.update(function = transformer::transform) } diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt index c34e986c29..4a3247b5e1 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/model/WalletNotification.kt @@ -2,7 +2,10 @@ package com.tangem.feature.wallet.presentation.wallet.state.model import androidx.compose.runtime.Immutable import com.tangem.core.ui.components.notifications.NotificationConfig -import com.tangem.core.ui.extensions.* +import com.tangem.core.ui.extensions.TextReference +import com.tangem.core.ui.extensions.pluralReference +import com.tangem.core.ui.extensions.resourceReference +import com.tangem.core.ui.extensions.wrappedList import com.tangem.core.ui.utils.DateTimeFormatters import com.tangem.feature.wallet.impl.R import org.joda.time.DateTime @@ -205,16 +208,9 @@ sealed class WalletNotification(val config: NotificationConfig) { val onCloseClick: () -> Unit, ) : WalletNotification( config = NotificationConfig( - title = resourceReference( - id = R.string.main_swap_changelly_promotion_title, - formatArgs = wrappedList("0%"), - ), - subtitle = resourceReference( - id = R.string.main_swap_changelly_promotion_message, - formatArgs = wrappedList("0%", startDateTime.dayOfMonth, endDateTime.dayOfMonth), - ), - iconResId = R.drawable.img_swap_promo, - backgroundResId = R.drawable.img_swap_promo_green_banner_background, + title = resourceReference(id = R.string.swap_promo_title), + subtitle = resourceReference(id = R.string.swap_promo_text), + iconResId = R.drawable.img_okx_dex_logo, onCloseClick = onCloseClick, ), ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt index 046b5fd498..fd70fb3cfa 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/state/transformers/SetTokenListErrorTransformer.kt @@ -1,22 +1,32 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers +import com.tangem.core.ui.utils.BigDecimalFormatter +import com.tangem.domain.appcurrency.model.AppCurrency import com.tangem.domain.tokens.error.TokenListError -import com.tangem.domain.wallets.models.UserWalletId +import com.tangem.domain.wallets.models.UserWallet +import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory +import com.tangem.feature.wallet.presentation.wallet.domain.getCardsCount +import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState import timber.log.Timber +import java.math.BigDecimal internal class SetTokenListErrorTransformer( - userWalletId: UserWalletId, + private val selectedWallet: UserWallet, private val error: TokenListError, -) : WalletStateTransformer(userWalletId) { + private val appCurrency: AppCurrency, +) : WalletStateTransformer(selectedWallet.walletId) { override fun transform(prevState: WalletState): WalletState { return when (error) { is TokenListError.EmptyTokens -> { when (prevState) { is WalletState.MultiCurrency.Content -> { - prevState.copy(tokensListState = WalletTokensListState.Empty) + prevState.copy( + walletCardState = prevState.walletCardState.toLoadedState(), + tokensListState = WalletTokensListState.Empty, + ) } is WalletState.MultiCurrency.Locked -> { Timber.w("Impossible to load tokens list for locked wallet") @@ -37,4 +47,21 @@ internal class SetTokenListErrorTransformer( -> prevState } } + + private fun WalletCardState.toLoadedState(): WalletCardState { + return WalletCardState.Content( + id = id, + title = title, + additionalInfo = WalletAdditionalInfoFactory.resolve(wallet = selectedWallet), + imageResId = imageResId, + onRenameClick = onRenameClick, + onDeleteClick = onDeleteClick, + balance = BigDecimalFormatter.formatFiatAmount( + fiatAmount = BigDecimal.ZERO, + fiatCurrencyCode = appCurrency.code, + fiatCurrencySymbol = appCurrency.symbol, + ), + cardCount = selectedWallet.getCardsCount(), + ) + } } \ No newline at end of file diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt index c9bd3afc92..f074661604 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/subscribers/BasicTokenListSubscriber.kt @@ -61,6 +61,11 @@ internal abstract class BasicTokenListSubscriber( }, flow2 = getSelectedAppCurrencyUseCase().distinctUntilChanged(), transform = { maybeTokenList, maybeAppCurrency -> + val appCurrency = maybeAppCurrency.getOrElse { e -> + Timber.e("Failed to load app currency: $e") + AppCurrency.Default + } + val tokenList = maybeTokenList.getOrElse( ifLoading = { maybeContent -> val isRefreshing = stateHolder.getWalletState(userWallet.walletId) @@ -74,16 +79,17 @@ internal abstract class BasicTokenListSubscriber( }, ifError = { e -> Timber.e("Failed to load token list: $e") - SetTokenListErrorTransformer(userWallet.walletId, e) + stateHolder.update( + SetTokenListErrorTransformer( + selectedWallet = userWallet, + error = e, + appCurrency = appCurrency, + ), + ) return@combine }, ) - val appCurrency = maybeAppCurrency.getOrElse { e -> - Timber.e("Failed to load app currency: $e") - AppCurrency.Default - } - updateContent(tokenList, appCurrency) walletWithFundsChecker.check(tokenList) }, diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt index 6b783e0eb0..08a341ebb7 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/ui/components/common/WalletNotifications.kt @@ -5,7 +5,7 @@ import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.items import androidx.compose.ui.Modifier import com.tangem.core.ui.components.notifications.Notification -import com.tangem.core.ui.components.notifications.NotificationWithBackground +import com.tangem.core.ui.components.notifications.OkxPromoNotification import com.tangem.core.ui.components.notifications.TravalaNotificationWithBackground import com.tangem.core.ui.res.TangemTheme import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification @@ -29,7 +29,7 @@ internal fun LazyListScope.notifications(configs: ImmutableList { - NotificationWithBackground( + OkxPromoNotification( config = it.config, modifier = modifier.animateItemPlacement(), ) diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt index dbad1dde46..058a853a52 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/WalletViewModel.kt @@ -216,6 +216,14 @@ internal class WalletViewModel @Inject constructor( is WalletsUpdateActionResolver.Action.DeleteWallet -> deleteWallet(action) is WalletsUpdateActionResolver.Action.UnlockWallet -> unlockWallet(action) is WalletsUpdateActionResolver.Action.UpdateWalletCardCount -> { + // refresh loader to use actual user wallet + walletScreenContentLoader.load( + userWallet = action.selectedWallet, + clickIntents = clickIntents, + isRefresh = true, + coroutineScope = viewModelScope, + ) + stateHolder.update(transformer = UpdateWalletCardsCountTransformer(action.selectedWallet)) } is WalletsUpdateActionResolver.Action.UpdateWalletName -> { diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt index 3266406422..eba25e8d34 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletClickIntents.kt @@ -1,6 +1,8 @@ package com.tangem.feature.wallet.presentation.wallet.viewmodels.intents import com.tangem.core.analytics.api.AnalyticsEventHandler +import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase +import com.tangem.domain.appcurrency.extenstions.unwrap import com.tangem.domain.common.util.cardTypesResolver import com.tangem.domain.settings.NeverToShowWalletsScrollPreview import com.tangem.domain.tokens.FetchCardTokenListUseCase @@ -37,6 +39,7 @@ internal class WalletClickIntents @Inject constructor( private val fetchTokenListUseCase: FetchTokenListUseCase, private val fetchCardTokenListUseCase: FetchCardTokenListUseCase, private val fetchCurrencyStatusUseCase: FetchCurrencyStatusUseCase, + private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase, private val neverToShowWalletsScrollPreview: NeverToShowWalletsScrollPreview, private val analyticsEventHandler: AnalyticsEventHandler, private val dispatchers: CoroutineDispatcherProvider, @@ -115,7 +118,13 @@ internal class WalletClickIntents @Inject constructor( } maybeFetchResult.onLeft { - stateHolder.update(SetTokenListErrorTransformer(userWalletId = userWallet.walletId, error = it)) + stateHolder.update( + SetTokenListErrorTransformer( + selectedWallet = userWallet, + error = it, + appCurrency = getSelectedAppCurrencyUseCase.unwrap(), + ), + ) } stateHolder.update( diff --git a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt index 21982d8010..d2af11e81b 100644 --- a/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt +++ b/features/wallet/impl/src/main/java/com/tangem/feature/wallet/presentation/wallet/viewmodels/intents/WalletWarningsClickIntents.kt @@ -14,13 +14,13 @@ import com.tangem.domain.settings.ShouldShowSwapPromoWalletUseCase import com.tangem.domain.settings.ShouldShowTravalaPromoWalletUseCase import com.tangem.domain.tokens.FetchTokenListUseCase import com.tangem.domain.tokens.model.CryptoCurrency +import com.tangem.domain.tokens.models.analytics.TokenSwapPromoAnalyticsEvent import com.tangem.domain.wallets.legacy.UserWalletsListManager.Lockable.UnlockType import com.tangem.domain.wallets.models.UnlockWalletsError import com.tangem.domain.wallets.models.UserWallet import com.tangem.domain.wallets.usecase.GetUserWalletUseCase import com.tangem.domain.wallets.usecase.UnlockWalletsUseCase import com.tangem.feature.wallet.impl.R -import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.Basic import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen import com.tangem.feature.wallet.presentation.wallet.domain.ScanCardToUnlockWalletClickHandler @@ -214,6 +214,13 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( } override fun onCloseSwapPromoClick() { + analyticsEventHandler.send( + TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( + source = AnalyticsParam.ScreensSources.Main, + programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX, + action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed, + ), + ) viewModelScope.launch(dispatchers.main) { shouldShowSwapPromoWalletUseCase.neverToShow() } @@ -221,10 +228,10 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( override fun onTravalaPromoClick(link: String?) { analyticsEventHandler.send( - WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked( + TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( source = AnalyticsParam.ScreensSources.Main, - programName = "Travala", - action = WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked.BannerAction.Clicked, + programName = TokenSwapPromoAnalyticsEvent.ProgramName.Travala, + action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Clicked, ), ) link?.let { @@ -236,10 +243,10 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor( override fun onCloseTravalaPromoClick() { analyticsEventHandler.send( - WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked( + TokenSwapPromoAnalyticsEvent.PromotionBannerClicked( source = AnalyticsParam.ScreensSources.Main, - programName = "Travala", - action = WalletScreenAnalyticsEvent.Promotion.PromotionBannerClicked.BannerAction.Closed, + programName = TokenSwapPromoAnalyticsEvent.ProgramName.Travala, + action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed, ), ) viewModelScope.launch(dispatchers.main) { diff --git a/gradle/dependencies.toml b/gradle/dependencies.toml index 88ac8d88ab..a821c9080b 100644 --- a/gradle/dependencies.toml +++ b/gradle/dependencies.toml @@ -87,9 +87,9 @@ markdown = "0.7.2" # endregion Other libraries # region Tangem -tangemBlockchainSdk = "release-app_5.12-691" +tangemBlockchainSdk = "release-app_5.12-694" #tangemBlockchainSdk = "0.0.1" # Keep it! - used for local builds -tangemCardSdk = "release-app_5.12-367" +tangemCardSdk = "release-app_5.12-369" #tangemCardSdk = "0.0.1" # Keep it! - used for local builds ^ # endregion Tangem