From 8f976cd4f5a93f6f77dec5e61050c5fff81a82e1 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Sat, 30 Nov 2024 15:18:32 +0100 Subject: [PATCH 1/9] DROID-3044 fixes --- .../anytypeio/anytype/core_models/Command.kt | 2 +- .../anytype/core_models/chats/Chat.kt | 9 ++- .../presentation/DiscussionViewModel.kt | 40 +++++++++- .../ui/DiscussionPreviews.kt | 19 +++-- .../ui/DiscussionScreen.kt | 80 +++++++++++++++++-- .../drawable/ic_chat_close_chat_box_reply.xml | 18 +++++ localization/src/main/res/values/strings.xml | 1 + 7 files changed, 150 insertions(+), 19 deletions(-) create mode 100644 feature-discussions/src/main/res/drawable/ic_chat_close_chat_box_reply.xml diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt index 14fb5e84de..7d205c08d6 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt @@ -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( diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/chats/Chat.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/chats/Chat.kt index 37c5bd9b33..90340fc6f9 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/chats/Chat.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/chats/Chat.kt @@ -41,16 +41,17 @@ sealed class Chat { */ fun new( text: String, - attachments: List = emptyList() - ) : Message = Chat.Message( + attachments: List = 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 diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt index 9f0a335c46..956a3ae90c 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt @@ -215,7 +215,6 @@ class DiscussionViewModel @Inject constructor( Timber.e(it, "Error while adding message") } } - is ChatBoxMode.EditMessage -> { editChatMessage.async( params = Command.ChatCommand.EditMessage( @@ -234,6 +233,30 @@ 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") + } + } } } } @@ -292,6 +315,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 +372,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 { diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionPreviews.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionPreviews.kt index 022e701160..ef06159365 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionPreviews.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionPreviews.kt @@ -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,9 @@ fun DiscussionScreenPreview() { onAttachFileClicked = {}, onUploadAttachmentClicked = {}, onAttachMediaClicked = {}, - onAttachObjectClicked = {} + onAttachObjectClicked = {}, + onReplyMessage = {}, + chatBoxMode = DiscussionViewModel.ChatBoxMode.Default ) } @@ -142,7 +144,8 @@ fun BubblePreview() { onCopyMessage = {}, onAttachmentClicked = {}, onEditMessage = {}, - onMarkupLinkClicked = {} + onMarkupLinkClicked = {}, + onReply = {} ) } @@ -167,7 +170,8 @@ fun BubbleEditedPreview() { onCopyMessage = {}, onAttachmentClicked = {}, onEditMessage = {}, - onMarkupLinkClicked = {} + onMarkupLinkClicked = {}, + onReply = {} ) } @@ -199,6 +203,7 @@ fun BubbleWithAttachmentPreview() { }, onAttachmentClicked = {}, onEditMessage = {}, - onMarkupLinkClicked = {} + onMarkupLinkClicked = {}, + onReply = {} ) } \ No newline at end of file diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt index 135e1cf5f6..be1ede0b36 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt @@ -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,8 @@ fun DiscussionScreenWrapper( }, onUploadAttachmentClicked = { - } + }, + onReplyMessage = vm::onReplyMessage ) LaunchedEffect(Unit) { vm.commands.collect { command -> @@ -211,6 +211,7 @@ fun DiscussionScreenWrapper( */ @Composable fun DiscussionScreen( + chatBoxMode: ChatBoxMode, isSpaceLevelChat: Boolean, isInEditMessageMode: Boolean = false, lazyListState: LazyListState, @@ -226,6 +227,7 @@ fun DiscussionScreen( 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 +286,7 @@ fun DiscussionScreen( chatBoxFocusRequester.requestFocus() } }, + onReplyMessage = onReplyMessage, onMarkupLinkClicked = onMarkupLinkClicked ) // Jump to bottom button shows up when user scrolls past a threshold. @@ -324,6 +327,7 @@ fun DiscussionScreen( } ChatBox( + mode = chatBoxMode, modifier = Modifier .imePadding() .navigationBarsPadding(), @@ -482,6 +486,7 @@ private fun OldChatBox( @Composable private fun ChatBox( + mode: ChatBoxMode = ChatBoxMode.Default, modifier: Modifier = Modifier, onBackButtonClicked: () -> Unit, chatBoxFocusRequester: FocusRequester, @@ -552,8 +557,54 @@ 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) + ) + } + } + } + Row { Box( modifier = Modifier .padding(horizontal = 4.dp, vertical = 8.dp) @@ -829,6 +880,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 +931,10 @@ fun Messages( onEditMessage = { onEditMessage(msg) }, - onMarkupLinkClicked = onMarkupLinkClicked + onMarkupLinkClicked = onMarkupLinkClicked, + onReply = { + onReplyMessage(msg) + } ) if (msg.isUserAuthor) { Spacer(modifier = Modifier.width(8.dp)) @@ -1008,6 +1063,7 @@ fun Bubble( onDeleteMessage: () -> Unit, onCopyMessage: () -> Unit, onEditMessage: () -> Unit, + onReply: () -> Unit, onAttachmentClicked: (DiscussionView.Message.Attachment) -> Unit, onMarkupLinkClicked: (String) -> Unit ) { @@ -1221,6 +1277,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( diff --git a/feature-discussions/src/main/res/drawable/ic_chat_close_chat_box_reply.xml b/feature-discussions/src/main/res/drawable/ic_chat_close_chat_box_reply.xml new file mode 100644 index 0000000000..ae824312fc --- /dev/null +++ b/feature-discussions/src/main/res/drawable/ic_chat_close_chat_box_reply.xml @@ -0,0 +1,18 @@ + + + + diff --git a/localization/src/main/res/values/strings.xml b/localization/src/main/res/values/strings.xml index 3327ca3d81..bc82489f79 100644 --- a/localization/src/main/res/values/strings.xml +++ b/localization/src/main/res/values/strings.xml @@ -1827,5 +1827,6 @@ Please provide specific details of your needs here. File Media Upload + Reply \ No newline at end of file From e720cebbf2a4f3b79b01abd5f1c45611254bc806 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Sat, 30 Nov 2024 15:26:17 +0100 Subject: [PATCH 2/9] DROID-3044 fixes --- .../anytypeio/anytype/core_models/Command.kt | 5 +++++ .../auth/repo/block/BlockDataRepository.kt | 4 ++++ .../data/auth/repo/block/BlockRemote.kt | 1 + .../domain/block/repo/BlockRepository.kt | 1 + .../domain/chats/GetChatMessagesByIds.kt | 18 ++++++++++++++++++ .../middleware/block/BlockMiddleware.kt | 4 ++++ .../middleware/interactor/Middleware.kt | 12 ++++++++++++ .../middleware/service/MiddlewareService.kt | 1 + .../service/MiddlewareServiceImplementation.kt | 13 +++++++++++++ 9 files changed, 59 insertions(+) create mode 100644 domain/src/main/java/com/anytypeio/anytype/domain/chats/GetChatMessagesByIds.kt diff --git a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt index 7d205c08d6..960a0bafeb 100644 --- a/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt +++ b/core-models/src/main/java/com/anytypeio/anytype/core_models/Command.kt @@ -613,6 +613,11 @@ sealed class Command { val limit: Int ) : ChatCommand() + data class GetMessagesByIds( + val chat: Id, + val messages: List + ) : ChatCommand() + data class SubscribeLastMessages( val chat: Id, val limit: Int diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt index f6d5a3ca15..70260ec1e5 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockDataRepository.kt @@ -1067,6 +1067,10 @@ class BlockDataRepository( return remote.getChatMessages(command) } + override suspend fun getChatMessagesByIds(command: Command.ChatCommand.GetMessagesByIds): List { + return remote.getChatMessagesByIds(command) + } + override suspend fun subscribeLastChatMessages( command: Command.ChatCommand.SubscribeLastMessages ): Command.ChatCommand.SubscribeLastMessages.Response { diff --git a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt index 235a9da480..498e3650fd 100644 --- a/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt +++ b/data/src/main/java/com/anytypeio/anytype/data/auth/repo/block/BlockRemote.kt @@ -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 + suspend fun getChatMessagesByIds(command: Command.ChatCommand.GetMessagesByIds): List suspend fun subscribeLastChatMessages(command: Command.ChatCommand.SubscribeLastMessages): Command.ChatCommand.SubscribeLastMessages.Response suspend fun toggleChatMessageReaction(command: Command.ChatCommand.ToggleMessageReaction) suspend fun unsubscribeChat(chat: Id) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt index a589fc93b3..b794e7a7c4 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/block/repo/BlockRepository.kt @@ -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 + suspend fun getChatMessagesByIds(command: Command.ChatCommand.GetMessagesByIds): List suspend fun subscribeLastChatMessages(command: Command.ChatCommand.SubscribeLastMessages): Command.ChatCommand.SubscribeLastMessages.Response suspend fun toggleChatMessageReaction(command: Command.ChatCommand.ToggleMessageReaction) suspend fun unsubscribeChat(chat: Id) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/chats/GetChatMessagesByIds.kt b/domain/src/main/java/com/anytypeio/anytype/domain/chats/GetChatMessagesByIds.kt new file mode 100644 index 0000000000..2c44238aa9 --- /dev/null +++ b/domain/src/main/java/com/anytypeio/anytype/domain/chats/GetChatMessagesByIds.kt @@ -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>(dispatchers.io) { + + override suspend fun doWork(params: Command.ChatCommand.GetMessagesByIds): List { + return repo.getChatMessagesByIds(params) + } +} \ No newline at end of file diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt index e3b8275a1c..e977a12fb0 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/block/BlockMiddleware.kt @@ -1038,6 +1038,10 @@ class BlockMiddleware( return middleware.chatGetMessages(command) } + override suspend fun getChatMessagesByIds(command: Command.ChatCommand.GetMessagesByIds): List { + return middleware.chatGetMessagesByIds(command) + } + override suspend fun subscribeLastChatMessages( command: Command.ChatCommand.SubscribeLastMessages ): Command.ChatCommand.SubscribeLastMessages.Response { diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt index 8494d0341b..f799bd421a 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/interactor/Middleware.kt @@ -2744,6 +2744,18 @@ class Middleware @Inject constructor( return response.messages.map { it.core() } } + @Throws + fun chatGetMessagesByIds(command: Command.ChatCommand.GetMessagesByIds) : List { + 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( diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt index eda123801c..563577dd27 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareService.kt @@ -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 diff --git a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt index 30415f5e02..71497b9d7b 100644 --- a/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt +++ b/middleware/src/main/java/com/anytypeio/anytype/middleware/service/MiddlewareServiceImplementation.kt @@ -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) From 81f88b7fd21010750cb79e36ee5e9a5353e1ce02 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Sat, 30 Nov 2024 15:55:39 +0100 Subject: [PATCH 3/9] DROID-3044 fixes --- .../anytype/domain/chats/ChatContainer.kt | 39 ++++++++++++--- .../presentation/DiscussionView.kt | 9 +++- .../presentation/DiscussionViewModel.kt | 33 ++++++++++++- .../ui/DiscussionScreen.kt | 49 +++++++++++++++++-- 4 files changed, 116 insertions(+), 14 deletions(-) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt b/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt index 84108e2f23..719783d258 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt @@ -20,6 +20,7 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach @@ -33,6 +34,7 @@ class ChatContainer @Inject constructor( private val payloads = MutableSharedFlow>() private val attachments = MutableSharedFlow>(replay = 0) + private val replies = MutableSharedFlow>(replay = 0) @Deprecated("Naive implementation. Add caching logic - maybe store for wrappers") fun fetchAttachments(space: Space) : Flow> { @@ -64,18 +66,39 @@ class ChatContainer @Inject constructor( .map { wrappers -> wrappers.associate { it.id to it } } } + @Deprecated("Naive implementation. Add caching logic") + fun fetchReplies(chat: Id) : Flow> { + 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> { return watch(chat) .onEach { messages -> - val ids = messages - .map { msg -> - msg.attachments.map { - it.target - } + val repliesIds = mutableSetOf() + val attachmentsIds = mutableSetOf() + 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) } } diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionView.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionView.kt index dfcefbf709..e4433cd729 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionView.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionView.kt @@ -16,7 +16,8 @@ sealed interface DiscussionView { val reactions: List = 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) { @@ -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, diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt index 956a3ae90c..c05db715e9 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt @@ -108,9 +108,13 @@ class DiscussionViewModel @Inject constructor( ) { 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 +125,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 +164,7 @@ class DiscussionViewModel @Inject constructor( ) } ), + reply = reply, author = member?.name ?: msg.creator.takeLast(5), isUserAuthor = msg.creator == account, isEdited = msg.modifiedAt > msg.createdAt, diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt index be1ede0b36..458fdca7df 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt @@ -934,7 +934,8 @@ fun Messages( onMarkupLinkClicked = onMarkupLinkClicked, onReply = { onReplyMessage(msg) - } + }, + reply = msg.reply ) if (msg.isUserAuthor) { Spacer(modifier = Modifier.width(8.dp)) @@ -1053,6 +1054,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 = emptyList(), @@ -1076,13 +1078,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, From 6db31bc3b0619cd2bb742d88890faa8ce74a4f32 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Sat, 30 Nov 2024 15:58:25 +0100 Subject: [PATCH 4/9] DROID-3044 fixes --- .../anytype/feature_discussions/ui/DiscussionScreen.kt | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt index 458fdca7df..badb0da68e 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt @@ -1199,12 +1199,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)})" From c14bacbfbdaa7764666b55ef1cd8056c3d28b052 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Sat, 30 Nov 2024 16:02:10 +0100 Subject: [PATCH 5/9] DROID-3044 fixes --- .../presentation/DiscussionViewModel.kt | 6 ++++++ .../ui/DiscussionPreviews.kt | 3 ++- .../ui/DiscussionScreen.kt | 19 +++++++++++++------ 3 files changed, 21 insertions(+), 7 deletions(-) diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt index c05db715e9..011c790f42 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt @@ -324,6 +324,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 { diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionPreviews.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionPreviews.kt index ef06159365..745b64771b 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionPreviews.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionPreviews.kt @@ -120,7 +120,8 @@ fun DiscussionScreenPreview() { onAttachMediaClicked = {}, onAttachObjectClicked = {}, onReplyMessage = {}, - chatBoxMode = DiscussionViewModel.ChatBoxMode.Default + chatBoxMode = DiscussionViewModel.ChatBoxMode.Default, + onClearReplyClicked = {} ) } diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt index badb0da68e..7f3eae8b1d 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/ui/DiscussionScreen.kt @@ -187,7 +187,8 @@ fun DiscussionScreenWrapper( onUploadAttachmentClicked = { }, - onReplyMessage = vm::onReplyMessage + onReplyMessage = vm::onReplyMessage, + onClearReplyClicked = vm::onClearReplyClicked ) LaunchedEffect(Unit) { vm.commands.collect { command -> @@ -223,6 +224,7 @@ fun DiscussionScreen( onAttachClicked: () -> Unit, onBackButtonClicked: () -> Unit, onClearAttachmentClicked: () -> Unit, + onClearReplyClicked: () -> Unit, onReacted: (Id, String) -> Unit, onDeleteMessage: (DiscussionView.Message) -> Unit, onCopyMessage: (DiscussionView.Message) -> Unit, @@ -353,7 +355,8 @@ fun DiscussionScreen( onAttachMediaClicked = onAttachMediaClicked, onUploadAttachmentClicked = onUploadAttachmentClicked, onAttachObjectClicked = onAttachObjectClicked, - onClearAttachmentClicked = onClearAttachmentClicked + onClearAttachmentClicked = onClearAttachmentClicked, + onClearReplyClicked = onClearReplyClicked ) } } @@ -502,7 +505,8 @@ private fun ChatBox( onAttachMediaClicked: () -> Unit, onAttachFileClicked: () -> Unit, onUploadAttachmentClicked: () -> Unit, - onClearAttachmentClicked: () -> Unit + onClearAttachmentClicked: () -> Unit, + onClearReplyClicked: () -> Unit ) { var showDropdownMenu by remember { mutableStateOf(false) } @@ -597,9 +601,12 @@ private fun ChatBox( 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) + modifier = Modifier + .padding(end = 12.dp) + .align(Alignment.CenterEnd) + .clickable { + onClearReplyClicked() + } ) } } From 9dffa4de21796a1db7427d903a68cd7ec8a30e2e Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Sat, 30 Nov 2024 16:12:28 +0100 Subject: [PATCH 6/9] DROID-3044 fixes --- .../com/anytypeio/anytype/domain/chats/ChatContainer.kt | 4 +++- .../presentation/DiscussionViewModel.kt | 6 +++++- .../presentation/DiscussionViewModelFactory.kt | 7 +++++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt b/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt index 719783d258..0932567da8 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt @@ -9,6 +9,7 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.chats.Chat import com.anytypeio.anytype.core_models.primitives.Space +import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.debugging.Logger import javax.inject.Inject @@ -29,7 +30,8 @@ import kotlinx.coroutines.flow.scan class ChatContainer @Inject constructor( private val repo: BlockRepository, private val channel: ChatEventChannel, - private val logger: Logger + private val logger: Logger, + private val dispatchers: AppCoroutineDispatchers ) { private val payloads = MutableSharedFlow>() diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt index 011c790f42..311bc8adb1 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt @@ -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(null) @@ -211,6 +214,7 @@ class DiscussionViewModel @Inject constructor( ) }.reversed() } +// .flowOn(dispatchers.io) .collect { result -> messages.value = result } diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModelFactory.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModelFactory.kt index cd6b75926c..072fee6f20 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModelFactory.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModelFactory.kt @@ -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 create(modelClass: Class): T = DiscussionViewModel( @@ -43,6 +45,7 @@ class DiscussionViewModelFactory @Inject constructor( deleteChatMessage = deleteChatMessage, urlBuilder = urlBuilder, editChatMessage = editChatMessage, - spaceViews = spaceViews + spaceViews = spaceViews, + dispatchers = dispatchers ) as T } \ No newline at end of file From fc391a9760381607b422aa84b49f2d366adf3838 Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Sat, 30 Nov 2024 16:19:18 +0100 Subject: [PATCH 7/9] DROID-3044 fix --- .../feature_discussions/presentation/DiscussionViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt index 311bc8adb1..70e3069d4c 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt @@ -289,6 +289,7 @@ class DiscussionViewModel @Inject constructor( }.onFailure { Timber.e(it, "Error while adding message") } + chatBoxMode.value = ChatBoxMode.Default } } } From 16ef06bd510d8756b9f7cf60de4cc039e77f6e6d Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Sat, 30 Nov 2024 16:20:10 +0100 Subject: [PATCH 8/9] DROID-3044 fix --- .../java/com/anytypeio/anytype/domain/chats/ChatContainer.kt | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt b/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt index 0932567da8..46452760d3 100644 --- a/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt +++ b/domain/src/main/java/com/anytypeio/anytype/domain/chats/ChatContainer.kt @@ -9,7 +9,6 @@ import com.anytypeio.anytype.core_models.ObjectWrapper import com.anytypeio.anytype.core_models.Relations import com.anytypeio.anytype.core_models.chats.Chat import com.anytypeio.anytype.core_models.primitives.Space -import com.anytypeio.anytype.domain.base.AppCoroutineDispatchers import com.anytypeio.anytype.domain.block.repo.BlockRepository import com.anytypeio.anytype.domain.debugging.Logger import javax.inject.Inject @@ -21,7 +20,6 @@ import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.emitAll import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.merge import kotlinx.coroutines.flow.onEach @@ -30,8 +28,7 @@ import kotlinx.coroutines.flow.scan class ChatContainer @Inject constructor( private val repo: BlockRepository, private val channel: ChatEventChannel, - private val logger: Logger, - private val dispatchers: AppCoroutineDispatchers + private val logger: Logger ) { private val payloads = MutableSharedFlow>() From 84c0b454b5da8b0850fa43b041837d8432fb497a Mon Sep 17 00:00:00 2001 From: Evgenii Kozlov Date: Sat, 30 Nov 2024 16:21:51 +0100 Subject: [PATCH 9/9] DROID-3044 fix --- .../feature_discussions/presentation/DiscussionViewModel.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt index 70e3069d4c..dac2b49c1f 100644 --- a/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt +++ b/feature-discussions/src/main/java/com/anytypeio/anytype/feature_discussions/presentation/DiscussionViewModel.kt @@ -105,6 +105,7 @@ class DiscussionViewModel @Inject constructor( } } + // TODO move to IO thread. private suspend fun proceedWithObservingChatMessages( account: Id, chat: Id