refactor(longpoll): route parsed long poll events through dedicated handler and persist message/conversation state updates

This commit is contained in:
2026-05-30 17:17:32 +03:00
parent f11b8dc6f4
commit fc3b3cfcb3
14 changed files with 889 additions and 274 deletions
@@ -19,6 +19,7 @@ import dev.meloda.fast.common.model.LongPollState
import dev.meloda.fast.data.UserConfig import dev.meloda.fast.data.UserConfig
import dev.meloda.fast.data.processState import dev.meloda.fast.data.processState
import dev.meloda.fast.datastore.AppSettings import dev.meloda.fast.datastore.AppSettings
import dev.meloda.fast.domain.LongPollEventsHandler
import dev.meloda.fast.domain.LongPollUpdatesParser import dev.meloda.fast.domain.LongPollUpdatesParser
import dev.meloda.fast.domain.LongPollUseCase import dev.meloda.fast.domain.LongPollUseCase
import dev.meloda.fast.model.api.data.LongPollUpdates import dev.meloda.fast.model.api.data.LongPollUpdates
@@ -56,6 +57,7 @@ class LongPollingService : Service() {
private val longPollUseCase: LongPollUseCase by inject() private val longPollUseCase: LongPollUseCase by inject()
private val updatesParser: LongPollUpdatesParser by inject() private val updatesParser: LongPollUpdatesParser by inject()
private val eventsHandler: LongPollEventsHandler by inject()
private var currentJob: Job? = null private var currentJob: Job? = null
@@ -193,7 +195,7 @@ class LongPollingService : Service() {
if (updates == null) { if (updates == null) {
failCount++ failCount++
} else { } else {
updates.forEach(updatesParser::parseNextUpdate) parseUpdates(updates)
} }
lastUpdatesResponse = getUpdatesResponse(serverInfo.copy(ts = newTs)) lastUpdatesResponse = getUpdatesResponse(serverInfo.copy(ts = newTs))
@@ -246,6 +248,11 @@ class LongPollingService : Service() {
} }
} }
private suspend fun parseUpdates(updates: List<List<Any>>) {
val parsedUpdates = updates.flatMap { updatesParser.parseNextUpdate(it) }
eventsHandler.handleEvents(parsedUpdates)
}
private fun handleError(throwable: Throwable) { private fun handleError(throwable: Throwable) {
Log.e(TAG, "error: $throwable") Log.e(TAG, "error: $throwable")
@@ -1,5 +1,6 @@
package dev.meloda.fast.service.longpolling.di package dev.meloda.fast.service.longpolling.di
import dev.meloda.fast.domain.LongPollEventsHandler
import dev.meloda.fast.domain.LongPollUpdatesParser import dev.meloda.fast.domain.LongPollUpdatesParser
import dev.meloda.fast.domain.LongPollUseCase import dev.meloda.fast.domain.LongPollUseCase
import dev.meloda.fast.domain.LongPollUseCaseImpl import dev.meloda.fast.domain.LongPollUseCaseImpl
@@ -10,4 +11,5 @@ import org.koin.dsl.module
val longPollModule = module { val longPollModule = module {
singleOf(::LongPollUseCaseImpl) bind LongPollUseCase::class singleOf(::LongPollUseCaseImpl) bind LongPollUseCase::class
singleOf(::LongPollUpdatesParser) singleOf(::LongPollUpdatesParser)
singleOf(::LongPollEventsHandler)
} }
@@ -0,0 +1,425 @@
{
"formatVersion": 1,
"database": {
"version": 12,
"identityHash": "5eca3b3da167aaf7e772977a1f4e56e2",
"entities": [
{
"tableName": "users",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `firstName` TEXT NOT NULL, `lastName` TEXT NOT NULL, `isOnline` INTEGER NOT NULL, `isOnlineMobile` INTEGER NOT NULL, `onlineAppId` INTEGER, `lastSeen` INTEGER, `lastSeenStatus` TEXT, `birthday` TEXT, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `photo400Orig` TEXT, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "firstName",
"columnName": "firstName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "lastName",
"columnName": "lastName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isOnline",
"columnName": "isOnline",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isOnlineMobile",
"columnName": "isOnlineMobile",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "onlineAppId",
"columnName": "onlineAppId",
"affinity": "INTEGER"
},
{
"fieldPath": "lastSeen",
"columnName": "lastSeen",
"affinity": "INTEGER"
},
{
"fieldPath": "lastSeenStatus",
"columnName": "lastSeenStatus",
"affinity": "TEXT"
},
{
"fieldPath": "birthday",
"columnName": "birthday",
"affinity": "TEXT"
},
{
"fieldPath": "photo50",
"columnName": "photo50",
"affinity": "TEXT"
},
{
"fieldPath": "photo100",
"columnName": "photo100",
"affinity": "TEXT"
},
{
"fieldPath": "photo200",
"columnName": "photo200",
"affinity": "TEXT"
},
{
"fieldPath": "photo400Orig",
"columnName": "photo400Orig",
"affinity": "TEXT"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "groups",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `screenName` TEXT NOT NULL, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `membersCount` INTEGER, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "name",
"columnName": "name",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "screenName",
"columnName": "screenName",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "photo50",
"columnName": "photo50",
"affinity": "TEXT"
},
{
"fieldPath": "photo100",
"columnName": "photo100",
"affinity": "TEXT"
},
{
"fieldPath": "photo200",
"columnName": "photo200",
"affinity": "TEXT"
},
{
"fieldPath": "membersCount",
"columnName": "membersCount",
"affinity": "INTEGER"
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "messages",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `cmId` INTEGER NOT NULL, `text` TEXT, `isOut` INTEGER NOT NULL, `peerId` INTEGER NOT NULL, `fromId` INTEGER NOT NULL, `date` INTEGER NOT NULL, `randomId` INTEGER NOT NULL, `action` TEXT, `actionMemberId` INTEGER, `actionText` TEXT, `actionCmId` INTEGER, `actionMessage` TEXT, `updateTime` INTEGER, `isImportant` INTEGER NOT NULL, `forwardIds` TEXT, `attachments` TEXT, `replyMessageId` INTEGER, `geoType` TEXT, `pinnedAt` INTEGER, `isPinned` INTEGER NOT NULL, `isDeleted` INTEGER NOT NULL, `isSpam` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "cmId",
"columnName": "cmId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "text",
"columnName": "text",
"affinity": "TEXT"
},
{
"fieldPath": "isOut",
"columnName": "isOut",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "peerId",
"columnName": "peerId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "fromId",
"columnName": "fromId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "date",
"columnName": "date",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "randomId",
"columnName": "randomId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "action",
"columnName": "action",
"affinity": "TEXT"
},
{
"fieldPath": "actionMemberId",
"columnName": "actionMemberId",
"affinity": "INTEGER"
},
{
"fieldPath": "actionText",
"columnName": "actionText",
"affinity": "TEXT"
},
{
"fieldPath": "actionCmId",
"columnName": "actionCmId",
"affinity": "INTEGER"
},
{
"fieldPath": "actionMessage",
"columnName": "actionMessage",
"affinity": "TEXT"
},
{
"fieldPath": "updateTime",
"columnName": "updateTime",
"affinity": "INTEGER"
},
{
"fieldPath": "isImportant",
"columnName": "isImportant",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "forwardIds",
"columnName": "forwardIds",
"affinity": "TEXT"
},
{
"fieldPath": "attachments",
"columnName": "attachments",
"affinity": "TEXT"
},
{
"fieldPath": "replyMessageId",
"columnName": "replyMessageId",
"affinity": "INTEGER"
},
{
"fieldPath": "geoType",
"columnName": "geoType",
"affinity": "TEXT"
},
{
"fieldPath": "pinnedAt",
"columnName": "pinnedAt",
"affinity": "INTEGER"
},
{
"fieldPath": "isPinned",
"columnName": "isPinned",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isDeleted",
"columnName": "isDeleted",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "isSpam",
"columnName": "isSpam",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
},
{
"tableName": "convos",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `localId` INTEGER NOT NULL, `ownerId` INTEGER, `title` TEXT, `photo50` TEXT, `photo100` TEXT, `photo200` TEXT, `isPhantom` INTEGER NOT NULL, `lastCmId` INTEGER NOT NULL, `inReadCmId` INTEGER NOT NULL, `outReadCmId` INTEGER NOT NULL, `inRead` INTEGER NOT NULL, `outRead` INTEGER NOT NULL, `lastMessageId` INTEGER, `unreadCount` INTEGER NOT NULL, `membersCount` INTEGER, `canChangePin` INTEGER NOT NULL, `canChangeInfo` INTEGER NOT NULL, `majorId` INTEGER NOT NULL, `minorId` INTEGER NOT NULL, `pinnedMessageId` INTEGER, `peerType` TEXT NOT NULL, `isArchived` INTEGER NOT NULL, PRIMARY KEY(`id`))",
"fields": [
{
"fieldPath": "id",
"columnName": "id",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "localId",
"columnName": "localId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "ownerId",
"columnName": "ownerId",
"affinity": "INTEGER"
},
{
"fieldPath": "title",
"columnName": "title",
"affinity": "TEXT"
},
{
"fieldPath": "photo50",
"columnName": "photo50",
"affinity": "TEXT"
},
{
"fieldPath": "photo100",
"columnName": "photo100",
"affinity": "TEXT"
},
{
"fieldPath": "photo200",
"columnName": "photo200",
"affinity": "TEXT"
},
{
"fieldPath": "isPhantom",
"columnName": "isPhantom",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastCmId",
"columnName": "lastCmId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "inReadCmId",
"columnName": "inReadCmId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "outReadCmId",
"columnName": "outReadCmId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "inRead",
"columnName": "inRead",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "outRead",
"columnName": "outRead",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "lastMessageId",
"columnName": "lastMessageId",
"affinity": "INTEGER"
},
{
"fieldPath": "unreadCount",
"columnName": "unreadCount",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "membersCount",
"columnName": "membersCount",
"affinity": "INTEGER"
},
{
"fieldPath": "canChangePin",
"columnName": "canChangePin",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "canChangeInfo",
"columnName": "canChangeInfo",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "majorId",
"columnName": "majorId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "minorId",
"columnName": "minorId",
"affinity": "INTEGER",
"notNull": true
},
{
"fieldPath": "pinnedMessageId",
"columnName": "pinnedMessageId",
"affinity": "INTEGER"
},
{
"fieldPath": "peerType",
"columnName": "peerType",
"affinity": "TEXT",
"notNull": true
},
{
"fieldPath": "isArchived",
"columnName": "isArchived",
"affinity": "INTEGER",
"notNull": true
}
],
"primaryKey": {
"autoGenerate": false,
"columnNames": [
"id"
]
}
}
],
"setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5eca3b3da167aaf7e772977a1f4e56e2')"
]
}
}
@@ -21,7 +21,7 @@ import dev.meloda.fast.model.database.VkUserEntity
VkConvoEntity::class VkConvoEntity::class
], ],
version = 11 version = 12
) )
@TypeConverters(Converters::class) @TypeConverters(Converters::class)
abstract class CacheDatabase : RoomDatabase() { abstract class CacheDatabase : RoomDatabase() {
@@ -13,7 +13,7 @@ abstract class ConvoDao : EntityDao<VkConvoEntity> {
abstract suspend fun getAll(): List<VkConvoEntity> abstract suspend fun getAll(): List<VkConvoEntity>
@Query("SELECT * FROM convos WHERE id IN (:ids)") @Query("SELECT * FROM convos WHERE id IN (:ids)")
abstract suspend fun getAllByIds(ids: List<Int>): List<VkConvoEntity> abstract suspend fun getAllByIds(ids: List<Long>): List<VkConvoEntity>
@Query("SELECT * FROM convos WHERE id IS (:id)") @Query("SELECT * FROM convos WHERE id IS (:id)")
abstract suspend fun getById(id: Long): VkConvoEntity? abstract suspend fun getById(id: Long): VkConvoEntity?
@@ -23,8 +23,23 @@ abstract class ConvoDao : EntityDao<VkConvoEntity> {
abstract suspend fun getByIdWithMessage(id: Long): ConvoWithMessage? abstract suspend fun getByIdWithMessage(id: Long): ConvoWithMessage?
@Query("DELETE FROM convos WHERE rowid IN (:ids)") @Query("DELETE FROM convos WHERE rowid IN (:ids)")
abstract suspend fun deleteByIds(ids: List<Int>): Int abstract suspend fun deleteByIds(ids: List<Long>): Int
@Query("UPDATE convos SET inReadCmId = :cmId, unreadCount = :unreadCount WHERE id = :convoId")
abstract suspend fun updateReadIncoming(convoId: Long, cmId: Long, unreadCount: Int): Int
@Query("UPDATE convos SET outReadCmId = :cmId, unreadCount = :unreadCount WHERE id = :convoId")
abstract suspend fun updateReadOutgoing(convoId: Long, cmId: Long, unreadCount: Int): Int
@Query("UPDATE convos SET isArchived = :isArchived WHERE id = :convoId")
abstract suspend fun updateIsArchived(convoId: Long, isArchived: Boolean): Int
@Query("UPDATE convos SET majorId = :majorId WHERE id = :convoId")
abstract suspend fun updateMajorId(convoId: Long, majorId: Int): Int
@Query("UPDATE convos SET minorId = :minorId WHERE id = :convoId")
abstract suspend fun updateMinorId(convoId: Long, minorId: Int): Int
@Query("UPDATE convos SET lastCmId = :cmId WHERE id = :convoId")
abstract suspend fun updateLastCmId(convoId: Long, cmId: Long): Int
} }
@@ -7,7 +7,7 @@ import dev.meloda.fast.model.database.VkMessageEntity
@Dao @Dao
abstract class MessageDao : EntityDao<VkMessageEntity> { abstract class MessageDao : EntityDao<VkMessageEntity> {
@Query("SELECT * FROM messages") @Query("SELECT * FROM messages WHERE isDeleted = 0 AND isSpam = 0")
abstract suspend fun getAll(): List<VkMessageEntity> abstract suspend fun getAll(): List<VkMessageEntity>
@Query("SELECT * FROM messages WHERE peerId IS (:convoId)") @Query("SELECT * FROM messages WHERE peerId IS (:convoId)")
@@ -21,4 +21,13 @@ abstract class MessageDao : EntityDao<VkMessageEntity> {
@Query("DELETE FROM messages WHERE id IN (:ids)") @Query("DELETE FROM messages WHERE id IN (:ids)")
abstract suspend fun deleteByIds(ids: List<Int>): Int abstract suspend fun deleteByIds(ids: List<Int>): Int
@Query("UPDATE messages SET isDeleted = :isDeleted WHERE peerId = :convoId AND cmId = :cmId")
abstract suspend fun markAsDeleted(convoId: Long, cmId: Long, isDeleted: Boolean): Int
@Query("UPDATE messages SET isImportant = :isImportant WHERE peerId = :convoId AND cmId = :cmId")
abstract suspend fun markAsImportant(convoId: Long, cmId: Long, isImportant: Boolean): Int
@Query("UPDATE messages SET isSpam = :isSpam WHERE peerId = :convoId AND cmId = :cmId")
abstract suspend fun markAsSpam(convoId: Long, cmId: Long, isSpam: Boolean): Int
} }
@@ -0,0 +1,164 @@
package dev.meloda.fast.domain
import android.util.Log
import dev.meloda.fast.database.dao.ConvoDao
import dev.meloda.fast.database.dao.MessageDao
import dev.meloda.fast.model.LongPollParsedEvent
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob
import kotlin.coroutines.CoroutineContext
class LongPollEventsHandler(
private val convoUseCase: ConvoUseCase,
private val messagesUseCase: MessagesUseCase,
private val convoDao: ConvoDao,
private val messageDao: MessageDao,
) {
private val job = SupervisorJob()
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)
suspend fun handleEvents(events: List<LongPollParsedEvent>) {
events.forEach { handleNextEvent(it) }
}
private suspend fun handleNextEvent(event: LongPollParsedEvent) {
when (event) {
is LongPollParsedEvent.AudioMessageListened -> {
}
is LongPollParsedEvent.ChatArchived -> {
val affectedRows = convoDao.updateIsArchived(
convoId = event.convo.id,
isArchived = event.convo.isArchived
)
Log.d(
"LongPollEventsHandler",
"isArchived ${event.convo.isArchived}: updated $affectedRows rows."
)
}
is LongPollParsedEvent.ChatCleared -> {
val affectedRows = convoDao.updateLastCmId(
convoId = event.peerId,
cmId = event.toCmId
)
Log.d("LongPollEventsHandler", "updateLastCmId: updated $affectedRows rows.")
}
is LongPollParsedEvent.ChatMajorChanged -> {
val affectedRows = convoDao.updateMajorId(
convoId = event.peerId,
majorId = event.majorId
)
Log.d("LongPollEventsHandler", "updateMajorId: updated $affectedRows rows.")
}
is LongPollParsedEvent.ChatMinorChanged -> {
val affectedRows = convoDao.updateMinorId(
convoId = event.peerId,
minorId = event.minorId
)
Log.d("LongPollEventsHandler", "updateMinorId: updated $affectedRows rows.")
}
is LongPollParsedEvent.Interaction -> {
}
is LongPollParsedEvent.MessageCacheClear -> {
messagesUseCase.storeMessage(event.message)
}
is LongPollParsedEvent.MessageDeleted -> {
val affectedRows = messageDao.markAsDeleted(
convoId = event.peerId,
cmId = event.cmId,
isDeleted = true
)
Log.d("LongPollEventsHandler", "markDeleted: updated $affectedRows rows.")
}
is LongPollParsedEvent.MessageEdited -> {
messagesUseCase.storeMessage(event.message)
}
is LongPollParsedEvent.MessageMarkedAsImportant -> {
val affectedRows = messageDao.markAsImportant(
convoId = event.peerId,
cmId = event.cmId,
isImportant = event.marked
)
Log.d("LongPollEventsHandler", "markImportant: updated $affectedRows rows.")
}
is LongPollParsedEvent.MessageMarkedAsNotSpam -> {
messagesUseCase.storeMessage(event.message)
}
is LongPollParsedEvent.MessageMarkedAsSpam -> {
val affectedRows = messageDao.markAsSpam(
convoId = event.peerId,
cmId = event.cmId,
isSpam = true
)
Log.d("LongPollEventsHandler", "markSpam: updated $affectedRows rows.")
}
is LongPollParsedEvent.MessageRestored -> {
messagesUseCase.storeMessage(event.message)
}
is LongPollParsedEvent.MessageUpdated -> {
messagesUseCase.storeMessage(event.message)
}
is LongPollParsedEvent.NewMessage -> {
messagesUseCase.storeMessage(event.message)
}
is LongPollParsedEvent.IncomingMessageRead -> {
val affectedRows = convoDao.updateReadIncoming(
convoId = event.peerId,
cmId = event.cmId,
unreadCount = event.unreadCount
)
Log.d("LongPollEventsHandler", "inMessageRead: updated $affectedRows rows.")
}
is LongPollParsedEvent.OutgoingMessageRead -> {
val affectedRows = convoDao.updateReadOutgoing(
convoId = event.peerId,
cmId = event.cmId,
unreadCount = event.unreadCount
)
Log.d("LongPollEventsHandler", "outMessageRead: updated $affectedRows rows.")
}
is LongPollParsedEvent.UnreadCounter -> {
}
}
}
}
@@ -22,10 +22,9 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.suspendCancellableCoroutine
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
class LongPollUpdatesParser( class LongPollUpdatesParser(
private val convoUseCase: ConvoUseCase, private val convoUseCase: ConvoUseCase,
@@ -47,11 +46,14 @@ class LongPollUpdatesParser(
private val listenersMap: MutableMap<LongPollEvent, MutableList<VkEventCallback<LongPollParsedEvent>>> = private val listenersMap: MutableMap<LongPollEvent, MutableList<VkEventCallback<LongPollParsedEvent>>> =
mutableMapOf() mutableMapOf()
fun parseNextUpdate(event: List<Any>) { suspend fun parseNextUpdate(event: List<Any>): List<LongPollParsedEvent> {
val eventId = event.first().asInt() val eventId = event.first().asInt()
when (val eventType = ApiEvent.parseOrNull(eventId)) { return when (val eventType = ApiEvent.parseOrNull(eventId)) {
null -> Log.d("LongPollUpdatesParser", "parseNextUpdate: unknownEvent: $event") null -> {
Log.d("LongPollUpdatesParser", "parseNextUpdate: unknownEvent: $event")
emptyList()
}
ApiEvent.MESSAGE_SET_FLAGS -> parseMessageSetFlags(eventType, event) ApiEvent.MESSAGE_SET_FLAGS -> parseMessageSetFlags(eventType, event)
ApiEvent.MESSAGE_CLEAR_FLAGS -> parseMessageClearFlags(eventType, event) ApiEvent.MESSAGE_CLEAR_FLAGS -> parseMessageClearFlags(eventType, event)
@@ -77,7 +79,10 @@ class LongPollUpdatesParser(
} }
} }
private fun parseMessageSetFlags(eventType: ApiEvent, event: List<Any>) { private fun parseMessageSetFlags(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> {
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val cmId = event[1].asLong() val cmId = event[1].asLong()
@@ -97,12 +102,8 @@ class LongPollUpdatesParser(
) )
eventsToSend += eventToSend eventsToSend += eventToSend
listenersMap[LongPollEvent.MARKED_AS_IMPORTANT]?.let { listeners -> listenersMap[LongPollEvent.MARKED_AS_IMPORTANT]
listeners.map { vkEventCallback -> ?.forEach { it.onEvent(eventToSend) }
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageMarkedAsImportant>)
?.onEvent(eventToSend)
}
}
} }
MessageFlags.SPAM -> { MessageFlags.SPAM -> {
@@ -111,13 +112,7 @@ class LongPollUpdatesParser(
cmId = cmId cmId = cmId
) )
eventsToSend += eventToSend eventsToSend += eventToSend
listenersMap[LongPollEvent.MARKED_AS_SPAM]?.forEach { it.onEvent(eventToSend) }
listenersMap[LongPollEvent.MARKED_AS_SPAM]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageMarkedAsSpam>)
?.onEvent(eventToSend)
}
}
} }
MessageFlags.DELETED -> { MessageFlags.DELETED -> {
@@ -136,13 +131,7 @@ class LongPollUpdatesParser(
) )
} }
eventsToSend += eventToSend eventsToSend += eventToSend
listenersMap[LongPollEvent.MESSAGE_DELETED]?.forEach { it.onEvent(eventToSend) }
listenersMap[LongPollEvent.MESSAGE_DELETED]?.let { listeners ->
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageDeleted>)
?.onEvent(eventToSend)
}
}
} }
MessageFlags.AUDIO_LISTENED -> { MessageFlags.AUDIO_LISTENED -> {
@@ -152,28 +141,36 @@ class LongPollUpdatesParser(
) )
eventsToSend += eventToSend eventsToSend += eventToSend
listenersMap[LongPollEvent.AUDIO_MESSAGE_LISTENED]?.let { listeners -> listenersMap[LongPollEvent.AUDIO_MESSAGE_LISTENED]
listeners.map { vkEventCallback -> ?.forEach { it.onEvent(eventToSend) }
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.AudioMessageListened>)
?.onEvent(eventToSend)
}
}
} }
else -> Unit MessageFlags.UNREAD -> Unit
MessageFlags.OUTGOING -> Unit
MessageFlags.FROM_GROUP_CHAT -> Unit
MessageFlags.CANCEL_SPAM -> Unit
MessageFlags.DELETED_FOR_ALL -> Unit
MessageFlags.DO_NOT_SHOW_NOTIFICATION -> Unit
MessageFlags.MESSAGE_WITH_REPLY -> Unit
MessageFlags.REACTION -> Unit
} }
} }
eventsToSend.forEach { eventToSend -> eventsToSend.forEach { eventToSend ->
listenersMap[LongPollEvent.MESSAGE_SET_FLAGS]?.let { listeners -> listenersMap[LongPollEvent.MESSAGE_SET_FLAGS]?.let { listeners ->
listeners.map { vkEventCallback -> listeners.forEach { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent>)?.onEvent(eventToSend) vkEventCallback.onEvent(eventToSend)
} }
} }
} }
return eventsToSend
} }
private fun parseMessageClearFlags(eventType: ApiEvent, event: List<Any>) { private suspend fun parseMessageClearFlags(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val cmId = event[1].asLong() val cmId = event[1].asLong()
@@ -184,7 +181,9 @@ class LongPollUpdatesParser(
val parsedFlags = MessageFlags.parse(flags) val parsedFlags = MessageFlags.parse(flags)
coroutineScope.launch { coroutineScope.launch(Dispatchers.IO) {
val message = loadMessage(peerId = peerId, cmId = cmId)
parsedFlags.forEach { flag -> parsedFlags.forEach { flag ->
when (flag) { when (flag) {
MessageFlags.IMPORTANT -> { MessageFlags.IMPORTANT -> {
@@ -195,81 +194,65 @@ class LongPollUpdatesParser(
) )
eventsToSend += eventToSend eventsToSend += eventToSend
listenersMap[LongPollEvent.MARKED_AS_IMPORTANT]?.let { listeners -> listenersMap[LongPollEvent.MARKED_AS_IMPORTANT]
listeners.map { vkEventCallback -> ?.forEach { it.onEvent(eventToSend) }
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageMarkedAsImportant>)
?.onEvent(eventToSend)
}
}
} }
MessageFlags.SPAM -> { MessageFlags.SPAM -> {
if (parsedFlags.contains(MessageFlags.CANCEL_SPAM)) { if (parsedFlags.contains(MessageFlags.CANCEL_SPAM)) {
withContext(Dispatchers.IO) { if (message != null) {
val message = loadMessage( val eventToSend =
peerId = peerId, LongPollParsedEvent.MessageMarkedAsNotSpam(message = message)
cmId = cmId eventsToSend += eventToSend
)
message?.let {
val eventToSend =
LongPollParsedEvent.MessageMarkedAsNotSpam(message = message)
eventsToSend += eventToSend
listenersMap[LongPollEvent.MARKED_AS_NOT_SPAM]?.let { listeners -> listenersMap[LongPollEvent.MARKED_AS_NOT_SPAM]
listeners.map { vkEventCallback -> ?.forEach { it.onEvent(eventToSend) }
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageMarkedAsNotSpam>)
?.onEvent(eventToSend)
}
}
}
} }
} }
} }
MessageFlags.DELETED -> { MessageFlags.DELETED -> {
withContext(Dispatchers.IO) { if (message != null) {
val message = loadMessage( val eventToSend =
peerId = peerId, LongPollParsedEvent.MessageRestored(message = message)
cmId = cmId eventsToSend += eventToSend
)
message?.let {
val eventToSend =
LongPollParsedEvent.MessageRestored(message = message)
eventsToSend += eventToSend
listenersMap[LongPollEvent.MESSAGE_RESTORED]?.let { listeners -> listenersMap[LongPollEvent.MESSAGE_RESTORED]
listeners.map { vkEventCallback -> ?.forEach { it.onEvent(eventToSend) }
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.MessageRestored>)
?.onEvent(eventToSend)
}
}
}
} }
} }
else -> Unit MessageFlags.UNREAD -> Unit
MessageFlags.OUTGOING -> Unit
MessageFlags.AUDIO_LISTENED -> Unit
MessageFlags.FROM_GROUP_CHAT -> Unit
MessageFlags.CANCEL_SPAM -> Unit
MessageFlags.DELETED_FOR_ALL -> Unit
MessageFlags.DO_NOT_SHOW_NOTIFICATION -> Unit
MessageFlags.MESSAGE_WITH_REPLY -> Unit
MessageFlags.REACTION -> Unit
} }
} }
eventsToSend.forEach { eventToSend -> listenersMap[LongPollEvent.MESSAGE_CLEAR_FLAGS]?.forEach { listener ->
listenersMap[LongPollEvent.MESSAGE_CLEAR_FLAGS]?.let { listeners -> eventsToSend.forEach { listener.onEvent(it) }
listeners.map { vkEventCallback ->
vkEventCallback.onEvent(eventToSend)
}
}
} }
continuation.resume(eventsToSend)
} }
} }
private fun parseMessageNew(eventType: ApiEvent, event: List<Any>) { private suspend fun parseMessageNew(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val cmId = event[1].asLong() val cmId = event[1].asLong()
val peerId = event[4].asLong() val peerId = event[4].asLong()
coroutineScope.launch(Dispatchers.IO) { coroutineScope.launch(Dispatchers.IO) {
val message = val message = async { loadMessage(peerId = peerId, cmId = cmId) }.await()
async { loadMessage(peerId = peerId, cmId = cmId) }.await()
val convo = val convo =
async { async {
@@ -280,87 +263,85 @@ class LongPollUpdatesParser(
) )
}.await() }.await()
message?.let { if (message != null) {
listenersMap[LongPollEvent.MESSAGE_NEW]?.let { val event = LongPollParsedEvent.NewMessage(
it.map { vkEventCallback -> message = message,
(vkEventCallback as VkEventCallback<LongPollParsedEvent.NewMessage>) inArchive = convo?.isArchived == true
.onEvent( // TODO: 03-Apr-25, Danil Nikolaev:
LongPollParsedEvent.NewMessage( // load user settings about restoring chats with
message = message, // enabled notifications from archive
inArchive = convo?.isArchived == true )
// TODO: 03-Apr-25, Danil Nikolaev:
// load user settings about restoring chats with listenersMap[LongPollEvent.MESSAGE_NEW]?.forEach { it.onEvent(event) }
// enabled notifications from archive continuation.resume(listOf(event))
) } else {
) continuation.resume(emptyList())
}
}
} }
} }
} }
private fun parseMessageEdit(eventType: ApiEvent, event: List<Any>) { private suspend fun parseMessageEdit(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val cmId = event[1].asLong() val cmId = event[1].asLong()
val peerId = event[3].asLong() val peerId = event[3].asLong()
coroutineScope.launch(Dispatchers.IO) { coroutineScope.launch(Dispatchers.IO) {
loadMessage( val message = loadMessage(peerId = peerId, cmId = cmId)
peerId = peerId, if (message != null) {
cmId = cmId val event = LongPollParsedEvent.MessageEdited(message)
)?.let { message -> listenersMap[LongPollEvent.MESSAGE_EDITED]?.forEach { it.onEvent(event) }
listenersMap[LongPollEvent.MESSAGE_EDITED]?.let { continuation.resume(listOf(event))
it.map { vkEventCallback -> } else {
(vkEventCallback as VkEventCallback<LongPollParsedEvent.MessageEdited>) continuation.resume(emptyList())
.onEvent(LongPollParsedEvent.MessageEdited(message))
}
}
} }
} }
} }
private fun parseMessageReadIncoming(eventType: ApiEvent, event: List<Any>) { private fun parseMessageReadIncoming(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> {
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asLong() val peerId = event[1].asLong()
val cmId = event[2].asLong() val cmId = event[2].asLong()
val unreadCount = event[3].asInt() val unreadCount = event[3].asInt()
listenersMap[LongPollEvent.INCOMING_MESSAGE_READ]?.let { listeners -> val event = LongPollParsedEvent.IncomingMessageRead(
listeners.map { vkEventCallback -> peerId = peerId,
(vkEventCallback as VkEventCallback<LongPollParsedEvent.IncomingMessageRead>) cmId = cmId,
.onEvent( unreadCount = unreadCount
LongPollParsedEvent.IncomingMessageRead( )
peerId = peerId, listenersMap[LongPollEvent.INCOMING_MESSAGE_READ]?.forEach { it.onEvent(event) }
cmId = cmId, return listOf(event)
unreadCount = unreadCount
)
)
}
}
} }
private fun parseMessageReadOutgoing(eventType: ApiEvent, event: List<Any>) { private fun parseMessageReadOutgoing(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> {
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asLong() val peerId = event[1].asLong()
val cmId = event[2].asLong() val cmId = event[2].asLong()
val unreadCount = event[3].asInt() val unreadCount = event[3].asInt()
listenersMap[LongPollEvent.OUTGOING_MESSAGE_READ]?.let { listeners -> val event = LongPollParsedEvent.OutgoingMessageRead(
listeners.map { vkEventCallback -> peerId = peerId,
(vkEventCallback as VkEventCallback<LongPollParsedEvent.OutgoingMessageRead>) cmId = cmId,
.onEvent( unreadCount = unreadCount
LongPollParsedEvent.OutgoingMessageRead( )
peerId = peerId,
cmId = cmId, listenersMap[LongPollEvent.OUTGOING_MESSAGE_READ]?.forEach { it.onEvent(event) }
unreadCount = unreadCount return listOf(event)
)
)
}
}
} }
private fun parseChatClearFlags(eventType: ApiEvent, event: List<Any>) { private suspend fun parseChatClearFlags(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asLong() val peerId = event[1].asLong()
@@ -391,31 +372,36 @@ class LongPollUpdatesParser(
) )
eventsToSend += eventToSend eventsToSend += eventToSend
listenersMap[LongPollEvent.CHAT_ARCHIVED]?.let { listeners -> listenersMap[LongPollEvent.CHAT_ARCHIVED]?.forEach { it.onEvent(eventToSend) }
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.ChatArchived>)
?.onEvent(eventToSend)
}
}
} }
else -> Unit ConvoFlags.DISABLE_PUSH -> Unit
ConvoFlags.DISABLE_SOUND -> Unit
ConvoFlags.INCOMING_CHAT_REQUEST -> Unit
ConvoFlags.DECLINED_CHAT_REQUEST -> Unit
ConvoFlags.MENTION -> Unit
ConvoFlags.HIDE_CHAT_FROM_SEARCH -> Unit
ConvoFlags.BUSINESS_CHAT -> Unit
ConvoFlags.MARKED_MESSAGE -> Unit
ConvoFlags.DO_NOT_NOTIFY_MENTIONS_ALL_ONLINE -> Unit
ConvoFlags.DO_NOT_NOTIFY_ALL_MENTIONS -> Unit
ConvoFlags.MARKED_AS_UNREAD -> Unit
ConvoFlags.CALL_IN_PROGRESS -> Unit
} }
} }
eventsToSend.forEach { eventToSend -> listenersMap[LongPollEvent.CHAT_CLEAR_FLAGS]?.forEach { listener ->
listenersMap[LongPollEvent.CHAT_CLEAR_FLAGS]?.let { listeners -> eventsToSend.forEach { listener.onEvent(it) }
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent>)?.onEvent(
eventToSend
)
}
}
} }
continuation.resume(eventsToSend)
} }
} }
private fun parseChatSetFlags(eventType: ApiEvent, event: List<Any>) { private suspend fun parseChatSetFlags(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asLong() val peerId = event[1].asLong()
@@ -446,88 +432,87 @@ class LongPollUpdatesParser(
) )
eventsToSend += eventToSend eventsToSend += eventToSend
listenersMap[LongPollEvent.CHAT_ARCHIVED]?.let { listeners -> listenersMap[LongPollEvent.CHAT_ARCHIVED]?.forEach { it.onEvent(eventToSend) }
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent.ChatArchived>)
?.onEvent(eventToSend)
}
}
} }
else -> Unit ConvoFlags.DISABLE_PUSH -> Unit
ConvoFlags.DISABLE_SOUND -> Unit
ConvoFlags.INCOMING_CHAT_REQUEST -> Unit
ConvoFlags.DECLINED_CHAT_REQUEST -> Unit
ConvoFlags.MENTION -> Unit
ConvoFlags.HIDE_CHAT_FROM_SEARCH -> Unit
ConvoFlags.BUSINESS_CHAT -> Unit
ConvoFlags.MARKED_MESSAGE -> Unit
ConvoFlags.DO_NOT_NOTIFY_MENTIONS_ALL_ONLINE -> Unit
ConvoFlags.DO_NOT_NOTIFY_ALL_MENTIONS -> Unit
ConvoFlags.MARKED_AS_UNREAD -> Unit
ConvoFlags.CALL_IN_PROGRESS -> Unit
} }
} }
eventsToSend.forEach { eventToSend -> listenersMap[LongPollEvent.CHAT_SET_FLAGS]?.forEach { listener ->
listenersMap[LongPollEvent.CHAT_SET_FLAGS]?.let { listeners -> eventsToSend.forEach { listener.onEvent(it) }
listeners.map { vkEventCallback ->
(vkEventCallback as? VkEventCallback<LongPollParsedEvent>)?.onEvent(
eventToSend
)
}
}
} }
continuation.resume(eventsToSend)
} }
} }
private fun parseMessagesDeleted(eventType: ApiEvent, event: List<Any>) { private fun parseMessagesDeleted(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> {
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asLong() val peerId = event[1].asLong()
val cmId = event[2].asLong() val cmId = event[2].asLong()
listenersMap[LongPollEvent.CHAT_CLEARED]?.let { listeners -> val event = LongPollParsedEvent.ChatCleared(
listeners.forEach { vkEventCallback -> peerId = peerId,
(vkEventCallback as VkEventCallback<LongPollParsedEvent.ChatCleared>) toCmId = cmId
.onEvent( )
LongPollParsedEvent.ChatCleared( listenersMap[LongPollEvent.CHAT_CLEARED]?.forEach { it.onEvent(event) }
peerId = peerId, return listOf(event)
toCmId = cmId
)
)
}
}
} }
private fun parseChatMajorChanged(eventType: ApiEvent, event: List<Any>) { private fun parseChatMajorChanged(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> {
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asLong() val peerId = event[1].asLong()
val majorId = event[2].asInt() val majorId = event[2].asInt()
listenersMap[LongPollEvent.CHAT_MAJOR_CHANGED]?.let { listeners -> val event = LongPollParsedEvent.ChatMajorChanged(
listeners.forEach { vkEventCallback -> peerId = peerId,
(vkEventCallback as VkEventCallback<LongPollParsedEvent.ChatMajorChanged>) majorId = majorId,
.onEvent( )
LongPollParsedEvent.ChatMajorChanged( listenersMap[LongPollEvent.CHAT_MAJOR_CHANGED]?.forEach { it.onEvent(event) }
peerId = peerId, return listOf(event)
majorId = majorId,
)
)
}
}
} }
private fun parseChatMinorChanged(eventType: ApiEvent, event: List<Any>) { private fun parseChatMinorChanged(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> {
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val peerId = event[1].asLong() val peerId = event[1].asLong()
val minorId = event[2].asInt() val minorId = event[2].asInt()
listenersMap[LongPollEvent.CHAT_MINOR_CHANGED]?.let { listeners -> val event = LongPollParsedEvent.ChatMinorChanged(
listeners.forEach { vkEventCallback -> peerId = peerId,
(vkEventCallback as VkEventCallback<LongPollParsedEvent.ChatMinorChanged>) minorId = minorId,
.onEvent( )
LongPollParsedEvent.ChatMinorChanged( listenersMap[LongPollEvent.CHAT_MINOR_CHANGED]?.forEach { it.onEvent(event) }
peerId = peerId, return listOf(event)
minorId = minorId,
)
)
}
}
} }
private fun parseInteraction(eventType: ApiEvent, event: List<Any>) { private fun parseInteraction(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> {
Log.d("LongPollUpdatesParser", "$eventType: $event") Log.d("LongPollUpdatesParser", "$eventType: $event")
val interactionType = when (eventType) { val interactionType = when (eventType) {
@@ -536,7 +521,7 @@ class LongPollUpdatesParser(
ApiEvent.PHOTO_UPLOADING -> InteractionType.Photo ApiEvent.PHOTO_UPLOADING -> InteractionType.Photo
ApiEvent.VIDEO_UPLOADING -> InteractionType.Video ApiEvent.VIDEO_UPLOADING -> InteractionType.Video
ApiEvent.FILE_UPLOADING -> InteractionType.File ApiEvent.FILE_UPLOADING -> InteractionType.File
else -> return else -> return emptyList()
} }
val longPollEvent: LongPollEvent = when (eventType) { val longPollEvent: LongPollEvent = when (eventType) {
@@ -545,7 +530,6 @@ class LongPollUpdatesParser(
ApiEvent.PHOTO_UPLOADING -> LongPollEvent.PHOTO_UPLOADING ApiEvent.PHOTO_UPLOADING -> LongPollEvent.PHOTO_UPLOADING
ApiEvent.VIDEO_UPLOADING -> LongPollEvent.VIDEO_UPLOADING ApiEvent.VIDEO_UPLOADING -> LongPollEvent.VIDEO_UPLOADING
ApiEvent.FILE_UPLOADING -> LongPollEvent.FILE_UPLOADING ApiEvent.FILE_UPLOADING -> LongPollEvent.FILE_UPLOADING
else -> return
} }
val peerId = event[1].asLong() val peerId = event[1].asLong()
@@ -554,25 +538,24 @@ class LongPollUpdatesParser(
val timestamp = event[4].asInt() val timestamp = event[4].asInt()
// if userIds contains only account's id, then we don't need to show our status // if userIds contains only account's id, then we don't need to show our status
if (userIds.isEmpty()) return if (userIds.isEmpty()) return emptyList()
listenersMap[longPollEvent]?.let { listeners -> val event = LongPollParsedEvent.Interaction(
listeners.forEach { vkEventCallback -> interactionType = interactionType,
(vkEventCallback as VkEventCallback<LongPollParsedEvent.Interaction>) peerId = peerId,
.onEvent( userIds = userIds,
LongPollParsedEvent.Interaction( totalCount = totalCount,
interactionType = interactionType, timestamp = timestamp
peerId = peerId, )
userIds = userIds,
totalCount = totalCount, listenersMap[longPollEvent]?.forEach { it.onEvent(event) }
timestamp = timestamp return listOf(event)
)
)
}
}
} }
private fun parseUnreadCounterUpdate(eventType: ApiEvent, event: List<Any>) { private fun parseUnreadCounterUpdate(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> {
Log.d("LongPollUpdatesParser", "$eventType $event") Log.d("LongPollUpdatesParser", "$eventType $event")
val unreadCount = event[1].asInt() val unreadCount = event[1].asInt()
@@ -583,58 +566,57 @@ class LongPollUpdatesParser(
val archiveUnreadUnmutedCount = event[8].asInt() val archiveUnreadUnmutedCount = event[8].asInt()
val archiveMentionsCount = event[9].asInt() val archiveMentionsCount = event[9].asInt()
listenersMap[LongPollEvent.UNREAD_COUNTER_UPDATE]?.let { listeners -> val event = LongPollParsedEvent.UnreadCounter(
listeners.forEach { vkEventCallback -> unread = unreadCount,
(vkEventCallback as VkEventCallback<LongPollParsedEvent.UnreadCounter>) unreadUnmuted = unreadUnmutedCount,
.onEvent( showOnlyMuted = showOnlyMuted,
LongPollParsedEvent.UnreadCounter( business = businessNotifyUnreadCount,
unread = unreadCount, archive = archiveUnreadCount,
unreadUnmuted = unreadUnmutedCount, archiveUnmuted = archiveUnreadUnmutedCount,
showOnlyMuted = showOnlyMuted, archiveMentions = archiveMentionsCount
business = businessNotifyUnreadCount, )
archive = archiveUnreadCount, listenersMap[LongPollEvent.UNREAD_COUNTER_UPDATE]?.forEach { it.onEvent(event) }
archiveUnmuted = archiveUnreadUnmutedCount, return listOf(event)
archiveMentions = archiveMentionsCount
)
)
}
}
} }
private fun parseMessageUpdated(eventType: ApiEvent, event: List<Any>) { private suspend fun parseMessageUpdated(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
Log.d("LongPollUpdatesParser", "$eventType $event") Log.d("LongPollUpdatesParser", "$eventType $event")
val cmId = event[1].asLong() val cmId = event[1].asLong()
val peerId = event[4].asLong() val peerId = event[4].asLong()
coroutineScope.launch(Dispatchers.IO) { coroutineScope.launch(Dispatchers.IO) {
loadMessage( val message = loadMessage(peerId = peerId, cmId = cmId)
peerId = peerId,
cmId = cmId if (message != null) {
)?.let { message -> val event = LongPollParsedEvent.MessageUpdated(message)
listenersMap[LongPollEvent.MESSAGE_UPDATED]?.let { listenersMap[LongPollEvent.MESSAGE_UPDATED]?.forEach { it.onEvent(event) }
it.map { vkEventCallback -> continuation.resume(listOf(event))
(vkEventCallback as VkEventCallback<LongPollParsedEvent.MessageUpdated>) } else {
.onEvent(LongPollParsedEvent.MessageUpdated(message)) continuation.resume(emptyList())
}
}
} }
} }
} }
private fun parseMessageCacheClear(eventType: ApiEvent, event: List<Any>) { private suspend fun parseMessageCacheClear(
eventType: ApiEvent,
event: List<Any>
): List<LongPollParsedEvent> = suspendCancellableCoroutine { continuation ->
Log.d("LongPollUpdatesParser", "$eventType $event") Log.d("LongPollUpdatesParser", "$eventType $event")
val messageId = event[1].asLong() val messageId = event[1].asLong()
coroutineScope.launch(Dispatchers.IO) { coroutineScope.launch(Dispatchers.IO) {
loadMessage(messageId = messageId)?.let { message -> val message = loadMessage(messageId = messageId)
listenersMap[LongPollEvent.MESSAGE_CACHE_CLEAR]?.let { if (message != null) {
it.map { vkEventCallback -> val event = LongPollParsedEvent.MessageCacheClear(message)
(vkEventCallback as VkEventCallback<LongPollParsedEvent.MessageCacheClear>) listenersMap[LongPollEvent.MESSAGE_CACHE_CLEAR]?.forEach { it.onEvent(event) }
.onEvent(LongPollParsedEvent.MessageCacheClear(message)) continuation.resume(listOf(event))
} } else {
} continuation.resume(emptyList())
} }
} }
} }
@@ -643,7 +625,7 @@ class LongPollUpdatesParser(
peerId: Long? = null, peerId: Long? = null,
cmId: Long? = null, cmId: Long? = null,
messageId: Long? = null messageId: Long? = null
): VkMessage? = suspendCoroutine { continuation -> ): VkMessage? = suspendCancellableCoroutine { continuation ->
require((peerId != null && cmId != null) || messageId != null) require((peerId != null && cmId != null) || messageId != null)
coroutineScope.launch(Dispatchers.IO) { coroutineScope.launch(Dispatchers.IO) {
@@ -677,7 +659,7 @@ class LongPollUpdatesParser(
peerId: Long, peerId: Long,
extended: Boolean = false, extended: Boolean = false,
fields: String? = null fields: String? = null
): VkConvo? = suspendCoroutine { continuation -> ): VkConvo? = suspendCancellableCoroutine { continuation ->
coroutineScope.launch(Dispatchers.IO) { coroutineScope.launch(Dispatchers.IO) {
convoUseCase.getById( convoUseCase.getById(
peerIds = listOf(peerId), peerIds = listOf(peerId),
@@ -117,5 +117,6 @@ fun VkMessageData.asDomain(): VkMessage = VkMessage(
pinnedAt = pinnedAt, pinnedAt = pinnedAt,
isPinned = isPinned == true, isPinned = isPinned == true,
formatData = formatData?.asDomain(), formatData = formatData?.asDomain(),
isSpam = false isSpam = false,
isDeleted = false
) )
@@ -56,5 +56,6 @@ data class VkPinnedMessageData(
isPinned = true, isPinned = true,
isSpam = false, isSpam = false,
formatData = null, formatData = null,
isDeleted = false
) )
} }
@@ -36,6 +36,8 @@ data class VkMessage(
val group: VkGroupDomain?, val group: VkGroupDomain?,
val actionUser: VkUser?, val actionUser: VkUser?,
val actionGroup: VkGroupDomain?, val actionGroup: VkGroupDomain?,
val isDeleted: Boolean
) { ) {
fun isPeerChat() = peerId > 2_000_000_000 fun isPeerChat() = peerId > 2_000_000_000
@@ -111,7 +113,7 @@ fun VkMessage.asEntity(): VkMessageEntity = VkMessageEntity(
actionCmId = actionCmId, actionCmId = actionCmId,
actionMessage = actionMessage, actionMessage = actionMessage,
updateTime = updateTime, updateTime = updateTime,
important = isImportant, isImportant = isImportant,
forwardIds = forwards.orEmpty().map(VkMessage::id), forwardIds = forwards.orEmpty().map(VkMessage::id),
// TODO: 05/05/2024, Danil Nikolaev: save attachments // TODO: 05/05/2024, Danil Nikolaev: save attachments
attachments = emptyList(), attachments = emptyList(),
@@ -119,4 +121,6 @@ fun VkMessage.asEntity(): VkMessageEntity = VkMessageEntity(
geoType = geoType, geoType = geoType,
pinnedAt = pinnedAt, pinnedAt = pinnedAt,
isPinned = isPinned, isPinned = isPinned,
isDeleted = isDeleted,
isSpam = isSpam
) )
@@ -21,13 +21,15 @@ data class VkMessageEntity(
val actionCmId: Long?, val actionCmId: Long?,
val actionMessage: String?, val actionMessage: String?,
val updateTime: Int?, val updateTime: Int?,
val important: Boolean, val isImportant: Boolean,
val forwardIds: List<Long>?, val forwardIds: List<Long>?,
val attachments: List<String>?, // TODO: 01/05/2024, Danil Nikolaev: how to store??? val attachments: List<String>?, // TODO: 01/05/2024, Danil Nikolaev: how to store???
val replyMessageId: Long?, val replyMessageId: Long?,
val geoType: String?, val geoType: String?,
val pinnedAt: Int?, val pinnedAt: Int?,
val isPinned: Boolean val isPinned: Boolean,
val isDeleted: Boolean,
val isSpam: Boolean
) )
fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage( fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage(
@@ -45,7 +47,7 @@ fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage(
actionCmId = actionCmId, actionCmId = actionCmId,
actionMessage = actionMessage, actionMessage = actionMessage,
updateTime = updateTime, updateTime = updateTime,
isImportant = important, isImportant = isImportant,
forwards = emptyList(),//forwards.orEmpty().map(VkMessageEntity::asExternalModel), forwards = emptyList(),//forwards.orEmpty().map(VkMessageEntity::asExternalModel),
// TODO: 05/05/2024, Danil Nikolaev: restore attachments // TODO: 05/05/2024, Danil Nikolaev: restore attachments
attachments = attachments.orEmpty().map { VkUnknownAttachment }, attachments = attachments.orEmpty().map { VkUnknownAttachment },
@@ -59,4 +61,5 @@ fun VkMessageEntity.asExternalModel(): VkMessage = VkMessage(
isPinned = isPinned, isPinned = isPinned,
isSpam = false, isSpam = false,
formatData = null, formatData = null,
isDeleted = isDeleted
) )
@@ -613,6 +613,7 @@ class ConvosViewModel(
if (convoIndex == null) { // диалога нет в списке if (convoIndex == null) { // диалога нет в списке
// pizdets // pizdets
} else { } else {
// TODO: 30.05.2026, Danil Nikolaev: reimplement
newConvos.removeAt(convoIndex) newConvos.removeAt(convoIndex)
replaceConvos(newConvos.sorted()) replaceConvos(newConvos.sorted())
@@ -1037,6 +1037,7 @@ class MessagesHistoryViewModelImpl(
isSpam = false, isSpam = false,
pinnedAt = null, pinnedAt = null,
formatData = formatData, formatData = formatData,
isDeleted = false
) )
formatData = formatData.copy(items = emptyList()) formatData = formatData.copy(items = emptyList())
sendingMessages += newMessage sendingMessages += newMessage