Leave and delete group logic

This commit is contained in:
Kgothatso Ngako
2026-07-13 00:35:20 +02:00
parent 947ae439f0
commit 30db090de8
6 changed files with 106 additions and 123 deletions

View File

@@ -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 {

View File

@@ -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")
}
}

View File

@@ -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 = {}
)
}
}

View File

@@ -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<FeedRoute> { backStackEntry ->
val route = backStackEntry.toRoute<FeedRoute>()
composable<HomeRoute> { backStackEntry ->
val route = backStackEntry.toRoute<HomeRoute>()
HomeScreen(
activeUserPublicKey = route.activeUserPublicKey,
onNavigateToRoute = { eventRoute ->
@@ -594,6 +594,12 @@ fun TorchNavHost(
navController.navigate(
route = actionRoute
)
},
onPopBackToRoute = { popRoute ->
navController.popBackStack(
route = popRoute,
inclusive = false
)
}
)
}

View File

@@ -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()

View File

@@ -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"