improvements in longpoll's stuff

This commit is contained in:
2025-03-23 17:27:46 +03:00
parent 3beb382334
commit 314ff806c0
9 changed files with 610 additions and 204 deletions
@@ -16,11 +16,11 @@ import dev.meloda.fast.common.LongPollController
import dev.meloda.fast.common.VkConstants
import dev.meloda.fast.common.extensions.listenValue
import dev.meloda.fast.common.model.LongPollState
import dev.meloda.fast.domain.LongPollUpdatesParser
import dev.meloda.fast.domain.LongPollUseCase
import dev.meloda.fast.data.UserConfig
import dev.meloda.fast.data.processState
import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.domain.LongPollUpdatesParser
import dev.meloda.fast.domain.LongPollUseCase
import dev.meloda.fast.model.api.data.LongPollUpdates
import dev.meloda.fast.model.api.data.VkLongPollData
import dev.meloda.fast.ui.R
@@ -249,6 +249,7 @@ class LongPollingService : Service() {
override fun onDestroy() {
Log.d(STATE_TAG, "onDestroy")
longPollController.updateCurrentState(LongPollState.Stopped)
updatesParser.clearListeners()
try {
AppSettings.edit { putBoolean(KEY_LONG_POLL_WAS_DESTROYED, true) }
job.cancel()
@@ -259,8 +260,7 @@ class LongPollingService : Service() {
}
override fun onTrimMemory(level: Int) {
Log.d(STATE_TAG, "onTrimMemory")
longPollController.updateCurrentState(LongPollState.Stopped)
Log.d(STATE_TAG, "onTrimMemory. Level: $level")
super.onTrimMemory(level)
}
@@ -11,11 +11,15 @@ import dev.meloda.fast.data.processState
import dev.meloda.fast.model.ApiEvent
import dev.meloda.fast.model.InteractionType
import dev.meloda.fast.model.LongPollEvent
import dev.meloda.fast.model.LongPollParsedEvent
import dev.meloda.fast.model.MessageFlags
import dev.meloda.fast.model.api.domain.VkMessage
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
@@ -25,17 +29,19 @@ class LongPollUpdatesParser(
) {
private val job = SupervisorJob()
private val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
Log.d("LongPollUpdatesParser", "error: $throwable")
throwable.printStackTrace()
}
private val exceptionHandler =
CoroutineExceptionHandler { _, throwable ->
Log.e("LongPollUpdatesParser", "error: $throwable")
throwable.printStackTrace()
}
private val coroutineContext: CoroutineContext
get() = Dispatchers.Default + job + exceptionHandler
private val coroutineScope = CoroutineScope(coroutineContext)
private val listenersMap: MutableMap<ApiEvent, MutableList<VkEventCallback<*>>> = mutableMapOf()
private val listenersMap: MutableMap<LongPollEvent, MutableList<VkEventCallback<LongPollParsedEvent>>> =
mutableMapOf()
fun parseNextUpdate(event: List<Any>) {
val eventId = event.first().asInt()
@@ -49,8 +55,11 @@ class LongPollUpdatesParser(
ApiEvent.MESSAGE_EDIT -> parseMessageEdit(eventType, event)
ApiEvent.MESSAGE_READ_INCOMING -> parseMessageReadIncoming(eventType, event)
ApiEvent.MESSAGE_READ_OUTGOING -> parseMessageReadOutgoing(eventType, event)
ApiEvent.CHAT_CLEAR_FLAGS -> parseChatClearFlags(eventType, event)
ApiEvent.CHAT_SET_FLAGS -> parseChatSetFlags(eventType, event)
ApiEvent.MESSAGES_DELETED -> parseMessagesDeleted(eventType, event)
ApiEvent.PIN_UNPIN_CONVERSATION -> parseConversationPinStateChanged(eventType, event)
ApiEvent.CHAT_MAJOR_CHANGED -> parseChatMajorChanged(eventType, event)
ApiEvent.CHAT_MINOR_CHANGED -> parseChatMinorChanged(eventType, event)
ApiEvent.TYPING,
ApiEvent.AUDIO_MESSAGE_RECORDING,
@@ -74,6 +83,15 @@ class LongPollUpdatesParser(
else -> return
}
val longPollEvent: LongPollEvent = when (eventType) {
ApiEvent.TYPING -> LongPollEvent.TYPING
ApiEvent.AUDIO_MESSAGE_RECORDING -> LongPollEvent.AUDIO_MESSAGE_RECORDING
ApiEvent.PHOTO_UPLOADING -> LongPollEvent.PHOTO_UPLOADING
ApiEvent.VIDEO_UPLOADING -> LongPollEvent.VIDEO_UPLOADING
ApiEvent.FILE_UPLOADING -> LongPollEvent.FILE_UPLOADING
else -> return
}
val peerId = event[1].asInt()
val userIds = event[2].toList(Any::asInt).filter { it != UserConfig.userId }
val totalCount = event[3].asInt()
@@ -82,20 +100,18 @@ class LongPollUpdatesParser(
// if userIds contains only account's id, then we don't need to show our status
if (userIds.isEmpty()) return
coroutineScope.launch {
listenersMap[eventType]?.let { listeners ->
listeners.forEach { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.Interaction>)
.onEvent(
LongPollEvent.Interaction(
interactionType = interactionType,
peerId = peerId,
userIds = userIds,
totalCount = totalCount,
timestamp = timestamp
)
listenersMap[longPollEvent]?.let { listeners ->
listeners.forEach { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollParsedEvent.Interaction>)
.onEvent(
LongPollParsedEvent.Interaction(
interactionType = interactionType,
peerId = peerId,
userIds = userIds,
totalCount = totalCount,
timestamp = timestamp
)
}
)
}
}
}
@@ -111,11 +127,11 @@ class LongPollUpdatesParser(
val archiveUnreadUnmutedCount = event[8].asInt()
val archiveMentionsCount = event[9].asInt()
listenersMap[ApiEvent.UNREAD_COUNT_UPDATE]?.let { listeners ->
listenersMap[LongPollEvent.UNREAD_COUNTER_UPDATE]?.let { listeners ->
listeners.forEach { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.UnreadCounter>)
(vkEventCallback as VkEventCallback<LongPollParsedEvent.UnreadCounter>)
.onEvent(
LongPollEvent.UnreadCounter(
LongPollParsedEvent.UnreadCounter(
unread = unreadCount,
unreadUnmuted = unreadUnmutedCount,
showOnlyMuted = showOnlyMuted,
@@ -129,31 +145,182 @@ class LongPollUpdatesParser(
}
}
private fun parseConversationPinStateChanged(eventType: ApiEvent, event: List<Any>) {
private fun parseMessageSetFlags(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asInt()
val majorId = event[2].asInt()
val messageId = event[1].asInt()
val flags = event[2].asInt()
val peerId = event[3].asInt()
listenersMap[ApiEvent.PIN_UNPIN_CONVERSATION]?.let { listeners ->
listeners.forEach { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.VkConversationPinStateChangedEvent>)
.onEvent(
LongPollEvent.VkConversationPinStateChangedEvent(
peerId = peerId,
majorId = majorId
)
val eventsToSend = mutableListOf<LongPollParsedEvent>()
val parsedFlags = MessageFlags.parse(flags)
parsedFlags.forEach { flag ->
when (flag) {
MessageFlags.IMPORTANT -> { // marked as important
val eventToSend = LongPollParsedEvent.MessageMarkedAsImportant(
peerId = peerId,
messageId = messageId,
marked = true
)
eventsToSend += eventToSend
listenersMap[LongPollEvent.MARKED_AS_IMPORTANT]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageMarkedAsImportant>)
?.onEvent(eventToSend)
}
}
}
MessageFlags.SPAM -> { // marked as spam
val eventToSend = LongPollParsedEvent.MessageMarkedAsSpam(
peerId = peerId,
messageId = messageId
)
eventsToSend += eventToSend
listenersMap[LongPollEvent.MARKED_AS_SPAM]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageMarkedAsSpam>)
?.onEvent(eventToSend)
}
}
}
MessageFlags.DELETED -> {
val eventToSend =
if (parsedFlags.contains(MessageFlags.DELETED_FOR_ALL)) { // deleted for all
LongPollParsedEvent.MessageDeleted(
peerId = peerId,
messageId = messageId,
forAll = true
)
} else { // deleted only for me
LongPollParsedEvent.MessageDeleted(
peerId = peerId,
messageId = messageId,
forAll = false
)
}
eventsToSend += eventToSend
listenersMap[LongPollEvent.MESSAGE_DELETED]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageDeleted>)
?.onEvent(eventToSend)
}
}
}
MessageFlags.AUDIO_LISTENED -> { // audio message listened
val eventToSend = LongPollParsedEvent.AudioMessageListened(
peerId = peerId,
messageId = messageId
)
eventsToSend += eventToSend
listenersMap[LongPollEvent.AUDIO_MESSAGE_LISTENED]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.AudioMessageListened>)
?.onEvent(eventToSend)
}
}
}
else -> Unit
}
}
eventsToSend.forEach { eventToSend ->
listenersMap[LongPollEvent.MESSAGE_SET_FLAGS]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent>)?.onEvent(eventToSend)
}
}
}
}
private fun parseMessageSetFlags(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
}
private fun parseMessageClearFlags(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
val messageId = event[1].asInt()
val flags = event[2].asInt()
val peerId = event[3].asInt()
val eventsToSend = mutableListOf<LongPollParsedEvent>()
val parsedFlags = MessageFlags.parse(flags)
coroutineScope.launch {
parsedFlags.forEach { flag ->
when (flag) {
MessageFlags.IMPORTANT -> { // not important anymore
val eventToSend = LongPollParsedEvent.MessageMarkedAsImportant(
peerId = peerId,
messageId = messageId,
marked = false
)
eventsToSend += eventToSend
listenersMap[LongPollEvent.MARKED_AS_IMPORTANT]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageMarkedAsImportant>)
?.onEvent(eventToSend)
}
}
}
MessageFlags.SPAM -> {
if (parsedFlags.contains(MessageFlags.CANCEL_SPAM)) { // not spam anymore
withContext(Dispatchers.IO) {
val message = loadMessage(messageId)
message?.let {
val eventToSend =
LongPollParsedEvent.MessageMarkedAsNotSpam(message = message)
eventsToSend += eventToSend
listenersMap[LongPollEvent.MARKED_AS_NOT_SPAM]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageMarkedAsNotSpam>)
?.onEvent(eventToSend)
}
}
}
}
}
}
MessageFlags.DELETED -> { // restored
withContext(Dispatchers.IO) {
val message = loadMessage(messageId)
message?.let {
val eventToSend =
LongPollParsedEvent.MessageRestored(message = message)
eventsToSend += eventToSend
listenersMap[LongPollEvent.MESSAGE_RESTORED]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageRestored>)
?.onEvent(eventToSend)
}
}
}
}
}
else -> Unit
}
}
eventsToSend.forEach { eventToSend ->
listenersMap[LongPollEvent.MESSAGE_CLEAR_FLAGS]?.let { listeners ->
listeners.map { vkEventCallback ->
vkEventCallback.onEvent(eventToSend)
}
}
}
}
}
private fun parseMessageNew(eventType: ApiEvent, event: List<Any>) {
@@ -161,17 +328,11 @@ class LongPollUpdatesParser(
val messageId = event[1].asInt()
coroutineScope.launch(Dispatchers.IO) {
val newMessageEvent: LongPollEvent.VkMessageNewEvent? =
loadNormalMessage(
eventType,
messageId
)
newMessageEvent?.let { event ->
listenersMap[ApiEvent.MESSAGE_NEW]?.let {
loadMessage(messageId)?.let { message ->
listenersMap[LongPollEvent.MESSAGE_NEW]?.let {
it.map { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageNewEvent>)
.onEvent(event)
(vkEventCallback as VkEventCallback<LongPollParsedEvent.NewMessage>)
.onEvent(LongPollParsedEvent.NewMessage(message))
}
}
}
@@ -182,18 +343,12 @@ class LongPollUpdatesParser(
Log.d("LongPollUpdatesParser", "$eventType: $event")
val messageId = event[1].asInt()
coroutineScope.launch {
val editedMessageEvent: LongPollEvent.VkMessageEditEvent? =
loadNormalMessage(
eventType,
messageId
)
editedMessageEvent?.let { event ->
listenersMap[ApiEvent.MESSAGE_EDIT]?.let {
coroutineScope.launch(Dispatchers.IO) {
loadMessage(messageId)?.let { message ->
listenersMap[LongPollEvent.MESSAGE_EDITED]?.let {
it.map { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageEditEvent>)
.onEvent(event)
(vkEventCallback as VkEventCallback<LongPollParsedEvent.MessageEdited>)
.onEvent(LongPollParsedEvent.MessageEdited(message))
}
}
}
@@ -206,11 +361,11 @@ class LongPollUpdatesParser(
val messageId = event[2].asInt()
val unreadCount = event[3].asInt()
listenersMap[ApiEvent.MESSAGE_READ_INCOMING]?.let { listeners ->
listenersMap[LongPollEvent.INCOMING_MESSAGE_READ]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageReadIncomingEvent>)
(vkEventCallback as VkEventCallback<LongPollParsedEvent.IncomingMessageRead>)
.onEvent(
LongPollEvent.VkMessageReadIncomingEvent(
LongPollParsedEvent.IncomingMessageRead(
peerId = peerId,
messageId = messageId,
unreadCount = unreadCount
@@ -226,11 +381,11 @@ class LongPollUpdatesParser(
val messageId = event[2].asInt()
val unreadCount = event[3].asInt()
listenersMap[ApiEvent.MESSAGE_READ_OUTGOING]?.let { listeners ->
listenersMap[LongPollEvent.OUTGOING_MESSAGE_READ]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollEvent.VkMessageReadOutgoingEvent>)
(vkEventCallback as VkEventCallback<LongPollParsedEvent.OutgoingMessageRead>)
.onEvent(
LongPollEvent.VkMessageReadOutgoingEvent(
LongPollParsedEvent.OutgoingMessageRead(
peerId = peerId,
messageId = messageId,
unreadCount = unreadCount
@@ -240,14 +395,72 @@ class LongPollUpdatesParser(
}
}
private fun parseMessagesDeleted(eventType: ApiEvent, event: List<Any>) {
private fun parseChatClearFlags(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
}
private suspend inline fun <reified T : LongPollEvent> loadNormalMessage(
eventType: ApiEvent,
messageId: Int
): T? = suspendCoroutine { continuation ->
private fun parseChatSetFlags(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
}
private fun parseMessagesDeleted(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asInt()
val messageId = event[2].asInt()
listenersMap[LongPollEvent.CHAT_CLEARED]?.let { listeners ->
listeners.forEach { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollParsedEvent.ChatCleared>)
.onEvent(
LongPollParsedEvent.ChatCleared(
peerId = peerId,
toMessageId = messageId
)
)
}
}
}
private fun parseChatMajorChanged(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asInt()
val majorId = event[2].asInt()
listenersMap[LongPollEvent.CHAT_MAJOR_CHANGED]?.let { listeners ->
listeners.forEach { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollParsedEvent.ChatMajorChanged>)
.onEvent(
LongPollParsedEvent.ChatMajorChanged(
peerId = peerId,
majorId = majorId,
)
)
}
}
}
private fun parseChatMinorChanged(eventType: ApiEvent, event: List<Any>) {
Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asInt()
val minorId = event[2].asInt()
listenersMap[LongPollEvent.CHAT_MINOR_CHANGED]?.let { listeners ->
listeners.forEach { vkEventCallback ->
(vkEventCallback as VkEventCallback<LongPollParsedEvent.ChatMinorChanged>)
.onEvent(
LongPollParsedEvent.ChatMinorChanged(
peerId = peerId,
minorId = minorId,
)
)
}
}
}
private suspend fun loadMessage(messageId: Int): VkMessage? = suspendCoroutine { continuation ->
coroutineScope.launch(Dispatchers.IO) {
messagesUseCase.getById(
messageIds = listOf(messageId),
@@ -256,10 +469,11 @@ class LongPollUpdatesParser(
).listenValue(this) { state ->
state.processState(
error = { error ->
Log.e("LongPollUpdatesParser", "loadNormalMessage: error: $error")
Log.e("LongPollUpdatesParser", "loadMessage: error: $error")
continuation.resume(null)
},
success = { messages ->
val message = messages.singleOrNull() ?: run {
success = { response ->
val message = response.singleOrNull() ?: run {
continuation.resume(null)
return@listenValue
}
@@ -267,107 +481,113 @@ class LongPollUpdatesParser(
VkMemoryCache[message.id] = message
messagesUseCase.storeMessage(message)
val resumeValue: LongPollEvent? = when (eventType) {
ApiEvent.MESSAGE_NEW -> LongPollEvent.VkMessageNewEvent(message)
ApiEvent.MESSAGE_EDIT -> LongPollEvent.VkMessageEditEvent(message)
else -> {
continuation.resume(null)
null
}
}
resumeValue?.let { value -> continuation.resume(value as T) }
continuation.resume(message)
}
)
}
}
}
private fun <T : LongPollEvent> registerListener(
eventType: ApiEvent,
@Suppress("UNCHECKED_CAST")
private fun <T : LongPollParsedEvent> registerListener(
eventType: LongPollEvent,
listener: VkEventCallback<T>
) {
listenersMap.let { map ->
map[eventType] = (map[eventType] ?: mutableListOf()).also { it.add(listener) }
map[eventType] = (map[eventType] ?: mutableListOf())
.also {
it.add(listener as VkEventCallback<LongPollParsedEvent>)
}
}
}
private fun <T : LongPollEvent> registerListeners(
eventTypes: List<ApiEvent>,
private fun <T : LongPollParsedEvent> registerListeners(
eventTypes: List<LongPollEvent>,
listener: VkEventCallback<T>
) {
eventTypes.forEach { eventType -> registerListener(eventType, listener) }
}
fun onConversationPinStateChanged(listener: VkEventCallback<LongPollEvent.VkConversationPinStateChangedEvent>) {
registerListener(ApiEvent.PIN_UNPIN_CONVERSATION, listener)
fun onMessageSetFlags(block: (LongPollParsedEvent) -> Unit) {
registerListener(LongPollEvent.MESSAGE_SET_FLAGS, assembleEventCallback(block))
}
fun onConversationPinStateChanged(block: (LongPollEvent.VkConversationPinStateChangedEvent) -> Unit) {
onConversationPinStateChanged(assembleEventCallback(block))
fun onMessageMarkedAsImportant(block: (LongPollParsedEvent.MessageMarkedAsImportant) -> Unit) {
registerListener(LongPollEvent.MARKED_AS_IMPORTANT, assembleEventCallback(block))
}
fun onMessageIncomingRead(listener: VkEventCallback<LongPollEvent.VkMessageReadIncomingEvent>) {
registerListener(ApiEvent.MESSAGE_READ_INCOMING, listener)
fun onMessageMarkedAsSpam(block: (LongPollParsedEvent.MessageMarkedAsSpam) -> Unit) {
registerListener(LongPollEvent.MARKED_AS_SPAM, assembleEventCallback(block))
}
fun onMessageIncomingRead(block: (LongPollEvent.VkMessageReadIncomingEvent) -> Unit) {
onMessageIncomingRead(assembleEventCallback(block))
fun onMessageDeleted(block: (LongPollParsedEvent.MessageDeleted) -> Unit) {
registerListener(LongPollEvent.MESSAGE_DELETED, assembleEventCallback(block))
}
fun onMessageOutgoingRead(listener: VkEventCallback<LongPollEvent.VkMessageReadOutgoingEvent>) {
registerListener(ApiEvent.MESSAGE_READ_OUTGOING, listener)
fun onMessageClearFlags(block: (LongPollParsedEvent) -> Unit) {
registerListener(LongPollEvent.MESSAGE_CLEAR_FLAGS, assembleEventCallback(block))
}
fun onMessageOutgoingRead(block: (LongPollEvent.VkMessageReadOutgoingEvent) -> Unit) {
onMessageOutgoingRead(assembleEventCallback(block))
fun onMessageMarkedAsNotSpam(block: (LongPollParsedEvent.MessageMarkedAsNotSpam) -> Unit) {
registerListener(LongPollEvent.MARKED_AS_NOT_SPAM, assembleEventCallback(block))
}
fun onNewMessage(listener: VkEventCallback<LongPollEvent.VkMessageNewEvent>) {
registerListener(ApiEvent.MESSAGE_NEW, listener)
fun onMessageRestored(block: (LongPollParsedEvent.MessageRestored) -> Unit) {
registerListener(LongPollEvent.MESSAGE_RESTORED, assembleEventCallback(block))
}
fun onNewMessage(block: (LongPollEvent.VkMessageNewEvent) -> Unit) {
onNewMessage(assembleEventCallback(block))
fun onNewMessage(block: (LongPollParsedEvent.NewMessage) -> Unit) {
registerListener(LongPollEvent.MESSAGE_NEW, assembleEventCallback(block))
}
fun onMessageEdited(listener: VkEventCallback<LongPollEvent.VkMessageEditEvent>) {
registerListener(ApiEvent.MESSAGE_EDIT, listener)
fun onMessageEdited(block: (LongPollParsedEvent.MessageEdited) -> Unit) {
registerListener(LongPollEvent.MESSAGE_EDITED, assembleEventCallback(block))
}
fun onMessageEdited(block: (LongPollEvent.VkMessageEditEvent) -> Unit) {
onMessageEdited(assembleEventCallback(block))
fun onMessageIncomingRead(block: (LongPollParsedEvent.IncomingMessageRead) -> Unit) {
registerListener(LongPollEvent.INCOMING_MESSAGE_READ, assembleEventCallback(block))
}
fun onInteractions(listener: VkEventCallback<LongPollEvent.Interaction>) {
fun onMessageOutgoingRead(block: (LongPollParsedEvent.OutgoingMessageRead) -> Unit) {
registerListener(LongPollEvent.OUTGOING_MESSAGE_READ, assembleEventCallback(block))
}
fun onChatCleared(block: (LongPollParsedEvent.ChatCleared) -> Unit) {
registerListener(LongPollEvent.CHAT_CLEARED, assembleEventCallback(block))
}
fun onChatMajorChanged(block: (LongPollParsedEvent.ChatMajorChanged) -> Unit) {
registerListener(LongPollEvent.CHAT_MAJOR_CHANGED, assembleEventCallback(block))
}
fun onChatMinorChanged(block: (LongPollParsedEvent.ChatMinorChanged) -> Unit) {
registerListener(LongPollEvent.CHAT_MINOR_CHANGED, assembleEventCallback(block))
}
fun onInteractions(block: (LongPollParsedEvent.Interaction) -> Unit) {
registerListeners(
eventTypes = listOf(
ApiEvent.TYPING,
ApiEvent.AUDIO_MESSAGE_RECORDING,
ApiEvent.PHOTO_UPLOADING,
ApiEvent.VIDEO_UPLOADING,
ApiEvent.FILE_UPLOADING
LongPollEvent.TYPING,
LongPollEvent.AUDIO_MESSAGE_RECORDING,
LongPollEvent.PHOTO_UPLOADING,
LongPollEvent.VIDEO_UPLOADING,
LongPollEvent.FILE_UPLOADING
),
listener = listener
listener = assembleEventCallback(block)
)
}
fun onInteractions(block: (LongPollEvent.Interaction) -> Unit) {
onInteractions(assembleEventCallback(block))
}
fun clearListeners() {
listenersMap.clear()
}
}
internal inline fun <R : LongPollEvent> assembleEventCallback(
internal inline fun <R : LongPollParsedEvent> assembleEventCallback(
crossinline block: (R) -> Unit,
): VkEventCallback<R> {
return VkEventCallback { event -> block.invoke(event) }
}
fun interface VkEventCallback<in T : LongPollEvent> {
fun interface VkEventCallback<in T : LongPollParsedEvent> {
fun onEvent(event: T)
}
@@ -7,8 +7,11 @@ enum class ApiEvent(val value: Int) {
MESSAGE_EDIT(5),
MESSAGE_READ_INCOMING(6),
MESSAGE_READ_OUTGOING(7),
CHAT_CLEAR_FLAGS(10),
CHAT_SET_FLAGS(12),
MESSAGES_DELETED(13),
PIN_UNPIN_CONVERSATION(20),
CHAT_MAJOR_CHANGED(20),
CHAT_MINOR_CHANGED(21),
TYPING(63),
AUDIO_MESSAGE_RECORDING(64),
PHOTO_UPLOADING(65),
@@ -0,0 +1,17 @@
package dev.meloda.fast.model
enum class ConversationFlags(val value: Int) {
DISABLE_PUSH(16),
DISABLE_SOUND(32),
INCOMING_CHAT_REQUEST(256),
DECLINED_CHAT_REQUEST(512),
MENTION(1024),
HIDE_CHAT_FROM_SEARCH(2048),
BUSINESS_CHAT(8192),
MARKED_MESSAGE(16384), // mention or disappearing message
DO_NOT_NOTIFY_MENTIONS_ALL_ONLINE(262144),
DO_NOT_NOTIFY_ALL_MENTIONS(524288),
MARKED_AS_UNREAD(1048576),
ARCHIVED(8388608),
CALL_IN_PROGRESS(16777216),
}
@@ -1,45 +1,27 @@
package dev.meloda.fast.model
import dev.meloda.fast.model.api.domain.VkMessage
sealed interface LongPollEvent {
data class VkMessageNewEvent(val message: VkMessage) : LongPollEvent
data class VkMessageEditEvent(val message: VkMessage) : LongPollEvent
data class VkMessageReadIncomingEvent(
val peerId: Int,
val messageId: Int,
val unreadCount: Int,
) : LongPollEvent
data class VkMessageReadOutgoingEvent(
val peerId: Int,
val messageId: Int,
val unreadCount: Int,
) : LongPollEvent
data class VkConversationPinStateChangedEvent(
val peerId: Int,
val majorId: Int,
) : LongPollEvent
data class Interaction(
val interactionType: InteractionType,
val peerId: Int,
val userIds: List<Int>,
val totalCount: Int,
val timestamp: Int
) : LongPollEvent
data class UnreadCounter(
val unread: Int,
val unreadUnmuted: Int,
val showOnlyMuted: Boolean,
val business: Int,
val archive: Int,
val archiveUnmuted: Int,
val archiveMentions: Int
): LongPollEvent
enum class LongPollEvent {
MESSAGE_SET_FLAGS,
MESSAGE_CLEAR_FLAGS,
MESSAGE_NEW,
MESSAGE_EDITED,
INCOMING_MESSAGE_READ,
OUTGOING_MESSAGE_READ,
CHAT_SET_FLAGS,
CHAT_CLEAR_FLAGS,
CHAT_MAJOR_CHANGED,
CHAT_MINOR_CHANGED,
TYPING,
AUDIO_MESSAGE_RECORDING,
PHOTO_UPLOADING,
VIDEO_UPLOADING,
FILE_UPLOADING,
UNREAD_COUNTER_UPDATE,
MARKED_AS_IMPORTANT,
MARKED_AS_SPAM,
MARKED_AS_NOT_SPAM,
MESSAGE_DELETED,
MESSAGE_RESTORED,
AUDIO_MESSAGE_LISTENED,
CHAT_CLEARED
}
@@ -0,0 +1,85 @@
package dev.meloda.fast.model
import dev.meloda.fast.model.api.domain.VkMessage
sealed interface LongPollParsedEvent {
data class NewMessage(val message: VkMessage) : LongPollParsedEvent
data class MessageEdited(val message: VkMessage) : LongPollParsedEvent
data class IncomingMessageRead(
val peerId: Int,
val messageId: Int,
val unreadCount: Int,
) : LongPollParsedEvent
data class OutgoingMessageRead(
val peerId: Int,
val messageId: Int,
val unreadCount: Int,
) : LongPollParsedEvent
data class ChatMajorChanged(
val peerId: Int,
val majorId: Int,
) : LongPollParsedEvent
data class ChatMinorChanged(
val peerId: Int,
val minorId: Int
) : LongPollParsedEvent
data class Interaction(
val interactionType: InteractionType,
val peerId: Int,
val userIds: List<Int>,
val totalCount: Int,
val timestamp: Int
) : LongPollParsedEvent
data class UnreadCounter(
val unread: Int,
val unreadUnmuted: Int,
val showOnlyMuted: Boolean,
val business: Int,
val archive: Int,
val archiveUnmuted: Int,
val archiveMentions: Int
) : LongPollParsedEvent
data class MessageMarkedAsImportant(
val peerId: Int,
val messageId: Int,
val marked: Boolean
) : LongPollParsedEvent
data class MessageMarkedAsSpam(
val peerId: Int,
val messageId: Int
) : LongPollParsedEvent
data class MessageMarkedAsNotSpam(
val message: VkMessage
) : LongPollParsedEvent
data class MessageDeleted(
val peerId: Int,
val messageId: Int,
val forAll: Boolean
) : LongPollParsedEvent
data class MessageRestored(
val message: VkMessage
) : LongPollParsedEvent
data class AudioMessageListened(
val peerId: Int,
val messageId: Int
) : LongPollParsedEvent
data class ChatCleared(
val peerId: Int,
val toMessageId: Int
): LongPollParsedEvent
}
@@ -0,0 +1,31 @@
package dev.meloda.fast.model
enum class MessageFlags(val value: Int) {
UNREAD(1),
OUTGOING(2),
IMPORTANT(8),
SPAM(64),
DELETED(128),
AUDIO_LISTENED(4096),
FROM_GROUP_CHAT(8192),
CANCEL_SPAM(32768),
DELETED_FOR_ALL(131072),
DO_NOT_SHOW_NOTIFICATION(1048576),
MESSAGE_WITH_REPLY(2097152),
REACTION(16777216);
companion object {
fun parse(mask: Int): List<MessageFlags> {
val flags = mutableListOf<MessageFlags>()
entries.forEach { flag ->
if (mask and flag.value > 0) {
flags.add(flag)
}
}
return flags
}
}
}
@@ -23,7 +23,7 @@ import dev.meloda.fast.domain.LongPollUpdatesParser
import dev.meloda.fast.domain.MessagesUseCase
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.model.InteractionType
import dev.meloda.fast.model.LongPollEvent
import dev.meloda.fast.model.LongPollParsedEvent
import dev.meloda.fast.model.api.domain.VkConversation
import dev.meloda.fast.network.VkErrorCode
import dev.meloda.fast.ui.model.api.ConversationOption
@@ -104,8 +104,10 @@ class ConversationsViewModelImpl(
updatesParser.onMessageEdited(::handleEditedMessage)
updatesParser.onMessageIncomingRead(::handleReadIncomingMessage)
updatesParser.onMessageOutgoingRead(::handleReadOutgoingMessage)
updatesParser.onConversationPinStateChanged(::handlePinStateChanged)
updatesParser.onInteractions(::handleInteraction)
updatesParser.onChatMajorChanged(::handleChatMajorChanged)
updatesParser.onChatMinorChanged(::handleChatMinorChanged)
updatesParser.onChatCleared(::handleChatClearing)
loadConversations()
}
@@ -383,11 +385,11 @@ class ConversationsViewModelImpl(
},
success = {
handlePinStateChanged(
LongPollEvent.VkConversationPinStateChangedEvent(
handleChatMajorChanged(
LongPollParsedEvent.ChatMajorChanged(
peerId = peerId,
majorId = if (pin) {
(pinnedConversationsCount.value + 1) * 16
pinnedConversationsCount.value.plus(1) * 16
} else {
0
}
@@ -400,7 +402,7 @@ class ConversationsViewModelImpl(
}
}
private fun handleNewMessage(event: LongPollEvent.VkMessageNewEvent) {
private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) {
val message = event.message
val newConversations = conversations.value.toMutableList()
@@ -487,7 +489,7 @@ class ConversationsViewModelImpl(
}
}
private fun handleEditedMessage(event: LongPollEvent.VkMessageEditEvent) {
private fun handleEditedMessage(event: LongPollParsedEvent.MessageEdited) {
val message = event.message
val newConversations = conversations.value.toMutableList()
@@ -516,7 +518,7 @@ class ConversationsViewModelImpl(
}
}
private fun handleReadIncomingMessage(event: LongPollEvent.VkMessageReadIncomingEvent) {
private fun handleReadIncomingMessage(event: LongPollParsedEvent.IncomingMessageRead) {
val newConversations = conversations.value.toMutableList()
val conversationIndex =
@@ -546,7 +548,7 @@ class ConversationsViewModelImpl(
}
}
private fun handleReadOutgoingMessage(event: LongPollEvent.VkMessageReadOutgoingEvent) {
private fun handleReadOutgoingMessage(event: LongPollParsedEvent.OutgoingMessageRead) {
val newConversations = conversations.value.toMutableList()
val conversationIndex =
@@ -575,47 +577,113 @@ class ConversationsViewModelImpl(
}
}
private fun handlePinStateChanged(event: LongPollEvent.VkConversationPinStateChangedEvent) {
var pinnedCount = pinnedConversationsCount.value
private fun handleChatMajorChanged(event: LongPollParsedEvent.ChatMajorChanged) {
val newConversations = conversations.value.toMutableList()
val conversationIndex =
newConversations.indexOfFirstOrNull { it.id == event.peerId }
if (conversationIndex == null) { // диалога нет в списке
// pizdets
} else {
val pin = event.majorId > 0
newConversations[conversationIndex] =
newConversations[conversationIndex].copy(majorId = event.majorId)
val conversation = newConversations[conversationIndex].copy(majorId = event.majorId)
conversations.setValue { newConversations }
screenState.setValue { old ->
old.copy(
conversations = newConversations.map {
it.asPresentation(
resources = resources,
useContactName = useContactNames
)
}
)
}
sortConversations()
}
}
newConversations.removeAt(conversationIndex)
private fun handleChatMinorChanged(event: LongPollParsedEvent.ChatMinorChanged) {
val newConversations = conversations.value.toMutableList()
val conversationIndex =
newConversations.indexOfFirstOrNull { it.id == event.peerId }
if (pin) {
newConversations.add(0, conversation)
} else {
pinnedCount -= 1
if (conversationIndex == null) { // диалога нет в списке
// pizdets
} else {
newConversations[conversationIndex] =
newConversations[conversationIndex].copy(minorId = event.minorId)
newConversations.add(conversation)
conversations.setValue { newConversations }
screenState.setValue { old ->
old.copy(
conversations = newConversations.map {
it.asPresentation(
resources = resources,
useContactName = useContactNames
)
}
)
}
sortConversations()
}
}
val pinnedSubList = newConversations.filter(VkConversation::isPinned)
val unpinnedSubList = newConversations
.filterNot(VkConversation::isPinned)
.sortedByDescending { it.lastMessage?.date }
private fun sortConversations() {
val newConversations = conversations.value.toMutableList()
val pinnedConversations = newConversations
.filter(VkConversation::isPinned)
.sortedWith { c1, c2 ->
val diff = c2.majorId - c1.majorId
newConversations.clear()
newConversations += pinnedSubList + unpinnedSubList
if (diff == 0) {
c2.minorId - c1.minorId
} else {
diff
}
}
conversations.update { newConversations }
newConversations.removeAll(pinnedConversations)
newConversations.sortWith { c1, c2 ->
(c2.lastMessage?.date ?: 0) - (c1.lastMessage?.date ?: 0)
}
screenState.setValue { old ->
old.copy(conversations = newConversations.map {
newConversations.addAll(0, pinnedConversations)
conversations.update { newConversations }
screenState.setValue { old ->
old.copy(
conversations = newConversations.map {
it.asPresentation(
resources = resources,
useContactName = useContactNames
)
})
}
)
}
}
private fun handleChatClearing(event: LongPollParsedEvent.ChatCleared) {
val newConversations = conversations.value.toMutableList()
val conversationIndex = newConversations.indexOfFirstOrNull { it.id == event.peerId }
if (conversationIndex == null) { // диалога нет в списке
// pizdets
} else {
newConversations.removeAt(conversationIndex)
conversations.setValue { newConversations }
screenState.setValue { old ->
old.copy(
conversations = newConversations.map {
it.asPresentation(
resources = resources,
useContactName = useContactNames
)
}
)
}
}
}
@@ -629,7 +697,7 @@ class ConversationsViewModelImpl(
private object NewInteractionException : CancellationException()
private fun handleInteraction(event: LongPollEvent.Interaction) {
private fun handleInteraction(event: LongPollParsedEvent.Interaction) {
val interactionType = event.interactionType
val peerId = event.peerId
val userIds = event.userIds
@@ -32,7 +32,7 @@ import dev.meloda.fast.messageshistory.util.extractAvatar
import dev.meloda.fast.messageshistory.util.extractTitle
import dev.meloda.fast.messageshistory.util.findMessageById
import dev.meloda.fast.model.BaseError
import dev.meloda.fast.model.LongPollEvent
import dev.meloda.fast.model.LongPollParsedEvent
import dev.meloda.fast.model.api.domain.VkAttachment
import dev.meloda.fast.model.api.domain.VkMessage
import kotlinx.coroutines.Dispatchers
@@ -158,7 +158,7 @@ class MessagesHistoryViewModelImpl(
loadMessagesHistory()
}
private fun handleNewMessage(event: LongPollEvent.VkMessageNewEvent) {
private fun handleNewMessage(event: LongPollParsedEvent.NewMessage) {
val message = event.message
Log.d("MessagesHistoryViewModel", "handleNewMessage: $message")
@@ -200,7 +200,7 @@ class MessagesHistoryViewModelImpl(
screenState.setValue { old -> old.copy(messages = newMessages) }
}
private fun handleEditedMessage(event: LongPollEvent.VkMessageEditEvent) {
private fun handleEditedMessage(event: LongPollParsedEvent.MessageEdited) {
val message = event.message
if (message.peerId != screenState.value.conversationId) return
@@ -223,7 +223,7 @@ class MessagesHistoryViewModelImpl(
}
}
private fun handleReadIncomingEvent(event: LongPollEvent.VkMessageReadIncomingEvent) {
private fun handleReadIncomingEvent(event: LongPollParsedEvent.IncomingMessageRead) {
if (event.peerId != screenState.value.conversationId) return
val messages = messages.value
@@ -257,7 +257,7 @@ class MessagesHistoryViewModelImpl(
}
}
private fun handleReadOutgoingEvent(event: LongPollEvent.VkMessageReadOutgoingEvent) {
private fun handleReadOutgoingEvent(event: LongPollParsedEvent.OutgoingMessageRead) {
if (event.peerId != screenState.value.conversationId) return
val messages = messages.value