Merge pull request #2852 from vitorpamplona/claude/fix-promote-speaker-menu-L9mT7

Fix coroutine scope for async broadcast operations in room actions
This commit is contained in:
Vitor Pamplona
2026-05-11 18:55:32 -04:00
committed by GitHub
2 changed files with 13 additions and 9 deletions
@@ -37,7 +37,6 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
@@ -56,7 +55,6 @@ import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ROLE
import kotlinx.coroutines.launch
/**
* Per-participant context sheet (T2 #2). Always shows the
@@ -88,7 +86,6 @@ internal fun ParticipantHostActionsSheet(
catalog: com.vitorpamplona.amethyst.commons.viewmodels.RoomSpeakerCatalog? = null,
) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
val scope = rememberCoroutineScope()
val roomATag =
ATag(
@@ -98,9 +95,13 @@ internal fun ParticipantHostActionsSheet(
relay = null,
)
// Go through accountViewModel.launchSigner so the broadcast runs on
// viewModelScope (survives onDismiss() removing the sheet from
// composition) and signer errors surface as toasts instead of being
// silently swallowed.
fun broadcast(template: com.vitorpamplona.quartz.nip01Core.signers.EventTemplate<out com.vitorpamplona.quartz.nip01Core.core.Event>?) {
template ?: return
scope.launch { runCatching { accountViewModel.account.signAndComputeBroadcast(template) } }
accountViewModel.launchSigner { accountViewModel.account.signAndComputeBroadcast(template) }
}
val targetUser =
@@ -37,7 +37,6 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@@ -53,7 +52,6 @@ import com.vitorpamplona.amethyst.ui.stringRes
import com.vitorpamplona.amethyst.ui.theme.Size35dp
import com.vitorpamplona.quartz.nip53LiveActivities.meetingSpaces.MeetingSpaceEvent
import com.vitorpamplona.quartz.nip53LiveActivities.streaming.tags.ROLE
import kotlinx.coroutines.launch
/**
* Host-only queue of audience members whose latest kind-10312
@@ -96,7 +94,6 @@ internal fun HandRaiseQueueSection(
if (hands.isEmpty()) return
val scope = rememberCoroutineScope()
Column(modifier = modifier.fillMaxSize().padding(top = 12.dp)) {
Text(
text = stringRes(R.string.nest_hand_raise_queue_title),
@@ -112,9 +109,15 @@ internal fun HandRaiseQueueSection(
hand = hand,
accountViewModel = accountViewModel,
onApprove = {
scope.launch {
// launchSigner runs on viewModelScope (+ Dispatchers.IO)
// and surfaces signer errors as toasts. Composition-scoped
// alternatives get cancelled when this row leaves the tree:
// approving flips `canSpeak()` true, the hand is filtered
// out, and if it was the last hand the section disposes
// entirely — killing the in-flight sign(...) before it ran.
accountViewModel.launchSigner {
val template = RoomParticipantActions.setRole(event, hand.pubkey, ROLE.SPEAKER)
template?.let { runCatching { accountViewModel.account.signAndComputeBroadcast(it) } }
template?.let { accountViewModel.account.signAndComputeBroadcast(it) }
}
},
)