feat: say who opened the ceremony on the shared key screen
The screen showed the quorum, the ladder and now the roster, but never named the member whose ceremony it was. Any member can open one and it settles the group's signing quorum for good, so who opened this one belongs on the screen that describes it — the same reason the chat notice names them. Alice started this ceremony. 2 of 3 members will be needed to sign with this key. It sits above the failure branch so it holds in every state. A ceremony that was abandoned or has already produced a key is still worth attributing: a member arriving at a finished ceremony they do not remember agreeing to should be able to see whose it was, and DkgSession.coordinatorPublicKey is kept for the life of the row either way. ## One naming rule, in one place HexKey.memberName() resolves a member's display name from the profiles joined onto the room, falling back to a shortened key. The roster switched to it, so the opener line and the rows below it cannot disagree about what to call somebody, and the chat notice's copy of the fallback went with it. This replaces a second private SHORTENED_PUBLIC_KEY_LENGTH I had added to ChatMessageListViewModel. A third still lives in SelectChatRoomTypeViewModel at a different value (12) and is deliberately untouched: that is a different choice about a different surface, not a duplicate of this one, and folding them together is a call about that screen rather than about this feature. ## Still reads a raw key on one surface The abandoned card shows DkgSession.failureReason verbatim, which for a ceremony ended by another member begins "Abandoned by 1a2b3c4d:" -- a truncated key where the chat line now shows a name. Naming them there needs the culprit stored beside the reason rather than inside it, which is a column on DkgSession and a schema version, so it is left as it is rather than parsed back out of the string. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
package press.mantra.compose.extensions
|
||||
|
||||
import press.mantra.compose.database.model.intermdiate.LocalParticipant
|
||||
import com.vitorpamplona.quartz.nip01Core.core.HexKey
|
||||
|
||||
/** Enough of a public key to tell two members apart when neither has a profile yet. */
|
||||
private const val SHORTENED_PUBLIC_KEY_LENGTH = 8
|
||||
|
||||
/** A public key cut to something readable, for where no profile has arrived yet. */
|
||||
fun HexKey.shortened(): String = take(SHORTENED_PUBLIC_KEY_LENGTH)
|
||||
|
||||
/**
|
||||
* What to call the member behind this public key on screen.
|
||||
*
|
||||
* Resolved from the profiles joined onto the room rather than stored anywhere, so
|
||||
* it follows a rename and is never stuck on the "LOADING..." placeholder a member
|
||||
* is given the moment they are first seen. Falls back to a short key, which is at
|
||||
* least stable and distinguishing, when there is no profile at all yet.
|
||||
*/
|
||||
fun HexKey.memberName(
|
||||
members: List<LocalParticipant>,
|
||||
activeUserPublicKey: HexKey
|
||||
): String = if (this == activeUserPublicKey) {
|
||||
"You"
|
||||
} else {
|
||||
members.firstOrNull { it.participant.participantPublicKey == this }
|
||||
?.profile
|
||||
?.humanReadableNameOrPubkey()
|
||||
?: shortened()
|
||||
}
|
||||
@@ -37,8 +37,11 @@ import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.text.withStyle
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
@@ -47,6 +50,7 @@ import press.mantra.compose.database.model.DkgSession
|
||||
import press.mantra.compose.database.model.intermdiate.LocalChatRoom
|
||||
import press.mantra.compose.database.model.intermdiate.LocalParticipant
|
||||
import press.mantra.compose.database.model.types.DkgRitualStage
|
||||
import press.mantra.compose.extensions.memberName
|
||||
import press.mantra.compose.managers.ChillDkgRitualManager
|
||||
import press.mantra.compose.repository.ChatRepository
|
||||
import press.mantra.compose.repository.DkgRepository
|
||||
@@ -237,6 +241,19 @@ private fun RitualProgress(
|
||||
val round1Count = round1Participants.size
|
||||
val round2Count = round2Participants.size
|
||||
|
||||
// Above the failure branch so it holds in every state. Any member can open a
|
||||
// ceremony and it settles the group's signing quorum for good, so who opened
|
||||
// this one is worth saying whether it is running, finished or abandoned.
|
||||
Text(
|
||||
text = buildAnnotatedString {
|
||||
withStyle(SpanStyle(color = ProfileColor.fromPublicKey(session.coordinatorPublicKey))) {
|
||||
append(session.coordinatorPublicKey.memberName(members, activeUserPublicKey))
|
||||
}
|
||||
append(" started this ceremony.")
|
||||
},
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
|
||||
if (stage == DkgRitualStage.FAILED) {
|
||||
Card(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
@@ -394,11 +411,7 @@ private fun RitualRoster(
|
||||
|
||||
Text(
|
||||
modifier = Modifier.weight(1f),
|
||||
text = if (publicKey == activeUserPublicKey) {
|
||||
"You"
|
||||
} else {
|
||||
member.profile?.humanReadableNameOrPubkey() ?: publicKey.take(8)
|
||||
},
|
||||
text = publicKey.memberName(members, activeUserPublicKey),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.MiddleEllipsis,
|
||||
color = ProfileColor.fromPublicKey(publicKey),
|
||||
|
||||
@@ -56,6 +56,7 @@ import press.mantra.compose.database.model.NegentropySynchronizeRequest
|
||||
import press.mantra.compose.database.model.intermdiate.LocalChatMessage
|
||||
import press.mantra.compose.database.model.intermdiate.LocalChatRoom
|
||||
import press.mantra.compose.database.model.types.SynchronizationFilter
|
||||
import press.mantra.compose.extensions.shortened
|
||||
import press.mantra.compose.extensions.toFormattedTimeAndDateString
|
||||
import press.mantra.compose.nostr.Relays
|
||||
import press.mantra.compose.repository.ChatRepository
|
||||
@@ -530,7 +531,7 @@ private fun RitualNotice(
|
||||
"You"
|
||||
} else {
|
||||
localChatMessage.profile?.humanReadableNameOrPubkey()
|
||||
?: chatMessage.senderPublicKey.take(SHORTENED_PUBLIC_KEY_LENGTH)
|
||||
?: chatMessage.senderPublicKey.shortened()
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -557,6 +558,3 @@ private fun RitualNotice(
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Enough of a public key to tell two members apart when neither has a profile yet. */
|
||||
private const val SHORTENED_PUBLIC_KEY_LENGTH = 8
|
||||
|
||||
Reference in New Issue
Block a user