Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-19 18:23:22 +03:00
commit ae15bcfc55
211 changed files with 4715 additions and 3641 deletions

View file

@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
@ -14,6 +15,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
@ -23,6 +25,7 @@ import com.tangem.feature.wallet.child.wallet.model.WalletModel
import com.tangem.feature.wallet.navigation.WalletRoute
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen2
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejectedComponent
import com.tangem.feature.walletsettings.component.RenameWalletComponent
import com.tangem.features.biometry.AskBiometryComponent
@ -38,6 +41,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.launch
@OptIn(ExperimentalDecomposeApi::class)
@Suppress("LongParameterList")
internal class WalletComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@ -50,6 +54,7 @@ internal class WalletComponent @AssistedInject constructor(
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
private val yieldSupplyDepositedWarningComponent: YieldSupplyDepositedWarningComponent.Factory,
private val feedFeatureToggle: FeedFeatureToggle,
private val designFeatureToggles: DesignFeatureToggles,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: WalletModel = getOrCreateModel()
@ -148,18 +153,33 @@ internal class WalletComponent @AssistedInject constructor(
var headerSize by remember { mutableStateOf(0.dp) }
val dialog by dialog.subscribeAsState()
WalletScreen(
state = model.uiState.collectAsStateWithLifecycle().value,
bottomSheetContent = {
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
modifier = modifier,
)
},
bottomSheetHeaderHeightProvider = { headerSize },
onBottomSheetStateChange = { bottomSheetState.value = it },
)
if (designFeatureToggles.isRedesignEnabled) {
WalletScreen2(
state = model.uiState.collectAsStateWithLifecycle().value,
bottomSheetContent = {
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
modifier = modifier,
)
},
bottomSheetHeaderHeightProvider = { headerSize },
onBottomSheetStateChange = { bottomSheetState.value = it },
)
} else {
WalletScreen(
state = model.uiState.collectAsStateWithLifecycle().value,
bottomSheetContent = {
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
modifier = modifier,
)
},
bottomSheetHeaderHeightProvider = { headerSize },
onBottomSheetStateChange = { bottomSheetState.value = it },
)
}
when (val dialog = dialog.child?.instance) {
is ComposableDialogComponent -> dialog.Dialog()

View file

@ -45,9 +45,7 @@ import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvid
import com.tangem.features.biometry.AskBiometryComponent
import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import kotlinx.coroutines.*
@ -90,10 +88,8 @@ internal class WalletModel @Inject constructor(
private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase,
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
private val userWalletsListRepository: UserWalletsListRepository,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val yieldSupplyApyUpdateUseCase: YieldSupplyApyUpdateUseCase,
private val tangemPayOnboardingRepository: OnboardingRepository,
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
private val getAppThemeModeUseCase: GetAppThemeModeUseCase,
@ -166,10 +162,8 @@ internal class WalletModel @Inject constructor(
}
private fun updateYieldSupplyApy() {
if (yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled) {
modelScope.launch(dispatchers.default) {
yieldSupplyApyUpdateUseCase()
}
modelScope.launch(dispatchers.default) {
yieldSupplyApyUpdateUseCase()
}
}
@ -394,7 +388,6 @@ internal class WalletModel @Inject constructor(
* Update state each time a user opens/returns to wallet screen
* and every minute while user stays on the main screen
*/
if (!tangemPayFeatureToggles.isTangemPayEnabled) return
combine(
flow = screenLifecycleProvider.isBackgroundState,

View file

@ -12,6 +12,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletType
import timber.log.Timber
import javax.inject.Inject
@ -110,7 +111,7 @@ internal class WalletsUpdateActionResolver @Inject constructor(
when (walletState) {
is WalletState.MultiCurrency -> {
val wallet = wallets.firstOrNull { it.walletId == walletState.walletCardState.id }
walletState.type == WalletState.MultiCurrency.WalletType.Hot && wallet is UserWallet.Cold
walletState.type == WalletType.Hot && wallet is UserWallet.Cold
}
else -> false
}
@ -212,7 +213,7 @@ internal class WalletsUpdateActionResolver @Inject constructor(
val previousState = state.wallets.firstOrNull { it.walletCardState.id == wallet.walletId }
?: return@filter false
wallet is UserWallet.Cold && previousState is WalletState.MultiCurrency &&
previousState.type == WalletState.MultiCurrency.WalletType.Hot
previousState.type == WalletType.Hot
}
return Action.ReinitializeWallets(selectedWallet, walletsToUpdate)
}

View file

@ -27,7 +27,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogCon
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayHideOnboardingStateTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshNeededStateTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.TangemPayRefreshShowProgressTransformer
import com.tangem.features.tangempay.TangemPayFeatureToggles
import kotlinx.coroutines.launch
import javax.inject.Inject
@ -64,7 +63,6 @@ internal interface TangemPayIntents {
@ModelScoped
internal class TangemPayClickIntentsImplementor @Inject constructor(
private val stateHolder: WalletStateController,
private val featureToggles: TangemPayFeatureToggles,
private val onboardingRepository: OnboardingRepository,
private val produceInitialDataTangemPay: ProduceTangemPayInitialDataUseCase,
private val getWalletMetainfoUseCase: GetWalletMetaInfoUseCase,
@ -77,9 +75,7 @@ internal class TangemPayClickIntentsImplementor @Inject constructor(
override suspend fun onPullToRefresh() {
val userWalletId = stateHolder.getSelectedWalletId()
if (!featureToggles.isTangemPayEnabled ||
!onboardingRepository.isTangemPayInitialDataProduced(userWalletId)
) {
if (!onboardingRepository.isTangemPayInitialDataProduced(userWalletId)) {
return
}
tangemPayMainScreenCustomerInfoUseCase.fetch(userWalletId)

View file

@ -11,8 +11,10 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.analytics.models.event.OfframpAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.TextReference
@ -36,13 +38,12 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.offramp.GetOfframpUrlUseCase
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.staking.model.StakingOption
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
@ -63,7 +64,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
@ -136,14 +136,14 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val getStoryContentUseCase: GetStoryContentUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val dispatchers: CoroutineDispatcherProvider,
private val reduxStateHolder: ReduxStateHolder,
private val getOfframpUrlUseCase: GetOfframpUrlUseCase,
private val urlOpener: UrlOpener,
private val vibratorHapticManager: VibratorHapticManager,
private val clipboardManager: ClipboardManager,
private val appRouter: AppRouter,
private val rampStateManager: RampStateManager,
private val saveViewedTokenReceiveWarningUseCase: SaveViewedTokenReceiveWarningUseCase,
private val receiveAddressesFactory: ReceiveAddressesFactory,
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
private val needShowYieldSupplyDepositedWarningUseCase: NeedShowYieldSupplyDepositedWarningUseCase,
private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase,
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
@ -335,12 +335,13 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
showErrorIfDemoModeOrElse {
modelScope.launch(dispatchers.main) {
reduxStateHolder.dispatch(
action = TradeCryptoAction.Sell(
cryptoCurrencyStatus = cryptoCurrencyStatus,
appCurrencyCode = getSelectedAppCurrencyUseCase.unwrap().code,
),
)
getOfframpUrlUseCase(
cryptoCurrencyStatus = cryptoCurrencyStatus,
appCurrencyCode = getSelectedAppCurrencyUseCase.unwrap().code,
).onRight { url ->
urlOpener.openUrl(url)
analyticsEventHandler.send(OfframpAnalyticsEvent.ScreenOpened)
}
}
}
}
@ -472,9 +473,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
override fun onMultiWalletSwapClick(userWalletId: UserWalletId) {
val selectedWallet = stateHolder.getSelectedWallet() as? WalletState.MultiCurrency.Content ?: return
val tokenListState = selectedWallet.tokensListState
when (tokenListState) {
when (val tokenListState = selectedWallet.tokensListState) {
is WalletTokensListState.ContentState.Content -> checkSwapCryptoAvailability(
tokenCount = tokenListState.items.count { it is TokensListItemUM.Token },
)
@ -663,8 +662,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
private suspend fun needShowYieldSupplyWarning(cryptoCurrencyStatus: CryptoCurrencyStatus): Boolean {
return yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled &&
needShowYieldSupplyDepositedWarningUseCase(cryptoCurrencyStatus)
return needShowYieldSupplyDepositedWarningUseCase(cryptoCurrencyStatus)
}
private fun navigateToSend(cryptoCurrencyStatus: CryptoCurrencyStatus, userWalletId: UserWalletId) {

View file

@ -1,6 +1,5 @@
package com.tangem.feature.wallet.presentation.common
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.event.consumedEvent
@ -186,17 +185,6 @@ internal object WalletPreviewData {
)
}
val bottomSheet by lazy {
TangemBottomSheetConfig(
isShown = false,
onDismissRequest = {},
content = WalletBottomSheetConfig.UnlockWallets(
onUnlockClick = {},
onScanClick = {},
),
)
}
val actionsBottomSheet = ActionsBottomSheetConfig(
actions = listOf(
TokenActionButtonConfig(

View file

@ -186,7 +186,7 @@ internal object WalletScreenPreviewData {
onItemClick = { },
),
tangemPayState = TangemPayState.Empty,
type = WalletState.MultiCurrency.WalletType.Cold,
type = WalletType.Cold,
)
}
@ -218,6 +218,7 @@ internal object WalletScreenPreviewData {
singleWalletLockedState,
multiWalletState,
),
wallets2 = persistentListOf(),
onWalletChange = { _, _ -> },
event = consumedEvent(),
isHidingMode = false,

View file

@ -0,0 +1,38 @@
package com.tangem.feature.wallet.presentation.preview
import com.tangem.core.ui.extensions.combinedReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.styledStringReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBalanceUM
internal object WalletBalancePreview {
val content: WalletBalanceUM.Content = WalletBalanceUM.Content(
id = UserWalletId("0"),
name = "My Wallet",
balance = combinedReference(
stringReference("1,234"),
styledStringReference(
".56",
{
TangemTheme.typography2.headingRegular28.toSpanStyle()
},
),
stringReference(" $"),
),
isBalanceFlickering = false,
isZeroBalance = false,
)
val loading: WalletBalanceUM.Loading = WalletBalanceUM.Loading(
id = UserWalletId("1"),
name = "My Wallet",
)
val error: WalletBalanceUM.Error = WalletBalanceUM.Error(
id = UserWalletId("2"),
name = "My Wallet",
)
}

View file

@ -8,9 +8,8 @@ import com.tangem.domain.tokens.model.analytics.PromoAnalyticsEvent.*
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.MainScreen.*
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.PushBannerPromo.*
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent.PushBannerPromo.PushBanner
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
import javax.inject.Inject
@ -34,10 +33,29 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
}
}
fun send(displayedWalletUM: WalletUM?, newNotifications: List<WalletNotificationUM>) {
if (screenLifecycleProvider.isBackgroundState.value) return
if (newNotifications.isEmpty()) return
if (displayedWalletUM == null || displayedWalletUM.pullToRefreshConfig.isRefreshing) return
val totalNotifications = displayedWalletUM.notifications + displayedWalletUM.notificationsCarousel
val notificationsDiff = newNotifications.filter { it !in totalNotifications }
val eventsToSend = getEvents2(notificationsDiff)
eventsToSend.forEach { event ->
analyticsEventHandler.send(event)
}
}
private fun getEvents(warnings: List<WalletNotification>): Set<AnalyticsEvent> {
return warnings.mapNotNullTo(mutableSetOf(), ::getEvent)
}
private fun getEvents2(notifications: List<WalletNotificationUM>): Set<AnalyticsEvent> {
return notifications.mapNotNullTo(mutableSetOf(), ::getEvent2)
}
@Suppress("CyclomaticComplexMethod")
private fun getEvent(warning: WalletNotification): AnalyticsEvent? {
return when (warning) {
@ -106,4 +124,53 @@ internal class WalletWarningsAnalyticsSender @Inject constructor(
is WalletNotification.UpgradeHotWalletPromo -> null
}
}
@Suppress("CyclomaticComplexMethod")
private fun getEvent2(notificationUM: WalletNotificationUM): AnalyticsEvent? {
return when (notificationUM) {
WalletNotificationUM.DevCard -> DevelopmentCard()
WalletNotificationUM.FailedCardValidation -> ProductSampleCard()
is WalletNotificationUM.MissingBackup -> BackupYourWallet()
is WalletNotificationUM.NumberOfSignedHashesIncorrect -> CardSignedTransactions()
WalletNotificationUM.TestnetCard -> TestnetCard()
WalletNotificationUM.DemoCard -> DemoCard()
is WalletNotificationUM.MissingAddresses -> MissingAddresses()
is WalletNotificationUM.RateApp -> HowDoYouLikeTangem()
is WalletNotificationUM.BackupError -> BackupError()
is WalletNotificationUM.NoteMigration -> NotePromo()
is WalletNotificationUM.OnePlusOnePromo -> NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
program = Program.OnePlusOne,
)
is WalletNotificationUM.YieldPromo -> NoticePromotionBanner(
source = AnalyticsParam.ScreensSources.Main,
program = Program.YieldPromo,
)
is WalletNotificationUM.FinishWalletActivation -> {
val activationState = if (notificationUM.isBackupExists) {
NoticeFinishActivation.ActivationState.Unfinished
} else {
NoticeFinishActivation.ActivationState.NotStarted
}
val balanceState = when (notificationUM.type) {
WalletNotificationType.Warning -> AnalyticsParam.EmptyFull.Full
else -> AnalyticsParam.EmptyFull.Empty
}
NoticeFinishActivation(
activationState = activationState,
balanceState = balanceState,
)
}
is WalletNotificationUM.SeedPhraseNotification -> NoticeSeedPhraseSupport()
is WalletNotificationUM.SeedPhraseSecondNotification -> NoticeSeedPhraseSupportSecond()
is WalletNotificationUM.PushNotifications -> PushBanner()
is WalletNotificationUM.UnlockWallets,
is WalletNotificationUM.NoAccount,
is WalletNotificationUM.LowSignatures,
WalletNotificationUM.SomeNetworksUnreachable,
is WalletNotificationUM.UsedOutdatedData,
is WalletNotificationUM.CloreMigration,
-> null
}
}
}

View file

@ -4,12 +4,8 @@ import com.tangem.common.routing.AppRoute.WalletBackup
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.navigation.Router
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.ui.components.bottomsheets.message.MessageBottomSheetUMV2
import com.tangem.core.ui.components.bottomsheets.message.icon
import com.tangem.core.ui.components.bottomsheets.message.infoBlock
import com.tangem.core.ui.components.bottomsheets.message.onClick
import com.tangem.core.ui.components.bottomsheets.message.primaryButton
import com.tangem.core.ui.components.bottomsheets.message.secondaryButton
import com.tangem.core.ui.components.bottomsheets.message.*
import com.tangem.core.ui.ds.message.TangemMessageEffect
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.message.bottomSheetMessage
import com.tangem.domain.models.wallet.UserWallet
@ -19,7 +15,9 @@ import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotificationUM
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
@ -71,6 +69,44 @@ internal class WalletWarningsSingleEventSender @Inject constructor(
}
}
suspend fun send(
userWalletId: UserWalletId,
displayedWalletUM: WalletUM?,
newNotifications: List<WalletNotificationUM>,
) {
if (screenLifecycleProvider.isBackgroundState.value) return
if (newNotifications.isEmpty()) return
if (displayedWalletUM == null || displayedWalletUM.pullToRefreshConfig.isRefreshing) return
val totalNotifications = displayedWalletUM.notifications + displayedWalletUM.notificationsCarousel
val events = newNotifications.filter { it !in totalNotifications }
// We must show activation bs only for the first seen wallet when open the app (if need, see conditions below),
// so we keep this wallet id and use for future checks, ignore other wallets during the app session.
if (isActivationBottomSheetShown.isEmpty()) {
isActivationBottomSheetShown[userWalletId] = false
}
events.forEach { event ->
when (event) {
is WalletNotificationUM.SeedPhraseNotification -> {
seedPhraseNotificationUseCase.notified(userWalletId = userWalletId)
}
is WalletNotificationUM.FinishWalletActivation -> {
// We check that map contains the first seen wallet (will return null instead false/true otherwise)
// and for this wallet we haven't shown the activation bs yet (check that returns false, not true)
if (isActivationBottomSheetShown[userWalletId] == false) {
if (event.messageEffect == TangemMessageEffect.Warning && event.isBackupExists.not()) {
showFinishActivationBottomSheet(userWalletId)
}
isActivationBottomSheetShown[userWalletId] = true
}
}
else -> Unit
}
}
}
private fun showFinishActivationBottomSheet(userWalletId: UserWalletId) {
val userWallet = getUserWalletUseCase(userWalletId).getOrNull() ?: return
if (userWallet !is UserWallet.Hot) return

View file

@ -0,0 +1,131 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.common.TangemSiteUrlBuilder
import com.tangem.common.ui.notifications.NotificationId
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.notifications.repository.NotificationsRepository
import com.tangem.domain.promo.ShouldShowPromoWalletUseCase
import com.tangem.domain.promo.models.PromoId
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotificationUM
import com.tangem.utils.extensions.addIf
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import javax.inject.Inject
/**
* Factory for creating a list of notifications that can be shown on the wallet screen.
* These notifications are not critical and can be stacked with each other.
*/
@ModelScoped
internal class GetWalletNotificationsCarouselFactory @Inject constructor(
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val shouldShowPromoWalletUseCase: ShouldShowPromoWalletUseCase,
private val getWalletsUseCase: GetWalletsUseCase,
private val notificationsRepository: NotificationsRepository,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotificationUM>> {
return combine(
flow = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.YieldPromo)
.distinctUntilChanged(),
flow2 = notificationsRepository.getShouldShowNotification(
NotificationId.EnablePushesReminderNotification.key,
).distinctUntilChanged(),
flow3 = shouldShowPromoWalletUseCase(userWalletId = userWallet.walletId, promoId = PromoId.OnePlusOne)
.distinctUntilChanged(),
flow4 = isReadyToShowRateAppUseCase().distinctUntilChanged(),
flow5 = getWalletsUseCase().conflate(),
) { showYieldPromo, showPushesNotification, showOnePlusOnePromo, showRateAppPromo, wallets ->
buildList {
addNoteMigrationNotification(userWallet, wallets, clickIntents)
addRateAppNotification(showRateAppPromo, clickIntents)
addOnePlusOnePromoNotification(clickIntents, showOnePlusOnePromo)
addYieldPromoNotification(clickIntents, showYieldPromo)
addPushNotification(
shouldShow = showPushesNotification,
isPushesAllowed = notificationsRepository.isUserAllowToSubscribeOnPushNotifications(),
clickIntents = clickIntents,
)
}.sortedBy { it.type.ordinal }.toImmutableList()
}
}
private fun MutableList<WalletNotificationUM>.addRateAppNotification(
isReadyToShowRating: Boolean,
clickIntents: WalletClickIntents,
) {
addIf(isReadyToShowRating) {
WalletNotificationUM.RateApp(
onLikeClick = clickIntents::onLikeAppClick,
onDislikeClick = clickIntents::onDislikeAppClick,
onCloseClick = clickIntents::onCloseRateAppWarningClick,
)
}
}
private fun MutableList<WalletNotificationUM>.addYieldPromoNotification(
clickIntents: WalletClickIntents,
shouldShowPromo: Boolean,
) {
addIf(shouldShowPromo) {
WalletNotificationUM.YieldPromo(
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.YieldPromo) },
onTermsAndConditionsClick = { clickIntents.onYieldPromoTermsAndConditionsClick() },
)
}
}
private fun MutableList<WalletNotificationUM>.addOnePlusOnePromoNotification(
clickIntents: WalletClickIntents,
shouldShowPromo: Boolean,
) {
addIf(shouldShowPromo) {
WalletNotificationUM.OnePlusOnePromo(
onCloseClick = { clickIntents.onClosePromoClick(promoId = PromoId.OnePlusOne) },
onClick = { clickIntents.onPromoClick(promoId = PromoId.OnePlusOne) },
)
}
}
private fun MutableList<WalletNotificationUM>.addNoteMigrationNotification(
userWallet: UserWallet,
userWallets: List<UserWallet>,
clickIntents: WalletClickIntents,
) {
val cardTypesResolver = (userWallet as? UserWallet.Cold)?.scanResponse?.cardTypesResolver
val isUserHasWalletOrWallet2 = userWallets.filterIsInstance<UserWallet.Cold>().any { wallet ->
val typesResolver = wallet.scanResponse.cardTypesResolver
typesResolver.isTangemWallet() || typesResolver.isWallet2()
}
addIf(cardTypesResolver != null && cardTypesResolver.isTangemNote() && !isUserHasWalletOrWallet2) {
WalletNotificationUM.NoteMigration(
onClick = { clickIntents.onNoteMigrationButtonClick(TangemSiteUrlBuilder.NOTE_MIGRATION_URL) },
)
}
}
private fun MutableList<WalletNotificationUM>.addPushNotification(
shouldShow: Boolean,
isPushesAllowed: Boolean,
clickIntents: WalletClickIntents,
) {
addIf(shouldShow && !isPushesAllowed) {
WalletNotificationUM.PushNotifications(
onCloseClick = clickIntents::onDenyPermissions,
onEnabledClick = clickIntents::onAllowPermissions,
)
}
}
}

View file

@ -0,0 +1,326 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.common.ui.userwallet.ext.walletInterationIcon
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.ui.ds.message.TangemMessageEffect
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
import com.tangem.domain.card.CardTypesResolver
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.hotwallet.GetAccessCodeSkippedUseCase
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.wallets.models.SeedPhraseNotificationsStatus
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
import com.tangem.domain.wallets.usecase.SeedPhraseNotificationUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotificationUM
import com.tangem.hot.sdk.model.HotWalletId
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.extensions.addIf
import com.tangem.utils.extensions.isPositive
import com.tangem.utils.extensions.orZero
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.*
import javax.inject.Inject
/**
* Factory for creating a list of notifications that can be shown on the wallet screen.
* These notifications are critical and should be shown separately from each other.
*/
@Suppress("LongParameterList")
@ModelScoped
internal class GetWalletWarningsFactory @Inject constructor(
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val backupValidator: BackupValidator,
private val seedPhraseNotificationUseCase: SeedPhraseNotificationUseCase,
private val accountDependencies: AccountDependencies,
private val getAccessCodeSkippedUseCase: GetAccessCodeSkippedUseCase,
private val hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotificationUM>> {
val cardTypesResolver = (userWallet as? UserWallet.Cold)?.scanResponse?.cardTypesResolver
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
val accountStatusListFlow = accountDependencies.singleAccountStatusListSupplier(params)
return combine(
flow = accountStatusListFlow,
flow2 = isNeedToBackupUseCase(userWallet.walletId).distinctUntilChanged(),
flow3 = seedPhraseNotificationUseCase(userWalletId = userWallet.walletId).distinctUntilChanged(),
flow4 = getAccessCodeSkippedUseCase(userWallet.walletId).distinctUntilChanged(),
) { accountList, isNeedToBackup, seedPhraseIssueStatus, shouldAccessCodeSkipped ->
val totalFiatBalance = accountList.totalFiatBalance
val flattenCurrencies = accountList.flattenCurrencies()
buildList {
addUsedOutdatedDataNotification(totalFiatBalance)
addCriticalNotifications(userWallet, seedPhraseIssueStatus, clickIntents)
addFinishWalletActivationNotification(
userWallet = userWallet,
totalFiatBalance = totalFiatBalance,
clickIntents = clickIntents,
shouldAccessCodeSkipped = shouldAccessCodeSkipped,
)
addInformationalNotifications(
userWallet = userWallet,
cardTypesResolver = cardTypesResolver,
flattenCurrencies = flattenCurrencies,
clickIntents = clickIntents,
)
addWarningNotifications(
userWallet = userWallet,
cardTypesResolver = cardTypesResolver,
flattenCurrencies = flattenCurrencies,
isNeedToBackup = isNeedToBackup,
clickIntents = clickIntents,
)
}.sortedBy { it.type.ordinal }.toImmutableList()
}
}
private fun MutableList<WalletNotificationUM>.addUsedOutdatedDataNotification(totalFiatBalance: TotalFiatBalance) {
addIf(
element = WalletNotificationUM.UsedOutdatedData,
condition = (totalFiatBalance as? TotalFiatBalance.Loaded)?.source == StatusSource.ONLY_CACHE,
)
}
private fun MutableList<WalletNotificationUM>.addCriticalNotifications(
userWallet: UserWallet,
seedPhraseIssueStatus: SeedPhraseNotificationsStatus,
clickIntents: WalletClickIntents,
) {
if (userWallet !is UserWallet.Cold) {
return
}
addSeedNotificationIfNeeded(userWallet, seedPhraseIssueStatus, clickIntents)
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
addIf(
element = WalletNotificationUM.BackupError { clickIntents.onSupportClick() },
condition = !backupValidator.isValidBackupStatus(userWallet.scanResponse.card) || userWallet.hasBackupError,
)
addIf(
element = WalletNotificationUM.DevCard,
condition = !cardTypesResolver.isReleaseFirmwareType(),
)
addIf(
element = WalletNotificationUM.FailedCardValidation,
condition = cardTypesResolver.isReleaseFirmwareType() && cardTypesResolver.isAttestationFailed(),
)
cardTypesResolver.getRemainingSignatures()?.let { remainingSignatures ->
addIf(
element = WalletNotificationUM.LowSignatures(count = remainingSignatures),
condition = remainingSignatures <= MAX_REMAINING_SIGNATURES_COUNT,
)
}
}
private fun MutableList<WalletNotificationUM>.addInformationalNotifications(
userWallet: UserWallet,
cardTypesResolver: CardTypesResolver?,
flattenCurrencies: List<CryptoCurrencyStatus>,
clickIntents: WalletClickIntents,
) {
addIf(
element = WalletNotificationUM.DemoCard,
condition = cardTypesResolver != null && isDemoCardUseCase(cardId = cardTypesResolver.getCardId()),
)
addMissingAddressesNotification(userWallet, flattenCurrencies, clickIntents)
}
private fun MutableList<WalletNotificationUM>.addMissingAddressesNotification(
userWallet: UserWallet,
flattenCurrencies: List<CryptoCurrencyStatus>,
clickIntents: WalletClickIntents,
) {
val currencies = flattenCurrencies.getMissingAddressCurrencies().ifEmpty { return }
addIf(
element = WalletNotificationUM.MissingAddresses(
tangemIcon = walletInterationIcon(userWallet),
missingAddressesCount = currencies.count(),
onGenerateClick = {
clickIntents.onGenerateMissedAddressesClick(missedAddressCurrencies = currencies)
},
),
condition = currencies.isNotEmpty(),
)
}
private fun List<CryptoCurrencyStatus>.getMissingAddressCurrencies(): List<CryptoCurrency> {
return this
.filter { it.value is CryptoCurrencyStatus.MissedDerivation }
.map(CryptoCurrencyStatus::currency)
}
private suspend fun MutableList<WalletNotificationUM>.addWarningNotifications(
userWallet: UserWallet,
cardTypesResolver: CardTypesResolver?,
flattenCurrencies: List<CryptoCurrencyStatus>,
isNeedToBackup: Boolean,
clickIntents: WalletClickIntents,
) {
addIf(
element = WalletNotificationUM.MissingBackup(
onClick = clickIntents::onAddBackupCardClick,
),
condition = isNeedToBackup,
)
addIf(
element = WalletNotificationUM.TestnetCard,
condition = cardTypesResolver?.isTestCard() == true,
)
addIf(
element = WalletNotificationUM.SomeNetworksUnreachable,
condition = flattenCurrencies.hasUnreachableNetworks(),
)
addCloreMigrationNotification(flattenCurrencies, clickIntents)
addNoAccountWarning(cryptoCurrencyStatus = flattenCurrencies.firstOrNull())
addIf(
element = WalletNotificationUM.NumberOfSignedHashesIncorrect(
onCloseClick = clickIntents::onCloseAlreadySignedHashesWarningClick,
),
condition = hasSignedHashes(userWallet, flattenCurrencies.firstOrNull()),
)
}
private fun MutableList<WalletNotificationUM>.addNoAccountWarning(cryptoCurrencyStatus: CryptoCurrencyStatus?) {
val noAccountStatus = cryptoCurrencyStatus?.value as? CryptoCurrencyStatus.NoAccount
if (noAccountStatus != null) {
add(
element = WalletNotificationUM.NoAccount(
network = cryptoCurrencyStatus.currency.name,
amount = noAccountStatus.amountToCreateAccount.toString(),
symbol = cryptoCurrencyStatus.currency.symbol,
),
)
}
}
private fun MutableList<WalletNotificationUM>.addCloreMigrationNotification(
flattenCurrencies: List<CryptoCurrencyStatus>,
clickIntents: WalletClickIntents,
) {
val cloreCurrency = flattenCurrencies.findCloreCurrency() ?: return
add(
WalletNotificationUM.CloreMigration(
onStartMigrationClick = { clickIntents.onCloreMigrationClick(cloreCurrency) },
),
)
}
private fun List<CryptoCurrencyStatus>.findCloreCurrency(): CryptoCurrencyStatus? {
return find { currencyStatus ->
BlockchainUtils.isClore(currencyStatus.currency.network.rawId)
}
}
private fun List<CryptoCurrencyStatus>.hasUnreachableNetworks(): Boolean {
return any { it.value is CryptoCurrencyStatus.Unreachable }
}
private fun MutableList<WalletNotificationUM>.addFinishWalletActivationNotification(
userWallet: UserWallet,
totalFiatBalance: TotalFiatBalance,
clickIntents: WalletClickIntents,
shouldAccessCodeSkipped: Boolean,
) {
if (userWallet !is UserWallet.Hot) return
val isBackupExists = userWallet.backedUp
val isAccessCodeRequired = userWallet.hotWalletId.authType == HotWalletId.AuthType.NoPassword &&
!shouldAccessCodeSkipped
val shouldShowFinishActivation = !isBackupExists || isAccessCodeRequired
val messageEffect = when (totalFiatBalance) {
TotalFiatBalance.Failed,
TotalFiatBalance.Loading,
-> TangemMessageEffect.None
is TotalFiatBalance.Loaded -> if (totalFiatBalance.amount.orZero().isPositive()) {
TangemMessageEffect.Warning
} else {
TangemMessageEffect.None
}
}
addIf(
element = WalletNotificationUM.FinishWalletActivation(
messageEffect = messageEffect,
onClick = { clickIntents.onFinishWalletActivationClick(isBackupExists) },
isBackupExists = isBackupExists,
),
condition = shouldShowFinishActivation,
)
}
private fun MutableList<WalletNotificationUM>.addSeedNotificationIfNeeded(
userWallet: UserWallet.Cold,
seedPhraseIssueStatus: SeedPhraseNotificationsStatus,
clickIntents: WalletClickIntents,
) {
val isNotificationAvailable = with(userWallet) {
val isDemo = isDemoCardUseCase(cardId = userWallet.cardId)
val isWalletWithSeedPhrase = scanResponse.cardTypesResolver.isWallet2() && userWallet.isImported
!isDemo && isWalletWithSeedPhrase
}
when (seedPhraseIssueStatus) {
SeedPhraseNotificationsStatus.SHOW_FIRST -> addIf(
element = WalletNotificationUM.SeedPhraseNotification(
onDeclineClick = clickIntents::onSeedPhraseNotificationDecline,
onConfirmClick = clickIntents::onSeedPhraseNotificationConfirm,
),
condition = isNotificationAvailable,
)
SeedPhraseNotificationsStatus.SHOW_SECOND -> addIf(
element = WalletNotificationUM.SeedPhraseSecondNotification(
onDeclineClick = clickIntents::onSeedPhraseSecondNotificationReject,
onConfirmClick = clickIntents::onSeedPhraseSecondNotificationAccept,
),
condition = isNotificationAvailable,
)
SeedPhraseNotificationsStatus.NOT_NEEDED -> Unit
}
}
private suspend fun hasSignedHashes(
selectedWallet: UserWallet,
cryptoCurrencyStatus: CryptoCurrencyStatus?,
): Boolean {
if (selectedWallet !is UserWallet.Cold || !selectedWallet.isMultiCurrency) return false
val network = cryptoCurrencyStatus?.currency?.network ?: return false
return hasSingleWalletSignedHashesUseCase(userWallet = selectedWallet, network = network)
.conflate()
.distinctUntilChanged()
.firstOrNull() == true
}
private companion object {
const val MAX_REMAINING_SIGNATURES_COUNT = 10
}
}

View file

@ -20,7 +20,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.features.tangempay.TangemPayFeatureToggles
@Suppress("LongParameterList")
@Deprecated("Use MultiWalletContentLoaderV2 instead")
@ -44,7 +43,6 @@ internal class MultiWalletContentLoader(
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
) : WalletContentLoader(id = userWallet.walletId) {
@ -88,9 +86,7 @@ internal class MultiWalletContentLoader(
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
if (tangemPayFeatureToggles.isTangemPayEnabled) {
add(tangemPayMainSubscriberFactory.create(userWallet))
}
add(tangemPayMainSubscriberFactory.create(userWallet))
}
}
}

View file

@ -20,7 +20,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.MultiWalletTokenList
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.TangemPayMainSubscriber
import com.tangem.features.tangempay.TangemPayFeatureToggles
import javax.inject.Inject
@Suppress("LongParameterList")
@ -43,7 +42,6 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
) {
@ -66,7 +64,6 @@ internal class MultiWalletContentLoaderFactory @Inject constructor(
currenciesRepository = currenciesRepository,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
tangemPayFeatureToggles = tangemPayFeatureToggles,
tangemPayMainSubscriberFactory = tangemPayMainSubscriberFactory,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
)

View file

@ -8,7 +8,6 @@ import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarni
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.features.tangempay.TangemPayFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -25,11 +24,10 @@ internal class MultiWalletContentLoaderV2 @AssistedInject constructor(
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> = listOfNotNull(
override fun create(): List<WalletSubscriber> = listOf(
accountListSubscriberFactory.create(userWallet = userWallet),
walletNFTListSubscriberV2Factory.create(userWallet = userWallet),
checkWalletWithFundsSubscriberFactory.create(userWallet = userWallet),
@ -46,12 +44,7 @@ internal class MultiWalletContentLoaderV2 @AssistedInject constructor(
stateHolder = stateController,
getStoryContentUseCase = getStoryContentUseCase,
),
if (tangemPayFeatureToggles.isTangemPayEnabled) {
tangemPayMainSubscriberFactory.create(userWallet)
} else {
null
},
tangemPayMainSubscriberFactory.create(userWallet),
)
@AssistedFactory

View file

@ -1,12 +1,10 @@
package com.tangem.feature.wallet.presentation.wallet.state
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.event.consumedEvent
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarConfig
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.OpenBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.WalletScreenStateTransformer
@ -25,7 +23,9 @@ import javax.inject.Singleton
[REDACTED_AUTHOR]
*/
@Singleton
internal class WalletStateController @Inject constructor() {
internal class WalletStateController @Inject constructor(
private val designFeatureToggles: DesignFeatureToggles,
) {
val uiState: StateFlow<WalletScreenState> get() = mutableUiState
@ -53,6 +53,10 @@ internal class WalletStateController @Inject constructor() {
return value.wallets.firstOrNull { it.walletCardState.id == userWalletId }
}
fun getWalletUM(userWalletId: UserWalletId): WalletUM? {
return value.wallets2.firstOrNull { it.walletsBalanceUM.id == userWalletId }
}
fun getWalletStateIfSelected(walletId: UserWalletId): WalletState? {
val selectedWalletId = getSelectedWalletId()
@ -61,16 +65,40 @@ internal class WalletStateController @Inject constructor() {
}
}
fun getWalletUMIfSelected(walletId: UserWalletId): WalletUM? {
val selectedWalletId = getSelectedWalletId()
return value.wallets2.firstOrNull {
it.walletsBalanceUM.id == walletId && it.walletsBalanceUM.id == selectedWalletId
}
}
fun getSelectedWallet(): WalletState {
return with(value) { wallets[selectedWalletIndex] }
}
fun getSelectedWalletUM(): WalletUM {
return with(value) { wallets2[selectedWalletIndex] }
}
fun getSelectedWalletId(): UserWalletId {
return with(value) { wallets[selectedWalletIndex].walletCardState.id }
return with(value) {
if (designFeatureToggles.isRedesignEnabled) {
wallets2[selectedWalletIndex].walletsBalanceUM.id
} else {
wallets[selectedWalletIndex].walletCardState.id
}
}
}
fun getWalletIndexByWalletId(userWalletId: UserWalletId): Int? {
return with(value) { wallets.indexOfFirstOrNull { it.walletCardState.id == userWalletId } }
return with(value) {
if (designFeatureToggles.isRedesignEnabled) {
wallets2.indexOfFirstOrNull { it.walletsBalanceUM.id == userWalletId }
} else {
wallets.indexOfFirstOrNull { it.walletCardState.id == userWalletId }
}
}
}
fun showBottomSheet(
@ -105,6 +133,7 @@ internal class WalletStateController @Inject constructor() {
topBarConfig = WalletTopBarConfig(onDetailsClick = {}),
selectedWalletIndex = NOT_INITIALIZED_WALLET_INDEX,
wallets = persistentListOf(),
wallets2 = persistentListOf(),
onWalletChange = { _, _ -> },
event = consumedEvent(),
isHidingMode = false,

View file

@ -0,0 +1,71 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.extensions.TextReference
import com.tangem.domain.models.wallet.UserWalletId
/**
* Represents the state of the wallet balance in the UI.
*
* The sealed interface has three implementations:
* - [Content]: Represents the state when the wallet balance is successfully loaded.
* - [Error]: Represents the state when there was an error loading the wallet balance.
* - [Loading]: Represents the state when the wallet balance is currently being loaded.
*
* @property id The unique identifier of the wallet.
* @property name The name of the wallet.
*/
@Immutable
internal sealed interface WalletBalanceUM {
/** Wallet Id */
val id: UserWalletId
/** Wallet Name */
val name: String
/**
* Wallet card content state
*
* @property id wallet id
* @property name wallet name
* @property balance wallet balance
*/
data class Content(
override val id: UserWalletId,
override val name: String,
val balance: TextReference,
val isBalanceFlickering: Boolean,
val isZeroBalance: Boolean?,
) : WalletBalanceUM
/**
* Wallet card error state
*
* @property id wallet id
* @property name wallet name
*/
data class Error(
override val id: UserWalletId,
override val name: String,
) : WalletBalanceUM
/**
* Wallet card loading state
*
* @property id wallet id
* @property name wallet name
*/
data class Loading(
override val id: UserWalletId,
override val name: String,
) : WalletBalanceUM
fun copySealed(name: String): WalletBalanceUM {
return when (this) {
is Content -> copy(name = name)
is Error -> copy(name = name)
is Loading -> copy(name = name)
}
}
}

View file

@ -1,51 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.annotation.DrawableRes
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.feature.wallet.impl.R
/**
* Wallet bottom sheet config
*
[REDACTED_AUTHOR]
*/
sealed class WalletBottomSheetConfig(
open val title: TextReference,
open val subtitle: TextReference,
@DrawableRes open val iconResId: Int,
val primaryButtonConfig: ButtonConfig,
val secondaryButtonConfig: ButtonConfig,
) : TangemBottomSheetConfigContent {
data class ButtonConfig(
val text: TextReference,
val onClick: () -> Unit,
@DrawableRes val iconResId: Int? = null,
)
data class UnlockWallets(val onUnlockClick: () -> Unit, val onScanClick: () -> Unit) : WalletBottomSheetConfig(
title = resourceReference(id = R.string.common_access_denied),
subtitle = resourceReference(
id = R.string.unlock_wallet_description_full,
formatArgs = wrappedList(
resourceReference(R.string.common_biometrics),
),
),
iconResId = R.drawable.ic_locked_24,
primaryButtonConfig = ButtonConfig(
text = resourceReference(
id = R.string.user_wallet_list_unlock_all_with,
formatArgs = wrappedList(resourceReference(R.string.common_biometrics)),
),
onClick = onUnlockClick,
),
secondaryButtonConfig = ButtonConfig(
text = resourceReference(id = R.string.welcome_unlock_card),
onClick = onScanClick,
iconResId = R.drawable.ic_tangem_24,
),
)
}

View file

@ -0,0 +1,496 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.annotation.DrawableRes
import com.tangem.core.ui.ds.button.TangemButtonType
import com.tangem.core.ui.ds.image.TangemIconUM
import com.tangem.core.ui.ds.message.TangemMessageButtonUM
import com.tangem.core.ui.ds.message.TangemMessageEffect
import com.tangem.core.ui.ds.message.TangemMessageUM
import com.tangem.core.ui.extensions.pluralReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.extensions.wrappedList
import com.tangem.core.ui.res.TangemTheme
import com.tangem.feature.wallet.impl.R
import kotlinx.collections.immutable.persistentListOf
/**
* Wallet notification types
*/
internal enum class WalletNotificationType {
Status,
Critical,
Warning,
Promo,
Survey,
Informational,
}
/**
* Wallet notification UI model
*
* @property messageUM - message to show in notification
* @property type - type of notification, affects design and priority
*/
internal sealed class WalletNotificationUM(val messageUM: TangemMessageUM, val type: WalletNotificationType) {
// region Status
data object SomeNetworksUnreachable : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "SomeNetworksUnreachableNotification",
title = resourceReference(id = R.string.warning_some_networks_unreachable_title),
subtitle = resourceReference(id = R.string.warning_some_networks_unreachable_message),
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.status.attention },
),
messageEffect = TangemMessageEffect.None,
),
type = WalletNotificationType.Status,
)
data object UsedOutdatedData : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "UsedOutdatedDataNotification",
title = stringReference("Missing some token balances"), // todo redesign main lokalise
subtitle = stringReference("Will be updated as soon as possible"), // todo redesign main lokalise
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_error_sync_default_24,
tintReference = { TangemTheme.colors2.graphic.status.attention },
),
messageEffect = TangemMessageEffect.None,
),
type = WalletNotificationType.Status,
)
data object FailedCardValidation : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "FailedCardValidationNotification",
title = resourceReference(id = R.string.warning_failed_to_verify_card_title),
subtitle = resourceReference(id = R.string.warning_failed_to_verify_card_message),
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
messageEffect = TangemMessageEffect.Warning,
),
type = WalletNotificationType.Status,
)
data object DevCard : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "DevCardNotification",
title = resourceReference(id = R.string.warning_developer_card_title),
subtitle = resourceReference(id = R.string.warning_developer_card_message),
messageEffect = TangemMessageEffect.None,
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
),
type = WalletNotificationType.Status,
)
data object TestnetCard : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "TestnetCardNotification",
title = resourceReference(id = R.string.warning_testnet_card_title),
subtitle = resourceReference(id = R.string.warning_testnet_card_message),
messageEffect = TangemMessageEffect.None,
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
),
type = WalletNotificationType.Status,
)
data object DemoCard : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "DemoCardNotification",
title = resourceReference(id = R.string.warning_demo_mode_title),
subtitle = resourceReference(id = R.string.warning_demo_mode_message),
messageEffect = TangemMessageEffect.None,
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
),
type = WalletNotificationType.Status,
)
// endregion
// region Critical
data class BackupError(val onClick: () -> Unit) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "BackupErrorNotification",
title = resourceReference(id = R.string.warning_backup_errors_title),
subtitle = resourceReference(id = R.string.warning_backup_errors_message),
messageEffect = TangemMessageEffect.Warning,
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.common_contact_support),
type = TangemButtonType.PrimaryInverse,
onClick = onClick,
),
),
),
type = WalletNotificationType.Critical,
)
data class SeedPhraseNotification(
val onDeclineClick: () -> Unit,
val onConfirmClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "SeedPhraseIssueNotification",
title = resourceReference(id = R.string.warning_seedphrase_issue_title),
subtitle = resourceReference(id = R.string.warning_seedphrase_issue_message),
messageEffect = TangemMessageEffect.Warning,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.common_no),
type = TangemButtonType.PrimaryInverse,
onClick = onDeclineClick,
),
TangemMessageButtonUM(
text = resourceReference(id = R.string.common_yes),
type = TangemButtonType.PrimaryInverse,
onClick = onConfirmClick,
),
),
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
),
type = WalletNotificationType.Critical,
)
data class SeedPhraseSecondNotification(
val onDeclineClick: () -> Unit,
val onConfirmClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "SeedPhraseSecondIssueNotification",
title = resourceReference(id = R.string.warning_seedphrase_action_required_title),
subtitle = resourceReference(id = R.string.warning_seedphrase_contacted_support),
messageEffect = TangemMessageEffect.Warning,
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.seed_warning_no),
type = TangemButtonType.PrimaryInverse,
onClick = onDeclineClick,
),
TangemMessageButtonUM(
text = resourceReference(id = R.string.seed_warning_yes),
type = TangemButtonType.PrimaryInverse,
onClick = onConfirmClick,
),
),
),
type = WalletNotificationType.Critical,
)
data class MissingBackup(val onClick: () -> Unit) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "MissingBackupNotification",
title = resourceReference(id = R.string.warning_no_backup_title),
subtitle = resourceReference(id = R.string.warning_no_backup_message),
messageEffect = TangemMessageEffect.Warning,
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.button_start_backup_process),
type = TangemButtonType.PrimaryInverse,
onClick = onClick,
),
),
),
type = WalletNotificationType.Critical,
)
data class LowSignatures(val count: Int) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "LowSignaturesNotification",
title = resourceReference(id = R.string.warning_low_signatures_title),
subtitle = resourceReference(
id = R.string.warning_low_signatures_message,
formatArgs = wrappedList(count.toString()),
),
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
messageEffect = TangemMessageEffect.None,
),
type = WalletNotificationType.Critical,
)
data class FinishWalletActivation(
val messageEffect: TangemMessageEffect,
val isBackupExists: Boolean,
val onClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "FinishWalletActivationNotification",
title = resourceReference(R.string.hw_activation_need_title),
subtitle = if (isBackupExists) {
resourceReference(R.string.hw_activation_need_warning_description)
} else {
resourceReference(R.string.hw_activation_need_description)
},
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.img_knight_shield_32,
tintReference = {
when (messageEffect) {
TangemMessageEffect.Warning -> TangemTheme.colors2.graphic.neutral.primary
else -> TangemTheme.colors2.graphic.status.attention
}
},
),
messageEffect = messageEffect,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.hw_activation_need_finish),
type = TangemButtonType.PrimaryInverse,
onClick = onClick,
),
),
),
type = when (messageEffect) {
TangemMessageEffect.Warning -> WalletNotificationType.Critical
else -> WalletNotificationType.Warning
},
)
data class NumberOfSignedHashesIncorrect(val onCloseClick: () -> Unit) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "NumberOfSignedHashesIncorrectNotification",
title = resourceReference(id = R.string.warning_number_of_signed_hashes_incorrect_title),
subtitle = resourceReference(id = R.string.warning_number_of_signed_hashes_incorrect_message),
messageEffect = TangemMessageEffect.Warning,
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.img_knight_shield_32,
tintReference = { TangemTheme.colors2.graphic.neutral.primary },
),
onCloseClick = onCloseClick,
),
type = WalletNotificationType.Critical,
)
// endregion
// region Warning
data class MissingAddresses(
@DrawableRes val tangemIcon: Int?,
val missingAddressesCount: Int,
val onGenerateClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "MissingAddressesNotification",
title = resourceReference(id = R.string.warning_missing_derivation_title),
subtitle = pluralReference(
id = R.plurals.warning_missing_derivation_message,
count = missingAddressesCount,
formatArgs = wrappedList(missingAddressesCount),
),
isCentered = true,
messageEffect = TangemMessageEffect.Card,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.common_generate_addresses),
type = TangemButtonType.Primary,
iconRes = tangemIcon,
onClick = onGenerateClick,
),
),
),
type = WalletNotificationType.Warning,
)
data class NoAccount(val network: String, val symbol: String, val amount: String) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "NoAccountNotification",
title = resourceReference(id = R.string.warning_no_account_title),
subtitle = resourceReference(
id = R.string.no_account_generic,
wrappedList(network, amount, symbol),
),
messageEffect = TangemMessageEffect.None,
),
type = WalletNotificationType.Warning,
)
data class UnlockWallets(val onClick: () -> Unit) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "UnlockWalletsNotification",
title = resourceReference(id = R.string.common_access_denied),
subtitle = resourceReference(
id = R.string.warning_access_denied_message,
formatArgs = wrappedList(
resourceReference(R.string.common_biometrics),
),
),
onClick = onClick,
messageEffect = TangemMessageEffect.Card,
isCentered = true,
),
type = WalletNotificationType.Warning,
)
// endregion
// region Promo
data class NoteMigration(val onClick: () -> Unit) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "NoteMigrationNotification",
title = resourceReference(R.string.wallet_promo_banner_title),
subtitle = resourceReference(R.string.wallet_promo_banner_description),
messageEffect = TangemMessageEffect.Magic,
isCentered = true,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.wallet_promo_banner_button_title),
onClick = onClick,
type = TangemButtonType.Primary,
),
),
),
type = WalletNotificationType.Promo,
)
data class OnePlusOnePromo(
val onCloseClick: () -> Unit,
val onClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "OnePlusOnePromoNotification",
title = resourceReference(R.string.notification_one_plus_one_title),
subtitle = resourceReference(R.string.notification_one_plus_one_text),
messageEffect = TangemMessageEffect.Magic,
onCloseClick = onCloseClick,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.notification_one_plus_one_button),
type = TangemButtonType.Primary,
onClick = onClick,
),
),
),
type = WalletNotificationType.Promo,
)
data class YieldPromo(
val onCloseClick: () -> Unit,
val onTermsAndConditionsClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "YieldPromoNotification",
title = resourceReference(R.string.notification_yield_promo_title),
subtitle = resourceReference(R.string.notification_yield_promo_text),
onCloseClick = onCloseClick,
messageEffect = TangemMessageEffect.Magic,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.notification_yield_promo_button),
type = TangemButtonType.Primary,
onClick = onTermsAndConditionsClick,
),
),
),
type = WalletNotificationType.Promo,
)
// endregion
// region Survey
data class RateApp(
val onLikeClick: () -> Unit,
val onDislikeClick: () -> Unit,
val onCloseClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "RateAppNotification",
title = resourceReference(id = R.string.warning_rate_app_title),
subtitle = resourceReference(id = R.string.warning_rate_app_message),
isCentered = true,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(id = R.string.warning_button_could_be_better),
type = TangemButtonType.PrimaryInverse,
onClick = onDislikeClick,
),
TangemMessageButtonUM(
text = resourceReference(id = R.string.warning_button_like_it),
type = TangemButtonType.Primary,
onClick = onLikeClick,
),
),
messageEffect = TangemMessageEffect.None,
onCloseClick = onCloseClick,
),
type = WalletNotificationType.Survey,
)
// endregion
// region Informational
data class PushNotifications(
val onCloseClick: () -> Unit,
val onEnabledClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "PushNotificationsNotification",
title = resourceReference(R.string.user_push_notification_banner_title),
subtitle = resourceReference(R.string.user_push_notification_banner_subtitle),
onCloseClick = onCloseClick,
messageEffect = TangemMessageEffect.Magic,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(R.string.common_later),
type = TangemButtonType.PrimaryInverse,
onClick = onCloseClick,
),
TangemMessageButtonUM(
text = resourceReference(R.string.common_enable),
type = TangemButtonType.Primary,
onClick = onEnabledClick,
),
),
),
type = WalletNotificationType.Informational,
)
data class CloreMigration(
val onStartMigrationClick: () -> Unit,
) : WalletNotificationUM(
messageUM = TangemMessageUM(
id = "CloreMigrationNotification",
title = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_title),
subtitle = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_description),
iconUM = TangemIconUM.Icon(
iconRes = R.drawable.ic_attention_default_24,
tintReference = { TangemTheme.colors2.graphic.status.attention },
),
messageEffect = TangemMessageEffect.None,
buttonsUM = persistentListOf(
TangemMessageButtonUM(
text = resourceReference(com.tangem.core.res.R.string.warning_clore_migration_button),
onClick = onStartMigrationClick,
type = TangemButtonType.PrimaryInverse,
),
),
),
type = WalletNotificationType.Informational,
)
// endregion
}

