Files
forkmessager/client-mobile/chats/data/repository/ChatRepositoryImpl.kt
2026-04-30 10:33:15 +03:00

195 lines
7.9 KiB
Kotlin

package chats.data.repository
import chats.data.remote.api.ChatApi
import chats.data.remote.api.SendMessageRequest
import chats.data.remote.dto.ChatDto
import chats.data.remote.dto.MessageDto
import chats.data.remote.dto.MediaItemDto
import chats.data.remote.dto.ReactionDto
import chats.domain.model.Chat
import chats.domain.model.Message
import chats.domain.model.MediaType
import chats.domain.repository.ChatRepository
import core.network.ServerConfig
import chats.data.remote.signalr.ReadMessagesRequest
import core.security.TokenManager
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.RequestBody.Companion.asRequestBody
import javax.inject.Inject
import kotlinx.coroutines.flow.map
class ChatRepositoryImpl @Inject constructor(
private val api: ChatApi,
private val tokenManager: TokenManager,
private val serverConfig: ServerConfig,
private val hubClient: chats.data.remote.signalr.ChatHubClient
) : ChatRepository {
private val gson = com.google.gson.Gson()
override suspend fun getChats(): List<Chat> {
val currentUserId = tokenManager.getUserId() ?: ""
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
return api.getChats().map { it.toDomain(currentUserId, baseUrl) }
}
override fun getMessagesFlow(chatId: String): kotlinx.coroutines.flow.Flow<List<Message>> {
// Кэш отключён - всегда возвращаем пустой поток
return kotlinx.coroutines.flow.flowOf(emptyList())
}
override suspend fun getMessages(chatId: String, cursor: String?, pivot: Long?, limit: Int?): List<Message> {
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
val currentUserId = tokenManager.getUserId() ?: ""
return try {
android.util.Log.d("ChatRepo", "FETCH: chatId=$chatId, cursor=$cursor, limit=$limit")
val messages = api.getMessages(chatId, cursor = cursor, limit = limit)
if (messages.isNotEmpty()) {
android.util.Log.d("ChatRepo", "Received ${messages.size} messages. TopSeq: ${messages.first().sequenceId}, BottomSeq: ${messages.last().sequenceId}")
}
// Мапим в доменные модели. По умолчанию считаем прочитанными,
// так как unreadCount нам тут не критичен для истории.
messages.map { msg ->
msg.toDomain(currentUserId, baseUrl).copy(isRead = true)
}
} catch (e: Exception) {
android.util.Log.e("ChatRepo", "Fetch messages failed", e)
emptyList()
}
}
override suspend fun sendMessage(
chatId: String,
content: String?,
type: String,
attachments: List<chats.data.remote.api.AttachmentRequest>?,
replyToId: String?,
forwardedFromId: String?
): Message {
val request = SendMessageRequest(
content = content,
type = type,
attachments = attachments,
replyToId = replyToId,
forwardedFromId = forwardedFromId
)
android.util.Log.d("ChatRepoImpl", "sendMessage request: $request")
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
val userId = tokenManager.getUserId() ?: ""
return try {
val messageId = api.sendMessage(chatId, request)
android.util.Log.d("ChatRepoImpl", "sendMessage response: $messageId")
// Поскольку сервер вернул только ID, создаем заглушку Message.
// Настоящее сообщение придет через SignalR.
Message(
id = messageId,
chatId = chatId,
senderId = userId,
senderName = "", // Будет обновлено через SignalR
content = content,
sequenceId = 0,
createdAt = java.time.ZonedDateTime.now().toString(),
media = attachments?.map {
chats.domain.model.Media(
id = java.util.UUID.randomUUID().toString(),
type = it.type,
url = it.url,
filename = it.fileName,
size = it.fileSize
)
} ?: emptyList(),
mediaType = when(type) {
"image" -> chats.domain.model.MediaType.IMAGE
"video" -> chats.domain.model.MediaType.VIDEO
"audio", "voice" -> chats.domain.model.MediaType.AUDIO
else -> chats.domain.model.MediaType.TEXT
}
)
} catch (e: Exception) {
android.util.Log.e("ChatRepoImpl", "sendMessage error", e)
throw e
}
}
override suspend fun addReaction(messageId: String, emoji: String) {
api.addReaction(messageId, emoji)
}
override suspend fun sendTypingStatus(chatId: String) {
api.sendTypingStatus(chatId)
}
override suspend fun markMessagesAsRead(chatId: String, lastMessageId: String, lastReadSequenceId: Int) {
try {
android.util.Log.d("ChatRepoImpl", "markMessagesAsRead CALLED FOR $chatId")
hubClient.readMessages(ReadMessagesRequest(chatId, lastMessageId, lastReadSequenceId))
} catch (e: Exception) {
android.util.Log.e("ChatRepo", "Error marking messages as read", e)
}
}
override suspend fun saveMessage(message: Message) {
// Кэш отключён
}
override suspend fun deleteLocalMessage(messageId: String) {
// Локальное удаление не поддерживается без кэша
}
override suspend fun uploadMedia(file: java.io.File): String {
val mimeType = when (file.extension.lowercase()) {
"jpg", "jpeg" -> "image/jpeg"
"png" -> "image/png"
"webp" -> "image/webp"
"mp4" -> "video/mp4"
"mp3", "m4a", "wav" -> "audio/mpeg"
else -> "application/octet-stream"
}
val requestFile = file.asRequestBody(mimeType.toMediaTypeOrNull())
val body = MultipartBody.Part.createFormData("file", file.name, requestFile)
return api.uploadFile(body).url
}
override suspend fun getTrendingGifs(page: Int): List<chats.data.remote.api.KlipyGifDto> {
return api.getTrendingGifs(page).data.data
}
override suspend fun searchGifs(query: String, page: Int): List<chats.data.remote.api.KlipyGifDto> {
return api.searchGifs(query, page).data.data
}
override suspend fun getGifCategories(): List<chats.data.remote.api.GifCategoryDto> {
return api.getGifCategories().data.categories
}
override suspend fun createPersonalChat(userId: String): Chat {
val currentUserId = tokenManager.getUserId() ?: ""
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
val request = chats.data.remote.api.CreatePersonalChatRequest(userId)
return api.createPersonalChat(request).toDomain(currentUserId, baseUrl)
}
override suspend fun deleteMessage(messageId: String, forEveryone: Boolean) {
api.deleteMessage(messageId, forEveryone)
}
override suspend fun editMessage(messageId: String, content: String): Message {
val request = SendMessageRequest(content = content)
val currentUserId = tokenManager.getUserId() ?: ""
val returnedId = api.editMessage(messageId, request)
return Message(
id = returnedId,
chatId = "",
senderId = currentUserId,
senderName = "",
content = content,
sequenceId = 0,
createdAt = java.time.ZonedDateTime.now().toString()
)
}
}