Updated on 2026-08-14

This commit is contained in:
Tangem 2026-02-10 15:13:07 +04:00
parent 498e73d6df
commit 89fb66aa3e
8 changed files with 197 additions and 10 deletions

View file

@ -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)

View file

@ -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 = {},
),
),
)

View file

@ -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,
)

View file

@ -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")
}
}
}
}

View file

@ -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)