diff --git a/composeApp/schemas/ac.cord.auxiliary.compose.database.AuxDatabase/1.json b/composeApp/schemas/ac.cord.auxiliary.compose.database.AuxDatabase/1.json index 9e3ddea4..dfec12a5 100644 --- a/composeApp/schemas/ac.cord.auxiliary.compose.database.AuxDatabase/1.json +++ b/composeApp/schemas/ac.cord.auxiliary.compose.database.AuxDatabase/1.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 1, - "identityHash": "6eec904dd8f91ab435301e23425eb536", + "identityHash": "10c21ff08b8acfda26aa17864d1e940e", "entities": [ { "tableName": "BroadcastNostrEventReceipt", @@ -380,7 +380,7 @@ }, { "tableName": "ChatRoom", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `subject` TEXT, `initialGiftWrapUnsignedId` TEXT NOT NULL, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`userPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`initialGiftWrapUnsignedId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` TEXT NOT NULL, `userPublicKey` TEXT NOT NULL, `subject` TEXT, `initialGiftWrapUnsignedId` TEXT, `createdAt` INTEGER NOT NULL, `updatedAt` INTEGER NOT NULL, `savedAt` INTEGER NOT NULL, `viewedAt` INTEGER, `deletedAt` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`userPublicKey`) REFERENCES `Profile`(`publicKey`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`initialGiftWrapUnsignedId`) REFERENCES `GiftWrapPayload`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", @@ -402,8 +402,7 @@ { "fieldPath": "initialGiftWrapUnsignedId", "columnName": "initialGiftWrapUnsignedId", - "affinity": "TEXT", - "notNull": true + "affinity": "TEXT" }, { "fieldPath": "createdAt", @@ -2544,7 +2543,7 @@ ], "setupQueries": [ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", - "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6eec904dd8f91ab435301e23425eb536')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '10c21ff08b8acfda26aa17864d1e940e')" ] } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/AuxDatabase.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/AuxDatabase.kt index 25a67d0b..bf63c28a 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/AuxDatabase.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/AuxDatabase.kt @@ -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 diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrNip17Dao.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrNip17Dao.kt new file mode 100644 index 00000000..94fe3284 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/dao/NostrNip17Dao.kt @@ -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 + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/ChatRoom.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/ChatRoom.kt index aa127d1d..622c7112 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/ChatRoom.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/model/ChatRoom.kt @@ -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, diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseChatRepository.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseChatRepository.kt new file mode 100644 index 00000000..ae264dc8 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/database/repository/DatabaseChatRepository.kt @@ -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 + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/repository/ChatRepository.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/repository/ChatRepository.kt new file mode 100644 index 00000000..824aa7f7 --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/repository/ChatRepository.kt @@ -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 + } + } + } +} \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/ChatRoomDetailScreen.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/ChatRoomDetailScreen.kt index b5de7323..044a74b9 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/ChatRoomDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/ChatRoomDetailScreen.kt @@ -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 ) } } diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/AuxNavHost.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/AuxNavHost.kt index 8e3a90c1..b0349852 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/AuxNavHost.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/composable/navigation/AuxNavHost.kt @@ -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 { ChatRoomDetailScreen( - nostrRepository = databaseNostrRepository + nostrRepository = databaseNostrRepository, + chatRepository = databaseChatRepository ) } composable { diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/ChatRoomDetailViewModel.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/ChatRoomDetailViewModel.kt index d5b9e63b..138130e0 100644 --- a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/ChatRoomDetailViewModel.kt +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/model/ChatRoomDetailViewModel.kt @@ -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 + ) + } + } + } } \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/state/ChatRoomDetailUIState.kt b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/state/ChatRoomDetailUIState.kt new file mode 100755 index 00000000..4d26b3bd --- /dev/null +++ b/composeApp/src/commonMain/kotlin/ac/cord/auxiliary/compose/ui/view/state/ChatRoomDetailUIState.kt @@ -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 +} +