Updated on 2026-08-14

This commit is contained in:
Tangem 2026-07-16 22:36:23 +04:00
parent 5f6767abaf
commit 74e4b644f7
42 changed files with 83 additions and 3490 deletions

View file

@ -8,13 +8,11 @@ import com.arkivanov.decompose.extensions.compose.stack.animation.stackAnimation
import com.arkivanov.decompose.router.stack.ChildStack
import com.arkivanov.decompose.router.stack.StackNavigation
import com.arkivanov.decompose.router.stack.childStack
import com.arkivanov.decompose.router.stack.pop
import com.arkivanov.decompose.router.stack.pushNew
import com.arkivanov.decompose.value.Value
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponentLegacy
import com.tangem.feature.wallet.child.wallet.WalletComponent
import com.tangem.feature.wallet.navigation.WalletRoute
import com.tangem.features.wallet.WalletEntryComponent
@ -24,7 +22,7 @@ import dagger.assisted.AssistedInject
internal class DefaultWalletEntryComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted params: Unit,
@Assisted val params: Unit,
walletComponentFactory: WalletComponent.Factory,
) : WalletEntryComponent, AppComponentContext by appComponentContext {
@ -40,11 +38,6 @@ internal class DefaultWalletEntryComponent @AssistedInject constructor(
appComponentContext = childByContext(context),
navigate = { navigation.pushNew(it) },
)
is WalletRoute.OrganizeTokens -> OrganizeTokensComponentLegacy(
appComponentContext = childByContext(context),
params = OrganizeTokensComponentLegacy.Params(route.userWalletId),
onBack = { navigation.pop() },
)
}
},
)

View file

@ -10,12 +10,10 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.managetokens.model.AddAndManageModel
import com.tangem.feature.wallet.child.managetokens.ui.AddAndManageBottomSheetContent
import com.tangem.feature.wallet.child.managetokens.ui.AddAndManageBottomSheetContentLegacy
import com.tangem.features.commonfeatures.api.portfolioselector.PortfolioSelectorComponent
import kotlinx.serialization.builtins.serializer
@ -53,21 +51,12 @@ internal class AddAndManageBottomSheetComponent(
val portfolioSelectorSlot by portfolioSelectorSlot.subscribeAsState()
val state by model.state.collectAsStateWithLifecycle()
if (LocalRedesignEnabled.current) {
AddAndManageBottomSheetContent(
onAddTokensClick = model::onAddTokensClick,
shouldShowOrganizeButton = state.shouldShowOrganize,
onOrganizeTokensClick = model::onOrganizeTokensClick,
onDismiss = ::dismiss,
)
} else {
AddAndManageBottomSheetContentLegacy(
onAddTokensClick = model::onAddTokensClick,
shouldShowOrganizeButton = state.shouldShowOrganize,
onOrganizeTokensClick = model::onOrganizeTokensClick,
onDismiss = ::dismiss,
)
}
portfolioSelectorSlot.child?.instance?.BottomSheet()
}

View file

@ -1,168 +0,0 @@
package com.tangem.feature.wallet.child.managetokens.ui
import android.content.res.Configuration
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
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.draw.clip
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.rememberVectorPainter
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.R
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheet
import com.tangem.core.ui.components.bottomsheets.modal.TangemModalBottomSheetTitle
import com.tangem.core.ui.decorations.roundedShapeItemDecoration
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.res.R as ResR
@Composable
internal fun AddAndManageBottomSheetContentLegacy(
onAddTokensClick: () -> Unit,
shouldShowOrganizeButton: Boolean,
onOrganizeTokensClick: () -> Unit,
onDismiss: () -> Unit,
) {
val config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = onDismiss,
content = AddAndManageBottomSheetConfigContent,
)
TangemModalBottomSheet<AddAndManageBottomSheetConfigContent>(
config = config,
containerColor = TangemTheme.colors.background.primary,
title = {
TangemModalBottomSheetTitle(
title = resourceReference(ResR.string.main_add_and_manage_tokens),
endIconRes = R.drawable.ic_close_24,
onEndClick = onDismiss,
)
},
content = {
AddAndManageContent(
onAddTokensClick = onAddTokensClick,
shouldShowOrganizeButton = shouldShowOrganizeButton,
onOrganizeTokensClick = onOrganizeTokensClick,
)
},
)
}
@Composable
private fun AddAndManageContent(
onAddTokensClick: () -> Unit,
shouldShowOrganizeButton: Boolean,
onOrganizeTokensClick: () -> Unit,
) {
Column(
modifier = Modifier.padding(
start = 16.dp,
end = 16.dp,
bottom = 16.dp,
),
) {
AddAndManageRow(
iconRes = R.drawable.ic_plus_24,
title = ResR.string.add_and_manage_sheet_manage_title,
subtitle = ResR.string.add_and_manage_sheet_manage_subtitle,
onClick = onAddTokensClick,
modifier = Modifier.roundedShapeItemDecoration(
currentIndex = 0,
lastIndex = if (shouldShowOrganizeButton) 1 else 0,
addDefaultPadding = false,
backgroundColor = TangemTheme.colors.background.action,
),
)
if (shouldShowOrganizeButton) {
AddAndManageRow(
iconRes = R.drawable.ic_filter_default_24,
title = ResR.string.add_and_manage_sheet_organize_title,
subtitle = ResR.string.add_and_manage_sheet_organize_subtitle,
onClick = onOrganizeTokensClick,
modifier = Modifier.roundedShapeItemDecoration(
currentIndex = 1,
lastIndex = 1,
addDefaultPadding = false,
backgroundColor = TangemTheme.colors.background.action,
),
)
}
}
}
@Composable
private fun AddAndManageRow(
iconRes: Int,
title: Int,
subtitle: Int,
onClick: () -> Unit,
modifier: Modifier = Modifier,
) {
Row(
modifier = modifier
.fillMaxWidth()
.clickable(onClick = onClick)
.padding(horizontal = 12.dp, vertical = 15.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(12.dp),
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(36.dp)
.clip(CircleShape)
.background(TangemTheme.colors.icon.accent.copy(alpha = 0.1f)),
) {
Icon(
modifier = Modifier.size(18.dp),
painter = rememberVectorPainter(ImageVector.vectorResource(id = iconRes)),
tint = TangemTheme.colors.icon.accent,
contentDescription = null,
)
}
Column(
verticalArrangement = Arrangement.spacedBy(2.dp),
) {
Text(
text = stringResourceSafe(id = title),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = stringResourceSafe(id = subtitle),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
)
}
}
}
private object AddAndManageBottomSheetConfigContent : TangemBottomSheetConfigContent
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun AddAndManageBottomSheetContent_Preview() {
TangemThemePreview {
AddAndManageContent(
onAddTokensClick = {},
shouldShowOrganizeButton = true,
onOrganizeTokensClick = {},
)
}
}
// endregion

View file

@ -1,37 +0,0 @@
package com.tangem.feature.wallet.child.organizetokens
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.decompose.ComposableContentComponent
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.organizetokens.model.OrganizeTokensModelLegacy
import com.tangem.feature.wallet.child.organizetokens.ui.OrganizeTokensScreen
import kotlinx.coroutines.launch
internal class OrganizeTokensComponentLegacy(
appComponentContext: AppComponentContext,
params: Params,
onBack: () -> Unit,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: OrganizeTokensModelLegacy = getOrCreateModel(params)
init {
componentScope.launch {
model.onBack.collect { onBack() }
}
}
data class Params(val userWalletId: UserWalletId)
@Composable
override fun Content(modifier: Modifier) {
val uiState by model.uiState.collectAsStateWithLifecycle()
OrganizeTokensScreen(state = uiState)
}
}

View file

