android: add message room schema and core domain models
Some checks failed
CI / test (push) Failing after 2m6s
Some checks failed
CI / test (push) Failing after 2m6s
This commit is contained in:
@@ -64,3 +64,10 @@
|
|||||||
- Fixed Hilt dependency cycle by separating refresh `AuthApiService` with a dedicated qualifier.
|
- Fixed Hilt dependency cycle by separating refresh `AuthApiService` with a dedicated qualifier.
|
||||||
- Added `CoroutineDispatcher` DI provider and qualifier for repositories.
|
- Added `CoroutineDispatcher` DI provider and qualifier for repositories.
|
||||||
- Fixed Material3 experimental API opt-in and removed deprecated `StateFlow.distinctUntilChanged()` usage.
|
- 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`.
|
||||||
|
|||||||
@@ -5,15 +5,21 @@ import androidx.room.RoomDatabase
|
|||||||
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
|
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.ChatEntity
|
||||||
import ru.daemonlord.messenger.data.chat.local.entity.UserShortEntity
|
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(
|
@Database(
|
||||||
entities = [
|
entities = [
|
||||||
ChatEntity::class,
|
ChatEntity::class,
|
||||||
UserShortEntity::class,
|
UserShortEntity::class,
|
||||||
|
MessageEntity::class,
|
||||||
|
MessageAttachmentEntity::class,
|
||||||
],
|
],
|
||||||
version = 1,
|
version = 2,
|
||||||
exportSchema = false,
|
exportSchema = false,
|
||||||
)
|
)
|
||||||
abstract class MessengerDatabase : RoomDatabase() {
|
abstract class MessengerDatabase : RoomDatabase() {
|
||||||
abstract fun chatDao(): ChatDao
|
abstract fun chatDao(): ChatDao
|
||||||
|
abstract fun messageDao(): MessageDao
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
|
)
|
||||||
@@ -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?,
|
||||||
|
)
|
||||||
@@ -9,6 +9,7 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
|||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
|
import ru.daemonlord.messenger.data.chat.local.dao.ChatDao
|
||||||
import ru.daemonlord.messenger.data.chat.local.db.MessengerDatabase
|
import ru.daemonlord.messenger.data.chat.local.db.MessengerDatabase
|
||||||
|
import ru.daemonlord.messenger.data.message.local.dao.MessageDao
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@@ -31,4 +32,8 @@ object DatabaseModule {
|
|||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideChatDao(database: MessengerDatabase): ChatDao = database.chatDao()
|
fun provideChatDao(database: MessengerDatabase): ChatDao = database.chatDao()
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideMessageDao(database: MessengerDatabase): MessageDao = database.messageDao()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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?,
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user