diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/participants/ParticipantHostActionsSheet.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/participants/ParticipantHostActionsSheet.kt index 8c76c30b40..8ff9d05cbf 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/participants/ParticipantHostActionsSheet.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/participants/ParticipantHostActionsSheet.kt @@ -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?) { template ?: return - scope.launch { runCatching { accountViewModel.account.signAndComputeBroadcast(template) } } + accountViewModel.launchSigner { accountViewModel.account.signAndComputeBroadcast(template) } } val targetUser = diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/HandRaiseQueueSection.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/HandRaiseQueueSection.kt index 2d1421f82d..bfb741a6a5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/HandRaiseQueueSection.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/nests/room/stage/HandRaiseQueueSection.kt @@ -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) } } }, )