Updated on 2026-08-14
This commit is contained in:
commit
4f7b1b791b
47 changed files with 440 additions and 1451 deletions
|
|
@ -177,20 +177,6 @@ internal object TokensDomainModule {
|
|||
return GetCryptoCurrencyUseCase(currenciesRepository, multiWalletCryptoCurrenciesSupplier)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideToggleTokenListGroupingUseCase(
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ToggleTokenListGroupingUseCase {
|
||||
return ToggleTokenListGroupingUseCase(dispatchers)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideToggleTokenListSortingUseCase(dispatchers: CoroutineDispatcherProvider): ToggleTokenListSortingUseCase {
|
||||
return ToggleTokenListSortingUseCase(dispatchers)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideApplyTokenListSortingUseCase(
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ internal object AccountStatusUseCaseModule {
|
|||
fun provideApplyTokenListSortingUseCaseV2(
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ApplyTokenListSortingUseCaseV2 {
|
||||
return ApplyTokenListSortingUseCaseV2(
|
||||
): ApplyTokenListSortingUseCase {
|
||||
return ApplyTokenListSortingUseCase(
|
||||
accountsCRUDRepository = accountsCRUDRepository,
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
|
|
@ -141,8 +141,8 @@ internal object AccountStatusUseCaseModule {
|
|||
@Singleton
|
||||
fun provideToggleTokenListSortingUseCaseV2(
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ToggleTokenListSortingUseCaseV2 {
|
||||
return ToggleTokenListSortingUseCaseV2(
|
||||
): ToggleTokenListSortingUseCase {
|
||||
return ToggleTokenListSortingUseCase(
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
@ -151,8 +151,8 @@ internal object AccountStatusUseCaseModule {
|
|||
@Singleton
|
||||
fun provideToggleTokenListGroupingUseCaseV2(
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): ToggleTokenListGroupingUseCaseV2 {
|
||||
return ToggleTokenListGroupingUseCaseV2(
|
||||
): ToggleTokenListGroupingUseCase {
|
||||
return ToggleTokenListGroupingUseCase(
|
||||
dispatchers = dispatchers,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import com.tangem.domain.models.account.Account
|
|||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
typealias AccountCryptoCurrencies = Map<Account, List<CryptoCurrency>>
|
||||
typealias AccountCryptoCurrencies = Map<out Account, List<CryptoCurrency>>
|
||||
|
||||
/**
|
||||
* Combines an [Account] with its corresponding [CryptoCurrency].
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ private typealias SortingErrorByAccountId = MutableMap<AccountId, TokenListSorti
|
|||
* @property accountsCRUDRepository Repository for CRUD operations on accounts.
|
||||
* @property dispatchers Coroutine dispatcher provider for managing threading.
|
||||
*/
|
||||
class ApplyTokenListSortingUseCaseV2(
|
||||
class ApplyTokenListSortingUseCase(
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
|
@ -131,8 +131,8 @@ class ApplyTokenListSortingUseCaseV2(
|
|||
|
||||
val accountCurrencies = account.cryptoCurrencies.toList()
|
||||
.sortTokens(sortedCurrencies = sortedCurrencies)
|
||||
.getOrElse {
|
||||
errors[account.accountId] = it
|
||||
.getOrElse { error ->
|
||||
errors[account.accountId] = error
|
||||
return@map account
|
||||
}
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|||
*
|
||||
* @property dispatchers Provides coroutine dispatchers for executing tasks.
|
||||
*/
|
||||
class ToggleTokenListGroupingUseCaseV2(
|
||||
class ToggleTokenListGroupingUseCase(
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
|
|
@ -21,7 +21,7 @@ import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
|||
*
|
||||
* @property dispatchers Provides coroutine dispatchers for executing tasks.
|
||||
*/
|
||||
class ToggleTokenListSortingUseCaseV2(
|
||||
class ToggleTokenListSortingUseCase(
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ internal class ApplyTokenListSortingUseCaseTest {
|
|||
|
||||
private val accountsCRUDRepository = mockk<AccountsCRUDRepository>(relaxUnitFun = true)
|
||||
|
||||
private val useCase = ApplyTokenListSortingUseCaseV2(
|
||||
private val useCase = ApplyTokenListSortingUseCase(
|
||||
accountsCRUDRepository = accountsCRUDRepository,
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -28,9 +28,9 @@ import java.math.BigDecimal
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class ToggleTokenListGroupingUseCaseV2Test {
|
||||
class ToggleTokenListGroupingUseCaseTest {
|
||||
|
||||
private val useCase = ToggleTokenListGroupingUseCaseV2(dispatchers = TestingCoroutineDispatcherProvider())
|
||||
private val useCase = ToggleTokenListGroupingUseCase(dispatchers = TestingCoroutineDispatcherProvider())
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
|
|
@ -28,9 +28,9 @@ import java.math.BigDecimal
|
|||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class ToggleTokenListSortingUseCaseV2Test {
|
||||
class ToggleTokenListSortingUseCaseTest {
|
||||
|
||||
private val useCase = ToggleTokenListSortingUseCaseV2(dispatchers = TestingCoroutineDispatcherProvider())
|
||||
private val useCase = ToggleTokenListSortingUseCase(dispatchers = TestingCoroutineDispatcherProvider())
|
||||
|
||||
private val userWalletId = UserWalletId("011")
|
||||
private val cryptoCurrencyFactory = MockCryptoCurrencyFactory()
|
||||
|
|
@ -1,51 +0,0 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.tokens.error.TokenListSortingError
|
||||
import com.tangem.domain.tokens.operations.TokenListFactory
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ToggleTokenListGroupingUseCase(
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(tokenList: TokenList): Either<TokenListSortingError, TokenList> {
|
||||
return withContext(dispatchers.default) {
|
||||
either {
|
||||
when (tokenList) {
|
||||
is TokenList.GroupedByNetwork -> ungroupTokens(tokenList)
|
||||
is TokenList.Ungrouped -> groupTokens(tokenList)
|
||||
is TokenList.Empty -> raise(TokenListSortingError.TokenListIsEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Raise<TokenListSortingError>.groupTokens(tokenList: TokenList.Ungrouped): TokenList.GroupedByNetwork {
|
||||
validate(tokenList)
|
||||
|
||||
return TokenListFactory.createGroupedByNetwork(tokenList)
|
||||
}
|
||||
|
||||
private fun Raise<TokenListSortingError>.ungroupTokens(tokenList: TokenList.GroupedByNetwork): TokenList.Ungrouped {
|
||||
validate(tokenList)
|
||||
|
||||
return TokenListFactory.createUngrouped(tokenList)
|
||||
}
|
||||
|
||||
private fun Raise<TokenListSortingError>.validate(tokenList: TokenList) {
|
||||
ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) {
|
||||
TokenListSortingError.TokenListIsLoading
|
||||
}
|
||||
|
||||
ensure(tokenList.flattenCurrencies().isNotEmpty()) {
|
||||
TokenListSortingError.TokenListIsEmpty
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.TotalFiatBalance
|
||||
import com.tangem.domain.models.tokenlist.TokenList
|
||||
import com.tangem.domain.tokens.error.TokenListSortingError
|
||||
import com.tangem.domain.tokens.operations.TokenListFactory
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ToggleTokenListSortingUseCase(
|
||||
private val dispatchers: CoroutineDispatcherProvider,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(tokenList: TokenList): Either<TokenListSortingError, TokenList> {
|
||||
return withContext(dispatchers.default) {
|
||||
either {
|
||||
ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) {
|
||||
TokenListSortingError.TokenListIsLoading
|
||||
}
|
||||
|
||||
TokenListFactory.create(
|
||||
statuses = tokenList.flattenCurrencies(),
|
||||
groupType = when (tokenList) {
|
||||
is TokenList.GroupedByNetwork -> TokensGroupType.NETWORK
|
||||
is TokenList.Ungrouped -> TokensGroupType.NONE
|
||||
is TokenList.Empty -> raise(TokenListSortingError.TokenListIsEmpty)
|
||||
},
|
||||
sortType = TokensSortType.BALANCE,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.tokens.error.TokenListSortingError
|
||||
import com.tangem.domain.tokens.mock.MockTokenLists
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class ToggleTokenListGroupingTest {
|
||||
|
||||
private val useCase = ToggleTokenListGroupingUseCase(
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `when list is empty then error should be received`() = runTest {
|
||||
// Given
|
||||
val expected = TokenListSortingError.TokenListIsEmpty.left()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.emptyUngroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is grouped and loading then error should be received`() = runTest {
|
||||
// Given
|
||||
val expected = TokenListSortingError.TokenListIsLoading.left()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.loadingGroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is ungrouped and loading then error should be received`() = runTest {
|
||||
// Given
|
||||
val expected = TokenListSortingError.TokenListIsLoading.left()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.loadingUngroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is ungrouped and sorted then sorted grouped list should be received`() = runTest {
|
||||
// Given
|
||||
val expected = MockTokenLists.sortedGroupedTokenList.right()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.sortedUngroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is ungrouped and unsorted then unsorted grouped list should be received`() = runTest {
|
||||
// Given
|
||||
val expected = MockTokenLists.unsortedGroupedTokenList.right()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.unsortedUngroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is grouped and sorted then sorted ungrouped list should be received`() = runTest {
|
||||
// Given
|
||||
val expected = MockTokenLists.sortedUngroupedTokenList.right()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.sortedGroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is grouped and unsorted then unsorted ungrouped list should be received`() = runTest {
|
||||
// Given
|
||||
val expected = MockTokenLists.unsortedUngroupedTokenList.right()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.unsortedGroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
package com.tangem.domain.tokens
|
||||
|
||||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.tokens.error.TokenListSortingError
|
||||
import com.tangem.domain.tokens.mock.MockTokenLists
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
internal class ToggleTokenListSortingUseCaseTest {
|
||||
|
||||
private val useCase = ToggleTokenListSortingUseCase(
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `when list is empty then error should be received`() = runTest {
|
||||
// Given
|
||||
val expected = TokenListSortingError.TokenListIsEmpty.left()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.emptyTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is grouped and loading then error should be received`() = runTest {
|
||||
// Given
|
||||
val expected = TokenListSortingError.TokenListIsLoading.left()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.loadingGroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is ungrouped and loading then error should be received`() = runTest {
|
||||
// Given
|
||||
val expected = TokenListSortingError.TokenListIsLoading.left()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.loadingUngroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is grouped and unsorted then grouped and sorted list should be received`() = runTest {
|
||||
// Given
|
||||
val expected = MockTokenLists.sortedGroupedTokenList.right()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.unsortedGroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is ungrouped and unsorted then ungrouped and sorted list should be received`() = runTest {
|
||||
// Given
|
||||
val expected = MockTokenLists.sortedUngroupedTokenList.right()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.unsortedUngroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is grouped and sorted then grouped and unsorted list should be received`() = runTest {
|
||||
// Given
|
||||
val expected = MockTokenLists.sortedGroupedTokenList.right()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.unsortedGroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when list is ungrouped and sorted then ungrouped and unsorted list should be received`() = runTest {
|
||||
// Given
|
||||
val expected = MockTokenLists.sortedUngroupedTokenList.right()
|
||||
|
||||
// When
|
||||
val actual = useCase(MockTokenLists.unsortedUngroupedTokenList)
|
||||
|
||||
// Then
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 {
|
||||
|
||||
|
|
@ -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,
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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(
|
||||
|
|
@ -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
|
||||
*
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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> {
|
||||
|
|
@ -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(),
|
||||
)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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> {
|
||||
|
||||
|
|
@ -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,
|
||||
)
|
||||
},
|
||||
)
|
||||
|
|
@ -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> {
|
||||
|
|
@ -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,
|
||||
|
|
@ -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>()
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +1,11 @@
|
|||
package com.tangem.feature.wallet.presentation.common
|
||||
|
||||
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 {
|
||||
|
|
@ -67,124 +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 actionsBottomSheet = ActionsBottomSheetConfig(
|
||||
actions = listOf(
|
||||
TokenActionButtonConfig(
|
||||
|
|
|
|||
|
|
@ -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()) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -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) }
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue