Updated on 2026-08-14

This commit is contained in:
Tangem 2025-01-29 21:44:25 +03:00
commit bf0cb06cb3
297 changed files with 4771 additions and 2376 deletions

View file

@ -7,7 +7,7 @@ import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData.topBarConfig

View file

@ -4,6 +4,7 @@ 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.analytics.TokenSwapPromoAnalyticsEvent
import com.tangem.domain.tokens.model.analytics.TokenSwapPromoAnalyticsEvent.ProgramName
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
@ -50,7 +51,7 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.NoteMigration -> MainScreen.NotePromo
is WalletNotification.SwapPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
)
is WalletNotification.UnlockWallets -> null // See [SelectedWalletAnalyticsSender]
is WalletNotification.Informational.NoAccount,
@ -58,10 +59,6 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.Warning.SomeNetworksUnreachable,
is WalletNotification.Warning.NetworksUnreachable,
-> null
is WalletNotification.RingPromo -> TokenSwapPromoAnalyticsEvent.NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Ring,
)
is WalletNotification.Critical.SeedPhraseNotification -> MainScreen.NoticeSeedPhraseSupport
}
}

View file

@ -4,9 +4,6 @@ import com.tangem.domain.common.CardTypesResolver
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.promo.PromoRepository
import com.tangem.domain.promo.ShouldShowRingPromoUseCase
import com.tangem.domain.promo.models.PromoBanner
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.model.CryptoCurrency
@ -22,7 +19,6 @@ import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.flow
import javax.inject.Inject
import kotlin.collections.count
@ -32,8 +28,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
private val tokenListStore: MultiWalletTokenListStore,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val shouldShowRingPromoUseCase: ShouldShowRingPromoUseCase,
private val promoRepository: PromoRepository,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val backupValidator: BackupValidator,
private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase,
@ -43,25 +37,13 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
val promoFlow = flow { emit(promoRepository.getRingPromoBanner()) }
return combine(
tokenListStore.getOrThrow(userWallet.walletId),
isReadyToShowRateAppUseCase(),
isNeedToBackupUseCase(userWallet.walletId),
shouldShowRingPromoUseCase(userWalletId = userWallet.walletId),
promoFlow,
seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
) {
val maybeTokenList = it[0] as Lce<TokenListError, TokenList>
val isReadyToShowRating = it[1] as Boolean
val isNeedToBackup = it[2] as Boolean
val shouldShowPromo = it[3] as Boolean
val promoBanner = it[4] as? PromoBanner
val seedPhraseIssueStatus = it[5] as Boolean
flow = tokenListStore.getOrThrow(userWallet.walletId),
flow2 = isReadyToShowRateAppUseCase(),
flow3 = isNeedToBackupUseCase(userWallet.walletId),
flow4 = seedPhraseNotificationUseCase(userWalletId = userWallet.walletId),
) { maybeTokenList, isReadyToShowRating, isNeedToBackup, seedPhraseIssueStatus ->
buildList {
addRingPromoNotification(shouldShowPromo, promoBanner, clickIntents)
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
addInformationalNotifications(cardTypesResolver, maybeTokenList, clickIntents)
@ -79,19 +61,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
}
}
private fun MutableList<WalletNotification>.addRingPromoNotification(
shouldShowPromo: Boolean,
promoBanner: PromoBanner?,
clickIntents: WalletClickIntents,
) {
promoBanner ?: return
val promoNotification = WalletNotification.RingPromo(onCloseClick = clickIntents::onCloseRingPromoClick)
addIf(
element = promoNotification,
condition = shouldShowPromo && promoBanner.isActive,
)
}
private fun MutableList<WalletNotification>.addCriticalNotifications(
userWallet: UserWallet,
seedPhraseIssueStatus: Boolean,

View file

@ -206,16 +206,6 @@ sealed class WalletNotification(val config: NotificationConfig) {
),
)
data class RingPromo(val onCloseClick: () -> Unit) : WalletNotification(
config = NotificationConfig(
title = resourceReference(id = R.string.ring_promo_title),
subtitle = resourceReference(id = R.string.ring_promo_text),
iconResId = R.drawable.img_card_with_ring,
backgroundResId = R.drawable.img_ring_promo_background,
onCloseClick = onCloseClick,
),
)
data class NoteMigration(val onClick: () -> Unit) : WalletNotification(
config = NotificationConfig(
title = resourceReference(R.string.wallet_promo_banner_title),

View file

@ -5,7 +5,7 @@ import com.tangem.common.ui.expressStatus.state.ExpressTransactionStateUM
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.LockedTxHistoryStateHolder
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.LockedWalletStateHolder
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder

View file

@ -1,7 +1,7 @@
package com.tangem.feature.wallet.presentation.wallet.state.model.holder
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification

View file

@ -1,6 +1,6 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.wallets.models.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import kotlinx.collections.immutable.PersistentList

View file

@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.utils
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.domain.common.util.cardTypesResolver
import com.tangem.domain.wallets.models.UserWallet
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory

View file

@ -15,9 +15,6 @@ import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.pullRefresh
import androidx.compose.material.pullrefresh.rememberPullRefreshState
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -61,7 +58,7 @@ import com.tangem.core.ui.components.snackbar.TangemSnackbar
import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
@ -285,7 +282,7 @@ private fun WalletContent(
}
@Suppress("LongParameterList", "LongMethod", "CyclomaticComplexMethod")
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private inline fun BaseScaffoldWithMarkets(
state: WalletScreenState,
@ -387,13 +384,6 @@ private inline fun BaseScaffoldWithMarkets(
}
},
content = { paddingValues ->
val pullRefreshState = rememberPullRefreshState(
refreshing = selectedWallet.pullToRefreshConfig.isRefreshing,
onRefresh = {
selectedWallet.pullToRefreshConfig.onRefresh(PullToRefreshConfig.ShowRefreshState())
},
)
Box {
MarketsHint(
modifier = Modifier
@ -405,16 +395,8 @@ private inline fun BaseScaffoldWithMarkets(
Column {
WalletTopBar(config = state.topBarConfig)
Box(
modifier = Modifier.pullRefresh(pullRefreshState),
) {
TangemPullToRefreshContainer(config = selectedWallet.pullToRefreshConfig) {
content(paddingValues)
WalletPullToRefreshIndicator(
isRefreshing = selectedWallet.pullToRefreshConfig.isRefreshing,
state = pullRefreshState,
modifier = Modifier.align(Alignment.TopCenter),
)
}
}

View file

@ -5,8 +5,6 @@ import androidx.compose.foundation.lazy.items
import androidx.compose.ui.Modifier
import com.tangem.core.ui.components.notifications.NoteMigrationNotification
import com.tangem.core.ui.components.notifications.Notification
import com.tangem.core.ui.components.notifications.OkxPromoNotification
import com.tangem.core.ui.components.notifications.RingPromoNotification
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import kotlinx.collections.immutable.ImmutableList
@ -28,16 +26,7 @@ internal fun LazyListScope.notifications(configs: ImmutableList<WalletNotificati
// TODO develop promo banner general component
when (it) {
is WalletNotification.SwapPromo -> {
OkxPromoNotification(
config = it.config,
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
)
}
is WalletNotification.RingPromo -> {
RingPromoNotification(
config = it.config,
modifier = modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
)
// Use it on new promo action
}
is WalletNotification.NoteMigration -> {
NoteMigrationNotification(

View file

@ -1,26 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.pullrefresh.PullRefreshIndicator
import androidx.compose.material.pullrefresh.PullRefreshState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
/**
* "Pull to refresh" indicator
*
* @param isRefreshing indicator is currently refreshing or not
* @param state indicator state
* @param modifier modifier
*
[REDACTED_AUTHOR]
*/
@OptIn(ExperimentalMaterialApi::class)
@Composable
internal fun WalletPullToRefreshIndicator(
isRefreshing: Boolean,
state: PullRefreshState,
modifier: Modifier = Modifier,
) {
PullRefreshIndicator(refreshing = isRefreshing, state = state, modifier = modifier)
}

View file

@ -107,32 +107,33 @@ private fun TransactionBlock(transaction: VisaTxDetailsBottomSheetConfig.Transac
)
}
@Suppress("LongMethod")
@Composable
private fun BlockchainRequestBlock(request: VisaTxDetailsBottomSheetConfig.Request, modifier: Modifier = Modifier) {
BlockContent(
modifier = modifier,
title = stringReference(value = "Blockchain request"),
description = {
if (request.onExploreClick == null) return
Row(
modifier = Modifier.clickable(onClick = request.onExploreClick),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
) {
Icon(
painter = painterResource(id = R.drawable.ic_compass_24),
contentDescription = null,
modifier = Modifier.size(size = TangemTheme.dimens.size18),
tint = TangemTheme.colors.icon.informative,
)
Text(
text = "Explore",
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption1,
)
if (request.onExploreClick != null) {
Row(
modifier = Modifier.clickable(onClick = request.onExploreClick),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing4),
) {
Icon(
painter = painterResource(id = R.drawable.ic_compass_24),
contentDescription = null,
modifier = Modifier.size(size = TangemTheme.dimens.size18),
tint = TangemTheme.colors.icon.informative,
)
Text(
text = "Explore",
color = TangemTheme.colors.text.tertiary,
style = TangemTheme.typography.caption1,
)
}
SpacerW12()
}
SpacerW12()
},
content = {
BlockItem(

View file

@ -2,11 +2,11 @@ package com.tangem.feature.wallet.presentation.wallet.ui.utils
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import com.google.android.gms.tasks.Task
import com.google.android.play.core.review.ReviewInfo
import com.google.android.play.core.review.ReviewManager
import com.google.android.play.core.review.ReviewManagerFactory
import com.tangem.core.ui.utils.findActivity
import timber.log.Timber
internal object ReviewManagerRequester {
@ -44,13 +44,4 @@ internal object ReviewManagerRequester {
Timber.e(task.exception)
}
}
private fun Context.findActivity(): Activity {
var context = this
while (context is ContextWrapper) {
if (context is Activity) return context
context = context.baseContext
}
error("Permissions should be called in the context of an Activity")
}
}

View file

@ -11,7 +11,6 @@ import com.tangem.domain.card.SetCardWasScannedUseCase
import com.tangem.domain.feedback.GetCardInfoUseCase
import com.tangem.domain.feedback.SendFeedbackEmailUseCase
import com.tangem.domain.feedback.models.FeedbackEmailType
import com.tangem.domain.promo.ShouldShowRingPromoUseCase
import com.tangem.domain.promo.ShouldShowSwapPromoWalletUseCase
import com.tangem.domain.redux.LegacyAction
import com.tangem.domain.redux.ReduxStateHolder
@ -66,8 +65,6 @@ internal interface WalletWarningsClickIntents {
fun onCloseSwapPromoClick()
fun onCloseRingPromoClick()
fun onSupportClick()
fun onNoteMigrationButtonClick(url: String)
@ -94,7 +91,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
private val reduxStateHolder: ReduxStateHolder,
private val dispatchers: CoroutineDispatcherProvider,
private val shouldShowSwapPromoWalletUseCase: ShouldShowSwapPromoWalletUseCase,
private val shouldShowRingPromoUseCase: ShouldShowRingPromoUseCase,
private val getCardInfoUseCase: GetCardInfoUseCase,
private val sendFeedbackEmailUseCase: SendFeedbackEmailUseCase,
private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase,
@ -249,7 +245,7 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
analyticsEventHandler.send(
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.OKX,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Empty, // Use it on new promo action
action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed,
),
)
@ -258,20 +254,6 @@ internal class WalletWarningsClickIntentsImplementor @Inject constructor(
}
}
override fun onCloseRingPromoClick() {
analyticsEventHandler.send(
TokenSwapPromoAnalyticsEvent.PromotionBannerClicked(
source = AnalyticsParam.ScreensSources.Main,
programName = TokenSwapPromoAnalyticsEvent.ProgramName.Ring,
action = TokenSwapPromoAnalyticsEvent.PromotionBannerClicked.BannerAction.Closed,
),
)
viewModelScope.launch {
shouldShowRingPromoUseCase.neverToShow()
}
}
override fun onSupportClick() {
val scanResponse = getSelectedUserWallet()?.scanResponse ?: return
val cardInfo = getCardInfoUseCase(scanResponse).getOrNull() ?: return