storing chats
This commit is contained in:
@@ -83,6 +83,12 @@ dependencies {
|
|||||||
implementation("com.google.dagger:hilt-android:2.50")
|
implementation("com.google.dagger:hilt-android:2.50")
|
||||||
kapt("com.google.dagger:hilt-compiler: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")
|
testImplementation("junit:junit:4.13.2")
|
||||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
}
|
||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.mattintech.lchat.di
|
package com.mattintech.lchat.di
|
||||||
|
|
||||||
import android.content.Context
|
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 com.mattintech.lchat.network.WifiAwareManager
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
@@ -20,4 +23,21 @@ object AppModule {
|
|||||||
): WifiAwareManager {
|
): WifiAwareManager {
|
||||||
return WifiAwareManager(context)
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -2,26 +2,36 @@ package com.mattintech.lchat.repository
|
|||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.mattintech.lchat.data.Message
|
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 com.mattintech.lchat.network.WifiAwareManager
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
|
||||||
@Singleton
|
@Singleton
|
||||||
class ChatRepository @Inject constructor(
|
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())
|
private val _messages = MutableStateFlow<List<Message>>(emptyList())
|
||||||
val messages: StateFlow<List<Message>> = _messages.asStateFlow()
|
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)
|
private val _connectionState = MutableStateFlow<ConnectionState>(ConnectionState.Disconnected)
|
||||||
val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()
|
val connectionState: StateFlow<ConnectionState> = _connectionState.asStateFlow()
|
||||||
|
|
||||||
@@ -69,9 +79,19 @@ class ChatRepository @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun startHostMode(roomName: String) {
|
fun startHostMode(roomName: String) {
|
||||||
|
currentRoomName = roomName
|
||||||
wifiAwareManager.startHostMode(roomName)
|
wifiAwareManager.startHostMode(roomName)
|
||||||
_connectionState.value = ConnectionState.Hosting(roomName)
|
_connectionState.value = ConnectionState.Hosting(roomName)
|
||||||
startConnectionMonitoring()
|
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() {
|
fun startClientMode() {
|
||||||
@@ -104,6 +124,11 @@ class ChatRepository @Inject constructor(
|
|||||||
|
|
||||||
private fun addMessage(message: Message) {
|
private fun addMessage(message: Message) {
|
||||||
_messages.value = _messages.value + message
|
_messages.value = _messages.value + message
|
||||||
|
|
||||||
|
// Save to database
|
||||||
|
CoroutineScope(Dispatchers.IO).launch {
|
||||||
|
messageDao.insertMessage(message.toEntity(currentRoomName))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearMessages() {
|
fun clearMessages() {
|
||||||
@@ -118,7 +143,9 @@ class ChatRepository @Inject constructor(
|
|||||||
connectionCallback = callback
|
connectionCallback = callback
|
||||||
wifiAwareManager.setConnectionCallback { roomName, isConnected ->
|
wifiAwareManager.setConnectionCallback { roomName, isConnected ->
|
||||||
if (isConnected) {
|
if (isConnected) {
|
||||||
|
currentRoomName = roomName
|
||||||
_connectionState.value = ConnectionState.Connected(roomName)
|
_connectionState.value = ConnectionState.Connected(roomName)
|
||||||
|
loadMessagesFromDatabase(roomName)
|
||||||
} else {
|
} else {
|
||||||
_connectionState.value = ConnectionState.Disconnected
|
_connectionState.value = ConnectionState.Disconnected
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,9 +7,8 @@ import androidx.lifecycle.viewModelScope
|
|||||||
import com.mattintech.lchat.data.Message
|
import com.mattintech.lchat.data.Message
|
||||||
import com.mattintech.lchat.repository.ChatRepository
|
import com.mattintech.lchat.repository.ChatRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.*
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
@@ -20,6 +19,7 @@ sealed class ChatState {
|
|||||||
data class Error(val message: String) : ChatState()
|
data class Error(val message: String) : ChatState()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class ChatViewModel @Inject constructor(
|
class ChatViewModel @Inject constructor(
|
||||||
private val chatRepository: ChatRepository
|
private val chatRepository: ChatRepository
|
||||||
@@ -28,7 +28,10 @@ class ChatViewModel @Inject constructor(
|
|||||||
private val _state = MutableLiveData<ChatState>(ChatState.Connected)
|
private val _state = MutableLiveData<ChatState>(ChatState.Connected)
|
||||||
val state: LiveData<ChatState> = _state
|
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(
|
.stateIn(
|
||||||
scope = viewModelScope,
|
scope = viewModelScope,
|
||||||
started = SharingStarted.WhileSubscribed(5000),
|
started = SharingStarted.WhileSubscribed(5000),
|
||||||
@@ -60,6 +63,9 @@ class ChatViewModel @Inject constructor(
|
|||||||
this.isHost = isHost
|
this.isHost = isHost
|
||||||
this.currentUserId = UUID.randomUUID().toString()
|
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
|
// Setup message callback if needed for additional processing
|
||||||
chatRepository.setMessageCallback { userId, userName, content ->
|
chatRepository.setMessageCallback { userId, userName, content ->
|
||||||
// Can add additional message processing here if needed
|
// Can add additional message processing here if needed
|
||||||
|
|||||||
Reference in New Issue
Block a user