View file

@ -9,6 +9,7 @@ internal data class WalletScreenState(
val topBarConfig: WalletTopBarConfig,
val selectedWalletIndex: Int,
val wallets: ImmutableList<WalletState>,
val wallets2: ImmutableList<WalletUM>,
val onWalletChange: (index: Int, onlyState: Boolean) -> Unit,
val event: StateEvent<WalletEvent>,
val isHidingMode: Boolean,

View file

@ -55,11 +55,6 @@ internal sealed interface WalletState : WalletStateHolder {
override val nftState: WalletNFTItemUM = WalletNFTItemUM.Hidden
override val tangemPayState: TangemPayState = TangemPayState.Empty
}
enum class WalletType {
Hot,
Cold,
}
}
sealed class SingleCurrency : WalletState, TxHistoryStateHolder {
@ -96,4 +91,9 @@ internal sealed interface WalletState : WalletStateHolder {
override val marketPriceBlockState: MarketPriceBlockState? = null
}
}
}
enum class WalletType {
Hot,
Cold,
}

View file

@ -0,0 +1,79 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.ds.button.TangemButtonUM
import com.tangem.core.ui.ds.row.TangemRowUM
import com.tangem.core.ui.ds.row.header.TangemHeaderRowUM
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
/**
* State of the tokens list in the wallet screen
*
* @property tokenList list of tokens to display
* @property organizeButtonUM configuration for the "Organize Tokens" button, if it should
*/
@Immutable
internal sealed class WalletTokensListUM {
abstract val tokenList: ImmutableList<TokensListItemUM2>
abstract val organizeButtonUM: TangemButtonUM?
data object Empty : WalletTokensListUM() {
override val tokenList: ImmutableList<TokensListItemUM2.Portfolio> = persistentListOf()
override val organizeButtonUM: TangemButtonUM? = null
}
data object Loading : WalletTokensListUM() {
override val tokenList: ImmutableList<TokensListItemUM2> = persistentListOf(
TokensListItemUM2.Portfolio(
tokenRowUM = TangemTokenRowUM.Loading(id = "0"),
tokenList = persistentListOf(),
isExpanded = false,
isCollapsable = true,
),
TokensListItemUM2.Portfolio(
tokenRowUM = TangemTokenRowUM.Loading(id = "1"),
tokenList = persistentListOf(),
isExpanded = false,
isCollapsable = true,
),
TokensListItemUM2.Portfolio(
tokenRowUM = TangemTokenRowUM.Loading(id = "2"),
tokenList = persistentListOf(),
isExpanded = false,
isCollapsable = true,
),
)
override val organizeButtonUM: TangemButtonUM? = null
}
data class Content(
override val tokenList: ImmutableList<TokensListItemUM2>,
override val organizeButtonUM: TangemButtonUM?,
) : WalletTokensListUM()
}
/**
* State of token list item in the wallet screen
*/
@Immutable
internal sealed interface TokensListItemUM2 {
val tokenRowUM: TangemRowUM
data class GroupTitle(
override val tokenRowUM: TangemHeaderRowUM,
) : TokensListItemUM2
data class Token(
override val tokenRowUM: TangemTokenRowUM,
) : TokensListItemUM2
data class Portfolio(
override val tokenRowUM: TangemTokenRowUM,
val tokenList: ImmutableList<TokensListItemUM2>,
val isExpanded: Boolean,
val isCollapsable: Boolean,
) : TokensListItemUM2
}