@ -3,7 +3,6 @@ package com.tangem.feature.wallet.child.organizetokens.di
import com.tangem.core.decompose.di.ModelComponent
import com.tangem.core.decompose.model.Model
import com.tangem.feature.wallet.child.organizetokens.model.OrganizeTokensModel
import com.tangem.feature.wallet.child.organizetokens.model.OrganizeTokensModelLegacy
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@ -14,11 +13,6 @@ import dagger.multibindings.IntoMap
@InstallIn(ModelComponent::class)
internal interface OrganizeTokensModule {
@Binds
@IntoMap
@ClassKey(OrganizeTokensModelLegacy::class)
fun bindOrganizeTokensModelLegacy(model: OrganizeTokensModelLegacy): Model
@Binds
@IntoMap
@ClassKey(OrganizeTokensModel::class)

View file

@ -1,224 +0,0 @@
package com.tangem.feature.wallet.child.organizetokens.model
import androidx.compose.runtime.Stable
import arrow.core.getOrElse
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.core.decompose.model.ParamsContainer
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.status.usecase.ApplyTokenListSortingUseCase
import com.tangem.domain.account.status.usecase.ToggleTokenListGroupingUseCase
import com.tangem.domain.account.status.usecase.ToggleTokenListSortingUseCase
import com.tangem.domain.account.status.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.domain.models.TokensSortType
import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponentLegacy
import com.tangem.feature.wallet.child.organizetokens.analytics.PortfolioOrganizeTokensAnalyticsEvent
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensState
import com.tangem.feature.wallet.child.organizetokens.model.dnd.DragAndDropAdapterLegacy
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import javax.inject.Inject
@Suppress("LongParameterList")
@Stable
@ModelScoped
internal class OrganizeTokensModelLegacy @Inject constructor(
paramsContainer: ParamsContainer,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
override val dispatchers: CoroutineDispatcherProvider,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val toggleTokenListGroupingUseCase: ToggleTokenListGroupingUseCase,
private val toggleTokenListSortingUseCase: ToggleTokenListSortingUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
) : Model(), OrganizeTokensIntents {
private val selectedAppCurrencyFlow = createSelectedAppCurrencyFlow()
private var isBalanceHidden = true
@Suppress("PropertyUsedBeforeDeclaration")
private val dragAndDropAdapterLegacy = DragAndDropAdapterLegacy(
tokenListUMProvider = Provider { uiState.value.tokenListUM },
)
private val stateHolder = OrganizeTokensStateHolder(
intents = this,
dragAndDropAdapterLegacy = dragAndDropAdapterLegacy,
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
)
private val userWalletId = paramsContainer.require<OrganizeTokensComponentLegacy.Params>().userWalletId
private var cachedAccountStatusList: AccountStatusList? = null
private var isAccountsModeEnabled: Boolean = false
val uiState: StateFlow<OrganizeTokensState> = stateHolder.stateFlow
val onBack = MutableSharedFlow<Unit>()
init {
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ScreenOpened())
getBalanceHidingSettingsUseCase()
.onEach { balanceHidingSettings ->
isBalanceHidden = balanceHidingSettings.isBalanceHidden
stateHolder.updateHiddenState(isBalanceHidden)
}
.launchIn(modelScope)
bootstrapTokenList()
bootstrapDragAndDropUpdates()
}
override fun onBackClick() {
modelScope.launch { onBack.emit(Unit) }
}
override fun onSortClick() {
val list = cachedAccountStatusList ?: return
if (list.sortType == TokensSortType.BALANCE) return
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ByBalance())
modelScope.launch {
toggleTokenListSortingUseCase(list).fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = { accountStatusList ->
stateHolder.updateStateAfterTokenListSorting(accountStatusList, isAccountsModeEnabled)
cachedAccountStatusList = accountStatusList
},
)
}
}
override fun onGroupClick() {
val list = cachedAccountStatusList ?: return
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Group())
modelScope.launch {
toggleTokenListGroupingUseCase(list).fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = { accountStatusList ->
stateHolder.updateStateAfterTokenListSorting(accountStatusList, isAccountsModeEnabled)
cachedAccountStatusList = accountStatusList
},
)
}
}
override fun onApplyClick() {
modelScope.launch {
stateHolder.updateStateToDisplayProgress()
val resolver = CryptoCurrenciesIdsResolver()
val isSortedByBalance = uiState.value.header.isSortedByBalance
val tokensListUM = uiState.value.tokenListUM
val isGroupedByNetwork = tokensListUM.isGrouped
sendAnalyticsEvent(
isGroupedByNetwork = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
val result = applyTokenListSortingUseCase(
sortedTokensIdsByAccount = resolver.resolveLegacy(tokensListUM, cachedAccountStatusList),
isGroupedByNetwork = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
result.fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
modelScope.launch { onBack.emit(Unit) }
stateHolder.updateStateToHideProgress()
},
)
}
}
override fun onCancelClick() {
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Cancel())
modelScope.launch { onBack.emit(Unit) }
}
private fun bootstrapTokenList() {
modelScope.launch {
val accountList = singleAccountStatusListSupplier.getSyncOrNull(
SingleAccountStatusListProducer.Params(userWalletId),
) ?: return@launch
isAccountsModeEnabled = isAccountsModeEnabledUseCase.invokeSync()
stateHolder.updateStateWithAccountList(
accountStatusList = accountList,
isAccountsModeEnabled = isAccountsModeEnabled,
)
cachedAccountStatusList = accountList
}
}
private fun bootstrapDragAndDropUpdates() {
dragAndDropAdapterLegacy.dragAndDropUpdates
.distinctUntilChanged()
.onEach { (type, updatedListState) ->
disableSortingByBalanceIfListChanged(type)
stateHolder.updateStateWithManualSorting(updatedListState)
}
.launchIn(modelScope)
}
private fun disableSortingByBalanceIfListChanged(dragOperationType: DragAndDropAdapterLegacy.DragOperation.Type) {
if (dragOperationType !is DragAndDropAdapterLegacy.DragOperation.Type.End) return
if (uiState.value.header.isSortedByBalance && dragOperationType.isItemsOrderChanged) {
cachedAccountStatusList = cachedAccountStatusList?.copy(sortType = TokensSortType.NONE)
stateHolder.disableSortingByBalance()
}
}
private fun createSelectedAppCurrencyFlow(): StateFlow<AppCurrency> {
return getSelectedAppCurrencyUseCase()
.map { maybeAppCurrency ->
maybeAppCurrency.getOrElse { AppCurrency.Default }
}
.stateIn(
scope = modelScope,
started = SharingStarted.Eagerly,
initialValue = AppCurrency.Default,
)
}
private fun sendAnalyticsEvent(isGroupedByNetwork: Boolean, isSortedByBalance: Boolean) {
analyticsEventsHandler.send(
PortfolioOrganizeTokensAnalyticsEvent.Apply(
grouping = if (isGroupedByNetwork) {
AnalyticsParam.OnOffState.On
} else {
AnalyticsParam.OnOffState.Off
},
organizeSortType = if (isSortedByBalance) {
AnalyticsParam.OrganizeSortType.ByBalance
} else {
AnalyticsParam.OrganizeSortType.Manually
},
),
)
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.feature.wallet.child.tokenActions
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
@ -8,18 +7,8 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.DpOffset
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.ui.components.SimpleSettingsRow
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfigContent
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheet
import com.tangem.core.ui.components.getDefaultRowColors
import com.tangem.core.ui.components.getWarningRowColors
import com.tangem.core.ui.extensions.resolveReference
import com.tangem.core.ui.res.LocalRedesignEnabled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
import com.tangem.feature.wallet.child.tokenActions.TokenActionsComponent.Params
import com.tangem.feature.wallet.presentation.wallet.ui.components.fastForEach
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@ -50,35 +39,7 @@ internal class DefaultTokenActionsComponent @AssistedInject constructor(
@Composable
override fun BottomSheet() {
if (!LocalRedesignEnabled.current) {
TangemBottomSheet<TangemBottomSheetConfigContent.Empty>(
containerColor = TangemTheme.colors.background.primary,
config = TangemBottomSheetConfig(
isShown = true,
onDismissRequest = ::dismiss,
content = TangemBottomSheetConfigContent.Empty,
),
) {
Column {
params.actions.fastForEach { action ->
if (action.isEnabled) {
val rowColors = if (action.isWarning) {
getWarningRowColors()
} else {
getDefaultRowColors()
}
SimpleSettingsRow(
title = action.text.resolveReference(),
icon = action.iconResId,
enabled = action.isEnabled,
rowColors = rowColors,
onItemsClick = action.onClick,
)
}
}
}
}
}
// No-op: token actions are rendered via Content() in the redesigned UI
}
@Composable
@ -87,7 +48,6 @@ internal class DefaultTokenActionsComponent @AssistedInject constructor(
dismiss()
} else {
val isBalanceHidden by isBalanceHiddenFlow.collectAsStateWithLifecycle()
if (LocalRedesignEnabled.current) {
val offset = with(LocalDensity.current) {
DpOffset(params.offsetX.toDp(), params.offsetY.toDp())
}
@ -101,7 +61,6 @@ internal class DefaultTokenActionsComponent @AssistedInject constructor(
)
}
}
}
@AssistedFactory
interface Factory : TokenActionsComponent.Factory {

View file

@ -15,7 +15,6 @@ 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.components.haze.hazeEffectTangem
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
@ -30,7 +29,6 @@ import com.tangem.feature.wallet.child.tokenActions.TokenActionsComponent
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
@ -71,7 +69,6 @@ internal class WalletComponent @AssistedInject constructor(
private val tokenActionsComponentFactory: TokenActionsComponent.Factory,
private val portfolioSelectorComponentFactory: PortfolioSelectorComponent.Factory,
private val manageFundsComponentFactory: ManageFundsComponent.Factory,
private val designFeatureToggles: DesignFeatureToggles,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: WalletModel = getOrCreateModel()
@ -295,50 +292,28 @@ internal class WalletComponent @AssistedInject constructor(
var headerSize by remember { mutableStateOf(0.dp) }
val dialog by dialog.subscribeAsState()
val uiState by model.uiState.collectAsStateWithLifecycle()
if (designFeatureToggles.isRedesignEnabled) {
WalletScreen2(
state = uiState,
promoBannersBlockComponent = promoBannersBlockComponent,
tangemPayComponent = tangemPayMainBlockComponent,
virtualAccountComponent = virtualAccountMainBlockComponent,
modifier = modifier,
bottomSheetContent = { onExpandSheet ->
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
onExpandSheet = onExpandSheet,
modifier = modifier,
modifier = Modifier,
)
},
bottomSheetHeaderHeightProvider = { headerSize },
onBottomSheetStateChange = { bottomSheetState.value = it },
)
} else {
WalletScreen(
state = uiState,
promoBannersBlockComponent = promoBannersBlockComponent,
tangemPayComponent = tangemPayMainBlockComponent,
virtualAccountComponent = virtualAccountMainBlockComponent,
bottomSheetContent = { onExpandSheet ->
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
onExpandSheet = onExpandSheet,
modifier = modifier,
)
},
bottomSheetHeaderHeightProvider = { headerSize },
onBottomSheetStateChange = { bottomSheetState.value = it },
)
}
when (val dialog = dialog.child?.instance) {
is ComposableDialogComponent -> dialog.Dialog()
is DefaultTokenActionsComponent -> {
if (designFeatureToggles.isRedesignEnabled) {
dialog.Content(Modifier.hazeEffectTangem())
} else {
dialog.BottomSheet()
}
}
is ComposableBottomSheetComponent -> dialog.BottomSheet()
else -> {}

View file

@ -3,7 +3,6 @@ package com.tangem.feature.wallet.child.wallet.model.intents
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.domain.exchange.RampStateManager
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
@ -19,7 +18,6 @@ import com.tangem.feature.wallet.presentation.wallet.domain.WalletContentFetcher
import com.tangem.feature.wallet.presentation.wallet.domain.unwrap
import com.tangem.feature.wallet.presentation.wallet.loaders.WalletScreenContentLoader
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
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.SetRefreshStateTransformer
import kotlinx.coroutines.CoroutineScope
@ -47,7 +45,6 @@ internal class WalletClickIntents @Inject constructor(
private val onrampStatusFactory: OnrampStatusFactory,
private val tangemPayIntents: TangemPayClickIntentsImplementor,
private val yieldSupplyApyUpdateUseCase: YieldSupplyApyUpdateUseCase,
private val designFeatureToggles: DesignFeatureToggles,
private val analyticsEventHandler: AnalyticsEventHandler,
) : BaseWalletClickIntents(),
WalletCardClickIntents by walletCardClickIntentsImplementor,
@ -94,25 +91,10 @@ internal class WalletClickIntents @Inject constructor(
}
fun onRefreshSwipe(showRefreshState: Boolean) {
if (designFeatureToggles.isRedesignEnabled) {
when (stateController.getSelectedWalletUM()) {
is WalletUM.Content -> refreshMultiCurrencyContent(showRefreshState)
is WalletUM.Locked -> Unit
}
} else {
when (stateController.getSelectedWallet()) {
is WalletState.MultiCurrency.Content -> {
refreshMultiCurrencyContent(showRefreshState)
}
is WalletState.SingleCurrency.Content,
-> {
refreshSingleCurrencyContent(showRefreshState)
}
is WalletState.MultiCurrency.Locked,
is WalletState.SingleCurrency.Locked,
-> Unit
}
}
}
fun onReloadClick() {

View file

@ -333,11 +333,7 @@ internal class WalletContentClickIntentsImplementor @Inject constructor(
}
override fun onNFTClick(userWallet: UserWallet) {
val nftState = if (stateHolder.value.isRedesignEnabled) {
stateHolder.getSelectedWalletUM().nftState
} else {
(stateHolder.getSelectedWallet() as? WalletState.MultiCurrency.Content)?.nftState
}
val nftState = stateHolder.getSelectedWalletUM().nftState
when (nftState) {
is WalletNFTItemUM.Content -> {
analyticsEventHandler.send(

View file

@ -598,21 +598,8 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
private fun isMultiWalletTokensLoaded(): Boolean {
return if (stateHolder.value.isRedesignEnabled) {
val selectedWalletUM = stateHolder.getSelectedWalletUM() as? WalletUM.Content ?: return false
selectedWalletUM.tokensListUM is WalletTokensListUM.Content
} else {
val selectedWallet = stateHolder.getSelectedWallet() as? WalletState.MultiCurrency.Content ?: return false
when (selectedWallet.tokensListState) {
is WalletTokensListState.ContentState.Content,
is WalletTokensListState.ContentState.PortfolioContent,
-> true
WalletTokensListState.ContentState.Loading,
WalletTokensListState.ContentState.Locked,
WalletTokensListState.Empty,
-> false
}
}
return selectedWalletUM.tokensListUM is WalletTokensListUM.Content
}
private fun onMultiWalletActionClick(

View file

@ -1,6 +1,5 @@
package com.tangem.feature.wallet.navigation
import com.tangem.domain.models.wallet.UserWalletId
import kotlinx.serialization.Serializable
@Serializable
@ -8,7 +7,4 @@ internal sealed class WalletRoute {
@Serializable
data object Wallet : WalletRoute()
@Serializable
data class OrganizeTokens(val userWalletId: UserWalletId) : WalletRoute()
}

View file

@ -224,7 +224,6 @@ internal object WalletScreenPreviewData {
isHidingMode = false,
showMarketsOnboarding = false,
onDismissMarketsTooltip = {},
isRedesignEnabled = true,
)
val defaultAccountState = defaultState.copy(

View file

@ -1,268 +0,0 @@
package com.tangem.feature.wallet.presentation.common.preview
import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.marketprice.PriceChangeType
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
import com.tangem.core.ui.components.token.AccountItemPreviewData
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.components.tokenlist.state.PortfolioItemContentUM
import com.tangem.core.ui.components.tokenlist.state.PortfolioTokensListItemUM
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewDataLegacy.topBarConfig
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.features.tangempay.entity.TangemPayMainUM
import com.tangem.utils.StringsSigns.DASH_SIGN
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toPersistentList
internal object WalletScreenPreviewDataLegacy {
private val buyButton = WalletManageButton.Buy(enabled = false, dimContent = true, onClick = {})
private val sendButton = WalletManageButton.Send(enabled = false, dimContent = true, onClick = {})
private val receiveButton = WalletManageButton.Receive(
enabled = false,
dimContent = true,
onClick = {},
onLongClick = null,
)
private val tokenItemState = TokenItemState.Content(
id = "1",
iconState = CurrencyIconState.Locked,
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Bitcoin")),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "12 368,14 \$"),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "0,35853044 BTC"),
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
price = "34 496,75 \$",
priceChangePercent = "0,43 %",
type = PriceChangeType.DOWN,
),
onItemClick = {},
onItemLongClick = {},
)
private val textContentTokensState = WalletTokensListState.ContentState.Content(
items = persistentListOf(
TokensListItemUM.GroupTitle(id = 111, text = stringReference("Network Bitcoin")),
TokensListItemUM.Token(state = tokenItemState),
TokensListItemUM.GroupTitle(id = 222, text = stringReference("Network Ethereum")),
TokensListItemUM.Token(
state = tokenItemState.copy(
id = "2",
titleState = TokenItemState.TitleState.Content(text = stringReference("Ethereum")),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "3 340,79 \$"),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "1,856660295 ETH"),
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
price = "1 799,41 \$",
priceChangePercent = "5,16 %",
type = PriceChangeType.UP,
),
),
),
TokensListItemUM.Token(
state = TokenItemState.Unreachable(
id = "3",
iconState = CurrencyIconState.Locked,
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
onItemClick = {},
onItemLongClick = {},
),
),
TokensListItemUM.Token(
state = tokenItemState.copy(
id = "4",
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Shiba Inu")),
fiatAmountState = TokenItemState.FiatAmountState.Content(text = "48,64 \$"),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "6 200 220,00 SHIB"),
subtitleState = TokenItemState.SubtitleState.CryptoPriceContent(
price = "0.01 \$",
priceChangePercent = "1,34 %",
type = PriceChangeType.DOWN,
),
),
),
),
organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig(
textRes = R.string.organize_tokens_title,
iconRes = R.drawable.ic_filter_24,
isEnabled = true,
onClick = {},
),
)
private val portfolioContentState = WalletTokensListState.ContentState.PortfolioContent(
items = persistentListOf(
TokensListItemUM.Portfolio(
content = PortfolioItemContentUM.Tokens(
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>()
.toPersistentList(),
),
isExpanded = false,
isCollapsable = true,
tokenItemUM = AccountItemPreviewData.accountItem
.copy(iconState = AccountItemPreviewData.accountLetterIcon),
),
TokensListItemUM.Portfolio(
content = PortfolioItemContentUM.Tokens(
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>()
.toPersistentList(),
),
isExpanded = true,
isCollapsable = true,
tokenItemUM = AccountItemPreviewData.accountItem,
),
),
organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig(
textRes = R.string.organize_tokens_title,
iconRes = R.drawable.ic_filter_24,
isEnabled = true,
onClick = {},
),
)
private val emptyPortfolioContentState = WalletTokensListState.ContentState.PortfolioContent(
items = persistentListOf(
TokensListItemUM.Portfolio(
content = PortfolioItemContentUM.Tokens(
tokens = textContentTokensState.items.filterIsInstance<PortfolioTokensListItemUM>()
.toPersistentList(),
),
isExpanded = false,
isCollapsable = true,
tokenItemUM = AccountItemPreviewData.accountItem
.copy(iconState = AccountItemPreviewData.accountLetterIcon),
),
TokensListItemUM.Portfolio(
content = PortfolioItemContentUM.Empty(
action = PortfolioItemContentUM.Empty.Action(
text = resourceReference(id = R.string.onboarding_add_tokens),
onClick = {},
),
),
isExpanded = true,
isCollapsable = true,
tokenItemUM = AccountItemPreviewData.accountItem,
),
),
organizeTokensButtonConfig = WalletTokensListState.OrganizeTokensButtonConfig(
textRes = R.string.organize_tokens_title,
iconRes = R.drawable.ic_filter_24,
isEnabled = true,
onClick = {},
),
)
private val noteLockedCard by lazy {
WalletCardState.LockedContent(
id = UserWalletId(stringValue = "1"),
title = "Note",
additionalInfo = WalletAdditionalInfo(
hideable = false,
content = WalletAdditionalInfo.Content.Text(TextReference.Str("Locked")),
),
imageResId = R.drawable.ill_note_btc_120_106,
dropDownItems = persistentListOf(),
)
}
private val miltiUnreachableCard by lazy {
WalletCardState.Content(
id = UserWalletId(stringValue = "2"),
title = "Wallet 1",
additionalInfo = WalletAdditionalInfo(
hideable = false,
content = WalletAdditionalInfo.Content.Text(TextReference.Str("Seed phrase")),
),
imageResId = R.drawable.ill_wallet2_cards3_120_106,
cardCount = 3,
balance = DASH_SIGN,
dropDownItems = persistentListOf(),
isZeroBalance = false,
isBalanceFlickering = false,
)
}
private val multiWalletState by lazy {
WalletState.MultiCurrency.Content(
pullToRefreshConfig = PullToRefreshConfig(
isRefreshing = false,
onRefresh = {},
),
walletCardState = miltiUnreachableCard,
buttons = persistentListOf(buyButton),
warnings = persistentListOf(
WalletNotification.Warning.SomeNetworksUnreachable,
WalletNotification.FinishWalletActivation(
type = WalletActivationBannerType.Attention,
buttonsState = ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.hw_activation_need_finish),
onClick = { },
),
isBackupExists = false,
),
),
bottomSheetConfig = null,
tokensListState = textContentTokensState,
nftState = WalletNFTItemUM.Content(
previews = persistentListOf(WalletNFTItemUM.Content.CollectionPreview.Image("img1")),
collectionsCount = 1,
allAssetsCount = 3,
noCollectionAssetsCount = 0,
isFlickering = false,
onItemClick = { },
),
type = WalletType.Cold,
tangemPayMainUM = TangemPayMainUM.Empty,
)
}
private val singleWalletLockedState = WalletState.SingleCurrency.Locked(
walletCardState = noteLockedCard,
buttons = persistentListOf(
buyButton,
sendButton,
receiveButton,
),
bottomSheetConfig = null,
onUnlockNotificationClick = {},
onExploreClick = {},
)
internal val walletScreenState = WalletScreenState(
topBarConfig = topBarConfig,
selectedWalletIndex = 0,
wallets = persistentListOf(
singleWalletLockedState,
multiWalletState,
),
wallets2 = persistentListOf(),
onWalletChange = { _, _ -> },
event = consumedEvent(),
isHidingMode = false,
showMarketsOnboarding = false,
onDismissMarketsTooltip = {},
isRedesignEnabled = false,
)
internal val accountScreenState =
walletScreenState.copy(
wallets = persistentListOf(
singleWalletLockedState,
multiWalletState.copy(tokensListState = portfolioContentState),
),
)
internal val accountScreenWithEmptyTokensState =
walletScreenState.copy(
wallets = persistentListOf(
singleWalletLockedState,
multiWalletState.copy(tokensListState = emptyPortfolioContentState),
),
)
}

View file

@ -8,7 +8,6 @@ import com.tangem.common.routing.AppRoute
import com.tangem.common.routing.AppRouter
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.ds.row.token.TangemTokenRowUM
import com.tangem.domain.models.TokenReceiveConfig
import com.tangem.domain.models.account.AccountId
@ -37,7 +36,6 @@ import javax.inject.Inject
internal class DefaultWalletRouter @Inject constructor(
private val router: AppRouter,
private val urlOpener: UrlOpener,
private val designFeatureToggles: DesignFeatureToggles,
) : InnerWalletRouter {
override val dialogNavigation: SlotNavigation<WalletDialogConfig> = SlotNavigation()
@ -51,13 +49,9 @@ internal class DefaultWalletRouter @Inject constructor(
get() = OrganizeCallbacks()
override fun openOrganizeTokensScreen(userWalletId: UserWalletId) {
if (designFeatureToggles.isRedesignEnabled) {
dialogNavigation.activate(
configuration = WalletDialogConfig.OrganizeTokens(userWalletId),
)
} else {
navigateToFlow.tryEmit(WalletRoute.OrganizeTokens(userWalletId))
}
}
override fun openDetailsScreen(selectedWalletId: UserWalletId) {

View file

@ -1,510 +0,0 @@
@file:Suppress("MaximumLineLength")
package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.common.ui.notifications.NotificationId
import com.tangem.common.ui.userwallet.ext.walletInterationIcon
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.components.notifications.NotificationConfig.ButtonsState
import com.tangem.core.ui.extensions.resourceReference
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
import com.tangem.domain.assetsdiscovery.model.AssetsDiscoveryProgress
import com.tangem.domain.assetsdiscovery.usecase.ObserveAssetsDiscoveryUseCase
import com.tangem.domain.card.CardTypesResolver
import com.tangem.domain.card.IsWalletBackupProblematicUseCase
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.hotwallet.CheckHotWalletUpgradeBannerUseCase
import com.tangem.domain.hotwallet.GetAccessCodeSkippedUseCase
import com.tangem.domain.hotwallet.GetUpgradeBannerClosureTimestampUseCase
import com.tangem.domain.hotwallet.ShouldShowUpgradeHotWalletBannerUseCase
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.account.PaymentAccountStatusValue
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.notifications.repository.NotificationsRepository
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
import com.tangem.domain.yield.supply.promo.usecase.ShouldShowYieldBoostMainBannerUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.WalletActivationBannerType
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.account.AccountDependencies
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.hot.sdk.model.HotWalletId
import com.tangem.lib.crypto.BlockchainUtils
import com.tangem.utils.annotations.RemoveWithToggle
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
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
@Suppress("LongParameterList", "LargeClass")
@ModelScoped
internal class GetMultiWalletWarningsFactory @Inject constructor(
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val isWalletBackupProblematicUseCase: IsWalletBackupProblematicUseCase,
private val notificationsRepository: NotificationsRepository,
private val accountDependencies: AccountDependencies,
private val getAccessCodeSkippedUseCase: GetAccessCodeSkippedUseCase,
private val shouldShowUpgradeHotWalletBannerUseCase: ShouldShowUpgradeHotWalletBannerUseCase,
private val getUpgradeBannerClosureTimestampUseCase: GetUpgradeBannerClosureTimestampUseCase,
private val checkHotWalletUpgradeBannerUseCase: CheckHotWalletUpgradeBannerUseCase,
private val observeAssetsDiscoveryUseCase: ObserveAssetsDiscoveryUseCase,
private val shouldShowYieldBoostMainBannerUseCase: ShouldShowYieldBoostMainBannerUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val designFeatureToggles: DesignFeatureToggles,
) {
@Suppress("UNCHECKED_CAST", "MagicNumber", "LongMethod", "CastNullableToNonNullableType")
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
val cardTypesResolver = (userWallet as? UserWallet.Cold)?.scanResponse?.cardTypesResolver
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
val accountStatusListFlow = accountDependencies.singleAccountStatusListSupplier(params)
val assetsDiscoveryProgressFlow =
if (userWallet is UserWallet.Hot) {
observeAssetsDiscoveryUseCase(userWallet.walletId).distinctUntilChanged()
} else {
flowOf(AssetsDiscoveryProgress.Idle)
}
return combine(
accountStatusListFlow,
isReadyToShowRateAppUseCase().distinctUntilChanged(),
isNeedToBackupUseCase(userWallet.walletId).distinctUntilChanged(),
notificationsRepository.getShouldShowNotification(NotificationId.EnablePushesReminderNotification.key)
.distinctUntilChanged(),
getAccessCodeSkippedUseCase(userWallet.walletId).distinctUntilChanged(),
shouldShowUpgradeHotWalletBannerUseCase.invoke(userWallet.walletId)
.distinctUntilChanged(),
getUpgradeBannerClosureTimestampUseCase(userWallet.walletId)
.distinctUntilChanged(),
assetsDiscoveryProgressFlow,
yieldSupplyGetShouldShowMainPromoUseCase().distinctUntilChanged(),
) { array -> array }
.map { array ->
val accountStatusList = array[0] as AccountStatusList
val isReadyToShowRating = array[1] as Boolean
val isNeedToBackup = array[2] as Boolean
val shouldShowEnablePushesReminderNotification = array[3] as Boolean
val shouldAccessCodeSkipped = array[4] as Boolean
val shouldShowUpgradeBanner = array[5] as Boolean
val closureTimestamp = array[6] as? Long
val assetsDiscoveryProgress = array[7] as AssetsDiscoveryProgress
val shouldShowYieldBoostPromoLocal = array[8] as Boolean
val flattenCurrencies = accountStatusList.flattenCurrencies()
val paymentAccountStatus = accountStatusList.accountStatuses
.filterIsInstance<AccountStatus.Payment>()
.firstOrNull()
val isAddFundsBannerShown = isAddFundsBannerVisible(accountStatusList.totalFiatBalance)
buildList {
addBackupErrorNotification(userWallet, clickIntents)
addUsedOutdatedDataNotification(accountStatusList.totalFiatBalance)
addAddFundsBanner(
isVisible = isAddFundsBannerShown,
userWallet = userWallet,
clickIntents = clickIntents,
)
addCriticalNotifications(userWallet)
addUpgradeHotWalletPromoNotification(
userWallet = userWallet,
flattenCurrencies = flattenCurrencies,
clickIntents = clickIntents,
shouldShowUpgradeBanner = shouldShowUpgradeBanner,
closureTimestamp = closureTimestamp,
)
if (!isAddFundsBannerShown) {
addFinishWalletActivationNotification(
userWallet = userWallet,
flattenCurrencies = flattenCurrencies,
clickIntents = clickIntents,
shouldAccessCodeSkipped = shouldAccessCodeSkipped,
)
}
addInformationalNotifications(
userWallet = userWallet,
cardTypesResolver = cardTypesResolver,
flattenCurrencies = flattenCurrencies,
clickIntents = clickIntents,
)
addWarningNotifications(
cardTypesResolver = cardTypesResolver,
flattenCurrencies = flattenCurrencies,
isNeedToBackup = isNeedToBackup,
clickIntents = clickIntents,
)
addAssetsDiscoveryCompletedNotification(
userWallet = userWallet,
assetsDiscoveryProgress = assetsDiscoveryProgress,
clickIntents = clickIntents,
)
addPushReminderNotification(
clickIntents = clickIntents,
shouldShowPushReminderBanner = shouldShowEnablePushesReminderNotification &&
!notificationsRepository.isUserAllowToSubscribeOnPushNotifications(),
)
val hasCriticalOrWarning = any { notification ->
notification is WalletNotification.Critical || notification is WalletNotification.Warning
}
if (!hasCriticalOrWarning) {
addRateTheAppNotification(isReadyToShowRating, clickIntents)
}
// add as last warning
paymentAccountStatus?.let { paymentAccountStatus ->
addTangemPayWarnings(
status = paymentAccountStatus,
userWallet = userWallet,
walletClickIntents = clickIntents,
)
}
addYieldBoostBannerNotification(
userWallet = userWallet,
shouldShowLocal = shouldShowYieldBoostPromoLocal,
clickIntents = clickIntents,
)
}.toImmutableList()
}
}
private suspend fun MutableList<WalletNotification>.addYieldBoostBannerNotification(
userWallet: UserWallet,
shouldShowLocal: Boolean,
clickIntents: WalletClickIntents,
) {
if (!shouldShowLocal) return
if (designFeatureToggles.isRedesignEnabled) return
val shouldShow = shouldShowYieldBoostMainBannerUseCase(userWallet.walletId).getOrNull() == true
if (!shouldShow) return
add(
WalletNotification.YieldBoostPromo(
onClick = { clickIntents.onYieldBoostBannerClick(userWallet.walletId) },
onCloseClick = { clickIntents.onDismissYieldBoostBanner(userWallet.walletId) },
),
)
}
private fun MutableList<WalletNotification>.addTangemPayWarnings(
status: AccountStatus.Payment,
userWallet: UserWallet,
walletClickIntents: WalletClickIntents,
) {
val notification = when (status.value) {
is PaymentAccountStatusValue.Error.NotSynced -> WalletNotification.Warning.TangemPayRefreshNeeded(
buttonText = resourceReference(id = R.string.tangempay_sync_needed_button),
onRefreshClick = { walletClickIntents.onRefreshPayToken(userWallet) },
shouldShowProgress = false,
)
is PaymentAccountStatusValue.NotCreated -> WalletNotification.CreateTangemPayAccount(
onClick = { walletClickIntents.onOnboardingBannerClick(userWallet.walletId) },
onCloseClick = { walletClickIntents.onOnboardingBannerCloseClick(userWallet.walletId) },
)
is PaymentAccountStatusValue.Error.Unavailable -> WalletNotification.Warning.TangemPayUnreachable
is PaymentAccountStatusValue.Error.CardIssueFailed,
is PaymentAccountStatusValue.Error.ExposedDevice,
is PaymentAccountStatusValue.IssuingCard,
is PaymentAccountStatusValue.AwaitingPlanSelection,
is PaymentAccountStatusValue.Inactive,
is PaymentAccountStatusValue.Loaded,
is PaymentAccountStatusValue.Loading,
is PaymentAccountStatusValue.UnderReview,
is PaymentAccountStatusValue.Empty,
is PaymentAccountStatusValue.Deactivated,
-> null
}
notification?.let(::add)
}
private fun MutableList<WalletNotification>.addUsedOutdatedDataNotification(totalFiatBalance: TotalFiatBalance) {
addIf(
element = WalletNotification.UsedOutdatedData,
condition = (totalFiatBalance as? TotalFiatBalance.Loaded)?.source == StatusSource.ONLY_CACHE,
)
}
private fun isAddFundsBannerVisible(totalFiatBalance: TotalFiatBalance): Boolean {
val loaded = totalFiatBalance as? TotalFiatBalance.Loaded ?: return false
return loaded.amount.orZero().signum() == 0
}
private fun MutableList<WalletNotification>.addAddFundsBanner(
isVisible: Boolean,
userWallet: UserWallet,
clickIntents: WalletClickIntents,
) {
addIf(
element = WalletNotification.AddFunds(
onClick = { clickIntents.onAddFundsPromoClick(userWallet.walletId) },
),
condition = isVisible,
)
}
private fun MutableList<WalletNotification>.addBackupErrorNotification(
userWallet: UserWallet,
clickIntents: WalletClickIntents,
) {
addIf(
element = WalletNotification.Critical.BackupError { clickIntents.onBackupErrorClick() },
condition = isWalletBackupProblematicUseCase(userWallet),
)
}
private fun MutableList<WalletNotification>.addCriticalNotifications(userWallet: UserWallet) {
if (userWallet !is UserWallet.Cold) {
return
}
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
addIf(
element = WalletNotification.Critical.DevCard,
condition = !cardTypesResolver.isReleaseFirmwareType(),
)
addIf(
element = WalletNotification.Critical.FailedCardValidation,
condition = cardTypesResolver.isReleaseFirmwareType() && cardTypesResolver.isAttestationFailed(),
)
cardTypesResolver.getRemainingSignatures()?.let { remainingSignatures ->
addIf(
element = WalletNotification.Warning.LowSignatures(count = remainingSignatures),
condition = remainingSignatures <= MAX_REMAINING_SIGNATURES_COUNT,
)
}
}
private fun MutableList<WalletNotification>.addInformationalNotifications(
userWallet: UserWallet,
cardTypesResolver: CardTypesResolver?,
flattenCurrencies: List<CryptoCurrencyStatus>,
clickIntents: WalletClickIntents,
) {
addIf(
element = WalletNotification.Informational.DemoCard,
condition = cardTypesResolver != null && isDemoCardUseCase(cardId = cardTypesResolver.getCardId()),
)
addMissingAddressesNotification(userWallet, flattenCurrencies, clickIntents)
}
private fun MutableList<WalletNotification>.addMissingAddressesNotification(
userWallet: UserWallet,
flattenCurrencies: List<CryptoCurrencyStatus>,
clickIntents: WalletClickIntents,
) {
val currencies = flattenCurrencies.getMissingAddressCurrencies()
.ifEmpty { return }
addIf(
element = WalletNotification.Informational.MissingAddresses(
tangemIcon = walletInterationIcon(userWallet),
missingAddressesCount = currencies.distinctBy { it.network.id }.count(),
onGenerateClick = {
clickIntents.onGenerateMissedAddressesClick(
userWalletId = userWallet.walletId,
missedAddressCurrencies = currencies,
)
},
),
condition = currencies.isNotEmpty(),
)
}
private fun List<CryptoCurrencyStatus>.getMissingAddressCurrencies(): List<CryptoCurrency> {
return this
.filter { it.value is CryptoCurrencyStatus.MissedDerivation }
.map(CryptoCurrencyStatus::currency)
}
private fun MutableList<WalletNotification>.addWarningNotifications(
cardTypesResolver: CardTypesResolver?,
flattenCurrencies: List<CryptoCurrencyStatus>,
isNeedToBackup: Boolean,
clickIntents: WalletClickIntents,
) {
addIf(
element = WalletNotification.Warning.MissingBackup(
onStartBackupClick = clickIntents::onAddBackupCardClick,
),
condition = isNeedToBackup,
)
addIf(
element = WalletNotification.Warning.TestNetCard,
condition = cardTypesResolver?.isTestCard() == true,
)
addIf(
element = WalletNotification.Warning.SomeNetworksUnreachable,
condition = flattenCurrencies.hasUnreachableNetworks(),
)
addCloreMigrationNotification(flattenCurrencies, clickIntents)
}
private fun MutableList<WalletNotification>.addCloreMigrationNotification(
flattenCurrencies: List<CryptoCurrencyStatus>,
clickIntents: WalletClickIntents,
) {
val cloreCurrency = flattenCurrencies.findCloreCurrency() ?: return
add(
WalletNotification.CloreMigration(
onStartMigrationClick = { clickIntents.onCloreMigrationClick(cloreCurrency) },
),
)
}
private fun List<CryptoCurrencyStatus>.findCloreCurrency(): CryptoCurrencyStatus? {
return this.find { currencyStatus ->
BlockchainUtils.isClore(currencyStatus.currency.network.rawId)
}
}
private fun MutableList<WalletNotification>.addPushReminderNotification(
clickIntents: WalletClickIntents,
shouldShowPushReminderBanner: Boolean,
) {
addIf(
element = WalletNotification.PushNotifications(
onCloseClick = clickIntents::onDenyPermissions,
onEnabledClick = clickIntents::onAllowPermissions,
),
condition = shouldShowPushReminderBanner,
)
}
private fun List<CryptoCurrencyStatus>.hasUnreachableNetworks(): Boolean {
return this.any { it.value is CryptoCurrencyStatus.Unreachable }
}
private fun MutableList<WalletNotification>.addAssetsDiscoveryCompletedNotification(
userWallet: UserWallet,
assetsDiscoveryProgress: AssetsDiscoveryProgress,
clickIntents: WalletClickIntents,
) {
addIf(
element = WalletNotification.AssetsDiscoveryCompleted(
onCloseClick = { clickIntents.onDismissAssetsDiscoveryNotification(userWallet.walletId) },
onManageTokensClick = { clickIntents.onAssetsDiscoveryManageClick(userWallet.walletId) },
),
condition = assetsDiscoveryProgress is AssetsDiscoveryProgress.Completed,
)
}
private fun MutableList<WalletNotification>.addRateTheAppNotification(
isReadyToShowRating: Boolean,
clickIntents: WalletClickIntents,
) {
addIf(
element = WalletNotification.RateApp(
onLikeClick = clickIntents::onLikeAppClick,
onDislikeClick = clickIntents::onDislikeAppClick,
onCloseClick = clickIntents::onCloseRateAppWarningClick,
),
condition = isReadyToShowRating,
)
}
private fun List<CryptoCurrencyStatus>?.getFinishWalletActivationType(): WalletActivationBannerType {
return if (this?.any { it.value.amount.orZero().isPositive() } == true) {
WalletActivationBannerType.Warning
} else {
WalletActivationBannerType.Attention
}
}
private fun MutableList<WalletNotification>.addFinishWalletActivationNotification(
userWallet: UserWallet,
flattenCurrencies: List<CryptoCurrencyStatus>,
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 type = flattenCurrencies.getFinishWalletActivationType()
addIf(
element = WalletNotification.FinishWalletActivation(
type = type,
buttonsState = when (type) {
WalletActivationBannerType.Warning -> ButtonsState.PrimaryButtonConfig(
text = resourceReference(R.string.hw_activation_need_finish),
onClick = { clickIntents.onFinishWalletActivationClick(isBackupExists) },
)
else -> ButtonsState.SecondaryButtonConfig(
text = resourceReference(R.string.hw_activation_need_finish),
onClick = { clickIntents.onFinishWalletActivationClick(isBackupExists) },
)
},
isBackupExists = isBackupExists,
),
condition = shouldShowFinishActivation,
)
}
private suspend fun MutableList<WalletNotification>.addUpgradeHotWalletPromoNotification(
userWallet: UserWallet,
flattenCurrencies: List<CryptoCurrencyStatus>,
clickIntents: WalletClickIntents,
shouldShowUpgradeBanner: Boolean,
closureTimestamp: Long?,
) {
if (userWallet !is UserWallet.Hot) return
val hasBalance = flattenCurrencies.any { it.value.amount.orZero().isPositive() }
val shouldShow = checkHotWalletUpgradeBannerUseCase(
walletId = userWallet.walletId,
hasBalance = hasBalance,
shouldShowUpgradeBanner = shouldShowUpgradeBanner,
closureTimestamp = closureTimestamp,
).getOrNull() ?: return
addIf(
element = WalletNotification.UpgradeHotWalletPromo(
onLaterClick = { clickIntents.onCloseUpgradeBannerClick(userWallet.walletId) },
onUpgradeClick = { clickIntents.onUpgradeHotWalletClick(userWallet.walletId) },
),
condition = shouldShow,
)
}
private companion object {
const val MAX_REMAINING_SIGNATURES_COUNT = 10
}
}

View file

@ -1,252 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import arrow.core.Either
import arrow.core.right
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.account.status.producer.SingleAccountStatusProducer
import com.tangem.domain.account.status.supplier.SingleAccountStatusSupplier
import com.tangem.domain.card.CardTypesResolver
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.settings.IsReadyToShowRateAppUseCase
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNotification
import com.tangem.utils.annotations.RemoveWithToggle
import com.tangem.utils.extensions.addIf
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.*
import javax.inject.Inject
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
@ModelScoped
@Suppress("LongParameterList")
internal class GetSingleWalletWarningsFactory @Inject constructor(
private val singleAccountStatusSupplier: SingleAccountStatusSupplier,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
private val hasSingleWalletSignedHashesUseCase: HasSingleWalletSignedHashesUseCase,
private val getWalletsUseCase: GetWalletsUseCase,
) {
private var isReadyForRateAppNotification = false
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
if (userWallet !is UserWallet.Cold) {
return flowOf(emptyList<WalletNotification>().toImmutableList())
}
val cardTypesResolver = userWallet.scanResponse.cardTypesResolver
return combine(
flow = getPrimaryCurrencyStatusFlow(userWallet),
flow2 = isReadyToShowRateAppUseCase().conflate(),
flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(),
flow4 = getWalletsUseCase().conflate(),
) { maybePrimaryCurrencyStatus, isReadyToShowRating, isNeedToBackup, userWallets ->
isReadyForRateAppNotification = true
buildList {
addUsedOutdatedDataNotification(maybePrimaryCurrencyStatus)
addCriticalNotifications(
cardTypesResolver = cardTypesResolver,
)
addInformationalNotifications(
userWallets = userWallets,
cardTypesResolver = cardTypesResolver,
clickIntents = clickIntents,
)
addWarningNotifications(
userWallet = userWallet,
cardTypesResolver = cardTypesResolver,
maybePrimaryCurrencyStatus = maybePrimaryCurrencyStatus,
isNeedToBackup = isNeedToBackup,
clickIntents = clickIntents,
)
addRateTheAppNotification(
isReadyToShowRating = isReadyToShowRating,
clickIntents = clickIntents,
)
}.toImmutableList()
}
}
private fun MutableList<WalletNotification>.addUsedOutdatedDataNotification(
maybePrimaryCurrencyStatus: Either<CurrencyStatusError, CryptoCurrencyStatus>,
) {
addIf(
element = WalletNotification.UsedOutdatedData,
condition = maybePrimaryCurrencyStatus.fold(
ifLeft = { false },
ifRight = { it.value.sources.total == StatusSource.ONLY_CACHE },
),
)
}
private fun MutableList<WalletNotification>.addCriticalNotifications(cardTypesResolver: CardTypesResolver) {
addIf(
element = WalletNotification.Critical.DevCard,
condition = !cardTypesResolver.isReleaseFirmwareType(),
)
addIf(
element = WalletNotification.Critical.FailedCardValidation,
condition = cardTypesResolver.isReleaseFirmwareType() && cardTypesResolver.isAttestationFailed(),
)
cardTypesResolver.getRemainingSignatures()?.let { remainingSignatures ->
addIf(
element = WalletNotification.Warning.LowSignatures(count = remainingSignatures),
condition = remainingSignatures <= MAX_REMAINING_SIGNATURES_COUNT,
)
}
}
private fun MutableList<WalletNotification>.addInformationalNotifications(
userWallets: List<UserWallet>,
cardTypesResolver: CardTypesResolver,
clickIntents: WalletClickIntents,
) {
val hasWalletOrWallet2 = userWallets.filterIsInstance<UserWallet.Cold>().any { coldWallet ->
val typesResolver = coldWallet.scanResponse.cardTypesResolver
typesResolver.isTangemWallet() || typesResolver.isWallet2()
}
addIf(
element = WalletNotification.NoteMigration(
onClick = { clickIntents.onNoteMigrationButtonClick(NOTE_MIGRATION_URL) },
),
condition = cardTypesResolver.isSingleCurrency() && !hasWalletOrWallet2,
)
addIf(
element = WalletNotification.Informational.DemoCard,
condition = isDemoCardUseCase(cardId = cardTypesResolver.getCardId()),
)
}
private suspend fun MutableList<WalletNotification>.addWarningNotifications(
userWallet: UserWallet.Cold,
cardTypesResolver: CardTypesResolver,
maybePrimaryCurrencyStatus: Either<CurrencyStatusError, CryptoCurrencyStatus>,
isNeedToBackup: Boolean,
clickIntents: WalletClickIntents,
) {
val cryptoCurrencyStatus = maybePrimaryCurrencyStatus.fold(ifLeft = { null }, ifRight = { it })
addIf(
element = WalletNotification.Warning.MissingBackup(
onStartBackupClick = clickIntents::onAddBackupCardClick,
),
condition = isNeedToBackup,
)
addIf(
element = WalletNotification.Warning.TestNetCard,
condition = cardTypesResolver.isTestCard(),
)
addIf(
element = WalletNotification.Warning.NetworksUnreachable,
condition = cryptoCurrencyStatus?.value is CryptoCurrencyStatus.Unreachable,
)
addNoAccountWarning(cryptoCurrencyStatus)
addIf(
element = WalletNotification.Warning.NumberOfSignedHashesIncorrect(
onCloseClick = clickIntents::onCloseAlreadySignedHashesWarningClick,
),
condition = hasSignedHashes(userWallet, cryptoCurrencyStatus),
)
}
private fun MutableList<WalletNotification>.addNoAccountWarning(cryptoCurrencyStatus: CryptoCurrencyStatus?) {
val noAccountStatus = cryptoCurrencyStatus?.value as? CryptoCurrencyStatus.NoAccount
if (noAccountStatus != null) {
add(
element = WalletNotification.Informational.NoAccount(
network = cryptoCurrencyStatus.currency.name,
amount = noAccountStatus.amountToCreateAccount.toString(),
symbol = cryptoCurrencyStatus.currency.symbol,
),
)
}
}
private suspend fun hasSignedHashes(
selectedWallet: UserWallet.Cold,
cryptoCurrencyStatus: CryptoCurrencyStatus?,
): Boolean {
return cryptoCurrencyStatus?.currency?.network?.let { network ->
hasSingleWalletSignedHashesUseCase(userWallet = selectedWallet, network = network)
.conflate()
.distinctUntilChanged()
.firstOrNull()
} == true
}
private fun MutableList<WalletNotification>.addRateTheAppNotification(
isReadyToShowRating: Boolean,
clickIntents: WalletClickIntents,
) {
addIf(
element = WalletNotification.RateApp(
onLikeClick = clickIntents::onLikeAppClick,
onDislikeClick = clickIntents::onDislikeAppClick,
onCloseClick = clickIntents::onCloseRateAppWarningClick,
),
condition = isReadyToShowRating && isReadyForRateAppNotification,
)
}
private fun MutableList<WalletNotification>.addIf(element: WalletNotification, condition: Boolean) {
addIf(condition) {
if (element is WalletNotification.Critical ||
element is WalletNotification.Warning ||
element is WalletNotification.NoteMigration
) {
isReadyForRateAppNotification = false
}
element
}
}
private fun getPrimaryCurrencyStatusFlow(
userWallet: UserWallet,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
return getAccountStatusFlow(userWallet).mapNotNull { accountStatus ->
accountStatus.flattenCurrencies().firstOrNull()
}
.distinctUntilChanged()
.conflate()
.map { it.right() }
}
private fun getAccountStatusFlow(userWallet: UserWallet): Flow<AccountStatus.CryptoPortfolio> {
val accountId = AccountId.forMainCryptoPortfolio(userWalletId = userWallet.walletId)
return singleAccountStatusSupplier(SingleAccountStatusProducer.Params(accountId))
.filterIsInstance<AccountStatus.CryptoPortfolio>()
.distinctUntilChanged()
.conflate()
}
private companion object {
const val NOTE_MIGRATION_URL = "https://tangem.com/en/?promocode=Note10"
const val MAX_REMAINING_SIGNATURES_COUNT = 10
}
}

View file

@ -1,41 +1,24 @@
package com.tangem.feature.wallet.presentation.wallet.loaders
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.isMultiCurrency
import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.*
import javax.inject.Inject
@Suppress("LongParameterList")
@ModelScoped
internal class WalletContentLoaderFactory @Inject constructor(
private val multiWalletContentLoaderFactory: MultiWalletContentLoader.Factory,
private val singleWalletWithTokenContentLoaderFactory: SingleWalletWithTokenContentLoader.Factory,
private val singleWalletContentLoaderLegacyFactory: SingleWalletContentLoaderLegacy.Factory,
private val singleWalletContentLoader: SingleWalletContentLoader.Factory,
private val designFeatureToggles: DesignFeatureToggles,
) {
fun create(userWallet: UserWallet, isRefresh: Boolean = false): WalletContentLoader? {
fun create(userWallet: UserWallet): WalletContentLoader? {
return when {
userWallet.isMultiCurrency -> {
multiWalletContentLoaderFactory.create(userWallet)
}
userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() -> {
if (designFeatureToggles.isRedesignEnabled) {
userWallet is UserWallet.Cold -> {
singleWalletContentLoader.create(userWallet)
} else {
singleWalletWithTokenContentLoaderFactory.create(userWallet)
}
}
userWallet is UserWallet.Cold && !userWallet.isMultiCurrency -> {
if (designFeatureToggles.isRedesignEnabled) {
singleWalletContentLoader.create(userWallet)
} else {
singleWalletContentLoaderLegacyFactory.create(userWallet, isRefresh)
}
}
else -> null
}

View file

@ -63,10 +63,7 @@ internal class WalletScreenContentLoader @Inject constructor(
}
private fun loadInternal(userWallet: UserWallet, coroutineScope: CoroutineScope, isRefresh: Boolean) {
val loader = factory.create(
userWallet = userWallet,
isRefresh = isRefresh,
)
val loader = factory.create(userWallet = userWallet)
if (loader == null) {
TangemLogger.e("Impossible to create loader for $userWallet")

View file

@ -1,6 +1,5 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import dagger.assisted.Assisted
@ -13,24 +12,18 @@ internal class MultiWalletContentLoader @AssistedInject constructor(
private val accountListSubscriberFactory: AccountListSubscriber.Factory,
private val walletNFTListSubscriberFactory: WalletNFTListSubscriber.Factory,
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
private val multiWalletWarningsSubscriberFactory: MultiWalletWarningsSubscriber.Factory,
private val walletNotificationsSubscriberFactory: WalletNotificationsSubscriber.Factory,
private val multiWalletActionButtonsSubscriberFactory: MultiWalletActionButtonsSubscriber.Factory,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
private val assetsDiscoverySubscriberFactory: AssetsDiscoverySubscriber.Factory,
private val tokenListAnalyticsSubscriberFactory: TokenListAnalyticsSubscriber.Factory,
private val designFeatureToggles: DesignFeatureToggles,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> = listOfNotNull(
accountListSubscriberFactory.create(userWallet),
walletNFTListSubscriberFactory.create(userWallet),
checkWalletWithFundsSubscriberFactory.create(userWallet),
if (designFeatureToggles.isRedesignEnabled) {
walletNotificationsSubscriberFactory.create(userWallet)
} else {
multiWalletWarningsSubscriberFactory.create(userWallet)
},
walletNotificationsSubscriberFactory.create(userWallet),
multiWalletActionButtonsSubscriberFactory.create(userWallet),
tangemPayMainSubscriberFactory.create(userWallet),
tokenListAnalyticsSubscriberFactory.create(userWallet),

View file

@ -1,37 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.utils.annotations.RemoveWithToggle
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
@Suppress("LongParameterList")
internal class SingleWalletContentLoaderLegacy @AssistedInject constructor(
@Assisted private val userWallet: UserWallet.Cold,
@Assisted private val isRefresh: Boolean,
private val primaryCurrencySubscriberFactory: PrimaryCurrencySubscriber.Factory,
private val singleWalletButtonsSubscriberFactory: SingleWalletButtonsSubscriber.Factory,
private val singleWalletNotificationsSubscriberFactory: SingleWalletNotificationsSubscriber.Factory,
private val singleWalletExpressStatusesSubscriberFactory: SingleWalletExpressStatusesSubscriber.Factory,
private val txHistorySubscriberLegacyFactory: TxHistorySubscriberLegacy.Factory,
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> = listOf(
primaryCurrencySubscriberFactory.create(userWallet),
singleWalletButtonsSubscriberFactory.create(userWallet),
singleWalletNotificationsSubscriberFactory.create(userWallet),
singleWalletExpressStatusesSubscriberFactory.create(userWallet),
txHistorySubscriberLegacyFactory.create(userWallet, isRefresh),
checkWalletWithFundsSubscriberFactory.create(userWallet),
)
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet.Cold, isRefresh: Boolean): SingleWalletContentLoaderLegacy
}
}

View file

@ -1,32 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.presentation.wallet.subscribers.CheckWalletWithFundsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletWarningsSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.SingleWalletWithTokenSubscriberLegacy
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
import com.tangem.utils.annotations.RemoveWithToggle
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
internal class SingleWalletWithTokenContentLoader @AssistedInject constructor(
@Assisted private val userWallet: UserWallet.Cold,
private val singleWalletWithTokenSubscriberLegacyFactory: SingleWalletWithTokenSubscriberLegacy.Factory,
private val multiWalletWarningsSubscriberFactory: MultiWalletWarningsSubscriber.Factory,
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> = listOf(
singleWalletWithTokenSubscriberLegacyFactory.create(userWallet),
multiWalletWarningsSubscriberFactory.create(userWallet),
checkWalletWithFundsSubscriberFactory.create(userWallet),
)
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet.Cold): SingleWalletWithTokenContentLoader
}
}

View file

@ -1,6 +1,5 @@
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
@ -24,9 +23,7 @@ import javax.inject.Singleton
[REDACTED_AUTHOR]
*/
@Singleton
internal class WalletStateController @Inject constructor(
private val designFeatureToggles: DesignFeatureToggles,
) {
internal class WalletStateController @Inject constructor() {
val uiState: StateFlow<WalletScreenState> get() = mutableUiState
@ -91,21 +88,13 @@ internal class WalletStateController @Inject constructor(
fun getSelectedWalletId(): UserWalletId {
return with(value) {
if (designFeatureToggles.isRedesignEnabled) {
wallets2[selectedWalletIndex].walletsBalanceUM.id
} else {
wallets[selectedWalletIndex].walletCardState.id
}
}
}
fun getWalletIndexByWalletId(userWalletId: UserWalletId): Int? {
return with(value) {
if (designFeatureToggles.isRedesignEnabled) {
wallets2.indexOfFirstOrNull { it.walletsBalanceUM.id == userWalletId }
} else {
wallets.indexOfFirstOrNull { it.walletCardState.id == userWalletId }
}
}
}
@ -147,7 +136,6 @@ internal class WalletStateController @Inject constructor(
isHidingMode = false,
showMarketsOnboarding = false,
onDismissMarketsTooltip = {},
isRedesignEnabled = designFeatureToggles.isRedesignEnabled,
)
}
}

View file

@ -15,5 +15,4 @@ internal data class WalletScreenState(
val isHidingMode: Boolean,
val showMarketsOnboarding: Boolean,
val onDismissMarketsTooltip: () -> Unit,
val isRedesignEnabled: Boolean,
)

View file

@ -1,53 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.core.ui.components.marketprice.MarketPriceBlockState
import com.tangem.domain.appcurrency.model.AppCurrency
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 com.tangem.utils.annotations.RemoveWithToggle
import com.tangem.utils.logging.TangemLogger
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
internal class SetPrimaryCurrencyTransformer(
private val userWallet: UserWallet,
private val status: CryptoCurrencyStatus,
private val appCurrency: AppCurrency,
) : WalletStateTransformer(userWallet.walletId) {
override fun transform(prevState: WalletState): WalletState {
return when (prevState) {
is WalletState.SingleCurrency.Content -> {
prevState.copy(
walletCardState = prevState.walletCardState.toLoadedSingleCurrencyState(),
marketPriceBlockState = prevState.marketPriceBlockState.toLoadedState(),
)
}
is WalletState.SingleCurrency.Locked -> {
TangemLogger.w("Impossible to load primary currency status for locked wallet")
prevState
}
is WalletState.MultiCurrency -> {
TangemLogger.w("Impossible to load primary currency status for multi-currency wallet")
prevState
}
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // It will not be used
}
private fun WalletCardState.toLoadedSingleCurrencyState(): WalletCardState {
return SingleWalletCardStateConverter(status.value, userWallet, appCurrency).convert(value = this)
}
private fun MarketPriceBlockState.toLoadedState(): MarketPriceBlockState {
return SingleWalletMarketPriceConverter(status.value, appCurrency).convert(value = this)
}
}

View file

@ -26,7 +26,6 @@ internal class SetTokenListTransformer(
private val stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
private val shouldShowMainPromo: Boolean,
private val isAccountsModeEnabled: Boolean,
private val isRedesignEnabled: Boolean,
private val isMultipleCardsEnabled: Boolean,
private val isPolymarketEnabled: Boolean,
) : WalletStateTransformer(userWallet.walletId) {
@ -34,15 +33,12 @@ internal class SetTokenListTransformer(
private val tangemPayConverter by lazy {
TangemPayMainBlockConverter(
tangemPayClickIntents = clickIntents,
isRedesignEnabled = isRedesignEnabled,
isMultipleCardsEnabled = isMultipleCardsEnabled,
)
}
private val virtualAccountConverter by lazy {
VirtualAccountMainBlockConverter(
isRedesignEnabled = isRedesignEnabled,
)
VirtualAccountMainBlockConverter()
}
override fun transform(prevState: WalletState): WalletState {

View file

@ -15,22 +15,12 @@ internal abstract class WalletStateTransformer(
abstract fun transform(walletUM: WalletUM): WalletUM
final override fun transform(prevState: WalletScreenState): WalletScreenState {
return if (prevState.isRedesignEnabled) {
prevState.copy(
return prevState.copy(
wallets2 = prevState.wallets2
.map { walletUM ->
if (walletUM.walletsBalanceUM.id == userWalletId) transform(walletUM) else walletUM
}
.toImmutableList(),
)
} else {
prevState.copy(
wallets = prevState.wallets
.map { state ->
if (state.walletCardState.id == userWalletId) transform(state) else state
}
.toImmutableList(),
)
}
}
}

View file

@ -4,7 +4,6 @@ import androidx.compose.ui.text.SpanStyle
import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.*
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.formatStyled
import com.tangem.core.ui.res.TangemTheme
import com.tangem.domain.models.StatusSource
@ -20,7 +19,6 @@ import java.util.Currency
internal class TangemPayMainBlockConverter(
private val tangemPayClickIntents: TangemPayIntents,
private val isRedesignEnabled: Boolean,
private val isMultipleCardsEnabled: Boolean,
) : Converter<AccountStatus.Payment, TangemPayMainUM> {
@Suppress("LongMethod", "CyclomaticComplexMethod")
@ -99,21 +97,12 @@ internal class TangemPayMainBlockConverter(
private fun getBalanceText(currencyCode: String, balance: BigDecimal): TextReference {
val currency = Currency.getInstance(currencyCode)
val formattedBalance = if (isRedesignEnabled) {
balance.formatStyled {
return balance.formatStyled {
fiat(
fiatCurrencyCode = currency.currencyCode,
fiatCurrencySymbol = currency.symbol,
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
)
}
} else {
stringReference(
balance.format {
fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol)
},
)
}
return formattedBalance
}
}

View file

@ -5,7 +5,6 @@ import com.tangem.common.ui.R
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.core.ui.format.bigdecimal.formatStyled
import com.tangem.core.ui.format.bigdecimal.getJavaCurrencyByCode
import com.tangem.core.ui.res.TangemTheme
@ -17,9 +16,7 @@ import com.tangem.features.virtualaccount.main.entity.VirtualAccountMainUM
import com.tangem.utils.converter.Converter
import java.math.BigDecimal
internal class VirtualAccountMainBlockConverter(
private val isRedesignEnabled: Boolean,
) : Converter<AccountStatus.Virtual, VirtualAccountMainUM> {
internal class VirtualAccountMainBlockConverter : Converter<AccountStatus.Virtual, VirtualAccountMainUM> {
override fun convert(value: AccountStatus.Virtual): VirtualAccountMainUM {
return when (val statusValue = value.value) {
@ -66,21 +63,12 @@ internal class VirtualAccountMainBlockConverter(
private fun getBalanceText(currencyCode: String, balance: BigDecimal): TextReference {
val currency = getJavaCurrencyByCode(currencyCode)
val formattedBalance = if (isRedesignEnabled) {
balance.formatStyled {
return balance.formatStyled {
fiat(
fiatCurrencyCode = currency.currencyCode,
fiatCurrencySymbol = currency.symbol,
spanStyleReference = { SpanStyle(color = TangemTheme.colors2.text.neutral.secondary) },
)
}
} else {
stringReference(
balance.format {
fiat(fiatCurrencyCode = currency.currencyCode, fiatCurrencySymbol = currency.symbol)
},
)
}
return formattedBalance
}
}

View file

@ -1,6 +1,5 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
@ -37,7 +36,6 @@ internal class AccountListSubscriber @AssistedInject constructor(
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val designFeatureToggles: DesignFeatureToggles,
private val polymarketFeatureToggles: PolymarketFeatureToggles,
) : BasicAccountListSubscriber() {
@ -83,7 +81,6 @@ internal class AccountListSubscriber @AssistedInject constructor(
"promo=$shouldShowMainPromo, " +
"stakingMap=${stakingAvailabilityMap.size}",
)
if (designFeatureToggles.isRedesignEnabled) {
updateState2(
accountList = accountList,
appCurrency = appCurrency,
@ -95,17 +92,6 @@ internal class AccountListSubscriber @AssistedInject constructor(
isMultipleCardsEnabled = true,
isPolymarketEnabled = polymarketFeatureToggles.isPolymarketEnabled,
)
} else {
updateState(
accountList = accountList,
appCurrency = appCurrency,
expandedAccounts = expandedAccounts,
isAccountMode = isAccountMode,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
)
}
}
}

View file

@ -5,20 +5,13 @@ import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.utils.getOrElse
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.domain.tokens.error.TokenListError
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.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListErrorTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTokenListTransformer
import com.tangem.utils.logging.TangemLogger
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged
import java.math.BigDecimal
@ -43,48 +36,6 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
.distinctUntilChanged()
}
protected fun updateState(
accountList: AccountStatusList,
appCurrency: AppCurrency,
expandedAccounts: Set<AccountId>,
isAccountMode: Boolean,
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
shouldShowMainPromo: Boolean = false,
) {
val mainAccount = accountList.mainAccount
when {
!isAccountMode -> {
val isMainAccountEmpty = mainAccount.tokenList.flattenCurrencies().isEmpty()
val maybeTokenList = if (isMainAccountEmpty) {
Lce.Error(TokenListError.EmptyTokens)
} else {
Lce.Content(mainAccount.tokenList)
}
singleAccountTransform(
maybeTokenList = maybeTokenList,
appCurrency = appCurrency,
mainAccount = mainAccount,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
)
}
isAccountMode -> {
val convertParams = TokenConverterParams.Account(accountList, expandedAccounts)
updateContent(
params = convertParams,
appCurrency = appCurrency,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
)
}
}
}
protected fun updateState2(
accountList: AccountStatusList,
appCurrency: AppCurrency,
@ -106,75 +57,9 @@ internal abstract class BasicAccountListSubscriber : BasicWalletSubscriber() {
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
isAccountsModeEnabled = isAccountMode,
isRedesignEnabled = true,
isMultipleCardsEnabled = isMultipleCardsEnabled,
isPolymarketEnabled = isPolymarketEnabled,
),
)
}
private fun singleAccountTransform(
maybeTokenList: Lce<TokenListError, TokenList>,
appCurrency: AppCurrency,
mainAccount: AccountStatus.CryptoPortfolio,
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
shouldShowMainPromo: Boolean,
) {
val tokenList = maybeTokenList.getOrElse(
ifLoading = { maybeContent ->
val isRefreshing = stateController.getWalletState(userWallet.walletId)
?.pullToRefreshConfig
?.isRefreshing == true
maybeContent
?.takeIf { !isRefreshing }
?: return
},
ifError = { e ->
TangemLogger.e("Failed to load token list: $e")
stateController.update(
SetTokenListErrorTransformer(
selectedWallet = userWallet,
error = e,
appCurrency = appCurrency,
clickIntents = clickIntents,
),
)
return
},
)
updateContent(
params = TokenConverterParams.Wallet(mainAccount, tokenList),
appCurrency = appCurrency,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
)
}
private fun updateContent(
params: TokenConverterParams,
appCurrency: AppCurrency,
yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
shouldShowMainPromo: Boolean,
) {
stateController.update(
SetTokenListTransformer(
params = params,
userWallet = userWallet,
appCurrency = appCurrency,
clickIntents = clickIntents,
yieldSupplyApyMap = yieldSupplyApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
isAccountsModeEnabled = false,
isRedesignEnabled = false,
isMultipleCardsEnabled = false,
isPolymarketEnabled = false,
),
)
}
}

View file

@ -1,37 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.utils.annotations.RemoveWithToggle
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.conflate
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.mapNotNull
/**
* Basic implementation of [WalletSubscriber] for single wallet.
*
[REDACTED_AUTHOR]
*/
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
internal abstract class BasicSingleWalletSubscriber : BasicWalletSubscriber() {
/** Account ID for the main crypto portfolio of the user wallet */
val accountId: AccountId
get() = AccountId.forMainCryptoPortfolio(userWalletId = userWallet.walletId)
/**
* Provides a flow of the primary [CryptoCurrencyStatus] for the associated user wallet.
*
* @return A [Flow] emitting the primary [CryptoCurrencyStatus] of the wallet, distinct and conflated.
*/
protected fun getPrimaryCurrencyStatusFlow(): Flow<CryptoCurrencyStatus> {
return getMainAccountStatusFlow()
.mapNotNull { accountStatus ->
accountStatus.flattenCurrencies().firstOrNull()
}
.distinctUntilChanged()
.conflate()
}
}

View file

@ -1,77 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.stories.GetStoryContentUseCase
import com.tangem.domain.stories.models.StoryContentIds
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.GetMultiWalletWarningsFactory
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 com.tangem.utils.annotations.RemoveWithToggle
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@Suppress("LongParameterList")
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
internal class MultiWalletWarningsSubscriber @AssistedInject constructor(
@Assisted private val userWallet: UserWallet,
private val stateController: WalletStateController,
private val clickIntents: WalletClickIntents,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val getStoryContentUseCase: GetStoryContentUseCase,
) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<ImmutableList<WalletNotification>> {
return getMultiWalletWarningsFactory.create(userWallet, clickIntents)
.conflate()
.distinctUntilChanged()
.onEach { warnings ->
if (warnings.any { it is WalletNotification.YieldBoostPromo }) {
coroutineScope.launch {
getStoryContentUseCase.invokeSync(
id = StoryContentIds.STORY_FIRST_TIME_YIELD_PROMO.id,
refresh = true,
)
}
}
val displayedState = stateController.getWalletState(userWallet.walletId)
// Wait until the wallet appears in the list
stateController.uiState.first {
it.wallets.any { walletState -> walletState.walletCardState.id == userWallet.walletId }
}
stateController.update(
SetWarningsTransformer(
userWalletId = userWallet.walletId,
warnings = warnings,
notifications = persistentListOf(),
),
)
walletWarningsAnalyticsSender.send(displayedState, warnings)
walletWarningsSingleEventSender.send(
userWalletId = userWallet.walletId,
displayedUiState = displayedState,
newWarnings = warnings,
)
}
}
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet): MultiWalletWarningsSubscriber
}
}

View file

@ -1,95 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.common.extensions.isZero
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.Basic
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetPrimaryCurrencyTransformer
import com.tangem.utils.annotations.RemoveWithToggle
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.onEach
import java.math.BigDecimal
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
internal class PrimaryCurrencySubscriber @AssistedInject constructor(
@Assisted override val userWallet: UserWallet,
override val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val stateController: WalletStateController,
private val analyticsEventHandler: AnalyticsEventHandler,
) : BasicSingleWalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<*> {
return combine(
flow = getPrimaryCurrencyStatusFlow(),
flow2 = getSelectedAppCurrencyUseCase.invokeOrDefault(),
transform = ::Pair,
)
.onEach { (status, appCurrency) ->
updateContent(status, appCurrency)
sendAnalyticsEvent(status)
}
}
private fun updateContent(status: CryptoCurrencyStatus, appCurrency: AppCurrency) {
stateController.update(
SetPrimaryCurrencyTransformer(
status = status,
userWallet = userWallet,
appCurrency = appCurrency,
),
)
}
private fun sendAnalyticsEvent(status: CryptoCurrencyStatus) {
val fiatAmount = status.value.fiatAmount
val cardBalanceState = when (status.value) {
is CryptoCurrencyStatus.Loaded,
is CryptoCurrencyStatus.NoAccount,
is CryptoCurrencyStatus.NoAmount,
-> createCardBalanceState(fiatAmount)
is CryptoCurrencyStatus.NoQuote -> AnalyticsParam.CardBalanceState.NoRate
is CryptoCurrencyStatus.Unreachable,
-> AnalyticsParam.CardBalanceState.BlockchainError
is CryptoCurrencyStatus.MissedDerivation,
is CryptoCurrencyStatus.Loading,
is CryptoCurrencyStatus.Custom,
-> null
}
cardBalanceState?.let { balanceState ->
// do not send tokens count for single currency wallet
analyticsEventHandler.send(
event = Basic.BalanceLoaded(
balance = balanceState,
tokensCount = null,
),
)
}
}
private fun createCardBalanceState(fiatAmount: BigDecimal?): AnalyticsParam.CardBalanceState? {
return when {
fiatAmount == null -> null
fiatAmount.isZero() -> AnalyticsParam.CardBalanceState.Empty
else -> AnalyticsParam.CardBalanceState.Full
}
}
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet): PrimaryCurrencySubscriber
}
}

View file

@ -1,56 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2
import com.tangem.domain.models.wallet.UserWallet
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.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetCryptoCurrencyActionsTransformer
import com.tangem.utils.annotations.RemoveWithToggle
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.onEach
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
internal class SingleWalletButtonsSubscriber @AssistedInject constructor(
@Assisted override val userWallet: UserWallet,
override val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val stateController: WalletStateController,
private val clickIntents: WalletClickIntents,
private val getCryptoCurrencyActionsUseCaseV2: GetCryptoCurrencyActionsUseCaseV2,
) : BasicSingleWalletSubscriber() {
@OptIn(ExperimentalCoroutinesApi::class)
override fun create(coroutineScope: CoroutineScope): Flow<TokenActionsState> {
return getPrimaryCurrencyStatusFlow()
.flatMapLatest {
getCryptoCurrencyActionsUseCaseV2(accountId = accountId, currency = it.currency)
}
.onEach {
updateContent(tokenActionsState = it)
}
}
private fun updateContent(tokenActionsState: TokenActionsState) {
stateController.update(
SetCryptoCurrencyActionsTransformer(
tokenActionsState = tokenActionsState,
userWallet = userWallet,
clickIntents = clickIntents,
accountId = accountId,
),
)
}
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet): SingleWalletButtonsSubscriber
}
}

View file

@ -1,95 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.onramp.GetOnrampTransactionsUseCase
import com.tangem.domain.onramp.OnrampRemoveTransactionUseCase
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.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetExpressStatusesTransformer
import com.tangem.utils.annotations.RemoveWithToggle
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
@Suppress("LongParameterList")
internal class SingleWalletExpressStatusesSubscriber @AssistedInject constructor(
@Assisted override val userWallet: UserWallet,
override val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
private val stateController: WalletStateController,
private val clickIntents: WalletClickIntents,
private val analyticsEventHandler: AnalyticsEventHandler,
) : BasicSingleWalletSubscriber() {
@OptIn(ExperimentalCoroutinesApi::class)
override fun create(coroutineScope: CoroutineScope): Flow<*> {
val getOnrampTransactionsFlow = getPrimaryCurrencyStatusFlow().flatMapLatest { currencyStatus ->
getOnrampTransactionsUseCase(
userWalletId = userWallet.walletId,
cryptoCurrencyId = currencyStatus.currency.id,
)
.map { currencyStatus to it }
}
return combine(
flow = getOnrampTransactionsFlow,
flow2 = getSelectedAppCurrencyUseCase.invokeOrDefault(),
transform = ::toTriple,
)
.onEach { (status, maybeTransaction, appCurrency) ->
maybeTransaction.fold(
ifRight = { onrampTxs ->
onrampTxs.clearHiddenTerminal()
stateController.update(
SetExpressStatusesTransformer(
userWalletId = userWallet.walletId,
onrampTxs = onrampTxs,
clickIntents = clickIntents,
cryptoCurrencyStatus = status,
appCurrency = appCurrency,
analyticsEventHandler = analyticsEventHandler,
),
)
},
ifLeft = {
stateController.update(
SetExpressStatusesTransformer(
userWalletId = userWallet.walletId,
onrampTxs = emptyList(),
clickIntents = clickIntents,
cryptoCurrencyStatus = status,
appCurrency = appCurrency,
analyticsEventHandler = analyticsEventHandler,
),
)
},
)
}
}
private fun <A, B, C> toTriple(firstPair: Pair<A, B>, second: C): Triple<A, B, C> {
return Triple(firstPair.first, firstPair.second, second)
}
private suspend fun List<OnrampTransaction>.clearHiddenTerminal() {
this
.filter { it.status.isHidden && it.status.isTerminal }
.forEach { onrampRemoveTransactionUseCase(txId = it.txId) }
}
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet): SingleWalletExpressStatusesSubscriber
}
}

View file

@ -1,51 +0,0 @@
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.domain.GetSingleWalletWarningsFactory
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 com.tangem.utils.annotations.RemoveWithToggle
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
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.onEach
/**
[REDACTED_AUTHOR]
*/
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
internal class SingleWalletNotificationsSubscriber @AssistedInject constructor(
@Assisted private val userWallet: UserWallet,
private val stateController: WalletStateController,
private val getSingleWalletWarningsFactory: GetSingleWalletWarningsFactory,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val clickIntents: WalletClickIntents,
) : WalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<ImmutableList<WalletNotification>> {
return getSingleWalletWarningsFactory.create(userWallet, clickIntents)
.conflate()
.distinctUntilChanged()
.onEach { warnings ->
val displayedState = stateController.getWalletState(userWallet.walletId)
stateController.update(SetWarningsTransformer(userWallet.walletId, warnings, persistentListOf()))
walletWarningsAnalyticsSender.send(displayedState, warnings)
}
}
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet): SingleWalletNotificationsSubscriber
}
}

View file

@ -1,38 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
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.WalletStateController
import com.tangem.utils.annotations.RemoveWithToggle
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
internal class SingleWalletWithTokenSubscriberLegacy @AssistedInject constructor(
@Assisted override val userWallet: UserWallet.Cold,
override val accountDependencies: AccountDependencies,
override val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
override val stateController: WalletStateController,
override val clickIntents: WalletClickIntents,
) : BasicAccountListSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<Unit> = combine(
flow = getAccountStatusListFlow(),
flow2 = getAppCurrencyFlow(),
flow3 = accountDependencies.expandedAccountsHolder.expandedAccounts(userWallet),
flow4 = accountDependencies.isAccountsModeEnabledUseCase(),
transform = ::updateState,
)
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet.Cold): SingleWalletWithTokenSubscriberLegacy
}
}

View file

@ -1,134 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.subscribers
import androidx.paging.PagingData
import androidx.paging.cachedIn
import androidx.paging.map
import arrow.core.Either
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.TxInfo
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.txhistory.models.TxHistoryListError
import com.tangem.domain.txhistory.models.TxHistoryStateError
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsCountUseCase
import com.tangem.domain.txhistory.usecase.GetTxHistoryItemsUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryCountErrorTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryCountTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsErrorTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.SetTxHistoryItemsTransformer
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TxHistoryItemStateConverter
import com.tangem.utils.annotations.RemoveWithToggle
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
@Suppress("LongParameterList")
internal class TxHistorySubscriberLegacy @AssistedInject constructor(
@Assisted override val userWallet: UserWallet.Cold,
@Assisted private val isRefresh: Boolean,
override val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
private val stateController: WalletStateController,
private val clickIntents: WalletClickIntents,
) : BasicSingleWalletSubscriber() {
override fun create(coroutineScope: CoroutineScope): Flow<PagingData<TxInfo>> {
return flow {
getPrimaryCurrencyStatusFlow().collectLatest { status ->
val maybeTxHistoryItemCount = txHistoryItemsCountUseCase(
userWalletId = userWallet.walletId,
currency = status.currency,
)
setLoadingTxHistoryState(maybeTxHistoryItemCount, status)
maybeTxHistoryItemCount.onRight { _ ->
val maybeTxHistoryItems = txHistoryItemsUseCase(
userWalletId = userWallet.walletId,
currency = status.currency,
refresh = isRefresh,
).map { it.cachedIn(coroutineScope) }
setLoadedTxHistoryState(maybeTxHistoryItems, currency = status.currency)
}
}
}
}
private fun setLoadingTxHistoryState(
maybeTxHistoryItemCount: Either<TxHistoryStateError, Int>,
status: CryptoCurrencyStatus,
) {
stateController.update(
maybeTxHistoryItemCount.fold(
ifLeft = { error ->
SetTxHistoryCountErrorTransformer(
userWallet = userWallet,
error = error,
pendingTransactions = status.value.pendingTransactions,
clickIntents = clickIntents,
currency = status.currency,
)
},
ifRight = { txCount ->
SetTxHistoryCountTransformer(
userWalletId = userWallet.walletId,
transactionsCount = txCount,
clickIntents = clickIntents,
)
},
),
)
}
private fun setLoadedTxHistoryState(
maybeTxHistoryItems: Either<TxHistoryListError, Flow<PagingData<TxInfo>>>,
currency: CryptoCurrency,
) {
stateController.update(
maybeTxHistoryItems.fold(
ifLeft = { error ->
SetTxHistoryItemsErrorTransformer(
userWalletId = userWallet.walletId,
error = error,
clickIntents = clickIntents,
)
},
ifRight = { itemsFlow ->
val blockchain = userWallet.scanResponse.cardTypesResolver.getBlockchain()
val itemConverter = TxHistoryItemStateConverter(
currency = currency,
symbol = blockchain.currency,
decimals = blockchain.decimals(),
clickIntents = clickIntents,
)
SetTxHistoryItemsTransformer(
userWallet = userWallet,
flow = itemsFlow.map { items ->
items.map(itemConverter::convert)
},
clickIntents = clickIntents,
)
},
),
)
}
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet.Cold, isRefresh: Boolean): TxHistorySubscriberLegacy
}
}

