fix(nests): tighten action bar state coverage
Several gaps in the action bar were causing dead-end UI states or making non-actionable controls visible: * Hand-raise was rendered while disconnected / connecting / failed. Raising can't be delivered to the room until we're Connected, so hide it until the connection state agrees. Threaded `isConnected` into `EndCluster` from the root composable. * Audience listen-mute toggle (Volume Up/Off) removed. System volume keys cover local volume; the on-screen control was redundant and shared the same speaker glyph as the broadcasting mic-mute toggle, which made the two affordances easy to confuse. * On-stage user without `canBroadcast` had no way to step down without leaving the whole room. Show a "Leave the Stage" outlined button in that branch so the speaker slot can be released. * Broadcast `Connecting` and `Failed` substates also had no step-down affordance — forcing the user to either retry the mic or leave the room. Added "Leave the Stage" to both. `stopBroadcast()` cancels the in-flight `speakerConnectJob` (NestViewModel `teardownBroadcast`), so cancelling mid-handshake is safe. * `BroadcastUiState.Broadcasting.muteError` was tracked in state but never surfaced. Route it through `ActionBarStatusStrip`. * Permission denial pill said "Open settings"; renamed to "No permissions" so the label communicates the *state* (the tap still deep-links to the system settings page).
This commit is contained in:
+42
-28
@@ -126,6 +126,7 @@ internal fun NestActionBar(
|
||||
}
|
||||
EndCluster(
|
||||
isOnStage = isOnStage,
|
||||
isConnected = ui.connection is ConnectionUiState.Connected,
|
||||
handRaised = handRaised,
|
||||
onHandRaisedChange = onHandRaisedChange,
|
||||
onShowReactionPicker = onShowReactionPicker,
|
||||
@@ -150,6 +151,12 @@ private fun ActionBarStatusStrip(ui: NestUiState) {
|
||||
stringRes(R.string.nest_broadcast_failed, broadcast.reason)
|
||||
}
|
||||
|
||||
is BroadcastUiState.Broadcasting -> {
|
||||
broadcast.muteError?.let {
|
||||
stringRes(R.string.nest_mute_failed, it)
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
null
|
||||
}
|
||||
@@ -201,33 +208,27 @@ private fun StartCluster(
|
||||
}
|
||||
|
||||
is ConnectionUiState.Connected -> {
|
||||
if (canBroadcast && isOnStage) {
|
||||
OnStageControls(
|
||||
viewModel = viewModel,
|
||||
ui = ui,
|
||||
speakerPubkeyHex = speakerPubkeyHex,
|
||||
)
|
||||
} else {
|
||||
// Audience: a single mute toggle for the inbound
|
||||
// listener stream. Mirrors the old ConnectionRow.
|
||||
FilledTonalIconToggleButton(
|
||||
checked = ui.isMuted,
|
||||
onCheckedChange = { viewModel.setMuted(it) },
|
||||
modifier = Modifier.size(width = 40.dp, height = ButtonDefaults.MinHeight),
|
||||
) {
|
||||
Icon(
|
||||
symbol =
|
||||
if (ui.isMuted) {
|
||||
MaterialSymbols.AutoMirrored.VolumeOff
|
||||
} else {
|
||||
MaterialSymbols.AutoMirrored.VolumeUp
|
||||
},
|
||||
contentDescription =
|
||||
stringRes(
|
||||
if (ui.isMuted) R.string.nest_unmute else R.string.nest_mute,
|
||||
),
|
||||
when {
|
||||
canBroadcast && isOnStage -> {
|
||||
OnStageControls(
|
||||
viewModel = viewModel,
|
||||
ui = ui,
|
||||
speakerPubkeyHex = speakerPubkeyHex,
|
||||
)
|
||||
}
|
||||
|
||||
isOnStage -> {
|
||||
// On stage but cannot broadcast (no signer / no permission).
|
||||
// Only sensible affordance is to step back down to audience.
|
||||
OutlinedButton(onClick = { viewModel.setOnStage(false) }) {
|
||||
Text(stringRes(R.string.nest_leave_stage))
|
||||
}
|
||||
}
|
||||
|
||||
else -> {
|
||||
// Audience: nothing to do here. System volume keys cover
|
||||
// local volume; the mute toggle was redundant.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -326,6 +327,14 @@ private fun OnStageControls(
|
||||
enabled = false,
|
||||
label = { Text(stringRes(R.string.nest_broadcast_connecting)) },
|
||||
)
|
||||
// Always offer a way down. stopBroadcast() cancels the
|
||||
// in-flight speakerConnectJob (NestViewModel.teardownBroadcast).
|
||||
OutlinedButton(onClick = {
|
||||
viewModel.stopBroadcast()
|
||||
viewModel.setOnStage(false)
|
||||
}) {
|
||||
Text(stringRes(R.string.nest_leave_stage))
|
||||
}
|
||||
}
|
||||
|
||||
is BroadcastUiState.Broadcasting -> {
|
||||
@@ -398,6 +407,9 @@ private fun OnStageControls(
|
||||
modifier = Modifier.size(28.dp),
|
||||
)
|
||||
}
|
||||
OutlinedButton(onClick = { viewModel.setOnStage(false) }) {
|
||||
Text(stringRes(R.string.nest_leave_stage))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -405,6 +417,7 @@ private fun OnStageControls(
|
||||
@Composable
|
||||
private fun EndCluster(
|
||||
isOnStage: Boolean,
|
||||
isConnected: Boolean,
|
||||
handRaised: Boolean,
|
||||
onHandRaisedChange: (Boolean) -> Unit,
|
||||
onShowReactionPicker: () -> Unit,
|
||||
@@ -414,9 +427,10 @@ private fun EndCluster(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
) {
|
||||
// Hand-raise only makes sense for audience: if you're already
|
||||
// on stage, you don't need to ask. Hidden when on-stage.
|
||||
if (!isOnStage) {
|
||||
// Hand-raise only makes sense for audience that's actually
|
||||
// connected — if you're already on stage you don't need to ask,
|
||||
// and raising while disconnected can't be delivered to the room.
|
||||
if (!isOnStage && isConnected) {
|
||||
FilledTonalIconToggleButton(
|
||||
checked = handRaised,
|
||||
onCheckedChange = onHandRaisedChange,
|
||||
|
||||
@@ -523,8 +523,9 @@
|
||||
<string name="nest_broadcast_connecting">Going live…</string>
|
||||
<string name="nest_broadcasting">Live</string>
|
||||
<string name="nest_broadcast_failed">Broadcast failed: %1$s</string>
|
||||
<string name="nest_mute_failed">Mute failed: %1$s</string>
|
||||
<string name="nest_record_permission_required">Microphone access is required to talk in this room.</string>
|
||||
<string name="nest_open_settings">Open settings</string>
|
||||
<string name="nest_open_settings">No permissions</string>
|
||||
<string name="nest_notification_channel">Nests</string>
|
||||
<string name="nest_notification_channel_description">Keeps audio playing while a room is open.</string>
|
||||
<string name="nest_notification_listening">Nest connected</string>
|
||||
|
||||
Reference in New Issue
Block a user