From caab21dde687d9a6ebb073637f83e179accf79ea Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 27 Apr 2026 01:13:13 +0000 Subject: [PATCH] feat(audio-rooms): in-feed Join button closes deep-link loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A `nostr:naddr1...` for a kind-30312 routes through MainActivity's URI handler to Route.Note(addressTag) and lands on the RenderMeetingSpaceEvent renderer in the note view. Until now that renderer only showed the room name + summary + participant list — there was no way to actually enter the room without going through the live-activity ChannelView lobby (which is for kind-30311). Extract the Join button from AudioRoomJoinCard into a standalone JoinAudioRoomButton composable, then drop it into the note-view renderer. Hidden when status=CLOSED (you can't join a finished room) or when the event lacks service/endpoint/d-tag. Single helper means the lobby card and the in-feed button share the same launch logic; future surfaces (room list, search results) can add the same button trivially. --- .../amethyst/ui/note/types/MeetingSpace.kt | 19 +++++++ .../audiorooms/room/AudioRoomJoinCard.kt | 55 ++++++++++++++----- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/MeetingSpace.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/MeetingSpace.kt index f678598bd7..eae46e8aa7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/MeetingSpace.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/MeetingSpace.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.ui.note.types import androidx.compose.foundation.background import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -158,6 +159,24 @@ fun RenderMeetingSpaceEventInner( } RenderParticipants(participants, accountViewModel, nav) + + // In-feed Join button — closes the deep-link loop. A + // `nostr:naddr1...` for a kind-30312 lands here through + // MainActivity's URI handler → Route.Note(addressTag) → this + // renderer; without a button the user has no path into the + // audio room. JoinAudioRoomButton hides itself when the + // event lacks service / endpoint / d-tag. + if (status != MeetingSpaceStatusTag.STATUS.CLOSED) { + Row( + modifier = Modifier.fillMaxWidth().padding(top = 8.dp), + horizontalArrangement = Arrangement.End, + ) { + com.vitorpamplona.amethyst.ui.screen.loggedIn.audiorooms.room.JoinAudioRoomButton( + event = noteEvent, + accountViewModel = accountViewModel, + ) + } + } } @Composable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/audiorooms/room/AudioRoomJoinCard.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/audiorooms/room/AudioRoomJoinCard.kt index 9ea42ac8ad..f84fc64c74 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/audiorooms/room/AudioRoomJoinCard.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/audiorooms/room/AudioRoomJoinCard.kt @@ -107,21 +107,48 @@ private fun AudioRoomJoinCardContent( horizontalArrangement = Arrangement.End, verticalAlignment = Alignment.CenterVertically, ) { - Button(onClick = { - AudioRoomBridge.set(accountViewModel) - AudioRoomActivity.launch( - context = context, - addressValue = addressValue, - authBaseUrl = serviceBase, - endpoint = endpoint, - hostPubkey = hostPubkey, - roomId = roomId, - kind = kind, - ) - }) { - Text(stringRes(R.string.audio_room_join)) - } + JoinAudioRoomButton(event = event, accountViewModel = accountViewModel) } } } } + +/** + * Standalone "Join audio room" button. Reusable from any composable + * that has a [MeetingSpaceEvent] in hand — the lobby card, the + * in-feed note renderer (so a `nostr:naddr1...` deep-link to a + * kind-30312 lands one tap away from the room), and any future + * room-list surface. Renders nothing for events without a + * service / endpoint / d-tag — those rooms can't be joined on + * the audio plane. + */ +@Composable +fun JoinAudioRoomButton( + event: MeetingSpaceEvent, + accountViewModel: AccountViewModel, +) { + val serviceBase = event.service() + val endpoint = event.endpoint() + val roomId = event.address().dTag + if (serviceBase.isNullOrBlank() || endpoint.isNullOrBlank() || roomId.isBlank()) return + + val context = LocalContext.current + val addressValue = remember(event) { event.address().toValue() } + val hostPubkey = event.pubKey + val kind = event.kind + + Button(onClick = { + AudioRoomBridge.set(accountViewModel) + AudioRoomActivity.launch( + context = context, + addressValue = addressValue, + authBaseUrl = serviceBase, + endpoint = endpoint, + hostPubkey = hostPubkey, + roomId = roomId, + kind = kind, + ) + }) { + Text(stringRes(R.string.audio_room_join)) + } +}