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:
@@ -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 = {},
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user