Ответы

This commit is contained in:
Халимов Рустам
2026-04-17 21:59:53 +03:00
parent 8c00d1376d
commit bef30c2c86
13 changed files with 227 additions and 33 deletions

View File

@@ -50,6 +50,7 @@ fun MessageBubble(
autoPlay: Boolean = false,
initialPlaybackSpeed: Float = 1.0f,
onVoiceFinished: (Float) -> Unit = {},
onReplyClick: (Message) -> Unit = {},
onMediaClick: (chats.domain.model.Media) -> Unit = {}
) {
val context = LocalContext.current
@@ -114,7 +115,7 @@ fun MessageBubble(
.clip(RoundedCornerShape(4.dp))
.background(contentColor.copy(alpha = 0.1f))
.height(IntrinsicSize.Min)
.clickable { /* Scroll to reply */ }
.clickable { onReplyClick(reply) }
) {
Box(
modifier = Modifier

View File

@@ -0,0 +1,97 @@
package chats.presentation.components
import androidx.compose.animation.core.Animatable
import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Reply
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.*
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.platform.LocalDensity
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch
import kotlin.math.roundToInt
@Composable
fun SwipeableMessageItem(
onReply: () -> Unit,
content: @Composable () -> Unit
) {
val density = LocalDensity.current
val scope = rememberCoroutineScope()
val offsetX = remember { Animatable(0f) }
val replyThreshold = with(density) { 60.dp.toPx() }
val maxDrag = with(density) { 90.dp.toPx() }
var isTriggered by remember { mutableStateOf(false) }
Box(
modifier = Modifier
.fillMaxWidth()
.draggable(
orientation = Orientation.Horizontal,
state = rememberDraggableState { delta ->
scope.launch {
// We only allow swiping to the left (negative delta)
val newOffset = (offsetX.value + delta).coerceIn(-maxDrag, 0f)
offsetX.snapTo(newOffset)
if (newOffset <= -replyThreshold && !isTriggered) {
isTriggered = true
// Trigger haptic feedback if available?
} else if (newOffset > -replyThreshold) {
isTriggered = false
}
}
},
onDragStopped = {
if (offsetX.value <= -replyThreshold) {
onReply()
}
scope.launch {
offsetX.animateTo(0f)
isTriggered = false
}
}
)
) {
// Reply Icon (Underneath)
Box(
modifier = Modifier
.align(Alignment.CenterEnd)
.padding(end = 16.dp)
.size(36.dp)
.alpha(((-offsetX.value) / replyThreshold).coerceIn(0f, 1f))
.clip(CircleShape)
.background(if (isTriggered) MaterialTheme.colorScheme.primary else Color.Gray.copy(alpha = 0.2f)),
contentAlignment = Alignment.Center
) {
Icon(
imageVector = Icons.Default.Reply,
contentDescription = "Reply",
tint = Color.White,
modifier = Modifier.size(20.dp)
)
}
// Message Content
Box(
modifier = Modifier
.offset { IntOffset(offsetX.value.roundToInt(), 0) }
.fillMaxWidth()
) {
content()
}
}
}