1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-3044 Chats | Enhancement | Basics for chat replies (#1854)

This commit is contained in:
Evgenii Kozlov 2024-11-30 16:22:03 +01:00 committed by GitHub
parent 81394a3589
commit 3994d456d5
Signed by: github
GPG key ID: B5690EEEBB952194
18 changed files with 352 additions and 43 deletions

View file

@ -594,7 +594,7 @@ sealed class Command {
sealed class ChatCommand {
data class AddMessage(
val chat: Id,
val message: Chat.Message
val message: Chat.Message,
) : ChatCommand()
data class DeleteMessage(
@ -613,6 +613,11 @@ sealed class Command {
val limit: Int
) : ChatCommand()
data class GetMessagesByIds(
val chat: Id,
val messages: List<Id>
) : ChatCommand()
data class SubscribeLastMessages(
val chat: Id,
val limit: Int

View file

@ -41,16 +41,17 @@ sealed class Chat {
*/
fun new(
text: String,
attachments: List<Attachment> = emptyList()
) : Message = Chat.Message(
attachments: List<Attachment> = emptyList(),
replyToMessageId: Id? = null
) : Message = Message(
id = "",
createdAt = 0L,
modifiedAt = 0L,
attachments = attachments,
reactions = emptyMap(),
creator = "",
replyToMessageId = "",
content = Chat.Message.Content(
replyToMessageId = replyToMessageId,
content = Content(
text = text,
marks = emptyList(),
style = Block.Content.Text.Style.P

View file

@ -1067,6 +1067,10 @@ class BlockDataRepository(
return remote.getChatMessages(command)
}
override suspend fun getChatMessagesByIds(command: Command.ChatCommand.GetMessagesByIds): List<Chat.Message> {
return remote.getChatMessagesByIds(command)
}
override suspend fun subscribeLastChatMessages(
command: Command.ChatCommand.SubscribeLastMessages
): Command.ChatCommand.SubscribeLastMessages.Response {

View file

@ -452,6 +452,7 @@ interface BlockRemote {
suspend fun editChatMessage(command: Command.ChatCommand.EditMessage)
suspend fun deleteChatMessage(command: Command.ChatCommand.DeleteMessage)
suspend fun getChatMessages(command: Command.ChatCommand.GetMessages): List<Chat.Message>
suspend fun getChatMessagesByIds(command: Command.ChatCommand.GetMessagesByIds): List<Chat.Message>
suspend fun subscribeLastChatMessages(command: Command.ChatCommand.SubscribeLastMessages): Command.ChatCommand.SubscribeLastMessages.Response
suspend fun toggleChatMessageReaction(command: Command.ChatCommand.ToggleMessageReaction)
suspend fun unsubscribeChat(chat: Id)

View file

@ -495,6 +495,7 @@ interface BlockRepository {
suspend fun editChatMessage(command: Command.ChatCommand.EditMessage)
suspend fun deleteChatMessage(command: Command.ChatCommand.DeleteMessage)
suspend fun getChatMessages(command: Command.ChatCommand.GetMessages): List<Chat.Message>
suspend fun getChatMessagesByIds(command: Command.ChatCommand.GetMessagesByIds): List<Chat.Message>
suspend fun subscribeLastChatMessages(command: Command.ChatCommand.SubscribeLastMessages): Command.ChatCommand.SubscribeLastMessages.Response
suspend fun toggleChatMessageReaction(command: Command.ChatCommand.ToggleMessageReaction)
suspend fun unsubscribeChat(chat: Id)

View file

@ -33,6 +33,7 @@ class ChatContainer @Inject constructor(
private val payloads = MutableSharedFlow<List<Event.Command.Chats>>()
private val attachments = MutableSharedFlow<Set<Id>>(replay = 0)
private val replies = MutableSharedFlow<Set<Id>>(replay = 0)
@Deprecated("Naive implementation. Add caching logic - maybe store for wrappers")
fun fetchAttachments(space: Space) : Flow<Map<Id, ObjectWrapper.Basic>> {
@ -64,18 +65,39 @@ class ChatContainer @Inject constructor(
.map { wrappers -> wrappers.associate { it.id to it } }
}
@Deprecated("Naive implementation. Add caching logic")
fun fetchReplies(chat: Id) : Flow<Map<Id, Chat.Message>> {
return replies
.distinctUntilChanged()
.map { ids ->
if (ids.isNotEmpty()) {
repo.getChatMessagesByIds(
command = Command.ChatCommand.GetMessagesByIds(
chat = chat,
messages = ids.toList()
)
)
} else {
emptyList()
}
}
.distinctUntilChanged()
.map { messages -> messages.associate { it.id to it } }
}
fun watchWhileTrackingAttachments(chat: Id): Flow<List<Chat.Message>> {
return watch(chat)
.onEach { messages ->
val ids = messages
.map { msg ->
msg.attachments.map {
it.target
}
val repliesIds = mutableSetOf<Id>()
val attachmentsIds = mutableSetOf<Id>()
messages.forEach { msg ->
attachmentsIds.addAll(msg.attachments.map { it.target })
if (!msg.replyToMessageId.isNullOrEmpty()) {
repliesIds.add(msg.replyToMessageId.orEmpty())
}
.flatten()
.toSet()
attachments.emit(ids)
}
attachments.emit(attachmentsIds)
replies.emit(repliesIds)
}
}

View file

@ -0,0 +1,18 @@
package com.anytypeio.anytype.domain.chats
import com.anytypeio.anytype.core_models.Command
import com.anytypeio.anytype.core_models.chats.Chat
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.ResultInteractor
import com.anytypeio.anytype.domain.block.repo.BlockRepository
import javax.inject.Inject
class GetChatMessagesByIds @Inject constructor(
private val repo: BlockRepository,
dispatchers: AppCoroutineDispatchers
) : ResultInteractor<Command.ChatCommand.GetMessagesByIds, List<Chat.Message>>(dispatchers.io) {
override suspend fun doWork(params: Command.ChatCommand.GetMessagesByIds): List<Chat.Message> {
return repo.getChatMessagesByIds(params)
}
}

View file

@ -16,7 +16,8 @@ sealed interface DiscussionView {
val reactions: List<Reaction> = emptyList(),
val isUserAuthor: Boolean = false,
val isEdited: Boolean = false,
val avatar: Avatar = Avatar.Initials()
val avatar: Avatar = Avatar.Initials(),
val reply: Reply? = null
) : DiscussionView {
data class Content(val msg: String, val parts: List<Part>) {
@ -32,6 +33,12 @@ sealed interface DiscussionView {
}
}
data class Reply(
val msg: Id,
val text: String,
val author: String
)
sealed class Attachment {
data class Image(
val target: Id,

View file

@ -11,6 +11,7 @@ import com.anytypeio.anytype.core_models.primitives.Space
import com.anytypeio.anytype.core_ui.text.splitByMarks
import com.anytypeio.anytype.core_utils.ext.withLatestFrom
import com.anytypeio.anytype.domain.auth.interactor.GetAccount
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.base.fold
import com.anytypeio.anytype.domain.base.onFailure
import com.anytypeio.anytype.domain.base.onSuccess
@ -33,6 +34,7 @@ import javax.inject.Inject
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import timber.log.Timber
@ -48,7 +50,8 @@ class DiscussionViewModel @Inject constructor(
private val members: ActiveSpaceMemberSubscriptionContainer,
private val getAccount: GetAccount,
private val urlBuilder: UrlBuilder,
private val spaceViews: SpaceViewSubscriptionContainer
private val spaceViews: SpaceViewSubscriptionContainer,
private val dispatchers: AppCoroutineDispatchers
) : BaseViewModel() {
val name = MutableStateFlow<String?>(null)
@ -102,15 +105,20 @@ class DiscussionViewModel @Inject constructor(
}
}
// TODO move to IO thread.
private suspend fun proceedWithObservingChatMessages(
account: Id,
chat: Id
) {
chatContainer
.watchWhileTrackingAttachments(chat = chat)
.withLatestFrom(chatContainer.fetchAttachments(vmParams.space)) { result, dependencies ->
.withLatestFrom(
chatContainer.fetchAttachments(vmParams.space),
chatContainer.fetchReplies(chat = chat)
) { result, dependencies, replies ->
result.map { msg ->
val member = members.get().let { type ->
val allMembers = members.get()
val member = allMembers.let { type ->
when (type) {
is Store.Data -> type.members.find { member ->
member.identity == msg.creator
@ -121,6 +129,30 @@ class DiscussionViewModel @Inject constructor(
val content = msg.content
val replyToId = msg.replyToMessageId
val reply = if (replyToId.isNullOrEmpty()) {
null
} else {
val msg = replies[replyToId]
if (msg != null) {
DiscussionView.Message.Reply(
msg = msg.id,
text = msg.content?.text.orEmpty(),
author = allMembers.let { type ->
when (type) {
is Store.Data -> type.members.find { member ->
member.identity == msg.creator
}?.name.orEmpty()
is Store.Empty -> ""
}
}
)
} else {
null
}
}
DiscussionView.Message(
id = msg.id,
timestamp = msg.createdAt * 1000,
@ -136,6 +168,7 @@ class DiscussionViewModel @Inject constructor(
)
}
),
reply = reply,
author = member?.name ?: msg.creator.takeLast(5),
isUserAuthor = msg.creator == account,
isEdited = msg.modifiedAt > msg.createdAt,
@ -182,6 +215,7 @@ class DiscussionViewModel @Inject constructor(
)
}.reversed()
}
// .flowOn(dispatchers.io)
.collect { result ->
messages.value = result
}
@ -215,7 +249,6 @@ class DiscussionViewModel @Inject constructor(
Timber.e(it, "Error while adding message")
}
}
is ChatBoxMode.EditMessage -> {
editChatMessage.async(
params = Command.ChatCommand.EditMessage(
@ -234,6 +267,31 @@ class DiscussionViewModel @Inject constructor(
chatBoxMode.value = ChatBoxMode.Default
}
}
is ChatBoxMode.Reply -> {
addChatMessage.async(
params = Command.ChatCommand.AddMessage(
chat = chat,
message = Chat.Message.new(
text = msg,
replyToMessageId = mode.msg,
attachments = attachments.value.map { a ->
Chat.Message.Attachment(
target = a.id,
type = Chat.Message.Attachment.Type.Link
)
}
)
)
).onSuccess { (id, payload) ->
attachments.value = emptyList()
chatContainer.onPayload(payload)
delay(JUMP_TO_BOTTOM_DELAY)
commands.emit(UXCommand.JumpToBottom)
}.onFailure {
Timber.e(it, "Error while adding message")
}
chatBoxMode.value = ChatBoxMode.Default
}
}
}
}
@ -272,6 +330,12 @@ class DiscussionViewModel @Inject constructor(
attachments.value = emptyList()
}
fun onClearReplyClicked() {
viewModelScope.launch {
chatBoxMode.value = ChatBoxMode.Default
}
}
fun onReacted(msg: Id, reaction: String) {
Timber.d("onReacted")
viewModelScope.launch {
@ -292,6 +356,16 @@ class DiscussionViewModel @Inject constructor(
}
}
fun onReplyMessage(msg: DiscussionView.Message) {
viewModelScope.launch {
chatBoxMode.value = ChatBoxMode.Reply(
msg = msg.id,
text = msg.content.msg,
author = msg.author
)
}
}
fun onDeleteMessage(msg: DiscussionView.Message) {
Timber.d("onDeleteMessageClicked")
viewModelScope.launch {
@ -339,6 +413,11 @@ class DiscussionViewModel @Inject constructor(
sealed class ChatBoxMode {
data object Default : ChatBoxMode()
data class EditMessage(val msg: Id) : ChatBoxMode()
data class Reply(
val msg: Id,
val text: String,
val author: String
): ChatBoxMode()
}
sealed class Params {

View file

@ -3,6 +3,7 @@ package com.anytypeio.anytype.feature_discussions.presentation
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.anytypeio.anytype.domain.auth.interactor.GetAccount
import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers
import com.anytypeio.anytype.domain.chats.AddChatMessage
import com.anytypeio.anytype.domain.chats.ChatContainer
import com.anytypeio.anytype.domain.chats.DeleteChatMessage
@ -28,7 +29,8 @@ class DiscussionViewModelFactory @Inject constructor(
private val members: ActiveSpaceMemberSubscriptionContainer,
private val getAccount: GetAccount,
private val urlBuilder: UrlBuilder,
private val spaceViews: SpaceViewSubscriptionContainer
private val spaceViews: SpaceViewSubscriptionContainer,
private val dispatchers: AppCoroutineDispatchers
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T = DiscussionViewModel(
@ -43,6 +45,7 @@ class DiscussionViewModelFactory @Inject constructor(
deleteChatMessage = deleteChatMessage,
urlBuilder = urlBuilder,
editChatMessage = editChatMessage,
spaceViews = spaceViews
spaceViews = spaceViews,
dispatchers = dispatchers
) as T
}

View file

@ -8,6 +8,7 @@ import androidx.compose.ui.tooling.preview.Preview
import com.anytypeio.anytype.core_models.chats.Chat
import com.anytypeio.anytype.feature_discussions.R
import com.anytypeio.anytype.feature_discussions.presentation.DiscussionView
import com.anytypeio.anytype.feature_discussions.presentation.DiscussionViewModel
import kotlin.time.DurationUnit
import kotlin.time.toDuration
@ -66,9 +67,8 @@ fun DiscussionPreview() {
onCopyMessage = {},
onAttachmentClicked = {},
onEditMessage = {},
onMarkupLinkClicked = {
}
onMarkupLinkClicked = {},
onReplyMessage = {}
)
}
@ -118,7 +118,10 @@ fun DiscussionScreenPreview() {
onAttachFileClicked = {},
onUploadAttachmentClicked = {},
onAttachMediaClicked = {},
onAttachObjectClicked = {}
onAttachObjectClicked = {},
onReplyMessage = {},
chatBoxMode = DiscussionViewModel.ChatBoxMode.Default,
onClearReplyClicked = {}
)
}
@ -142,7 +145,8 @@ fun BubblePreview() {
onCopyMessage = {},
onAttachmentClicked = {},
onEditMessage = {},
onMarkupLinkClicked = {}
onMarkupLinkClicked = {},
onReply = {}
)
}
@ -167,7 +171,8 @@ fun BubbleEditedPreview() {
onCopyMessage = {},
onAttachmentClicked = {},
onEditMessage = {},
onMarkupLinkClicked = {}
onMarkupLinkClicked = {},
onReply = {}
)
}
@ -199,6 +204,7 @@ fun BubbleWithAttachmentPreview() {
},
onAttachmentClicked = {},
onEditMessage = {},
onMarkupLinkClicked = {}
onMarkupLinkClicked = {},
onReply = {}
)
}

View file

@ -93,10 +93,8 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter
import coil.request.ImageRequest
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.chats.Chat
import com.anytypeio.anytype.core_ui.foundation.AlertConfig
import com.anytypeio.anytype.core_ui.foundation.AlertIcon
import com.anytypeio.anytype.core_ui.foundation.Divider
@ -158,6 +156,7 @@ fun DiscussionScreenWrapper(
val clipboard = LocalClipboardManager.current
val lazyListState = rememberLazyListState()
DiscussionScreen(
chatBoxMode = vm.chatBoxMode.collectAsState().value,
isSpaceLevelChat = isSpaceLevelChat,
title = vm.name.collectAsState().value,
messages = vm.messages.collectAsState().value,
@ -187,7 +186,9 @@ fun DiscussionScreenWrapper(
},
onUploadAttachmentClicked = {
}
},
onReplyMessage = vm::onReplyMessage,
onClearReplyClicked = vm::onClearReplyClicked
)
LaunchedEffect(Unit) {
vm.commands.collect { command ->
@ -211,6 +212,7 @@ fun DiscussionScreenWrapper(
*/
@Composable
fun DiscussionScreen(
chatBoxMode: ChatBoxMode,
isSpaceLevelChat: Boolean,
isInEditMessageMode: Boolean = false,
lazyListState: LazyListState,
@ -222,10 +224,12 @@ fun DiscussionScreen(
onAttachClicked: () -> Unit,
onBackButtonClicked: () -> Unit,
onClearAttachmentClicked: () -> Unit,
onClearReplyClicked: () -> Unit,
onReacted: (Id, String) -> Unit,
onDeleteMessage: (DiscussionView.Message) -> Unit,
onCopyMessage: (DiscussionView.Message) -> Unit,
onEditMessage: (DiscussionView.Message) -> Unit,
onReplyMessage: (DiscussionView.Message) -> Unit,
onAttachmentClicked: (DiscussionView.Message.Attachment) -> Unit,
onExitEditMessageMode: () -> Unit,
onMarkupLinkClicked: (String) -> Unit,
@ -284,6 +288,7 @@ fun DiscussionScreen(
chatBoxFocusRequester.requestFocus()
}
},
onReplyMessage = onReplyMessage,
onMarkupLinkClicked = onMarkupLinkClicked
)
// Jump to bottom button shows up when user scrolls past a threshold.
@ -324,6 +329,7 @@ fun DiscussionScreen(
}
ChatBox(
mode = chatBoxMode,
modifier = Modifier
.imePadding()
.navigationBarsPadding(),
@ -349,7 +355,8 @@ fun DiscussionScreen(
onAttachMediaClicked = onAttachMediaClicked,
onUploadAttachmentClicked = onUploadAttachmentClicked,
onAttachObjectClicked = onAttachObjectClicked,
onClearAttachmentClicked = onClearAttachmentClicked
onClearAttachmentClicked = onClearAttachmentClicked,
onClearReplyClicked = onClearReplyClicked
)
}
}
@ -482,6 +489,7 @@ private fun OldChatBox(
@Composable
private fun ChatBox(
mode: ChatBoxMode = ChatBoxMode.Default,
modifier: Modifier = Modifier,
onBackButtonClicked: () -> Unit,
chatBoxFocusRequester: FocusRequester,
@ -497,7 +505,8 @@ private fun ChatBox(
onAttachMediaClicked: () -> Unit,
onAttachFileClicked: () -> Unit,
onUploadAttachmentClicked: () -> Unit,
onClearAttachmentClicked: () -> Unit
onClearAttachmentClicked: () -> Unit,
onClearReplyClicked: () -> Unit
) {
var showDropdownMenu by remember { mutableStateOf(false) }
@ -552,8 +561,57 @@ private fun ChatBox(
)
}
}
Row(
) {
when(mode) {
is ChatBoxMode.Default -> {
}
is ChatBoxMode.EditMessage -> {
}
is ChatBoxMode.Reply -> {
Box(
modifier = Modifier
.fillMaxWidth()
.height(54.dp)
) {
Text(
text = "Reply to ${mode.author}",
modifier = Modifier.padding(
start = 12.dp,
top = 8.dp,
end = 44.dp
),
style = Caption1Medium,
color = colorResource(R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = mode.text,
modifier = Modifier.padding(
start = 12.dp,
top = 28.dp,
end = 44.dp
),
style = Caption1Regular,
color = colorResource(R.color.text_primary),
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Image(
painter = painterResource(R.drawable.ic_chat_close_chat_box_reply),
contentDescription = "Clear reply to icon",
modifier = Modifier
.padding(end = 12.dp)
.align(Alignment.CenterEnd)
.clickable {
onClearReplyClicked()
}
)
}
}
}
Row {
Box(
modifier = Modifier
.padding(horizontal = 4.dp, vertical = 8.dp)
@ -829,6 +887,7 @@ fun Messages(
onCopyMessage: (DiscussionView.Message) -> Unit,
onAttachmentClicked: (DiscussionView.Message.Attachment) -> Unit,
onEditMessage: (DiscussionView.Message) -> Unit,
onReplyMessage: (DiscussionView.Message) -> Unit,
onMarkupLinkClicked: (String) -> Unit
) {
LazyColumn(
@ -879,7 +938,11 @@ fun Messages(
onEditMessage = {
onEditMessage(msg)
},
onMarkupLinkClicked = onMarkupLinkClicked
onMarkupLinkClicked = onMarkupLinkClicked,
onReply = {
onReplyMessage(msg)
},
reply = msg.reply
)
if (msg.isUserAuthor) {
Spacer(modifier = Modifier.width(8.dp))
@ -998,6 +1061,7 @@ val userMessageBubbleColor = Color(0x66000000)
fun Bubble(
modifier: Modifier = Modifier,
name: String,
reply: DiscussionView.Message.Reply? = null,
content: DiscussionView.Message.Content,
timestamp: Long,
attachments: List<DiscussionView.Message.Attachment> = emptyList(),
@ -1008,6 +1072,7 @@ fun Bubble(
onDeleteMessage: () -> Unit,
onCopyMessage: () -> Unit,
onEditMessage: () -> Unit,
onReply: () -> Unit,
onAttachmentClicked: (DiscussionView.Message.Attachment) -> Unit,
onMarkupLinkClicked: (String) -> Unit
) {
@ -1020,13 +1085,54 @@ fun Bubble(
userMessageBubbleColor
else
defaultBubbleColor,
shape = RoundedCornerShape(24.dp)
shape = RoundedCornerShape(20.dp)
)
.clip(RoundedCornerShape(24.dp))
.clip(RoundedCornerShape(20.dp))
.clickable {
showDropdownMenu = !showDropdownMenu
}
) {
if (reply != null) {
Box(
modifier = Modifier
.padding(4.dp)
.fillMaxWidth()
.height(54.dp)
.background(
color = colorResource(R.color.navigation_panel_icon),
shape = RoundedCornerShape(16.dp)
)
) {
Text(
text = reply.author,
modifier = Modifier.padding(
start = 12.dp,
top = 8.dp,
end = 12.dp
),
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = if (isUserAuthor)
colorResource(id = R.color.text_white)
else
colorResource(id = R.color.text_primary),
)
Text(
modifier = Modifier.padding(
start = 12.dp,
top = 26.dp,
end = 12.dp
),
text = reply.text,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
color = if (isUserAuthor)
colorResource(id = R.color.text_white)
else
colorResource(id = R.color.text_primary),
)
}
}
Row(
modifier = Modifier.padding(
start = 16.dp,
@ -1100,12 +1206,7 @@ fun Bubble(
if (isEdited) {
withStyle(
style = SpanStyle(
color = if (isUserAuthor)
colorResource(id = R.color.text_white)
else
colorResource(id = R.color.text_primary),
)
style = SpanStyle(color = colorResource(id = R.color.text_tertiary))
) {
append(
" (${stringResource(R.string.chats_message_edited)})"
@ -1221,6 +1322,18 @@ fun Bubble(
// Do nothing.
}
)
DropdownMenuItem(
text = {
Text(
text = stringResource(R.string.chats_reply),
color = colorResource(id = R.color.text_primary)
)
},
onClick = {
onReply()
showDropdownMenu = false
}
)
DropdownMenuItem(
text = {
Text(

View file

@ -0,0 +1,18 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M7,7L12,12L7,17"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="@color/glyph_button"
android:strokeLineCap="round"/>
<path
android:pathData="M17,7L12,12L17,17"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="@color/glyph_button"
android:strokeLineCap="round"/>
</vector>

View file

@ -1827,5 +1827,6 @@ Please provide specific details of your needs here.</string>
<string name="chat_attachment_file">File</string>
<string name="chat_attachment_media">Media</string>
<string name="chat_attachment_upload">Upload</string>
<string name="chats_reply">Reply</string>
</resources>

View file

@ -1038,6 +1038,10 @@ class BlockMiddleware(
return middleware.chatGetMessages(command)
}
override suspend fun getChatMessagesByIds(command: Command.ChatCommand.GetMessagesByIds): List<Chat.Message> {
return middleware.chatGetMessagesByIds(command)
}
override suspend fun subscribeLastChatMessages(
command: Command.ChatCommand.SubscribeLastMessages
): Command.ChatCommand.SubscribeLastMessages.Response {

View file

@ -2744,6 +2744,18 @@ class Middleware @Inject constructor(
return response.messages.map { it.core() }
}
@Throws
fun chatGetMessagesByIds(command: Command.ChatCommand.GetMessagesByIds) : List<Chat.Message> {
val request = Rpc.Chat.GetMessagesByIds.Request(
chatObjectId = command.chat,
messageIds = command.messages
)
logRequestIfDebug(request)
val (response, time) = measureTimedValue { service.chatGetMessagesByIds(request) }
logResponseIfDebug(response, time)
return response.messages.map { it.core() }
}
@Throws
fun chatDeleteMessage(command: Command.ChatCommand.DeleteMessage) {
val request = Rpc.Chat.DeleteMessage.Request(

View file

@ -593,6 +593,7 @@ interface MiddlewareService {
fun chatAddMessage(request: Rpc.Chat.AddMessage.Request): Rpc.Chat.AddMessage.Response
fun chatEditMessage(request: Rpc.Chat.EditMessageContent.Request): Rpc.Chat.EditMessageContent.Response
fun chatGetMessages(request: Rpc.Chat.GetMessages.Request): Rpc.Chat.GetMessages.Response
fun chatGetMessagesByIds(request: Rpc.Chat.GetMessagesByIds.Request): Rpc.Chat.GetMessagesByIds.Response
fun chatDeleteMessage(request: Rpc.Chat.DeleteMessage.Request): Rpc.Chat.DeleteMessage.Response
fun chatSubscribeLastMessages(request: Rpc.Chat.SubscribeLastMessages.Request): Rpc.Chat.SubscribeLastMessages.Response
fun chatToggleMessageReaction(request: Rpc.Chat.ToggleMessageReaction.Request): Rpc.Chat.ToggleMessageReaction.Response

View file

@ -2358,6 +2358,19 @@ class MiddlewareServiceImplementation @Inject constructor(
}
}
override fun chatGetMessagesByIds(request: Rpc.Chat.GetMessagesByIds.Request): Rpc.Chat.GetMessagesByIds.Response {
val encoded = Service.chatGetMessagesByIds(
Rpc.Chat.GetMessagesByIds.Request.ADAPTER.encode(request)
)
val response = Rpc.Chat.GetMessagesByIds.Response.ADAPTER.decode(encoded)
val error = response.error
if (error != null && error.code != Rpc.Chat.GetMessagesByIds.Response.Error.Code.NULL) {
throw Exception(error.description)
} else {
return response
}
}
override fun chatSubscribeLastMessages(request: Rpc.Chat.SubscribeLastMessages.Request): Rpc.Chat.SubscribeLastMessages.Response {
val encoded = Service.chatSubscribeLastMessages(
Rpc.Chat.SubscribeLastMessages.Request.ADAPTER.encode(request)