diff --git a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/UserSearchCard.kt b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/UserSearchCard.kt index 60fe3b5531..bd7e82f54f 100644 --- a/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/UserSearchCard.kt +++ b/commons/src/commonMain/kotlin/com/vitorpamplona/amethyst/commons/ui/components/UserSearchCard.kt @@ -22,6 +22,7 @@ package com.vitorpamplona.amethyst.commons.ui.components import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.BoxScope import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth @@ -46,12 +47,17 @@ import org.jetbrains.compose.resources.stringResource /** * A card displaying user search result with avatar, name, and nip05/pubkey. * Shared between Android and Desktop search screens. + * + * @param badge Optional overlay drawn on top of the avatar (bottom-right + * by convention). Used by Desktop for the WoT trust-score chip; Android + * call sites leave it null. Forwarded to [UserAvatar]. */ @Composable fun UserSearchCard( user: User, onClick: () -> Unit, modifier: Modifier = Modifier, + badge: @Composable (BoxScope.() -> Unit)? = null, ) { Card( modifier = @@ -73,6 +79,7 @@ fun UserSearchCard( pictureUrl = user.profilePicture(), size = 40.dp, contentDescription = stringResource(Res.string.accessibility_user_avatar), + badge = badge, ) Column(modifier = Modifier.weight(1f)) { diff --git a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/search/SearchResultsList.kt b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/search/SearchResultsList.kt index 4b35e4d95c..68bd2e8fbc 100644 --- a/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/search/SearchResultsList.kt +++ b/desktopApp/src/jvmMain/kotlin/com/vitorpamplona/amethyst/desktop/ui/search/SearchResultsList.kt @@ -55,12 +55,16 @@ import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbol import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols +import com.vitorpamplona.amethyst.commons.moderation.LocalSpamExemptKeys import com.vitorpamplona.amethyst.commons.search.AdvancedSearchBarState import com.vitorpamplona.amethyst.commons.search.SearchSortOrder import com.vitorpamplona.amethyst.commons.ui.components.UserSearchCard +import com.vitorpamplona.amethyst.commons.wot.LocalWoTReady +import com.vitorpamplona.amethyst.commons.wot.LocalWoTService import com.vitorpamplona.amethyst.desktop.cache.DesktopLocalCache import com.vitorpamplona.amethyst.desktop.ui.note.NoteCard import com.vitorpamplona.amethyst.desktop.ui.note.SpamCheckedNoteRender +import com.vitorpamplona.amethyst.desktop.ui.note.WoTBadge import com.vitorpamplona.amethyst.desktop.ui.rememberDisplayData import com.vitorpamplona.quartz.nip23LongContent.LongTextNoteEvent @@ -116,6 +120,7 @@ fun SearchResultsList( UserSearchCard( user = user, onClick = { onNavigateToProfile(user.pubkeyHex) }, + badge = wotBadgeFor(user.pubkeyHex), ) } if (people.size > 5) { @@ -126,6 +131,7 @@ fun SearchResultsList( UserSearchCard( user = user, onClick = { onNavigateToProfile(user.pubkeyHex) }, + badge = wotBadgeFor(user.pubkeyHex), ) } } @@ -286,6 +292,34 @@ fun SearchResultsList( } } +/** + * Returns a WoT-badge lambda for the given pubkey, or null when the + * badge should be hidden. Same gates as [WoTBadgedAvatar]: + * - WoT service is provided + * - initial batch fetch has finished (or 2 s startup timeout fired) + * - the pubkey is not exempt (self or already followed) + * - the score is > 0 + * Inlined here (rather than wrapped in a new composable) because it's + * only used at the two SearchResultsList person-result call sites. + */ +@Composable +private fun wotBadgeFor(userHex: String): (@Composable androidx.compose.foundation.layout.BoxScope.() -> Unit)? { + val service = LocalWoTService.current + val ready = LocalWoTReady.current + val exempt = LocalSpamExemptKeys.current + val score = + if (service != null && ready && userHex !in exempt) { + service.scores[userHex] ?: 0 + } else { + 0 + } + return if (score > 0) { + { WoTBadge(count = score, modifier = Modifier.align(Alignment.BottomEnd)) } + } else { + null + } +} + @Composable private fun SortableHeader( title: String,