Foundation for getOrCreate chat room

This commit is contained in:
Kgothatso Ngako
2026-05-31 20:29:09 +02:00
parent 16985b6545
commit c8ba6a0bf0
10 changed files with 218 additions and 30 deletions

View File

@@ -16,6 +16,7 @@ import ac.cord.auxiliary.compose.database.dao.NegentropySynchronizeResultDao
import ac.cord.auxiliary.compose.database.dao.NostrDao
import ac.cord.auxiliary.compose.database.dao.NostrEventDao
import ac.cord.auxiliary.compose.database.dao.NostrEventRelayDao
import ac.cord.auxiliary.compose.database.dao.NostrNip17Dao
import ac.cord.auxiliary.compose.database.dao.PostDao
import ac.cord.auxiliary.compose.database.dao.ProfileDao
import ac.cord.auxiliary.compose.database.dao.QuotedRelationDao
@@ -123,6 +124,8 @@ abstract class AuxDatabase: RoomDatabase() {
abstract fun nostrEventRelayDao(): NostrEventRelayDao
abstract fun nostrNip17Dao(): NostrNip17Dao
abstract fun postDao(): PostDao
abstract fun profileDao(): ProfileDao

View File

@@ -0,0 +1,18 @@
package ac.cord.auxiliary.compose.database.dao
import ac.cord.auxiliary.compose.database.AuxDatabase
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
import androidx.room3.Dao
import androidx.room3.Transaction
@Dao
abstract class NostrNip17Dao(
private val database: AuxDatabase
) {
@Transaction
open suspend fun getOrCreateChatRoom(publicKey: String): LocalChatRoom? {
return null
}
}

View File

@@ -63,7 +63,7 @@ data class ChatRoom(
// TODO: Add a LastChatMessageRelation
// val lastChatMessageId: String? = null,
// Might want to focus on the other last messages...
val initialGiftWrapUnsignedId: String,
val initialGiftWrapUnsignedId: String? = null,
override val createdAt: Instant = Clock.System.now(),
override val updatedAt: Instant = createdAt,

View File

@@ -0,0 +1,26 @@
package ac.cord.auxiliary.compose.database.repository
import ac.cord.auxiliary.compose.database.AuxDatabase
import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
import ac.cord.auxiliary.compose.repository.ChatRepository
import kotlinx.coroutines.CoroutineScope
class DatabaseChatRepository(
private val database: AuxDatabase,
private val scope: CoroutineScope
): ChatRepository {
override suspend fun getOrCreateChatRoom(publicKey: String): LocalChatRoom? {
return database.nostrNip17Dao().getOrCreateChatRoom(
publicKey
)
}
override fun updateChatRoomSubject(
publicKey: String,
subject: String
): ChatRoom? {
return null
}
}

View File

@@ -0,0 +1,22 @@
package ac.cord.auxiliary.compose.repository
import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
interface ChatRepository {
suspend fun getOrCreateChatRoom(publicKey: String): LocalChatRoom?
fun updateChatRoomSubject(publicKey: String, subject: String): ChatRoom?
companion object {
val NO_OP_CHAT_REPOSITORY = object: ChatRepository {
override suspend fun getOrCreateChatRoom(publicKey: String): LocalChatRoom? {
return null
}
override fun updateChatRoomSubject(publicKey: String, subject: String): ChatRoom? {
return null
}
}
}
}

View File

@@ -1,20 +1,28 @@
package ac.cord.auxiliary.compose.ui.composable
import ac.cord.auxiliary.compose.database.model.ChatRoom
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
import ac.cord.auxiliary.compose.repository.ChatRepository
import ac.cord.auxiliary.compose.repository.NostrRepository
import ac.cord.auxiliary.compose.ui.composable.widgets.LoadingDataIndicator
import ac.cord.auxiliary.compose.ui.theme.AuxTheme
import ac.cord.auxiliary.compose.ui.view.model.ChatRoomDetailViewModel
import ac.cord.auxiliary.compose.ui.view.state.ChatRoomDetailUIState
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.BottomAppBar
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Alignment
@@ -23,26 +31,65 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import kotlin.time.Clock
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalMaterial3Api::class)
@Composable
fun ChatRoomDetailScreen(
nostrRepository: NostrRepository
initialChatRoomDetailUIState: ChatRoomDetailUIState = ChatRoomDetailUIState.Loading,
nostrRepository: NostrRepository,
chatRepository: ChatRepository
) {
val chatRoomDetailViewModel: ChatRoomDetailViewModel = viewModel(
factory = ChatRoomDetailViewModel.factory(
nostrRepository = nostrRepository
publicKey = "",
initialChatRoomDetailUIState = initialChatRoomDetailUIState,
nostrRepository = nostrRepository,
chatRepository = chatRepository
)
)
Scaffold { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding)
) {
when (val chatRoomDetailUIState = chatRoomDetailViewModel.chatRoomDetailUIState) {
ChatRoomDetailUIState.Error -> {
Column(
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(
modifier = Modifier.height(50.dp)
)
Text(
text = "Something went wrong",
)
}
}
is ChatRoomDetailUIState.Loaded -> {
Scaffold(
topBar = {
TopAppBar(
title = {
Text(chatRoomDetailUIState.localChatRoom.chatRoom.subject ?: "Message")
}
)
},
bottomBar = {
BottomAppBar {
Text("Message actions coming soon...")
}
}
) { innerPadding ->
Column(
modifier = Modifier.padding(innerPadding)
) {
Text("Messages will appear here...")
}
}
}
ChatRoomDetailUIState.Loading -> {
Column(
modifier = Modifier.fillMaxWidth().padding(
10.dp
20.dp
),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(20.dp)
@@ -75,7 +122,9 @@ fun ChatRoomDetailScreen(
}
LaunchedEffect(true) {
chatRoomDetailViewModel.queueSynchronization()
if (initialChatRoomDetailUIState == ChatRoomDetailUIState.Loading) {
chatRoomDetailViewModel.initiateChatRoomDetail()
}
}
}
@@ -87,7 +136,18 @@ private fun UnqueuedProfileSynchronizationScreenPreview() {
modifier = Modifier.fillMaxSize()
) {
ChatRoomDetailScreen(
nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY
initialChatRoomDetailUIState = ChatRoomDetailUIState.Loaded(
localChatRoom = LocalChatRoom(
chatRoom = ChatRoom(
id = "",
userPublicKey = "",
subject = "sdfa",
initialGiftWrapUnsignedId = ""
)
)
),
nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY,
chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY
)
}
}

View File

@@ -1,6 +1,7 @@
package ac.cord.auxiliary.compose.ui.composable.navigation
import ac.cord.auxiliary.compose.AuxGlobal
import ac.cord.auxiliary.compose.database.repository.DatabaseChatRepository
import ac.cord.auxiliary.compose.database.repository.DatabaseNostrRepository
import ac.cord.auxiliary.compose.database.repository.DatabaseSearchRepository
import ac.cord.auxiliary.compose.managers.DatabaseManager
@@ -88,6 +89,11 @@ fun AuxNavHost(
database = databaseManager.auxDatabase,
applicationIOScope
)
val databaseChatRepository = DatabaseChatRepository(
database = databaseManager.auxDatabase,
applicationIOScope
)
val searchRepository = DatabaseSearchRepository(
database = databaseManager.auxDatabase
)
@@ -353,7 +359,8 @@ fun AuxNavHost(
}
composable<ChatRoomDetailRoute> {
ChatRoomDetailScreen(
nostrRepository = databaseNostrRepository
nostrRepository = databaseNostrRepository,
chatRepository = databaseChatRepository
)
}
composable<SearchRoute> {

View File

@@ -1,30 +1,31 @@
package ac.cord.auxiliary.compose.ui.view.model
import ac.cord.auxiliary.compose.repository.ChatRepository
import ac.cord.auxiliary.compose.repository.NostrRepository
import ac.cord.auxiliary.compose.ui.view.state.ChatRoomDetailUIState
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.viewmodel.initializer
import androidx.lifecycle.viewmodel.viewModelFactory
import co.touchlab.kermit.Logger
import kotlinx.coroutines.launch
class ChatRoomDetailViewModel(
val nostrRepository: NostrRepository
val publicKey: String,
initialChatRoomDetailUIState: ChatRoomDetailUIState,
val nostrRepository: NostrRepository,
val chatRepository: ChatRepository
): ViewModel() {
companion object {
private const val TAG = "DirectMessageDetailViewModel"
fun factory(
nostrRepository: NostrRepository
): ViewModelProvider.Factory = viewModelFactory {
initializer {
ChatRoomDetailViewModel(
nostrRepository = nostrRepository
)
}
}
}
var chatRoomDetailUIState: ChatRoomDetailUIState by mutableStateOf(initialChatRoomDetailUIState)
private set
private val logger = Logger.withTag(TAG)
@@ -33,4 +34,43 @@ class ChatRoomDetailViewModel(
fun queueSynchronization() {
logger.d { "queueSynchronization" }
}
// TODO: Render MessageList...
fun initiateChatRoomDetail() {
// TODO: GetOrCreateChatRoom
viewModelScope.launch {
val localChatRoom = chatRepository.getOrCreateChatRoom(publicKey)
chatRoomDetailUIState = if (localChatRoom == null) {
ChatRoomDetailUIState.Error
} else {
ChatRoomDetailUIState.Loaded(
localChatRoom = localChatRoom
)
}
queueSynchronization()
}
}
companion object {
private const val TAG = "ChatRoomDetailViewModel"
fun factory(
publicKey: String,
initialChatRoomDetailUIState: ChatRoomDetailUIState = ChatRoomDetailUIState.Loading,
nostrRepository: NostrRepository,
chatRepository: ChatRepository
): ViewModelProvider.Factory = viewModelFactory {
initializer {
ChatRoomDetailViewModel(
publicKey = publicKey,
initialChatRoomDetailUIState = initialChatRoomDetailUIState,
nostrRepository = nostrRepository,
chatRepository = chatRepository
)
}
}
}
}

View File

@@ -0,0 +1,13 @@
package ac.cord.auxiliary.compose.ui.view.state
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
sealed interface ChatRoomDetailUIState {
data class Loaded(
val localChatRoom: LocalChatRoom
): ChatRoomDetailUIState
data object Error: ChatRoomDetailUIState
data object Loading: ChatRoomDetailUIState
}