From c981058a9f8dbe4d1b524604beabc43a65c92187 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 1 May 2026 21:16:38 +0000 Subject: [PATCH] fix: render StageControlsBar inside the Stage card surface The previous commit placed the controls strip as a sibling row right after StageGrid. Visually it read as a separate band, not as part of the stage card. Adds a bottomBar slot to StageGrid that renders inside the same Surface, below the grid, sharing the card's tonal background and horizontal padding. StageControlsBar drops its own horizontal padding since the card supplies it, and keeps a top inset so it has breathing room from the avatars. --- .../nests/room/screen/NestActionBar.kt | 2 +- .../nests/room/screen/NestFullScreen.kt | 26 ++++----- .../nests/room/stage/ParticipantsGrid.kt | 54 ++++++++++--------- 3 files changed, 45 insertions(+), 37 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestActionBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestActionBar.kt index c723c2491..ce8bb9c9b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestActionBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestActionBar.kt @@ -154,7 +154,7 @@ internal fun StageControlsBar( modifier = modifier .fillMaxWidth() - .padding(horizontal = 16.dp, vertical = 8.dp), + .padding(top = 8.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(8.dp), ) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestFullScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestFullScreen.kt index 32fb19579..ed6f90ea2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestFullScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/screen/NestFullScreen.kt @@ -246,18 +246,20 @@ internal fun NestFullScreen( onTapSelf = onTapSelf, listenerCount = presences.size, modifier = Modifier.padding(horizontal = 16.dp), - ) - // Speaker controls live attached to the Stage card so they - // only render when the user is actually on stage. This keeps - // the action bar narrow for audience and makes the controls - // appearing/disappearing a deliberate signal that the mic - // is now (or no longer) available. - StageControlsBar( - viewModel = viewModel, - ui = ui, - isOnStage = isOnStageMe, - canBroadcast = viewModel.canBroadcast, - speakerPubkeyHex = myPubkey, + // Speaker controls live inside the Stage card so they + // only render when the user is actually on stage. This + // keeps the action bar narrow for audience and makes + // the controls appearing/disappearing a deliberate + // signal that the mic is now (or no longer) available. + bottomBar = { + StageControlsBar( + viewModel = viewModel, + ui = ui, + isOnStage = isOnStageMe, + canBroadcast = viewModel.canBroadcast, + speakerPubkeyHex = myPubkey, + ) + }, ) NestActionBar( viewModel = viewModel, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/ParticipantsGrid.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/ParticipantsGrid.kt index 01ecb4181..0e8efdefb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/ParticipantsGrid.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/ParticipantsGrid.kt @@ -126,6 +126,10 @@ private val STAGE_MAX_HEIGHT = 320.dp * * When [members] is empty, renders an "Waiting for speakers…" hint * instead so the strip stays visually anchored. + * + * [bottomBar] is rendered inside the same Surface, below the grid — + * used to anchor speaker controls to the stage card so they only + * appear when the local user is on stage. */ @Composable internal fun StageGrid( @@ -140,6 +144,7 @@ internal fun StageGrid( myPubkey: String? = null, onTapSelf: (() -> Unit)? = null, listenerCount: Int = 0, + bottomBar: (@Composable () -> Unit)? = null, ) { // Float currently-speaking members to the top so the listener can // see who they're hearing without scrolling. sortedBy is stable in @@ -193,32 +198,33 @@ internal fun StageGrid( } if (members.isEmpty()) { EmptyStageHint() - return@Column - } - LazyVerticalGrid( - columns = GridCells.Adaptive(STAGE_CELL_MIN), - modifier = Modifier.fillMaxWidth().heightIn(max = STAGE_MAX_HEIGHT), - horizontalArrangement = Arrangement.spacedBy(GRID_SPACING), - verticalArrangement = Arrangement.spacedBy(GRID_SPACING), - ) { - items(items = sortedMembers, key = { it.pubkey }) { member -> - val isSelf = myPubkey != null && member.pubkey == myPubkey - MemberCell( - member = member, - avatarSize = STAGE_AVATAR, - isSpeaking = member.pubkey in speakingNow, - audioLevel = audioLevels[member.pubkey] ?: 0f, - isConnecting = member.pubkey in connectingSpeakers, - showMicBadge = true, - reactions = reactionsByPubkey[member.pubkey].orEmpty(), - accountViewModel = accountViewModel, - onLongPressParticipant = onLongPressParticipant, - isSelf = isSelf, - onTapSelf = if (isSelf) onTapSelf else null, - modifier = Modifier.animateItem(), - ) + } else { + LazyVerticalGrid( + columns = GridCells.Adaptive(STAGE_CELL_MIN), + modifier = Modifier.fillMaxWidth().heightIn(max = STAGE_MAX_HEIGHT), + horizontalArrangement = Arrangement.spacedBy(GRID_SPACING), + verticalArrangement = Arrangement.spacedBy(GRID_SPACING), + ) { + items(items = sortedMembers, key = { it.pubkey }) { member -> + val isSelf = myPubkey != null && member.pubkey == myPubkey + MemberCell( + member = member, + avatarSize = STAGE_AVATAR, + isSpeaking = member.pubkey in speakingNow, + audioLevel = audioLevels[member.pubkey] ?: 0f, + isConnecting = member.pubkey in connectingSpeakers, + showMicBadge = true, + reactions = reactionsByPubkey[member.pubkey].orEmpty(), + accountViewModel = accountViewModel, + onLongPressParticipant = onLongPressParticipant, + isSelf = isSelf, + onTapSelf = if (isSelf) onTapSelf else null, + modifier = Modifier.animateItem(), + ) + } } } + bottomBar?.invoke() } } }