remove swipe action

This commit is contained in:
m
2026-09-12 20:58:27 +02:00
parent 7cc8c6eb24
commit 86006c285e
2 changed files with 10 additions and 125 deletions
@@ -1,14 +1,8 @@
package com.vaultdrop.mobile.ui.review
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.detectHorizontalDragGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
@@ -24,26 +18,16 @@ import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.key
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.vaultdrop.mobile.R
import com.vaultdrop.mobile.data.local.entity.FileEntity
import com.vaultdrop.mobile.ui.components.FileCategoryIcon
import com.vaultdrop.mobile.ui.document.content.DocumentContentViewer
import kotlinx.coroutines.launch
import java.time.Instant
import java.time.ZoneId
import java.time.format.DateTimeFormatter
@@ -51,34 +35,27 @@ import java.time.format.FormatStyle
import java.util.Locale
/**
* Pile de cartes « tinder » pour traiter les nouveaux documents.
*
* La carte du dessus affiche le contenu réel du document et glisse à
* gauche (supprimer) ou à droite (garder) dès qu'elle dépasse le seuil ;
* les cartes du dessous ne sont que des emplacements simplifiés (nom +
* catégorie), le contenu lourd n'étant chargé que pour la carte visible.
* Pile de cartes du mode review : la carte du dessus affiche le contenu réel
* du document ; les cartes du dessous ne sont que des emplacements simplifiés
* (nom + catégorie), le contenu lourd n'étant chargé que pour la carte
* visible. Les actions (garder/supprimer) sont pilotées par les boutons de
* l'écran, pas par le geste.
*/
@Composable
fun SwipeCardDeck(
cards: List<FileEntity>,
onKeep: (FileEntity) -> Unit,
onDelete: (FileEntity) -> Unit,
onOpenExternalFailed: () -> Unit,
modifier: Modifier = Modifier,
) {
BoxWithConstraints(modifier = modifier) {
val widthPx = with(LocalDensity.current) { maxWidth.toPx() }
Box(modifier = modifier) {
val visible = cards.take(MAX_STACK)
visible.asReversed().forEach { file ->
val depth = visible.indexOf(file)
key(file.resourceId) {
if (depth == 0) {
SwipeableTopCard(
TopCard(
file = file,
widthPx = widthPx,
onKeep = { onKeep(file) },
onDelete = { onDelete(file) },
onOpenExternalFailed = onOpenExternalFailed,
modifier = Modifier.fillMaxSize(),
)
@@ -94,57 +71,17 @@ fun SwipeCardDeck(
}
}
/** Carte du dessus : contenu réel du document + barre méta. */
@Composable
private fun SwipeableTopCard(
private fun TopCard(
file: FileEntity,
widthPx: Float,
onKeep: () -> Unit,
onDelete: () -> Unit,
onOpenExternalFailed: () -> Unit,
modifier: Modifier = Modifier,
) {
val offsetX = remember(file.resourceId) { Animatable(0f) }
val scope = rememberCoroutineScope()
val threshold = widthPx * SWIPE_THRESHOLD
Box(
modifier = modifier
.graphicsLayer {
translationX = offsetX.value
rotationZ = (offsetX.value / widthPx) * MAX_ROTATION_DEG
}
.clip(CardShape)
.background(MaterialTheme.colorScheme.surface)
.pointerInput(file.resourceId) {
detectHorizontalDragGestures(
onHorizontalDrag = { change, dragAmount ->
change.consume()
scope.launch {
offsetX.snapTo((offsetX.value + dragAmount).coerceIn(-widthPx, widthPx))
}
},
onDragEnd = {
scope.launch {
when {
offsetX.value >= threshold -> {
offsetX.animateTo(widthPx * 1.7f, tween(DRAG_OUT_MS))
onKeep()
}
offsetX.value <= -threshold -> {
offsetX.animateTo(-widthPx * 1.7f, tween(DRAG_OUT_MS))
onDelete()
}
else -> offsetX.animateTo(0f, spring(stiffness = Spring.StiffnessMediumLow))
}
}
},
onDragCancel = {
scope.launch { offsetX.animateTo(0f, spring(stiffness = Spring.StiffnessMediumLow)) }
},
)
},
.background(MaterialTheme.colorScheme.surface),
) {
DocumentContentViewer(
file = file,
@@ -158,26 +95,6 @@ private fun SwipeableTopCard(
.align(Alignment.BottomCenter)
.fillMaxWidth(),
)
val progress = (offsetX.value / threshold).coerceIn(-1f, 1f)
ReviewSwipeLabel(
text = stringResource(R.string.review_keep),
color = KeepColor,
visible = progress > 0f,
modifier = Modifier
.align(Alignment.CenterStart)
.padding(start = 16.dp, top = 72.dp)
.alpha(progress),
)
ReviewSwipeLabel(
text = stringResource(R.string.review_delete),
color = DeleteColor,
visible = progress < 0f,
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(end = 16.dp, top = 72.dp)
.alpha(-progress),
)
}
}
@@ -246,31 +163,6 @@ private fun ReviewCardMetaBar(file: FileEntity, modifier: Modifier = Modifier) {
}
}
/** Badge « GARDER » / « SUPPRIMER » affiché pendant le drag. */
@Composable
private fun ReviewSwipeLabel(
text: String,
color: Color,
visible: Boolean,
modifier: Modifier = Modifier,
) {
if (!visible) return
Surface(
modifier = modifier,
color = color,
shape = RoundedCornerShape(8.dp),
) {
Text(
text = text,
color = Color.White,
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
letterSpacing = 2.sp,
modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp),
)
}
}
private fun formatSize(bytes: Long): String {
val kb = 1024.0
val mb = kb * 1024
@@ -289,11 +181,6 @@ private fun formatDate(file: FileEntity): String {
}
private val CardShape = RoundedCornerShape(20.dp)
private val KeepColor = Color(0xFF2E7D32)
private val DeleteColor = Color(0xFFC62828)
private const val MAX_STACK = 3
private const val SWIPE_THRESHOLD = 0.3f
private const val MAX_ROTATION_DEG = 12f
private const val DRAG_OUT_MS = 220
private const val STACK_SHRINK = 0.045f
private const val STACK_OFFSET_DP = 18f
@@ -128,8 +128,6 @@ fun SwipeReviewScreen(
) {
SwipeCardDeck(
cards = state.cards,
onKeep = viewModel::keep,
onDelete = viewModel::delete,
onOpenExternalFailed = {
scope.launch { snackbarHostState.showSnackbar(openExternalErrorMsg) }
},