fix: move on-stage controls out of action bar onto stage card
NestActionBar held the speaker controls (Talk / MicMute / Leave the
Stage) alongside the audience controls (hand-raise / react / leave
room), making the bar uncomfortably wide on phones once a user was
promoted.
Splits responsibilities:
- NestActionBar now only handles connection state in the start
cluster (Connect / Connecting chip / Reconnecting chip / empty)
plus the existing end cluster.
- StageControlsBar is a new strip rendered under the Stage card,
gated only on isOnStage, holding the full broadcast state machine.
isOnStage is the single visibility signal — a stable derived value
from the kind-30312 role + presence onstage flag — so connection
blips, broadcast state churn, mute toggles, and permission flows
never make the new bar appear or disappear. It only flips on a real
promote/demote or the user tapping Leave the Stage.
This commit is contained in:
+54
-39
@@ -74,18 +74,17 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
|||||||
* Layout: `[ start cluster ] · · · [ end cluster ]` with an optional
|
* Layout: `[ start cluster ] · · · [ end cluster ]` with an optional
|
||||||
* red status strip above for connection / broadcast / mute failures.
|
* red status strip above for connection / broadcast / mute failures.
|
||||||
*
|
*
|
||||||
* Start cluster — driven by connection × broadcast × on-stage state:
|
* Start cluster — driven by connection state only:
|
||||||
*
|
*
|
||||||
* | State | Start cluster contents |
|
* | State | Start cluster contents |
|
||||||
* |---------------------------------------------|-----------------------------------------|
|
* |------------------------------------|------------------------|
|
||||||
* | Idle / Closed / Failed connection | `[Connect]` |
|
* | Idle / Closed / Failed connection | `[Connect]` |
|
||||||
* | Connecting / Reconnecting | status chip |
|
* | Connecting | status chip |
|
||||||
* | Connected, audience | empty (system volume keys are enough) |
|
* | Reconnecting | status chip |
|
||||||
* | Connected, on stage, !canBroadcast | `[Leave the Stage]` |
|
* | Connected | empty |
|
||||||
* | Connected, on stage, mic idle | `[Talk] [Leave the Stage]` (+ pill) |
|
*
|
||||||
* | Connected, on stage, going live | status chip + `[Leave the Stage]` |
|
* On-stage controls (Talk / MicMute / Leave the Stage) live in
|
||||||
* | Connected, on stage, broadcasting | `[MicMute] [Leave the Stage]` |
|
* [StageControlsBar], attached to the bottom of the Stage card.
|
||||||
* | Connected, on stage, broadcast failed | `[Retry] [Leave the Stage]` |
|
|
||||||
*
|
*
|
||||||
* End cluster: hand-raise (audience + connected only), react, leave room.
|
* End cluster: hand-raise (audience + connected only), react, leave room.
|
||||||
*/
|
*/
|
||||||
@@ -94,8 +93,6 @@ internal fun NestActionBar(
|
|||||||
viewModel: NestViewModel,
|
viewModel: NestViewModel,
|
||||||
ui: NestUiState,
|
ui: NestUiState,
|
||||||
isOnStage: Boolean,
|
isOnStage: Boolean,
|
||||||
canBroadcast: Boolean,
|
|
||||||
speakerPubkeyHex: String,
|
|
||||||
handRaised: Boolean,
|
handRaised: Boolean,
|
||||||
onHandRaisedChange: (Boolean) -> Unit,
|
onHandRaisedChange: (Boolean) -> Unit,
|
||||||
onShowReactionPicker: () -> Unit,
|
onShowReactionPicker: () -> Unit,
|
||||||
@@ -116,13 +113,7 @@ internal fun NestActionBar(
|
|||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
) {
|
||||||
Box(modifier = Modifier.weight(1f, fill = true)) {
|
Box(modifier = Modifier.weight(1f, fill = true)) {
|
||||||
StartCluster(
|
StartCluster(viewModel = viewModel, ui = ui)
|
||||||
viewModel = viewModel,
|
|
||||||
ui = ui,
|
|
||||||
isOnStage = isOnStage,
|
|
||||||
canBroadcast = canBroadcast,
|
|
||||||
speakerPubkeyHex = speakerPubkeyHex,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
EndCluster(
|
EndCluster(
|
||||||
isOnStage = isOnStage,
|
isOnStage = isOnStage,
|
||||||
@@ -137,6 +128,49 @@ internal fun NestActionBar(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Controls strip that attaches under the Stage card. Contains
|
||||||
|
* everything a speaker needs while on stage: Talk / MicMute / Retry +
|
||||||
|
* Leave the Stage. Renders nothing when the local user isn't on stage.
|
||||||
|
*
|
||||||
|
* Visibility is gated on [isOnStage] alone — a stable signal driven
|
||||||
|
* by the kind-30312 role + presence `onstage` flag — so connection
|
||||||
|
* blips, broadcast state churn, mute toggles, and permission flows
|
||||||
|
* never make the bar appear or disappear. It only shows up on a real
|
||||||
|
* promote, and only goes away on a real demote (or the user tapping
|
||||||
|
* Leave the Stage).
|
||||||
|
*/
|
||||||
|
@Composable
|
||||||
|
internal fun StageControlsBar(
|
||||||
|
viewModel: NestViewModel,
|
||||||
|
ui: NestUiState,
|
||||||
|
isOnStage: Boolean,
|
||||||
|
canBroadcast: Boolean,
|
||||||
|
speakerPubkeyHex: String,
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
) {
|
||||||
|
if (!isOnStage) return
|
||||||
|
Row(
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
) {
|
||||||
|
if (canBroadcast) {
|
||||||
|
OnStageControls(
|
||||||
|
viewModel = viewModel,
|
||||||
|
broadcast = ui.broadcast,
|
||||||
|
speakerPubkeyHex = speakerPubkeyHex,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
// No signing/permission — only thing we can do is step down.
|
||||||
|
LeaveStageButton(onClick = { viewModel.setOnStage(false) })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Single-line red error strip. Surfaces the most relevant failure. */
|
/** Single-line red error strip. Surfaces the most relevant failure. */
|
||||||
@Composable
|
@Composable
|
||||||
private fun ActionBarStatusStrip(ui: NestUiState) {
|
private fun ActionBarStatusStrip(ui: NestUiState) {
|
||||||
@@ -166,9 +200,6 @@ private fun NestUiState.statusStripText(): String? {
|
|||||||
private fun StartCluster(
|
private fun StartCluster(
|
||||||
viewModel: NestViewModel,
|
viewModel: NestViewModel,
|
||||||
ui: NestUiState,
|
ui: NestUiState,
|
||||||
isOnStage: Boolean,
|
|
||||||
canBroadcast: Boolean,
|
|
||||||
speakerPubkeyHex: String,
|
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
@@ -190,29 +221,13 @@ private fun StartCluster(
|
|||||||
StatusChip(label = stringRes(R.string.nest_reconnecting))
|
StatusChip(label = stringRes(R.string.nest_reconnecting))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// On-stage controls live in [StageControlsBar]; audience
|
||||||
|
// has nothing to do here (system volume keys are enough).
|
||||||
is ConnectionUiState.Connected -> {
|
is ConnectionUiState.Connected -> {
|
||||||
when {
|
|
||||||
canBroadcast && isOnStage -> {
|
|
||||||
OnStageControls(
|
|
||||||
viewModel = viewModel,
|
|
||||||
broadcast = ui.broadcast,
|
|
||||||
speakerPubkeyHex = speakerPubkeyHex,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
// On stage but no signing/permission — only thing we can do is step down.
|
|
||||||
isOnStage -> {
|
|
||||||
LeaveStageButton(onClick = { viewModel.setOnStage(false) })
|
|
||||||
}
|
|
||||||
|
|
||||||
// Audience: nothing here. Phone volume keys cover local volume.
|
|
||||||
else -> {
|
|
||||||
Unit
|
Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|||||||
+11
-1
@@ -247,12 +247,22 @@ internal fun NestFullScreen(
|
|||||||
listenerCount = presences.size,
|
listenerCount = presences.size,
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
)
|
)
|
||||||
NestActionBar(
|
// 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,
|
viewModel = viewModel,
|
||||||
ui = ui,
|
ui = ui,
|
||||||
isOnStage = isOnStageMe,
|
isOnStage = isOnStageMe,
|
||||||
canBroadcast = viewModel.canBroadcast,
|
canBroadcast = viewModel.canBroadcast,
|
||||||
speakerPubkeyHex = myPubkey,
|
speakerPubkeyHex = myPubkey,
|
||||||
|
)
|
||||||
|
NestActionBar(
|
||||||
|
viewModel = viewModel,
|
||||||
|
ui = ui,
|
||||||
|
isOnStage = isOnStageMe,
|
||||||
handRaised = handRaised,
|
handRaised = handRaised,
|
||||||
onHandRaisedChange = onHandRaisedChange,
|
onHandRaisedChange = onHandRaisedChange,
|
||||||
onShowReactionPicker = { showReactionPicker = true },
|
onShowReactionPicker = { showReactionPicker = true },
|
||||||
|
|||||||
Reference in New Issue
Block a user