android: add message room schema and core domain models
Some checks failed
CI / test (push) Failing after 2m6s

This commit is contained in:
Codex
2026-03-08 23:03:34 +03:00
parent 4939754de8
commit 5ad89fc05b
7 changed files with 169 additions and 1 deletions

View File

@@ -64,3 +64,10 @@
- Fixed Hilt dependency cycle by separating refresh `AuthApiService` with a dedicated qualifier.
- Added `CoroutineDispatcher` DI provider and qualifier for repositories.
- Fixed Material3 experimental API opt-in and removed deprecated `StateFlow.distinctUntilChanged()` usage.
### Step 11 - Sprint A / 1) Message Room + models
- Added message domain model (`MessageItem`) for chat screen rendering.
- Added Room entities `messages` and `message_attachments` with chat-history indexes.
- Added `MessageDao` with observe/pagination/upsert/delete APIs.
- Updated `MessengerDatabase` schema to include message tables and DAO.
- Added Hilt DI provider for `MessageDao`.

View File

@@ -5,15 +5,21 @@ import androidx.room.RoomDatabase
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
import ru.daemonlord.messenger.data.chat.local.entity.ChatEntity
import ru.daemonlord.messenger.data.chat.local.entity.UserShortEntity
import ru.daemonlord.messenger.data.message.local.dao.MessageDao
import ru.daemonlord.messenger.data.message.local.entity.MessageAttachmentEntity
import ru.daemonlord.messenger.data.message.local.entity.MessageEntity
@Database(
entities = [
ChatEntity::class,
UserShortEntity::class,
MessageEntity::class,
MessageAttachmentEntity::class,
],
version = 1,
version = 2,
exportSchema = false,
)
abstract class MessengerDatabase : RoomDatabase() {
abstract fun chatDao(): ChatDao
abstract fun messageDao(): MessageDao
}

View File

@@ -0,0 +1,67 @@
package ru.daemonlord.messenger.data.message.local.dao
import androidx.room.Dao
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Transaction
import kotlinx.coroutines.flow.Flow
import ru.daemonlord.messenger.data.message.local.entity.MessageAttachmentEntity
import ru.daemonlord.messenger.data.message.local.entity.MessageEntity
@Dao
interface MessageDao {
@Query(
"""
SELECT * FROM messages
WHERE chat_id = :chatId
ORDER BY created_at DESC, id DESC
LIMIT :limit
"""
)
fun observeRecentMessages(
chatId: Long,
limit: Int = 50,
): Flow<List<MessageEntity>>
@Query(
"""
SELECT * FROM messages
WHERE chat_id = :chatId
AND id < :beforeMessageId
ORDER BY created_at DESC, id DESC
LIMIT :limit
"""
)
suspend fun getMessagesPage(
chatId: Long,
beforeMessageId: Long,
limit: Int = 50,
): List<MessageEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertMessages(messages: List<MessageEntity>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun upsertAttachments(attachments: List<MessageAttachmentEntity>)
@Query("DELETE FROM messages WHERE chat_id = :chatId")
suspend fun clearChatMessages(chatId: Long)
@Query("DELETE FROM messages WHERE id = :messageId")
suspend fun deleteMessage(messageId: Long)
@Transaction
suspend fun clearAndReplaceMessages(
chatId: Long,
messages: List<MessageEntity>,
attachments: List<MessageAttachmentEntity>,
) {
clearChatMessages(chatId = chatId)
upsertMessages(messages)
if (attachments.isNotEmpty()) {
upsertAttachments(attachments)
}
}
}

View File

@@ -0,0 +1,26 @@
package ru.daemonlord.messenger.data.message.local.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
tableName = "message_attachments",
indices = [
Index(value = ["message_id"]),
],
)
data class MessageAttachmentEntity(
@PrimaryKey
@ColumnInfo(name = "id")
val id: Long,
@ColumnInfo(name = "message_id")
val messageId: Long,
@ColumnInfo(name = "file_url")
val fileUrl: String,
@ColumnInfo(name = "file_type")
val fileType: String,
@ColumnInfo(name = "file_size")
val fileSize: Long,
)

View File

@@ -0,0 +1,42 @@
package ru.daemonlord.messenger.data.message.local.entity
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
tableName = "messages",
indices = [
Index(value = ["chat_id", "created_at"]),
Index(value = ["chat_id", "id"]),
Index(value = ["chat_id", "updated_at"]),
],
)
data class MessageEntity(
@PrimaryKey
@ColumnInfo(name = "id")
val id: Long,
@ColumnInfo(name = "chat_id")
val chatId: Long,
@ColumnInfo(name = "sender_id")
val senderId: Long,
@ColumnInfo(name = "sender_display_name")
val senderDisplayName: String?,
@ColumnInfo(name = "sender_username")
val senderUsername: String?,
@ColumnInfo(name = "sender_avatar_url")
val senderAvatarUrl: String?,
@ColumnInfo(name = "reply_to_message_id")
val replyToMessageId: Long?,
@ColumnInfo(name = "type")
val type: String,
@ColumnInfo(name = "text")
val text: String?,
@ColumnInfo(name = "status")
val status: String?,
@ColumnInfo(name = "created_at")
val createdAt: String,
@ColumnInfo(name = "updated_at")
val updatedAt: String?,
)

View File

@@ -9,6 +9,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
import ru.daemonlord.messenger.data.chat.local.db.MessengerDatabase
import ru.daemonlord.messenger.data.message.local.dao.MessageDao
import javax.inject.Singleton
@Module
@@ -31,4 +32,8 @@ object DatabaseModule {
@Provides
@Singleton
fun provideChatDao(database: MessengerDatabase): ChatDao = database.chatDao()
@Provides
@Singleton
fun provideMessageDao(database: MessengerDatabase): MessageDao = database.messageDao()
}

View File

@@ -0,0 +1,15 @@
package ru.daemonlord.messenger.domain.message.model
data class MessageItem(
val id: Long,
val chatId: Long,
val senderId: Long,
val senderDisplayName: String?,
val type: String,
val text: String?,
val createdAt: String,
val updatedAt: String?,
val isOutgoing: Boolean,
val status: String?,
val replyToMessageId: Long?,
)