From bc6778c8019a42ce93eafffe460b68b568f2db5b Mon Sep 17 00:00:00 2001 From: Ren Amamiya Date: Thu, 4 Jun 2026 08:31:06 +0700 Subject: [PATCH] allow user self messaging --- .../su/reya/coop/NostrForegroundService.kt | 4 +-- .../kotlin/su/reya/coop/screens/ChatScreen.kt | 11 ++++++-- .../commonMain/kotlin/su/reya/coop/Nostr.kt | 17 ++++++------ .../kotlin/su/reya/coop/NostrViewModel.kt | 26 ++++++++++++------- 4 files changed, 36 insertions(+), 22 deletions(-) diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt index 03a45ee..a627578 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/NostrForegroundService.kt @@ -35,9 +35,7 @@ class NostrForegroundService : Service() { override fun onCreate() { super.onCreate() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - createNotificationChannel() - } + createNotificationChannel() val notification = createNotification() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { diff --git a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt index 8300e02..1da9ef6 100644 --- a/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt +++ b/composeApp/src/androidMain/kotlin/su/reya/coop/screens/ChatScreen.kt @@ -1,6 +1,7 @@ package su.reya.coop.screens import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.IntrinsicSize @@ -47,6 +48,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import coop.composeapp.generated.resources.Res import coop.composeapp.generated.resources.ic_arrow_back @@ -235,10 +237,15 @@ fun ChatScreen(id: Long) { .fillMaxWidth(), contentAlignment = Alignment.Center ) { - Column(horizontalAlignment = Alignment.CenterHorizontally) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { Text( text = "No messages yet", - style = MaterialTheme.typography.titleLargeEmphasized, + style = MaterialTheme.typography.titleLargeEmphasized.copy( + fontWeight = FontWeight.SemiBold + ), color = MaterialTheme.colorScheme.onSurface ) Text( diff --git a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt index bbf83f7..a89c0c9 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/Nostr.kt @@ -55,6 +55,7 @@ import rust.nostr.sdk.giftWrapAsync import rust.nostr.sdk.initLogger import rust.nostr.sdk.nip17ExtractRelayList import kotlin.time.Duration +import kotlin.time.Duration.Companion.milliseconds object NostrManager { val instance = Nostr() @@ -228,7 +229,7 @@ class Nostr { client?.subscribe( target = ReqTarget.manual(target), - id = "all-gift-wraps" + id = "gift-wraps" ) } catch (e: Exception) { throw IllegalStateException("Failed to fetch user messages: ${e.message}", e) @@ -293,7 +294,7 @@ class Nostr { eoseTrackerJob?.cancel() // Start a new tracker eoseTrackerJob = launch { - delay(10000) // Wait for 10 seconds + delay(10000.milliseconds) // Wait for 10 seconds onSubscriptionClose() } @@ -312,7 +313,7 @@ class Nostr { is RelayMessageEnum.EndOfStoredEvents -> { val subscriptionId = message.subscriptionId - if (subscriptionId == "all-gift-wraps" || subscriptionId == "newest-gift-wraps") { + if (subscriptionId == "gift-wraps") { onSubscriptionClose() } } @@ -366,7 +367,7 @@ class Nostr { Tag.identifier(giftId.toHex()), Tag.event(rumor.id()!!), Tag.reference(roomId.toString()), - Tag.custom(TagKind.Unknown("k"), listOf("dm")) + Tag.custom(TagKind.Unknown("k"), listOf("14")) ) // Set event kind @@ -395,7 +396,6 @@ class Nostr { // Try to unwrap the gift with each signer for (signer in signers) { try { - // TODO: custom unwrapping logic val gift = UnwrappedGift.fromGiftWrapAsync(signer = signer, giftWrap = event) val rumor = gift.rumor() // Save the rumor to the database @@ -644,6 +644,8 @@ class Nostr { return events ?.toVec() ?.map { UnsignedEvent.fromJson(it.content()) } + // Filter out events without public keys (receivers) + ?.filter { it.tags().publicKeys().isNotEmpty() } ?.sortedByDescending { it.createdAt().asSecs() } ?: emptyList() } catch (e: Exception) { throw IllegalStateException("Failed to get chat room messages: ${e.message}", e) @@ -723,9 +725,7 @@ class Nostr { // Add public key tags for each recipient to.forEach { pubkey -> - if (pubkey != currentUser) { - tags.add(Tag.publicKey(pubkey)) - } + tags.add(Tag.publicKey(pubkey)) } for (receiver in listOf(currentUser) + to) { @@ -734,6 +734,7 @@ class Nostr { val rumor = EventBuilder .privateMsgRumor(receiver = receiver, message = content) .tags(tags) + .allowSelfTagging() .build(currentUser) // Ensure the event ID is set .ensureId() diff --git a/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt b/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt index 62007e2..f3dfebb 100644 --- a/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt +++ b/shared/src/commonMain/kotlin/su/reya/coop/NostrViewModel.kt @@ -248,7 +248,7 @@ class NostrViewModel( // Get chat rooms val rooms = nostr.getChatRooms() ?: emptySet() if (rooms.isNotEmpty()) { - _chatRooms.value = rooms + mergeChatRooms(rooms) _isPartialProcessedGiftWrap.value = true } @@ -476,7 +476,7 @@ class NostrViewModel( // Update the chat rooms state _chatRooms.update { currentRooms -> - currentRooms + room + (currentRooms + room).sortedDescending().toSet() } return room.id @@ -490,21 +490,29 @@ class NostrViewModel( ?: throw IllegalArgumentException("Room not found") } + private fun mergeChatRooms(rooms: Set) { + _chatRooms.update { currentRooms -> + val merged = currentRooms.associateBy { it.id }.toMutableMap() + // Add or update rooms from the database + rooms.forEach { room -> + merged[room.id] = room + } + // Return as a sorted set to maintain UI consistency + merged.values.sortedDescending().toSet() + } + } + fun getChatRooms() { viewModelScope.launch { val rooms = nostr.getChatRooms() ?: emptySet() - _chatRooms.update { currentRooms -> - val virtualRooms = currentRooms.filter { local -> - rooms.none { db -> db.id == local.id } - } - rooms + virtualRooms - } + mergeChatRooms(rooms) } } suspend fun refreshChatRooms() { try { - _chatRooms.value = nostr.getChatRooms() ?: emptySet() + val rooms = nostr.getChatRooms() ?: emptySet() + mergeChatRooms(rooms) } catch (e: Exception) { showError("Error: ${e.message}") }