Ответы
This commit is contained in:
@@ -56,6 +56,11 @@ import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.rememberAsyncImagePainter
|
||||
|
||||
import chats.presentation.components.SwipeableMessageItem
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class, androidx.compose.ui.ExperimentalComposeUiApi::class, androidx.compose.foundation.ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun ChatDetailScreen(
|
||||
@@ -98,6 +103,14 @@ fun ChatDetailScreen(
|
||||
|
||||
var selectedMediaList by remember { mutableStateOf<List<chats.domain.model.Media>?>(null) }
|
||||
var initialMediaIndex by remember { mutableIntStateOf(0) }
|
||||
val focusRequester = remember { FocusRequester() }
|
||||
|
||||
// Автоматический фокус при ответе
|
||||
LaunchedEffect(state.replyingMessage) {
|
||||
if (state.replyingMessage != null) {
|
||||
focusRequester.requestFocus()
|
||||
}
|
||||
}
|
||||
|
||||
val playNextVoiceMessage = { currentId: String, speed: Float ->
|
||||
val currentIndex = state.messages.indexOfFirst { it.id == currentId }
|
||||
@@ -319,30 +332,45 @@ fun ChatDetailScreen(
|
||||
}) { item ->
|
||||
when(item) {
|
||||
is MessageListItem.MessageItem -> {
|
||||
MessageBubble(
|
||||
message = item.message,
|
||||
isCurrentUser = item.message.senderId == viewModel.getCurrentUserId(),
|
||||
autoPlay = item.message.id == autoPlayingMessageId,
|
||||
initialPlaybackSpeed = if (item.message.id == autoPlayingMessageId) currentPlaybackSpeed else 1.0f,
|
||||
onVoiceFinished = { speed -> playNextVoiceMessage(item.message.id, speed) },
|
||||
onReactionClick = { emoji -> viewModel.addReaction(item.message.id, emoji) },
|
||||
onMediaClick = { clickedMedia ->
|
||||
val isMedia = clickedMedia.type.startsWith("image") ||
|
||||
clickedMedia.type.startsWith("video") ||
|
||||
clickedMedia.type.contains("gif")
|
||||
|
||||
if (isMedia) {
|
||||
val initialIndex = allChatMedia.indexOfFirst { it.first.url == clickedMedia.url }
|
||||
selectedMediaList = allChatMedia.map { it.first }
|
||||
initialMediaIndex = if (initialIndex != -1) initialIndex else 0
|
||||
} else {
|
||||
try {
|
||||
val intent = android.content.Intent(android.content.Intent.ACTION_VIEW, android.net.Uri.parse(clickedMedia.url))
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) { }
|
||||
}
|
||||
SwipeableMessageItem(
|
||||
onReply = {
|
||||
viewModel.onReply(item.message)
|
||||
}
|
||||
)
|
||||
) {
|
||||
MessageBubble(
|
||||
message = item.message,
|
||||
isCurrentUser = item.message.senderId == viewModel.getCurrentUserId(),
|
||||
autoPlay = item.message.id == autoPlayingMessageId,
|
||||
initialPlaybackSpeed = if (item.message.id == autoPlayingMessageId) currentPlaybackSpeed else 1.0f,
|
||||
onVoiceFinished = { speed -> playNextVoiceMessage(item.message.id, speed) },
|
||||
onReactionClick = { emoji -> viewModel.addReaction(item.message.id, emoji) },
|
||||
onReplyClick = { reply ->
|
||||
// TODO: Scroll to reply
|
||||
val index = listItems.indexOfFirst {
|
||||
it is MessageListItem.MessageItem && it.message.id == reply.id
|
||||
}
|
||||
if (index != -1) {
|
||||
scope.launch { listState.animateScrollToItem(index) }
|
||||
}
|
||||
},
|
||||
onMediaClick = { clickedMedia ->
|
||||
val isMedia = clickedMedia.type.startsWith("image") ||
|
||||
clickedMedia.type.startsWith("video") ||
|
||||
clickedMedia.type.contains("gif")
|
||||
|
||||
if (isMedia) {
|
||||
val initialIndex = allChatMedia.indexOfFirst { it.first.url == clickedMedia.url }
|
||||
selectedMediaList = allChatMedia.map { it.first }
|
||||
initialMediaIndex = if (initialIndex != -1) initialIndex else 0
|
||||
} else {
|
||||
try {
|
||||
val intent = android.content.Intent(android.content.Intent.ACTION_VIEW, android.net.Uri.parse(clickedMedia.url))
|
||||
context.startActivity(intent)
|
||||
} catch (e: Exception) { }
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
is MessageListItem.DateHeader -> {
|
||||
Box(
|
||||
@@ -586,6 +614,51 @@ fun ChatDetailScreen(
|
||||
}
|
||||
}
|
||||
}
|
||||
// Reply Preview Bar
|
||||
state.replyingMessage?.let { replyMsg ->
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp, vertical = 4.dp)
|
||||
.height(IntrinsicSize.Min),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxHeight()
|
||||
.width(2.dp)
|
||||
.background(MaterialTheme.colorScheme.primary)
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Default.Reply,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
modifier = Modifier.padding(start = 8.dp).size(20.dp)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.padding(horizontal = 8.dp, vertical = 2.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Ответ " + (if (replyMsg.senderId == viewModel.getCurrentUserId()) "самом себе" else replyMsg.senderName),
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.primary,
|
||||
fontWeight = FontWeight.Bold
|
||||
)
|
||||
Text(
|
||||
text = replyMsg.content ?: if (replyMsg.media.isNotEmpty()) "Медиа" else "...",
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { viewModel.cancelReply() }) {
|
||||
Icon(Icons.Default.Close, contentDescription = "Cancel", modifier = Modifier.size(20.dp), tint = Color.Gray)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier
|
||||
@@ -628,11 +701,12 @@ fun ChatDetailScreen(
|
||||
)
|
||||
}
|
||||
|
||||
BasicTextField(
|
||||
BasicTextField(
|
||||
value = state.inputText,
|
||||
onValueChange = { viewModel.onInputTextChanged(it) },
|
||||
modifier = Modifier
|
||||
.weight(1f)
|
||||
.focusRequester(focusRequester)
|
||||
.onFocusChanged {
|
||||
if (it.isFocused) {
|
||||
isEmojiPickerVisible = false
|
||||
|
||||
Reference in New Issue
Block a user