Updated on 2026-08-14
This commit is contained in:
parent
498e73d6df
commit
89fb66aa3e
8 changed files with 197 additions and 10 deletions
|
|
@ -68,6 +68,7 @@ dependencies {
|
|||
implementation(deps.compose.material3)
|
||||
implementation(deps.compose.shimmer)
|
||||
implementation(deps.compose.coil)
|
||||
implementation(deps.compose.reorderableV2)
|
||||
|
||||
/* DI */
|
||||
implementation(deps.hilt.android)
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import com.tangem.core.ui.extensions.stringReference
|
|||
import com.tangem.core.ui.extensions.wrappedList
|
||||
import com.tangem.features.details.component.UserWalletListComponent
|
||||
import com.tangem.features.details.entity.UserWalletListUM
|
||||
import com.tangem.features.details.entity.WalletReorderUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.features.details.ui.UserWalletListBlock
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
|
@ -51,6 +52,11 @@ internal class PreviewUserWalletListComponent : UserWalletListComponent {
|
|||
addNewWalletText = resourceReference(R.string.user_wallet_list_add_button),
|
||||
isWalletSavingInProgress = true,
|
||||
onAddNewWalletClick = {},
|
||||
walletReorderUM = WalletReorderUM(
|
||||
isDragEnabled = true,
|
||||
onMove = { _, _ -> },
|
||||
onDragStopped = {},
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -11,4 +11,11 @@ internal data class UserWalletListUM(
|
|||
val isWalletSavingInProgress: Boolean,
|
||||
val addNewWalletText: TextReference,
|
||||
val onAddNewWalletClick: () -> Unit,
|
||||
val walletReorderUM: WalletReorderUM,
|
||||
)
|
||||
|
||||
internal data class WalletReorderUM(
|
||||
val isDragEnabled: Boolean,
|
||||
val onMove: (fromIndex: Int, toIndex: Int) -> Unit,
|
||||
val onDragStopped: () -> Unit,
|
||||
)
|
||||
|
|
@ -13,15 +13,19 @@ import com.tangem.core.decompose.ui.UiMessageSender
|
|||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.usecase.ApplyUserWalletListSortingUseCase
|
||||
import com.tangem.domain.wallets.usecase.UnlockWalletUseCase
|
||||
import com.tangem.features.details.entity.UserWalletListUM
|
||||
import com.tangem.features.details.entity.WalletReorderUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import com.tangem.features.details.utils.UserWalletSaver
|
||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||
import com.tangem.features.wallet.featuretoggles.WalletFeatureToggles
|
||||
import com.tangem.features.wallet.utils.UserWalletsFetcher
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
|
@ -38,6 +42,8 @@ internal class UserWalletListModel @Inject constructor(
|
|||
private val hotWalletFeatureToggles: HotWalletFeatureToggles,
|
||||
private val unlockWalletUseCase: UnlockWalletUseCase,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val walletFeatureToggles: WalletFeatureToggles,
|
||||
private val applyUserWalletListSortingUseCase: ApplyUserWalletListSortingUseCase,
|
||||
) : Model() {
|
||||
|
||||
private val isWalletSavingInProgress: MutableStateFlow<Boolean> = MutableStateFlow(value = false)
|
||||
|
|
@ -55,6 +61,11 @@ internal class UserWalletListModel @Inject constructor(
|
|||
isWalletSavingInProgress = false,
|
||||
addNewWalletText = TextReference.EMPTY,
|
||||
onAddNewWalletClick = ::onAddNewWalletClick,
|
||||
walletReorderUM = WalletReorderUM(
|
||||
isDragEnabled = false,
|
||||
onMove = ::onWalletReorder,
|
||||
onDragStopped = ::onWalletDragStopped,
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -78,6 +89,11 @@ internal class UserWalletListModel @Inject constructor(
|
|||
userWallets = userWallets,
|
||||
isWalletSavingInProgress = isWalletSavingInProgress,
|
||||
addNewWalletText = resourceReference(R.string.user_wallet_list_add_button),
|
||||
walletReorderUM = WalletReorderUM(
|
||||
isDragEnabled = walletFeatureToggles.isWalletReorderFeatureEnabled && userWallets.size > 1,
|
||||
onMove = ::onWalletReorder,
|
||||
onDragStopped = ::onWalletDragStopped,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -109,4 +125,23 @@ internal class UserWalletListModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onWalletReorder(fromIndex: Int, toIndex: Int) {
|
||||
state.update { prevState ->
|
||||
val wallets = prevState.userWallets.toMutableList()
|
||||
wallets.add(toIndex, wallets.removeAt(fromIndex))
|
||||
prevState.copy(userWallets = wallets.toPersistentList())
|
||||
}
|
||||
}
|
||||
|
||||
private fun onWalletDragStopped() {
|
||||
val userWalletIds = state.value.userWallets.map { UserWalletId(it.id) }
|
||||
|
||||
modelScope.launch {
|
||||
applyUserWalletListSortingUseCase(userWalletIds)
|
||||
.onLeft { error ->
|
||||
Timber.e("Failed to apply wallet list sorting: $error")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,17 +2,25 @@ package com.tangem.features.details.ui
|
|||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyItemScope
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.vectorResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
|
|
@ -20,6 +28,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameter
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.tangem.common.ui.userwallet.UserWalletItem
|
||||
import com.tangem.common.ui.userwallet.state.UserWalletItemUM
|
||||
import com.tangem.core.ui.components.block.BlockCard
|
||||
import com.tangem.core.ui.extensions.TextReference
|
||||
import com.tangem.core.ui.extensions.resolveReference
|
||||
|
|
@ -28,26 +37,84 @@ import com.tangem.core.ui.res.TangemThemePreview
|
|||
import com.tangem.features.details.component.UserWalletListComponent
|
||||
import com.tangem.features.details.component.preview.PreviewUserWalletListComponent
|
||||
import com.tangem.features.details.entity.UserWalletListUM
|
||||
import com.tangem.features.details.entity.WalletReorderUM
|
||||
import com.tangem.features.details.impl.R
|
||||
import sh.calvin.reorderable.ReorderableItem
|
||||
import sh.calvin.reorderable.ReorderableLazyListState
|
||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||
|
||||
@Composable
|
||||
internal fun UserWalletListBlock(state: UserWalletListUM, modifier: Modifier = Modifier) {
|
||||
val listState = rememberLazyListState()
|
||||
val reorderableListState = rememberReorderableLazyListState(
|
||||
lazyListState = listState,
|
||||
onMove = { from, to -> state.walletReorderUM.onMove(from.index, to.index) },
|
||||
)
|
||||
|
||||
val listHeight = WALLET_ITEM_HEIGHT * state.userWallets.size + ADD_WALLET_BUTTON_HEIGHT
|
||||
|
||||
BlockCard(
|
||||
modifier = modifier,
|
||||
) {
|
||||
state.userWallets.forEach { state ->
|
||||
key(state.id) {
|
||||
UserWalletItem(
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.heightIn(max = listHeight),
|
||||
) {
|
||||
items(
|
||||
items = state.userWallets,
|
||||
key = { it.id },
|
||||
) { walletState ->
|
||||
WalletItem(
|
||||
model = walletState,
|
||||
reorderableListState = reorderableListState,
|
||||
walletReorderUM = state.walletReorderUM,
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
state = state,
|
||||
)
|
||||
}
|
||||
item(key = "add_wallet_button") {
|
||||
AddWalletButton(
|
||||
text = state.addNewWalletText,
|
||||
isInProgress = state.isWalletSavingInProgress,
|
||||
onClick = state.onAddNewWalletClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
AddWalletButton(
|
||||
text = state.addNewWalletText,
|
||||
isInProgress = state.isWalletSavingInProgress,
|
||||
onClick = state.onAddNewWalletClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
@Composable
|
||||
private fun LazyItemScope.WalletItem(
|
||||
model: UserWalletItemUM,
|
||||
reorderableListState: ReorderableLazyListState,
|
||||
walletReorderUM: WalletReorderUM,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
ReorderableItem(
|
||||
state = reorderableListState,
|
||||
key = model.id,
|
||||
modifier = modifier,
|
||||
) { isDragging ->
|
||||
val elevation by animateDpAsState(if (isDragging) 9.dp else 0.dp)
|
||||
val scale by animateFloatAsState(if (isDragging) 1.04f else 1f)
|
||||
val shapeRadius by animateDpAsState(if (isDragging) 16.dp else 0.dp)
|
||||
|
||||
Surface(
|
||||
shape = RoundedCornerShape(shapeRadius),
|
||||
shadowElevation = elevation,
|
||||
modifier = Modifier.scale(scale),
|
||||
) {
|
||||
UserWalletItem(
|
||||
state = model,
|
||||
modifier = Modifier
|
||||
.longPressDraggableHandle(
|
||||
enabled = walletReorderUM.isDragEnabled,
|
||||
onDragStopped = walletReorderUM.onDragStopped,
|
||||
)
|
||||
.background(color = TangemTheme.colors.background.primary),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -109,6 +176,9 @@ private fun AddWalletButton(
|
|||
}
|
||||
}
|
||||
|
||||
private val WALLET_ITEM_HEIGHT = 72.dp
|
||||
private val ADD_WALLET_BUTTON_HEIGHT = 60.dp
|
||||
|
||||
// region Preview
|
||||
@Composable
|
||||
@Preview(showBackground = true, widthDp = 360)
|
||||
|
|
|
|||
|
|
@ -487,6 +487,7 @@ internal class WalletModel @Inject constructor(
|
|||
is WalletsUpdateActionResolver.Action.ReloadWallets -> {
|
||||
reloadWarnings(action)
|
||||
}
|
||||
is WalletsUpdateActionResolver.Action.ReorderWallets -> reorderWallets(action)
|
||||
WalletsUpdateActionResolver.Action.EmptyWallets -> {
|
||||
Timber.w("Wallets list is empty!")
|
||||
}
|
||||
|
|
@ -496,6 +497,25 @@ internal class WalletModel @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun reorderWallets(action: WalletsUpdateActionResolver.Action.ReorderWallets) {
|
||||
val currentWalletId = stateHolder.getSelectedWalletId()
|
||||
val currentIndex = stateHolder.getWalletIndexByWalletId(userWalletId = currentWalletId)
|
||||
|
||||
stateHolder.update(
|
||||
transformer = ReorderWalletsTransformer(
|
||||
wallets = action.wallets,
|
||||
),
|
||||
)
|
||||
|
||||
val newIndex = stateHolder.getWalletIndexByWalletId(userWalletId = currentWalletId)
|
||||
|
||||
if (currentIndex != null && newIndex != null && currentIndex != newIndex) {
|
||||
scrollToWallet(prevIndex = currentIndex, newIndex = newIndex) {
|
||||
stateHolder.update { it.copy(selectedWalletIndex = newIndex) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun reloadWarnings(action: WalletsUpdateActionResolver.Action.ReloadWallets) {
|
||||
action.wallets.forEach {
|
||||
walletScreenContentLoader.load(
|
||||
|
|
|
|||
|
|
@ -82,6 +82,9 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
isWalletsCountChanged(state, wallets) -> {
|
||||
getChangeWalletsListAction(state, wallets, selectedWallet)
|
||||
}
|
||||
isWalletsOrderChanged(state, wallets) -> {
|
||||
Action.ReorderWallets(wallets = wallets)
|
||||
}
|
||||
isAnotherWalletSelected(state, selectedWallet) -> {
|
||||
Action.ReinitializeNewWallet(
|
||||
prevWalletId = state.getPrevSelectedWallet().id,
|
||||
|
|
@ -121,6 +124,13 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
return prevWalletsSize != walletsSize
|
||||
}
|
||||
|
||||
private fun isWalletsOrderChanged(state: WalletScreenState, wallets: List<UserWallet>): Boolean {
|
||||
val prevWalletIds = state.wallets.map { it.walletCardState.id }
|
||||
val newWalletIds = wallets.map { it.walletId }
|
||||
|
||||
return prevWalletIds != newWalletIds
|
||||
}
|
||||
|
||||
private fun getChangeWalletsListAction(
|
||||
state: WalletScreenState,
|
||||
wallets: List<UserWallet>,
|
||||
|
|
@ -394,6 +404,15 @@ internal class WalletsUpdateActionResolver @Inject constructor(
|
|||
val wallets: List<UserWallet>,
|
||||
) : Action()
|
||||
|
||||
data class ReorderWallets(
|
||||
val wallets: List<UserWallet>,
|
||||
) : Action() {
|
||||
|
||||
override fun toString(): String {
|
||||
return "ReorderWallets(wallets = ${wallets.joinToString { it.walletId.toString() }})"
|
||||
}
|
||||
}
|
||||
|
||||
data object EmptyWallets : Action()
|
||||
|
||||
data object Unknown : Action()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,29 @@
|
|||
package com.tangem.feature.wallet.presentation.wallet.state.transformers
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.feature.wallet.presentation.wallet.state.model.WalletScreenState
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
||||
/**
|
||||
* Transformer that reorders wallets according to the new order
|
||||
*
|
||||
* @property wallets wallets in the new order
|
||||
*/
|
||||
internal class ReorderWalletsTransformer(
|
||||
private val wallets: List<UserWallet>,
|
||||
) : WalletScreenStateTransformer {
|
||||
|
||||
override fun transform(prevState: WalletScreenState): WalletScreenState {
|
||||
val walletIdToIndex = wallets.withIndex().associate { it.value.walletId to it.index }
|
||||
|
||||
val reorderedWallets = prevState.wallets
|
||||
.sortedBy { walletIdToIndex[it.walletCardState.id] ?: Int.MAX_VALUE }
|
||||
.toImmutableList()
|
||||
|
||||
return if (reorderedWallets != prevState.wallets) {
|
||||
prevState.copy(wallets = reorderedWallets)
|
||||
} else {
|
||||
prevState
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue