feat: open the proposal a transcript line is about, not the room's latest
Tapping a signing line in the transcript opened whichever session the room was running, resolved by `liveSessionForChatRoom` -- the newest one not yet finished, or failing that the newest one at all. That is a guess, and it was a good one exactly as long as a room had one proposal to guess at. With a chapter and its translation open together, half the lines in the transcript led to the other proposal. The line now says which session it belongs to, so there is nothing left to guess: it carries `frostSigningSessionId` through to the route. A line written before that column opens the room's proposal list instead, which is the honest answer to a line that cannot say what it meant -- every proposal with its own state, and the reader picks -- rather than a guess dressed as an answer. That empties `FrostSigningRoute.sessionId` of its reason to be optional, so it is required, and `liveSessionForChatRoom` goes with it from the interface, the implementation and the no-op. `FrostSigningViewModel` loses its resolution step and the "This group is not signing anything right now" error underneath it -- which was never the right thing to say to somebody who had just tapped a line about a specific session. The transcript keeps doing the one job it is good at: showing a proposal as it happens, and saying whether it is still asking something of you. What it stops doing is standing in for a list of them. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -13,7 +13,6 @@ import press.mantra.compose.database.model.FrostSigningItem
|
||||
import press.mantra.compose.database.model.FrostSigningSession
|
||||
import press.mantra.compose.database.model.intermdiate.LocalChatRoom
|
||||
import press.mantra.compose.database.model.intermdiate.LocalFrostSigningSession
|
||||
import press.mantra.compose.database.model.types.FrostSigningStage
|
||||
import press.mantra.compose.managers.FrostSigningManager
|
||||
import press.mantra.compose.repository.FrostSigningRepository
|
||||
|
||||
@@ -41,14 +40,6 @@ class DatabaseFrostSigningRepository(
|
||||
override suspend fun getSessionById(sessionId: String): FrostSigningSession? =
|
||||
database.frostSigningSessionDao().getSessionById(sessionId)
|
||||
|
||||
override suspend fun liveSessionForChatRoom(chatRoomId: String): FrostSigningSession? {
|
||||
val sessions = database.frostSigningSessionDao().getSessionsForChatRoom(chatRoomId)
|
||||
|
||||
return sessions.firstOrNull {
|
||||
it.stage != FrostSigningStage.COMPLETE && it.stage != FrostSigningStage.FAILED
|
||||
} ?: sessions.firstOrNull()
|
||||
}
|
||||
|
||||
override suspend fun canSign(chatRoomId: String): Boolean =
|
||||
FrostSigningManager.canSign(database, chatRoomId)
|
||||
|
||||
|
||||
@@ -45,16 +45,6 @@ interface FrostSigningRepository {
|
||||
|
||||
suspend fun getSessionById(sessionId: String): FrostSigningSession?
|
||||
|
||||
/**
|
||||
* The session a room is currently running, or its most recent one if none is.
|
||||
*
|
||||
* For callers that mean "the signing going on here" without holding an id --
|
||||
* a transcript line, mostly. Prefers a live session because that is the one
|
||||
* anybody tapping through wants to act on; a finished one is only what is
|
||||
* left to show when there is nothing live.
|
||||
*/
|
||||
suspend fun liveSessionForChatRoom(chatRoomId: String): FrostSigningSession?
|
||||
|
||||
/** Whether this room holds a key it can sign with, so the UI offers nothing that would fail. */
|
||||
suspend fun canSign(chatRoomId: String): Boolean
|
||||
|
||||
@@ -156,8 +146,6 @@ interface FrostSigningRepository {
|
||||
|
||||
override suspend fun getSessionById(sessionId: String): FrostSigningSession? = null
|
||||
|
||||
override suspend fun liveSessionForChatRoom(chatRoomId: String): FrostSigningSession? = null
|
||||
|
||||
override suspend fun canSign(chatRoomId: String): Boolean = false
|
||||
|
||||
override suspend fun proposeSigning(
|
||||
|
||||
@@ -48,6 +48,7 @@ import press.mantra.compose.repository.NostrRepository
|
||||
import press.mantra.compose.ui.composable.navigation.routes.ChatRoomDetailRoute
|
||||
import press.mantra.compose.ui.composable.navigation.routes.DkgRitualRoute
|
||||
import press.mantra.compose.ui.composable.navigation.routes.FrostSigningRoute
|
||||
import press.mantra.compose.ui.composable.navigation.routes.ProposalListRoute
|
||||
import press.mantra.compose.ui.composable.navigation.routes.Route
|
||||
import press.mantra.compose.ui.composable.widgets.LoadingDataIndicator
|
||||
import press.mantra.compose.ui.theme.TorchTheme
|
||||
@@ -147,14 +148,25 @@ fun ChatRoomMessagingScreen(
|
||||
)
|
||||
)
|
||||
},
|
||||
onOpenSigning = {
|
||||
// No session id: a chat row carries none, and the
|
||||
// screen resolves the room's live one.
|
||||
onOpenSigning = { sessionId ->
|
||||
// A line written before chat rows named their
|
||||
// session cannot say which proposal it is about,
|
||||
// and the room may have several. The list is the
|
||||
// honest answer: it shows all of them with their
|
||||
// own state, rather than guessing at one.
|
||||
onNavigateToRoute.invoke(
|
||||
FrostSigningRoute(
|
||||
activeUserPublicKey = activeUserPublicKey,
|
||||
chatRoomId = chatRoomId
|
||||
)
|
||||
if (sessionId == null) {
|
||||
ProposalListRoute(
|
||||
activeUserPublicKey = activeUserPublicKey,
|
||||
chatRoomId = chatRoomId
|
||||
)
|
||||
} else {
|
||||
FrostSigningRoute(
|
||||
activeUserPublicKey = activeUserPublicKey,
|
||||
chatRoomId = chatRoomId,
|
||||
sessionId = sessionId
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
@@ -74,7 +74,7 @@ import press.mantra.compose.ui.view.state.FrostSigningUIState
|
||||
fun FrostSigningScreen(
|
||||
activeUserPublicKey: HexKey,
|
||||
chatRoomId: String,
|
||||
sessionId: String?,
|
||||
sessionId: String,
|
||||
initialFrostSigningUIState: FrostSigningUIState = FrostSigningUIState.Loading,
|
||||
chatRepository: ChatRepository,
|
||||
frostSigningRepository: FrostSigningRepository,
|
||||
|
||||
@@ -8,20 +8,17 @@ import kotlinx.serialization.Serializable
|
||||
* Carries a session id rather than only a room, because a group signs
|
||||
* repeatedly and can have more than one session open at a time -- unlike a
|
||||
* ceremony, where "the room's ritual" identifies it.
|
||||
*
|
||||
* The id is required. It used to be optional, resolved to "the room's live
|
||||
* session" for callers that did not know which one they meant -- a transcript
|
||||
* line, mostly, since chat rows carried no session. Two proposals open at once
|
||||
* made that guess wrong, and every one of those lines now names its session; a
|
||||
* line too old to have one goes to [ProposalListRoute] instead, where the
|
||||
* reader picks from the whole list rather than being sent to a guess.
|
||||
*/
|
||||
@Serializable
|
||||
data class FrostSigningRoute(
|
||||
val activeUserPublicKey: String,
|
||||
val chatRoomId: String,
|
||||
|
||||
/**
|
||||
* Null when the caller does not know which session it means.
|
||||
*
|
||||
* A transcript line is the main case: chat rows carry no session, and adding
|
||||
* a column for one feature to a table every message uses is a poor trade for
|
||||
* a lookup the screen can do. It resolves to the room's live session, which
|
||||
* is the one a line is talking about in every case but a group running two
|
||||
* at once.
|
||||
*/
|
||||
val sessionId: String? = null
|
||||
val sessionId: String
|
||||
): Route()
|
||||
|
||||
@@ -237,7 +237,15 @@ class ChatMessageListViewModel(
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
fun RenderMessages(onOpenSharedKey: () -> Unit, onOpenSigning: () -> Unit) {
|
||||
fun RenderMessages(
|
||||
onOpenSharedKey: () -> Unit,
|
||||
/**
|
||||
* Opens the signing this line is about, by session id -- or the room's
|
||||
* whole list of proposals when the line predates [ChatMessage.frostSigningSessionId]
|
||||
* and cannot say which one it meant.
|
||||
*/
|
||||
onOpenSigning: (sessionId: String?) -> Unit,
|
||||
) {
|
||||
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -383,7 +391,11 @@ class ChatMessageListViewModel(
|
||||
localChatMessage = localChatMessage,
|
||||
isAnswered = localChatMessage.chatMessage.id in answeredRequests,
|
||||
isSettled = localChatMessage.chatMessage.id in settledRequests,
|
||||
onClick = onOpenSigning
|
||||
onClick = {
|
||||
onOpenSigning(
|
||||
localChatMessage.chatMessage.frostSigningSessionId
|
||||
)
|
||||
}
|
||||
)
|
||||
return@items
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ import press.mantra.compose.ui.view.state.FrostSigningUIState
|
||||
|
||||
class FrostSigningViewModel(
|
||||
val chatRoomId: String,
|
||||
val sessionId: String?,
|
||||
val sessionId: String,
|
||||
val activeUserPublicKey: HexKey,
|
||||
initialFrostSigningUIState: FrostSigningUIState,
|
||||
val chatRepository: ChatRepository,
|
||||
@@ -53,22 +53,12 @@ class FrostSigningViewModel(
|
||||
return@launch
|
||||
}
|
||||
|
||||
// A transcript line knows its room but not its session, so resolve one
|
||||
// before watching anything.
|
||||
val id = sessionId
|
||||
?: frostSigningRepository.liveSessionForChatRoom(chatRoomId)?.id
|
||||
if (id == null) {
|
||||
frostSigningUIState =
|
||||
FrostSigningUIState.Error("This group is not signing anything right now.")
|
||||
return@launch
|
||||
}
|
||||
|
||||
frostSigningUIState = FrostSigningUIState.Loaded(localChatRoom = localChatRoom)
|
||||
|
||||
combine(
|
||||
frostSigningRepository.observeSessionById(id),
|
||||
frostSigningRepository.observeMessages(id),
|
||||
frostSigningRepository.observeItems(id)
|
||||
frostSigningRepository.observeSessionById(sessionId),
|
||||
frostSigningRepository.observeMessages(sessionId),
|
||||
frostSigningRepository.observeItems(sessionId)
|
||||
) { session, messages, items -> Triple(session, messages, items) }
|
||||
.collect { (session, messages, items) ->
|
||||
frostSigningUIState = FrostSigningUIState.Loaded(
|
||||
@@ -138,7 +128,7 @@ class FrostSigningViewModel(
|
||||
|
||||
fun factory(
|
||||
chatRoomId: String,
|
||||
sessionId: String?,
|
||||
sessionId: String,
|
||||
activeUserPublicKey: HexKey,
|
||||
initialFrostSigningUIState: FrostSigningUIState = FrostSigningUIState.Loading,
|
||||
chatRepository: ChatRepository,
|
||||
|
||||
Reference in New Issue
Block a user