storing chats

This commit is contained in:
2025-07-03 21:43:49 -04:00
parent e22cc7cf5c
commit 50d1aae039
9 changed files with 169 additions and 9 deletions

View File

@@ -83,6 +83,12 @@ dependencies {
implementation("com.google.dagger:hilt-android:2.50")
kapt("com.google.dagger:hilt-compiler:2.50")
// Room dependencies
val roomVersion = "2.6.1"
implementation("androidx.room:room-runtime:$roomVersion")
implementation("androidx.room:room-ktx:$roomVersion")
kapt("androidx.room:room-compiler:$roomVersion")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.5")
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

View File

@@ -0,0 +1,15 @@
package com.mattintech.lchat.data.db
import androidx.room.Database
import androidx.room.RoomDatabase
import com.mattintech.lchat.data.db.dao.MessageDao
import com.mattintech.lchat.data.db.entities.MessageEntity
@Database(
entities = [MessageEntity::class],
version = 1,
exportSchema = false
)
abstract class LChatDatabase : RoomDatabase() {
abstract fun messageDao(): MessageDao
}

View File

@@ -0,0 +1,29 @@
package com.mattintech.lchat.data.db.dao
import androidx.room.*
import com.mattintech.lchat.data.db.entities.MessageEntity
import kotlinx.coroutines.flow.Flow
@Dao
interface MessageDao {
@Query("SELECT * FROM messages WHERE roomName = :roomName ORDER BY timestamp ASC")
fun getMessagesForRoom(roomName: String): Flow<List<MessageEntity>>
@Query("SELECT * FROM messages WHERE roomName = :roomName ORDER BY timestamp ASC")
suspend fun getMessagesForRoomOnce(roomName: String): List<MessageEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMessage(message: MessageEntity)
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertMessages(messages: List<MessageEntity>)
@Query("DELETE FROM messages WHERE roomName = :roomName")
suspend fun deleteMessagesForRoom(roomName: String)
@Query("DELETE FROM messages")
suspend fun deleteAllMessages()
@Query("SELECT COUNT(*) FROM messages WHERE roomName = :roomName")
suspend fun getMessageCountForRoom(roomName: String): Int
}

View File

@@ -0,0 +1,16 @@
package com.mattintech.lchat.data.db.entities
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity(tableName = "messages")
data class MessageEntity(
@PrimaryKey
val id: String,
val roomName: String,
val userId: String,
val userName: String,
val content: String,
val timestamp: Long,
val isOwnMessage: Boolean = false
)

View File

@@ -0,0 +1,27 @@
package com.mattintech.lchat.data.db.mappers
import com.mattintech.lchat.data.Message
import com.mattintech.lchat.data.db.entities.MessageEntity
fun MessageEntity.toMessage(): Message {
return Message(
id = id,
userId = userId,
userName = userName,
content = content,
timestamp = timestamp,
isOwnMessage = isOwnMessage
)
}
fun Message.toEntity(roomName: String): MessageEntity {
return MessageEntity(
id = id,
roomName = roomName,
userId = userId,
userName = userName,
content = content,
timestamp = timestamp,
isOwnMessage = isOwnMessage
)
}

View File

@@ -0,0 +1,14 @@
package com.mattintech.lchat.data.db.migrations
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
// Placeholder for future migrations
object Migrations {
// Example migration from version 1 to 2
// val MIGRATION_1_2 = object : Migration(1, 2) {
// override fun migrate(database: SupportSQLiteDatabase) {
// // Migration code here
// }
// }
}

View File

@@ -1,6 +1,9 @@
package com.mattintech.lchat.di
import android.content.Context
import androidx.room.Room
import com.mattintech.lchat.data.db.LChatDatabase
import com.mattintech.lchat.data.db.dao.MessageDao
import com.mattintech.lchat.network.WifiAwareManager
import dagger.Module
import dagger.Provides
@@ -20,4 +23,21 @@ object AppModule {
): WifiAwareManager {
return WifiAwareManager(context)
}
@Provides
@Singleton
fun provideLChatDatabase(
@ApplicationContext context: Context
): LChatDatabase {
return Room.databaseBuilder(
context,
LChatDatabase::class.java,
"lchat_database"
).build()
}
@Provides
fun provideMessageDao(database: LChatDatabase): MessageDao {
return database.messageDao()
}
}

