Чат
This commit is contained in:
@@ -51,7 +51,7 @@ interface ChatApi {
|
||||
suspend fun markGifShared(@Path("id") id: String, @Body query: String)
|
||||
|
||||
@POST("messages/{messageId}/reactions")
|
||||
suspend fun addReaction(@Path("messageId") messageId: String, @Body emoji: String)
|
||||
suspend fun addReaction(@Path("messageId") messageId: String, @Query("emoji") emoji: String)
|
||||
|
||||
@POST("chats/{chatId}/typing")
|
||||
suspend fun sendTypingStatus(@Path("chatId") chatId: String)
|
||||
|
||||
@@ -18,7 +18,14 @@ data class MessageDto(
|
||||
@SerializedName("sequenceId") val sequenceId: Int,
|
||||
@SerializedName("createdAt") val createdAt: String,
|
||||
@SerializedName("sender") val sender: UserBasicDto,
|
||||
@SerializedName("media") val media: List<MediaItemDto> = emptyList()
|
||||
@SerializedName("media") val media: List<MediaItemDto> = emptyList(),
|
||||
@SerializedName("reactions") val reactions: List<ReactionDto>? = emptyList()
|
||||
)
|
||||
|
||||
data class ReactionDto(
|
||||
@SerializedName("emoji") val emoji: String,
|
||||
@SerializedName("count") val count: Int,
|
||||
@SerializedName("isSetByMe") val isSetByMe: Boolean
|
||||
)
|
||||
|
||||
data class MediaItemDto(
|
||||
|
||||
@@ -10,6 +10,9 @@ import chats.data.remote.dto.MessageDto
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -18,6 +21,7 @@ class ChatHubClient @Inject constructor() {
|
||||
private var hubConnection: HubConnection? = null
|
||||
private val _events = MutableSharedFlow<ChatEvent>(extraBufferCapacity = 64)
|
||||
val events: SharedFlow<ChatEvent> = _events.asSharedFlow()
|
||||
private val scope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
fun connect(baseUrl: String, accessToken: String) {
|
||||
if (hubConnection?.connectionState == HubConnectionState.CONNECTED) return
|
||||
@@ -30,9 +34,17 @@ class ChatHubClient @Inject constructor() {
|
||||
|
||||
hubConnection?.onClosed { exception ->
|
||||
Log.e("ChatHubClient", "Connection closed", exception)
|
||||
// Optional: Reconnect logic
|
||||
}
|
||||
|
||||
hubConnection?.start()?.blockingAwait()
|
||||
scope.launch {
|
||||
try {
|
||||
hubConnection?.start()?.blockingAwait()
|
||||
Log.d("ChatHubClient", "SignalR Connected")
|
||||
} catch (e: Exception) {
|
||||
Log.e("ChatHubClient", "SignalR Connection Error", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupHandlers() {
|
||||
|
||||
@@ -8,6 +8,7 @@ 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 core.security.TokenManager
|
||||
import okhttp3.MediaType.Companion.toMediaTypeOrNull
|
||||
import okhttp3.MultipartBody
|
||||
@@ -16,21 +17,25 @@ import javax.inject.Inject
|
||||
|
||||
class ChatRepositoryImpl @Inject constructor(
|
||||
private val api: ChatApi,
|
||||
private val tokenManager: TokenManager
|
||||
private val tokenManager: TokenManager,
|
||||
private val serverConfig: ServerConfig
|
||||
) : ChatRepository {
|
||||
|
||||
override suspend fun getChats(): List<Chat> {
|
||||
val currentUserId = tokenManager.getUserId() ?: ""
|
||||
return api.getChats().map { it.toDomain(currentUserId) }
|
||||
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
|
||||
return api.getChats().map { it.toDomain(currentUserId, baseUrl) }
|
||||
}
|
||||
|
||||
override suspend fun getMessages(chatId: String): List<Message> {
|
||||
return api.getMessages(chatId).map { it.toDomain() }
|
||||
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
|
||||
return api.getMessages(chatId).map { it.toDomain(baseUrl) }
|
||||
}
|
||||
|
||||
override suspend fun sendMessage(chatId: String, content: String): Message {
|
||||
val request = SendMessageRequest(content = content, type = "text")
|
||||
return api.sendMessage(chatId, request).toDomain()
|
||||
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
|
||||
return api.sendMessage(chatId, request).toDomain(baseUrl)
|
||||
}
|
||||
|
||||
override suspend fun addReaction(messageId: String, emoji: String) {
|
||||
@@ -56,15 +61,15 @@ class ChatRepositoryImpl @Inject constructor(
|
||||
|
||||
|
||||
// Mappers
|
||||
fun ChatDto.toDomain(currentUserId: String): Chat {
|
||||
fun ChatDto.toDomain(currentUserId: String, baseUrl: String): Chat {
|
||||
// Если это личный чат и имя пустое, ищем имя собеседника в списке участников
|
||||
val chatName = name ?: if (type == "personal") {
|
||||
members.firstOrNull { it.userId != currentUserId }?.user?.displayName ?: "Unknown Chat"
|
||||
} else "Group Chat"
|
||||
|
||||
val chatAvatar = avatar ?: if (type == "personal") {
|
||||
val chatAvatar = (avatar ?: if (type == "personal") {
|
||||
members.firstOrNull { it.userId != currentUserId }?.user?.avatarUrl
|
||||
} else null
|
||||
} else null)?.ensureAbsoluteUrl(baseUrl)
|
||||
|
||||
return Chat(
|
||||
id = id,
|
||||
@@ -72,25 +77,48 @@ fun ChatDto.toDomain(currentUserId: String): Chat {
|
||||
name = chatName,
|
||||
avatar = chatAvatar,
|
||||
unreadCount = unreadCount,
|
||||
lastMessage = messages.firstOrNull()?.toDomain()
|
||||
lastMessage = messages.firstOrNull()?.toDomain(baseUrl)
|
||||
)
|
||||
}
|
||||
|
||||
fun MessageDto.toDomain(): Message = Message(
|
||||
id = id,
|
||||
chatId = chatId,
|
||||
senderId = senderId,
|
||||
senderName = sender.displayName,
|
||||
content = content,
|
||||
sequenceId = sequenceId,
|
||||
createdAt = createdAt,
|
||||
media = media.map { it.url },
|
||||
mediaUrl = media.firstOrNull()?.url,
|
||||
mediaType = when (media.firstOrNull()?.type) {
|
||||
"image" -> MediaType.IMAGE
|
||||
fun MessageDto.toDomain(baseUrl: String): Message {
|
||||
val domainMediaType = when (type) {
|
||||
"image", "photo" -> MediaType.IMAGE
|
||||
"video" -> MediaType.VIDEO
|
||||
"audio" -> MediaType.AUDIO
|
||||
"audio", "voice" -> MediaType.AUDIO
|
||||
"file" -> MediaType.FILE
|
||||
else -> MediaType.TEXT
|
||||
else -> when (media.firstOrNull()?.type) {
|
||||
"image", "photo" -> MediaType.IMAGE
|
||||
"video" -> MediaType.VIDEO
|
||||
"audio", "voice" -> MediaType.AUDIO
|
||||
"file" -> MediaType.FILE
|
||||
else -> MediaType.TEXT
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return Message(
|
||||
id = id,
|
||||
chatId = chatId,
|
||||
senderId = senderId,
|
||||
senderName = sender.displayName,
|
||||
senderAvatar = sender.avatarUrl?.ensureAbsoluteUrl(baseUrl),
|
||||
content = content,
|
||||
sequenceId = sequenceId,
|
||||
createdAt = createdAt,
|
||||
media = media.map { it.url.ensureAbsoluteUrl(baseUrl) },
|
||||
mediaUrl = media.firstOrNull()?.url?.ensureAbsoluteUrl(baseUrl),
|
||||
mediaType = domainMediaType,
|
||||
reactions = reactions?.associate { it.emoji to it.count } ?: emptyMap()
|
||||
)
|
||||
}
|
||||
|
||||
fun String.ensureAbsoluteUrl(baseUrl: String): String {
|
||||
return if (this.startsWith("http")) {
|
||||
this
|
||||
} else {
|
||||
val base = baseUrl.removeSuffix("/")
|
||||
val path = if (this.startsWith("/")) this else "/$this"
|
||||
"$base$path"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user