From afa1a3b652072ced558760acc58d1bee8d380647 Mon Sep 17 00:00:00 2001 From: nrobi144 Date: Mon, 6 Jul 2026 09:22:51 +0300 Subject: [PATCH] feat(desktop): WoT badges on search-result person cards MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends the WoT trust indicator to the Search screen's person-picker results, matching the badges already shown on note-card avatars. - `UserSearchCard` (commons) gains an optional `badge: @Composable (BoxScope.() -> Unit)? = null` param, forwarded to its embedded `UserAvatar` (which has the slot from the WoT PR). Default null → no visual change for callers that don't opt in; Android search screens continue to render as before. - `SearchResultsList` (desktopApp) inlines the score-lookup gates in a small `wotBadgeFor(pubkey)` helper and passes the badge lambda at both person-result call sites (main list + expandable overflow). Same visibility rules as the note-card avatar badges: score > 0, past the 2 s startup readiness gate, and pubkey not in `LocalSpamExemptKeys` (self / already-followed). --- .../commons/ui/components/UserSearchCard.kt | 7 ++++ .../desktop/ui/search/SearchResultsList.kt | 34 +++++++++++++++++++ 2 files changed, 41 insertions(+) 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,