Merge pull request #2695 from vitorpamplona/claude/fix-duplicate-mic-buttons-AKgfT
fix: drop redundant StopBroadcastButton from nest action bar
This commit is contained in:
+35
@@ -83,12 +83,31 @@ class NestActivity : AppCompatActivity() {
|
||||
}
|
||||
|
||||
ACTION_PIP_LEAVE -> {
|
||||
// Tear down the speaker session + listener BEFORE
|
||||
// finish() so AudioRecord (and the system mic
|
||||
// indicator) releases promptly. onCleared() alone
|
||||
// runs late in the destroy lifecycle.
|
||||
pipCleanupAction?.invoke()
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wired by [NestActivityBody] via a DisposableEffect. Invokes
|
||||
* `viewModel.leave()` so the broadcast handle + listener close
|
||||
* route through `cleanupScope`/GlobalScope and survive Activity
|
||||
* destruction. Called from PIP close paths (the overlay's Leave
|
||||
* action and a swipe-dismiss while in PIP).
|
||||
*/
|
||||
@Volatile
|
||||
private var pipCleanupAction: (() -> Unit)? = null
|
||||
|
||||
fun setPipCleanupAction(action: (() -> Unit)?) {
|
||||
pipCleanupAction = action
|
||||
}
|
||||
|
||||
private val _toggleMuteSignal =
|
||||
MutableSharedFlow<Unit>(replay = 0, extraBufferCapacity = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
|
||||
|
||||
@@ -180,6 +199,22 @@ class NestActivity : AppCompatActivity() {
|
||||
enterPip()
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects a PIP swipe-dismiss. When the user dismisses the floating
|
||||
* window the system finishes the activity, so [isFinishing] is true
|
||||
* by the time onStop runs. Tearing down here (rather than waiting
|
||||
* for onCleared) keeps AudioRecord + the system mic indicator from
|
||||
* lingering through the destruction queue. Non-PIP onStop (Home /
|
||||
* Recents while not in PIP) is a no-op so backgrounding doesn't
|
||||
* teardown.
|
||||
*/
|
||||
override fun onStop() {
|
||||
if (isInPipMode.value && isFinishing) {
|
||||
pipCleanupAction?.invoke()
|
||||
}
|
||||
super.onStop()
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter Picture-in-Picture if the audio session is live and the
|
||||
* system supports PIP. Called both by [onUserLeaveHint] (Home /
|
||||
|
||||
+15
@@ -31,6 +31,7 @@ import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.OutlinedButton
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.DisposableEffect
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@@ -39,6 +40,7 @@ import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.vitorpamplona.amethyst.R
|
||||
@@ -274,6 +276,19 @@ private fun NestActivityBody(
|
||||
PipBridge(ui, pipMuteSignal, viewModel, onMuteState, onConnectedChange)
|
||||
NestForegroundServiceLifecycle(ui)
|
||||
|
||||
// Tell NestActivity how to tear the session down when the user
|
||||
// closes PIP. Both close paths in PIP — the overlay's Leave
|
||||
// action and a swipe-dismiss while in PIP — invoke this so the
|
||||
// speaker handle close (releases AudioRecord and clears the
|
||||
// system mic indicator) and the listener close run on
|
||||
// cleanupScope/GlobalScope before the activity destruction
|
||||
// queue eats viewModelScope.
|
||||
val activity = LocalContext.current as? NestActivity
|
||||
DisposableEffect(activity, viewModel) {
|
||||
activity?.setPipCleanupAction { viewModel.leave() }
|
||||
onDispose { activity?.setPipCleanupAction(null) }
|
||||
}
|
||||
|
||||
// Hand-raise is screen-local UI state; presence-publish picks it
|
||||
// up via the heartbeat and emits onstage / muted / publishing
|
||||
// alongside.
|
||||
|
||||
+65
-69
@@ -74,18 +74,17 @@ import com.vitorpamplona.amethyst.ui.stringRes
|
||||
* Layout: `[ start cluster ] · · · [ end cluster ]` with an optional
|
||||
* 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 |
|
||||
* |---------------------------------------------|-----------------------------------------|
|
||||
* | Idle / Closed / Failed connection | `[Connect]` |
|
||||
* | Connecting / Reconnecting | status chip |
|
||||
* | Connected, audience | empty (system volume keys are enough) |
|
||||
* | Connected, on stage, !canBroadcast | `[Leave the Stage]` |
|
||||
* | Connected, on stage, mic idle | `[Talk] [Leave the Stage]` (+ pill) |
|
||||
* | Connected, on stage, going live | status chip + `[Leave the Stage]` |
|
||||
* | Connected, on stage, broadcasting | `[MicMute] [Stop] [Leave the Stage]` |
|
||||
* | Connected, on stage, broadcast failed | `[Retry] [Leave the Stage]` |
|
||||
* | State | Start cluster contents |
|
||||
* |------------------------------------|------------------------|
|
||||
* | Idle / Closed / Failed connection | `[Connect]` |
|
||||
* | Connecting | status chip |
|
||||
* | Reconnecting | status chip |
|
||||
* | Connected | empty |
|
||||
*
|
||||
* On-stage controls (Talk / MicMute / Leave the Stage) live in
|
||||
* [StageControlsBar], attached to the bottom of the Stage card.
|
||||
*
|
||||
* End cluster: hand-raise (audience + connected only), react, leave room.
|
||||
*/
|
||||
@@ -94,8 +93,6 @@ internal fun NestActionBar(
|
||||
viewModel: NestViewModel,
|
||||
ui: NestUiState,
|
||||
isOnStage: Boolean,
|
||||
canBroadcast: Boolean,
|
||||
speakerPubkeyHex: String,
|
||||
handRaised: Boolean,
|
||||
onHandRaisedChange: (Boolean) -> Unit,
|
||||
onShowReactionPicker: () -> Unit,
|
||||
@@ -116,13 +113,7 @@ internal fun NestActionBar(
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
Box(modifier = Modifier.weight(1f, fill = true)) {
|
||||
StartCluster(
|
||||
viewModel = viewModel,
|
||||
ui = ui,
|
||||
isOnStage = isOnStage,
|
||||
canBroadcast = canBroadcast,
|
||||
speakerPubkeyHex = speakerPubkeyHex,
|
||||
)
|
||||
StartCluster(viewModel = viewModel, ui = ui)
|
||||
}
|
||||
EndCluster(
|
||||
isOnStage = isOnStage,
|
||||
@@ -137,6 +128,57 @@ 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 uses two signals AND'd together:
|
||||
* - [isOnStage] — derived from `participantGrid.onStage` (kind-30312
|
||||
* role + presence `onstage` flag); flips on a real promote/demote
|
||||
* after the round-trip lands. Connection blips, broadcast state
|
||||
* churn, mute toggles, and permission flows do NOT change it.
|
||||
* - [ui.onStageNow] — the LOCAL intent flag. Flips synchronously on
|
||||
* `setOnStage(false)` so a Leave-the-Stage tap hides the bar on
|
||||
* the next frame, instead of waiting for the presence event to
|
||||
* sign, broadcast, and loop back through LocalCache (a delay made
|
||||
* visible in scenario "broadcast → mute → unmute → leave", where
|
||||
* the signer queues behind a pending mute frame + 500 ms debounce).
|
||||
*
|
||||
* AND-of-both also handles host demotion correctly: intent stays true
|
||||
* but the aggregator drops us, so the bar still hides.
|
||||
*/
|
||||
@Composable
|
||||
internal fun StageControlsBar(
|
||||
viewModel: NestViewModel,
|
||||
ui: NestUiState,
|
||||
isOnStage: Boolean,
|
||||
canBroadcast: Boolean,
|
||||
speakerPubkeyHex: String,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
if (!isOnStage || !ui.onStageNow) return
|
||||
Row(
|
||||
modifier =
|
||||
modifier
|
||||
.fillMaxWidth()
|
||||
.padding(top = 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. */
|
||||
@Composable
|
||||
private fun ActionBarStatusStrip(ui: NestUiState) {
|
||||
@@ -166,9 +208,6 @@ private fun NestUiState.statusStripText(): String? {
|
||||
private fun StartCluster(
|
||||
viewModel: NestViewModel,
|
||||
ui: NestUiState,
|
||||
isOnStage: Boolean,
|
||||
canBroadcast: Boolean,
|
||||
speakerPubkeyHex: String,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
@@ -190,26 +229,10 @@ private fun StartCluster(
|
||||
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 -> {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -242,10 +265,6 @@ private fun OnStageControls(
|
||||
|
||||
is BroadcastUiState.Broadcasting -> {
|
||||
MicMuteToggle(isMuted = broadcast.isMuted, onToggle = viewModel::setMicMuted)
|
||||
// Stop sending audio without leaving the stage — viewer
|
||||
// can pause the mic and resume later via Talk. Distinct
|
||||
// from [LeaveStageButton], which also vacates the slot.
|
||||
StopBroadcastButton(onClick = viewModel::stopBroadcast)
|
||||
LeaveStageButton(onClick = leaveStage)
|
||||
}
|
||||
|
||||
@@ -381,29 +400,6 @@ private fun TalkButton(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Big error-color 56dp mic button shown while broadcasting. Same
|
||||
* footprint as [TalkButton] so the on/off swap is impossible to miss.
|
||||
*/
|
||||
@Composable
|
||||
private fun StopBroadcastButton(onClick: () -> Unit) {
|
||||
FilledIconButton(
|
||||
onClick = onClick,
|
||||
modifier = Modifier.size(56.dp),
|
||||
colors =
|
||||
IconButtonDefaults.filledIconButtonColors(
|
||||
containerColor = MaterialTheme.colorScheme.error,
|
||||
contentColor = MaterialTheme.colorScheme.onError,
|
||||
),
|
||||
) {
|
||||
Icon(
|
||||
symbol = MaterialSymbols.Mic,
|
||||
contentDescription = stringRes(R.string.nest_stop_talking),
|
||||
modifier = Modifier.size(28.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cheap broadcast-side mute toggle. Keeps the MoQ session open and
|
||||
* just stops sending audio frames; unmute is sample-accurate.
|
||||
|
||||
+34
-2
@@ -246,13 +246,25 @@ internal fun NestFullScreen(
|
||||
onTapSelf = onTapSelf,
|
||||
listenerCount = presences.size,
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
// 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,
|
||||
ui = ui,
|
||||
isOnStage = isOnStageMe,
|
||||
canBroadcast = viewModel.canBroadcast,
|
||||
speakerPubkeyHex = myPubkey,
|
||||
handRaised = handRaised,
|
||||
onHandRaisedChange = onHandRaisedChange,
|
||||
onShowReactionPicker = { showReactionPicker = true },
|
||||
@@ -360,6 +372,17 @@ internal fun NestFullScreen(
|
||||
R.string.nest_leave_host_close_failed,
|
||||
)
|
||||
}
|
||||
// Tear down the speaker session + listener
|
||||
// BEFORE finishing the activity so the
|
||||
// AudioRecord (and the system mic indicator)
|
||||
// releases promptly. onCleared() alone runs
|
||||
// late in the destroy lifecycle and can
|
||||
// leave the mic held while the activity is
|
||||
// queued for destruction. Only the
|
||||
// "Close the Room" path triggers this — the
|
||||
// "Just leave" and non-host leave paths
|
||||
// still rely on onCleared.
|
||||
viewModel.leave()
|
||||
onLeave()
|
||||
}
|
||||
},
|
||||
@@ -371,6 +394,15 @@ internal fun NestFullScreen(
|
||||
TextButton(
|
||||
onClick = {
|
||||
showHostLeaveConfirm = false
|
||||
// Tear down the speaker session + listener before
|
||||
// finishing so the AudioRecord (and the system mic
|
||||
// indicator) releases promptly. Same reasoning as
|
||||
// the Close the Room path — onCleared() runs late
|
||||
// in the destroy lifecycle and can leave the mic
|
||||
// held while the activity is queued for destruction.
|
||||
// Unlike Close the Room, this path leaves the
|
||||
// kind-30312 meeting space open for other users.
|
||||
viewModel.leave()
|
||||
onLeave()
|
||||
},
|
||||
) {
|
||||
|
||||
+30
-24
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -484,7 +484,6 @@
|
||||
<string name="nest_audio_failed">Zvuk selhal: %1$s</string>
|
||||
<string name="nest_audio_unavailable">Zvuk pro tuto místnost není k dispozici</string>
|
||||
<string name="nest_talk">Mluvit</string>
|
||||
<string name="nest_stop_talking">Přestat mluvit</string>
|
||||
<string name="nest_leave_stage">Opustit pódium</string>
|
||||
<string name="nest_mic_mute">Ztlumit mikrofon</string>
|
||||
<string name="nest_mic_unmute">Zapnout mikrofon</string>
|
||||
|
||||
@@ -489,7 +489,6 @@ anz der Bedingungen ist erforderlich</string>
|
||||
<string name="nest_audio_failed">Audio fehlgeschlagen: %1$s</string>
|
||||
<string name="nest_audio_unavailable">Audio ist für diesen Raum nicht verfügbar</string>
|
||||
<string name="nest_talk">Sprechen</string>
|
||||
<string name="nest_stop_talking">Aufhören zu sprechen</string>
|
||||
<string name="nest_leave_stage">Bühne verlassen</string>
|
||||
<string name="nest_mic_mute">Mikrofon stumm</string>
|
||||
<string name="nest_mic_unmute">Mikrofon aktivieren</string>
|
||||
|
||||
@@ -486,7 +486,6 @@
|
||||
<string name="nest_audio_failed">Hanghiba: %1$s</string>
|
||||
<string name="nest_audio_unavailable">A hang nem érhető el ebben a szobában</string>
|
||||
<string name="nest_talk">Beszéd</string>
|
||||
<string name="nest_stop_talking">Beszéd befejezése</string>
|
||||
<string name="nest_leave_stage">Színpad elhagyása</string>
|
||||
<string name="nest_mic_mute">Mikrofon némítása</string>
|
||||
<string name="nest_mic_unmute">Mikrofon némításának megszüntetése</string>
|
||||
|
||||
@@ -460,7 +460,6 @@
|
||||
<string name="nest_audio_failed">Audio mislukt: %1$s</string>
|
||||
<string name="nest_audio_unavailable">Audio is niet beschikbaar voor deze ruimte</string>
|
||||
<string name="nest_talk">Praten</string>
|
||||
<string name="nest_stop_talking">Stoppen met praten</string>
|
||||
<string name="nest_leave_stage">Podium verlaten</string>
|
||||
<string name="nest_force_mute">Spreker forceren te dempen</string>
|
||||
<string name="nest_kick_action">Kicken</string>
|
||||
|
||||
@@ -483,7 +483,6 @@
|
||||
<string name="nest_audio_failed">Błąd dźwięku: %1$s</string>
|
||||
<string name="nest_audio_unavailable">Dźwięk nie jest dostępny dla tego pokoju</string>
|
||||
<string name="nest_talk">Mów</string>
|
||||
<string name="nest_stop_talking">Przestań mówić</string>
|
||||
<string name="nest_leave_stage">Zejść ze sceny</string>
|
||||
<string name="nest_mic_mute">Wycisz mikrofon</string>
|
||||
<string name="nest_mic_unmute">Włącz mikrofon</string>
|
||||
|
||||
@@ -484,7 +484,6 @@
|
||||
<string name="nest_audio_failed">Áudio falhou: %1$s</string>
|
||||
<string name="nest_audio_unavailable">O áudio não está disponível para esta sala</string>
|
||||
<string name="nest_talk">Falar</string>
|
||||
<string name="nest_stop_talking">Parar de falar</string>
|
||||
<string name="nest_leave_stage">Sair do palco</string>
|
||||
<string name="nest_mic_mute">Silenciar microfone</string>
|
||||
<string name="nest_mic_unmute">Ativar microfone</string>
|
||||
|
||||
@@ -497,7 +497,6 @@ Za podpisovanje se je potrebno prijaviti s privatnim ključem</string>
|
||||
<string name="nest_audio_failed">Napaka pri zvoku: %1$s</string>
|
||||
<string name="nest_audio_unavailable">Zvok v tej sobi ni na voljo</string>
|
||||
<string name="nest_talk">Govori</string>
|
||||
<string name="nest_stop_talking">Prenehaj govoriti</string>
|
||||
<string name="nest_leave_stage">Zapusti oder</string>
|
||||
<string name="nest_mic_mute">Izklopi mikrofon</string>
|
||||
<string name="nest_mic_unmute">Vklopi mikrofon</string>
|
||||
|
||||
@@ -484,7 +484,6 @@
|
||||
<string name="nest_audio_failed">Ljudet misslyckades: %1$s</string>
|
||||
<string name="nest_audio_unavailable">Ljud är inte tillgängligt för det här rummet</string>
|
||||
<string name="nest_talk">Tala</string>
|
||||
<string name="nest_stop_talking">Sluta tala</string>
|
||||
<string name="nest_leave_stage">Lämna scenen</string>
|
||||
<string name="nest_mic_mute">Stäng av mikrofon</string>
|
||||
<string name="nest_mic_unmute">Slå på mikrofon</string>
|
||||
|
||||
@@ -486,7 +486,6 @@
|
||||
<string name="nest_audio_failed">音频失败: %1$s</string>
|
||||
<string name="nest_audio_unavailable">此聊天室没有音频可用</string>
|
||||
<string name="nest_talk">交谈</string>
|
||||
<string name="nest_stop_talking">停止交谈</string>
|
||||
<string name="nest_leave_stage">离开舞台</string>
|
||||
<string name="nest_mic_mute">麦克风静音</string>
|
||||
<string name="nest_mic_unmute">取消麦克风静音</string>
|
||||
|
||||
@@ -516,7 +516,6 @@
|
||||
<string name="nest_audio_failed">Audio failed: %1$s</string>
|
||||
<string name="nest_audio_unavailable">Audio is not available for this room</string>
|
||||
<string name="nest_talk">Talk</string>
|
||||
<string name="nest_stop_talking">Stop talking</string>
|
||||
<string name="nest_leave_stage">Leave stage</string>
|
||||
<string name="nest_mic_mute">Mute mic</string>
|
||||
<string name="nest_mic_unmute">Unmute mic</string>
|
||||
|
||||
+18
@@ -545,6 +545,24 @@ class NestViewModel(
|
||||
teardown(targetState = ConnectionUiState.Idle, finalCleanup = false)
|
||||
}
|
||||
|
||||
/**
|
||||
* User-driven leave-the-activity teardown. Mirrors [onCleared]: marks
|
||||
* the VM closed and routes both the speaker handle close (which
|
||||
* releases the AudioRecord and clears the system mic indicator) and
|
||||
* the listener close to [cleanupScope] so the teardown survives
|
||||
* Activity destruction. Call this from the leave-the-room button
|
||||
* BEFORE finishing the activity; relying on [onCleared] alone leaves
|
||||
* a window where the AudioRecord stays held while the activity is
|
||||
* paused / queued for destruction.
|
||||
*/
|
||||
fun leave() {
|
||||
if (closed) return
|
||||
closed = true
|
||||
requestedSpeakers = emptySet()
|
||||
teardownBroadcast(BroadcastUiState.Idle, finalCleanup = true)
|
||||
teardown(targetState = ConnectionUiState.Closed, finalCleanup = true)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
closed = true
|
||||
teardownBroadcast(BroadcastUiState.Idle, finalCleanup = true)
|
||||
|
||||
Reference in New Issue
Block a user