Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-25 16:39:35 +03:00
commit bfd903b43c
595 changed files with 12755 additions and 19837 deletions

View file

@ -9,7 +9,7 @@ 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.OrganizeTokensModel
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensScreen
import com.tangem.feature.wallet.child.organizetokens.ui.OrganizeTokensScreen
import kotlinx.coroutines.launch
internal class OrganizeTokensComponent(

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.organizetokens.analytics
package com.tangem.feature.wallet.child.organizetokens.analytics
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.organizetokens.model
package com.tangem.feature.wallet.child.organizetokens.entity
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.components.token.state.TokenItemState

View file

@ -1,27 +1,9 @@
package com.tangem.feature.wallet.presentation.organizetokens.model
package com.tangem.feature.wallet.child.organizetokens.entity
import androidx.compose.runtime.Immutable
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
@Deprecated("Use OrganizeTokensListUM instead, will be removed in future releases")
@Immutable
internal sealed class OrganizeTokensListState {
abstract val items: PersistentList<DraggableItem>
data class GroupedByNetwork(
override val items: PersistentList<DraggableItem>,
) : OrganizeTokensListState()
data class Ungrouped(
override val items: PersistentList<DraggableItem>,
) : OrganizeTokensListState()
data object Empty : OrganizeTokensListState() {
override val items: PersistentList<DraggableItem> = persistentListOf()
}
}
@Immutable
internal sealed interface OrganizeTokensListUM {

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.organizetokens.model
package com.tangem.feature.wallet.child.organizetokens.entity
import androidx.compose.runtime.Immutable
import com.tangem.core.ui.event.StateEvent
@ -7,7 +7,6 @@ import org.burnoutcrew.reorderable.ItemPosition
@Immutable
internal data class OrganizeTokensState(
val onBackClick: () -> Unit,
val itemsState: OrganizeTokensListState,
val tokenListUM: OrganizeTokensListUM,
val header: HeaderConfig,
val actions: ActionsConfig,

View file

@ -0,0 +1,35 @@
package com.tangem.feature.wallet.child.organizetokens.model
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.model.AccountCryptoCurrencies
import com.tangem.domain.models.account.filterCryptoPortfolio
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensListUM
internal class CryptoCurrenciesIdsResolver {
fun resolve(tokensListUM: OrganizeTokensListUM, accountStatusList: AccountStatusList?): AccountCryptoCurrencies {
if (accountStatusList == null) return emptyMap()
val draggableTokens = when (tokensListUM) {
OrganizeTokensListUM.EmptyList -> return emptyMap()
is OrganizeTokensListUM.AccountList,
is OrganizeTokensListUM.TokensList,
-> tokensListUM.items.filterIsInstance<DraggableItem.Token>()
}
return accountStatusList.accountStatuses
.filterCryptoPortfolio()
.filter { it.tokenList != TokenList.Empty }
.associate { accountStatus ->
val currenciesById = accountStatus.flattenCurrencies().associateBy { it.currency.id.value }
accountStatus.account to draggableTokens
.asSequence()
.filter { it.accountId == accountStatus.account.accountId.value }
.mapNotNull { token -> currenciesById[token.id]?.currency }
.toList()
}
}
}

View file

@ -1,6 +1,6 @@
package com.tangem.feature.wallet.presentation.organizetokens
package com.tangem.feature.wallet.child.organizetokens.model
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
import org.burnoutcrew.reorderable.ItemPosition
internal interface OrganizeTokensIntents {

View file

@ -7,35 +7,21 @@ 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.featuretoggle.AccountsFeatureToggles
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.ApplyTokenListSortingUseCaseV2
import com.tangem.domain.account.status.usecase.ToggleTokenListGroupingUseCaseV2
import com.tangem.domain.account.status.usecase.ToggleTokenListSortingUseCaseV2
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.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.core.lce.Lce
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.ToggleTokenListGroupingUseCase
import com.tangem.domain.tokens.ToggleTokenListSortingUseCase
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.feature.wallet.child.organizetokens.OrganizeTokensComponent
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensIntents
import com.tangem.feature.wallet.presentation.organizetokens.OrganizeTokensStateHolder
import com.tangem.feature.wallet.presentation.organizetokens.analytics.PortfolioOrganizeTokensAnalyticsEvent
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
import com.tangem.feature.wallet.presentation.organizetokens.utils.CryptoCurrenciesIdsResolver
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.disableSortingByBalance
import com.tangem.feature.wallet.presentation.organizetokens.utils.dnd.DragAndDropAdapter
import com.tangem.feature.wallet.presentation.organizetokens.utils.dnd.DragAndDropAdapterV2
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.DragAndDropAdapter
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.flow.*
@ -49,18 +35,13 @@ internal class OrganizeTokensModel @Inject constructor(
paramsContainer: ParamsContainer,
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
override val dispatchers: CoroutineDispatcherProvider,
private val getTokenListUseCase: GetTokenListUseCase,
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,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
private val toggleTokenListGroupingUseCaseV2: ToggleTokenListGroupingUseCaseV2,
private val toggleTokenListSortingUseCaseV2: ToggleTokenListSortingUseCaseV2,
private val applyTokenListSortingUseCaseV2: ApplyTokenListSortingUseCaseV2,
) : Model(), OrganizeTokensIntents {
private val selectedAppCurrencyFlow = createSelectedAppCurrencyFlow()
@ -68,24 +49,17 @@ internal class OrganizeTokensModel @Inject constructor(
private var isBalanceHidden = true
private val dragAndDropAdapter = DragAndDropAdapter(
listStateProvider = Provider { uiState.value.itemsState },
)
private val dragAndDropAdapterV2 = DragAndDropAdapterV2(
tokenListUMProvider = Provider { uiState.value.tokenListUM },
)
private val stateHolder = OrganizeTokensStateHolder(
intents = this,
dragAndDropIntents = dragAndDropAdapter,
dragAndDropAdapterV2 = dragAndDropAdapterV2,
dragAndDropAdapter = dragAndDropAdapter,
appCurrencyProvider = Provider(selectedAppCurrencyFlow::value),
accountsFeatureToggles = accountsFeatureToggles,
)
private val userWalletId = paramsContainer.require<OrganizeTokensComponent.Params>().userWalletId
private var cachedTokenList: TokenList? = null
private var cachedAccountStatusList: AccountStatusList? = null
private var isAccountsModeEnabled: Boolean = false
@ -113,68 +87,35 @@ internal class OrganizeTokensModel @Inject constructor(
}
override fun onSortClick() {
if (accountsFeatureToggles.isFeatureEnabled) {
val list = cachedAccountStatusList ?: return
if (list.sortType == TokensSortType.BALANCE) return
val list = cachedAccountStatusList ?: return
if (list.sortType == TokensSortType.BALANCE) return
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ByBalance())
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ByBalance())
modelScope.launch {
toggleTokenListSortingUseCaseV2(list).fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateAfterTokenListSortingV2(it, isAccountsModeEnabled)
cachedAccountStatusList = it
},
)
}
} else {
val list = cachedTokenList ?: return
if (list.sortedBy == TokensSortType.BALANCE) return
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.ByBalance())
modelScope.launch {
toggleTokenListSortingUseCase(list).fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateAfterTokenListSorting(it)
cachedTokenList = it
},
)
}
modelScope.launch {
toggleTokenListSortingUseCase(list).fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = { accountStatusList ->
stateHolder.updateStateAfterTokenListSorting(accountStatusList, isAccountsModeEnabled)
cachedAccountStatusList = accountStatusList
},
)
}
}
override fun onGroupClick() {
if (accountsFeatureToggles.isFeatureEnabled) {
val list = cachedAccountStatusList ?: return
val list = cachedAccountStatusList ?: return
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Group())
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Group())
modelScope.launch {
toggleTokenListGroupingUseCaseV2(list).fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateAfterTokenListSortingV2(it, isAccountsModeEnabled)
cachedAccountStatusList = it
},
)
}
} else {
val list = cachedTokenList ?: return
analyticsEventsHandler.send(PortfolioOrganizeTokensAnalyticsEvent.Group())
modelScope.launch {
toggleTokenListGroupingUseCase(list).fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = {
stateHolder.updateStateAfterTokenListSorting(it)
cachedTokenList = it
},
)
}
modelScope.launch {
toggleTokenListGroupingUseCase(list).fold(
ifLeft = stateHolder::updateStateWithError,
ifRight = { accountStatusList ->
stateHolder.updateStateAfterTokenListSorting(accountStatusList, isAccountsModeEnabled)
cachedAccountStatusList = accountStatusList
},
)
}
}
@ -183,39 +124,20 @@ internal class OrganizeTokensModel @Inject constructor(
stateHolder.updateStateToDisplayProgress()
val resolver = CryptoCurrenciesIdsResolver()
val isSortedByBalance = uiState.value.header.isSortedByBalance
val tokensListUM = uiState.value.tokenListUM
val result = if (accountsFeatureToggles.isFeatureEnabled) {
val tokensListUM = uiState.value.tokenListUM
val isGroupedByNetwork = tokensListUM.isGrouped
val isGroupedByNetwork = tokensListUM.isGrouped
sendAnalyticsEvent(
isGroupedByNetwork = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
sendAnalyticsEvent(
isGroupedByNetwork = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
applyTokenListSortingUseCaseV2(
sortedTokensIdsByAccount = resolver.resolveV2(tokensListUM, cachedAccountStatusList),
isGroupedByNetwork = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
} else {
val listState = uiState.value.itemsState
val isGroupedByNetwork = listState is OrganizeTokensListState.GroupedByNetwork
sendAnalyticsEvent(
isGroupedByNetwork = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
applyTokenListSortingUseCase(
userWalletId = userWalletId,
sortedTokensIds = resolver.resolve(listState, cachedTokenList),
isGroupedByNetwork = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
}
val result = applyTokenListSortingUseCase(
sortedTokensIdsByAccount = resolver.resolve(tokensListUM, cachedAccountStatusList),
isGroupedByNetwork = isGroupedByNetwork,
isSortedByBalance = isSortedByBalance,
)
result.fold(
ifLeft = stateHolder::updateStateWithError,
@ -235,72 +157,35 @@ internal class OrganizeTokensModel @Inject constructor(
private fun bootstrapTokenList() {
modelScope.launch {
if (accountsFeatureToggles.isFeatureEnabled) {
val accountList = singleAccountStatusListSupplier.getSyncOrNull(
SingleAccountStatusListProducer.Params(userWalletId),
) ?: return@launch
val accountList = singleAccountStatusListSupplier.getSyncOrNull(
SingleAccountStatusListProducer.Params(userWalletId),
) ?: return@launch
isAccountsModeEnabled = isAccountsModeEnabledUseCase.invokeSync()
isAccountsModeEnabled = isAccountsModeEnabledUseCase.invokeSync()
stateHolder.updateStateWithAccountList(
accountStatusList = accountList,
isAccountsModeEnabled = isAccountsModeEnabled,
)
stateHolder.updateStateWithAccountList(
accountStatusList = accountList,
isAccountsModeEnabled = isAccountsModeEnabled,
)
cachedAccountStatusList = accountList
} else {
val tokenList = getTokenList() ?: return@launch
stateHolder.updateStateWithTokenList(tokenList)
cachedTokenList = tokenList
}
cachedAccountStatusList = accountList
}
}
private suspend fun getTokenList(): TokenList? {
val maybeTokenList = getTokenListUseCase.launch(userWalletId)
.filterNot(Lce<TokenListError, TokenList>::isLoading)
.firstOrNull()
?: return null
return maybeTokenList
.onError(stateHolder::updateStateWithError)
.getOrNull(isPartialContentAccepted = false)
}
private fun bootstrapDragAndDropUpdates() {
if (accountsFeatureToggles.isFeatureEnabled) {
dragAndDropAdapterV2.dragAndDropUpdates
.distinctUntilChanged()
.onEach { (type, updatedListState) ->
disableSortingByBalanceIfListChangedV2(type)
dragAndDropAdapter.dragAndDropUpdates
.distinctUntilChanged()
.onEach { (type, updatedListState) ->
disableSortingByBalanceIfListChanged(type)
stateHolder.updateStateWithManualSortingV2(updatedListState)
}
.launchIn(modelScope)
} else {
dragAndDropAdapter.dragAndDropUpdates
.distinctUntilChanged()
.onEach { (type, updatedListState) ->
disableSortingByBalanceIfListChanged(type)
stateHolder.updateStateWithManualSorting(updatedListState)
}
.launchIn(modelScope)
}
stateHolder.updateStateWithManualSorting(updatedListState)
}
.launchIn(modelScope)
}
private fun disableSortingByBalanceIfListChanged(dragOperationType: DragAndDropAdapter.DragOperation.Type) {
if (dragOperationType !is DragAndDropAdapter.DragOperation.Type.End) return
if (uiState.value.header.isSortedByBalance && dragOperationType.isItemsOrderChanged) {
cachedTokenList = cachedTokenList?.disableSortingByBalance()
stateHolder.disableSortingByBalance()
}
}
private fun disableSortingByBalanceIfListChangedV2(dragOperationType: DragAndDropAdapterV2.DragOperation.Type) {
if (dragOperationType !is DragAndDropAdapterV2.DragOperation.Type.End) return
if (uiState.value.header.isSortedByBalance && dragOperationType.isItemsOrderChanged) {
cachedAccountStatusList = cachedAccountStatusList?.copy(sortType = TokensSortType.NONE)
stateHolder.disableSortingByBalance()

View file

@ -0,0 +1,111 @@
package com.tangem.feature.wallet.child.organizetokens.model
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensListUM
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensState
import com.tangem.feature.wallet.child.organizetokens.model.converter.InProgressStateConverter
import com.tangem.feature.wallet.child.organizetokens.model.converter.TokenListToStateConverter
import com.tangem.feature.wallet.child.organizetokens.model.converter.error.TokenListSortingErrorConverter
import com.tangem.feature.wallet.child.organizetokens.model.dnd.DragAndDropAdapter
import com.tangem.utils.Provider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
internal class OrganizeTokensStateHolder(
private val intents: OrganizeTokensIntents,
private val dragAndDropAdapter: DragAndDropAdapter,
private val appCurrencyProvider: Provider<AppCurrency>,
) {
private val stateFlowInternal: MutableStateFlow<OrganizeTokensState> = MutableStateFlow(getInitialState())
private val inProgressStateConverter by lazy { InProgressStateConverter() }
private val tokenListSortingErrorConverter by lazy {
TokenListSortingErrorConverter(Provider(stateFlowInternal::value), inProgressStateConverter)
}
val stateFlow: StateFlow<OrganizeTokensState> = stateFlowInternal
fun updateStateWithAccountList(accountStatusList: AccountStatusList, isAccountsModeEnabled: Boolean) {
updateState {
TokenListToStateConverter(
accountStatusList = accountStatusList,
isAccountsMode = isAccountsModeEnabled,
appCurrency = appCurrencyProvider(),
).transform(this)
}
}
fun updateStateAfterTokenListSorting(accountStatusList: AccountStatusList, isAccountsModeEnabled: Boolean) {
updateState {
TokenListToStateConverter(
accountStatusList = accountStatusList,
isAccountsMode = isAccountsModeEnabled,
appCurrency = appCurrencyProvider(),
).transform(this).copy(
scrollListToTop = triggeredEvent(Unit, ::consumeScrollListToTopEvent),
)
}
}
fun updateStateToDisplayProgress() {
updateState { inProgressStateConverter.convert(value = this) }
}
fun updateStateToHideProgress() {
updateState { inProgressStateConverter.convertBack(value = this) }
}
fun updateStateWithManualSorting(tokenListUM: OrganizeTokensListUM) {
updateState { copy(tokenListUM = tokenListUM) }
}
fun disableSortingByBalance() {
updateState { copy(header = header.copy(isSortedByBalance = false)) }
}
fun updateHiddenState(isBalanceHidden: Boolean) {
updateState { copy(isBalanceHidden = isBalanceHidden) }
}
fun updateStateWithError(error: TokenListSortingError) {
updateState { tokenListSortingErrorConverter.convert(error) }
}
private fun getInitialState(): OrganizeTokensState {
return OrganizeTokensState(
onBackClick = intents::onBackClick,
tokenListUM = OrganizeTokensListUM.EmptyList,
header = OrganizeTokensState.HeaderConfig(
onSortClick = intents::onSortClick,
onGroupClick = intents::onGroupClick,
),
actions = OrganizeTokensState.ActionsConfig(
onApplyClick = intents::onApplyClick,
onCancelClick = intents::onCancelClick,
),
dndConfig = OrganizeTokensState.DragAndDropConfig(
onItemDragged = dragAndDropAdapter::onItemDragged,
onItemDragStart = dragAndDropAdapter::onItemDraggingStart,
onItemDragEnd = dragAndDropAdapter::onItemDraggingEnd,
canDragItemOver = dragAndDropAdapter::canDragItemOver,
),
scrollListToTop = consumedEvent(),
isBalanceHidden = true,
)
}
private inline fun updateState(block: OrganizeTokensState.() -> OrganizeTokensState) {
stateFlowInternal.update(block)
}
private fun consumeScrollListToTopEvent() {
updateState { copy(scrollListToTop = consumedEvent()) }
}
}

View file

@ -1,6 +1,6 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.common
package com.tangem.feature.wallet.child.organizetokens.model.common
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
internal fun getGroupPlaceholder(index: Int, accountId: String = ""): DraggableItem.Placeholder {
return DraggableItem.Placeholder(

View file

@ -1,36 +1,8 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.common
package com.tangem.feature.wallet.child.organizetokens.model.common
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
internal fun List<DraggableItem>.uniteItems(): List<DraggableItem> {
val items = prepareItems()
val lastItemIndex = items.lastIndex
return prepareItems().mapIndexed { index, item ->
val mode = when (index) {
// 1 index is used because the first item is always a placeholder, check `prepareItems()` function
1 -> DraggableItem.RoundingMode.Top()
lastItemIndex -> DraggableItem.RoundingMode.Bottom()
else -> when (item) {
is DraggableItem.Portfolio,
is DraggableItem.Placeholder,
-> DraggableItem.RoundingMode.None
is DraggableItem.GroupHeader -> DraggableItem.RoundingMode.Top(showGap = true)
is DraggableItem.Token -> if (items[index + 1] is DraggableItem.Placeholder) {
DraggableItem.RoundingMode.Bottom(showGap = true)
} else {
DraggableItem.RoundingMode.None
}
}
}
item
.updateRoundingMode(mode)
.updateShadowVisibility(show = false)
}
}
internal fun List<DraggableItem>.uniteItemsV2(isAccountsMode: Boolean): List<DraggableItem> {
internal fun List<DraggableItem>.uniteItems(isAccountsMode: Boolean): List<DraggableItem> {
val items = this
val lastItemIndex = items.lastIndex
@ -92,27 +64,6 @@ internal fun List<DraggableItem>.divideMovingItem(movingItem: DraggableItem): Li
return mutableList
}
/**
* !!! Workaround !!!
*
* We need to add a [DraggableItem.Placeholder] (since it's not draggable) as the first item of the list, because the
* [DND library](https://github.com/aclassen/ComposeReorderable) glitches when a user tries to drag the first item.
*
* @since 07.09.2023
* */
private fun List<DraggableItem>.prepareItems(): List<DraggableItem> {
val firstPlaceholderId = "initial_placeholder"
val items = this
return mutableListOf<DraggableItem>().apply {
add(DraggableItem.Placeholder(firstPlaceholderId))
val itemsWithoutFirstPlaceholder = items.filterNot { it.id == firstPlaceholderId }
addAll(itemsWithoutFirstPlaceholder)
}
}
/**
* Applying rounding to tokens
*

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.common
package com.tangem.feature.wallet.child.organizetokens.model.common
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.network.Network

View file

@ -0,0 +1,18 @@
package com.tangem.feature.wallet.child.organizetokens.model.common
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensListUM
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
internal inline fun OrganizeTokensListUM.updateItems(
update: (PersistentList<DraggableItem>) -> List<DraggableItem>,
): OrganizeTokensListUM {
val updatedItems = update(items).toPersistentList()
return when (this) {
is OrganizeTokensListUM.AccountList -> copy(items = updatedItems)
is OrganizeTokensListUM.TokensList -> copy(items = updatedItems)
OrganizeTokensListUM.EmptyList -> this
}
}

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.common
package com.tangem.feature.wallet.child.organizetokens.model.common
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.tokenlist.TokenList

View file

@ -1,6 +1,6 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter
package com.tangem.feature.wallet.child.organizetokens.model.converter
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensState
import com.tangem.utils.converter.TwoWayConverter
internal class InProgressStateConverter : TwoWayConverter<OrganizeTokensState, OrganizeTokensState> {

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter
package com.tangem.feature.wallet.child.organizetokens.model.converter
import com.tangem.common.ui.account.AccountCryptoPortfolioItemStateConverter
import com.tangem.domain.account.models.AccountStatusList
@ -8,17 +8,17 @@ import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.account.filterCryptoPortfolio
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListUM
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupPlaceholder
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.uniteItemsV2
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items.OrganizedTokenListConverter
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensListUM
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensState
import com.tangem.feature.wallet.child.organizetokens.model.common.getGroupPlaceholder
import com.tangem.feature.wallet.child.organizetokens.model.common.uniteItems
import com.tangem.feature.wallet.child.organizetokens.model.converter.items.OrganizedTokenListConverter
import com.tangem.utils.converter.Converter
import com.tangem.utils.transformer.Transformer
import kotlinx.collections.immutable.toPersistentList
internal class TokenListToStateConverterV2(
internal class TokenListToStateConverter(
private val accountStatusList: AccountStatusList,
private val isAccountsMode: Boolean,
private val appCurrency: AppCurrency,
@ -82,7 +82,7 @@ internal class AccountTokenItemConverter(
emptyList()
}
}.toList()
.uniteItemsV2(true).toPersistentList(),
.uniteItems(true).toPersistentList(),
)
} else {
OrganizeTokensListUM.TokensList(
@ -92,7 +92,7 @@ internal class AccountTokenItemConverter(
add(getGroupPlaceholder(accountId = value.mainAccount.accountId.value, index = -1))
}
addAll(organizedTokenListConverter.convert(value.mainAccount))
}.uniteItemsV2(false)
}.uniteItems(false)
.toPersistentList(),
)
}

View file

@ -1,8 +1,8 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter.error
package com.tangem.feature.wallet.child.organizetokens.model.converter.error
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.InProgressStateConverter
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensState
import com.tangem.feature.wallet.child.organizetokens.model.converter.InProgressStateConverter
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter

View file

@ -1,8 +1,8 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter.error
package com.tangem.feature.wallet.child.organizetokens.model.converter.error
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.InProgressStateConverter
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensState
import com.tangem.feature.wallet.child.organizetokens.model.converter.InProgressStateConverter
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items
package com.tangem.feature.wallet.child.organizetokens.model.converter.items
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.token.state.TokenItemState
@ -11,14 +11,14 @@ import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.common.getTotalWithRewardsStakingBalance
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupHeaderId
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getTokenItemId
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.model.common.getGroupHeaderId
import com.tangem.feature.wallet.child.organizetokens.model.common.getTokenItemId
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.orZero
import java.math.BigDecimal
internal class CryptoCurrencyToDraggableItemConverterV2(
internal class CryptoCurrencyToDraggableItemConverter(
private val appCurrency: AppCurrency,
) : Converter<AccountCryptoCurrencyStatus, DraggableItem.Token> {

View file

@ -1,16 +1,16 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items
package com.tangem.feature.wallet.child.organizetokens.model.converter.items
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
import com.tangem.domain.models.account.Account
import com.tangem.domain.models.account.AccountId
import com.tangem.domain.models.tokenlist.TokenList.GroupedByNetwork.NetworkGroup
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupHeaderId
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupPlaceholder
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.model.common.getGroupHeaderId
import com.tangem.feature.wallet.child.organizetokens.model.common.getGroupPlaceholder
import com.tangem.utils.converter.Converter
internal class NetworkGroupToDraggableItemsConverterV2(
private val itemConverter: CryptoCurrencyToDraggableItemConverterV2,
internal class NetworkGroupToDraggableItemsConverter(
private val itemConverter: CryptoCurrencyToDraggableItemConverter,
) : Converter<Pair<Account.CryptoPortfolio, NetworkGroup>, List<DraggableItem>> {
override fun convert(value: Pair<Account.CryptoPortfolio, NetworkGroup>): List<DraggableItem> {
@ -42,10 +42,10 @@ internal class NetworkGroupToDraggableItemsConverterV2(
private fun createTokens(account: Account.CryptoPortfolio, group: NetworkGroup): List<DraggableItem.Token> {
return itemConverter.convertList(
group.currencies.map {
group.currencies.map { currencyStatus ->
AccountCryptoCurrencyStatus(
account = account,
status = it,
status = currencyStatus,
)
},
)

View file

@ -1,10 +1,10 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items
package com.tangem.feature.wallet.child.organizetokens.model.converter.items
import com.tangem.domain.account.status.model.AccountCryptoCurrencyStatus
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.account.AccountStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
@ -14,9 +14,9 @@ internal class OrganizedTokenListConverter(
private val appCurrency: AppCurrency,
) : Converter<AccountStatus.CryptoPortfolio, PersistentList<DraggableItem>> {
private val tokensConverter by lazy { CryptoCurrencyToDraggableItemConverterV2(appCurrency) }
private val tokensConverter by lazy { CryptoCurrencyToDraggableItemConverter(appCurrency) }
private val groupsConverter by lazy {
NetworkGroupToDraggableItemsConverterV2(tokensConverter)
NetworkGroupToDraggableItemsConverter(tokensConverter)
}
override fun convert(value: AccountStatus.CryptoPortfolio): PersistentList<DraggableItem> {

View file

@ -1,11 +1,11 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.dnd
package com.tangem.feature.wallet.child.organizetokens.model.dnd
import com.tangem.feature.wallet.presentation.organizetokens.DragAndDropIntents
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListUM
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.divideMovingItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.uniteItemsV2
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.updateItems
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensListUM
import com.tangem.feature.wallet.child.organizetokens.model.DragAndDropIntents
import com.tangem.feature.wallet.child.organizetokens.model.common.divideMovingItem
import com.tangem.feature.wallet.child.organizetokens.model.common.uniteItems
import com.tangem.feature.wallet.child.organizetokens.model.common.updateItems
import com.tangem.utils.Provider
import kotlinx.collections.immutable.mutate
import kotlinx.coroutines.flow.Flow
@ -13,7 +13,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import org.burnoutcrew.reorderable.ItemPosition
internal class DragAndDropAdapterV2(
internal class DragAndDropAdapter(
private val tokenListUMProvider: Provider<OrganizeTokensListUM>,
) : DragAndDropIntents {
@ -81,7 +81,7 @@ internal class DragAndDropAdapterV2(
is DraggableItem.Placeholder,
is DraggableItem.Portfolio,
-> items
is DraggableItem.GroupHeader -> draggableGroupsOperations.collapseGroupV2(items, item)
is DraggableItem.GroupHeader -> draggableGroupsOperations.collapseGroup(items, item)
.divideMovingItem(item)
is DraggableItem.Token -> items.divideMovingItem(item)
}
@ -96,11 +96,11 @@ internal class DragAndDropAdapterV2(
updateListState(DragOperation.Type.End(isItemsOrderChanged = checkIsItemsOrderChanged())) {
when (draggingItem) {
is DraggableItem.GroupHeader -> {
draggableGroupsOperations.expandGroupsV2(items)
.uniteItemsV2(tokenListUM is OrganizeTokensListUM.AccountList)
draggableGroupsOperations.expandGroups(items)
.uniteItems(tokenListUM is OrganizeTokensListUM.AccountList)
}
is DraggableItem.Token -> {
items.uniteItemsV2(tokenListUM is OrganizeTokensListUM.AccountList)
items.uniteItems(tokenListUM is OrganizeTokensListUM.AccountList)
}
is DraggableItem.Placeholder,
is DraggableItem.Portfolio,

View file

@ -1,9 +1,8 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.dnd
package com.tangem.feature.wallet.child.organizetokens.model.dnd
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.divideMovingItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupPlaceholder
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.uniteItems
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.model.common.divideMovingItem
import com.tangem.feature.wallet.child.organizetokens.model.common.getGroupPlaceholder
internal class DraggableGroupsOperations {
@ -24,47 +23,9 @@ internal class DraggableGroupsOperations {
return itemsWithoutGroupTokens.divideMovingItem(movingGroup)
}
fun collapseGroupV2(items: List<DraggableItem>, movingGroup: DraggableItem.GroupHeader): List<DraggableItem> {
if (!groupIdToTokens.isNullOrEmpty()) return items
groupIdToTokens = items
.asSequence()
.filterIsInstance<DraggableItem.Token>()
.groupBy { it.groupId }
val itemsWithoutGroupTokens = items.filterNot {
it is DraggableItem.Token && it.groupId == movingGroup.id
}
return itemsWithoutGroupTokens.divideMovingItem(movingGroup)
}
fun expandGroups(items: List<DraggableItem>): List<DraggableItem> {
if (groupIdToTokens.isNullOrEmpty()) return items
val currentGroups = items.filterIsInstance<DraggableItem.GroupHeader>()
val lastGroupIndex = currentGroups.lastIndex
val expandedGroups = currentGroups
.flatMapIndexed { index, group ->
buildList {
add(group)
addAll(groupIdToTokens?.get(group.id).orEmpty())
if (index != lastGroupIndex) {
add(getGroupPlaceholder(index))
}
}
}
.uniteItems()
groupIdToTokens = null
return expandedGroups
}
fun expandGroupsV2(items: List<DraggableItem>): List<DraggableItem> {
if (groupIdToTokens.isNullOrEmpty()) return items
val accountList = items.filterIsInstance<DraggableItem.Portfolio>()
val currentGroups = items.filterIsInstance<DraggableItem.GroupHeader>()

View file

@ -1,4 +1,4 @@
package com.tangem.feature.wallet.presentation.organizetokens
package com.tangem.feature.wallet.child.organizetokens.ui
import android.content.res.Configuration
import androidx.activity.compose.BackHandler
@ -44,12 +44,11 @@ import com.tangem.core.ui.res.TangemThemePreview
import com.tangem.core.ui.test.OrganizeTokensScreenTestTags
import com.tangem.core.ui.utils.WindowInsetsZero
import com.tangem.core.ui.utils.lazyListItemPosition
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensListUM
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensState
import com.tangem.feature.wallet.child.organizetokens.ui.preview.OrganizeTokensPreview
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.common.WalletPreviewData
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListUM
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
import org.burnoutcrew.reorderable.ReorderableLazyListState
import org.burnoutcrew.reorderable.rememberReorderableLazyListState
import org.burnoutcrew.reorderable.reorderable
@ -76,7 +75,6 @@ internal fun OrganizeTokensScreen(state: OrganizeTokensState, modifier: Modifier
.fillMaxSize(),
listState = tokensListState,
tokensListUM = state.tokenListUM,
state = state.itemsState,
dndConfig = state.dndConfig,
isBalanceHidden = state.isBalanceHidden,
)
@ -98,18 +96,13 @@ internal fun OrganizeTokensScreen(state: OrganizeTokensState, modifier: Modifier
@Composable
private fun TokenList(
listState: LazyListState,
state: OrganizeTokensListState,
tokensListUM: OrganizeTokensListUM,
dndConfig: OrganizeTokensState.DragAndDropConfig,
isBalanceHidden: Boolean,
modifier: Modifier = Modifier,
) {
val hapticFeedback = LocalHapticFeedback.current
val tokenList = if (tokensListUM !is OrganizeTokensListUM.EmptyList) {
tokensListUM.items
} else {
state.items
}
val tokenList = tokensListUM.items
Box(modifier = modifier) {
val onDragEnd: (Int, Int) -> Unit = remember {
{ _, _ ->
@ -423,8 +416,8 @@ private fun OrganizeTokensScreenPreview(
private class OrganizeTokensStateProvider : CollectionPreviewParameterProvider<OrganizeTokensState>(
collection = listOf(
WalletPreviewData.organizeTokensState,
WalletPreviewData.groupedOrganizeTokensState,
OrganizeTokensPreview.stateAccounts,
OrganizeTokensPreview.state,
),
)
// endregion Preview

View file

@ -0,0 +1,127 @@
package com.tangem.feature.wallet.child.organizetokens.ui.preview
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.feature.wallet.child.organizetokens.entity.DraggableItem
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensListUM
import com.tangem.feature.wallet.child.organizetokens.entity.OrganizeTokensState
import com.tangem.feature.wallet.impl.R
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
import java.util.UUID
internal object OrganizeTokensPreview {
private const val networksSize = 10
private const val tokensSize = 3
private val tokenItemDragState by lazy {
TokenItemState.Draggable(
id = UUID.randomUUID().toString(),
iconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_polygon_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
),
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "3 172,14 $"),
)
}
private val draggableItems: PersistentList<DraggableItem> by lazy {
List(networksSize) { it }
.flatMap { index ->
val lastNetworkIndex = networksSize - 1
val lastTokenIndex = tokensSize - 1
val networkNumber = index + 1
val group = DraggableItem.GroupHeader(
id = networkNumber,
networkName = "$networkNumber",
roundingMode = when (index) {
0 -> DraggableItem.RoundingMode.Top()
lastNetworkIndex -> DraggableItem.RoundingMode.Bottom()
else -> DraggableItem.RoundingMode.None
},
accountId = "account_$networkNumber",
)
val tokens: MutableList<DraggableItem.Token> = mutableListOf()
repeat(times = tokensSize) { i ->
val tokenNumber = i + 1
tokens.add(
DraggableItem.Token(
tokenItemState = tokenItemDragState.copy(
id = "${group.id}_token_$tokenNumber",
titleState = TokenItemState.TitleState.Content(
text = stringReference(value = "Token $tokenNumber from $networkNumber network"),
),
),
groupId = group.id,
accountId = "account_$networkNumber",
roundingMode = when {
i == lastTokenIndex && index == lastNetworkIndex -> DraggableItem.RoundingMode.Bottom()
else -> DraggableItem.RoundingMode.None
},
),
)
}
val divider = DraggableItem.Placeholder(
id = "divider_$networkNumber",
accountId = "account_$networkNumber",
)
buildList {
add(group)
addAll(tokens)
if (index != lastNetworkIndex) {
add(divider)
}
}
}
.toPersistentList()
}
val stateAccounts by lazy {
OrganizeTokensState(
onBackClick = {},
tokenListUM = OrganizeTokensListUM.AccountList(
items = draggableItems,
isGrouped = true,
),
header = OrganizeTokensState.HeaderConfig(
onSortClick = {},
onGroupClick = {},
),
dndConfig = OrganizeTokensState.DragAndDropConfig(
onItemDragged = { _, _ -> },
onItemDragStart = {},
canDragItemOver = { _, _ -> false },
onItemDragEnd = {},
),
actions = OrganizeTokensState.ActionsConfig(
onApplyClick = {},
onCancelClick = {},
),
scrollListToTop = consumedEvent(),
isBalanceHidden = true,
)
}
val state by lazy {
stateAccounts.copy(
tokenListUM = OrganizeTokensListUM.TokensList(
items = draggableItems,
isGrouped = true,
),
)
}
}

View file

@ -5,6 +5,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.subscribeAsState
import com.arkivanov.decompose.router.slot.childSlot
import com.arkivanov.decompose.router.slot.dismiss
@ -14,6 +15,7 @@ import com.tangem.core.decompose.context.AppComponentContext
import com.tangem.core.decompose.context.child
import com.tangem.core.decompose.context.childByContext
import com.tangem.core.decompose.model.getOrCreateModel
import com.tangem.core.ui.DesignFeatureToggles
import com.tangem.core.ui.components.bottomsheets.state.BottomSheetState
import com.tangem.core.ui.decompose.ComposableBottomSheetComponent
import com.tangem.core.ui.decompose.ComposableContentComponent
@ -23,12 +25,11 @@ import com.tangem.feature.wallet.child.wallet.model.WalletModel
import com.tangem.feature.wallet.navigation.WalletRoute
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletDialogConfig
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen
import com.tangem.feature.wallet.presentation.wallet.ui.WalletScreen2
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejectedComponent
import com.tangem.feature.walletsettings.component.RenameWalletComponent
import com.tangem.features.biometry.AskBiometryComponent
import com.tangem.features.feed.entry.components.FeedEntryComponent
import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
import com.tangem.features.markets.entry.MarketsEntryComponent
import com.tangem.features.pushnotifications.api.PushNotificationsBottomSheetComponent
import com.tangem.features.pushnotifications.api.PushNotificationsParams
import com.tangem.features.tokenreceive.TokenReceiveComponent
@ -38,18 +39,18 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import kotlinx.coroutines.launch
@OptIn(ExperimentalDecomposeApi::class)
@Suppress("LongParameterList")
internal class WalletComponent @AssistedInject constructor(
@Assisted appComponentContext: AppComponentContext,
@Assisted navigate: (WalletRoute) -> Unit,
marketsEntryComponentFactory: MarketsEntryComponent.Factory,
feedEntryComponentFactory: FeedEntryComponent.Factory,
private val renameWalletComponentFactory: RenameWalletComponent.Factory,
private val askBiometryComponentFactory: AskBiometryComponent.Factory,
private val pushNotificationsBottomSheetComponent: PushNotificationsBottomSheetComponent.Factory,
private val tokenReceiveComponentFactory: TokenReceiveComponent.Factory,
private val yieldSupplyDepositedWarningComponent: YieldSupplyDepositedWarningComponent.Factory,
private val feedFeatureToggle: FeedFeatureToggle,
private val designFeatureToggles: DesignFeatureToggles,
) : ComposableContentComponent, AppComponentContext by appComponentContext {
private val model: WalletModel = getOrCreateModel()
@ -60,9 +61,6 @@ internal class WalletComponent @AssistedInject constructor(
entryRoute = null,
)
}
private val marketsEntryComponent by lazy {
marketsEntryComponentFactory.create(child("marketsEntryComponent"))
}
init {
lifecycle.subscribe(model.screenLifecycleProvider)
@ -148,18 +146,33 @@ internal class WalletComponent @AssistedInject constructor(
var headerSize by remember { mutableStateOf(0.dp) }
val dialog by dialog.subscribeAsState()
WalletScreen(
state = model.uiState.collectAsStateWithLifecycle().value,
bottomSheetContent = {
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
modifier = modifier,
)
},
bottomSheetHeaderHeightProvider = { headerSize },
onBottomSheetStateChange = { bottomSheetState.value = it },
)
if (designFeatureToggles.isRedesignEnabled) {
WalletScreen2(
state = model.uiState.collectAsStateWithLifecycle().value,
bottomSheetContent = {
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
modifier = modifier,
)
},
bottomSheetHeaderHeightProvider = { headerSize },
onBottomSheetStateChange = { bottomSheetState.value = it },
)
} else {
WalletScreen(
state = model.uiState.collectAsStateWithLifecycle().value,
bottomSheetContent = {
BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = { headerSize = it },
modifier = modifier,
)
},
bottomSheetHeaderHeightProvider = { headerSize },
onBottomSheetStateChange = { bottomSheetState.value = it },
)
}
when (val dialog = dialog.child?.instance) {
is ComposableDialogComponent -> dialog.Dialog()
@ -174,19 +187,11 @@ internal class WalletComponent @AssistedInject constructor(
onHeaderSizeChange: (Dp) -> Unit,
modifier: Modifier = Modifier,
) {
if (feedFeatureToggle.isFeedEnabled) {
feedEntryComponent.BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = onHeaderSizeChange,
modifier = modifier,
)
} else {
marketsEntryComponent.BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = onHeaderSizeChange,
modifier = modifier,
)
}
feedEntryComponent.BottomSheetContent(
bottomSheetState = bottomSheetState,
onHeaderSizeChange = onHeaderSizeChange,
modifier = modifier,
)
}
@AssistedFactory

View file

@ -11,7 +11,6 @@ import com.tangem.core.analytics.utils.TrackingContextProxy
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.model.Model
import com.tangem.datasource.local.appsflyer.AppsFlyerStore
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.supplier.SingleAccountListSupplier
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
@ -31,7 +30,10 @@ import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.router.InnerWalletRouter
import com.tangem.feature.wallet.presentation.wallet.analytics.WalletScreenAnalyticsEvent
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.SelectedWalletAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.*
import com.tangem.feature.wallet.presentation.wallet.domain.OnrampStatusFactory
import com.tangem.feature.wallet.presentation.wallet.domain.WalletContentFetcher
import com.tangem.feature.wallet.presentation.wallet.domain.WalletImageResolver
import com.tangem.feature.wallet.presentation.wallet.domain.WalletNameMigrationUseCase
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.WalletDialogConfig
@ -43,11 +45,8 @@ import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSend
import com.tangem.feature.wallet.presentation.wallet.ui.components.visa.KycRejectedCallbacks
import com.tangem.feature.wallet.presentation.wallet.utils.ScreenLifecycleProvider
import com.tangem.features.biometry.AskBiometryComponent
import com.tangem.features.feed.entry.featuretoggle.FeedFeatureToggle
import com.tangem.features.pushnotifications.api.PushNotificationsModelCallbacks
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.features.wallet.deeplink.WalletDeepLinkActionListener
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
import com.tangem.utils.Provider
import com.tangem.utils.coroutines.*
import kotlinx.coroutines.*
@ -80,7 +79,6 @@ internal class WalletModel @Inject constructor(
private val walletNameMigrationUseCase: WalletNameMigrationUseCase,
private val refreshMultiCurrencyWalletQuotesUseCase: RefreshMultiCurrencyWalletQuotesUseCase,
private val walletImageResolver: WalletImageResolver,
private val tokenListStore: MultiWalletTokenListStore,
private val onrampStatusFactory: OnrampStatusFactory,
private val analyticsEventsHandler: AnalyticsEventHandler,
private val walletContentFetcher: WalletContentFetcher,
@ -90,17 +88,13 @@ internal class WalletModel @Inject constructor(
private val setNotificationsEnabledUseCase: SetNotificationsEnabledUseCase,
private val getIsHuaweiDeviceWithoutGoogleServicesUseCase: GetIsHuaweiDeviceWithoutGoogleServicesUseCase,
private val userWalletsListRepository: UserWalletsListRepository,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val yieldSupplyApyUpdateUseCase: YieldSupplyApyUpdateUseCase,
private val tangemPayOnboardingRepository: OnboardingRepository,
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val tangemPayMainScreenCustomerInfoUseCase: TangemPayMainScreenCustomerInfoUseCase,
private val getAppThemeModeUseCase: GetAppThemeModeUseCase,
private val trackingContextProxy: TrackingContextProxy,
private val singleAccountListSupplier: SingleAccountListSupplier,
private val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
private val feedFeatureToggle: FeedFeatureToggle,
private val bindRefcodeWithWalletUseCase: BindRefcodeWithWalletUseCase,
private val appsFlyerStore: AppsFlyerStore,
val screenLifecycleProvider: ScreenLifecycleProvider,
@ -116,13 +110,12 @@ internal class WalletModel @Inject constructor(
private val refreshWalletJobHolder = JobHolder()
private val updateTangemPayJobHolder = JobHolder()
private var needToRefreshWallet = false
private var expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
private var shouldRefreshWallet = false
private val expressTxStatusTaskScheduler = SingleTaskScheduler<Unit>()
init {
trackScreenOpened()
updateMarketToggle()
suggestToOpenMarkets()
maybeMigrateNames()
@ -159,17 +152,9 @@ internal class WalletModel @Inject constructor(
}
}
private fun updateMarketToggle() {
stateHolder.update {
it.copy(isNewMarketEnabled = feedFeatureToggle.isFeedEnabled)
}
}
private fun updateYieldSupplyApy() {
if (yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled) {
modelScope.launch(dispatchers.default) {
yieldSupplyApyUpdateUseCase()
}
modelScope.launch(dispatchers.default) {
yieldSupplyApyUpdateUseCase()
}
}
@ -188,7 +173,6 @@ internal class WalletModel @Inject constructor(
override fun onDestroy() {
super.onDestroy()
tokenListStore.clear()
stateHolder.clear()
walletScreenContentLoader.cancelAll()
}
@ -267,9 +251,9 @@ internal class WalletModel @Inject constructor(
getWalletsUseCase()
.conflate()
.distinctUntilChanged()
.map {
.map { userWallets ->
walletsUpdateActionResolver.resolve(
wallets = it,
wallets = userWallets,
currentState = stateHolder.value,
)
}
@ -361,7 +345,7 @@ internal class WalletModel @Inject constructor(
refreshWalletJobHolder.cancel()
when {
isBackground -> needToRefreshTimer()
needToRefreshWallet && !isBackground -> {
shouldRefreshWallet && !isBackground -> {
triggerRefreshWalletQuotes()
}
}
@ -394,7 +378,6 @@ internal class WalletModel @Inject constructor(
* Update state each time a user opens/returns to wallet screen
* and every minute while user stays on the main screen
*/
if (!tangemPayFeatureToggles.isTangemPayEnabled) return
combine(
flow = screenLifecycleProvider.isBackgroundState,
@ -433,12 +416,12 @@ internal class WalletModel @Inject constructor(
private fun needToRefreshTimer() {
modelScope.launch {
delay(REFRESH_WALLET_BACKGROUND_TIMER_MILLIS)
needToRefreshWallet = true
shouldRefreshWallet = true
}.saveIn(refreshWalletJobHolder)
}
private fun triggerRefreshWalletQuotes() {
needToRefreshWallet = false
shouldRefreshWallet = false
val state = stateHolder.uiState.value
val wallet = state.wallets.getOrNull(state.selectedWalletIndex) ?: return
modelScope.launch {
@ -469,7 +452,6 @@ internal class WalletModel @Inject constructor(
// refresh loader to use actual user wallet
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
isRefresh = true,
coroutineScope = modelScope,
)
@ -517,10 +499,9 @@ internal class WalletModel @Inject constructor(
}
private fun reloadWarnings(action: WalletsUpdateActionResolver.Action.ReloadWallets) {
action.wallets.forEach {
action.wallets.forEach { userWallet ->
walletScreenContentLoader.load(
userWallet = it,
clickIntents = clickIntents,
userWallet = userWallet,
coroutineScope = modelScope,
isRefresh = true,
)
@ -539,7 +520,6 @@ internal class WalletModel @Inject constructor(
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
coroutineScope = modelScope,
)
@ -566,11 +546,9 @@ internal class WalletModel @Inject constructor(
private fun reinitializeNewWallet(action: WalletsUpdateActionResolver.Action.ReinitializeNewWallet) {
walletScreenContentLoader.cancel(action.prevWalletId)
tokenListStore.remove(action.prevWalletId)
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
coroutineScope = modelScope,
)
@ -589,11 +567,9 @@ internal class WalletModel @Inject constructor(
private fun reinitializeWallets(action: WalletsUpdateActionResolver.Action.ReinitializeWallets) {
action.wallets.forEach { userWallet ->
walletScreenContentLoader.cancel(userWallet.walletId)
tokenListStore.remove(userWallet.walletId)
walletScreenContentLoader.load(
userWallet = userWallet,
clickIntents = clickIntents,
coroutineScope = modelScope,
)
@ -610,39 +586,20 @@ internal class WalletModel @Inject constructor(
}
private fun addWallet(action: WalletsUpdateActionResolver.Action.AddWallet) {
if (accountsFeatureToggles.isFeatureEnabled) {
fetchWalletContent(userWallet = action.selectedWallet)
fetchWalletContent(userWallet = action.selectedWallet)
stateHolder.update(
AddWalletTransformer(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
),
)
walletScreenContentLoader.load(
stateHolder.update(
AddWalletTransformer(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
coroutineScope = modelScope,
)
} else {
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
coroutineScope = modelScope,
)
walletImageResolver = walletImageResolver,
),
)
fetchWalletContent(userWallet = action.selectedWallet)
stateHolder.update(
AddWalletTransformer(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
walletImageResolver = walletImageResolver,
),
)
}
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
coroutineScope = modelScope,
)
scrollToWallet(prevIndex = action.prevWalletIndex, newIndex = action.selectedWalletIndex) {
stateHolder.update {
@ -655,11 +612,9 @@ internal class WalletModel @Inject constructor(
private fun deleteWallet(action: WalletsUpdateActionResolver.Action.DeleteWallet) {
walletScreenContentLoader.cancel(action.deletedWalletId)
tokenListStore.remove(action.deletedWalletId)
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
coroutineScope = modelScope,
)
@ -703,7 +658,6 @@ internal class WalletModel @Inject constructor(
walletScreenContentLoader.load(
userWallet = action.selectedWallet,
clickIntents = clickIntents,
coroutineScope = modelScope,
)

View file

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

View file

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

View file

@ -30,7 +30,7 @@ internal class WalletClickIntents @Inject constructor(
private val currencyActionsClickIntentsImplementor: WalletCurrencyActionsClickIntentsImplementor,
private val contentClickIntentsImplementor: WalletContentClickIntentsImplementor,
private val pushPermissionClickIntentsImplementor: WalletPushPermissionClickIntentsImplementor,
private val stateHolder: WalletStateController,
private val stateController: WalletStateController,
private val walletScreenContentLoader: WalletScreenContentLoader,
private val getSelectedWalletSyncUseCase: GetSelectedWalletSyncUseCase,
private val selectWalletUseCase: SelectWalletUseCase,
@ -62,7 +62,7 @@ internal class WalletClickIntents @Inject constructor(
fun onWalletChange(index: Int, onlyState: Boolean) {
if (onlyState) {
stateHolder.update { it.copy(selectedWalletIndex = index) }
stateController.update { it.copy(selectedWalletIndex = index) }
return
}
@ -70,27 +70,23 @@ internal class WalletClickIntents @Inject constructor(
launch { neverToShowWalletsScrollPreview() }
val maybeUserWallet = selectWalletUseCase(
userWalletId = stateHolder.value.wallets[index].walletCardState.id,
userWalletId = stateController.value.wallets[index].walletCardState.id,
)
stateHolder.update { it.copy(selectedWalletIndex = index) }
stateController.update { it.copy(selectedWalletIndex = index) }
maybeUserWallet.onRight {
if (!it.isLocked) {
launch { walletContentFetcher(userWalletId = it.walletId) }
maybeUserWallet.onRight { userWallet ->
if (!userWallet.isLocked) {
launch { walletContentFetcher(userWalletId = userWallet.walletId) }
}
walletScreenContentLoader.load(
userWallet = it,
clickIntents = this@WalletClickIntents,
coroutineScope = modelScope,
)
walletScreenContentLoader.load(userWallet = userWallet, coroutineScope = modelScope)
}
}
}
fun onRefreshSwipe(showRefreshState: Boolean) {
when (stateHolder.getSelectedWallet()) {
when (stateController.getSelectedWallet()) {
is WalletState.MultiCurrency.Content -> {
refreshMultiCurrencyContent(showRefreshState)
}
@ -111,7 +107,7 @@ internal class WalletClickIntents @Inject constructor(
private fun refreshMultiCurrencyContent(showRefreshState: Boolean) {
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
stateHolder.update(
stateController.update(
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = showRefreshState),
)
@ -126,7 +122,7 @@ internal class WalletClickIntents @Inject constructor(
}
.awaitAll()
stateHolder.update(
stateController.update(
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = false),
)
}
@ -137,7 +133,7 @@ internal class WalletClickIntents @Inject constructor(
private fun refreshSingleCurrencyContent(showRefreshState: Boolean) {
val userWallet = getSelectedWalletSyncUseCase.unwrap() ?: return
stateHolder.update(
stateController.update(
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = showRefreshState),
)
@ -147,12 +143,11 @@ internal class WalletClickIntents @Inject constructor(
onrampStatusFactory.updateOnrmapTransactionStatuses(userWallet)
walletScreenContentLoader.load(
userWallet = userWallet,
clickIntents = this@WalletClickIntents,
isRefresh = true,
coroutineScope = modelScope,
)
stateHolder.update(
stateController.update(
SetRefreshStateTransformer(userWalletId = userWallet.walletId, isRefreshing = false),
)
}

View file

@ -11,8 +11,10 @@ import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.analytics.models.AnalyticsEvent
import com.tangem.core.analytics.models.AnalyticsParam
import com.tangem.core.analytics.models.event.MainScreenAnalyticsEvent
import com.tangem.core.analytics.models.event.OfframpAnalyticsEvent
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.core.decompose.ui.UiMessageSender
import com.tangem.core.navigation.url.UrlOpener
import com.tangem.core.ui.clipboard.ClipboardManager
import com.tangem.core.ui.components.tokenlist.state.TokensListItemUM
import com.tangem.core.ui.extensions.TextReference
@ -20,7 +22,6 @@ import com.tangem.core.ui.extensions.resourceReference
import com.tangem.core.ui.haptic.TangemHapticEffect
import com.tangem.core.ui.haptic.VibratorHapticManager
import com.tangem.core.ui.message.DialogMessage
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.usecase.GetAccountCurrencyStatusUseCase
import com.tangem.domain.account.status.usecase.ManageCryptoCurrenciesUseCase
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
@ -36,13 +37,12 @@ import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.NetworkAddress
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.offramp.GetOfframpUrlUseCase
import com.tangem.domain.onramp.model.OnrampSource
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.promo.models.StoryContentIds
import com.tangem.domain.redux.ReduxStateHolder
import com.tangem.domain.staking.model.StakingOption
import com.tangem.domain.tokens.*
import com.tangem.domain.tokens.legacy.TradeCryptoAction
import com.tangem.domain.tokens.model.ScenarioUnavailabilityReason
import com.tangem.domain.tokens.model.analytics.TokenReceiveCopyActionSource
import com.tangem.domain.tokens.model.analytics.TokenReceiveNewAnalyticsEvent
@ -63,7 +63,6 @@ import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.transformers.CloseBottomSheetTransformer
import com.tangem.feature.wallet.presentation.wallet.state.utils.WalletEventSender
import com.tangem.features.yield.supply.api.YieldSupplyFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.Flow
@ -136,19 +135,17 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
private val getStoryContentUseCase: GetStoryContentUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val dispatchers: CoroutineDispatcherProvider,
private val reduxStateHolder: ReduxStateHolder,
private val getOfframpUrlUseCase: GetOfframpUrlUseCase,
private val urlOpener: UrlOpener,
private val vibratorHapticManager: VibratorHapticManager,
private val clipboardManager: ClipboardManager,
private val appRouter: AppRouter,
private val rampStateManager: RampStateManager,
private val saveViewedTokenReceiveWarningUseCase: SaveViewedTokenReceiveWarningUseCase,
private val receiveAddressesFactory: ReceiveAddressesFactory,
private val yieldSupplyFeatureToggles: YieldSupplyFeatureToggles,
private val needShowYieldSupplyDepositedWarningUseCase: NeedShowYieldSupplyDepositedWarningUseCase,
private val saveViewedYieldSupplyWarningUseCase: SaveViewedYieldSupplyWarningUseCase,
private val isCryptoCurrencyCoinCouldHide: IsCryptoCurrencyCoinCouldHideUseCase,
private val removeCurrencyUseCase: RemoveCurrencyUseCase,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getAccountCurrencyStatusUseCase: GetAccountCurrencyStatusUseCase,
private val manageCryptoCurrenciesUseCase: ManageCryptoCurrenciesUseCase,
private val uiMessageSender: UiMessageSender,
@ -289,23 +286,19 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
override fun onPerformHideToken(userWalletId: UserWalletId, cryptoCurrencyStatus: CryptoCurrencyStatus) {
modelScope.launch(dispatchers.io) {
if (accountsFeatureToggles.isFeatureEnabled) {
val accountId = getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = userWalletId,
currency = cryptoCurrencyStatus.currency,
)
.map { it.account.accountId }
.getOrNull()
val accountId = getAccountCurrencyStatusUseCase.invokeSync(
userWalletId = userWalletId,
currency = cryptoCurrencyStatus.currency,
)
.map { it.account.accountId }
.getOrNull()
if (accountId == null) {
Timber.e("Account ID is null, cannot hide currency ${cryptoCurrencyStatus.currency.id}")
return@launch
}
manageCryptoCurrenciesUseCase(accountId = accountId, remove = cryptoCurrencyStatus.currency)
} else {
removeCurrencyUseCase(userWalletId, cryptoCurrencyStatus.currency)
if (accountId == null) {
Timber.e("Account ID is null, cannot hide currency ${cryptoCurrencyStatus.currency.id}")
return@launch
}
manageCryptoCurrenciesUseCase(accountId = accountId, remove = cryptoCurrencyStatus.currency)
.fold(
ifLeft = {
walletEventSender.send(
@ -335,12 +328,13 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
showErrorIfDemoModeOrElse {
modelScope.launch(dispatchers.main) {
reduxStateHolder.dispatch(
action = TradeCryptoAction.Sell(
cryptoCurrencyStatus = cryptoCurrencyStatus,
appCurrencyCode = getSelectedAppCurrencyUseCase.unwrap().code,
),
)
getOfframpUrlUseCase(
cryptoCurrencyStatus = cryptoCurrencyStatus,
appCurrencyCode = getSelectedAppCurrencyUseCase.unwrap().code,
).onRight { url ->
urlOpener.openUrl(url)
analyticsEventHandler.send(OfframpAnalyticsEvent.ScreenOpened)
}
}
}
}
@ -472,9 +466,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
override fun onMultiWalletSwapClick(userWalletId: UserWalletId) {
val selectedWallet = stateHolder.getSelectedWallet() as? WalletState.MultiCurrency.Content ?: return
val tokenListState = selectedWallet.tokensListState
when (tokenListState) {
when (val tokenListState = selectedWallet.tokensListState) {
is WalletTokensListState.ContentState.Content -> checkSwapCryptoAvailability(
tokenCount = tokenListState.items.count { it is TokensListItemUM.Token },
)
@ -663,8 +655,7 @@ internal class WalletCurrencyActionsClickIntentsImplementor @Inject constructor(
}
private suspend fun needShowYieldSupplyWarning(cryptoCurrencyStatus: CryptoCurrencyStatus): Boolean {
return yieldSupplyFeatureToggles.isYieldSupplyFeatureEnabled &&
needShowYieldSupplyDepositedWarningUseCase(cryptoCurrencyStatus)
return needShowYieldSupplyDepositedWarningUseCase(cryptoCurrencyStatus)
}
private fun navigateToSend(cryptoCurrencyStatus: CryptoCurrencyStatus, userWalletId: UserWalletId) {

View file

@ -1,7 +1,6 @@
package com.tangem.feature.wallet.presentation.account
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
import com.tangem.domain.account.status.supplier.SingleAccountStatusSupplier
import com.tangem.domain.account.usecase.IsAccountsModeEnabledUseCase
@ -9,7 +8,6 @@ import javax.inject.Inject
@ModelScoped
internal class AccountDependencies @Inject constructor(
val accountsFeatureToggles: AccountsFeatureToggles,
val isAccountsModeEnabledUseCase: IsAccountsModeEnabledUseCase,
val expandedAccountsHolder: ExpandedAccountsHolder,
val singleAccountStatusListSupplier: SingleAccountStatusListSupplier,

View file

@ -1,24 +1,11 @@
package com.tangem.feature.wallet.presentation.common
import com.tangem.core.ui.components.bottomsheets.TangemBottomSheetConfig
import com.tangem.core.ui.components.currency.icon.CurrencyIconState
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.extensions.TextReference
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.res.TangemColorPalette
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.impl.R
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListUM
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toPersistentList
import java.util.UUID
@Suppress("LargeClass")
internal object WalletPreviewData {
@ -68,135 +55,6 @@ internal object WalletPreviewData {
)
}
private val tokenItemDragState by lazy {
TokenItemState.Draggable(
id = UUID.randomUUID().toString(),
iconState = CurrencyIconState.TokenIcon(
url = null,
topBadgeIconResId = R.drawable.img_polygon_22,
fallbackTint = TangemColorPalette.Black,
fallbackBackground = TangemColorPalette.Meadow,
isGrayscale = false,
shouldShowCustomBadge = false,
),
titleState = TokenItemState.TitleState.Content(text = stringReference(value = "Polygon")),
subtitle2State = TokenItemState.Subtitle2State.TextContent(text = "3 172,14 $"),
)
}
private const val networksSize = 10
private const val tokensSize = 3
private val draggableItems: PersistentList<DraggableItem> by lazy {
List(networksSize) { it }
.flatMap { index ->
val lastNetworkIndex = networksSize - 1
val lastTokenIndex = tokensSize - 1
val networkNumber = index + 1
val group = DraggableItem.GroupHeader(
id = networkNumber,
networkName = "$networkNumber",
roundingMode = when (index) {
0 -> DraggableItem.RoundingMode.Top()
lastNetworkIndex -> DraggableItem.RoundingMode.Bottom()
else -> DraggableItem.RoundingMode.None
},
accountId = "account_$networkNumber",
)
val tokens: MutableList<DraggableItem.Token> = mutableListOf()
repeat(times = tokensSize) { i ->
val tokenNumber = i + 1
tokens.add(
DraggableItem.Token(
tokenItemState = tokenItemDragState.copy(
id = "${group.id}_token_$tokenNumber",
titleState = TokenItemState.TitleState.Content(
text = stringReference(value = "Token $tokenNumber from $networkNumber network"),
),
),
groupId = group.id,
accountId = "account_$networkNumber",
roundingMode = when {
i == lastTokenIndex && index == lastNetworkIndex -> DraggableItem.RoundingMode.Bottom()
else -> DraggableItem.RoundingMode.None
},
),
)
}
val divider = DraggableItem.Placeholder(
id = "divider_$networkNumber",
accountId = "account_$networkNumber",
)
buildList {
add(group)
addAll(tokens)
if (index != lastNetworkIndex) {
add(divider)
}
}
}
.toPersistentList()
}
private val draggableTokens by lazy {
draggableItems
.filterIsInstance<DraggableItem.Token>()
.toMutableList()
.also {
it[0] = it[0].copy(roundingMode = DraggableItem.RoundingMode.Top())
}
.toPersistentList()
}
val groupedOrganizeTokensState by lazy {
OrganizeTokensState(
onBackClick = {},
itemsState = OrganizeTokensListState.GroupedByNetwork(
items = draggableItems,
),
tokenListUM = OrganizeTokensListUM.EmptyList,
header = OrganizeTokensState.HeaderConfig(
onSortClick = {},
onGroupClick = {},
),
dndConfig = OrganizeTokensState.DragAndDropConfig(
onItemDragged = { _, _ -> },
onItemDragStart = {},
canDragItemOver = { _, _ -> false },
onItemDragEnd = {},
),
actions = OrganizeTokensState.ActionsConfig(
onApplyClick = {},
onCancelClick = {},
),
scrollListToTop = consumedEvent(),
isBalanceHidden = true,
)
}
val organizeTokensState by lazy {
groupedOrganizeTokensState.copy(
itemsState = OrganizeTokensListState.Ungrouped(
items = draggableTokens,
),
)
}
val bottomSheet by lazy {
TangemBottomSheetConfig(
isShown = false,
onDismissRequest = {},
content = WalletBottomSheetConfig.UnlockWallets(
onUnlockClick = {},
onScanClick = {},
),
)
}
val actionsBottomSheet = ActionsBottomSheetConfig(
actions = listOf(
TokenActionButtonConfig(

View file

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

View file

@ -1,166 +0,0 @@
package com.tangem.feature.wallet.presentation.organizetokens
import com.tangem.core.ui.event.consumedEvent
import com.tangem.core.ui.event.triggeredEvent
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListUM
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.InProgressStateConverter
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.TokenListToStateConverter
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.TokenListToStateConverterV2
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.error.TokenListErrorConverter
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.error.TokenListSortingErrorConverter
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items.CryptoCurrencyToDraggableItemConverter
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items.NetworkGroupToDraggableItemsConverter
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items.TokenListToListStateConverter
import com.tangem.feature.wallet.presentation.organizetokens.utils.dnd.DragAndDropAdapterV2
import com.tangem.utils.Provider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
internal class OrganizeTokensStateHolder(
private val intents: OrganizeTokensIntents,
private val dragAndDropIntents: DragAndDropIntents,
private val dragAndDropAdapterV2: DragAndDropAdapterV2,
private val appCurrencyProvider: Provider<AppCurrency>,
private val accountsFeatureToggles: AccountsFeatureToggles,
) {
private val stateFlowInternal: MutableStateFlow<OrganizeTokensState> = MutableStateFlow(getInitialState())
private val tokenListConverter by lazy {
val tokensConverter = CryptoCurrencyToDraggableItemConverter(appCurrencyProvider)
val itemsConverter = TokenListToListStateConverter(
tokensConverter = tokensConverter,
groupsConverter = NetworkGroupToDraggableItemsConverter(tokensConverter),
)
TokenListToStateConverter(Provider(stateFlowInternal::value), itemsConverter)
}
private val inProgressStateConverter by lazy { InProgressStateConverter() }
private val tokenListErrorConverter by lazy {
TokenListErrorConverter(Provider(stateFlowInternal::value), inProgressStateConverter)
}
private val tokenListSortingErrorConverter by lazy {
TokenListSortingErrorConverter(Provider(stateFlowInternal::value), inProgressStateConverter)
}
val stateFlow: StateFlow<OrganizeTokensState> = stateFlowInternal
fun updateStateWithTokenList(tokenList: TokenList) {
updateState { tokenListConverter.convert(tokenList) }
}
fun updateStateWithAccountList(accountStatusList: AccountStatusList, isAccountsModeEnabled: Boolean) {
updateState {
TokenListToStateConverterV2(
accountStatusList = accountStatusList,
isAccountsMode = isAccountsModeEnabled,
appCurrency = appCurrencyProvider(),
).transform(this)
}
}
fun updateStateAfterTokenListSorting(tokenList: TokenList) {
updateState {
tokenListConverter.convert(tokenList).copy(
scrollListToTop = triggeredEvent(Unit, ::consumeScrollListToTopEvent),
)
}
}
fun updateStateAfterTokenListSortingV2(accountStatusList: AccountStatusList, isAccountsModeEnabled: Boolean) {
updateState {
TokenListToStateConverterV2(
accountStatusList = accountStatusList,
isAccountsMode = isAccountsModeEnabled,
appCurrency = appCurrencyProvider(),
).transform(this).copy(
scrollListToTop = triggeredEvent(Unit, ::consumeScrollListToTopEvent),
)
}
}
fun updateStateToDisplayProgress() {
updateState { inProgressStateConverter.convert(value = this) }
}
fun updateStateToHideProgress() {
updateState { inProgressStateConverter.convertBack(value = this) }
}
fun updateStateWithManualSortingV2(tokenListUM: OrganizeTokensListUM) {
updateState { copy(tokenListUM = tokenListUM) }
}
fun updateStateWithManualSorting(itemsState: OrganizeTokensListState) {
updateState { copy(itemsState = itemsState) }
}
fun disableSortingByBalance() {
updateState { copy(header = header.copy(isSortedByBalance = false)) }
}
fun updateHiddenState(isBalanceHidden: Boolean) {
updateState { copy(isBalanceHidden = isBalanceHidden) }
}
fun updateStateWithError(error: TokenListError) {
updateState { tokenListErrorConverter.convert(error) }
}
fun updateStateWithError(error: TokenListSortingError) {
updateState { tokenListSortingErrorConverter.convert(error) }
}
private fun getInitialState(): OrganizeTokensState {
return OrganizeTokensState(
onBackClick = intents::onBackClick,
itemsState = OrganizeTokensListState.Empty,
tokenListUM = OrganizeTokensListUM.EmptyList,
header = OrganizeTokensState.HeaderConfig(
onSortClick = intents::onSortClick,
onGroupClick = intents::onGroupClick,
),
actions = OrganizeTokensState.ActionsConfig(
onApplyClick = intents::onApplyClick,
onCancelClick = intents::onCancelClick,
),
dndConfig = if (accountsFeatureToggles.isFeatureEnabled) {
OrganizeTokensState.DragAndDropConfig(
onItemDragged = dragAndDropAdapterV2::onItemDragged,
onItemDragStart = dragAndDropAdapterV2::onItemDraggingStart,
onItemDragEnd = dragAndDropAdapterV2::onItemDraggingEnd,
canDragItemOver = dragAndDropAdapterV2::canDragItemOver,
)
} else {
OrganizeTokensState.DragAndDropConfig(
onItemDragged = dragAndDropIntents::onItemDragged,
onItemDragStart = dragAndDropIntents::onItemDraggingStart,
onItemDragEnd = dragAndDropIntents::onItemDraggingEnd,
canDragItemOver = dragAndDropIntents::canDragItemOver,
)
},
scrollListToTop = consumedEvent(),
isBalanceHidden = true,
)
}
private inline fun updateState(block: OrganizeTokensState.() -> OrganizeTokensState) {
stateFlowInternal.update(block)
}
private fun consumeScrollListToTopEvent() {
updateState { copy(scrollListToTop = consumedEvent()) }
}
}

View file

@ -1,60 +0,0 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils
import com.tangem.domain.account.models.AccountStatusList
import com.tangem.domain.account.status.model.AccountCryptoCurrencies
import com.tangem.domain.models.account.filterCryptoPortfolio
import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListUM
internal class CryptoCurrenciesIdsResolver {
fun resolve(listState: OrganizeTokensListState, tokenList: TokenList?): List<CryptoCurrency.ID> {
val draggableTokens = when (listState) {
is OrganizeTokensListState.Empty -> return emptyList()
is OrganizeTokensListState.GroupedByNetwork -> listState.items.filterIsInstance<DraggableItem.Token>()
is OrganizeTokensListState.Ungrouped -> listState.items.filterIsInstance<DraggableItem.Token>()
}
val currenciesStatuses = when (tokenList) {
is TokenList.GroupedByNetwork -> tokenList.groups.flatMap { it.currencies }
is TokenList.Ungrouped -> tokenList.currencies
is TokenList.Empty,
null,
-> return emptyList()
}
return draggableTokens.mapNotNull { draggableToken ->
val currencyStatus = currenciesStatuses.firstOrNull {
it.currency.id.value == draggableToken.id
}
currencyStatus?.currency?.id
}
}
@Suppress("UseOrEmpty")
fun resolveV2(tokensListUM: OrganizeTokensListUM, accountStatusList: AccountStatusList?): AccountCryptoCurrencies {
val draggableTokens = when (tokensListUM) {
OrganizeTokensListUM.EmptyList -> return emptyMap()
is OrganizeTokensListUM.AccountList,
is OrganizeTokensListUM.TokensList,
-> tokensListUM.items.filterIsInstance<DraggableItem.Token>()
}
return accountStatusList?.accountStatuses
?.filterCryptoPortfolio()
?.filter { it.tokenList != TokenList.Empty }
?.associate { accountStatus ->
val currencies = accountStatus.flattenCurrencies()
accountStatus.account to draggableTokens
.asSequence()
.filter { it.accountId == accountStatus.account.accountId.value }
.mapNotNull { sortedToken ->
currencies.firstOrNull { it.currency.id.value == sortedToken.id }?.currency
}
.toList()
} ?: emptyMap()
}
}

View file

@ -1,31 +0,0 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.common
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListUM
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
internal inline fun OrganizeTokensListState.updateItems(
update: (PersistentList<DraggableItem>) -> List<DraggableItem>,
): OrganizeTokensListState {
val updatedItems = update(items).toPersistentList()
return when (this) {
is OrganizeTokensListState.GroupedByNetwork -> copy(items = updatedItems)
is OrganizeTokensListState.Ungrouped -> copy(items = updatedItems)
is OrganizeTokensListState.Empty -> this
}
}
internal inline fun OrganizeTokensListUM.updateItems(
update: (PersistentList<DraggableItem>) -> List<DraggableItem>,
): OrganizeTokensListUM {
val updatedItems = update(items).toPersistentList()
return when (this) {
is OrganizeTokensListUM.AccountList -> copy(items = updatedItems)
is OrganizeTokensListUM.TokensList -> copy(items = updatedItems)
OrganizeTokensListUM.EmptyList -> this
}
}

View file

@ -1,32 +0,0 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensState
import com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items.TokenListToListStateConverter
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
internal class TokenListToStateConverter(
private val currentState: Provider<OrganizeTokensState>,
private val itemsConverter: TokenListToListStateConverter,
) : Converter<TokenList, OrganizeTokensState> {
override fun convert(value: TokenList): OrganizeTokensState {
val state = currentState()
val itemsState = itemsConverter.convert(value)
return state.copy(
itemsState = itemsState,
header = state.header.copy(
isEnabled = itemsState !is OrganizeTokensListState.Empty,
isSortedByBalance = value.sortedBy == TokensSortType.BALANCE,
isGrouped = value is TokenList.GroupedByNetwork,
),
actions = state.actions.copy(
canApply = itemsState !is OrganizeTokensListState.Empty,
),
)
}
}

View file

@ -1,74 +0,0 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items
import com.tangem.common.getTotalWithRewardsStakingBalance
import com.tangem.core.ui.components.currency.icon.converter.CryptoCurrencyToIconStateConverter
import com.tangem.core.ui.components.token.state.TokenItemState
import com.tangem.core.ui.extensions.stringReference
import com.tangem.core.ui.format.bigdecimal.BigDecimalFormatConstants
import com.tangem.core.ui.format.bigdecimal.fiat
import com.tangem.core.ui.format.bigdecimal.format
import com.tangem.domain.appcurrency.model.AppCurrency
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.staking.StakingBalance
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupHeaderId
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getTokenItemId
import com.tangem.utils.Provider
import com.tangem.utils.converter.Converter
import com.tangem.utils.extensions.orZero
import java.math.BigDecimal
internal class CryptoCurrencyToDraggableItemConverter(
private val appCurrencyProvider: Provider<AppCurrency>,
) : Converter<CryptoCurrencyStatus, DraggableItem.Token> {
private val iconStateConverter = CryptoCurrencyToIconStateConverter()
override fun convert(value: CryptoCurrencyStatus): DraggableItem.Token {
return createDraggableToken(value, appCurrencyProvider())
}
override fun convertList(input: Collection<CryptoCurrencyStatus>): List<DraggableItem.Token> {
val appCurrency = appCurrencyProvider()
return input.map { createDraggableToken(it, appCurrency) }
}
private fun createDraggableToken(
currencyStatus: CryptoCurrencyStatus,
appCurrency: AppCurrency,
): DraggableItem.Token {
return DraggableItem.Token(
tokenItemState = createTokenItemState(currencyStatus, appCurrency),
groupId = getGroupHeaderId(currencyStatus.currency.network),
)
}
private fun createTokenItemState(
currencyStatus: CryptoCurrencyStatus,
appCurrency: AppCurrency,
): TokenItemState.Draggable {
val currency = currencyStatus.currency
return TokenItemState.Draggable(
id = getTokenItemId(currency.id),
iconState = iconStateConverter.convert(currencyStatus),
titleState = TokenItemState.TitleState.Content(text = stringReference(currency.name)),
subtitle2State = if (currencyStatus.value.isError) {
TokenItemState.Subtitle2State.Unreachable
} else {
TokenItemState.Subtitle2State.TextContent(text = getFormattedFiatAmount(currencyStatus, appCurrency))
},
)
}
private fun getFormattedFiatAmount(currency: CryptoCurrencyStatus, appCurrency: AppCurrency): String {
val stakingBalance = currency.value.stakingBalance as? StakingBalance.Data
val fiatRate = currency.value.fiatRate ?: BigDecimal.ZERO
val fiatStakingBalance = stakingBalance?.getTotalWithRewardsStakingBalance(currency.currency.network.rawId)
?.multiply(fiatRate).orZero()
val fiatAmount = currency.value.fiatAmount ?: return BigDecimalFormatConstants.EMPTY_BALANCE_SIGN
return (fiatAmount + fiatStakingBalance).format { fiat(appCurrency.code, appCurrency.symbol) }
}
}

View file

@ -1,41 +0,0 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items
import com.tangem.domain.models.tokenlist.TokenList.GroupedByNetwork.NetworkGroup
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupHeaderId
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.getGroupPlaceholder
import com.tangem.utils.converter.Converter
internal class NetworkGroupToDraggableItemsConverter(
private val itemConverter: CryptoCurrencyToDraggableItemConverter,
) : Converter<NetworkGroup, List<DraggableItem>> {
override fun convert(value: NetworkGroup): List<DraggableItem> {
return buildList {
add(createGroupHeader(value))
addAll(createTokens(value))
}
}
override fun convertList(input: Collection<NetworkGroup>): List<List<DraggableItem>> {
val lastItemIndex = input.size - 1
return input.mapIndexed { index, networkGroup ->
convert(networkGroup).toMutableList()
.also { mutableGroup ->
if (index != lastItemIndex) {
mutableGroup.add(getGroupPlaceholder(index))
}
}
}
}
private fun createGroupHeader(group: NetworkGroup) = DraggableItem.GroupHeader(
id = getGroupHeaderId(group.network),
networkName = group.network.name,
)
private fun createTokens(group: NetworkGroup): List<DraggableItem.Token> {
return itemConverter.convertList(group.currencies)
}
}

View file

@ -1,45 +0,0 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.converter.items
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.uniteItems
import com.tangem.utils.converter.Converter
import kotlinx.collections.immutable.PersistentList
import kotlinx.collections.immutable.toPersistentList
internal class TokenListToListStateConverter(
private val groupsConverter: NetworkGroupToDraggableItemsConverter,
private val tokensConverter: CryptoCurrencyToDraggableItemConverter,
) : Converter<TokenList, OrganizeTokensListState> {
override fun convert(value: TokenList): OrganizeTokensListState {
return when (value) {
is TokenList.GroupedByNetwork -> createListState(value)
is TokenList.Ungrouped -> createListState(value)
is TokenList.Empty -> createEmptyListState()
}
}
private fun createListState(tokenList: TokenList.GroupedByNetwork): OrganizeTokensListState.GroupedByNetwork {
return OrganizeTokensListState.GroupedByNetwork(
items = groupsConverter.convertList(tokenList.groups)
.flatten()
.uniteItems()
.toPersistentList(),
)
}
@Suppress("UNCHECKED_CAST") // Erased type
private fun createListState(tokenList: TokenList.Ungrouped): OrganizeTokensListState.Ungrouped {
return OrganizeTokensListState.Ungrouped(
items = tokensConverter.convertList(tokenList.currencies)
.uniteItems()
.toPersistentList() as PersistentList<DraggableItem.Token>,
)
}
private fun createEmptyListState(): OrganizeTokensListState.Empty {
return OrganizeTokensListState.Empty
}
}

View file

@ -1,185 +0,0 @@
package com.tangem.feature.wallet.presentation.organizetokens.utils.dnd
import com.tangem.feature.wallet.presentation.organizetokens.DragAndDropIntents
import com.tangem.feature.wallet.presentation.organizetokens.model.DraggableItem
import com.tangem.feature.wallet.presentation.organizetokens.model.OrganizeTokensListState
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.divideMovingItem
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.uniteItems
import com.tangem.feature.wallet.presentation.organizetokens.utils.common.updateItems
import com.tangem.utils.Provider
import kotlinx.collections.immutable.mutate
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.filterNotNull
import org.burnoutcrew.reorderable.ItemPosition
internal class DragAndDropAdapter(
private val listStateProvider: Provider<OrganizeTokensListState>,
) : DragAndDropIntents {
private val draggableGroupsOperations = DraggableGroupsOperations()
private val externalListState: OrganizeTokensListState
get() = listStateProvider.invoke()
private val dragAndDropUpdatesInternal: MutableStateFlow<DragOperation?> = MutableStateFlow(value = null)
private var draggingItem: DraggableItem? = null
private var draggingListState: OrganizeTokensListState? = null
val dragAndDropUpdates: Flow<DragOperation>
get() = dragAndDropUpdatesInternal.filterNotNull()
override fun canDragItemOver(dragOver: ItemPosition, dragging: ItemPosition): Boolean {
val items = when (val listState = externalListState) {
is OrganizeTokensListState.GroupedByNetwork -> listState.items
is OrganizeTokensListState.Empty,
is OrganizeTokensListState.Ungrouped,
-> return true // If ungrouped then item can be moved anywhere
}
val (dragOverItem, draggingItem) = findItemsToMove(
items = items,
moveOverItemKey = dragOver.key,
movedItemKey = dragging.key,
)
if (dragOverItem == null || draggingItem == null) {
return false
}
return when (draggingItem) {
is DraggableItem.GroupHeader -> checkCanMoveHeaderOver(dragOver, dragOverItem, items.lastIndex)
is DraggableItem.Token -> checkCanMoveTokenOver(draggingItem, dragOverItem)
is DraggableItem.Placeholder,
is DraggableItem.Portfolio,
-> false
}
}
override fun onItemDraggingStart(item: DraggableItem) {
if (draggingItem != null) return
draggingItem = item
updateListState(DragOperation.Type.Start) {
when (item) {
is DraggableItem.Placeholder,
is DraggableItem.Portfolio,
-> items
is DraggableItem.GroupHeader -> draggableGroupsOperations.collapseGroup(items, item)
is DraggableItem.Token -> when (this) {
is OrganizeTokensListState.GroupedByNetwork -> items.divideMovingItem(item)
is OrganizeTokensListState.Ungrouped -> items.divideMovingItem(item)
is OrganizeTokensListState.Empty -> items
}
}
}
draggingListState = externalListState
}
override fun onItemDraggingEnd() {
val draggingItem = draggingItem ?: return
updateListState(DragOperation.Type.End(isItemsOrderChanged = checkIsItemsOrderChanged())) {
when (draggingItem) {
is DraggableItem.GroupHeader -> draggableGroupsOperations.expandGroups(items)
is DraggableItem.Token -> items.uniteItems()
is DraggableItem.Placeholder,
is DraggableItem.Portfolio,
-> items
}
}
this.draggingItem = null
}
override fun onItemDragged(from: ItemPosition, to: ItemPosition) {
updateListState(DragOperation.Type.Dragged) {
items.mutate {
it.add(to.index, it.removeAt(from.index))
}
}
}
private fun updateListState(type: DragOperation.Type, block: OrganizeTokensListState.() -> List<DraggableItem>) {
val updatedState = externalListState.updateItems { block(externalListState) }
dragAndDropUpdatesInternal.value = DragOperation(type, updatedState)
}
private fun findItemsToMove(
items: List<DraggableItem>,
moveOverItemKey: Any?,
movedItemKey: Any?,
): Pair<DraggableItem?, DraggableItem?> {
var moveOverItem: DraggableItem? = null
var movedItem: DraggableItem? = null
for (item in items) {
if (item.id == moveOverItemKey) {
moveOverItem = item
}
if (item.id == movedItemKey) {
movedItem = item
}
if (moveOverItem != null && movedItem != null) {
break
}
}
return Pair(moveOverItem, movedItem)
}
private fun checkCanMoveHeaderOver(
moveOverItemPosition: ItemPosition,
moveOverItem: DraggableItem,
lastItemIndex: Int,
): Boolean {
// Group item can be moved only to group divider or to ages of the items list
return when {
moveOverItemPosition.index == 0 -> true
moveOverItemPosition.index == lastItemIndex -> true
moveOverItem is DraggableItem.Placeholder -> true
else -> false
}
}
private fun checkCanMoveTokenOver(item: DraggableItem.Token, moveOverItem: DraggableItem): Boolean {
// Token item can be moved only in its group
return when (moveOverItem) {
is DraggableItem.GroupHeader -> false // Token item can not be moved to group item
is DraggableItem.Token -> item.groupId == moveOverItem.groupId // Token item can not be moved over its group
is DraggableItem.Portfolio,
is DraggableItem.Placeholder,
-> false
}
}
private fun checkIsItemsOrderChanged(): Boolean {
fun OrganizeTokensListState?.getItemsIds(): List<Any>? = this?.items?.mapNotNull { item ->
if (item is DraggableItem.Placeholder) {
null
} else {
item.id
}
}
return externalListState.getItemsIds() != draggingListState.getItemsIds()
}
data class DragOperation(
val type: Type,
val listState: OrganizeTokensListState,
) {
sealed class Type {
data object Start : Type()
data object Dragged : Type()
data class End(val isItemsOrderChanged: Boolean) : Type()
}
}
}

View file

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

View file

@ -2,7 +2,7 @@ package com.tangem.feature.wallet.presentation.wallet.analytics.utils
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.pay.model.CustomerInfo
import com.tangem.domain.models.kyc.KycStatus
import com.tangem.domain.pay.model.MainScreenCustomerInfo
import com.tangem.domain.pay.model.OrderStatus
import com.tangem.domain.tangempay.TangemPayAnalyticsEvents
@ -29,7 +29,7 @@ internal class WalletTangemPayAnalyticsEventSender @Inject constructor(
// ignore cancelled state on analytics
customerInfo.orderStatus == OrderStatus.CANCELED -> return
// ignore kyc not approved state on analytics
customerInfo.info.kycStatus != CustomerInfo.KycStatus.APPROVED -> return
customerInfo.info.kycStatus != KycStatus.APPROVED -> return
cardInfo != null && productInstance != null -> return
else -> TangemPayAnalyticsEvents.IssuingBannerDisplayed()
}

View file

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

View file

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

View file

@ -11,7 +11,6 @@ import com.tangem.domain.account.status.producer.SingleAccountStatusListProducer
import com.tangem.domain.card.CardTypesResolver
import com.tangem.domain.card.common.util.cardTypesResolver
import com.tangem.domain.core.lce.Lce
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.demo.IsDemoCardUseCase
import com.tangem.domain.hotwallet.CheckHotWalletUpgradeBannerUseCase
import com.tangem.domain.hotwallet.GetAccessCodeSkippedUseCase
@ -52,7 +51,6 @@ import javax.inject.Inject
@Suppress("LongParameterList", "LargeClass")
@ModelScoped
internal class GetMultiWalletWarningsFactory @Inject constructor(
private val tokenListStore: MultiWalletTokenListStore,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
private val isNeedToBackupUseCase: IsNeedToBackupUseCase,
@ -71,30 +69,15 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
val cardTypesResolver = (userWallet as? UserWallet.Cold)?.scanResponse?.cardTypesResolver
val accountStatusList by lazy {
val accountStatusListFlow by lazy {
val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
accountDependencies.singleAccountStatusListSupplier(params)
.map { it.totalFiatBalance to it.flattenCurrencies() }
.map { Lce.Content(it) }
}
fun tokenListFlow(): LceFlow<TokenListError, Pair<TotalFiatBalance, List<CryptoCurrencyStatus>>> {
return if (accountDependencies.accountsFeatureToggles.isFeatureEnabled) {
accountStatusList
} else {
runCatching { tokenListStore.getOrThrow(userWallet.walletId) }
.map { result -> result.map { lce -> lce.map { it.totalFiatBalance to it.flattenCurrencies() } } }
.getOrNull()
// in case of runtime change ft in tester menu
?: accountStatusList
}
}
// val params = SingleAccountStatusListProducer.Params(userWallet.walletId)
// val accountStatusListFlow = accountDependencies.singleAccountStatusListSupplier(params)
return combine(
// todo account just use it, after delete accountsFeatureToggles
// accountStatusListFlow,
accountStatusListFlow,
isReadyToShowRateAppUseCase().distinctUntilChanged(),
isNeedToBackupUseCase(userWallet.walletId).distinctUntilChanged(),
seedPhraseNotificationUseCase(userWalletId = userWallet.walletId).distinctUntilChanged(),
@ -110,7 +93,6 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
getUpgradeBannerClosureTimestampUseCase(userWallet.walletId)
.distinctUntilChanged(),
) { array -> array }
.combine(tokenListFlow()) { array, any: Any? -> arrayOf(any).plus(elements = array) }
.map { array ->
val lceTokens = array[0] as Lce<TokenListError, Pair<TotalFiatBalance, List<CryptoCurrencyStatus>>>
val totalFiatBalance = lceTokens.map { it.first }
@ -149,9 +131,19 @@ internal class GetMultiWalletWarningsFactory @Inject constructor(
addYieldPromoNotification(clickIntents, shouldShowYieldPromo)
addInformationalNotifications(userWallet, cardTypesResolver, flattenCurrencies, clickIntents)
addInformationalNotifications(
userWallet = userWallet,
cardTypesResolver = cardTypesResolver,
flattenCurrencies = flattenCurrencies,
clickIntents = clickIntents,
)
addWarningNotifications(cardTypesResolver, flattenCurrencies, isNeedToBackup, clickIntents)
addWarningNotifications(
cardTypesResolver = cardTypesResolver,
flattenCurrencies = flattenCurrencies,
isNeedToBackup = isNeedToBackup,
clickIntents = clickIntents,
)
addPushReminderNotification(
clickIntents = clickIntents,

View file

@ -3,7 +3,6 @@ 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.featuretoggle.AccountsFeatureToggles
import com.tangem.domain.account.status.producer.SingleAccountStatusProducer
import com.tangem.domain.account.status.supplier.SingleAccountStatusSupplier
import com.tangem.domain.card.CardTypesResolver
@ -15,7 +14,6 @@ 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.GetSingleCryptoCurrencyStatusUseCase
import com.tangem.domain.tokens.error.CurrencyStatusError
import com.tangem.domain.wallets.usecase.GetWalletsUseCase
import com.tangem.domain.wallets.usecase.IsNeedToBackupUseCase
@ -30,8 +28,6 @@ import javax.inject.Inject
@ModelScoped
@Suppress("LongParameterList")
internal class GetSingleWalletWarningsFactory @Inject constructor(
private val accountsFeatureToggles: AccountsFeatureToggles,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val singleAccountStatusSupplier: SingleAccountStatusSupplier,
private val isDemoCardUseCase: IsDemoCardUseCase,
private val isReadyToShowRateAppUseCase: IsReadyToShowRateAppUseCase,
@ -40,7 +36,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
private val getWalletsUseCase: GetWalletsUseCase,
) {
private var readyForRateAppNotification = false
private var isReadyForRateAppNotification = false
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): Flow<ImmutableList<WalletNotification>> {
if (userWallet !is UserWallet.Cold) {
@ -54,7 +50,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
flow3 = isNeedToBackupUseCase(userWallet.walletId).conflate(),
flow4 = getWalletsUseCase().conflate(),
) { maybePrimaryCurrencyStatus, isReadyToShowRating, isNeedToBackup, userWallets ->
readyForRateAppNotification = true
isReadyForRateAppNotification = true
buildList {
addUsedOutdatedDataNotification(maybePrimaryCurrencyStatus)
@ -120,8 +116,8 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
cardTypesResolver: CardTypesResolver,
clickIntents: WalletClickIntents,
) {
val userHasWalletOrWallet2 = userWallets.filterIsInstance<UserWallet.Cold>().any {
val typesResolver = it.scanResponse.cardTypesResolver
val hasWalletOrWallet2 = userWallets.filterIsInstance<UserWallet.Cold>().any { coldWallet ->
val typesResolver = coldWallet.scanResponse.cardTypesResolver
typesResolver.isTangemWallet() || typesResolver.isWallet2()
}
@ -129,7 +125,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
element = WalletNotification.NoteMigration(
onClick = { clickIntents.onNoteMigrationButtonClick(NOTE_MIGRATION_URL) },
),
condition = cardTypesResolver.isTangemNote() && !userHasWalletOrWallet2,
condition = cardTypesResolver.isTangemNote() && !hasWalletOrWallet2,
)
addIf(
@ -191,8 +187,8 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
selectedWallet: UserWallet.Cold,
cryptoCurrencyStatus: CryptoCurrencyStatus?,
): Boolean {
return cryptoCurrencyStatus?.currency?.network?.let {
hasSingleWalletSignedHashesUseCase(userWallet = selectedWallet, network = it)
return cryptoCurrencyStatus?.currency?.network?.let { network ->
hasSingleWalletSignedHashesUseCase(userWallet = selectedWallet, network = network)
.conflate()
.distinctUntilChanged()
.firstOrNull()
@ -209,7 +205,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
onDislikeClick = clickIntents::onDislikeAppClick,
onCloseClick = clickIntents::onCloseRateAppWarningClick,
),
condition = isReadyToShowRating && readyForRateAppNotification,
condition = isReadyToShowRating && isReadyForRateAppNotification,
)
}
@ -219,7 +215,7 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
element is WalletNotification.Warning ||
element is WalletNotification.NoteMigration
) {
readyForRateAppNotification = false
isReadyForRateAppNotification = false
}
element
@ -229,16 +225,12 @@ internal class GetSingleWalletWarningsFactory @Inject constructor(
private fun getPrimaryCurrencyStatusFlow(
userWallet: UserWallet,
): Flow<Either<CurrencyStatusError, CryptoCurrencyStatus>> {
return if (accountsFeatureToggles.isFeatureEnabled) {
getAccountStatusFlow(userWallet).mapNotNull { accountStatus ->
accountStatus.flattenCurrencies().firstOrNull()
}
.distinctUntilChanged()
.conflate()
.map { it.right() }
} else {
getSingleCryptoCurrencyStatusUseCase.invokeSingleWallet(userWallet.walletId)
return getAccountStatusFlow(userWallet).mapNotNull { accountStatus ->
accountStatus.flattenCurrencies().firstOrNull()
}
.distinctUntilChanged()
.conflate()
.map { it.right() }
}
private fun getAccountStatusFlow(userWallet: UserWallet): Flow<AccountStatus.CryptoPortfolio> {

View file

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

View file

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

View file

@ -1,62 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.core.lce.LceFlow
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.GetTokenListUseCase
import com.tangem.domain.tokens.error.TokenListError
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.shareIn
import timber.log.Timber
import java.util.concurrent.ConcurrentHashMap
import javax.inject.Inject
@ModelScoped
internal class MultiWalletTokenListStore @Inject constructor(
private val getTokenListUseCase: GetTokenListUseCase,
) {
private val flows: ConcurrentHashMap<UserWalletId, LceFlow<TokenListError, TokenList>> by lazy {
ConcurrentHashMap()
}
fun addIfNot(userWalletId: UserWalletId, coroutineScope: CoroutineScope) {
if (flows[userWalletId] != null) {
Timber.d("Flow with token list for $userWalletId already exists")
return
}
coroutineScope.ensureActive()
flows[userWalletId] = getTokenListUseCase
.launch(userWalletId)
.shareIn(
scope = coroutineScope,
started = SharingStarted.WhileSubscribed(),
replay = 1,
)
Timber.d("Flow with token list for $userWalletId created")
}
fun getOrThrow(userWalletId: UserWalletId): LceFlow<TokenListError, TokenList> {
return requireNotNull(flows[userWalletId]) {
"Flow with token list for $userWalletId doesn't exist"
}
}
fun remove(userWalletId: UserWalletId) {
flows.remove(userWalletId)
Timber.d("Flow with token list for $userWalletId removed")
}
fun clear() {
flows.clear()
Timber.d("All flows with token list cleared")
}
}

View file

@ -1,7 +1,8 @@
package com.tangem.feature.wallet.presentation.wallet.domain
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
import com.tangem.features.tangempay.TangemPayFeatureToggles
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import com.tangem.utils.coroutines.JobHolder
import com.tangem.utils.coroutines.saveInAndJoin
@ -27,6 +28,7 @@ import javax.inject.Singleton
internal class WalletContentFetcher @Inject constructor(
private val walletBalanceFetcher: WalletBalanceFetcher,
private val dispatchers: CoroutineDispatcherProvider,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
) {
private val fetchingJobMap = ConcurrentHashMap<UserWalletId, JobHolder>()
@ -64,8 +66,12 @@ internal class WalletContentFetcher @Inject constructor(
Timber.d("Start fetching for $userWalletId")
val maybeResult = launch {
walletBalanceFetcher(params = WalletBalanceFetcher.Params(userWalletId = userWalletId))
.onLeft(Timber::e)
walletBalanceFetcher(
params = WalletBalanceFetcher.Params(
userWalletId = userWalletId,
isPaymentAccountRefactorEnabled = tangemPayFeatureToggles.isTangemPayAccountsRefactorEnabled,
),
).onLeft(Timber::e)
}
.saveInAndJoin(jobHolder)

View file

@ -1,52 +1,33 @@
package com.tangem.feature.wallet.presentation.wallet.loaders
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
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.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.*
import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.MultiWalletContentLoader
import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.SingleWalletContentLoader
import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.SingleWalletWithTokenContentLoader
import com.tangem.feature.wallet.presentation.wallet.loaders.implementors.WalletContentLoader
import javax.inject.Inject
@Suppress("LongParameterList")
@ModelScoped
internal class WalletContentLoaderFactory @Inject constructor(
private val multiWalletContentLoaderFactory: MultiWalletContentLoaderFactory,
private val multiWalletContentLoaderV2Factory: MultiWalletContentLoaderV2.Factory,
private val singleWalletWithTokenContentLoaderFactory: SingleWalletWithTokenContentLoaderFactory,
private val singleWalletWithTokenContentLoaderV2Factory: SingleWalletWithTokenContentLoaderV2.Factory,
private val accountsFeatureToggles: AccountsFeatureToggles,
private val singleWalletContentLoaderFactory: SingleWalletContentLoaderFactory,
private val singleWalletContentLoaderV2Factory: SingleWalletContentLoaderV2.Factory,
private val multiWalletContentLoaderFactory: MultiWalletContentLoader.Factory,
private val singleWalletWithTokenContentLoaderFactory: SingleWalletWithTokenContentLoader.Factory,
private val singleWalletContentLoaderFactory: SingleWalletContentLoader.Factory,
) {
fun create(
userWallet: UserWallet,
clickIntents: WalletClickIntents,
isRefresh: Boolean = false,
): WalletContentLoader? {
fun create(userWallet: UserWallet, isRefresh: Boolean = false): WalletContentLoader? {
return when {
userWallet.isMultiCurrency -> {
if (accountsFeatureToggles.isFeatureEnabled) {
multiWalletContentLoaderV2Factory.create(userWallet)
} else {
multiWalletContentLoaderFactory.create(userWallet, clickIntents)
}
multiWalletContentLoaderFactory.create(userWallet)
}
userWallet is UserWallet.Cold && userWallet.scanResponse.cardTypesResolver.isSingleWalletWithToken() -> {
if (accountsFeatureToggles.isFeatureEnabled) {
singleWalletWithTokenContentLoaderV2Factory.create(userWallet)
} else {
singleWalletWithTokenContentLoaderFactory.create(userWallet, clickIntents)
}
singleWalletWithTokenContentLoaderFactory.create(userWallet)
}
userWallet is UserWallet.Cold && !userWallet.isMultiCurrency -> {
if (accountsFeatureToggles.isFeatureEnabled) {
singleWalletContentLoaderV2Factory.create(userWallet, isRefresh)
} else {
singleWalletContentLoaderFactory.create(userWallet, clickIntents, isRefresh)
}
singleWalletContentLoaderFactory.create(userWallet, isRefresh)
}
else -> null
}

View file

@ -18,8 +18,8 @@ internal class WalletLoaderStorage @Inject constructor() {
}
fun remove(id: UserWalletId) {
loaders[id]?.let {
it.forEach(Job::cancel)
loaders[id]?.let { jobs ->
jobs.forEach(Job::cancel)
loaders.remove(id)
}
}

View file

@ -4,7 +4,6 @@ import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.models.wallet.isLocked
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import kotlinx.coroutines.CloseableCoroutineDispatcher
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.newSingleThreadContext
@ -14,9 +13,8 @@ import javax.inject.Inject
/**
* Base wallet screen content loader. Use it to load content by [UserWallet].
*
* @property factory factory that creates loader
* @property storage storage that save loader's jobs
* @property dispatchers coroutine dispatchers provider
* @property factory factory that creates loader
* @property storage storage that save loader's jobs
*
[REDACTED_AUTHOR]
*/
@ -33,25 +31,19 @@ internal class WalletScreenContentLoader @Inject constructor(
* Load content by [UserWallet]
*
* @param userWallet user wallet
* @param clickIntents click intents
* @param isRefresh flag that determinate if content must load again
* @param coroutineScope coroutine scope
*/
fun load(
userWallet: UserWallet,
clickIntents: WalletClickIntents,
isRefresh: Boolean = false,
coroutineScope: CoroutineScope,
) {
fun load(userWallet: UserWallet, isRefresh: Boolean = false, coroutineScope: CoroutineScope) {
if (userWallet.isLocked) return
val id = userWallet.walletId
if (!storage.contains(id)) {
loadInternal(userWallet, clickIntents, coroutineScope, isRefresh)
loadInternal(userWallet, coroutineScope, isRefresh)
} else {
if (isRefresh) {
storage.remove(id)
loadInternal(userWallet, clickIntents, coroutineScope, isRefresh = true)
loadInternal(userWallet, coroutineScope, isRefresh = true)
} else {
Timber.d("$id content loading has already started")
}
@ -70,15 +62,9 @@ internal class WalletScreenContentLoader @Inject constructor(
singleBackgroundDispatcher.close()
}
private fun loadInternal(
userWallet: UserWallet,
clickIntents: WalletClickIntents,
coroutineScope: CoroutineScope,
isRefresh: Boolean,
) {
private fun loadInternal(userWallet: UserWallet, coroutineScope: CoroutineScope, isRefresh: Boolean) {
val loader = factory.create(
userWallet = userWallet,
clickIntents = clickIntents,
isRefresh = isRefresh,
)

View file

@ -1,96 +1,33 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
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.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.features.tangempay.TangemPayFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("LongParameterList")
@Deprecated("Use MultiWalletContentLoaderV2 instead")
@ModelScoped
internal class MultiWalletContentLoader(
private val userWallet: UserWallet,
private val stateHolder: WalletStateController,
private val clickIntents: WalletClickIntents,
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val tokenListStore: MultiWalletTokenListStore,
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val walletsRepository: WalletsRepository,
private val currenciesRepository: CurrenciesRepository,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
internal class MultiWalletContentLoader @AssistedInject constructor(
@Assisted private val userWallet: UserWallet,
private val accountListSubscriberFactory: AccountListSubscriber.Factory,
private val walletNFTListSubscriberFactory: WalletNFTListSubscriberV2.Factory,
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
private val multiWalletWarningsSubscriberFactory: MultiWalletWarningsSubscriber.Factory,
private val multiWalletActionButtonsSubscriberFactory: MultiWalletActionButtonsSubscriber.Factory,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
return buildList {
MultiWalletTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
tokenListAnalyticsSender = tokenListAnalyticsSender,
walletWithFundsChecker = walletWithFundsChecker,
tokenListStore = tokenListStore,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
).let(::add)
override fun create(): List<WalletSubscriber> = listOf(
accountListSubscriberFactory.create(userWallet),
walletNFTListSubscriberFactory.create(userWallet),
checkWalletWithFundsSubscriberFactory.create(userWallet),
multiWalletWarningsSubscriberFactory.create(userWallet),
multiWalletActionButtonsSubscriberFactory.create(userWallet),
tangemPayMainSubscriberFactory.create(userWallet),
)
WalletNFTListSubscriber(
userWallet = userWallet,
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
stateHolder = stateHolder,
walletsRepository = walletsRepository,
clickIntents = clickIntents,
currenciesRepository = currenciesRepository,
).let(::add)
MultiWalletWarningsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
).let(::add)
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
if (tangemPayFeatureToggles.isTangemPayEnabled) {
add(tangemPayMainSubscriberFactory.create(userWallet))
}
}
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet): MultiWalletContentLoader
}
}

View file

@ -1,74 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.nft.GetNFTCollectionsUseCase
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.tokens.ApplyTokenListSortingUseCase
import com.tangem.domain.tokens.repository.CurrenciesRepository
import com.tangem.domain.wallets.repository.WalletsRepository
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
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.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.TangemPayMainSubscriber
import com.tangem.features.tangempay.TangemPayFeatureToggles
import javax.inject.Inject
@Suppress("LongParameterList")
@Deprecated("Use MultiWalletContentLoaderV2.Factory instead")
@ModelScoped
internal class MultiWalletContentLoaderFactory @Inject constructor(
private val stateHolder: WalletStateController,
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val tokenListStore: MultiWalletTokenListStore,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val applyTokenListSortingUseCase: ApplyTokenListSortingUseCase,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val walletsRepository: WalletsRepository,
private val getNFTCollectionsUseCase: GetNFTCollectionsUseCase,
private val currenciesRepository: CurrenciesRepository,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
) {
fun create(userWallet: UserWallet, clickIntents: WalletClickIntents): WalletContentLoader {
return MultiWalletContentLoader(
userWallet = userWallet,
clickIntents = clickIntents,
stateHolder = stateHolder,
tokenListAnalyticsSender = tokenListAnalyticsSender,
walletWithFundsChecker = walletWithFundsChecker,
tokenListStore = tokenListStore,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
applyTokenListSortingUseCase = applyTokenListSortingUseCase,
getStoryContentUseCase = getStoryContentUseCase,
walletsRepository = walletsRepository,
getNFTCollectionsUseCase = getNFTCollectionsUseCase,
currenciesRepository = currenciesRepository,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
tangemPayFeatureToggles = tangemPayFeatureToggles,
tangemPayMainSubscriberFactory = tangemPayMainSubscriberFactory,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
)
}
}

View file

@ -1,63 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.promo.GetStoryContentUseCase
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.subscribers.*
import com.tangem.features.tangempay.TangemPayFeatureToggles
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("LongParameterList")
internal class MultiWalletContentLoaderV2 @AssistedInject constructor(
@Assisted private val userWallet: UserWallet,
private val accountListSubscriberFactory: AccountListSubscriber.Factory,
private val tokenListAnalyticsSubscriberFactory: TokenListAnalyticsSubscriber.Factory,
private val walletNFTListSubscriberV2Factory: WalletNFTListSubscriberV2.Factory,
private val stateController: WalletStateController,
private val clickIntents: WalletClickIntents,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
private val tangemPayFeatureToggles: TangemPayFeatureToggles,
private val tangemPayMainSubscriberFactory: TangemPayMainSubscriber.Factory,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> = listOfNotNull(
accountListSubscriberFactory.create(userWallet = userWallet),
tokenListAnalyticsSubscriberFactory.create(userWallet = userWallet),
walletNFTListSubscriberV2Factory.create(userWallet = userWallet),
checkWalletWithFundsSubscriberFactory.create(userWallet = userWallet),
MultiWalletWarningsSubscriber(
userWallet = userWallet,
stateHolder = stateController,
clickIntents = clickIntents,
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
),
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateController,
getStoryContentUseCase = getStoryContentUseCase,
),
if (tangemPayFeatureToggles.isTangemPayEnabled) {
tangemPayMainSubscriberFactory.create(userWallet)
} else {
null
},
)
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet): MultiWalletContentLoaderV2
}
}

View file

@ -1,83 +1,34 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.analytics.api.AnalyticsEventHandler
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.settings.SetWalletWithFundsFoundUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
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.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.subscribers.*
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("LongParameterList")
internal class SingleWalletContentLoader(
private val userWallet: UserWallet.Cold,
private val clickIntents: WalletClickIntents,
private val isRefresh: Boolean,
private val stateHolder: WalletStateController,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
private val getSingleWalletWarningsFactory: GetSingleWalletWarningsFactory,
private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase,
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
internal class SingleWalletContentLoader @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 txHistorySubscriberFactory: TxHistorySubscriber.Factory,
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
return listOf(
PrimaryCurrencySubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase,
setWalletWithFundsFoundUseCase = setWalletWithFundsFoundUseCase,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
analyticsEventHandler = analyticsEventHandler,
),
SingleWalletButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase,
getCryptoCurrencyActionsUseCase = getCryptoCurrencyActionsUseCase,
),
SingleWalletNotificationsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
getSingleWalletWarningsFactory = getSingleWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
),
SingleWalletExpressStatusesSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
analyticsEventHandler = analyticsEventHandler,
getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase,
getOnrampTransactionsUseCase = getOnrampTransactionsUseCase,
onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase,
),
TxHistorySubscriber(
userWallet = userWallet,
isRefresh = isRefresh,
stateHolder = stateHolder,
clickIntents = clickIntents,
getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase,
txHistoryItemsCountUseCase = txHistoryItemsCountUseCase,
txHistoryItemsUseCase = txHistoryItemsUseCase,
),
)
override fun create(): List<WalletSubscriber> = listOf(
primaryCurrencySubscriberFactory.create(userWallet),
singleWalletButtonsSubscriberFactory.create(userWallet),
singleWalletNotificationsSubscriberFactory.create(userWallet),
singleWalletExpressStatusesSubscriberFactory.create(userWallet),
txHistorySubscriberFactory.create(userWallet, isRefresh),
checkWalletWithFundsSubscriberFactory.create(userWallet),
)
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet.Cold, isRefresh: Boolean): SingleWalletContentLoader
}
}

View file

@ -1,57 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.core.decompose.di.ModelScoped
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.settings.SetWalletWithFundsFoundUseCase
import com.tangem.domain.tokens.GetCryptoCurrencyActionsUseCase
import com.tangem.domain.tokens.GetSingleCryptoCurrencyStatusUseCase
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.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetSingleWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import javax.inject.Inject
@ModelScoped
@Suppress("LongParameterList")
@Deprecated("Use SingleWalletContentLoaderV2.Factory instead")
internal class SingleWalletContentLoaderFactory @Inject constructor(
private val stateHolder: WalletStateController,
private val getSingleCryptoCurrencyStatusUseCase: GetSingleCryptoCurrencyStatusUseCase,
private val getCryptoCurrencyActionsUseCase: GetCryptoCurrencyActionsUseCase,
private val getSingleWalletWarningsFactory: GetSingleWalletWarningsFactory,
private val setWalletWithFundsFoundUseCase: SetWalletWithFundsFoundUseCase,
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
) {
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents, isRefresh: Boolean): WalletContentLoader {
return SingleWalletContentLoader(
userWallet = userWallet,
clickIntents = clickIntents,
isRefresh = isRefresh,
stateHolder = stateHolder,
getSingleCryptoCurrencyStatusUseCase = getSingleCryptoCurrencyStatusUseCase,
getCryptoCurrencyActionsUseCase = getCryptoCurrencyActionsUseCase,
getSingleWalletWarningsFactory = getSingleWalletWarningsFactory,
setWalletWithFundsFoundUseCase = setWalletWithFundsFoundUseCase,
txHistoryItemsCountUseCase = txHistoryItemsCountUseCase,
txHistoryItemsUseCase = txHistoryItemsUseCase,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
analyticsEventHandler = analyticsEventHandler,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
getOnrampTransactionsUseCase = getOnrampTransactionsUseCase,
onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase,
)
}
}

View file

@ -1,96 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.analytics.api.AnalyticsEventHandler
import com.tangem.domain.account.status.usecase.GetCryptoCurrencyActionsUseCaseV2
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.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.account.AccountDependencies
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.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.*
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("LongParameterList")
internal class SingleWalletContentLoaderV2 @AssistedInject constructor(
@Assisted private val userWallet: UserWallet.Cold,
@Assisted private val isRefresh: Boolean,
private val clickIntents: WalletClickIntents,
private val stateHolder: WalletStateController,
private val getCryptoCurrencyActionsUseCaseV2: GetCryptoCurrencyActionsUseCaseV2,
private val getSingleWalletWarningsFactory: GetSingleWalletWarningsFactory,
private val txHistoryItemsCountUseCase: GetTxHistoryItemsCountUseCase,
private val txHistoryItemsUseCase: GetTxHistoryItemsUseCase,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getOnrampTransactionsUseCase: GetOnrampTransactionsUseCase,
private val onrampRemoveTransactionUseCase: OnrampRemoveTransactionUseCase,
private val analyticsEventHandler: AnalyticsEventHandler,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val accountDependencies: AccountDependencies,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val dispatchers: CoroutineDispatcherProvider,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> = listOf(
PrimaryCurrencySubscriberV2(
userWallet = userWallet,
singleAccountStatusListSupplier = accountDependencies.singleAccountStatusListSupplier,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
stateController = stateHolder,
analyticsEventHandler = analyticsEventHandler,
),
SingleWalletButtonsSubscriberV2(
userWallet = userWallet,
singleAccountStatusListSupplier = accountDependencies.singleAccountStatusListSupplier,
stateController = stateHolder,
clickIntents = clickIntents,
getCryptoCurrencyActionsUseCaseV2 = getCryptoCurrencyActionsUseCaseV2,
),
SingleWalletNotificationsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
getSingleWalletWarningsFactory = getSingleWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
),
SingleWalletExpressStatusesSubscriberV2(
userWallet = userWallet,
singleAccountStatusListSupplier = accountDependencies.singleAccountStatusListSupplier,
getOnrampTransactionsUseCase = getOnrampTransactionsUseCase,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
onrampRemoveTransactionUseCase = onrampRemoveTransactionUseCase,
stateController = stateHolder,
clickIntents = clickIntents,
analyticsEventHandler = analyticsEventHandler,
),
TxHistorySubscriberV2(
userWallet = userWallet,
singleAccountStatusListSupplier = accountDependencies.singleAccountStatusListSupplier,
txHistoryItemsCountUseCase = txHistoryItemsCountUseCase,
txHistoryItemsUseCase = txHistoryItemsUseCase,
isRefresh = isRefresh,
stateController = stateHolder,
clickIntents = clickIntents,
),
CheckWalletWithFundsSubscriber(
userWallet = userWallet,
singleAccountStatusListSupplier = accountDependencies.singleAccountStatusListSupplier,
walletWithFundsChecker = walletWithFundsChecker,
dispatchers = dispatchers,
),
)
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet.Cold, isRefresh: Boolean): SingleWalletContentLoaderV2
}
}

View file

@ -1,70 +1,29 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
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.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import com.tangem.feature.wallet.presentation.wallet.subscribers.MultiWalletActionButtonsSubscriber
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.SingleWalletWithTokenListSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.SingleWalletWithTokenSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Deprecated("Use SingleWalletWithTokenContentLoaderV2 instead")
@Suppress("LongParameterList")
internal class SingleWalletWithTokenContentLoader(
private val userWallet: UserWallet.Cold,
private val clickIntents: WalletClickIntents,
private val stateHolder: WalletStateController,
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val tokenListStore: MultiWalletTokenListStore,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
internal class SingleWalletWithTokenContentLoader @AssistedInject constructor(
@Assisted private val userWallet: UserWallet.Cold,
private val singleWalletWithTokenSubscriberFactory: SingleWalletWithTokenSubscriber.Factory,
private val multiWalletWarningsSubscriberFactory: MultiWalletWarningsSubscriber.Factory,
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> {
return buildList {
SingleWalletWithTokenListSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
tokenListAnalyticsSender = tokenListAnalyticsSender,
walletWithFundsChecker = walletWithFundsChecker,
tokenListStore = tokenListStore,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
).let(::add)
MultiWalletWarningsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
clickIntents = clickIntents,
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
).let(::add)
MultiWalletActionButtonsSubscriber(
userWallet = userWallet,
stateHolder = stateHolder,
getStoryContentUseCase = getStoryContentUseCase,
).let(::add)
}
override fun create(): List<WalletSubscriber> = listOf(
singleWalletWithTokenSubscriberFactory.create(userWallet),
multiWalletWarningsSubscriberFactory.create(userWallet),
checkWalletWithFundsSubscriberFactory.create(userWallet),
)
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet.Cold): SingleWalletWithTokenContentLoader
}
}

View file

@ -1,57 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.core.decompose.di.ModelScoped
import com.tangem.domain.appcurrency.GetSelectedAppCurrencyUseCase
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.promo.GetStoryContentUseCase
import com.tangem.domain.staking.usecase.StakingAvailabilityListUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyApyFlowUseCase
import com.tangem.domain.yield.supply.usecase.YieldSupplyGetShouldShowMainPromoUseCase
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.TokenListAnalyticsSender
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.domain.MultiWalletTokenListStore
import com.tangem.feature.wallet.presentation.wallet.domain.WalletWithFundsChecker
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
import javax.inject.Inject
// TODO: Refactor
@Suppress("LongParameterList")
@Deprecated("Use SingleWalletWithTokenContentLoaderV2.Factory instead")
@ModelScoped
internal class SingleWalletWithTokenContentLoaderFactory @Inject constructor(
private val stateHolder: WalletStateController,
private val tokenListAnalyticsSender: TokenListAnalyticsSender,
private val walletWithFundsChecker: WalletWithFundsChecker,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
private val tokenListStore: MultiWalletTokenListStore,
private val getSelectedAppCurrencyUseCase: GetSelectedAppCurrencyUseCase,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val getStoryContentUseCase: GetStoryContentUseCase,
private val yieldSupplyApyFlowUseCase: YieldSupplyApyFlowUseCase,
private val stakingAvailabilityListUseCase: StakingAvailabilityListUseCase,
private val yieldSupplyGetShouldShowMainPromoUseCase: YieldSupplyGetShouldShowMainPromoUseCase,
) {
fun create(userWallet: UserWallet.Cold, clickIntents: WalletClickIntents): SingleWalletWithTokenContentLoader {
return SingleWalletWithTokenContentLoader(
userWallet = userWallet,
clickIntents = clickIntents,
stateHolder = stateHolder,
tokenListAnalyticsSender = tokenListAnalyticsSender,
walletWithFundsChecker = walletWithFundsChecker,
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
tokenListStore = tokenListStore,
getSelectedAppCurrencyUseCase = getSelectedAppCurrencyUseCase,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
getStoryContentUseCase = getStoryContentUseCase,
yieldSupplyApyFlowUseCase = yieldSupplyApyFlowUseCase,
stakingAvailabilityListUseCase = stakingAvailabilityListUseCase,
yieldSupplyGetShouldShowMainPromoUseCase = yieldSupplyGetShouldShowMainPromoUseCase,
)
}
}

View file

@ -1,46 +0,0 @@
package com.tangem.feature.wallet.presentation.wallet.loaders.implementors
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsAnalyticsSender
import com.tangem.feature.wallet.presentation.wallet.analytics.utils.WalletWarningsSingleEventSender
import com.tangem.feature.wallet.presentation.wallet.domain.GetMultiWalletWarningsFactory
import com.tangem.feature.wallet.presentation.wallet.state.WalletStateController
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.SingleWalletWithTokenSubscriber
import com.tangem.feature.wallet.presentation.wallet.subscribers.WalletSubscriber
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
@Suppress("LongParameterList")
internal class SingleWalletWithTokenContentLoaderV2 @AssistedInject constructor(
@Assisted private val userWallet: UserWallet.Cold,
private val singleWalletWithTokenSubscriberFactory: SingleWalletWithTokenSubscriber.Factory,
private val checkWalletWithFundsSubscriberFactory: CheckWalletWithFundsSubscriber.Factory,
private val clickIntents: WalletClickIntents,
private val stateController: WalletStateController,
private val walletWarningsAnalyticsSender: WalletWarningsAnalyticsSender,
private val walletWarningsSingleEventSender: WalletWarningsSingleEventSender,
private val getMultiWalletWarningsFactory: GetMultiWalletWarningsFactory,
) : WalletContentLoader(id = userWallet.walletId) {
override fun create(): List<WalletSubscriber> = listOf(
singleWalletWithTokenSubscriberFactory.create(userWallet),
MultiWalletWarningsSubscriber(
userWallet = userWallet,
stateHolder = stateController,
clickIntents = clickIntents,
getMultiWalletWarningsFactory = getMultiWalletWarningsFactory,
walletWarningsAnalyticsSender = walletWarningsAnalyticsSender,
walletWarningsSingleEventSender = walletWarningsSingleEventSender,
),
checkWalletWithFundsSubscriberFactory.create(userWallet),
)
@AssistedFactory
interface Factory {
fun create(userWallet: UserWallet.Cold): SingleWalletWithTokenContentLoaderV2
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,6 +3,7 @@ package com.tangem.feature.wallet.presentation.wallet.state.transformers
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletNFTItemUM
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
internal class RemoveNFTCollectionsTransformer(
userWalletId: UserWalletId,
@ -17,4 +18,13 @@ internal class RemoveNFTCollectionsTransformer(
is WalletState.SingleCurrency.Locked,
-> prevState
}
override fun transform(walletUM: WalletUM): WalletUM {
return when (walletUM) {
is WalletUM.Content -> walletUM.copy(
nftState = WalletNFTItemUM.Hidden,
)
is WalletUM.Locked -> walletUM
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -7,9 +7,7 @@ import com.tangem.domain.card.common.util.getCardsCount
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.tokens.error.TokenListError
import com.tangem.feature.wallet.presentation.wallet.domain.WalletAdditionalInfoFactory
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.utils.disableButtons
import timber.log.Timber
import java.math.BigDecimal
@ -51,6 +49,20 @@ internal class SetTokenListErrorTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return when (walletUM) {
is WalletUM.Content -> {
walletUM.copy(
tokensListUM = WalletTokensListUM.Empty,
)
}
is WalletUM.Locked -> {
Timber.w("Impossible to load tokens list for locked wallet")
walletUM
}
}
}
private fun WalletCardState.toLoadedState(): WalletCardState {
return WalletCardState.Content(
id = id,

View file

@ -5,11 +5,10 @@ import com.tangem.domain.models.currency.CryptoCurrency
import com.tangem.domain.models.wallet.UserWallet
import com.tangem.domain.staking.model.StakingAvailability
import com.tangem.feature.wallet.child.wallet.model.intents.WalletClickIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletCardState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletTokensListState
import com.tangem.feature.wallet.presentation.wallet.state.model.*
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.MultiWalletCardStateConverter
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.TokenListStateConverter
import com.tangem.feature.wallet.presentation.wallet.state.transformers.converter.WalletTokensListUMTransformer
import com.tangem.feature.wallet.presentation.wallet.state.utils.enableButtons
import timber.log.Timber
import java.math.BigDecimal
@ -22,6 +21,7 @@ internal class SetTokenListTransformer(
private val yieldSupplyApyMap: Map<String, BigDecimal> = emptyMap(),
private val stakingAvailabilityMap: Map<CryptoCurrency, StakingAvailability> = emptyMap(),
private val shouldShowMainPromo: Boolean,
private val isAccountsModeEnabled: Boolean,
) : WalletStateTransformer(userWallet.walletId) {
override fun transform(prevState: WalletState): WalletState {
@ -45,6 +45,20 @@ internal class SetTokenListTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return when (walletUM) {
is WalletUM.Content -> {
walletUM.copy(
tokensListUM = toLoadedState(),
)
}
is WalletUM.Locked -> {
Timber.w("Impossible to load tokens list for locked wallet")
walletUM
}
}
}
private fun WalletCardState.toLoadedState(): WalletCardState {
val fiatBalance = when (params) {
is TokenConverterParams.Account -> params.accountList.totalFiatBalance
@ -68,4 +82,19 @@ internal class SetTokenListTransformer(
shouldShowMainPromo = shouldShowMainPromo,
).convert(value = this)
}
private fun toLoadedState(): WalletTokensListUM {
if (params !is TokenConverterParams.Account) return WalletTokensListUM.Empty
return WalletTokensListUMTransformer(
selectedWallet = userWallet,
appCurrency = appCurrency,
clickIntents = clickIntents,
yieldModuleApyMap = yieldSupplyApyMap,
stakingAvailabilityMap = stakingAvailabilityMap,
shouldShowMainPromo = shouldShowMainPromo,
isAccountsModeEnabled = isAccountsModeEnabled,
expandedAccounts = params.expandedAccounts,
).convert(value = params.accountList)
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,6 +5,7 @@ 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.domain.models.kyc.KycStatus
import com.tangem.domain.models.wallet.UserWalletId
import com.tangem.domain.pay.TangemPayDetailsConfig
import com.tangem.domain.pay.model.CustomerInfo.CardInfo
@ -16,8 +17,7 @@ import com.tangem.feature.wallet.child.wallet.model.intents.TangemPayIntents
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState
import com.tangem.feature.wallet.presentation.wallet.state.model.TangemPayState.Progress
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletState
import com.tangem.domain.pay.model.CustomerInfo.KycStatus.APPROVED
import com.tangem.domain.pay.model.CustomerInfo
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletUM
import java.util.Currency
/**
@ -42,6 +42,10 @@ internal class TangemPayUpdateInfoStateTransformer(
}
}
override fun transform(walletUM: WalletUM): WalletUM {
return walletUM // todo redesign main
}
private fun createInitialState(): TangemPayState {
val cardInfo = value.info.cardInfo
val productInstance = value.info.productInstance
@ -50,7 +54,7 @@ internal class TangemPayUpdateInfoStateTransformer(
// when statement copied to WalletTangemPayAnalyticsEventSender. Be careful when editing.
return when {
value.orderStatus == OrderStatus.CANCELED -> createCancelledState(customerId)
value.info.kycStatus != APPROVED && !value.info.customerId.isNullOrEmpty() ->
value.info.kycStatus != KycStatus.APPROVED && !value.info.customerId.isNullOrEmpty() ->
createKycInProgressState(kycStatus = value.info.kycStatus, customerId = customerId)
cardInfo != null && productInstance != null ->
getCardInfoState(customerId, cardInfo, productInstance)
@ -74,7 +78,6 @@ internal class TangemPayUpdateInfoStateTransformer(
cardId = productInstance.cardId,
isPinSet = cardInfo.isPinSet,
cardFrozenState = cardFrozenState,
customerWalletAddress = cardInfo.customerWalletAddress,
cardNumberEnd = cardInfo.lastFourDigits,
chainId = POLYGON_CHAIN_ID,
),
@ -89,25 +92,24 @@ internal class TangemPayUpdateInfoStateTransformer(
}
}
private fun createKycInProgressState(kycStatus: CustomerInfo.KycStatus, customerId: String): TangemPayState =
Progress(
title = TextReference.Res(R.string.tangempay_payment_account),
description = when (kycStatus) {
CustomerInfo.KycStatus.REJECTED -> TextReference.Res(R.string.tangempay_kyc_has_failed)
else -> TextReference.Res(R.string.tangempay_kyc_in_progress)
},
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
iconRes = R.drawable.ic_promo_kyc_36,
onButtonClick = {
when (kycStatus) {
CustomerInfo.KycStatus.REJECTED -> tangemPayClickIntents.onKycRejectedClicked(
userWalletId = userWalletId,
customerId = customerId,
)
else -> tangemPayClickIntents.onKycProgressClicked(userWalletId)
}
},
)
private fun createKycInProgressState(kycStatus: KycStatus, customerId: String): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_payment_account),
description = when (kycStatus) {
KycStatus.REJECTED -> TextReference.Res(R.string.tangempay_kyc_has_failed)
else -> TextReference.Res(R.string.tangempay_kyc_in_progress)
},
buttonText = TextReference.Res(R.string.tangempay_kyc_in_progress_notification_button),
iconRes = R.drawable.ic_promo_kyc_36,
onButtonClick = {
when (kycStatus) {
KycStatus.REJECTED -> tangemPayClickIntents.onKycRejectedClicked(
userWalletId = userWalletId,
customerId = customerId,
)
else -> tangemPayClickIntents.onKycProgressClicked(userWalletId)
}
},
)
private fun createIssueProgressState(): TangemPayState = Progress(
title = TextReference.Res(R.string.tangempay_payment_account),

View file

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

View file

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

Some files were not shown because too many files have changed in this diff Show more