Меню для сообщений
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -261,43 +261,62 @@ fun ChatDetailScreen(
|
|||||||
|
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
TopAppBar(
|
if (state.selectedMessageIds.isNotEmpty()) {
|
||||||
title = {
|
TopAppBar(
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
title = { Text("${state.selectedMessageIds.size}") },
|
||||||
AppAvatar(
|
navigationIcon = {
|
||||||
url = state.chatAvatar,
|
IconButton(onClick = { viewModel.clearSelection() }) {
|
||||||
name = state.chatName ?: chatName,
|
Icon(Icons.Default.Close, contentDescription = "Cancel")
|
||||||
size = 36.dp,
|
}
|
||||||
modifier = Modifier.padding(end = 8.dp)
|
},
|
||||||
)
|
actions = {
|
||||||
Column {
|
IconButton(onClick = { /* TODO: Forward */ }) {
|
||||||
Text(state.chatName ?: chatName, style = MaterialTheme.typography.titleMedium)
|
Icon(Icons.Default.Forward, contentDescription = "Forward")
|
||||||
if (state.isTyping) {
|
}
|
||||||
Text(
|
IconButton(onClick = { /* TODO: Delete */ }) {
|
||||||
stringResource(R.string.typing),
|
Icon(Icons.Default.Delete, contentDescription = "Delete")
|
||||||
style = MaterialTheme.typography.labelSmall,
|
}
|
||||||
color = MaterialTheme.colorScheme.primary
|
}
|
||||||
)
|
)
|
||||||
|
} else {
|
||||||
|
TopAppBar(
|
||||||
|
title = {
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
AppAvatar(
|
||||||
|
url = state.chatAvatar,
|
||||||
|
name = state.chatName ?: chatName,
|
||||||
|
size = 36.dp,
|
||||||
|
modifier = Modifier.padding(end = 8.dp)
|
||||||
|
)
|
||||||
|
Column {
|
||||||
|
Text(state.chatName ?: chatName, style = MaterialTheme.typography.titleMedium)
|
||||||
|
if (state.isTyping) {
|
||||||
|
Text(
|
||||||
|
stringResource(R.string.typing),
|
||||||
|
style = MaterialTheme.typography.labelSmall,
|
||||||
|
color = MaterialTheme.colorScheme.primary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
navigationIcon = {
|
||||||
|
IconButton(onClick = onBack) {
|
||||||
|
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(R.string.back))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
if (state.canCall) {
|
||||||
|
IconButton(onClick = { /* Вызов (WebRTC) */ }) {
|
||||||
|
Icon(Icons.Default.Call, contentDescription = stringResource(R.string.call))
|
||||||
|
}
|
||||||
|
IconButton(onClick = { /* Видеозвонок (WebRTC) */ }) {
|
||||||
|
Icon(Icons.Default.VideoCall, contentDescription = stringResource(R.string.video_call))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
)
|
||||||
navigationIcon = {
|
}
|
||||||
IconButton(onClick = onBack) {
|
|
||||||
Icon(Icons.Default.ArrowBack, contentDescription = stringResource(R.string.back))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
actions = {
|
|
||||||
if (state.canCall) {
|
|
||||||
IconButton(onClick = { /* Вызов (WebRTC) */ }) {
|
|
||||||
Icon(Icons.Default.Call, contentDescription = stringResource(R.string.call))
|
|
||||||
}
|
|
||||||
IconButton(onClick = { /* Видеозвонок (WebRTC) */ }) {
|
|
||||||
Icon(Icons.Default.VideoCall, contentDescription = stringResource(R.string.video_call))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
) { paddingValues ->
|
) { paddingValues ->
|
||||||
Column(
|
Column(
|
||||||
@@ -345,7 +364,6 @@ fun ChatDetailScreen(
|
|||||||
onVoiceFinished = { speed -> playNextVoiceMessage(item.message.id, speed) },
|
onVoiceFinished = { speed -> playNextVoiceMessage(item.message.id, speed) },
|
||||||
onReactionClick = { emoji -> viewModel.addReaction(item.message.id, emoji) },
|
onReactionClick = { emoji -> viewModel.addReaction(item.message.id, emoji) },
|
||||||
onReplyClick = { reply ->
|
onReplyClick = { reply ->
|
||||||
// TODO: Scroll to reply
|
|
||||||
val index = listItems.indexOfFirst {
|
val index = listItems.indexOfFirst {
|
||||||
it is MessageListItem.MessageItem && it.message.id == reply.id
|
it is MessageListItem.MessageItem && it.message.id == reply.id
|
||||||
}
|
}
|
||||||
@@ -353,6 +371,9 @@ fun ChatDetailScreen(
|
|||||||
scope.launch { listState.animateScrollToItem(index) }
|
scope.launch { listState.animateScrollToItem(index) }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
onSelect = { viewModel.toggleSelection(it) },
|
||||||
|
isSelected = state.selectedMessageIds.contains(item.message.id),
|
||||||
|
isSelectionMode = state.selectedMessageIds.isNotEmpty(),
|
||||||
onMediaClick = { clickedMedia ->
|
onMediaClick = { clickedMedia ->
|
||||||
val isMedia = clickedMedia.type.startsWith("image") ||
|
val isMedia = clickedMedia.type.startsWith("image") ||
|
||||||
clickedMedia.type.startsWith("video") ||
|
clickedMedia.type.startsWith("video") ||
|
||||||
@@ -491,11 +512,12 @@ fun ChatDetailScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Панель ввода
|
// Панель ввода
|
||||||
Column(
|
if (state.selectedMessageIds.isEmpty()) {
|
||||||
modifier = Modifier
|
Column(
|
||||||
.fillMaxWidth()
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f))
|
.fillMaxWidth()
|
||||||
) {
|
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f))
|
||||||
|
) {
|
||||||
// Pending Attachments Bar
|
// Pending Attachments Bar
|
||||||
if (state.pendingAttachments.isNotEmpty()) {
|
if (state.pendingAttachments.isNotEmpty()) {
|
||||||
Row(
|
Row(
|
||||||
@@ -895,6 +917,7 @@ fun ChatDetailScreen(
|
|||||||
viewModel.loadGifCategories()
|
viewModel.loadGifCategories()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ data class ChatDetailState(
|
|||||||
val isUploading: Boolean = false,
|
val isUploading: Boolean = false,
|
||||||
val isCompressionEnabled: Boolean = true,
|
val isCompressionEnabled: Boolean = true,
|
||||||
val inputText: String = "",
|
val inputText: String = "",
|
||||||
val replyingMessage: Message? = null
|
val replyingMessage: Message? = null,
|
||||||
|
val selectedMessageIds: Set<String> = emptySet()
|
||||||
)
|
)
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
@@ -312,6 +313,21 @@ class ChatDetailViewModel @Inject constructor(
|
|||||||
_state.update { it.copy(replyingMessage = message) }
|
_state.update { it.copy(replyingMessage = message) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun toggleSelection(messageId: String) {
|
||||||
|
_state.update { s ->
|
||||||
|
val newSelection = if (s.selectedMessageIds.contains(messageId)) {
|
||||||
|
s.selectedMessageIds - messageId
|
||||||
|
} else {
|
||||||
|
s.selectedMessageIds + messageId
|
||||||
|
}
|
||||||
|
s.copy(selectedMessageIds = newSelection)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearSelection() {
|
||||||
|
_state.update { it.copy(selectedMessageIds = emptySet()) }
|
||||||
|
}
|
||||||
|
|
||||||
fun cancelReply() {
|
fun cancelReply() {
|
||||||
_state.update { it.copy(replyingMessage = null) }
|
_state.update { it.copy(replyingMessage = null) }
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable
|
|||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.combinedClickable
|
import androidx.compose.foundation.combinedClickable
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.foundation.shape.CircleShape
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.CircularProgressIndicator
|
import androidx.compose.material3.CircularProgressIndicator
|
||||||
@@ -14,6 +15,9 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import ru.knot.messager.R
|
import ru.knot.messager.R
|
||||||
import androidx.compose.ui.unit.IntOffset
|
import androidx.compose.ui.unit.IntOffset
|
||||||
@@ -33,13 +37,15 @@ import chats.presentation.components.LinkPreview
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.*
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.foundation.shape.CircleShape
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import core.presentation.components.AppMediaLightbox
|
import androidx.compose.foundation.gestures.detectTapGestures
|
||||||
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.time.format.DateTimeFormatter
|
import java.time.format.DateTimeFormatter
|
||||||
|
import core.presentation.components.AppMediaLightbox
|
||||||
|
|
||||||
@OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class)
|
@OptIn(androidx.compose.foundation.ExperimentalFoundationApi::class)
|
||||||
@Composable
|
@Composable
|
||||||
@@ -51,19 +57,24 @@ fun MessageBubble(
|
|||||||
initialPlaybackSpeed: Float = 1.0f,
|
initialPlaybackSpeed: Float = 1.0f,
|
||||||
onVoiceFinished: (Float) -> Unit = {},
|
onVoiceFinished: (Float) -> Unit = {},
|
||||||
onReplyClick: (Message) -> Unit = {},
|
onReplyClick: (Message) -> Unit = {},
|
||||||
|
onSelect: (String) -> Unit = {},
|
||||||
|
isSelected: Boolean = false,
|
||||||
|
isSelectionMode: Boolean = false,
|
||||||
onMediaClick: (chats.domain.model.Media) -> Unit = {}
|
onMediaClick: (chats.domain.model.Media) -> Unit = {}
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var showReactionPicker by remember { mutableStateOf(false) }
|
var showReactionPicker by remember { mutableStateOf(false) }
|
||||||
var lightboxMediaIndex by remember { mutableStateOf<Int?>(null) }
|
var lightboxMediaIndex by remember { mutableStateOf<Int?>(null) }
|
||||||
|
|
||||||
val backgroundColor = if (isCurrentUser) {
|
|
||||||
Color(0xFF3096E5)
|
|
||||||
} else {
|
|
||||||
Color(0xFF212121)
|
|
||||||
}
|
|
||||||
|
|
||||||
val contentColor = Color.White
|
val contentColor = Color.White
|
||||||
|
var showContextMenu by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
val backgroundColor = when {
|
||||||
|
isSelected -> if (isCurrentUser) Color(0xFF3096E5).copy(alpha = 0.5f) else Color(0xFF212121).copy(alpha = 0.5f)
|
||||||
|
showContextMenu -> Color(0xFF007AFF) // Even brighter highlight
|
||||||
|
isCurrentUser -> Color(0xFF3096E5)
|
||||||
|
else -> Color(0xFF212121)
|
||||||
|
}
|
||||||
|
|
||||||
val shape = if (isCurrentUser) {
|
val shape = if (isCurrentUser) {
|
||||||
RoundedCornerShape(16.dp, 16.dp, 4.dp, 16.dp)
|
RoundedCornerShape(16.dp, 16.dp, 4.dp, 16.dp)
|
||||||
@@ -76,35 +87,93 @@ fun MessageBubble(
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
.pointerInput(Unit) { // Clicks anywhere in the Row row now work
|
||||||
|
detectTapGestures(
|
||||||
|
onTap = {
|
||||||
|
if (isSelectionMode) {
|
||||||
|
onSelect(message.id)
|
||||||
|
} else {
|
||||||
|
showContextMenu = !showContextMenu
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onLongPress = {
|
||||||
|
if (!isSelectionMode) {
|
||||||
|
onSelect(message.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
.padding(horizontal = 8.dp, vertical = 2.dp),
|
.padding(horizontal = 8.dp, vertical = 2.dp),
|
||||||
horizontalArrangement = if (isCurrentUser) Arrangement.End else Arrangement.Start,
|
horizontalArrangement = if (isCurrentUser) Arrangement.End else Arrangement.Start,
|
||||||
verticalAlignment = Alignment.Bottom
|
verticalAlignment = Alignment.Bottom
|
||||||
) {
|
) {
|
||||||
// No avatars here as per user request (web parity)
|
var bubbleHeight by remember { mutableIntStateOf(0) }
|
||||||
|
var isNearBottom by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Column(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.widthIn(max = 300.dp)
|
.widthIn(max = 300.dp)
|
||||||
.clip(shape)
|
.onGloballyPositioned { coords ->
|
||||||
.background(backgroundColor)
|
bubbleHeight = coords.size.height
|
||||||
.combinedClickable(
|
val screenHeight = coords.parentLayoutCoordinates?.size?.height ?: 2000
|
||||||
onClick = { /* Handle normal click */ },
|
val yInWindow = coords.localToWindow(Offset.Zero).y
|
||||||
onLongClick = { showReactionPicker = true }
|
// If bottom of message is in the last 40% of screen, open menu UP
|
||||||
)
|
isNearBottom = yInWindow > screenHeight * 0.6f
|
||||||
.padding(8.dp)
|
},
|
||||||
|
contentAlignment = if (isCurrentUser) Alignment.CenterEnd else Alignment.CenterStart
|
||||||
) {
|
) {
|
||||||
if (showReactionPicker) {
|
// Actual bubble
|
||||||
Popup(
|
Column(
|
||||||
alignment = Alignment.TopCenter,
|
modifier = Modifier
|
||||||
offset = IntOffset(0, -100),
|
.clip(shape)
|
||||||
onDismissRequest = { showReactionPicker = false }
|
.background(backgroundColor)
|
||||||
) {
|
.padding(8.dp)
|
||||||
ReactionPicker(onReactionSelected = {
|
) {
|
||||||
onReactionClick(it)
|
if (showContextMenu) {
|
||||||
showReactionPicker = false
|
val density = androidx.compose.ui.platform.LocalDensity.current
|
||||||
})
|
val menuWidth = 220.dp
|
||||||
|
val menuWidthPx = with(density) { menuWidth.toPx() }
|
||||||
|
|
||||||
|
Popup(
|
||||||
|
alignment = if (isNearBottom) {
|
||||||
|
if (isCurrentUser) Alignment.BottomStart else Alignment.BottomEnd
|
||||||
|
} else {
|
||||||
|
if (isCurrentUser) Alignment.TopStart else Alignment.TopEnd
|
||||||
|
},
|
||||||
|
offset = if (isCurrentUser) {
|
||||||
|
// Message is on RIGHT, show menu to its LEFT
|
||||||
|
IntOffset(-menuWidthPx.toInt() - 20, 0)
|
||||||
|
} else {
|
||||||
|
// Message is on LEFT, show menu to its RIGHT
|
||||||
|
IntOffset(menuWidthPx.toInt() + 20, 0)
|
||||||
|
},
|
||||||
|
onDismissRequest = { showContextMenu = false },
|
||||||
|
properties = androidx.compose.ui.window.PopupProperties(
|
||||||
|
focusable = true,
|
||||||
|
dismissOnBackPress = true,
|
||||||
|
dismissOnClickOutside = true
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
MessageContextMenu(
|
||||||
|
onReactionSelected = {
|
||||||
|
onReactionClick(it)
|
||||||
|
showContextMenu = false
|
||||||
|
},
|
||||||
|
onAction = { action ->
|
||||||
|
showContextMenu = false
|
||||||
|
when(action) {
|
||||||
|
"reply" -> onReplyClick(message)
|
||||||
|
"select" -> onSelect(message.id)
|
||||||
|
"copy" -> {
|
||||||
|
val clipboard = context.getSystemService(android.content.Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
|
||||||
|
val clip = android.content.ClipData.newPlainText("message", message.content)
|
||||||
|
clipboard.setPrimaryClip(clip)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Reply Info
|
// Reply Info
|
||||||
message.replyTo?.let { reply ->
|
message.replyTo?.let { reply ->
|
||||||
@@ -387,8 +456,22 @@ fun MessageBubble(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} // End of bubble Column
|
||||||
|
|
||||||
|
if (isSelected) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.CheckCircle,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = Color.White,
|
||||||
|
modifier = Modifier
|
||||||
|
.align(Alignment.Center)
|
||||||
|
.size(32.dp)
|
||||||
|
.background(Color(0xFF3096E5), CircleShape)
|
||||||
|
.padding(2.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
} // End of Box
|
||||||
|
} // End of Row
|
||||||
|
|
||||||
lightboxMediaIndex?.let { index ->
|
lightboxMediaIndex?.let { index ->
|
||||||
AppMediaLightbox(
|
AppMediaLightbox(
|
||||||
@@ -554,7 +637,89 @@ fun GridItem(media: Media, modifier: Modifier = Modifier) {
|
|||||||
color = Color.White.copy(alpha = 0.5f),
|
color = Color.White.copy(alpha = 0.5f),
|
||||||
fontSize = 8.sp
|
fontSize = 8.sp
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun MessageContextMenu(
|
||||||
|
onReactionSelected: (String) -> Unit,
|
||||||
|
onAction: (String) -> Unit
|
||||||
|
) {
|
||||||
|
androidx.compose.material3.Surface(
|
||||||
|
modifier = Modifier
|
||||||
|
.width(220.dp)
|
||||||
|
.clip(RoundedCornerShape(12.dp)),
|
||||||
|
color = Color(0xFF1E1E1E),
|
||||||
|
shadowElevation = 8.dp
|
||||||
|
) {
|
||||||
|
Column {
|
||||||
|
// Reactions
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(8.dp),
|
||||||
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
|
) {
|
||||||
|
listOf("👍", "❤️", "😂", "😮", "😢", "🔥").forEach { emoji ->
|
||||||
|
Text(
|
||||||
|
text = emoji,
|
||||||
|
fontSize = 24.sp,
|
||||||
|
modifier = Modifier
|
||||||
|
.clip(CircleShape)
|
||||||
|
.clickable { onReactionSelected(emoji) }
|
||||||
|
.padding(4.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Divider(color = Color.White.copy(alpha = 0.1f))
|
||||||
|
|
||||||
|
ContextMenuItem(Icons.Default.Reply, "Ответить", onClick = { onAction("reply") })
|
||||||
|
ContextMenuItem(Icons.Default.CheckCircle, "Выбрать", onClick = { onAction("select") })
|
||||||
|
ContextMenuItem(Icons.Default.Forward, "Переслать", onClick = { onAction("forward") })
|
||||||
|
ContextMenuItem(Icons.Default.PushPin, "Закрепить", onClick = { onAction("pin") })
|
||||||
|
ContextMenuItem(Icons.Default.ContentCopy, "Копировать", onClick = { onAction("copy") })
|
||||||
|
ContextMenuItem(Icons.Default.Edit, "Редактировать", onClick = { onAction("edit") })
|
||||||
|
|
||||||
|
Divider(color = Color.White.copy(alpha = 0.1f))
|
||||||
|
|
||||||
|
ContextMenuItem(
|
||||||
|
Icons.Default.Delete,
|
||||||
|
"Удалить",
|
||||||
|
textColor = Color(0xFFE53935),
|
||||||
|
onClick = { onAction("delete") }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ContextMenuItem(
|
||||||
|
icon: ImageVector,
|
||||||
|
text: String,
|
||||||
|
textColor: Color = Color.White,
|
||||||
|
onClick: () -> Unit
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(horizontal = 16.dp, vertical = 10.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
tint = textColor.copy(alpha = 0.7f),
|
||||||
|
modifier = Modifier.size(20.dp)
|
||||||
|
)
|
||||||
|
Spacer(modifier = Modifier.width(16.dp))
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
color = textColor,
|
||||||
|
style = MaterialTheme.typography.bodyMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user