feat(audio-rooms): in-feed Join button closes deep-link loop

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.
This commit is contained in:
Claude
2026-04-27 01:13:13 +00:00
parent 05e7e57c4b
commit caab21dde6
2 changed files with 60 additions and 14 deletions
@@ -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
@@ -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))
}
}