diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/database/repository/DatabaseChatRepository.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/database/repository/DatabaseChatRepository.kt index 1c6c2e55..2c4f9205 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/database/repository/DatabaseChatRepository.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/database/repository/DatabaseChatRepository.kt @@ -8,7 +8,6 @@ import at.torch.compose.database.model.GiftWrapPayload import at.torch.compose.database.model.MarmotInnerEvent import at.torch.compose.database.model.MarmotKeyPackage import at.torch.compose.database.model.Participant -import at.torch.compose.database.model.Profile import at.torch.compose.database.model.intermdiate.LocalChatMessage import at.torch.compose.database.model.intermdiate.LocalChatRoom import co.touchlab.kermit.Logger @@ -348,110 +347,46 @@ class DatabaseChatRepository( } } - override suspend fun leaveChatRoom(localChatRoom: LocalChatRoom) { - val text = "Left group." - - val mlsGroup = localChatRoom.chatRoom.mlsGroupState?.let { mlsGroupState -> - MlsGroup.restore( - MlsGroupState.decodeTls( - mlsGroupState.hexToByteArray() - ) + override suspend fun leaveChatRoom( + localChatRoom: LocalChatRoom, + onCompletion: () -> Unit + ) { + database.chatRoomDao().upsert( + chatRoom = localChatRoom.chatRoom.copy( + leftGroupAt = Clock.System.now() ) - } + ) - if (mlsGroup != null) { - // Create ChatRumor... - val createdAt = Clock.System.now().epochSeconds - val marmotInnerEventKind = ChatEvent.KIND + // TODO: Process this in the notary... + sendChatMessage( + text = "Left Group", + localChatRoom = localChatRoom, + // TODO: Add Information/LeaveChatGroup Message Type... + ) - val marmotInnerEventId = EventHasher.hashId( - pubKey = localChatRoom.chatRoom.userPublicKey, - createdAt = createdAt, - tags = emptyArray(), - content = text, - kind = marmotInnerEventKind - ) - - database.marmotInnerEventDao().upsert( - MarmotInnerEvent( - id = marmotInnerEventId, - publicKey = localChatRoom.chatRoom.userPublicKey, - createdAt = Instant.fromEpochSeconds(createdAt), - tags = emptyArray(), - content = text, - chatRoomId = localChatRoom.chatRoom.id, - kind = marmotInnerEventKind - ) - ) - - database.chatMessageDao().upsert( - ChatMessage( - content = text, - chatRoomId = localChatRoom.chatRoom.id, - senderPublicKey = localChatRoom.chatRoom.userPublicKey, - isUserMessage = true, - giftWrapPayloadId = null, - marmotGroupEventId = null, - marmotInnerEventId = marmotInnerEventId, - ) - ) - } else { - val innerEventKind = ChatMessageEvent.KIND - // Send GiftWrapMessage - val receiverTags = localChatRoom.localParticipants.filter { participant -> - participant.participant.participantPublicKey != localChatRoom.chatRoom.userPublicKey // TODO: Allow this where it's notes to self... - }.map { localParticipant -> - PTag.assemble( - localParticipant.participant.participantPublicKey, - localParticipant.participant.relayHint?.let { NormalizedRelayUrl(it) } - ) - } - - val createdAt = Clock.System.now().epochSeconds - val giftWrapPayloadId = EventHasher.hashId( - pubKey = localChatRoom.chatRoom.userPublicKey, - createdAt = createdAt, - tags = receiverTags.toTypedArray(), - content = text, - kind = innerEventKind - ) - - database.giftWrapPayloadDao().upsert( - GiftWrapPayload( - id = giftWrapPayloadId, - kind = innerEventKind, - tags = receiverTags.toTypedArray(), - createdAt = Instant.fromEpochSeconds(createdAt), - content = text, - chatRoomId = localChatRoom.chatRoom.id, - publicKey = localChatRoom.chatRoom.userPublicKey - ) - ) - - database.chatMessageDao().upsert( - ChatMessage( - content = text, - chatRoomId = localChatRoom.chatRoom.id, - senderPublicKey = localChatRoom.chatRoom.userPublicKey, - isUserMessage = true, - giftWrapPayloadId = giftWrapPayloadId, - marmotGroupEventId = null, - marmotInnerEventId = null - ) - ) - } + onCompletion.invoke() } - override suspend fun softDeleteChatRoom(chatRoom: ChatRoom) { + override suspend fun softDeleteChatRoom( + chatRoom: ChatRoom, + onCompletion: () -> Unit + ) { database.chatRoomDao().upsert( chatRoom.copy( deletedAt = Clock.System.now() ) ) + + onCompletion.invoke() } - override suspend fun deleteChatRoom(chatRoom: ChatRoom) { + override suspend fun deleteChatRoom( + chatRoom: ChatRoom, + onCompletion: () -> Unit + ) { database.chatRoomDao().delete(chatRoom) + + onCompletion.invoke() } companion object { diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/repository/ChatRepository.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/repository/ChatRepository.kt index 6efe1cef..2ac194dc 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/repository/ChatRepository.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/repository/ChatRepository.kt @@ -4,7 +4,6 @@ import at.torch.compose.database.model.ChatRoom import at.torch.compose.database.model.GiftWrapPayload import at.torch.compose.database.model.MarmotKeyPackage import at.torch.compose.database.model.Participant -import at.torch.compose.database.model.Profile import at.torch.compose.database.model.intermdiate.LocalChatMessage import at.torch.compose.database.model.intermdiate.LocalChatRoom import com.vitorpamplona.quartz.nip01Core.core.HexKey @@ -67,11 +66,20 @@ interface ChatRepository { suspend fun sealGiftWrapPayload(giftWrapPayload: GiftWrapPayload, nostrSignerSync: NostrSignerSync) - suspend fun leaveChatRoom(localChatRoom: LocalChatRoom) + suspend fun leaveChatRoom( + localChatRoom: LocalChatRoom, + onCompletion: () -> Unit + ) - suspend fun softDeleteChatRoom(chatRoom: ChatRoom) + suspend fun softDeleteChatRoom( + chatRoom: ChatRoom, + onCompletion: () -> Unit + ) - suspend fun deleteChatRoom(chatRoom: ChatRoom) + suspend fun deleteChatRoom( + chatRoom: ChatRoom, + onCompletion: () -> Unit + ) companion object { val NO_OP_CHAT_REPOSITORY = object: ChatRepository { @@ -160,15 +168,24 @@ interface ChatRepository { TODO("Not yet implemented") } - override suspend fun leaveChatRoom(localChatRoom: LocalChatRoom) { + override suspend fun leaveChatRoom( + localChatRoom: LocalChatRoom, + onCompletion: () -> Unit + ) { TODO("Not yet implemented") } - override suspend fun softDeleteChatRoom(chatRoom: ChatRoom) { + override suspend fun softDeleteChatRoom( + chatRoom: ChatRoom, + onCompletion: () -> Unit + ) { TODO("Not yet implemented") } - override suspend fun deleteChatRoom(chatRoom: ChatRoom) { + override suspend fun deleteChatRoom( + chatRoom: ChatRoom, + onCompletion: () -> Unit + ) { TODO("Not yet implemented") } } diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/ChatRoomDetailScreen.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/ChatRoomDetailScreen.kt index c4973866..c56667cd 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/ChatRoomDetailScreen.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/ChatRoomDetailScreen.kt @@ -63,6 +63,7 @@ fun ChatRoomDetailScreen( nostrRepository: NostrRepository, chatRepository: ChatRepository, onNavigateToRoute: (Route) -> Unit, + onPopBackToRoute: (Route) -> Unit ) { val chatRoomDetailViewModel: ChatRoomDetailViewModel = viewModel( factory = ChatRoomDetailViewModel.factory( @@ -213,10 +214,9 @@ fun ChatRoomDetailScreen( ), enabled = chatRoomDetailUIState.localChatRoom.chatRoom.leftGroupAt == null, // Disable after we leave group... onClick = { - // Leave group logic chatRoomDetailViewModel.leaveGroup( localChatRoom = chatRoomDetailUIState.localChatRoom, - onNavigateToRoute = onNavigateToRoute + onPopBackToRoute = onPopBackToRoute ) } ) { @@ -240,10 +240,9 @@ fun ChatRoomDetailScreen( ), enabled = chatRoomDetailUIState.localChatRoom.chatRoom.leftGroupAt != null, onClick = { - onNavigateToRoute.invoke( - ImplementationPendingRoute( - "Delete Group" - ) + chatRoomDetailViewModel.softDeleteGroup( + localChatRoom = chatRoomDetailUIState.localChatRoom, + onPopBackToRoute = onPopBackToRoute ) } ) { @@ -345,7 +344,8 @@ private fun ChatRoomMessagingScreenPreview() { ), nostrRepository = NostrRepository.NO_OP_NOSTR_REPOSITORY, chatRepository = ChatRepository.NO_OP_CHAT_REPOSITORY, - onNavigateToRoute = {} + onNavigateToRoute = {}, + onPopBackToRoute = {} ) } } diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/navigation/TorchNavHost.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/navigation/TorchNavHost.kt index 20b606de..2ffc4bdc 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/navigation/TorchNavHost.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/navigation/TorchNavHost.kt @@ -51,7 +51,7 @@ import at.torch.compose.ui.composable.navigation.routes.ChatRoomCreationRoute import at.torch.compose.ui.composable.navigation.routes.ChatRoomDetailRoute import at.torch.compose.ui.composable.navigation.routes.ChatRoomMessagingRoute import at.torch.compose.ui.composable.navigation.routes.CreateProfileRoute -import at.torch.compose.ui.composable.navigation.routes.FeedRoute +import at.torch.compose.ui.composable.navigation.routes.HomeRoute import at.torch.compose.ui.composable.navigation.routes.ImplementationPendingRoute import at.torch.compose.ui.composable.navigation.routes.KeyPackageManagementRoute import at.torch.compose.ui.composable.navigation.routes.LandingRoute @@ -444,7 +444,7 @@ fun TorchNavHost( SocialPreconditionScreen( onNavigateToSkipForNow = { navController.navigate( - route = FeedRoute( + route = HomeRoute( activeUserPublicKey = route.activeUserPubkey ) ) @@ -465,8 +465,8 @@ fun TorchNavHost( } ) } - composable { backStackEntry -> - val route = backStackEntry.toRoute() + composable { backStackEntry -> + val route = backStackEntry.toRoute() HomeScreen( activeUserPublicKey = route.activeUserPublicKey, onNavigateToRoute = { eventRoute -> @@ -594,6 +594,12 @@ fun TorchNavHost( navController.navigate( route = actionRoute ) + }, + onPopBackToRoute = { popRoute -> + navController.popBackStack( + route = popRoute, + inclusive = false + ) } ) } diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/navigation/routes/FeedRoute.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/navigation/routes/HomeRoute.kt similarity index 82% rename from composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/navigation/routes/FeedRoute.kt rename to composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/navigation/routes/HomeRoute.kt index 3145863f..694d7232 100755 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/navigation/routes/FeedRoute.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/composable/navigation/routes/HomeRoute.kt @@ -3,6 +3,6 @@ package at.torch.compose.ui.composable.navigation.routes import kotlinx.serialization.Serializable @Serializable -data class FeedRoute( +data class HomeRoute( val activeUserPublicKey: String ): Route() \ No newline at end of file diff --git a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/ChatRoomDetailViewModel.kt b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/ChatRoomDetailViewModel.kt index 68a79c5d..4060309a 100644 --- a/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/ChatRoomDetailViewModel.kt +++ b/composeApp/src/commonMain/kotlin/at/torch/compose/ui/view/model/ChatRoomDetailViewModel.kt @@ -9,11 +9,10 @@ import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.initializer import androidx.lifecycle.viewmodel.viewModelFactory -import at.torch.compose.database.model.ChatRoom import at.torch.compose.database.model.intermdiate.LocalChatRoom import at.torch.compose.repository.ChatRepository import at.torch.compose.repository.NostrRepository -import at.torch.compose.ui.composable.navigation.routes.ImplementationPendingRoute +import at.torch.compose.ui.composable.navigation.routes.HomeRoute import at.torch.compose.ui.composable.navigation.routes.Route import at.torch.compose.ui.view.state.ChatRoomDetailUIState import co.touchlab.kermit.Logger @@ -46,6 +45,7 @@ class ChatRoomDetailViewModel( viewModelScope.launch(Dispatchers.IO) { val localChatRoom = chatRepository.getChatRoomByIdentifier(chatRoomId) + logger.d("localChatRoom: $localChatRoom") chatRoomDetailUIState = if (localChatRoom == null) { // Initiate new chat... and navigate to new chat... ChatRoomDetailUIState.Error("Missing chat room") @@ -59,20 +59,45 @@ class ChatRoomDetailViewModel( fun leaveGroup( localChatRoom: LocalChatRoom, - onNavigateToRoute: (Route) -> Unit + onPopBackToRoute: (Route) -> Unit ) { viewModelScope.launch(Dispatchers.IO) { - chatRepository.leaveChatRoom(localChatRoom) - - viewModelScope.launch(Dispatchers.Main) { - onNavigateToRoute.invoke( - ImplementationPendingRoute( - "Leave Group" - ) - ) - } + chatRepository.leaveChatRoom( + localChatRoom, + onCompletion = { + viewModelScope.launch(Dispatchers.Main) { + onPopBackToRoute.invoke( + HomeRoute( + activeUserPublicKey = activeUserPublicKey, + ) + ) + } + } + ) } } + + fun softDeleteGroup( + localChatRoom: LocalChatRoom, + onPopBackToRoute: (Route) -> Unit + ) { + viewModelScope.launch(Dispatchers.IO) { + chatRepository.softDeleteChatRoom( + chatRoom = localChatRoom.chatRoom, + onCompletion = { + viewModelScope.launch(Dispatchers.Main) { + onPopBackToRoute.invoke( + HomeRoute( + activeUserPublicKey = activeUserPublicKey, + ) + ) + } + } + ) + } + } + + companion object { private const val TAG = "ChatRoomDetailViewModel"