View File

@@ -2,26 +2,36 @@ package com.mattintech.lchat.repository
import android.content.Context
import com.mattintech.lchat.data.Message
import com.mattintech.lchat.data.db.dao.MessageDao
import com.mattintech.lchat.data.db.mappers.toEntity
import com.mattintech.lchat.data.db.mappers.toMessage
import com.mattintech.lchat.network.WifiAwareManager
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.*
import java.util.UUID
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class ChatRepository @Inject constructor(
@ApplicationContext private val context: Context
@ApplicationContext private val context: Context,
private val wifiAwareManager: WifiAwareManager,
private val messageDao: MessageDao
) {
private val wifiAwareManager = WifiAwareManager(context)
private var currentRoomName: String = ""
private val _messages = MutableStateFlow<List<Message>>(emptyList())
val messages: StateFlow<List<Message>> = _messages.asStateFlow()
// Flow that combines in-memory and database messages
fun getMessagesFlow(roomName: String): Flow<List<Message>> {
return messageDao.getMessagesForRoom(roomName)
.map { entities -> entities.map { it.toMessage() } }
.onStart { loadMessagesFromDatabase(roomName) }
}
private val _connectionState = MutableStateFlow<ConnectionState>(ConnectionState.Disconnected)
val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()
@@ -69,9 +79,19 @@ class ChatRepository @Inject constructor(
}
fun startHostMode(roomName: String) {
currentRoomName = roomName
wifiAwareManager.startHostMode(roomName)
_connectionState.value = ConnectionState.Hosting(roomName)
startConnectionMonitoring()
loadMessagesFromDatabase(roomName)
}
private fun loadMessagesFromDatabase(roomName: String) {
CoroutineScope(Dispatchers.IO).launch {
val storedMessages = messageDao.getMessagesForRoomOnce(roomName)
.map { it.toMessage() }
_messages.value = storedMessages
}
}
fun startClientMode() {
@@ -104,6 +124,11 @@ class ChatRepository @Inject constructor(
private fun addMessage(message: Message) {
_messages.value = _messages.value + message
// Save to database
CoroutineScope(Dispatchers.IO).launch {
messageDao.insertMessage(message.toEntity(currentRoomName))
}
}
fun clearMessages() {
@@ -118,7 +143,9 @@ class ChatRepository @Inject constructor(
connectionCallback = callback
wifiAwareManager.setConnectionCallback { roomName, isConnected ->
if (isConnected) {
currentRoomName = roomName
_connectionState.value = ConnectionState.Connected(roomName)
loadMessagesFromDatabase(roomName)
} else {
_connectionState.value = ConnectionState.Disconnected
}

View File

@@ -7,9 +7,8 @@ import androidx.lifecycle.viewModelScope
import com.mattintech.lchat.data.Message
import com.mattintech.lchat.repository.ChatRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import java.util.UUID
import javax.inject.Inject
@@ -20,6 +19,7 @@ sealed class ChatState {
data class Error(val message: String) : ChatState()
}
@OptIn(ExperimentalCoroutinesApi::class)
@HiltViewModel
class ChatViewModel @Inject constructor(
private val chatRepository: ChatRepository
@@ -28,7 +28,10 @@ class ChatViewModel @Inject constructor(
private val _state = MutableLiveData<ChatState>(ChatState.Connected)
val state: LiveData<ChatState> = _state
val messages: StateFlow<List<Message>> = chatRepository.messages
private val _messagesFlow = MutableStateFlow<Flow<List<Message>>>(flowOf(emptyList()))
val messages: StateFlow<List<Message>> = _messagesFlow
.flatMapLatest { it }
.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5000),
@@ -60,6 +63,9 @@ class ChatViewModel @Inject constructor(
this.isHost = isHost
this.currentUserId = UUID.randomUUID().toString()
// Set up messages flow for this room
_messagesFlow.value = chatRepository.getMessagesFlow(roomName)
// Setup message callback if needed for additional processing
chatRepository.setMessageCallback { userId, userName, content ->
// Can add additional message processing here if needed