feat(talk): меню сообщения — Копировать/Копировать ссылку/Переслать в «Личные заметки» (P1 talk.diff)

This commit is contained in:
b-mob-conf
2026-07-13 06:42:00 +00:00
parent 056e4dd8ba
commit 59543a7028
6 changed files with 76 additions and 5 deletions
@@ -804,6 +804,9 @@ fun TalkMessageActionSheet(
onEdit: (TalkMessage) -> Unit,
onDelete: (TalkMessage) -> Unit,
onReaction: (TalkMessage, String) -> Unit,
onCopy: (TalkMessage) -> Unit,
onCopyLink: (TalkMessage) -> Unit,
onForwardNoteToSelf: (TalkMessage) -> Unit,
) {
if (message == null) return
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
@@ -831,6 +834,14 @@ fun TalkMessageActionSheet(
F7TextButton(text = emoji, onClick = { onReaction(message, emoji) })
}
}
if (!message.isSystemMessage) {
F7TextButton(text = "Копировать", onClick = { onCopy(message) })
F7TextButton(text = "Копировать ссылку", onClick = { onCopyLink(message) })
F7TextButton(
text = "Переслать в «Личные заметки»",
onClick = { onForwardNoteToSelf(message) },
)
}
if (canModify) {
F7TextButton(text = "Изменить", onClick = { onEdit(message) })
F7TextButton(text = "Удалить", onClick = { onDelete(message) })
@@ -3,6 +3,7 @@ package ru.forbion.f7cloud.feature.talk
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.foundation.clickable
@@ -45,7 +46,9 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel
@@ -292,6 +295,7 @@ fun TalkScreen(
onReaction = { msg, emoji -> vm.addReaction(session, msg.id, emoji) },
onDeleteMessage = { vm.deleteMessage(session, it.id) },
onEditMessage = { msg, text -> vm.editMessage(session, msg.id, text) },
onForwardNoteToSelf = { vm.forwardToNoteToSelf(session, it) },
onMentionQuery = { vm.setMentionQuery(session, it) },
onMentionSelected = { mention ->
draft = insertMentionIntoDraft(draft, mention)
@@ -430,6 +434,7 @@ private fun ChatContent(
onReaction: (TalkMessage, String) -> Unit,
onDeleteMessage: (TalkMessage) -> Unit,
onEditMessage: (TalkMessage, String) -> Unit,
onForwardNoteToSelf: (TalkMessage) -> Unit,
onMentionQuery: (String?) -> Unit,
onMentionSelected: (TalkMention) -> Unit,
onClearReply: () -> Unit,
@@ -441,6 +446,8 @@ private fun ChatContent(
var editDraft by remember { mutableStateOf("") }
val listState = rememberLazyListState()
val scope = rememberCoroutineScope()
val context = LocalContext.current
val clipboard = LocalClipboardManager.current
val chatItems = remember(state.messages, state.openRoomLastReadMessageId, state.collapseSystemMessages) {
buildChatItems(
messages = state.messages,
@@ -507,6 +514,24 @@ private fun ChatContent(
onReaction(msg, emoji)
actionMessage = null
},
onCopy = { msg ->
clipboard.setText(AnnotatedString(msg.displayText))
Toast.makeText(context, "Скопировано", Toast.LENGTH_SHORT).show()
actionMessage = null
},
onCopyLink = { msg ->
val token = state.selectedRoomToken
if (token != null) {
clipboard.setText(AnnotatedString(TalkUrls.messageLink(session, token, msg.id)))
Toast.makeText(context, "Ссылка скопирована", Toast.LENGTH_SHORT).show()
}
actionMessage = null
},
onForwardNoteToSelf = { msg ->
onForwardNoteToSelf(msg)
Toast.makeText(context, "Переслано в «Личные заметки»", Toast.LENGTH_SHORT).show()
actionMessage = null
},
)
editingMessage?.let { message ->
@@ -19,4 +19,10 @@ object TalkUrls {
val base = session.serverUrl.trimEnd('/')
return "$base/index.php/core/preview?fileId=$fileId&x=$size&y=$size&a=1&mode=cover&forceIcon=0"
}
/** Веб-ссылка на сообщение (/call/<token>#message_<id>). */
fun messageLink(session: AuthSession, token: String, messageId: Long): String {
val base = session.serverUrl.trimEnd('/')
return "$base/index.php/call/$token#message_$messageId"
}
}
@@ -594,6 +594,28 @@ class TalkViewModel(
}
}
/** Переслать сообщение в «Личные заметки» (note-to-self, type 6). */
fun forwardToNoteToSelf(session: AuthSession, message: TalkMessage) {
val text = message.displayText.trim()
if (text.isEmpty()) return
val noteRoom = _state.value.rooms.find { it.type == ROOM_TYPE_NOTE_TO_SELF }
if (noteRoom == null) {
_state.value = _state.value.copy(
error = "«Личные заметки» недоступны",
)
return
}
viewModelScope.launch(Dispatchers.IO) {
runCatching { repository.sendMessage(session, noteRoom.token, text) }
.onFailure { t ->
_state.value = _state.value.copy(
error = t.message ?: "Не удалось переслать",
unauthorized = t is UnauthorizedException,
)
}
}
}
fun deleteMessage(session: AuthSession, messageId: Long) {
val token = _state.value.selectedRoomToken ?: return
viewModelScope.launch(Dispatchers.IO) {
@@ -749,5 +771,8 @@ class TalkViewModel(
companion object {
private const val BACKGROUND_POLL_DELAY_MS = 5_000L
private const val POLL_ERROR_DELAY_MS = 5_000L
/** spreed conversation type: note-to-self (Личные заметки). */
private const val ROOM_TYPE_NOTE_TO_SELF = 6
}
}