add check sent message

This commit is contained in:
2026-05-18 17:39:43 +07:00
parent 1c85e26e7f
commit 5903de7e82
4 changed files with 84 additions and 41 deletions

View File

@@ -1,5 +1,6 @@
package su.reya.coop.screens package su.reya.coop.screens
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.IntrinsicSize import androidx.compose.foundation.layout.IntrinsicSize
@@ -240,7 +241,17 @@ fun ChatMessage(
color = containerColor, color = containerColor,
contentColor = contentColor, contentColor = contentColor,
shape = bubbleShape, shape = bubbleShape,
modifier = Modifier.widthIn(max = 280.dp) modifier = Modifier
.widthIn(max = 280.dp)
.clickable(
onClick = {
val id = rumor.id()
if (id != null) {
val sent = viewModel.isMessageSent(id)
println("Sent: $sent")
}
}
)
) { ) {
Text( Text(
text = rumor.content(), text = rumor.content(),

View File

@@ -66,6 +66,10 @@ class Nostr {
private set private set
var msgRelayList: Map<PublicKey, List<RelayUrl>> = emptyMap() var msgRelayList: Map<PublicKey, List<RelayUrl>> = emptyMap()
private set private set
var sentEvents: MutableMap<EventId, List<RelayUrl>> = mutableMapOf()
private set
var rumorMap: MutableMap<EventId, EventId> = mutableMapOf()
private set
suspend fun init(dbPath: String) { suspend fun init(dbPath: String) {
try { try {
@@ -94,6 +98,7 @@ class Nostr {
.build() .build()
// Bootstrap relays // Bootstrap relays
client?.addRelay(RelayUrl.parse("wss://relay.damus.io"))
client?.addRelay(RelayUrl.parse("wss://relay.primal.net")) client?.addRelay(RelayUrl.parse("wss://relay.primal.net"))
client?.addRelay(RelayUrl.parse("wss://user.kindpag.es")) client?.addRelay(RelayUrl.parse("wss://user.kindpag.es"))
client?.addRelay(RelayUrl.parse("wss://purplepag.es")) client?.addRelay(RelayUrl.parse("wss://purplepag.es"))
@@ -334,6 +339,13 @@ class Nostr {
} }
} }
is RelayMessageEnum.Ok -> {
if (sentEvents.containsKey(message.eventId)) {
val currentRelays = sentEvents[message.eventId] ?: emptyList()
sentEvents[message.eventId] = currentRelays + relayUrl
}
}
else -> { else -> {
/* Ignore other message types */ /* Ignore other message types */
} }
@@ -495,7 +507,7 @@ class Nostr {
client?.sendEvent( client?.sendEvent(
event = metadataEvent, event = metadataEvent,
target = SendEventTarget.toNip65(), target = SendEventTarget.broadcast(),
ackPolicy = AckPolicy.none() ackPolicy = AckPolicy.none()
) )
@@ -515,7 +527,7 @@ class Nostr {
suspend fun fetchMetadataBatch(keys: List<PublicKey>) { suspend fun fetchMetadataBatch(keys: List<PublicKey>) {
try { try {
val limit = keys.size.toULong(); val limit = keys.size.toULong() * 4u;
val opts = SubscribeAutoCloseOptions().exitPolicy(ReqExitPolicy.ExitOnEose) val opts = SubscribeAutoCloseOptions().exitPolicy(ReqExitPolicy.ExitOnEose)
// Construct a filter for metadata events // Construct a filter for metadata events
@@ -528,8 +540,10 @@ class Nostr {
val target = val target =
ReqTarget.manual( ReqTarget.manual(
mapOf( mapOf(
RelayUrl.parse("wss://purplepag.es") to listOf(filter),
RelayUrl.parse("wss://user.kindpag.es") to listOf(filter), RelayUrl.parse("wss://user.kindpag.es") to listOf(filter),
RelayUrl.parse("wss://relay.primal.net") to listOf(filter) RelayUrl.parse("wss://relay.primal.net") to listOf(filter),
RelayUrl.parse("wss://relay.damus.io") to listOf(filter),
) )
) )
@@ -550,37 +564,31 @@ class Nostr {
val events = client?.database()?.query(filter) val events = client?.database()?.query(filter)
// Collect rooms // Collect rooms
val rooms: MutableSet<Room> = mutableSetOf() val roomsMap: MutableMap<Long, Room> = mutableMapOf()
events events
?.toVec() ?.toVec()
?.map { UnsignedEvent.fromJson(it.content()) } ?.map { UnsignedEvent.fromJson(it.content()) }
?.filter { it.tags().publicKeys().isNotEmpty() } ?.filter { it.tags().publicKeys().isNotEmpty() }
?.sortedByDescending { it.createdAt().asSecs() }
?.forEach { event -> ?.forEach { event ->
val room = Room.new(rumor = event, userPubkey = userPubkey) val newRoom = Room.new(rumor = event, userPubkey = userPubkey)
val existingRoom = roomsMap[newRoom.id]
// Check if the room already exists // Check if the room already exists
if (rooms.contains(room)) { if (existingRoom == null || newRoom.createdAt.asSecs() > existingRoom.createdAt.asSecs()) {
room.setCreatedAt(room.createdAt)
room.setLastMessage(room.lastMessage)
}
val filter = val filter =
Filter().kind(kind).author(userPubkey).pubkeys(room.members.toList()); Filter().kind(kind).author(userPubkey).pubkeys(newRoom.members.toList())
// Check if the user is interacting with the room's members // Determine if it's an ongoing room
val isOngoing = client?.database()?.query(filter)?.isEmpty() == false; val isOngoing = client?.database()?.query(filter)?.isEmpty() == false
// Set the room kind based on interaction status // Append room to map
if (isOngoing) { roomsMap[newRoom.id] =
room.setKind(RoomKind.Ongoing) if (isOngoing) newRoom.copy(kind = RoomKind.Ongoing) else newRoom
}
} }
rooms.add(room) return roomsMap.values.toSet()
}
return rooms
} catch (e: Exception) { } catch (e: Exception) {
println("Failed to get chat rooms: ${e.message}") println("Failed to get chat rooms: ${e.message}")
return null return null
@@ -625,7 +633,7 @@ class Nostr {
content: String, content: String,
subject: String? = null, subject: String? = null,
replies: List<EventId> = emptyList(), replies: List<EventId> = emptyList(),
onNewMessage: ((UnsignedEvent) -> Unit)? = null onRumorCreated: ((UnsignedEvent) -> Unit)? = null,
) { ) {
try { try {
val currentUser = val currentUser =
@@ -664,7 +672,7 @@ class Nostr {
// Emit the rumor to the chat screen // Emit the rumor to the chat screen
if (receiver == currentUser) { if (receiver == currentUser) {
onNewMessage?.invoke(rumor) onRumorCreated?.invoke(rumor)
} }
// Construct the gift wrap event // Construct the gift wrap event
@@ -678,12 +686,19 @@ class Nostr {
) )
// Send the event to receiver's NIP-17 relays // Send the event to receiver's NIP-17 relays
client?.sendEvent( val output = client?.sendEvent(
event = gift, event = gift,
target = SendEventTarget.toNip17(), target = SendEventTarget.toNip17(),
ackPolicy = AckPolicy.none(), ackPolicy = AckPolicy.none(),
authenticationTimeout = Duration.parse("2s") authenticationTimeout = Duration.parse("2s")
) )
if (output != null) {
sentEvents[output.id] = emptyList()
if (rumor.id() != null) {
rumorMap[rumor.id()!!] = output.id
}
}
} }
} catch (e: Exception) { } catch (e: Exception) {
throw IllegalStateException("Failed to send message: ${e.message}", e) throw IllegalStateException("Failed to send message: ${e.message}", e)

View File

@@ -24,6 +24,7 @@ import rust.nostr.sdk.Metadata
import rust.nostr.sdk.NostrConnect import rust.nostr.sdk.NostrConnect
import rust.nostr.sdk.NostrConnectUri import rust.nostr.sdk.NostrConnectUri
import rust.nostr.sdk.PublicKey import rust.nostr.sdk.PublicKey
import rust.nostr.sdk.RelayUrl
import rust.nostr.sdk.Tag import rust.nostr.sdk.Tag
import rust.nostr.sdk.UnsignedEvent import rust.nostr.sdk.UnsignedEvent
import su.reya.coop.blossom.BlossomClient import su.reya.coop.blossom.BlossomClient
@@ -50,6 +51,9 @@ class NostrViewModel(
private val _newEvents = MutableSharedFlow<UnsignedEvent>(extraBufferCapacity = 100) private val _newEvents = MutableSharedFlow<UnsignedEvent>(extraBufferCapacity = 100)
val newEvents = _newEvents.asSharedFlow() val newEvents = _newEvents.asSharedFlow()
private val _sentReports = MutableStateFlow<Map<EventId, List<RelayUrl>>>(emptyMap())
val sentReport = _sentReports.asSharedFlow()
private val _errorEvents = Channel<String>(Channel.BUFFERED) private val _errorEvents = Channel<String>(Channel.BUFFERED)
val errorEvents = _errorEvents.receiveAsFlow() val errorEvents = _errorEvents.receiveAsFlow()
@@ -104,6 +108,7 @@ class NostrViewModel(
if (batch.size >= 10 || (now - lastFlushTime) >= timeout || nextKey == null) { if (batch.size >= 10 || (now - lastFlushTime) >= timeout || nextKey == null) {
val keysToRequest = batch.toList() val keysToRequest = batch.toList()
batch.clear() batch.clear()
nostr.fetchMetadataBatch(keysToRequest) nostr.fetchMetadataBatch(keysToRequest)
} }
} }
@@ -366,11 +371,10 @@ class NostrViewModel(
content = message, content = message,
subject = room.subject, subject = room.subject,
replies = replies, replies = replies,
onNewMessage = { event -> onRumorCreated = { event ->
viewModelScope.launch { updateRoomList(roomId, event)
_newEvents.emit(event) viewModelScope.launch { _newEvents.emit(event) }
} },
}
) )
} catch (e: Exception) { } catch (e: Exception) {
showError("Error: ${e.message}") showError("Error: ${e.message}")
@@ -378,6 +382,27 @@ class NostrViewModel(
} }
} }
fun isMessageSent(id: EventId): Boolean {
val giftWrapId = nostr.rumorMap[id]
if (giftWrapId != null) {
val isSent = nostr.sentEvents[giftWrapId]?.isNotEmpty() ?: false
return isSent
} else {
return false
}
}
private fun updateRoomList(roomId: Long, newMessage: UnsignedEvent) {
_chatRooms.value = _chatRooms.value.map { room ->
if (room.id == roomId) {
room.copy(lastMessage = newMessage.content(), createdAt = newMessage.createdAt())
} else {
room
}
}.toSet()
}
suspend fun searchByAddress(query: String): PublicKey? { suspend fun searchByAddress(query: String): PublicKey? {
try { try {
return nostr.searchByAddress(query) return nostr.searchByAddress(query)

View File

@@ -29,14 +29,6 @@ data class Room(
val kind: RoomKind = RoomKind.default(), val kind: RoomKind = RoomKind.default(),
val lastMessage: String? = null val lastMessage: String? = null
) : Comparable<Room> { ) : Comparable<Room> {
override fun hashCode(): Int = id.hashCode()
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Room) return false
return id == other.id
}
override fun compareTo(other: Room): Int { override fun compareTo(other: Room): Int {
return this.createdAt.asSecs().compareTo(other.createdAt.asSecs()) return this.createdAt.asSecs().compareTo(other.createdAt.asSecs())
} }