Add description to chat room
This commit is contained in:
@@ -1,26 +1,32 @@
|
||||
package at.torch.compose.database.dao
|
||||
|
||||
import androidx.room3.Dao
|
||||
import androidx.room3.Delete
|
||||
import androidx.room3.Query
|
||||
import androidx.room3.Transaction
|
||||
import androidx.room3.Upsert
|
||||
import at.torch.compose.database.model.ChatRoom
|
||||
import at.torch.compose.database.model.intermdiate.LocalChatRoom
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
@Dao
|
||||
interface ChatRoomDao {
|
||||
@Transaction
|
||||
@Query("SELECT * FROM ChatRoom WHERE id = :id")
|
||||
fun findChatRoomById(id: String): at.torch.compose.database.model.intermdiate.LocalChatRoom?
|
||||
fun findChatRoomById(id: String): LocalChatRoom?
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM ChatRoom WHERE userPublicKey = :userPublicKey")
|
||||
fun getChatRoomListByUserPublicKey(userPublicKey: String): List<at.torch.compose.database.model.intermdiate.LocalChatRoom>
|
||||
fun getChatRoomListByUserPublicKey(userPublicKey: String): List<LocalChatRoom>
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM ChatRoom WHERE userPublicKey = :userPublicKey")
|
||||
fun observeChatRoomListByUserPublicKey(userPublicKey: String): Flow<List<at.torch.compose.database.model.intermdiate.LocalChatRoom>>
|
||||
fun observeChatRoomListByUserPublicKey(userPublicKey: String): Flow<List<LocalChatRoom>>
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(chatRoom: at.torch.compose.database.model.ChatRoom)
|
||||
suspend fun upsert(chatRoom: ChatRoom)
|
||||
|
||||
@Delete
|
||||
suspend fun delete(chatRoom: ChatRoom)
|
||||
|
||||
}
|
||||
@@ -56,6 +56,8 @@ abstract class MarmotOutboundDao(
|
||||
|
||||
@Transaction
|
||||
open suspend fun createMlsDirectMessageChatRoom(
|
||||
name: String? = null,
|
||||
description: String? = null,
|
||||
userPublicKey: HexKey,
|
||||
peerPublicKey: HexKey,
|
||||
peerKeyPackage: MarmotKeyPackage,
|
||||
@@ -78,7 +80,8 @@ abstract class MarmotOutboundDao(
|
||||
id = nostrGroupId,
|
||||
userPublicKey = userPublicKey,
|
||||
mlsGroupState = mlsGroup.saveState().encodeTls().toHex(),
|
||||
subject = null,
|
||||
subject = name,
|
||||
description = description
|
||||
)
|
||||
database.chatRoomDao().upsert(
|
||||
chatRoom
|
||||
@@ -275,7 +278,6 @@ abstract class MarmotOutboundDao(
|
||||
)
|
||||
}
|
||||
)
|
||||
// TODO: Save chatMessage
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -510,6 +510,7 @@ abstract class NostrDao(
|
||||
id = decryptedGiftWrapPayload.chatRoomId,
|
||||
userPublicKey = userPublicKey,
|
||||
subject = decryptedGiftWrapPayload.parseSubject(),
|
||||
description = null,
|
||||
createdAt = decryptedGiftWrapPayload.createdAt,
|
||||
initialGiftWrapPayloadId = decryptedGiftWrapPayload.id,
|
||||
mlsGroupState = null
|
||||
@@ -736,6 +737,7 @@ abstract class NostrDao(
|
||||
id = nostrGroupId,
|
||||
userPublicKey = userPublicKey,
|
||||
subject = group.currentMarmotData()?.name?.ifBlank { null },
|
||||
description = group.currentMarmotData()?.description?.ifBlank { null },
|
||||
initialGiftWrapPayloadId = decryptedGiftWrapPayload.id,
|
||||
createdAt = decryptedGiftWrapPayload.createdAt,
|
||||
mlsGroupState = group.saveState().encodeTls().toHex(),
|
||||
|
||||
@@ -28,7 +28,14 @@ abstract class NostrNip17Dao(
|
||||
val logger = Logger.withTag(TAG)
|
||||
|
||||
@Transaction
|
||||
open suspend fun getOrCreateChatRoom(chatRoomId: String, activeUserPublicKey: HexKey, relayHint: String?, defaultSubject: String? = null, mlsGroupState: String? = null): LocalChatRoom? {
|
||||
open suspend fun getOrCreateChatRoom(
|
||||
chatRoomId: HexKey,
|
||||
activeUserPublicKey: HexKey,
|
||||
relayHint: String?,
|
||||
defaultSubject: String? = null,
|
||||
description: String? = null,
|
||||
mlsGroupState: String? = null
|
||||
): LocalChatRoom? {
|
||||
logger.d("getOrCreateChatRoom: $chatRoomId")
|
||||
|
||||
|
||||
@@ -51,7 +58,8 @@ abstract class NostrNip17Dao(
|
||||
id = chatRoomId,
|
||||
userPublicKey = activeUserPublicKey,
|
||||
subject = defaultSubject,
|
||||
mlsGroupState = mlsGroupState
|
||||
mlsGroupState = mlsGroupState,
|
||||
description = description
|
||||
)
|
||||
database.chatRoomDao().upsert(chatRoom)
|
||||
|
||||
|
||||
@@ -58,6 +58,8 @@ data class ChatRoom(
|
||||
*/
|
||||
val subject: String?,
|
||||
|
||||
val description: String?,
|
||||
|
||||
val mlsGroupState: String?,
|
||||
|
||||
/**
|
||||
@@ -75,6 +77,8 @@ data class ChatRoom(
|
||||
// Might want to focus on the other last messages...
|
||||
val initialGiftWrapPayloadId: HexKey? = null,
|
||||
|
||||
val leftGroupAt: Instant? = null,
|
||||
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val updatedAt: Instant = createdAt,
|
||||
override val savedAt: Instant = createdAt,
|
||||
|
||||
@@ -3,6 +3,7 @@ package at.torch.compose.database.repository
|
||||
import at.torch.compose.database.GENESIS_AT
|
||||
import at.torch.compose.database.TorchDatabase
|
||||
import at.torch.compose.database.model.ChatMessage
|
||||
import at.torch.compose.database.model.ChatRoom
|
||||
import at.torch.compose.database.model.GiftWrapPayload
|
||||
import at.torch.compose.database.model.MarmotInnerEvent
|
||||
import at.torch.compose.database.model.MarmotKeyPackage
|
||||
@@ -14,7 +15,6 @@ import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.marmot.mip02Welcome.WelcomeEvent
|
||||
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroup
|
||||
import com.vitorpamplona.quartz.marmot.mls.group.MlsGroupState
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Event
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.crypto.EventHasher
|
||||
@@ -69,6 +69,7 @@ class DatabaseChatRepository(
|
||||
activeUserPublicKey: HexKey,
|
||||
relayHint: String?,
|
||||
defaultSubject: String?,
|
||||
description: String?,
|
||||
mlsGroupState: String?
|
||||
): LocalChatRoom? = try {
|
||||
return database.nostrNip17Dao().getOrCreateChatRoom(
|
||||
@@ -76,6 +77,7 @@ class DatabaseChatRepository(
|
||||
activeUserPublicKey = activeUserPublicKey,
|
||||
relayHint = relayHint,
|
||||
defaultSubject = defaultSubject,
|
||||
description = description,
|
||||
mlsGroupState = mlsGroupState
|
||||
)
|
||||
} catch (e: Throwable) {
|
||||
@@ -120,11 +122,15 @@ class DatabaseChatRepository(
|
||||
}
|
||||
|
||||
override suspend fun createMlsDirectMessage(
|
||||
name: String?,
|
||||
description: String?,
|
||||
userPublicKey: HexKey,
|
||||
peerPublicKey: HexKey,
|
||||
peerKeyPackage: MarmotKeyPackage
|
||||
): String {
|
||||
return database.marmotOutboundDao().createMlsDirectMessageChatRoom(
|
||||
name = name,
|
||||
description = description,
|
||||
userPublicKey = userPublicKey,
|
||||
peerPublicKey = peerPublicKey,
|
||||
peerKeyPackage = peerKeyPackage
|
||||
@@ -155,6 +161,7 @@ class DatabaseChatRepository(
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (mlsGroup != null) {
|
||||
// Create ChatRumor...
|
||||
val createdAt = Clock.System.now().epochSeconds
|
||||
@@ -345,6 +352,112 @@ 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()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
if (mlsGroup != null) {
|
||||
// Create ChatRumor...
|
||||
val createdAt = Clock.System.now().epochSeconds
|
||||
val marmotInnerEventKind = ChatEvent.KIND
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun softDeleteChatRoom(chatRoom: ChatRoom) {
|
||||
database.chatRoomDao().upsert(
|
||||
chatRoom.copy(
|
||||
deletedAt = Clock.System.now()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun deleteChatRoom(chatRoom: ChatRoom) {
|
||||
database.chatRoomDao().delete(chatRoom)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "DatabaseChatRepository"
|
||||
}
|
||||
|
||||
@@ -7,13 +7,11 @@ 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 at.torch.compose.database.model.intermdiate.LocalProfile
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip17Dm.messages.ChatMessageEvent
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import fr.acinq.secp256k1.Hex
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
@@ -35,6 +33,7 @@ interface ChatRepository {
|
||||
activeUserPublicKey: HexKey,
|
||||
relayHint: String?,
|
||||
defaultSubject: String?,
|
||||
description: String?,
|
||||
mlsGroupState: String?
|
||||
): LocalChatRoom?
|
||||
|
||||
@@ -45,6 +44,8 @@ interface ChatRepository {
|
||||
suspend fun getProfileWithPublicKey(publicKey: HexKey): Profile?
|
||||
|
||||
suspend fun createMlsDirectMessage(
|
||||
name: String? = null,
|
||||
description: String? = null,
|
||||
userPublicKey: HexKey,
|
||||
peerPublicKey: HexKey,
|
||||
peerKeyPackage: MarmotKeyPackage
|
||||
@@ -68,6 +69,12 @@ interface ChatRepository {
|
||||
|
||||
suspend fun sealGiftWrapPayload(giftWrapPayload: GiftWrapPayload, nostrSignerSync: NostrSignerSync)
|
||||
|
||||
suspend fun leaveChatRoom(localChatRoom: LocalChatRoom)
|
||||
|
||||
suspend fun softDeleteChatRoom(chatRoom: ChatRoom)
|
||||
|
||||
suspend fun deleteChatRoom(chatRoom: ChatRoom)
|
||||
|
||||
companion object {
|
||||
val NO_OP_CHAT_REPOSITORY = object: ChatRepository {
|
||||
override suspend fun observeChatRoomListByPublicKey(publicKey: String): Flow<List<LocalChatRoom>> {
|
||||
@@ -100,6 +107,7 @@ interface ChatRepository {
|
||||
activeUserPublicKey: HexKey,
|
||||
relayHint: String?,
|
||||
defaultSubject: String?,
|
||||
description: String?,
|
||||
mlsGroupState: String?
|
||||
): LocalChatRoom? {
|
||||
return null
|
||||
@@ -118,6 +126,8 @@ interface ChatRepository {
|
||||
}
|
||||
|
||||
override suspend fun createMlsDirectMessage(
|
||||
name: String?,
|
||||
description: String?,
|
||||
userPublicKey: HexKey,
|
||||
peerPublicKey: HexKey,
|
||||
peerKeyPackage: MarmotKeyPackage
|
||||
@@ -155,6 +165,18 @@ interface ChatRepository {
|
||||
) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun leaveChatRoom(localChatRoom: LocalChatRoom) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun softDeleteChatRoom(chatRoom: ChatRoom) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override suspend fun deleteChatRoom(chatRoom: ChatRoom) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,6 +219,7 @@ private fun ChatRoomDetailScreenPreview() {
|
||||
id = "hex",
|
||||
userPublicKey = "hex",
|
||||
subject = "Room Title",
|
||||
description = "See something... say something.",
|
||||
initialGiftWrapPayloadId = "sdfaer",
|
||||
mlsGroupState = null
|
||||
),
|
||||
|
||||
@@ -206,12 +206,12 @@ fun ChatRoomDetailScreen(
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.secondary
|
||||
),
|
||||
enabled = true, // Disable after we leave group...
|
||||
enabled = chatRoomDetailUIState.localChatRoom.chatRoom.leftGroupAt == null, // Disable after we leave group...
|
||||
onClick = {
|
||||
onNavigateToRoute.invoke(
|
||||
ImplementationPendingRoute(
|
||||
"Leave Group"
|
||||
)
|
||||
// Leave group logic
|
||||
chatRoomDetailViewModel.leaveGroup(
|
||||
localChatRoom = chatRoomDetailUIState.localChatRoom,
|
||||
onNavigateToRoute = onNavigateToRoute
|
||||
)
|
||||
}
|
||||
) {
|
||||
@@ -233,7 +233,7 @@ fun ChatRoomDetailScreen(
|
||||
colors = ButtonDefaults.textButtonColors(
|
||||
contentColor = MaterialTheme.colorScheme.error
|
||||
),
|
||||
enabled = true, // TODO: Only enabled after we leave group...
|
||||
enabled = chatRoomDetailUIState.localChatRoom.chatRoom.leftGroupAt != null,
|
||||
onClick = {
|
||||
onNavigateToRoute.invoke(
|
||||
ImplementationPendingRoute(
|
||||
@@ -316,7 +316,8 @@ private fun ChatRoomMessagingScreenPreview() {
|
||||
chatRoom = ChatRoom(
|
||||
id = "",
|
||||
userPublicKey = "",
|
||||
subject = "Message title",
|
||||
subject = "Message Title",
|
||||
description = "Description",
|
||||
initialGiftWrapPayloadId = "sdfaer",
|
||||
mlsGroupState = null
|
||||
),
|
||||
|
||||
@@ -340,6 +340,7 @@ private fun ChatRoomMessagingScreenPreview() {
|
||||
id = "",
|
||||
userPublicKey = "",
|
||||
subject = "Message title",
|
||||
description = "See something. Say somethin",
|
||||
initialGiftWrapPayloadId = "sdfaer",
|
||||
mlsGroupState = null
|
||||
),
|
||||
|
||||
@@ -227,6 +227,7 @@ private fun ChatRoomDetailScreenPreview() {
|
||||
id = "",
|
||||
userPublicKey = "",
|
||||
subject = "Message title",
|
||||
description = "See something... say something.",
|
||||
initialGiftWrapPayloadId = "sdfaer",
|
||||
mlsGroupState = null
|
||||
),
|
||||
|
||||
@@ -84,6 +84,7 @@ class ChatRoomCreationViewModel(
|
||||
activeUserPublicKey = keyPair.pubKey.toHexKey(),
|
||||
relayHint = null,
|
||||
defaultSubject = name.toString(),
|
||||
description = description.toString(),
|
||||
mlsGroupState = group.saveState().encodeTls().toHex()
|
||||
)
|
||||
|
||||
|
||||
@@ -9,8 +9,12 @@ 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.Route
|
||||
import at.torch.compose.ui.view.state.ChatRoomDetailUIState
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
@@ -53,6 +57,22 @@ class ChatRoomDetailViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun leaveGroup(
|
||||
localChatRoom: LocalChatRoom,
|
||||
onNavigateToRoute: (Route) -> Unit
|
||||
) {
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
chatRepository.leaveChatRoom(localChatRoom)
|
||||
|
||||
viewModelScope.launch(Dispatchers.Main) {
|
||||
onNavigateToRoute.invoke(
|
||||
ImplementationPendingRoute(
|
||||
"Leave Group"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
companion object {
|
||||
private const val TAG = "ChatRoomDetailViewModel"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user