View file

@ -1,847 +0,0 @@
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.*
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideIn
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.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.geometry.CornerRadius
import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.geometry.RoundRect
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Outline
import androidx.compose.ui.graphics.Path
import androidx.compose.ui.graphics.Shape
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.res.painterResource
import androidx.compose.ui.semantics.clearAndSetSemantics
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.PreviewParameterProvider
import androidx.compose.ui.unit.*
import androidx.paging.compose.collectAsLazyPagingItems
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheet
import com.tangem.common.ui.bottomsheet.chooseaddress.ChooseAddressBottomSheetConfig
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheet
import com.tangem.common.ui.expressStatus.ExpressStatusBottomSheetConfig
import com.tangem.common.ui.expressStatus.expressTransactionsItemsLegacy
import com.tangem.core.ui.components.atoms.handComposableComponentHeight
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.bottomsheets.sheet.TangemBottomSheetDraggableHeaderLegacy
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.components.containers.pullToRefresh.TangemPullToRefreshContainer
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.components.transactions.state.TxHistoryState
import com.tangem.core.ui.event.StateEvent
import com.tangem.core.ui.extensions.softLayerShadow
import com.tangem.core.ui.extensions.stringResourceSafe
import com.tangem.core.ui.res.LocalMainBottomSheetColor
import com.tangem.core.ui.res.LocalWindowSize
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.MainScreenTestTags
import com.tangem.core.ui.test.MarketTooltipTestTags
import com.tangem.core.ui.utils.TangemSharedTransitionLayout
import com.tangem.core.ui.utils.toPx
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewDataLegacy.accountScreenState
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewDataLegacy.accountScreenWithEmptyTokensState
import com.tangem.feature.wallet.presentation.common.preview.WalletScreenPreviewDataLegacy.walletScreenState
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.model.holder.TxHistoryStateHolder
import com.tangem.feature.wallet.presentation.wallet.ui.components.WalletsList
import com.tangem.feature.wallet.presentation.wallet.ui.components.common.*
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.nftCollections
import com.tangem.feature.wallet.presentation.wallet.ui.components.multicurrency.organizeTokensButton
import com.tangem.feature.wallet.presentation.wallet.ui.components.singlecurrency.marketPriceBlock
import com.tangem.feature.wallet.presentation.wallet.ui.utils.changeWalletAnimator
import com.tangem.features.promobanners.api.PromoBannersBlockComponent
import com.tangem.features.tangempay.component.TangemPayMainBlockComponent
import com.tangem.features.tangempay.entity.TangemPayMainUM
import com.tangem.features.virtualaccount.main.component.VirtualAccountMainBlockComponent
import com.tangem.features.virtualaccount.main.entity.VirtualAccountMainUM
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@Suppress("LongParameterList")
@Composable
internal fun WalletScreen(
state: WalletScreenState,
tangemPayComponent: TangemPayMainBlockComponent,
virtualAccountComponent: VirtualAccountMainBlockComponent,
promoBannersBlockComponent: PromoBannersBlockComponent? = null,
bottomSheetContent: @Composable (onExpandSheet: () -> Unit) -> 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) }
WalletContent(
state = state,
tangemPayComponent = tangemPayComponent,
virtualAccountComponent = virtualAccountComponent,
walletsListState = walletsListState,
snackbarHostState = snackbarHostState,
isAutoScroll = isAutoScroll,
onAutoScrollReset = { isAutoScroll.value = false },
promoBannersBlockComponent = promoBannersBlockComponent,
bottomSheetContent = bottomSheetContent,
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
onBottomSheetStateChange = onBottomSheetStateChange,
)
WalletEventEffectLegacy(
walletsListState = walletsListState,
snackbarHostState = snackbarHostState,
event = state.event,
onAutoScrollSet = { isAutoScroll.value = true },
)
}
@Suppress("LongMethod", "LongParameterList", "CyclomaticComplexMethod")
@Composable
private fun WalletContent(
state: WalletScreenState,
tangemPayComponent: TangemPayMainBlockComponent,
virtualAccountComponent: VirtualAccountMainBlockComponent,
walletsListState: LazyListState,
snackbarHostState: SnackbarHostState,
isAutoScroll: State<Boolean>,
onAutoScrollReset: () -> Unit,
promoBannersBlockComponent: PromoBannersBlockComponent? = null,
bottomSheetHeaderHeightProvider: () -> Dp,
onBottomSheetStateChange: (BottomSheetState) -> Unit,
bottomSheetContent: @Composable (onExpandSheet: () -> Unit) -> 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.wallets.getOrElse(selectedWalletIndex) { state.wallets[state.selectedWalletIndex] }
val listState = rememberLazyListState()
val scaffoldContent: @Composable (PaddingValues?) -> Unit = { paddingValues ->
val movableItemModifier = Modifier.changeWalletAnimator(walletsListState)
val lazyTxHistoryItems = (selectedWallet as? TxHistoryStateHolder)?.let { walletState ->
(walletState.txHistoryState as? TxHistoryState.Content)?.contentItems?.collectAsLazyPagingItems()
}
val txHistoryItems by remember(selectedWallet.walletCardState.id, lazyTxHistoryItems?.itemCount) {
mutableStateOf(value = lazyTxHistoryItems)
}
val betweenItemsPadding = TangemTheme.dimens.spacing12
val horizontalPadding = TangemTheme.dimens.spacing16
val itemModifier = movableItemModifier
.padding(top = betweenItemsPadding)
.padding(horizontal = horizontalPadding)
val bottomBarHeight = with(LocalDensity.current) { WindowInsets.systemBars.getBottom(this).toDp() }
val marketHintAproxHeight = with(LocalDensity.current) {
TangemTheme.typography.caption2.lineHeight.toDp() * 2
} + 40.dp
val contentPadding = paddingValues?.let {
PaddingValues(
bottom = it.calculateBottomPadding() + marketHintAproxHeight + 52.dp,
)
} ?: PaddingValues(bottom = TangemTheme.dimens.spacing92 + bottomBarHeight)
TangemSharedTransitionLayout {
LazyColumn(
modifier = Modifier.testTag(MainScreenTestTags.SCREEN_CONTAINER),
state = listState,
contentPadding = contentPadding,
horizontalAlignment = Alignment.CenterHorizontally,
) {
item(
// !!! Type of the key should be saveable via Bundle on Android !!!
key = state.wallets.map { it.walletCardState.id.stringValue },
contentType = state.wallets.map { it.walletCardState.id },
) {
WalletsList(
modifier = Modifier.animateItem(fadeInSpec = null, fadeOutSpec = null),
lazyListState = walletsListState,
wallets = state.wallets.map(WalletState::walletCardState).toImmutableList(),
isBalanceHidden = state.isHidingMode,
)
}
when (selectedWallet) {
is WalletState.MultiCurrency -> {
actions(
actions = selectedWallet.buttons,
selectedWalletIndex = selectedWalletIndex,
modifier = movableItemModifier.padding(top = betweenItemsPadding),
)
}
is WalletState.SingleCurrency -> {
lazyActions(
actions = selectedWallet.buttons,
selectedWalletIndex = selectedWalletIndex,
modifier = movableItemModifier.padding(top = betweenItemsPadding),
)
}
}
notifications(configs = selectedWallet.warnings, modifier = itemModifier)
promoBannersBlockComponent?.let { component ->
item(key = "PromoBannersBlock") {
component.ContentWithPadding(
horizontalItemPadding = 12.dp,
modifier = itemModifier,
walletId = null,
)
}
}
tangemPayItem(
modifier = itemModifier,
state = selectedWallet,
isHidingMode = state.isHidingMode,
tangemPayComponent = tangemPayComponent,
)
virtualAccountItem(
modifier = itemModifier,
state = selectedWallet,
isHidingMode = state.isHidingMode,
virtualAccountComponent = virtualAccountComponent,
)
(selectedWallet as? WalletState.SingleCurrency)?.let { walletState ->
walletState.marketPriceBlockState?.let { marketPriceBlockState ->
marketPriceBlock(state = marketPriceBlockState, modifier = itemModifier)
}
if (walletState is WalletState.SingleCurrency.Content) {
expressTransactionsItemsLegacy(
expressTxs = walletState.expressTxsToDisplay,
modifier = itemModifier,
)
}
}
contentItems(
state = selectedWallet,
txHistoryItems = txHistoryItems,
isBalanceHidden = state.isHidingMode,
modifier = movableItemModifier,
)
nftCollections(state = selectedWallet, itemModifier = itemModifier)
organizeTokens(state = selectedWallet, itemModifier = itemModifier)
}
}
ShowBottomSheet(bottomSheetConfig = selectedWallet.bottomSheetConfig)
WalletsListEffects(
lazyListState = walletsListState,
selectedWalletIndex = selectedWalletIndex,
onUserScroll = onAutoScrollReset,
onIndexChange = { index ->
// Auto scroll must not change wallet
if (isAutoScroll.value) {
state.onWalletChange(index, true)
} else {
state.onWalletChange(index, false)
}
},
)
}
BaseScaffoldWithMarkets(
state = state,
listState = listState,
selectedWallet = selectedWallet,
snackbarHostState = snackbarHostState,
bottomSheetHeaderHeightProvider = bottomSheetHeaderHeightProvider,
onBottomSheetStateChange = onBottomSheetStateChange,
bottomSheetContent = bottomSheetContent,
content = scaffoldContent,
)
}
@Suppress("LongParameterList", "LongMethod", "CyclomaticComplexMethod")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private inline fun BaseScaffoldWithMarkets(
state: WalletScreenState,
listState: LazyListState,
selectedWallet: WalletState,
snackbarHostState: SnackbarHostState,
bottomSheetHeaderHeightProvider: () -> Dp,
noinline onBottomSheetStateChange: (BottomSheetState) -> Unit,
crossinline bottomSheetContent: @Composable (onExpandSheet: () -> Unit) -> Unit,
crossinline content: @Composable (PaddingValues) -> Unit,
) {
val isKeyboardVisible by rememberIsKeyboardVisible()
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 = TangemTheme.colors.background.tertiary
val bottomSheetState = rememberTangemStandardBottomSheetState()
val scaffoldState = rememberTangemBottomSheetScaffoldState(
bottomSheetState = bottomSheetState,
snackbarHostState = snackbarHostState,
)
val showMarketsHint by remember {
derivedStateOf {
// Show hint only when there are items in the list
// and when there a no items to scroll
listState.layoutInfo.totalItemsCount > 0 &&
!listState.canScrollBackward && !listState.canScrollForward ||
listState.canScrollBackward && !listState.canScrollForward
}
}
CompositionLocalProvider(
LocalMainBottomSheetColor provides remember(background) { mutableStateOf(background) },
) {
val backgroundColor by LocalMainBottomSheetColor.current
var isSearchFieldFocused by remember { mutableStateOf(false) }
val isNavBarVisible = remember { mutableStateOf(true) }
BottomSheetStateEffects(
bottomSheetState = bottomSheetState,
onBottomSheetStateChange = onBottomSheetStateChange,
navigationBarVisible = isNavBarVisible,
isSearchFieldFocused = isSearchFieldFocused,
)
Box {
TangemBottomSheetScaffold(
snackbarHost = {
WalletSnackbarHost(
snackbarHostState = it,
event = state.event,
modifier = Modifier
.padding(bottom = TangemTheme.dimens.spacing4)
.navigationBarsPadding(),
)
},
sheetPeekHeight = peekHeight,
containerColor = TangemTheme.colors.background.secondary,
scaffoldState = scaffoldState,
bottomSheet = {
CustomBottomSheet(
state = scaffoldState.bottomSheetState,
peekHeight = peekHeight,
modifier = Modifier
.softLayerShadow(
radius = 8.dp,
color = Color.Black.copy(
alpha = if (isSystemInDarkTheme()) .16f else .08f,
),
shape = TangemTheme.shapes.bottomSheetLarge,
offset = DpOffset(x = 0.dp, y = (-4).dp),
isAlphaContentClip = true,
)
.clip(TangemTheme.shapes.bottomSheetLarge)
.background(backgroundColor),
content = {
// hide bottom sheet when back pressed
BackHandler(
isKeyboardVisible.not() &&
bottomSheetState.currentValue == TangemSheetValue.Expanded,
) {
coroutineScope.launch { bottomSheetState.partialExpand() }
}
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
// expand bottom sheet when clicked on the drag handle
.clickable(
enabled = bottomSheetState.currentValue == TangemSheetValue.PartiallyExpanded,
indication = null,
interactionSource = null,
) {
coroutineScope.launch { bottomSheetState.expand() }
}
.sizeIn(maxHeight = maxHeight - statusBarHeight),
) {
TangemBottomSheetDraggableHeaderLegacy(backgroundColor)
Box(
modifier = Modifier
.onFocusChanged {
isSearchFieldFocused = it.isFocused
},
) {
bottomSheetContent {
coroutineScope.launch { bottomSheetState.expand() }
}
}
}
},
)
},
content = { paddingValues ->
Box {
MarketsHint(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = peekHeight + 12.dp)
.fillMaxWidth(fraction = .4f),
isVisible = showMarketsHint,
)
Column {
WalletTopBar(config = state.topBarConfig)
TangemPullToRefreshContainer(config = selectedWallet.pullToRefreshConfig) {
content(paddingValues)
}
}
BottomSheetScrim(
color = Color.Black.copy(alpha = .40f),
visible = bottomSheetState.targetValue == TangemSheetValue.Expanded,
onDismissRequest = {
coroutineScope.launch { bottomSheetState.partialExpand() }
},
)
MarketsTooltip(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 8.dp)
.padding(horizontal = 12.dp)
.fillMaxWidth(),
isVisible = state.showMarketsOnboarding,
availableHeight = maxHeight,
bottomSheetState = bottomSheetState,
onCloseClick = state.onDismissMarketsTooltip,
)
}
},
)
AnimatedVisibility(
modifier = Modifier.align(Alignment.BottomCenter),
visible = isNavBarVisible.value,
) {
Box(
Modifier
.align(Alignment.BottomCenter)
.background(backgroundColor)
.height(bottomBarHeight)
.fillMaxWidth(),
)
}
}
LaunchedEffect(state.showMarketsOnboarding, bottomSheetState.targetValue) {
if (state.showMarketsOnboarding && bottomSheetState.targetValue == TangemSheetValue.Expanded) {
state.onDismissMarketsTooltip()
}
}
}
}
@Composable
private fun MarketsTooltip(
availableHeight: Dp,
bottomSheetState: TangemSheetState,
isVisible: Boolean,
onCloseClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val density = LocalDensity.current
val tooltipOffset by remember {
derivedStateOf {
val bottomSheetOffset = try {
// Can throw exception during the first composition
with(density) { bottomSheetState.requireOffset().toDp() }
} catch (e: Exception) {
0.dp
}
bottomSheetOffset - availableHeight
}
}
var visible by remember { mutableStateOf(value = false) }
LaunchedEffect(isVisible) {
if (isVisible) {
delay(timeMillis = 300)
}
visible = isVisible
}
val slideOffset = 40.dp.toPx()
AnimatedVisibility(
modifier = modifier
.offset { IntOffset(x = 0, y = tooltipOffset.roundToPx()) }
.testTag(MarketTooltipTestTags.CONTAINER),
visible = visible,
enter = slideIn(
animationSpec = spring(
stiffness = Spring.StiffnessLow,
visibilityThreshold = IntOffset.VisibilityThreshold,
),
initialOffset = { _ -> IntOffset(y = -slideOffset.roundToInt(), x = 0) },
) + fadeIn(),
exit = fadeOut(),
) {
MarketsTooltipContent(onCloseClick = onCloseClick)
}
}
@Composable
private fun MarketsHint(isVisible: Boolean, modifier: Modifier = Modifier) {
AnimatedVisibility(
modifier = modifier,
visible = isVisible,
enter = fadeIn(animationSpec = tween(durationMillis = 500)),
exit = fadeOut(),
) {
Column(
verticalArrangement = Arrangement.spacedBy(space = 4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
) {
Text(
text = stringResourceSafe(R.string.markets_hint),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.tertiary,
textAlign = TextAlign.Center,
)
Icon(
modifier = Modifier.size(size = 24.dp),
painter = painterResource(id = R.drawable.ic_chevron_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}
}
@Composable
private fun MarketsTooltipContent(onCloseClick: () -> Unit, modifier: Modifier = Modifier) {
val backgroundColor = TangemTheme.colors.background.action
val tipDpSize = DpSize(width = 20.dp, height = 8.dp)
val tooltipShape = remember(tipDpSize) { TooltipShape(cornerRadius = 16.dp, tipSize = tipDpSize) }
Row(
modifier = modifier
.shadow(
elevation = TangemTheme.dimens.elevation12,
shape = tooltipShape,
clip = false,
ambientColor = Color.Black.copy(alpha = 0.7f),
)
.background(backgroundColor, tooltipShape)
.clickable(interactionSource = null, indication = null, onClick = {})
.padding(all = 12.dp)
.padding(bottom = tipDpSize.height),
horizontalArrangement = Arrangement.spacedBy(space = 12.dp),
verticalAlignment = Alignment.Top,
) {
Icon(
modifier = Modifier.size(size = 18.dp),
painter = painterResource(id = R.drawable.ic_plus_18),
tint = Color.Unspecified,
contentDescription = null,
)
Column(
modifier = Modifier.weight(1f),
verticalArrangement = Arrangement.spacedBy(space = 2.dp),
) {
Text(
text = stringResourceSafe(id = R.string.markets_tooltip_v2_title),
style = TangemTheme.typography.subtitle2,
color = TangemTheme.colors.text.primary1,
)
Text(
text = stringResourceSafe(id = R.string.markets_tooltip_message),
style = TangemTheme.typography.caption2,
color = TangemTheme.colors.text.secondary,
)
}
Icon(
modifier = Modifier
.size(size = 16.dp)
.clickable(
interactionSource = null,
indication = null,
onClick = onCloseClick,
)
.testTag(MarketTooltipTestTags.CLOSE_BUTTON),
painter = painterResource(id = R.drawable.ic_close_24),
tint = TangemTheme.colors.icon.informative,
contentDescription = null,
)
}
}
private class TooltipShape(
private val cornerRadius: Dp,
private val tipSize: DpSize,
) : Shape {
override fun createOutline(size: Size, layoutDirection: LayoutDirection, density: Density): Outline {
val cornerRadiusPx = with(density) { cornerRadius.toPx() }
val tipWidth = with(density) { tipSize.width.toPx() }
val tipHeight = with(density) { tipSize.height.toPx() }
val bodyHeight = size.height - tipHeight
val path = Path().apply {
addRoundRect(
RoundRect(
rect = Rect(left = 0f, top = 0f, right = size.width, bottom = bodyHeight),
cornerRadius = CornerRadius(cornerRadiusPx),
),
)
moveTo(size.width / 2 - tipWidth / 2, bodyHeight)
lineTo(size.width / 2, size.height)
lineTo(size.width / 2 + tipWidth / 2, bodyHeight)
close()
}
return Outline.Generic(path)
}
}
@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)
}
}
}
internal fun LazyListScope.organizeTokens(state: WalletState, itemModifier: Modifier) {
val multiCurrencyState = state as? WalletState.MultiCurrency ?: return
val contentState = multiCurrencyState.tokensListState as? WalletTokensListState.ContentState ?: return
val config = contentState.organizeTokensButtonConfig ?: return
organizeTokensButton(
modifier = itemModifier,
config = config,
)
}
internal fun LazyListScope.nftCollections(state: WalletState, itemModifier: Modifier) {
(state as? WalletState.MultiCurrency)?.let {
nftCollections(
modifier = itemModifier,
state = it.nftState,
)
}
}
internal fun LazyListScope.tangemPayItem(
state: WalletState,
isHidingMode: Boolean,
tangemPayComponent: TangemPayMainBlockComponent,
modifier: Modifier = Modifier,
) {
if (state !is WalletState.MultiCurrency) return
with(tangemPayComponent) {
tangemPayMainContent(modifier = modifier, state = state.tangemPayMainUM, isBalanceHidden = isHidingMode)
}
}
internal fun LazyListScope.virtualAccountItem(
state: WalletState,
isHidingMode: Boolean,
virtualAccountComponent: VirtualAccountMainBlockComponent,
modifier: Modifier = Modifier,
) {
if (state !is WalletState.MultiCurrency) return
with(virtualAccountComponent) {
virtualAccountMainContent(
modifier = modifier,
state = state.virtualAccountMainUM,
isBalanceHidden = isHidingMode,
)
}
}
@Composable
private fun ShowBottomSheet(bottomSheetConfig: TangemBottomSheetConfig?) {
if (bottomSheetConfig != null) {
when (bottomSheetConfig.content) {
is ChooseAddressBottomSheetConfig -> ChooseAddressBottomSheet(config = bottomSheetConfig)
is ExpressStatusBottomSheetConfig -> ExpressStatusBottomSheet(config = bottomSheetConfig)
}
}
}
// region Preview
@Preview(showBackground = true, widthDp = 360)
@Preview(showBackground = true, widthDp = 360, uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun WalletScreen_Preview(@PreviewParameter(WalletScreenPreviewProvider::class) data: WalletScreenState) {
TangemThemePreview {
WalletScreen(
state = data,
tangemPayComponent = object : TangemPayMainBlockComponent {
override fun LazyListScope.tangemPayMainContent(
state: TangemPayMainUM,
isBalanceHidden: Boolean,
modifier: Modifier,
) {
}
},
virtualAccountComponent = object : VirtualAccountMainBlockComponent {
override fun LazyListScope.virtualAccountMainContent(
state: VirtualAccountMainUM,
isBalanceHidden: Boolean,
modifier: Modifier,
) {
}
},
bottomSheetContent = {
Text("Markets Content")
},
bottomSheetHeaderHeightProvider = { 10.dp },
onBottomSheetStateChange = {},
)
}
}
private class WalletScreenPreviewProvider : 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

@ -13,11 +13,9 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.lerp
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import com.tangem.core.ui.components.haze.hazeEffectTangem
import com.tangem.core.ui.ds.topbar.TangemTopBar
@ -32,9 +30,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect
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.WalletPreviewDataLegacy
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTopBarConfig
import com.tangem.utils.annotations.RemoveWithToggle
import dev.chrisbanes.haze.HazeProgressive
import dev.chrisbanes.haze.HazeTint
import kotlinx.collections.immutable.persistentListOf
@ -114,46 +110,6 @@ internal fun WalletTopBar(
}
}
/**
* Wallet screen top bar
*
* @param config component config
*/
@Suppress("MagicNumber")
@Deprecated("Remove with main toggle [DesignFeatureToggles.isRedesignEnabled]")
@RemoveWithToggle("APP_REDESIGN_ENABLED")
@OptIn(ExperimentalMaterial3Api::class)
@Composable
internal fun WalletTopBar(config: WalletTopBarConfig) {
TopAppBar(
title = {
Icon(painter = painterResource(id = R.drawable.img_tangem_logo_90_24), contentDescription = null)
},
actions = {
config.endActions.forEach { action ->
action.onClick?.let { onClick ->
IconButton(onClick = onClick) {
Icon(
painter = painterResource(id = action.iconRes),
contentDescription = null,
modifier = Modifier
.rotate(if (action.iconRes == R.drawable.ic_more_default_24) 90f else 0f)
.testTag(MainScreenTestTags.MORE_BUTTON),
)
}
}
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = TangemTheme.colors.background.secondary,
titleContentColor = TangemTheme.colors.icon.primary1,
actionIconContentColor = TangemTheme.colors.icon.primary1,
),
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
modifier = Modifier.testTag(MainScreenTestTags.TOP_BAR),
)
}
@Composable
private fun Modifier.hazeEffectTangemTopBar(behavior: TangemCollapsingAppBarBehavior): Modifier {
val rootBackground by LocalRootBackgroundColor.current
@ -169,15 +125,6 @@ private fun Modifier.hazeEffectTangemTopBar(behavior: TangemCollapsingAppBarBeha
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun Preview_WalletTopBar() {
TangemThemePreview {
WalletTopBar(config = WalletPreviewDataLegacy.topBarConfig)
}
}
// region Preview
@Composable
@Preview(showBackground = true, widthDp = 360)

View file

@ -73,7 +73,6 @@ class SetTokenListTransformerTest {
clickIntents = mockk<WalletClickIntents>(relaxed = true),
shouldShowMainPromo = false,
isAccountsModeEnabled = false,
isRedesignEnabled = true,
isMultipleCardsEnabled = false,
isPolymarketEnabled = false,
)