feat: send begin/stop typing state
Signed-off-by: Infi <infi@infi.sh>
This commit is contained in:
parent
5f0b7b8c2c
commit
331736119b
|
|
@ -29,6 +29,8 @@ import chat.revolt.api.realtime.frames.receivable.ServerUpdateFrame
|
|||
import chat.revolt.api.realtime.frames.receivable.UserRelationshipFrame
|
||||
import chat.revolt.api.realtime.frames.receivable.UserUpdateFrame
|
||||
import chat.revolt.api.realtime.frames.sendable.AuthorizationFrame
|
||||
import chat.revolt.api.realtime.frames.sendable.BeginTypingFrame
|
||||
import chat.revolt.api.realtime.frames.sendable.EndTypingFrame
|
||||
import chat.revolt.api.realtime.frames.sendable.PingFrame
|
||||
import chat.revolt.api.routes.server.fetchMember
|
||||
import chat.revolt.api.schemas.Channel
|
||||
|
|
@ -704,4 +706,28 @@ object RealtimeSocket {
|
|||
private suspend fun pushReconnectEvent() {
|
||||
RevoltAPI.wsFrameChannel.send(RealtimeSocketFrames.Reconnected)
|
||||
}
|
||||
|
||||
suspend fun beginTyping(channelId: String) {
|
||||
if (disconnectionState != DisconnectionState.Connected) return
|
||||
|
||||
val beginTypingFrame = BeginTypingFrame("BeginTyping", channelId)
|
||||
socket?.send(
|
||||
RevoltJson.encodeToString(
|
||||
BeginTypingFrame.serializer(),
|
||||
beginTypingFrame
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
suspend fun endTyping(channelId: String) {
|
||||
if (disconnectionState != DisconnectionState.Connected) return
|
||||
|
||||
val endTypingFrame = EndTypingFrame("EndTyping", channelId)
|
||||
socket?.send(
|
||||
RevoltJson.encodeToString(
|
||||
EndTypingFrame.serializer(),
|
||||
endTypingFrame
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -487,10 +487,7 @@ fun ChannelScreen(
|
|||
} else {
|
||||
NativeMessageField(
|
||||
value = viewModel.pendingMessageContent,
|
||||
onValueChange = {
|
||||
viewModel.pendingMessageContent = it
|
||||
// viewModel.textSelection = it.selection
|
||||
},
|
||||
onValueChange = viewModel::updatePendingMessageContent,
|
||||
onSendMessage = viewModel::sendPendingMessage,
|
||||
onAddAttachment = {
|
||||
val isTiramisu = Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
|
||||
|
|
@ -586,7 +583,7 @@ fun ChannelScreen(
|
|||
Toast.LENGTH_SHORT
|
||||
).show()
|
||||
}
|
||||
|
||||
|
||||
viewModel.currentBottomPane = BottomPane.None
|
||||
},
|
||||
onClose = {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ import chat.revolt.api.internals.Roles
|
|||
import chat.revolt.api.internals.SpecialUsers
|
||||
import chat.revolt.api.internals.ULID
|
||||
import chat.revolt.api.internals.hasPermission
|
||||
import chat.revolt.api.realtime.RealtimeSocket
|
||||
import chat.revolt.api.realtime.RealtimeSocketFrames
|
||||
import chat.revolt.api.realtime.frames.receivable.ChannelDeleteFrame
|
||||
import chat.revolt.api.realtime.frames.receivable.ChannelStartTypingFrame
|
||||
|
|
@ -57,6 +58,7 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlinx.datetime.Clock
|
||||
import kotlinx.datetime.Instant
|
||||
|
||||
const val MAX_MESSAGE_LENGTH = 2000
|
||||
|
|
@ -108,6 +110,57 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
pendingReplies.add(reply)
|
||||
}
|
||||
|
||||
private var lastSentBeginTyping: Instant? = null
|
||||
|
||||
private fun startTyping() {
|
||||
if (editingMessage != null) return
|
||||
if (lastSentBeginTyping != null) {
|
||||
val diff = Clock.System.now() - lastSentBeginTyping!!
|
||||
if (diff.inWholeSeconds < 1) return
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
withContext(RevoltAPI.realtimeContext) {
|
||||
activeChannelId?.let {
|
||||
RealtimeSocket.beginTyping(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lastSentBeginTyping = Clock.System.now()
|
||||
}
|
||||
|
||||
private var stopTypingJob: Job? = null
|
||||
|
||||
private fun queueStopTyping() {
|
||||
stopTypingJob = viewModelScope.launch {
|
||||
delay(5000)
|
||||
stopTyping()
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopTyping() {
|
||||
if (editingMessage != null) return
|
||||
viewModelScope.launch {
|
||||
withContext(RevoltAPI.realtimeContext) {
|
||||
activeChannelId?.let {
|
||||
RealtimeSocket.endTyping(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun updatePendingMessageContent(newContent: String) {
|
||||
pendingMessageContent = newContent
|
||||
if (newContent.isNotBlank()) {
|
||||
startTyping()
|
||||
stopTypingJob?.cancel()
|
||||
queueStopTyping()
|
||||
} else {
|
||||
stopTyping()
|
||||
}
|
||||
}
|
||||
|
||||
fun toggleReplyMentionFor(reply: SendMessageReply) {
|
||||
val index = pendingReplies.indexOf(reply)
|
||||
val newReply = SendMessageReply(
|
||||
|
|
@ -229,7 +282,7 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
replies = pendingReplies
|
||||
)
|
||||
|
||||
pendingMessageContent = ""
|
||||
updatePendingMessageContent("")
|
||||
hasNoMoreMessages = false
|
||||
isSendingMessage = false
|
||||
pendingUploadProgress = 0f
|
||||
|
|
@ -248,7 +301,7 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
newContent = pendingMessageContent.trimIndent()
|
||||
)
|
||||
|
||||
pendingMessageContent = ""
|
||||
updatePendingMessageContent("")
|
||||
isSendingMessage = false
|
||||
}
|
||||
|
||||
|
|
@ -466,7 +519,7 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
val message = renderableMessages.find { msg ->
|
||||
msg.id == it.messageId
|
||||
} ?: return@onEach
|
||||
pendingMessageContent = message.content ?: ""
|
||||
updatePendingMessageContent(message.content ?: "")
|
||||
textSelection =
|
||||
(message.content?.length ?: 0) to (message.content?.length ?: 0)
|
||||
}
|
||||
|
|
@ -512,7 +565,7 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
|
||||
fun cancelEditingMessage() {
|
||||
editingMessage = null
|
||||
pendingMessageContent = ""
|
||||
updatePendingMessageContent("")
|
||||
}
|
||||
|
||||
fun putAtCursorPosition(content: String) {
|
||||
|
|
@ -521,7 +574,7 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
|
||||
// if out of bounds, just append
|
||||
if (currentSelection.first > currentContent.length) {
|
||||
pendingMessageContent = currentContent + content
|
||||
updatePendingMessageContent(currentContent + content)
|
||||
textSelection =
|
||||
currentSelection.first + content.length to currentSelection.first + content.length
|
||||
return
|
||||
|
|
@ -531,7 +584,7 @@ class ChannelScreenViewModel : ViewModel() {
|
|||
content +
|
||||
currentContent.substring(currentSelection.second)
|
||||
|
||||
pendingMessageContent = newContent
|
||||
updatePendingMessageContent(newContent)
|
||||
textSelection =
|
||||
currentSelection.first + content.length to currentSelection.first + content.length
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue