remove swipe action
This commit is contained in:
@@ -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) }
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user