diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/profile/ProfileBadgesScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/profile/ProfileBadgesScreen.kt index ab219fe1d..c4492435b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/profile/ProfileBadgesScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/badges/profile/ProfileBadgesScreen.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.ui.screen.loggedIn.badges.profile +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -56,6 +57,7 @@ import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.EventFind import com.vitorpamplona.amethyst.service.relayClient.reqCommand.event.observeNoteEvent import com.vitorpamplona.amethyst.ui.components.RobohashAsyncImage import com.vitorpamplona.amethyst.ui.navigation.navs.INav +import com.vitorpamplona.amethyst.ui.navigation.routes.routeFor import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarWithBackButton import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @@ -116,13 +118,17 @@ fun ProfileBadgesScreen( } val receivedAwards = - remember(myPubkey, bundleTick) { + remember(myPubkey, bundleTick, acceptedAwardIds) { LocalCache.notes .filterIntoSet { _, it -> val event = it.event event is BadgeAwardEvent && event.awardeeIds().contains(myPubkey) }.mapNotNull { it.event as? BadgeAwardEvent } - .sortedByDescending { it.createdAt } + .sortedWith( + // Accepted badges on top, then most recent first. + compareByDescending { acceptedAwardIds.contains(it.id) } + .thenByDescending { it.createdAt }, + ) } Scaffold( @@ -156,6 +162,7 @@ fun ProfileBadgesScreen( award = award, isAccepted = acceptedAwardIds.contains(award.id), accountViewModel = accountViewModel, + nav = nav, ) HorizontalDivider() } @@ -170,26 +177,43 @@ private fun AwardRow( award: BadgeAwardEvent, isAccepted: Boolean, accountViewModel: AccountViewModel, + nav: INav, ) { val defAddr = award.awardDefinition().firstOrNull() if (defAddr == null) { - StaticAwardRow(definition = null, isAccepted = isAccepted, accountViewModel = accountViewModel, award = award) + StaticAwardRow( + definition = null, + defNote = null, + isAccepted = isAccepted, + accountViewModel = accountViewModel, + award = award, + nav = nav, + ) return } LoadAddressableNote(defAddr, accountViewModel) { defNote -> if (defNote == null) { - StaticAwardRow(definition = null, isAccepted = isAccepted, accountViewModel = accountViewModel, award = award) + StaticAwardRow( + definition = null, + defNote = null, + isAccepted = isAccepted, + accountViewModel = accountViewModel, + award = award, + nav = nav, + ) } else { // Ask relays for the definition if we don't have it yet, then watch. EventFinderFilterAssemblerSubscription(defNote, accountViewModel) val definition by observeNoteEvent(defNote, accountViewModel) StaticAwardRow( definition = definition, + defNote = defNote, isAccepted = isAccepted, accountViewModel = accountViewModel, award = award, + nav = nav, ) } } @@ -198,15 +222,27 @@ private fun AwardRow( @Composable private fun StaticAwardRow( definition: BadgeDefinitionEvent?, + defNote: com.vitorpamplona.amethyst.model.AddressableNote?, isAccepted: Boolean, accountViewModel: AccountViewModel, award: BadgeAwardEvent, + nav: INav, ) { - Row( - modifier = + val rowModifier = + if (defNote != null) { Modifier .fillMaxWidth() - .padding(horizontal = 20.dp, vertical = 12.dp), + .clickable { + routeFor(defNote, accountViewModel.account)?.let { nav.nav(it) } + }.padding(horizontal = 20.dp, vertical = 12.dp) + } else { + Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 12.dp) + } + + Row( + modifier = rowModifier, verticalAlignment = Alignment.CenterVertically, ) { BadgeThumb(definition) diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip58Badges/accepted/AcceptedBadgeSetEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip58Badges/accepted/AcceptedBadgeSetEvent.kt index fe5f01e65..451de1943 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip58Badges/accepted/AcceptedBadgeSetEvent.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip58Badges/accepted/AcceptedBadgeSetEvent.kt @@ -25,10 +25,11 @@ import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip01Core.core.BaseAddressableEvent import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.core.tagArray import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider -import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag import com.vitorpamplona.quartz.nip01Core.tags.dTag.dTag import com.vitorpamplona.quartz.nip01Core.tags.events.ETag @@ -80,13 +81,18 @@ class AcceptedBadgeSetEvent( acceptedBadges: List = emptyList(), createdAt: Long = TimeUtils.now(), initializer: TagArrayBuilder.() -> Unit = {}, - ) = eventTemplate(KIND, "", createdAt) { - dTag(STANDARD_D_TAG) - alt(ALT_DESCRIPTION) - if (acceptedBadges.isNotEmpty()) { - acceptedBadges(acceptedBadges) - } - initializer() + ): EventTemplate { + // TagArrayBuilder groups tags by name, which would scramble the + // alternating a/e pairs NIP-58 requires. Build the prefix tags via + // the builder, then append the ordered pair tags verbatim. + val prefix = + tagArray { + dTag(STANDARD_D_TAG) + alt(ALT_DESCRIPTION) + initializer() + } + val pairs = AcceptedBadge.assemble(acceptedBadges).toTypedArray() + return EventTemplate(createdAt, KIND, prefix + pairs, "") } } } diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip58Badges/profile/ProfileBadgesEvent.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip58Badges/profile/ProfileBadgesEvent.kt index 1680530b6..e683cc51c 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip58Badges/profile/ProfileBadgesEvent.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip58Badges/profile/ProfileBadgesEvent.kt @@ -25,10 +25,11 @@ import com.vitorpamplona.quartz.nip01Core.core.Address import com.vitorpamplona.quartz.nip01Core.core.BaseReplaceableEvent import com.vitorpamplona.quartz.nip01Core.core.HexKey import com.vitorpamplona.quartz.nip01Core.core.TagArrayBuilder +import com.vitorpamplona.quartz.nip01Core.core.tagArray import com.vitorpamplona.quartz.nip01Core.hints.AddressHintProvider import com.vitorpamplona.quartz.nip01Core.hints.EventHintProvider import com.vitorpamplona.quartz.nip01Core.hints.PubKeyHintProvider -import com.vitorpamplona.quartz.nip01Core.signers.eventTemplate +import com.vitorpamplona.quartz.nip01Core.signers.EventTemplate import com.vitorpamplona.quartz.nip01Core.tags.aTag.ATag import com.vitorpamplona.quartz.nip01Core.tags.events.ETag import com.vitorpamplona.quartz.nip01Core.tags.people.PTag @@ -78,12 +79,17 @@ class ProfileBadgesEvent( acceptedBadges: List = emptyList(), createdAt: Long = TimeUtils.now(), initializer: TagArrayBuilder.() -> Unit = {}, - ) = eventTemplate(KIND, "", createdAt) { - alt(ALT_DESCRIPTION) - if (acceptedBadges.isNotEmpty()) { - acceptedBadges(acceptedBadges) - } - initializer() + ): EventTemplate { + // TagArrayBuilder groups tags by name, which would scramble the + // alternating a/e pairs NIP-58 requires. Build the prefix tags via + // the builder, then append the ordered pair tags verbatim. + val prefix = + tagArray { + alt(ALT_DESCRIPTION) + initializer() + } + val pairs = AcceptedBadge.assemble(acceptedBadges).toTypedArray() + return EventTemplate(createdAt, KIND, prefix + pairs, "") } } }