Рабочие счетчики и переработка чата
This commit is contained in:
@@ -10,17 +10,17 @@ data class UserBasicDto(
|
||||
)
|
||||
|
||||
data class MessageDto(
|
||||
@SerializedName("id") val id: String,
|
||||
@SerializedName("chatId") val chatId: String? = null,
|
||||
@SerializedName("senderId") val senderId: String? = null,
|
||||
@SerializedName("content") val content: String? = null,
|
||||
@SerializedName("type") val type: String? = null,
|
||||
@SerializedName("sequenceId") val sequenceId: Int? = null,
|
||||
@SerializedName("createdAt") val createdAt: String? = null,
|
||||
@SerializedName("sender") val sender: UserBasicDto? = null,
|
||||
@SerializedName("media") val media: List<MediaItemDto> = emptyList(),
|
||||
@SerializedName("reactions") val reactions: List<ReactionDto>? = emptyList(),
|
||||
@SerializedName("replyTo") val replyTo: MessageDto? = null
|
||||
@SerializedName("id", alternate = ["Id"]) val id: String,
|
||||
@SerializedName("chatId", alternate = ["ChatId"]) val chatId: String? = null,
|
||||
@SerializedName("senderId", alternate = ["SenderId"]) val senderId: String? = null,
|
||||
@SerializedName("content", alternate = ["Content"]) val content: String? = null,
|
||||
@SerializedName("type", alternate = ["Type"]) val type: String? = null,
|
||||
@SerializedName("sequenceId", alternate = ["SequenceId"]) val sequenceId: Int? = null,
|
||||
@SerializedName("createdAt", alternate = ["CreatedAt"]) val createdAt: String? = null,
|
||||
@SerializedName("sender", alternate = ["Sender"]) val sender: UserBasicDto? = null,
|
||||
@SerializedName("media", alternate = ["Media"]) val media: List<MediaItemDto> = emptyList(),
|
||||
@SerializedName("reactions", alternate = ["Reactions"]) val reactions: List<ReactionDto>? = emptyList(),
|
||||
@SerializedName("replyTo", alternate = ["ReplyTo"]) val replyTo: MessageDto? = null
|
||||
)
|
||||
|
||||
data class ReactionDto(
|
||||
@@ -43,7 +43,7 @@ data class ChatDto(
|
||||
@SerializedName("type") val type: String,
|
||||
@SerializedName("name") val name: String? = null,
|
||||
@SerializedName("avatar") val avatar: String? = null,
|
||||
@SerializedName("unreadCount") val unreadCount: Int = 0,
|
||||
@SerializedName("unreadCount", alternate = ["UnreadCount", "unread_count"]) val unreadCount: Int = 0,
|
||||
@SerializedName("messages") val messages: List<MessageDto> = emptyList(),
|
||||
@SerializedName("members") val members: List<ChatMemberDto> = emptyList()
|
||||
)
|
||||
|
||||
@@ -22,12 +22,30 @@ data class ReadMessagesRequest(
|
||||
val lastReadSequenceId: Int
|
||||
)
|
||||
|
||||
data class MessagesReadEvent(
|
||||
@com.google.gson.annotations.SerializedName("chatId", alternate = ["ChatId"]) val chatId: String? = null,
|
||||
@com.google.gson.annotations.SerializedName("userId", alternate = ["UserId"]) val userId: String? = null,
|
||||
@com.google.gson.annotations.SerializedName("lastReadSequenceId", alternate = ["LastReadSequenceId"]) val lastReadSequenceId: Int? = null
|
||||
) {
|
||||
val effectiveChatId: String get() = chatId ?: ""
|
||||
val effectiveUserId: String get() = userId ?: ""
|
||||
val effectiveLastReadSequenceId: Int get() = lastReadSequenceId ?: 0
|
||||
}
|
||||
|
||||
data class ReactionEvent(
|
||||
@com.google.gson.annotations.SerializedName("messageId", alternate = ["MessageId"]) val messageId: String? = null,
|
||||
@com.google.gson.annotations.SerializedName("chatId", alternate = ["ChatId"]) val chatId: String? = null,
|
||||
@com.google.gson.annotations.SerializedName("userId", alternate = ["UserId"]) val userId: String? = null,
|
||||
@com.google.gson.annotations.SerializedName("username", alternate = ["Username", "UserName"]) val username: String? = null,
|
||||
@com.google.gson.annotations.SerializedName("emoji", alternate = ["Emoji"]) val emoji: String? = null
|
||||
)
|
||||
|
||||
enum class ConnectionStatus { CONNECTED, CONNECTING, DISCONNECTED }
|
||||
|
||||
@Singleton
|
||||
class ChatHubClient @Inject constructor() {
|
||||
private var hubConnection: HubConnection? = null
|
||||
private val _events = MutableSharedFlow<ChatEvent>(extraBufferCapacity = 64)
|
||||
private val _events = MutableSharedFlow<ChatEvent>(extraBufferCapacity = 1024)
|
||||
val events: SharedFlow<ChatEvent> = _events.asSharedFlow()
|
||||
|
||||
private val _status = MutableStateFlow(ConnectionStatus.DISCONNECTED)
|
||||
@@ -77,13 +95,17 @@ class ChatHubClient @Inject constructor() {
|
||||
_events.tryEmit(ChatEvent.NewMessage(message))
|
||||
}, MessageDto::class.java)
|
||||
|
||||
conn.on("messages_read", { chatId: String, userId: String, lastReadSequenceId: Int ->
|
||||
_events.tryEmit(ChatEvent.MessagesRead(chatId, userId, lastReadSequenceId))
|
||||
}, String::class.java, String::class.java, Int::class.java)
|
||||
conn.on("messages_read", { data: MessagesReadEvent ->
|
||||
_events.tryEmit(ChatEvent.MessagesRead(
|
||||
data.effectiveChatId,
|
||||
data.effectiveUserId,
|
||||
data.effectiveLastReadSequenceId
|
||||
))
|
||||
}, MessagesReadEvent::class.java)
|
||||
|
||||
conn.on("user_typing", { chatId: String, userId: String ->
|
||||
_events.tryEmit(ChatEvent.UserTyping(chatId, userId))
|
||||
}, String::class.java, String::class.java)
|
||||
conn.on("user_typing", { data: ReactionEvent ->
|
||||
_events.tryEmit(ChatEvent.UserTyping(data.chatId ?: "", data.userId ?: ""))
|
||||
}, ReactionEvent::class.java)
|
||||
|
||||
conn.on("user_online", { userId: String ->
|
||||
_events.tryEmit(ChatEvent.UserOnline(userId))
|
||||
@@ -93,14 +115,23 @@ class ChatHubClient @Inject constructor() {
|
||||
_events.tryEmit(ChatEvent.NewChat(chat))
|
||||
}, ChatDto::class.java)
|
||||
|
||||
conn.on("reaction_added", { messageId: String, chatId: String, userId: String, username: String, emoji: String ->
|
||||
_events.tryEmit(ChatEvent.ReactionUpdated(messageId, chatId, userId, emoji))
|
||||
}, String::class.java, String::class.java, String::class.java, String::class.java, String::class.java)
|
||||
conn.on("reaction_added", { data: ReactionEvent ->
|
||||
_events.tryEmit(ChatEvent.ReactionUpdated(
|
||||
data.messageId ?: "",
|
||||
data.chatId ?: "",
|
||||
data.userId ?: "",
|
||||
data.emoji ?: ""
|
||||
))
|
||||
}, ReactionEvent::class.java)
|
||||
|
||||
conn.on("reaction_removed", { messageId: String, chatId: String, userId: String, emoji: String ->
|
||||
// Using ReactionUpdated with empty emoji to signal removal or just a specific removal event
|
||||
_events.tryEmit(ChatEvent.ReactionUpdated(messageId, chatId, userId, ""))
|
||||
}, String::class.java, String::class.java, String::class.java, String::class.java)
|
||||
conn.on("reaction_removed", { data: ReactionEvent ->
|
||||
_events.tryEmit(ChatEvent.ReactionUpdated(
|
||||
data.messageId ?: "",
|
||||
data.chatId ?: "",
|
||||
data.userId ?: "",
|
||||
"" // empty emoji signals removal
|
||||
))
|
||||
}, ReactionEvent::class.java)
|
||||
|
||||
// WebRTC Signaling Handlers
|
||||
conn.on("call_incoming", { chatId: String, from: String, offer: String, callType: String ->
|
||||
@@ -128,7 +159,26 @@ class ChatHubClient @Inject constructor() {
|
||||
|
||||
fun readMessages(request: ReadMessagesRequest) {
|
||||
if (hubConnection?.connectionState == HubConnectionState.CONNECTED) {
|
||||
hubConnection?.invoke("read_messages", request)
|
||||
hubConnection?.send("read_messages", request)
|
||||
Log.d("ChatHubClient", "Sent read_messages for chat: ${request.chatId}")
|
||||
}
|
||||
}
|
||||
|
||||
fun joinChat(chatId: String) {
|
||||
scope.launch {
|
||||
// Wait for connection to be established if it's currently connecting
|
||||
var attempts = 0
|
||||
while (hubConnection?.connectionState != HubConnectionState.CONNECTED && attempts < 10) {
|
||||
delay(500)
|
||||
attempts++
|
||||
}
|
||||
|
||||
if (hubConnection?.connectionState == HubConnectionState.CONNECTED) {
|
||||
hubConnection?.send("join_chat", chatId)
|
||||
Log.d("ChatHubClient", "Joined chat room: $chatId")
|
||||
} else {
|
||||
Log.e("ChatHubClient", "Failed to join chat room $chatId: Not connected")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ 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
|
||||
@@ -40,15 +41,27 @@ class ChatRepositoryImpl @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getMessages(chatId: String): List<Message> {
|
||||
override suspend fun getMessages(chatId: String, cursor: String?, limit: Int?): List<Message> {
|
||||
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
|
||||
val currentUserId = tokenManager.getUserId() ?: ""
|
||||
return try {
|
||||
val messages = api.getMessages(chatId)
|
||||
// Save to DB
|
||||
messageDao.insertMessages(messages.map { msg: MessageDto -> msg.toEntity(baseUrl, gson) })
|
||||
messages.map { it.toDomain(baseUrl) }
|
||||
val chats = api.getChats()
|
||||
val chatDto = chats.find { it.id == chatId }
|
||||
val unreadCount = chatDto?.unreadCount ?: 0
|
||||
|
||||
android.util.Log.d("ChatRepo", "Fetching messages from network for chat: $chatId, cursor: $cursor")
|
||||
val messages = api.getMessages(chatId, cursor = cursor, limit = limit)
|
||||
if (messages.isNotEmpty()) {
|
||||
android.util.Log.d("ChatRepo", "Received ${messages.size} messages. First: ${messages.first().createdAt}, Last: ${messages.last().createdAt}")
|
||||
}
|
||||
|
||||
// Определяем, какие сообщения считаются прочитанными
|
||||
val mappedMessages = messages.mapIndexed { index, msg ->
|
||||
val isUnread = (messages.size - index) <= unreadCount && msg.senderId != currentUserId
|
||||
msg.toDomain(currentUserId, baseUrl).copy(isRead = !isUnread)
|
||||
}
|
||||
mappedMessages
|
||||
} catch (e: Exception) {
|
||||
// If network fails, caller should ideally use the Flow from DB
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
@@ -65,7 +78,8 @@ class ChatRepositoryImpl @Inject constructor(
|
||||
attachments = attachments
|
||||
)
|
||||
val baseUrl = serverConfig.getBaseUrl().removeSuffix("/api/")
|
||||
return api.sendMessage(chatId, request).toDomain(baseUrl)
|
||||
val userId = tokenManager.getUserId() ?: ""
|
||||
return api.sendMessage(chatId, request).toDomain(userId, baseUrl)
|
||||
}
|
||||
|
||||
override suspend fun addReaction(messageId: String, emoji: String) {
|
||||
@@ -77,16 +91,18 @@ class ChatRepositoryImpl @Inject constructor(
|
||||
}
|
||||
|
||||
override suspend fun markMessagesAsRead(chatId: String, lastMessageId: String, lastReadSequenceId: Int) {
|
||||
val request = chats.data.remote.signalr.ReadMessagesRequest(
|
||||
chatId = chatId,
|
||||
lastReadMessageId = lastMessageId,
|
||||
lastReadSequenceId = lastReadSequenceId
|
||||
)
|
||||
hubClient.readMessages(request)
|
||||
try {
|
||||
android.util.Log.d("ChatRepoImpl", "markMessagesAsRead CALLED FOR $chatId. Caller stack: ${android.util.Log.getStackTraceString(Throwable())}")
|
||||
hubClient.readMessages(ReadMessagesRequest(chatId, lastMessageId, lastReadSequenceId))
|
||||
} catch (e: Exception) {
|
||||
android.util.Log.e("ChatRepo", "Error marking messages as read", e)
|
||||
}
|
||||
// Обновляем локальную БД
|
||||
messageDao.markMessagesAsRead(chatId, lastReadSequenceId)
|
||||
}
|
||||
|
||||
override suspend fun saveMessage(message: Message) {
|
||||
messageDao.insertMessages(listOf(message.toEntity(gson)))
|
||||
android.util.Log.d("ChatRepo", "DB cache disabled, skipping save: ${message.id}")
|
||||
}
|
||||
|
||||
override suspend fun deleteLocalMessage(messageId: String) {
|
||||
@@ -119,168 +135,3 @@ class ChatRepositoryImpl @Inject constructor(
|
||||
return api.getGifCategories().data.categories
|
||||
}
|
||||
}
|
||||
|
||||
// Mappers
|
||||
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") {
|
||||
members.firstOrNull { it.userId != currentUserId }?.user?.avatarUrl
|
||||
} else null)?.ensureAbsoluteUrl(baseUrl)
|
||||
|
||||
return Chat(
|
||||
id = id,
|
||||
type = type,
|
||||
name = chatName,
|
||||
avatar = chatAvatar,
|
||||
unreadCount = unreadCount,
|
||||
lastMessage = messages.firstOrNull()?.toDomain(baseUrl)
|
||||
)
|
||||
}
|
||||
|
||||
fun Message.toEntity(gson: com.google.gson.Gson): core.database.data.MessageEntity {
|
||||
return core.database.data.MessageEntity(
|
||||
id = id,
|
||||
chatId = chatId,
|
||||
senderId = senderId,
|
||||
senderName = senderName,
|
||||
senderAvatar = senderAvatar,
|
||||
content = content,
|
||||
sequenceId = sequenceId,
|
||||
createdAt = createdAt,
|
||||
mediaType = mediaType.name.lowercase(),
|
||||
mediaJson = gson.toJson(media),
|
||||
reactionsJson = gson.toJson(reactions),
|
||||
isRead = isRead,
|
||||
replyToId = replyTo?.id
|
||||
)
|
||||
}
|
||||
|
||||
fun MessageDto.toDomain(baseUrl: String): Message {
|
||||
val domainMediaType = when (type) {
|
||||
"gif" -> MediaType.GIF
|
||||
"image", "photo" -> MediaType.IMAGE
|
||||
"video" -> MediaType.VIDEO
|
||||
"audio", "voice" -> MediaType.AUDIO
|
||||
"file" -> MediaType.FILE
|
||||
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 ?: "Unknown",
|
||||
senderAvatar = sender?.avatarUrl?.ensureAbsoluteUrl(baseUrl),
|
||||
content = content,
|
||||
sequenceId = sequenceId ?: 0,
|
||||
createdAt = createdAt ?: "",
|
||||
media = media.map {
|
||||
chats.domain.model.Media(
|
||||
id = it.id ?: java.util.UUID.randomUUID().toString(),
|
||||
type = it.type ?: "unknown",
|
||||
url = (it.url ?: "").ensureAbsoluteUrl(baseUrl),
|
||||
filename = it.filename,
|
||||
size = it.size,
|
||||
duration = it.duration
|
||||
)
|
||||
},
|
||||
mediaType = domainMediaType,
|
||||
reactions = reactions?.associate { it.emoji to it.count } ?: emptyMap(),
|
||||
isRead = true, // Network messages are usually considered read when fetched or handled by server
|
||||
replyTo = replyTo?.toDomain(baseUrl)
|
||||
)
|
||||
}
|
||||
|
||||
fun MessageDto.toEntity(baseUrl: String, gson: com.google.gson.Gson): core.database.data.MessageEntity {
|
||||
return core.database.data.MessageEntity(
|
||||
id = id,
|
||||
chatId = chatId ?: "",
|
||||
senderId = senderId ?: "",
|
||||
senderName = sender?.displayName ?: "Unknown",
|
||||
senderAvatar = sender?.avatarUrl?.ensureAbsoluteUrl(baseUrl),
|
||||
content = content,
|
||||
sequenceId = sequenceId ?: 0,
|
||||
createdAt = createdAt ?: "",
|
||||
mediaType = type ?: "text",
|
||||
mediaJson = gson.toJson(media),
|
||||
reactionsJson = gson.toJson(reactions),
|
||||
isRead = true,
|
||||
replyToId = replyTo?.id
|
||||
)
|
||||
}
|
||||
|
||||
fun core.database.data.MessageEntity.toDomain(baseUrl: String, gson: com.google.gson.Gson): Message {
|
||||
val mediaTypeEnum = when (mediaType) {
|
||||
"gif" -> MediaType.GIF
|
||||
"image", "photo" -> MediaType.IMAGE
|
||||
"video" -> MediaType.VIDEO
|
||||
"audio", "voice" -> MediaType.AUDIO
|
||||
"file" -> MediaType.FILE
|
||||
else -> MediaType.TEXT
|
||||
}
|
||||
|
||||
val mediaTypeToken = object : com.google.gson.reflect.TypeToken<List<chats.data.remote.dto.MediaItemDto>>() {}.type
|
||||
val mediaDtos: List<chats.data.remote.dto.MediaItemDto> = try {
|
||||
gson.fromJson(mediaJson, mediaTypeToken)
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
} ?: emptyList()
|
||||
|
||||
val reactionsTypeToken = object : com.google.gson.reflect.TypeToken<List<chats.data.remote.dto.ReactionDto>>() {}.type
|
||||
val reactionDtos: List<chats.data.remote.dto.ReactionDto> = try {
|
||||
// Try to parse as array (List<ReactionDto>)
|
||||
gson.fromJson(reactionsJson, reactionsTypeToken)
|
||||
} catch (e: Exception) {
|
||||
// If it's an object instead of array, parse as map and convert to list
|
||||
try {
|
||||
val mapType = object : com.google.gson.reflect.TypeToken<Map<String, Int>>() {}.type
|
||||
val map: Map<String, Int> = gson.fromJson(reactionsJson, mapType) ?: emptyMap()
|
||||
map.map { chats.data.remote.dto.ReactionDto(it.key, it.value, false) }
|
||||
} catch (innerE: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
} ?: emptyList()
|
||||
|
||||
return Message(
|
||||
id = id,
|
||||
chatId = chatId,
|
||||
senderId = senderId,
|
||||
senderName = senderName,
|
||||
senderAvatar = senderAvatar,
|
||||
content = content,
|
||||
sequenceId = sequenceId,
|
||||
createdAt = createdAt,
|
||||
media = mediaDtos.map {
|
||||
chats.domain.model.Media(
|
||||
id = it.id ?: java.util.UUID.randomUUID().toString(),
|
||||
type = it.type ?: "unknown",
|
||||
url = (it.url ?: "").ensureAbsoluteUrl(baseUrl),
|
||||
filename = it.filename,
|
||||
size = it.size,
|
||||
duration = it.duration
|
||||
)
|
||||
},
|
||||
mediaType = mediaTypeEnum,
|
||||
reactions = reactionDtos.associate { it.emoji to it.count },
|
||||
isRead = isRead
|
||||
)
|
||||
}
|
||||
|
||||
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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
package chats.data.repository
|
||||
|
||||
import chats.data.remote.dto.*
|
||||
import chats.domain.model.*
|
||||
|
||||
// Mappers
|
||||
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") {
|
||||
members.firstOrNull { it.userId != currentUserId }?.user?.avatarUrl
|
||||
} else null)?.ensureAbsoluteUrl(baseUrl)
|
||||
|
||||
return Chat(
|
||||
id = id,
|
||||
type = type,
|
||||
name = chatName,
|
||||
avatar = chatAvatar,
|
||||
unreadCount = unreadCount,
|
||||
lastMessage = messages.firstOrNull()?.toDomain(currentUserId, baseUrl)
|
||||
)
|
||||
}
|
||||
|
||||
fun Message.toEntity(gson: com.google.gson.Gson): core.database.data.MessageEntity {
|
||||
return core.database.data.MessageEntity(
|
||||
id = id,
|
||||
chatId = chatId,
|
||||
senderId = senderId,
|
||||
senderName = senderName,
|
||||
senderAvatar = senderAvatar,
|
||||
content = content,
|
||||
sequenceId = sequenceId,
|
||||
createdAt = createdAt,
|
||||
mediaType = mediaType.name.lowercase(),
|
||||
mediaJson = gson.toJson(media),
|
||||
reactionsJson = gson.toJson(reactions),
|
||||
isRead = isRead,
|
||||
replyToId = replyTo?.id
|
||||
)
|
||||
}
|
||||
|
||||
fun MessageDto.toDomain(currentUserId: String, baseUrl: String): Message {
|
||||
val domainMediaType = when (type) {
|
||||
"gif" -> MediaType.GIF
|
||||
"image", "photo" -> MediaType.IMAGE
|
||||
"video" -> MediaType.VIDEO
|
||||
"audio", "voice" -> MediaType.AUDIO
|
||||
"file" -> MediaType.FILE
|
||||
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 ?: "Unknown",
|
||||
senderAvatar = sender?.avatarUrl?.ensureAbsoluteUrl(baseUrl),
|
||||
content = content,
|
||||
sequenceId = sequenceId ?: 0,
|
||||
createdAt = createdAt ?: "",
|
||||
media = media.map {
|
||||
chats.domain.model.Media(
|
||||
id = it.id ?: java.util.UUID.randomUUID().toString(),
|
||||
type = it.type ?: "unknown",
|
||||
url = (it.url ?: "").ensureAbsoluteUrl(baseUrl),
|
||||
filename = it.filename,
|
||||
size = it.size,
|
||||
duration = it.duration
|
||||
)
|
||||
},
|
||||
mediaType = domainMediaType,
|
||||
reactions = reactions?.associate { it.emoji to it.count } ?: emptyMap(),
|
||||
isRead = senderId == currentUserId,
|
||||
replyTo = replyTo?.toDomain(currentUserId, baseUrl)
|
||||
)
|
||||
}
|
||||
|
||||
fun MessageDto.toEntity(baseUrl: String, currentUserId: String, gson: com.google.gson.Gson): core.database.data.MessageEntity {
|
||||
return core.database.data.MessageEntity(
|
||||
id = id,
|
||||
chatId = chatId ?: "",
|
||||
senderId = senderId ?: "",
|
||||
senderName = sender?.displayName ?: "Unknown",
|
||||
senderAvatar = sender?.avatarUrl?.ensureAbsoluteUrl(baseUrl),
|
||||
content = content,
|
||||
sequenceId = sequenceId ?: 0,
|
||||
createdAt = createdAt ?: "",
|
||||
mediaType = type ?: "text",
|
||||
mediaJson = gson.toJson(media),
|
||||
reactionsJson = gson.toJson(reactions),
|
||||
isRead = senderId == currentUserId,
|
||||
replyToId = replyTo?.id
|
||||
)
|
||||
}
|
||||
|
||||
fun core.database.data.MessageEntity.toDomain(baseUrl: String, gson: com.google.gson.Gson): Message {
|
||||
val mediaTypeEnum = when (mediaType) {
|
||||
"gif" -> MediaType.GIF
|
||||
"image", "photo" -> MediaType.IMAGE
|
||||
"video" -> MediaType.VIDEO
|
||||
"audio", "voice" -> MediaType.AUDIO
|
||||
"file" -> MediaType.FILE
|
||||
else -> MediaType.TEXT
|
||||
}
|
||||
|
||||
val mediaTypeToken = object : com.google.gson.reflect.TypeToken<List<chats.data.remote.dto.MediaItemDto>>() {}.type
|
||||
val mediaDtos: List<chats.data.remote.dto.MediaItemDto> = try {
|
||||
gson.fromJson(mediaJson, mediaTypeToken)
|
||||
} catch (e: Exception) {
|
||||
emptyList()
|
||||
} ?: emptyList()
|
||||
|
||||
val reactionsTypeToken = object : com.google.gson.reflect.TypeToken<List<chats.data.remote.dto.ReactionDto>>() {}.type
|
||||
val reactionDtos: List<chats.data.remote.dto.ReactionDto> = try {
|
||||
gson.fromJson(reactionsJson, reactionsTypeToken)
|
||||
} catch (e: Exception) {
|
||||
try {
|
||||
val mapType = object : com.google.gson.reflect.TypeToken<Map<String, Int>>() {}.type
|
||||
val map: Map<String, Int> = gson.fromJson(reactionsJson, mapType) ?: emptyMap()
|
||||
map.map { chats.data.remote.dto.ReactionDto(it.key, it.value, false) }
|
||||
} catch (innerE: Exception) {
|
||||
emptyList()
|
||||
}
|
||||
} ?: emptyList()
|
||||
|
||||
return Message(
|
||||
id = id,
|
||||
chatId = chatId,
|
||||
senderId = senderId,
|
||||
senderName = senderName,
|
||||
senderAvatar = senderAvatar,
|
||||
content = content,
|
||||
sequenceId = sequenceId,
|
||||
createdAt = createdAt,
|
||||
media = mediaDtos.map {
|
||||
chats.domain.model.Media(
|
||||
id = it.id ?: java.util.UUID.randomUUID().toString(),
|
||||
type = it.type ?: "unknown",
|
||||
url = (it.url ?: "").ensureAbsoluteUrl(baseUrl),
|
||||
filename = it.filename,
|
||||
size = it.size,
|
||||
duration = it.duration
|
||||
)
|
||||
},
|
||||
mediaType = mediaTypeEnum,
|
||||
reactions = reactionDtos.associate { it.emoji to it.count },
|
||||
isRead = isRead
|
||||
)
|
||||
}
|
||||
|
||||
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