implement manual visibility control for DraggableActionPanel via isOpen and onDismiss props, and add a "more" options button to VocabularyCard to trigger the panel.

This commit is contained in:
jonasgaudian
2026-02-13 17:41:49 +01:00
parent 4a014e6206
commit 99d379071b
2 changed files with 104 additions and 67 deletions

View File

@@ -14,6 +14,7 @@ import androidx.compose.foundation.gestures.animateTo
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.offset
@@ -36,8 +37,8 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.runtime.snapshotFlow
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
@@ -51,7 +52,8 @@ import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import eu.gaudian.translator.R import eu.gaudian.translator.R
import eu.gaudian.translator.view.composable.AppIcons import eu.gaudian.translator.view.composable.AppIcons
import kotlinx.coroutines.launch import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlin.math.roundToInt import kotlin.math.roundToInt
private enum class DragState { Minimized, Extended } private enum class DragState { Minimized, Extended }
@@ -60,6 +62,8 @@ private enum class DragState { Minimized, Extended }
@Composable @Composable
internal fun DraggableActionPanel( internal fun DraggableActionPanel(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
isOpen: Boolean,
onDismiss: () -> Unit,
isEditing: Boolean, isEditing: Boolean,
onEditClick: () -> Unit, onEditClick: () -> Unit,
onSaveClick: () -> Unit, onSaveClick: () -> Unit,
@@ -71,7 +75,6 @@ internal fun DraggableActionPanel(
showAnalyzeGrammarButton: Boolean, showAnalyzeGrammarButton: Boolean,
onAnalyzeGrammarClick: () -> Unit, onAnalyzeGrammarClick: () -> Unit,
) { ) {
val coroutineScope = rememberCoroutineScope()
val density = LocalDensity.current val density = LocalDensity.current
val positionalThreshold = { totalDistance: Float -> totalDistance * 0.5f } val positionalThreshold = { totalDistance: Float -> totalDistance * 0.5f }
@@ -92,14 +95,13 @@ internal fun DraggableActionPanel(
} }
var panelWidth by remember { mutableFloatStateOf(0f) } var panelWidth by remember { mutableFloatStateOf(0f) }
val minimizedWidth = with(density) { 64.dp.toPx() }
val isExtended = state.targetValue == DragState.Extended val isExtended = state.targetValue == DragState.Extended
LaunchedEffect(panelWidth) { LaunchedEffect(panelWidth) {
if (panelWidth > 0) { if (panelWidth > 0) {
val anchors = DraggableAnchors { val anchors = DraggableAnchors {
DragState.Extended at 0f DragState.Extended at 0f
DragState.Minimized at panelWidth - minimizedWidth DragState.Minimized at panelWidth
} }
if (state.anchors != anchors) { if (state.anchors != anchors) {
state.updateAnchors(anchors) state.updateAnchors(anchors)
@@ -107,6 +109,24 @@ internal fun DraggableActionPanel(
} }
} }
LaunchedEffect(isOpen) {
if (isOpen) {
state.animateTo(DragState.Extended)
} else {
state.animateTo(DragState.Minimized)
}
}
LaunchedEffect(state) {
snapshotFlow { state.currentValue }
.distinctUntilChanged()
.collectLatest {
if (it == DragState.Minimized) {
onDismiss()
}
}
}
Card( Card(
shape = RoundedCornerShape(topStart = 20.dp, bottomStart = 20.dp), shape = RoundedCornerShape(topStart = 20.dp, bottomStart = 20.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 8.dp), elevation = CardDefaults.cardElevation(defaultElevation = 8.dp),
@@ -151,9 +171,7 @@ internal fun DraggableActionPanel(
val actionClickHandler: (() -> Unit) -> () -> Unit = { action -> val actionClickHandler: (() -> Unit) -> () -> Unit = { action ->
{ {
action() action()
coroutineScope.launch { onDismiss()
state.animateTo(DragState.Minimized)
}
} }
} }
@@ -220,6 +238,7 @@ private fun ActionItem(
Icon(icon, contentDescription = label) Icon(icon, contentDescription = label)
} }
if (isExtended) { if (isExtended) {
Spacer(modifier = Modifier.width(8.dp))
Text(text = label, style = MaterialTheme.typography.bodyMedium) Text(text = label, style = MaterialTheme.typography.bodyMedium)
} }
} }
@@ -229,6 +248,8 @@ private fun ActionItem(
@Composable @Composable
fun DraggableActionPanelPreview() { fun DraggableActionPanelPreview() {
DraggableActionPanel( DraggableActionPanel(
isOpen = true,
onDismiss = {},
isEditing = false, isEditing = false,
onEditClick = {}, onEditClick = {},
onSaveClick = {}, onSaveClick = {},

View File

@@ -130,6 +130,7 @@ fun VocabularyCard(
.collectAsState(initial = null) .collectAsState(initial = null)
var isEditing by remember(item.id) { mutableStateOf(false) } var isEditing by remember(item.id) { mutableStateOf(false) }
var showActionPanel by remember { mutableStateOf(false) }
LaunchedEffect(key1 = item.features, key2 = isEditing) { LaunchedEffect(key1 = item.features, key2 = isEditing) {
if (!isEditing) { if (!isEditing) {
@@ -237,13 +238,11 @@ fun VocabularyCard(
Box( Box(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
val panelVisible = isFlipped || !exerciseMode
val panelMargin = if (panelVisible) 64.dp else 0.dp
Card( Card(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(start = 0.dp, top = 0.dp, bottom = 0.dp, end = panelMargin) .padding(start = 0.dp, top = 0.dp, bottom = 0.dp, end = 0.dp)
.graphicsLayer { this.rotationY = rotationY }, .graphicsLayer { this.rotationY = rotationY },
shape = RoundedCornerShape(24.dp), shape = RoundedCornerShape(24.dp),
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp), elevation = CardDefaults.cardElevation(defaultElevation = 0.dp),
@@ -294,10 +293,25 @@ fun VocabularyCard(
showBottomSheet = true showBottomSheet = true
} }
) )
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 4.dp)
) {
HorizontalDivider( HorizontalDivider(
modifier = Modifier.padding(horizontal = 4.dp), modifier = Modifier.weight(1f),
color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f) color = MaterialTheme.colorScheme.outline.copy(alpha = 0.3f)
) )
if (!exerciseMode && !isFlipped) {
IconButton(onClick = { showActionPanel = true }) {
Icon(
imageVector = AppIcons.MoreVert,
contentDescription = stringResource(R.string.more),
tint = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
CardFace( CardFace(
modifier = backFaceModifier, modifier = backFaceModifier,
@@ -336,6 +350,8 @@ fun VocabularyCard(
modifier = Modifier modifier = Modifier
.align(Alignment.CenterEnd) .align(Alignment.CenterEnd)
.height((IntrinsicSize.Min)), .height((IntrinsicSize.Min)),
isOpen = showActionPanel,
onDismiss = { showActionPanel = false },
isEditing = isEditing, isEditing = isEditing,
onEditClick = { isEditing = true }, onEditClick = { isEditing = true },
onSaveClick = { handleSave() }, onSaveClick = { handleSave() },
@@ -712,7 +728,7 @@ private fun CardFace(
} }
// Info icon in bottom left // Info icon in bottom left
if (onMoreClick != null && !isEditing) { if (onMoreClick != null && !isEditing && (!isExerciseMode)) {
IconButton( IconButton(
onClick = onMoreClick, onClick = onMoreClick,
modifier = Modifier modifier = Modifier