View file

@ -0,0 +1,52 @@
package com.tangem.feature.wallet.presentation.wallet.state.model
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.ds.button.TangemButtonUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
@Immutable
internal sealed interface WalletUM {
val pullToRefreshConfig: PullToRefreshConfig
val walletsBalanceUM: WalletBalanceUM
val buttons: PersistentList<TangemButtonUM>
val notifications: ImmutableList<WalletNotificationUM>
val notificationsCarousel: ImmutableList<WalletNotificationUM>
val tokensListUM: WalletTokensListUM
val nftState: WalletNFTItemUM
val type: WalletType
val tangemPayState: TangemPayState
data class Content(
override val pullToRefreshConfig: PullToRefreshConfig,
override val walletsBalanceUM: WalletBalanceUM,
override val buttons: PersistentList<TangemButtonUM>,
override val notifications: ImmutableList<WalletNotificationUM>,
override val notificationsCarousel: ImmutableList<WalletNotificationUM>,
override val tokensListUM: WalletTokensListUM,
override val nftState: WalletNFTItemUM,
override val type: WalletType,
override val tangemPayState: TangemPayState,
) : WalletUM
data class Locked(
override val walletsBalanceUM: WalletBalanceUM,
override val buttons: PersistentList<TangemButtonUM>,
override val type: WalletType,
override val notifications: ImmutableList<WalletNotificationUM> = persistentListOf(),
) : WalletUM {
override val notificationsCarousel: ImmutableList<WalletNotificationUM> = persistentListOf()
override val pullToRefreshConfig = PullToRefreshConfig(false, {})
override val tokensListUM: WalletTokensListUM = WalletTokensListUM.Empty // todo redesign main locked state
override val nftState: WalletNFTItemUM = WalletNFTItemUM.Hidden
override val tangemPayState: TangemPayState = TangemPayState.Empty
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class CloseBottomSheetTransformer(userWalletId: UserWalletId) : WalletStateTransformer(userWalletId) {
@ -22,6 +23,10 @@ internal class CloseBottomSheetTransformer(userWalletId: UserWalletId) : WalletS
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun updateConfig(prevState: WalletState) = prevState.bottomSheetConfig?.copy(
isShown = false,
)

View file

@ -7,7 +7,6 @@ import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState.MultiCurrency.WalletType
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory
import com.tangem.feature.wallet.presentation.wallet.state.utils.createStateByWalletType
import kotlinx.collections.immutable.PersistentList

View file

@ -4,6 +4,7 @@ import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class OpenBottomSheetTransformer(
userWalletId: UserWalletId,
@ -28,6 +29,10 @@ internal class OpenBottomSheetTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun updateConfig() = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onDismissBottomSheet,

View file

@ -4,6 +4,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletLoadingStateFactory
/**
@ -26,6 +27,10 @@ internal class ReinitializeWalletTransformer(
)
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
override fun transform(prevState: WalletState): WalletState {
return walletLoadingStateFactory.create(
userWallet = userWallet,

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNFTItemUM
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class RemoveNFTCollectionsTransformer(
userWalletId: UserWalletId,
@ -17,4 +18,8 @@ internal class RemoveNFTCollectionsTransformer(
is WalletState.SingleCurrency.Locked,
-> prevState
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.domain.tokens.model.TokenActionsState
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
import timber.log.Timber
@ -35,6 +36,10 @@ internal class SetCryptoCurrencyActionsTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun TokenActionsState.toManageButtons(): PersistentList<WalletManageButton> {
return states
.filterIfS2C()

View file

@ -10,6 +10,7 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.onramp.model.cache.OnrampTransaction
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.SingleWalletOnrampTransactionConverter
import kotlinx.collections.immutable.toPersistentList
import timber.log.Timber
@ -56,6 +57,10 @@ internal class SetExpressStatusesTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun TangemBottomSheetConfig.updateStateWithExpressStatusBottomSheet(
expressState: ExpressTransactionStateUM?,
): TangemBottomSheetConfig {

View file

@ -6,6 +6,7 @@ import com.tangem.domain.nft.models.NFTCollections
import com.tangem.domain.nft.models.allLoadedCollectionsEmpty
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNFTItemUM
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import kotlinx.collections.immutable.toPersistentList
internal class SetNFTCollectionsTransformer(
@ -28,6 +29,10 @@ internal class SetNFTCollectionsTransformer(
-> prevState
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun createContentNFTItemUM(onItemClick: () -> Unit): WalletNFTItemUM.Content {
val collectionsContent = nftCollections
.map { it.content }

View file

@ -6,6 +6,7 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
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.WalletUM
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.SingleWalletCardStateConverter
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.SingleWalletMarketPriceConverter
import timber.log.Timber
@ -35,6 +36,10 @@ internal class SetPrimaryCurrencyTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun WalletCardState.toLoadedSingleCurrencyState(): WalletCardState {
return SingleWalletCardStateConverter(status.value, userWallet, appCurrency).convert(value = this)
}

View file

@ -5,6 +5,7 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletManageButton
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.mutate
@ -33,6 +34,10 @@ internal class SetRefreshStateTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun PullToRefreshConfig.toUpdatedState(isRefreshing: Boolean): PullToRefreshConfig {
return copy(isRefreshing = isRefreshing)
}

View file

@ -10,6 +10,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfo
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 com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.state.utils.disableButtons
import timber.log.Timber
import java.math.BigDecimal
@ -51,6 +52,10 @@ internal class SetTokenListErrorTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun WalletCardState.toLoadedState(): WalletCardState {
return WalletCardState.Content(
id = id,

View file

@ -8,6 +8,7 @@ import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
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 com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCardStateConverter
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
@ -45,6 +46,10 @@ internal class SetTokenListTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun WalletCardState.toLoadedState(): WalletCardState {
val fiatBalance = when (params) {
is TokenConverterParams.Account -> params.accountList.totalFiatBalance

View file

@ -9,6 +9,7 @@ import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.txhistory.models.TxHistoryStateError
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TxHistoryItemStateConverter
import kotlinx.collections.immutable.toImmutableList
import timber.log.Timber
@ -35,6 +36,10 @@ internal class SetTxHistoryCountErrorTransformer(
)
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
override fun transform(prevState: WalletState): WalletState {
return when (prevState) {
is WalletState.SingleCurrency.Content -> prevState.copy(txHistoryState = createErrorState())

View file

@ -6,6 +6,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.update
import timber.log.Timber
@ -33,6 +34,10 @@ internal class SetTxHistoryCountTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun TxHistoryState.toLoadingState(): TxHistoryState {
return if (this is TxHistoryState.Content) {
Timber.d("Load transactions history: $transactionsCount")

View file

@ -5,6 +5,7 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import timber.log.Timber
internal class SetTxHistoryItemsErrorTransformer(
@ -27,6 +28,10 @@ internal class SetTxHistoryItemsErrorTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun createErrorState(): TxHistoryState.Error = when (error) {
is TxHistoryListError.DataError -> {
TxHistoryState.Error(

View file

@ -6,6 +6,7 @@ import com.tangem.core.ui.components.transactions.state.TxHistoryState
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TxHistoryItemFlowConverter
import kotlinx.coroutines.flow.Flow
import timber.log.Timber
@ -32,6 +33,10 @@ internal class SetTxHistoryItemsTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun TxHistoryState.toContentState(): TxHistoryState {
val converter = TxHistoryItemFlowConverter(
currentState = this,

View file

@ -2,13 +2,18 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotificationUM
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import timber.log.Timber
internal class SetWarningsTransformer(
userWalletId: UserWalletId,
private val warnings: ImmutableList<WalletNotification>,
private val notifications: ImmutableList<WalletNotificationUM> = persistentListOf(),
private val notificationsCarousel: ImmutableList<WalletNotificationUM> = persistentListOf(),
) : WalletStateTransformer(userWalletId) {
override fun transform(prevState: WalletState): WalletState {
@ -23,4 +28,17 @@ internal class SetWarningsTransformer(
}
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return when (walletUM) {
is WalletUM.Content -> walletUM.copy(
notifications = notifications,
notificationsCarousel = notificationsCarousel,
)
is WalletUM.Locked -> {
Timber.w("Impossible to update notifications for locked wallet")
walletUM
}
}
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayExposedDeviceTransformer(
userWalletId: UserWalletId,
@ -14,4 +15,8 @@ internal class TangemPayExposedDeviceTransformer(
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayHiddenStateTransformer(
userWalletId: UserWalletId,
@ -15,4 +16,8 @@ internal class TangemPayHiddenStateTransformer(
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayHideOnboardingStateTransformer(
userWalletId: UserWalletId,
@ -15,4 +16,8 @@ internal class TangemPayHideOnboardingStateTransformer(
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayLoadingStateTransformer(userWalletId: UserWalletId) : WalletStateTransformer(userWalletId) {
override fun transform(prevState: WalletState): WalletState {
@ -12,4 +13,8 @@ internal class TangemPayLoadingStateTransformer(userWalletId: UserWalletId) : Wa
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayOnboardingBannerStateTransformer(
userWalletId: UserWalletId,
@ -22,4 +23,8 @@ internal class TangemPayOnboardingBannerStateTransformer(
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -7,6 +7,7 @@ import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification.Warning.TangemPayRefreshNeeded
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayRefreshNeededStateTransformer(
userWalletId: UserWalletId,
@ -32,4 +33,8 @@ internal class TangemPayRefreshNeededStateTransformer(
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayRefreshShowProgressTransformer(
userWalletId: UserWalletId,
@ -21,4 +22,8 @@ internal class TangemPayRefreshShowProgressTransformer(
),
)
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -4,6 +4,7 @@ import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class TangemPayUnavailableStateTransformer(
userWalletId: UserWalletId,
@ -20,4 +21,8 @@ internal class TangemPayUnavailableStateTransformer(
prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -18,6 +18,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.domain.pay.model.CustomerInfo.KycStatus.APPROVED
import com.tangem.domain.pay.model.CustomerInfo
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import java.util.Currency
/**
@ -42,6 +43,10 @@ internal class TangemPayUpdateInfoStateTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun createInitialState(): TangemPayState {
val cardInfo = value.info.cardInfo
val productInstance = value.info.productInstance

View file

@ -1,22 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import kotlin.reflect.KClass
internal abstract class TypedWalletStateTransformer<S : WalletState>(
userWalletId: UserWalletId,
protected val targetStateClass: KClass<S>,
) : WalletStateTransformer(userWalletId) {
abstract fun transformTyped(prevState: S): WalletState
@Suppress("UNCHECKED_CAST")
final override fun transform(prevState: WalletState): WalletState {
return if (prevState::class == targetStateClass) {
transformTyped(prevState as S)
} else {
prevState
}
}
}

View file

@ -2,6 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import com.tangem.feature.wallet.presentation.wallet.state.utils.showSwapBadge
internal class UpdateMultiWalletActionButtonBadgeTransformer(
@ -16,4 +17,8 @@ internal class UpdateMultiWalletActionButtonBadgeTransformer(
else -> prevState
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
}

View file

@ -6,6 +6,7 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfo
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
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.WalletUM
import timber.log.Timber
internal class UpdateWalletCardsCountTransformer(
@ -30,6 +31,10 @@ internal class UpdateWalletCardsCountTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun WalletCardState.toUpdatedState(): WalletCardState {
return when (this) {
is WalletCardState.Content -> copy(

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import kotlinx.collections.immutable.toImmutableList
internal abstract class WalletStateTransformer(
@ -11,6 +12,8 @@ internal abstract class WalletStateTransformer(
abstract fun transform(prevState: WalletState): WalletState
abstract fun transform(walletUM: WalletUM): WalletUM
final override fun transform(prevState: WalletScreenState): WalletScreenState {
return prevState.copy(
wallets = prevState.wallets
@ -18,6 +21,11 @@ internal abstract class WalletStateTransformer(
if (state.walletCardState.id == userWalletId) transform(state) else state
}
.toImmutableList(),
wallets2 = prevState.wallets2
.map { walletUM ->
if (walletUM.walletsBalanceUM.id == userWalletId) transform(walletUM) else walletUM
}
.toImmutableList(),
)
}
}

View file

@ -52,7 +52,7 @@ internal class WalletLoadingStateFactory(
bottomSheetConfig = null,
tokensListState = WalletTokensListState.ContentState.Loading,
nftState = WalletNFTItemUM.Hidden,
type = WalletState.MultiCurrency.WalletType.Hot,
type = WalletType.Hot,
tangemPayState = TangemPayState.Empty,
)
}
@ -66,7 +66,7 @@ internal class WalletLoadingStateFactory(
bottomSheetConfig = null,
tokensListState = WalletTokensListState.ContentState.Loading,
nftState = WalletNFTItemUM.Hidden,
type = WalletState.MultiCurrency.WalletType.Cold,
type = WalletType.Cold,
tangemPayState = TangemPayState.Empty,
)
}

View file

@ -9,12 +9,9 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetWarningsTransformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.*
internal class MultiWalletWarningsSubscriber(
private val userWallet: UserWallet,
@ -37,7 +34,13 @@ internal class MultiWalletWarningsSubscriber(
it.wallets.any { walletState -> walletState.walletCardState.id == userWallet.walletId }
}
stateHolder.update(SetWarningsTransformer(userWallet.walletId, warnings))
stateHolder.update(
SetWarningsTransformer(
userWalletId = userWallet.walletId,
warnings = warnings,
notifications = persistentListOf(),
),
)
walletWarningsAnalyticsSender.send(displayedState, warnings)
walletWarningsSingleEventSender.send(
userWalletId = userWallet.walletId,

View file

@ -0,0 +1,71 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetWalletNotificationsCarouselFactory
import com.tangem.feature.wallet.presentation.wallet.domain.GetWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotificationUM
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetWarningsTransformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
@Suppress("LongParameterList")
internal class MultiWalletWarningsSubscriberV2(
private val userWallet: UserWallet,
private val stateHolder: WalletStateController,
private val clickIntents: WalletClickIntents,
private val getWalletWarningsFactory: GetWalletWarningsFactory,
private val getWalletNotificationsCarouselFactory: GetWalletNotificationsCarouselFactory,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<ImmutableList<WalletNotificationUM>> {
return combine(
flow = getWalletWarningsFactory.create(userWallet, clickIntents).conflate().distinctUntilChanged(),
flow2 = getWalletNotificationsCarouselFactory.create(userWallet, clickIntents).conflate()
.distinctUntilChanged(),
) { notifications, notificationsCarousel ->
val displayedWalletUM = stateHolder.getWalletUM(userWallet.walletId)
// Wait until the wallet appears in the list
stateHolder.uiState.first {
it.wallets2.any { walletUM -> walletUM.walletsBalanceUM.id == userWallet.walletId }
}
// If there are notifications, we need to filter out the RateApp notification from stackable notifications,
// because it should not be shown together with other notifications.
val alteredNotificationsCarousel = if (notifications.isNotEmpty()) {
notificationsCarousel.filterNot { it is WalletNotificationUM.RateApp }
} else {
notificationsCarousel
}.toPersistentList()
stateHolder.update(
SetWarningsTransformer(
userWalletId = userWallet.walletId,
warnings = persistentListOf(),
notifications = notifications,
notificationsCarousel = alteredNotificationsCarousel,
),
)
val totalNotifications = (notifications + alteredNotificationsCarousel).toPersistentList()
walletWarningsAnalyticsSender.send(displayedWalletUM, totalNotifications)
walletWarningsSingleEventSender.send(
userWalletId = userWallet.walletId,
displayedWalletUM = displayedWalletUM,
newNotifications = totalNotifications,
)
totalNotifications
}
}
}

View file

@ -8,6 +8,7 @@ import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetWarningsTransformer
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.conflate
@ -32,7 +33,7 @@ internal class SingleWalletNotificationsSubscriber(
.onEach { warnings ->
val displayedState = stateHolder.getWalletState(userWallet.walletId)
stateHolder.update(SetWarningsTransformer(userWallet.walletId, warnings))
stateHolder.update(SetWarningsTransformer(userWallet.walletId, warnings, persistentListOf()))
walletWarningsAnalyticsSender.send(displayedState, warnings)
}
}

View file

@ -720,7 +720,6 @@ internal fun LazyListScope.nftCollections(state: WalletState, itemModifier: Modi
private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
if (bottomSheetConfig != null) {
when (bottomSheetConfig.content) {
is WalletBottomSheetConfig -> WalletBottomSheet(config = bottomSheetConfig)
is ActionsBottomSheetConfig -> TokenActionsBottomSheet(config = bottomSheetConfig)
is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig)
is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(config = bottomSheetConfig)

View file

@ -0,0 +1,440 @@
package com.tangem.feature.wallet.presentation.wallet.ui
import android.content.res.Configuration
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.semantics.clearAndSetSemantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.tangem.core.ui.components.atoms.Hand
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.components.haze.hazeSourceTangem
import com.tangem.core.ui.components.rememberIsKeyboardVisible
import com.tangem.core.ui.components.sheetscaffold.*
import com.tangem.core.ui.components.snackbar.CopiedTextSnackbar
import com.tangem.core.ui.components.snackbar.TangemSnackbar
import com.tangem.core.ui.ds.topbar.TangemTopBar
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.*
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.accountScreenState
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.accountScreenWithEmptyTokensState
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewData.walletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.NOT_INITIALIZED_WALLET_INDEX
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletEvent
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
import dev.chrisbanes.haze.HazeProgressive
import kotlinx.coroutines.launch
@OptIn(ExperimentalDecomposeApi::class)
@Composable
internal fun WalletScreen2(
state: WalletScreenState,
bottomSheetContent: @Composable (() -> Unit),
bottomSheetHeaderHeightProvider: () -> Dp,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
) {
// It means that screen is still initializing
if (state.selectedWalletIndex == NOT_INITIALIZED_WALLET_INDEX) return
val walletsListState = rememberLazyListState(initialFirstVisibleItemIndex = state.selectedWalletIndex)
val snackbarHostState = remember(::SnackbarHostState)
val isAutoScroll = remember { mutableStateOf(value = false) }
WalletContent2(
state = state,
walletsListState = walletsListState,
snackbarHostState = snackbarHostState,
isAutoScroll = isAutoScroll,
onAutoScrollReset = { isAutoScroll.value = false },
bottomSheetContent = bottomSheetContent,
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
onBottomSheetStateChange = onBottomSheetStateChange,
)
WalletEventEffect(
walletsListState = walletsListState,
snackbarHostState = snackbarHostState,
event = state.event,
onAutoScrollSet = { isAutoScroll.value = true },
)
}
@OptIn(ExperimentalMaterial3Api::class, ExperimentalDecomposeApi::class)
@Suppress("LongMethod", "LongParameterList", "UnusedPrivateMember")
@Composable
private fun WalletContent2(
state: WalletScreenState,
walletsListState: LazyListState,
snackbarHostState: SnackbarHostState,
isAutoScroll: State<Boolean>,
onAutoScrollReset: () -> Unit,
bottomSheetHeaderHeightProvider: () -> Dp,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
bottomSheetContent: @Composable (() -> Unit),
) {
/*
* Don't pass key to remember, because it will brake scroll animation.
* selectedWalletIndex will be changed in WalletsListEffects.
*/
// val selectedWalletIndex by remember(state.selectedWalletIndex) { mutableIntStateOf(state.selectedWalletIndex) }
// val selectedWallet = state.wallets2.getOrElse(selectedWalletIndex) { state.wallets2[state.selectedWalletIndex] }
val statusBarHeight = with(LocalDensity.current) { WindowInsets.statusBars.getBottom(this).toDp() }
val listState = rememberLazyListState()
val partialCollapsedHeight = 64.dp + statusBarHeight
val scaffoldContent: @Composable (PaddingValues?) -> Unit = { _ ->
val pagerState = rememberPagerState(
initialPage = state.selectedWalletIndex,
pageCount = { state.wallets2.size },
)
LaunchedEffect(pagerState.currentPage) {
if (pagerState.currentPage != state.selectedWalletIndex) {
state.onWalletChange(pagerState.currentPage, false)
}
}
}
BaseScaffoldWithMarkets(
state = state,
listState = listState,
snackbarHostState = snackbarHostState,
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
onBottomSheetStateChange = onBottomSheetStateChange,
bottomSheetContent = bottomSheetContent,
content = scaffoldContent,
)
}
@Suppress("LongParameterList", "LongMethod", "CyclomaticComplexMethod", "UnusedPrivateMember")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private inline fun BaseScaffoldWithMarkets(
state: WalletScreenState,
snackbarHostState: SnackbarHostState,
listState: LazyListState,
bottomSheetHeaderHeightProvider: () -> Dp,
modifier: Modifier = Modifier,
noinline onBottomSheetStateChange: (BottomSheetState) -> Unit,
crossinline bottomSheetContent: @Composable () -> Unit,
crossinline content: @Composable (PaddingValues) -> Unit,
) {
val bottomSheetState = rememberTangemStandardBottomSheetState()
val isPowerSaving by LocalPowerSavingState.current.isPowerSavingModeEnabled.collectAsStateWithLifecycle()
val isKeyboardVisible by rememberIsKeyboardVisible()
val scaffoldState = rememberTangemBottomSheetScaffoldState(
bottomSheetState = bottomSheetState,
snackbarHostState = snackbarHostState,
)
val density = LocalDensity.current
val bottomBarHeight = with(density) { WindowInsets.systemBars.getBottom(density = this).toDp() }
val statusBarHeight = with(density) { WindowInsets.statusBars.getTop(density = this).toDp() }
val peekHeight = bottomSheetHeaderHeightProvider() + handComposableComponentHeight + bottomBarHeight
val maxHeight = LocalWindowSize.current.height
val coroutineScope = rememberCoroutineScope()
val background = if (state.isNewMarketEnabled) {
TangemTheme.colors.background.tertiary
} else {
TangemTheme.colors.background.primary
}
CompositionLocalProvider(
LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) },
) {
val backgroundColor = LocalMainBottomSheetColor.current
var isSearchFieldFocused by remember { mutableStateOf(false) }
val isNavBarVisible = remember { mutableStateOf(true) }
BottomSheetStateEffects(
bottomSheetState = bottomSheetState,
onBottomSheetStateChange = onBottomSheetStateChange,
navigationBarVisible = isNavBarVisible,
isSearchFieldFocused = isSearchFieldFocused,
)
Box(modifier = modifier) {
TangemBottomSheetScaffold(
modifier = Modifier.background(
brush = Brush.verticalGradient(
listOf(
TangemTheme.colors2.surface.level1,
TangemTheme.colors2.surface.level2,
),
),
),
snackbarHost = { snackbarHostState ->
WalletSnackbarHost(
snackbarHostState = snackbarHostState,
event = state.event,
modifier = Modifier
.padding(bottom = TangemTheme.dimens.spacing4)
.navigationBarsPadding(),
)
},
containerColor = Color.Unspecified,
sheetContainerColor = backgroundColor.value,
scaffoldState = scaffoldState,
sheetPeekHeight = peekHeight,
sheetShape = TangemTheme.shapes.bottomSheetLarge,
sheetContent = {
// hide bottom sheet when back pressed
BackHandler(
isKeyboardVisible.not() &&
bottomSheetState.currentValue == TangemSheetValue.Expanded,
) {
coroutineScope.launch { bottomSheetState.partialExpand() }
}
Column(
modifier = Modifier
// expand bottom sheet when clicked on the header
.clickable(
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
indication = null,
interactionSource = null,
) {
coroutineScope.launch { bottomSheetState.expand() }
}
.sizeIn(maxHeight = maxHeight - statusBarHeight),
) {
Hand(Modifier.drawBehind { drawRect(backgroundColor.value) })
Box(
modifier = Modifier
.onFocusChanged {
isSearchFieldFocused = it.isFocused
},
) {
bottomSheetContent()
}
}
},
content = { paddingValues ->
Box {
Column(
modifier = Modifier.hazeSourceTangem(-1f),
) {
content(paddingValues)
}
Surface(
color = Color.Unspecified,
contentColor = Color.Unspecified,
modifier = Modifier
.hazeEffectTangem {
progressive =
HazeProgressive.verticalGradient(startIntensity = 1f, endIntensity = 0f)
},
) {
TangemTopBar(
title = stringReference(""), // todo balance
startIconRes = R.drawable.ic_tangem_24,
endIconRes = R.drawable.ic_more_default_24,
onEndContentClick = state.topBarConfig.onDetailsClick,
isGhostButtons = !isPowerSaving,
modifier = Modifier
.testTag(MainScreenTestTags.TOP_BAR),
)
}
BottomSheetScrim(
color = if (state.showMarketsOnboarding) {
Color.Black.copy(alpha = .65f)
} else {
Color.Black.copy(alpha = .40f)
},
visible = bottomSheetState.targetValue == TangemSheetValue.Expanded ||
state.showMarketsOnboarding,
onDismissRequest = {
coroutineScope.launch { bottomSheetState.partialExpand() }
state.onDismissMarketsTooltip()
},
)
}
},
)
AnimatedVisibility(
modifier = Modifier.align(Alignment.BottomCenter),
visible = isNavBarVisible.value,
) {
Box(
Modifier
.align(Alignment.BottomCenter)
.background(backgroundColor.value)
.height(bottomBarHeight)
.fillMaxWidth(),
)
}
}
LaunchedEffect(state.showMarketsOnboarding, bottomSheetState.targetValue) {
if (state.showMarketsOnboarding && bottomSheetState.targetValue == TangemSheetValue.Expanded) {
state.onDismissMarketsTooltip()
}
}
}
}
@Composable
private fun BottomSheetScrim(color: Color, visible: Boolean, onDismissRequest: () -> Unit) {
val alpha by animateFloatAsState(
targetValue = if (visible) 1f else 0f,
animationSpec = tween(),
label = "scrim",
)
val dismissSheet = if (visible) {
Modifier
.pointerInput(onDismissRequest) {
detectTapGestures {
onDismissRequest()
}
}
.clearAndSetSemantics {}
} else {
Modifier
}
Canvas(
Modifier
.fillMaxSize()
.then(dismissSheet),
) {
drawRect(color = color, alpha = alpha)
}
}
@Suppress("CyclomaticComplexMethod", "MagicNumber", "LongMethod")
@Composable
private fun BottomSheetStateEffects(
bottomSheetState: TangemSheetState,
navigationBarVisible: MutableState<Boolean>,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
isSearchFieldFocused: Boolean,
) {
LaunchedEffect(bottomSheetState.targetValue) {
when (bottomSheetState.targetValue) {
TangemSheetValue.Hidden,
TangemSheetValue.Expanded,
-> navigationBarVisible.value = false
TangemSheetValue.PartiallyExpanded,
-> navigationBarVisible.value = true
}
}
// expand bottom sheet when keyboard appears
val isKeyboardVisible by rememberIsKeyboardVisible()
LaunchedEffect(isKeyboardVisible) {
if (isKeyboardVisible && isSearchFieldFocused) {
bottomSheetState.expand()
}
}
val keyboardController = LocalSoftwareKeyboardController.current
// hide keyboard when bottom sheet is about to be hidden
LaunchedEffect(Unit) {
snapshotFlow {
bottomSheetState.currentValue == TangemSheetValue.Expanded &&
bottomSheetState.targetValue == TangemSheetValue.PartiallyExpanded
}.collect { sheetHasBeenHidden ->
if (sheetHasBeenHidden) {
keyboardController?.hide()
}
}
}
val isSheetHidden = bottomSheetState.targetValue == TangemSheetValue.PartiallyExpanded
LaunchedEffect(isSheetHidden) {
onBottomSheetStateChange(
if (isSheetHidden) {
BottomSheetState.COLLAPSED
} else {
BottomSheetState.EXPANDED
},
)
}
}
@Composable
private fun WalletSnackbarHost(
snackbarHostState: SnackbarHostState,
event: StateEvent<WalletEvent>,
modifier: Modifier = Modifier,
) {
SnackbarHost(hostState = snackbarHostState, modifier = modifier) { data ->
if (event is StateEvent.Triggered && event.data is WalletEvent.CopyAddress) {
CopiedTextSnackbar(data)
} else {
TangemSnackbar(data)
}
}
}
// region Preview
@OptIn(ExperimentalDecomposeApi::class)
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun WalletScreen2_Preview(@PreviewParameter(WalletScreen2PreviewProvider::class) data: WalletScreenState) {
TangemThemePreviewRedesign {
WalletScreen2(
state = data,
bottomSheetContent = {
Text("Markets Content")
},
bottomSheetHeaderHeightProvider = { 10.dp },
onBottomSheetStateChange = {},
)
}
}
private class WalletScreen2PreviewProvider : PreviewParameterProvider<WalletScreenState> {
override val values: Sequence<WalletScreenState>
get() = sequenceOf(
walletScreenState,
walletScreenState.copy(selectedWalletIndex = 1),
accountScreenState.copy(selectedWalletIndex = 1),
accountScreenWithEmptyTokensState.copy(selectedWalletIndex = 1),
)
}
// endregion

View file

@ -1,141 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.ui.components.common
import android.content.res.Configuration
import androidx.compose.foundation.layout.*
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.datasource.CollectionPreviewParameterProvider
import com.tangem.core.ui.components.PrimaryButton
import com.tangem.core.ui.components.PrimaryButtonIconStart
import com.tangem.core.ui.components.SecondaryButton
import com.tangem.core.ui.components.SecondaryButtonIconStart
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletBottomSheetConfig
/**
* Wallet bottom sheet with detail notification information
*
* @param config component config
*
[REDACTED_AUTHOR]
*/
@Composable
internal fun WalletBottomSheet(config: TangemBottomSheetConfig) {
TangemBottomSheet(config) { content: WalletBottomSheetConfig ->
BottomSheetContent(config = content)
}
}
@Composable
private fun BottomSheetContent(config: WalletBottomSheetConfig) {
Column(
modifier = Modifier
.padding(horizontal = TangemTheme.dimens.spacing16)
.padding(top = TangemTheme.dimens.spacing40, bottom = TangemTheme.dimens.spacing16),
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing40),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Icon(
painter = painterResource(id = config.iconResId),
contentDescription = null,
modifier = Modifier.size(size = TangemTheme.dimens.size48),
tint = when (config) {
is WalletBottomSheetConfig.UnlockWallets -> TangemTheme.colors.icon.primary1
},
)
Column(
verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing16),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = config.title.resolveReference(),
color = TangemTheme.colors.text.primary1,
textAlign = TextAlign.Center,
style = TangemTheme.typography.h2,
)
Text(
text = config.subtitle.resolveReference(),
color = TangemTheme.colors.text.secondary,
textAlign = TextAlign.Center,
style = TangemTheme.typography.body2,
)
}
Column(verticalArrangement = Arrangement.spacedBy(space = TangemTheme.dimens.spacing10)) {
val buttonModifier = Modifier.fillMaxWidth()
PrimaryButton(config = config.primaryButtonConfig, modifier = buttonModifier)
SecondaryButton(config = config.secondaryButtonConfig, modifier = buttonModifier)
}
}
}
@Composable
private fun PrimaryButton(config: WalletBottomSheetConfig.ButtonConfig, modifier: Modifier = Modifier) {
if (config.iconResId == null) {
PrimaryButton(
text = config.text.resolveReference(),
onClick = config.onClick,
modifier = modifier,
)
} else {
PrimaryButtonIconStart(
text = config.text.resolveReference(),
iconResId = config.iconResId,
onClick = config.onClick,
modifier = modifier,
)
}
}
@Composable
private fun SecondaryButton(config: WalletBottomSheetConfig.ButtonConfig, modifier: Modifier = Modifier) {
if (config.iconResId == null) {
SecondaryButton(
text = config.text.resolveReference(),
onClick = config.onClick,
modifier = modifier,
)
} else {
SecondaryButtonIconStart(
text = config.text.resolveReference(),
iconResId = config.iconResId,
onClick = config.onClick,
modifier = modifier,
)
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun WalletBottomSheetContent_Preview(
@PreviewParameter(WalletBottomSheetConfigProvider::class)
config: WalletBottomSheetConfig,
) {
TangemThemePreview {
// Use preview of content because ModalBottomSheet isn't supported in Preview mode
BottomSheetContent(config = config)
}
}
private class WalletBottomSheetConfigProvider : CollectionPreviewParameterProvider<WalletBottomSheetConfig>(
collection = listOf(WalletPreviewData.bottomSheet.content as WalletBottomSheetConfig),
)
// endregion