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.
This commit is contained in:
+1
-1
@@ -154,7 +154,7 @@ internal fun StageControlsBar(
|
|||||||
modifier =
|
modifier =
|
||||||
modifier
|
modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
.padding(top = 8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
|
|||||||
+14
-12
@@ -246,18 +246,20 @@ internal fun NestFullScreen(
|
|||||||
onTapSelf = onTapSelf,
|
onTapSelf = onTapSelf,
|
||||||
listenerCount = presences.size,
|
listenerCount = presences.size,
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
)
|
// Speaker controls live inside the Stage card so they
|
||||||
// Speaker controls live attached to the Stage card so they
|
// only render when the user is actually on stage. This
|
||||||
// only render when the user is actually on stage. This keeps
|
// keeps the action bar narrow for audience and makes
|
||||||
// the action bar narrow for audience and makes the controls
|
// the controls appearing/disappearing a deliberate
|
||||||
// appearing/disappearing a deliberate signal that the mic
|
// signal that the mic is now (or no longer) available.
|
||||||
// is now (or no longer) available.
|
bottomBar = {
|
||||||
StageControlsBar(
|
StageControlsBar(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
ui = ui,
|
ui = ui,
|
||||||
isOnStage = isOnStageMe,
|
isOnStage = isOnStageMe,
|
||||||
canBroadcast = viewModel.canBroadcast,
|
canBroadcast = viewModel.canBroadcast,
|
||||||
speakerPubkeyHex = myPubkey,
|
speakerPubkeyHex = myPubkey,
|
||||||
|
)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
NestActionBar(
|
NestActionBar(
|
||||||
viewModel = viewModel,
|
viewModel = viewModel,
|
||||||
|
|||||||
+30
-24
@@ -126,6 +126,10 @@ private val STAGE_MAX_HEIGHT = 320.dp
|
|||||||
*
|
*
|
||||||
* When [members] is empty, renders an "Waiting for speakers…" hint
|
* When [members] is empty, renders an "Waiting for speakers…" hint
|
||||||
* instead so the strip stays visually anchored.
|
* 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
|
@Composable
|
||||||
internal fun StageGrid(
|
internal fun StageGrid(
|
||||||
@@ -140,6 +144,7 @@ internal fun StageGrid(
|
|||||||
myPubkey: String? = null,
|
myPubkey: String? = null,
|
||||||
onTapSelf: (() -> Unit)? = null,
|
onTapSelf: (() -> Unit)? = null,
|
||||||
listenerCount: Int = 0,
|
listenerCount: Int = 0,
|
||||||
|
bottomBar: (@Composable () -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
// Float currently-speaking members to the top so the listener can
|
// Float currently-speaking members to the top so the listener can
|
||||||
// see who they're hearing without scrolling. sortedBy is stable in
|
// see who they're hearing without scrolling. sortedBy is stable in
|
||||||
@@ -193,32 +198,33 @@ internal fun StageGrid(
|
|||||||
}
|
}
|
||||||
if (members.isEmpty()) {
|
if (members.isEmpty()) {
|
||||||
EmptyStageHint()
|
EmptyStageHint()
|
||||||
return@Column
|
} else {
|
||||||
}
|
LazyVerticalGrid(
|
||||||
LazyVerticalGrid(
|
columns = GridCells.Adaptive(STAGE_CELL_MIN),
|
||||||
columns = GridCells.Adaptive(STAGE_CELL_MIN),
|
modifier = Modifier.fillMaxWidth().heightIn(max = STAGE_MAX_HEIGHT),
|
||||||
modifier = Modifier.fillMaxWidth().heightIn(max = STAGE_MAX_HEIGHT),
|
horizontalArrangement = Arrangement.spacedBy(GRID_SPACING),
|
||||||
horizontalArrangement = Arrangement.spacedBy(GRID_SPACING),
|
verticalArrangement = Arrangement.spacedBy(GRID_SPACING),
|
||||||
verticalArrangement = Arrangement.spacedBy(GRID_SPACING),
|
) {
|
||||||
) {
|
items(items = sortedMembers, key = { it.pubkey }) { member ->
|
||||||
items(items = sortedMembers, key = { it.pubkey }) { member ->
|
val isSelf = myPubkey != null && member.pubkey == myPubkey
|
||||||
val isSelf = myPubkey != null && member.pubkey == myPubkey
|
MemberCell(
|
||||||
MemberCell(
|
member = member,
|
||||||
member = member,
|
avatarSize = STAGE_AVATAR,
|
||||||
avatarSize = STAGE_AVATAR,
|
isSpeaking = member.pubkey in speakingNow,
|
||||||
isSpeaking = member.pubkey in speakingNow,
|
audioLevel = audioLevels[member.pubkey] ?: 0f,
|
||||||
audioLevel = audioLevels[member.pubkey] ?: 0f,
|
isConnecting = member.pubkey in connectingSpeakers,
|
||||||
isConnecting = member.pubkey in connectingSpeakers,
|
showMicBadge = true,
|
||||||
showMicBadge = true,
|
reactions = reactionsByPubkey[member.pubkey].orEmpty(),
|
||||||
reactions = reactionsByPubkey[member.pubkey].orEmpty(),
|
accountViewModel = accountViewModel,
|
||||||
accountViewModel = accountViewModel,
|
onLongPressParticipant = onLongPressParticipant,
|
||||||
onLongPressParticipant = onLongPressParticipant,
|
isSelf = isSelf,
|
||||||
isSelf = isSelf,
|
onTapSelf = if (isSelf) onTapSelf else null,
|
||||||
onTapSelf = if (isSelf) onTapSelf else null,
|
modifier = Modifier.animateItem(),
|
||||||
modifier = Modifier.animateItem(),
|
)
|
||||||
)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
bottomBar?.invoke()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user