refactor(nests): route host actions through AccountViewModel.launchSigner

Replace the raw viewModelScope.launch + runCatching with launchSigner,
matching the rest of the codebase. Same survival-past-dismiss
guarantee, plus signer errors (read-only key, NIP-46 unauthorized /
timeout, signer not found) now surface as toasts instead of being
silently swallowed.
This commit is contained in:
Claude
2026-05-11 21:02:57 +00:00
parent 5bda97db04
commit c0710b794f
2 changed files with 13 additions and 21 deletions
@@ -42,7 +42,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.ui.MainActivity
import com.vitorpamplona.amethyst.ui.components.toasts.multiline.UserBasedErrorMessage
@@ -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
@@ -97,16 +95,13 @@ internal fun ParticipantHostActionsSheet(
relay = null,
)
// Use the AccountViewModel's scope so the launched broadcast
// survives the onDismiss() that every action row triggers. A
// composition-scoped rememberCoroutineScope() gets cancelled the
// moment hostMenuTarget flips to null and the sheet leaves the
// tree — which races (and reliably loses) against the suspending
// signer.sign(template) call, so the promote/demote/kick events
// never actually went out.
// 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
accountViewModel.viewModelScope.launch { runCatching { accountViewModel.account.signAndComputeBroadcast(template) } }
accountViewModel.launchSigner { accountViewModel.account.signAndComputeBroadcast(template) }
}
val targetUser =
@@ -40,7 +40,6 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewModelScope
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.viewmodels.NestViewModel
import com.vitorpamplona.amethyst.commons.viewmodels.RoomPresence
@@ -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
@@ -111,16 +109,15 @@ internal fun HandRaiseQueueSection(
hand = hand,
accountViewModel = accountViewModel,
onApprove = {
// Approving makes `canSpeak()` true for this user, which
// removes them from `hands` on the next recompose. If the
// queue empties (no other raised hands), the whole
// section leaves composition — a rememberCoroutineScope()
// bound here would cancel the in-flight signer.sign(...)
// and the promote never goes out. Launch on the
// AccountViewModel's scope so signing outlives the row.
accountViewModel.viewModelScope.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) }
}
},
)