Working with ChatMessageRelayList
This commit is contained in:
@@ -18,5 +18,5 @@ interface BroadcastNostrEventReceiptDao {
|
||||
fun getBroadcastNostrEventReceiptByNostrEventId(nostrEventId: String): Flow<BroadcastNostrEventReceipt?>
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(broadcastNostrEventReceipt: BroadcastNostrEventReceipt)
|
||||
suspend fun upsert(broadcastNostrEventReceipt: BroadcastNostrEventReceipt): Long
|
||||
}
|
||||
@@ -1,11 +1,19 @@
|
||||
package ac.cord.auxiliary.compose.database.dao
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.ChatMessageBroadcastNostrEventRequestRelation
|
||||
import ac.cord.auxiliary.compose.database.model.NostrEvent
|
||||
import androidx.room3.Dao
|
||||
import androidx.room3.Query
|
||||
import androidx.room3.Transaction
|
||||
import androidx.room3.Upsert
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import kotlin.time.Instant
|
||||
|
||||
@Dao
|
||||
interface ChatMessageBroadcastNostrEventRequestRelationDao {
|
||||
@Query("SELECT * FROM ChatMessageBroadcastNostrEventRequestRelation WHERE broadcastNostrEventRequestId = :broadcastNostrEventRequestId")
|
||||
suspend fun getChatMessageBroadcastNostrEventRequestRelationByBroadcastRequest(broadcastNostrEventRequestId: Long): ChatMessageBroadcastNostrEventRequestRelation?
|
||||
|
||||
@Upsert
|
||||
suspend fun upsert(chatMessageBroadcastNostrEventRequestRelation: ChatMessageBroadcastNostrEventRequestRelation)
|
||||
|
||||
@@ -371,12 +371,13 @@ abstract class NostrDao(
|
||||
)
|
||||
|
||||
if (localChatRoom == null) {
|
||||
val userPublicKey = SeedManager.activeKeyPair().pubKey.toHex()
|
||||
database.chatRoomDao().upsert(
|
||||
ChatRoom(
|
||||
id = chatRoomId,
|
||||
userPublicKey = SeedManager.activeKeyPair().pubKey.toHex(),
|
||||
subject = null, // TODO: Get subject from tags...
|
||||
createdAt = giftWrapPayload.createdAt,
|
||||
userPublicKey = userPublicKey,
|
||||
subject = decryptedGiftWrapPayload.parseSubject(), // TODO: Get subject from tags...
|
||||
createdAt = decryptedGiftWrapPayload.createdAt,
|
||||
initialGiftWrapPayloadId = giftWrapPayload.id
|
||||
)
|
||||
)
|
||||
@@ -425,6 +426,71 @@ abstract class NostrDao(
|
||||
database.participantDao().upsert(
|
||||
participants
|
||||
)
|
||||
|
||||
participants.filter { it.participantPublicKey != userPublicKey }.forEach { participant ->
|
||||
val publicKey = participant.participantPublicKey
|
||||
|
||||
val chatMessageRelayListEvent = database.nostrEventDao().getAuthoredNostrEvents(
|
||||
kinds = arrayOf(
|
||||
ChatMessageRelayListEvent.KIND
|
||||
),
|
||||
authors = arrayOf(
|
||||
publicKey
|
||||
),
|
||||
since = GENESIS_AT,
|
||||
limit = 5
|
||||
).firstOrNull()?.let { nostrEvent ->
|
||||
if (nostrEvent.kind != ChatMessageRelayListEvent.KIND) {
|
||||
logger.e("getAuthoredNostrEvents returned invalid ChatMessageRelayListEvent for ${publicKey}: $nostrEvent")
|
||||
null
|
||||
} else {
|
||||
ChatMessageRelayListEvent(
|
||||
id = nostrEvent.id,
|
||||
tags = nostrEvent.tags,
|
||||
pubKey = nostrEvent.pubKey,
|
||||
content = nostrEvent.content,
|
||||
createdAt = nostrEvent.createdAt.epochSeconds,
|
||||
sig = nostrEvent.sig
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (chatMessageRelayListEvent != null) {
|
||||
// Sync messages from this relay...
|
||||
val synchronizationFilter = SynchronizationFilter(
|
||||
kinds = arrayOf(
|
||||
GiftWrapEvent.KIND,
|
||||
),
|
||||
tags = mapOf(
|
||||
Pair("p", listOf(participant.participantPublicKey))
|
||||
),
|
||||
limit = 50
|
||||
)
|
||||
database.negentropySynchronizeRequestDao().insert(
|
||||
chatMessageRelayListEvent.relays().map { normalizedRelayUrl ->
|
||||
NegentropySynchronizeRequest(
|
||||
id = NegentropySynchronizeRequest.computeId(
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
synchronizationFilter = synchronizationFilter
|
||||
),
|
||||
purpose = "sent-messages",
|
||||
synchronizationFilter = synchronizationFilter,
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
level = 0
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
logger.w("We don't have a chatMessageRelayListEvent for the pubkey $publicKey")
|
||||
|
||||
// Sync ChatMessageRelayListEvent publicKey...
|
||||
// TODO: Get relayHint form participant...
|
||||
if (profilePublicKeysToSync.containsKey(relayURL)) {
|
||||
profilePublicKeysToSync[relayURL] = mutableSetOf()
|
||||
}
|
||||
profilePublicKeysToSync[relayURL]?.add(participant.participantPublicKey)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
giftWrapPayload.parseSubject()?.let { subject ->
|
||||
database.chatRoomDao().upsert(
|
||||
|
||||
@@ -44,6 +44,8 @@ data class Participant(
|
||||
val participantPublicKey: HexKey,
|
||||
val chatRoomId: String,
|
||||
|
||||
// TODO: Add relayHint...
|
||||
|
||||
override val createdAt: Instant = Clock.System.now(),
|
||||
override val updatedAt: Instant = Clock.System.now(),
|
||||
override val savedAt: Instant = Clock.System.now(),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ac.cord.auxiliary.compose.database.repository
|
||||
|
||||
import ac.cord.auxiliary.compose.database.AuxDatabase
|
||||
import ac.cord.auxiliary.compose.database.GENESIS_AT
|
||||
import ac.cord.auxiliary.compose.database.model.ChatMessage
|
||||
import ac.cord.auxiliary.compose.database.model.ChatMessageNostrEventRelation
|
||||
import ac.cord.auxiliary.compose.database.model.ChatRoom
|
||||
@@ -15,6 +16,7 @@ import com.vitorpamplona.quartz.nip01Core.core.Kind
|
||||
import com.vitorpamplona.quartz.nip01Core.signers.NostrSignerSync
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.PTag
|
||||
import com.vitorpamplona.quartz.nip01Core.tags.people.taggedUsers
|
||||
import com.vitorpamplona.quartz.nip17Dm.settings.ChatMessageRelayListEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.seals.SealedRumorEvent
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||
@@ -54,6 +56,34 @@ class DatabaseChatRepository(
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun getChatMessageRelayForPublicKey(publicKey: HexKey): ChatMessageRelayListEvent? {
|
||||
// TODO: Implement a UserChatMessageRelayList model
|
||||
return database.nostrEventDao().getAuthoredNostrEvents(
|
||||
kinds = arrayOf(
|
||||
ChatMessageRelayListEvent.KIND
|
||||
),
|
||||
authors = arrayOf(
|
||||
publicKey
|
||||
),
|
||||
since = GENESIS_AT,
|
||||
limit = 5
|
||||
).firstOrNull()?.let { nostrEvent ->
|
||||
if (nostrEvent.kind != ChatMessageRelayListEvent.KIND) {
|
||||
logger.e("getAuthoredNostrEvents returned invalid ChatMessageRelayListEvent for ${publicKey}: $nostrEvent")
|
||||
null
|
||||
} else {
|
||||
ChatMessageRelayListEvent(
|
||||
id = nostrEvent.id,
|
||||
tags = nostrEvent.tags,
|
||||
pubKey = nostrEvent.pubKey,
|
||||
content = nostrEvent.content,
|
||||
createdAt = nostrEvent.createdAt.epochSeconds,
|
||||
sig = nostrEvent.sig
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun sendChatMessage(
|
||||
text: String,
|
||||
localChatRoom: LocalChatRoom,
|
||||
|
||||
@@ -4,6 +4,7 @@ import ac.cord.auxiliary.compose.database.AuxDatabase
|
||||
import ac.cord.auxiliary.compose.database.GENESIS_AT
|
||||
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventReceipt
|
||||
import ac.cord.auxiliary.compose.database.model.BroadcastNostrEventRequest
|
||||
import ac.cord.auxiliary.compose.database.model.ChatMessageBroadcastNostrEventReceiptRelation
|
||||
import ac.cord.auxiliary.compose.database.model.Connection
|
||||
import ac.cord.auxiliary.compose.database.model.NegentropySynchronizeRequest
|
||||
import ac.cord.auxiliary.compose.database.model.NostrEvent
|
||||
@@ -406,6 +407,29 @@ class DatabaseNostrRepository(
|
||||
status = status
|
||||
)
|
||||
)
|
||||
|
||||
if (status == "published") {
|
||||
val broadcastNostrEventReceiptId = database.broadcastNostrEventReceiptDao().upsert(
|
||||
BroadcastNostrEventReceipt(
|
||||
nostrEventId = broadcastNostrEventRequest.nostrEventId,
|
||||
unsignedNostrEventId = broadcastNostrEventRequest.unsignedNostrEventId,
|
||||
isAccepted = true,
|
||||
relayURL = broadcastNostrEventRequest.relayURL
|
||||
)
|
||||
)
|
||||
|
||||
database.chatMessageBroadcastNostrEventRequestRelationDao().getChatMessageBroadcastNostrEventRequestRelationByBroadcastRequest(
|
||||
broadcastNostrEventRequestId = broadcastNostrEventRequest.id
|
||||
)?.let { chatMessageBroadcastNostrEventRequestRelation ->
|
||||
// This is actually a chatMessage related broadcast event...
|
||||
database.chatMessageBroadcastNostrEventReceiptRelationDao().upsert(
|
||||
ChatMessageBroadcastNostrEventReceiptRelation(
|
||||
chatMessageId = chatMessageBroadcastNostrEventRequestRelation.chatMessageId,
|
||||
broadcastNostrEventReceiptId = broadcastNostrEventReceiptId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun synchronizeNostrEventRequestProcessed(synchronizeNostrEventRequest: SynchronizeNostrEventRequest) {
|
||||
|
||||
@@ -9,6 +9,7 @@ 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 kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
@@ -23,6 +24,8 @@ interface ChatRepository {
|
||||
|
||||
suspend fun getOrCreateChatRoom(publicKey: String, defaultSubject: String? = null): LocalChatRoom?
|
||||
|
||||
suspend fun getChatMessageRelayForPublicKey(publicKey: HexKey): ChatMessageRelayListEvent?
|
||||
|
||||
suspend fun sendChatMessage(
|
||||
text: String,
|
||||
localChatRoom: LocalChatRoom,
|
||||
@@ -58,6 +61,10 @@ interface ChatRepository {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun getChatMessageRelayForPublicKey(publicKey: HexKey): ChatMessageRelayListEvent? {
|
||||
return null
|
||||
}
|
||||
|
||||
override suspend fun sendChatMessage(
|
||||
text: String,
|
||||
localChatRoom: LocalChatRoom,
|
||||
|
||||
@@ -118,6 +118,7 @@ fun AuxNavHost(
|
||||
val synchronizationViewModel: SynchronizationViewModel = viewModel(
|
||||
factory = SynchronizationViewModel.factory(
|
||||
nostrRepository = databaseNostrRepository,
|
||||
chatRepository = databaseChatRepository,
|
||||
relayRepository = databaseNostrRepository,
|
||||
scope = applicationIOScope
|
||||
)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package ac.cord.auxiliary.compose.ui.view.model
|
||||
|
||||
import ac.cord.auxiliary.compose.database.model.NegentropySynchronizeRequest
|
||||
import ac.cord.auxiliary.compose.database.model.intermdiate.LocalChatRoom
|
||||
import ac.cord.auxiliary.compose.database.model.types.SynchronizationFilter
|
||||
import ac.cord.auxiliary.compose.extensions.toFormattedTimeAndDateString
|
||||
import ac.cord.auxiliary.compose.repository.ChatRepository
|
||||
import ac.cord.auxiliary.compose.repository.NostrRepository
|
||||
@@ -24,9 +26,7 @@ import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccessTime
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.KeyOff
|
||||
import androidx.compose.material.icons.filled.LockClock
|
||||
import androidx.compose.material.icons.filled.Pending
|
||||
import androidx.compose.material.icons.filled.PendingActions
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -34,6 +34,7 @@ import androidx.compose.material3.LoadingIndicator
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
@@ -48,6 +49,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.initializer
|
||||
import androidx.lifecycle.viewmodel.viewModelFactory
|
||||
import co.touchlab.kermit.Logger
|
||||
import com.vitorpamplona.quartz.nip59Giftwrap.wraps.GiftWrapEvent
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.IO
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
@@ -63,6 +65,8 @@ class ChatRoomDetailMessageListViewModel(
|
||||
var chatRoomDetailMessageListUIState: ChatRoomDetailMessageListUIState by mutableStateOf(initialChatRoomDetailMessageListUIState)
|
||||
private set
|
||||
|
||||
val isReceiverChatMessageRelayListMissing: MutableState<Boolean> = mutableStateOf(false)
|
||||
|
||||
fun initiate() {
|
||||
logger.d("init")
|
||||
scheduleSynchronization()
|
||||
@@ -85,23 +89,43 @@ class ChatRoomDetailMessageListViewModel(
|
||||
|
||||
fun scheduleSynchronization() {
|
||||
logger.d("scheduleSynchronization")
|
||||
// viewModelScope.launch(Dispatchers.IO) {
|
||||
// // Sync Notifications... might want to also run this in the background
|
||||
// nostrRepository.queueNegentropySynchronizeRequest(
|
||||
// Relays.DefaultDMRelayList.shuffled().map { normalizedRelayUrl ->
|
||||
// NegentropySynchronizeRequest(
|
||||
// id = NegentropySynchronizeRequest.computeId(
|
||||
// relayURL = normalizedRelayUrl.url,
|
||||
// synchronizationFilter = synchronizationFilter
|
||||
// ),
|
||||
// purpose = "chat",
|
||||
// synchronizationFilter = synchronizationFilter,
|
||||
// relayURL = normalizedRelayUrl.url,
|
||||
// level = 0
|
||||
// )
|
||||
// }
|
||||
// )
|
||||
// }
|
||||
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
// Sync Notifications... might want to also run this in the background
|
||||
localChatRoom.participants.filter { it.participant.participantPublicKey != localChatRoom.chatRoom.userPublicKey }.forEach { recipients ->
|
||||
|
||||
val chatMessageRelayListEvent = chatRepository.getChatMessageRelayForPublicKey(recipients.participant.participantPublicKey)
|
||||
|
||||
if (chatMessageRelayListEvent != null) {
|
||||
// Sync messages from this relay...
|
||||
val synchronizationFilter = SynchronizationFilter(
|
||||
kinds = arrayOf(
|
||||
GiftWrapEvent.KIND,
|
||||
),
|
||||
tags = mapOf(
|
||||
Pair("p", listOf(recipients.participant.participantPublicKey))
|
||||
),
|
||||
limit = 50
|
||||
)
|
||||
nostrRepository.queueNegentropySynchronizeRequest(
|
||||
chatMessageRelayListEvent.relays().map { normalizedRelayUrl ->
|
||||
NegentropySynchronizeRequest(
|
||||
id = NegentropySynchronizeRequest.computeId(
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
synchronizationFilter = synchronizationFilter
|
||||
),
|
||||
purpose = "sent-messages",
|
||||
synchronizationFilter = synchronizationFilter,
|
||||
relayURL = normalizedRelayUrl.url,
|
||||
level = 0
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
isReceiverChatMessageRelayListMissing.value = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@@ -218,7 +242,6 @@ class ChatRoomDetailMessageListViewModel(
|
||||
style = MaterialTheme.typography.labelSmall
|
||||
)
|
||||
if (localChatMessage.chatMessage.isUserMessage) {
|
||||
// TODO: Add delivered and read receipt..
|
||||
if (localChatMessage.chatMessageBroadcastNostrEventReceiptRelation != null) {
|
||||
Icon(
|
||||
Icons.Default.Check,
|
||||
|
||||
@@ -34,13 +34,7 @@ class ChatRoomDetailViewModel(
|
||||
|
||||
val isActionPending: MutableState<Boolean> = mutableStateOf(false)
|
||||
|
||||
fun queueSynchronization() {
|
||||
logger.d { "queueSynchronization" }
|
||||
}
|
||||
|
||||
// TODO: Render MessageList...
|
||||
fun initiateChatRoomDetail() {
|
||||
// TODO: GetOrCreateChatRoom
|
||||
if (Crypto.isPubKeyCompressed(directMessageId.hexToByteArray())) {
|
||||
logger.d("compressed: $directMessageId")
|
||||
// Get ChatRoomById
|
||||
@@ -71,7 +65,6 @@ class ChatRoomDetailViewModel(
|
||||
}
|
||||
}
|
||||
}
|
||||
queueSynchronization()
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ import ac.cord.auxiliary.compose.network.relays.RelaysSocketManager
|
||||
import ac.cord.auxiliary.compose.network.sockets.NostrIncomingMessage
|
||||
import ac.cord.auxiliary.compose.network.sockets.NostrSocketClientFactory
|
||||
import ac.cord.auxiliary.compose.repository.CachingImportRepository
|
||||
import ac.cord.auxiliary.compose.repository.ChatRepository
|
||||
import ac.cord.auxiliary.compose.repository.NostrRepository
|
||||
import ac.cord.auxiliary.compose.repository.RelayRepository
|
||||
import androidx.lifecycle.ViewModel
|
||||
@@ -37,6 +38,7 @@ import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
class SynchronizationViewModel(
|
||||
val nostrRepository: NostrRepository,
|
||||
val chatRepository: ChatRepository,
|
||||
val relayRepository: RelayRepository,
|
||||
val scope: CoroutineScope,
|
||||
): ViewModel() {
|
||||
@@ -54,12 +56,14 @@ class SynchronizationViewModel(
|
||||
|
||||
fun factory(
|
||||
nostrRepository: NostrRepository,
|
||||
chatRepository: ChatRepository,
|
||||
relayRepository: RelayRepository,
|
||||
scope: CoroutineScope,
|
||||
): ViewModelProvider.Factory = viewModelFactory {
|
||||
initializer {
|
||||
SynchronizationViewModel(
|
||||
nostrRepository = nostrRepository,
|
||||
chatRepository = chatRepository,
|
||||
relayRepository = relayRepository,
|
||||
scope = scope
|
||||
)
|
||||
@@ -372,7 +376,7 @@ class SynchronizationViewModel(
|
||||
logger.d("Nostr Publish Result: $nostrPublishResult")
|
||||
nostrRepository.broadcastProcessed(
|
||||
localBroadcastNostrEventRequest.broadcastNostrEventRequest,
|
||||
"published"
|
||||
"published" // TODO: Might want to pass the nostrPublishResult.result
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user