104 lines
4.4 KiB
Kotlin
104 lines
4.4 KiB
Kotlin
package chats.data.local.paging
|
||
|
||
import androidx.paging.ExperimentalPagingApi
|
||
import androidx.paging.LoadType
|
||
import androidx.paging.PagingState
|
||
import androidx.paging.RemoteMediator
|
||
import androidx.room.withTransaction
|
||
import chats.data.local.dao.ChatDao
|
||
import chats.data.local.dao.MessageDao
|
||
import chats.data.local.database.AppDatabase
|
||
import chats.data.local.database.MessageEntity
|
||
import chats.data.local.mappers.toEntity
|
||
import chats.data.repository.toDomain as dtoToDomain
|
||
import chats.data.remote.api.ChatApi
|
||
import chats.domain.model.MessageStatus
|
||
import core.network.ServerConfig
|
||
import core.security.TokenManager
|
||
|
||
/**
|
||
* RemoteMediator для синхронизации сообщений с сервером.
|
||
* Работает с Room через withTransaction для атомарности.
|
||
*/
|
||
@OptIn(ExperimentalPagingApi::class)
|
||
class MessageRemoteMediator(
|
||
private val chatId: String,
|
||
private val messageDao: MessageDao,
|
||
private val chatDao: ChatDao,
|
||
private val chatApi: ChatApi,
|
||
private val tokenManager: TokenManager,
|
||
private val appDatabase: AppDatabase,
|
||
private val serverConfig: ServerConfig
|
||
) : RemoteMediator<Int, MessageEntity>() {
|
||
|
||
companion object {
|
||
private const val PAGE_SIZE = 30
|
||
}
|
||
|
||
override suspend fun load(
|
||
loadType: LoadType,
|
||
state: PagingState<Int, MessageEntity>
|
||
): RemoteMediator.MediatorResult {
|
||
return try {
|
||
val currentUserId = tokenManager.getUserId() ?: ""
|
||
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
|
||
|
||
val cursor = when (loadType) {
|
||
LoadType.REFRESH -> {
|
||
// При refresh загружаем новые сообщения после последнего локального
|
||
messageDao.getLastMessage(chatId)?.serverId
|
||
}
|
||
LoadType.PREPEND -> {
|
||
// Загружаем более старые сообщения перед первым локальным
|
||
messageDao.getFirstMessage(chatId)?.serverId
|
||
}
|
||
LoadType.APPEND -> {
|
||
// Загружаем новые сообщения после последнего локального
|
||
messageDao.getLastMessage(chatId)?.serverId
|
||
}
|
||
}
|
||
|
||
val remoteMessages = chatApi.getMessages(chatId, cursor = cursor, limit = PAGE_SIZE)
|
||
val endOfPaginationReached = remoteMessages.size < PAGE_SIZE
|
||
|
||
appDatabase.withTransaction {
|
||
if (loadType == LoadType.REFRESH) {
|
||
// При полном обновлении можно очистить старые SENT/DELIVERED/READ,
|
||
// но оставить локальные PENDING/FAILED
|
||
// messageDao.deleteMessagesByChatId(chatId) // Опционально
|
||
}
|
||
|
||
val entities = remoteMessages.map { dto ->
|
||
val existing = messageDao.getMessageByServerId(dto.id)
|
||
if (existing != null) {
|
||
// Обновляем существующее, сохраняя localId
|
||
existing.copy(
|
||
content = dto.content,
|
||
reactionsJson = com.google.gson.Gson().toJson(
|
||
dto.reactions?.associate { it.emoji to it.count } ?: emptyMap<String, Int>()
|
||
),
|
||
status = if (dto.senderId == currentUserId) MessageStatus.SENT else MessageStatus.DELIVERED,
|
||
updatedAtMillis = System.currentTimeMillis()
|
||
)
|
||
} else {
|
||
// Создаём новое
|
||
dto.dtoToDomain(currentUserId, baseUrl).toEntity(MessageStatus.SENT).copy(
|
||
localId = java.util.UUID.randomUUID().toString(),
|
||
serverId = dto.id,
|
||
idempotencyKey = dto.id
|
||
)
|
||
}
|
||
}
|
||
|
||
messageDao.insertMessages(entities)
|
||
}
|
||
|
||
RemoteMediator.MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
|
||
} catch (e: Exception) {
|
||
RemoteMediator.MediatorResult.Error(e)
|
||
}
|
||
}
|
||
|
||
override suspend fun initialize(): InitializeAction = InitializeAction.SKIP_INITIAL_REFRESH
|
||
}
|