From 1c5230cfc5f7fe9d89e240af26fcab288c1585b3 Mon Sep 17 00:00:00 2001 From: m Date: Tue, 19 May 2026 06:33:47 +1000 Subject: [PATCH] feat(search): inline Namecoin resolution indicator in global search bar Reuses the NamecoinResolutionRow composable already shipping for the onchain-zap recipient field, promoting it from ui/screen/loggedIn/wallet/ to a generic ui/components/namecoin/ location so it can be mounted anywhere a .bit-shaped search input may race the local-cache prefix search. In the global search bar, typing a bare ".bit" host (e.g. "testls.bit") used to surface a cached sibling profile like "m@testls.bit" first (LocalCache.findUsersStartingWith hits the prefix) and only several seconds later be corrected by the slower on-chain ElectrumX resolution from SearchBarViewModel.directNip05Resolver. No in-flight indicator and no feedback on hard failures (timeout, malformed record, etc.). Changes: - git-rename NamecoinResolutionRow.kt and its test from ui/screen/loggedIn/wallet/ to ui/components/namecoin/, updating the package declaration only. - Add an optional `modifier: Modifier = Modifier` parameter to the composable (standard Compose convention) and wrap the spinner / result / error rows in a Column taking the caller-provided modifier. No visual change in OnchainZapSendDialog. - Update OnchainZapSendDialog import to the new package location. - Mount NamecoinResolutionRow in SearchScreen.SearchBar between SearchTextField and SearchFilterRow, with horizontal padding to match the rest of the bar. onUserResolved navigates to the user and clears the field, matching the bech32 auto-resolve path in SearchBarViewModel.directRouteResolver. State is held in the shared commons.NamecoinResolveState (no new state class introduced) and diagnostic wording comes from the existing mapOutcomeToResolveState helper, so every Namecoin surface continues to produce the same message for the same outcome. --- .../namecoin}/NamecoinResolutionRow.kt | 29 ++++++++++++------- .../ui/screen/loggedIn/search/SearchScreen.kt | 19 ++++++++++++ .../loggedIn/wallet/OnchainZapSendDialog.kt | 1 + .../namecoin}/NamecoinResolutionRowTest.kt | 2 +- 4 files changed, 39 insertions(+), 12 deletions(-) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{screen/loggedIn/wallet => components/namecoin}/NamecoinResolutionRow.kt (92%) rename amethyst/src/test/java/com/vitorpamplona/amethyst/ui/{screen/loggedIn/wallet => components/namecoin}/NamecoinResolutionRowTest.kt (99%) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/NamecoinResolutionRow.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRow.kt similarity index 92% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/NamecoinResolutionRow.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRow.kt index 8709b21734..e3bca2104d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/NamecoinResolutionRow.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRow.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet +package com.vitorpamplona.amethyst.ui.components.namecoin import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -111,15 +111,16 @@ fun looksLikeNamecoinIdentifier(raw: String): Boolean { } /** - * Inline Namecoin resolution indicator + result row, sandwiched between - * the recipient text field and the local-cache suggestion dropdown in - * [OnchainZapSendDialog]. + * Inline Namecoin resolution indicator + result row. Designed to be + * mounted alongside any text input whose local-cache prefix search can + * race ahead of an on-chain `.bit` lookup (the onchain-zap recipient + * field and the global search bar both have this race). * * Behaviour: * - Renders nothing when [searchInput] is not a `.bit` identifier. * - Shows a small spinner row ("Resolving on Namecoin…") while the * ElectrumX lookup is in flight (after a 300 ms debounce to match - * the dropdown's own debounce). + * typical input-field debounce intervals). * - On success, shows the resolved user as a tappable row with a * `MaterialSymbols.Link` badge labelled "Namecoin"; tapping calls * [onUserResolved]. @@ -132,12 +133,16 @@ fun looksLikeNamecoinIdentifier(raw: String): Boolean { * The composable is intentionally self-contained: it owns its own * [LaunchedEffect] keyed on [searchInput], so it cancels in-flight * lookups whenever the user keeps typing. + * + * @param modifier applied to the outer `Column` so callers can position + * or pad the row (e.g. the search bar pads horizontally). */ @Composable fun NamecoinResolutionRow( searchInput: String, accountViewModel: AccountViewModel, onUserResolved: (User) -> Unit, + modifier: Modifier = Modifier, ) { val trimmed = remember(searchInput) { searchInput.trim().removePrefix("@") } if (!looksLikeNamecoinIdentifier(trimmed)) return @@ -166,12 +171,14 @@ fun NamecoinResolutionRow( } } - Spacer(Modifier.size(8.dp)) - when (val s = state) { - null, NamecoinResolveState.Loading -> ResolvingChip(trimmed) - is NamecoinResolveState.Resolved -> ResolvedRow(trimmed, s, accountViewModel, onUserResolved) - NamecoinResolveState.NotFound -> FailedRow("No record for $trimmed on Namecoin.") - is NamecoinResolveState.Error -> FailedRow(s.message) + Column(modifier = modifier) { + Spacer(Modifier.size(8.dp)) + when (val s = state) { + null, NamecoinResolveState.Loading -> ResolvingChip(trimmed) + is NamecoinResolveState.Resolved -> ResolvedRow(trimmed, s, accountViewModel, onUserResolved) + NamecoinResolveState.NotFound -> FailedRow("No record for $trimmed on Namecoin.") + is NamecoinResolveState.Error -> FailedRow(s.message) + } } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt index 0ffcd44282..61cc4d5f16 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchScreen.kt @@ -79,6 +79,7 @@ import com.vitorpamplona.amethyst.commons.search.SearchSource import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.nip11RelayInfo.loadRelayInfo import com.vitorpamplona.amethyst.service.relayClient.searchCommand.TextSearchDataSourceSubscription +import com.vitorpamplona.amethyst.ui.components.namecoin.NamecoinResolutionRow import com.vitorpamplona.amethyst.ui.feeds.WatchLifecycleAndUpdateModel import com.vitorpamplona.amethyst.ui.layouts.DisappearingScaffold import com.vitorpamplona.amethyst.ui.layouts.rememberFeedContentPadding @@ -194,6 +195,24 @@ private fun SearchBar( Column(modifier = Modifier.statusBarsPadding()) { SearchTextField(searchBarViewModel, Modifier) + // Inline Namecoin lookup feedback for the global search field. + // Mirrors the wiring in OnchainZapSendDialog: the local prefix + // search can race ahead of the on-chain resolution and show a + // cached sibling profile (e.g. "m@testls.bit") before the bare + // ".bit" host resolves to its `_@host` profile. Surfaces the + // in-flight state, the eventual on-chain match, and any failure + // explicitly. Tapping the resolved row navigates to the user and + // clears the search field, matching the existing bech32 auto- + // resolve behaviour in `SearchBarViewModel.directRouteResolver`. + NamecoinResolutionRow( + searchInput = searchBarViewModel.searchValue, + accountViewModel = accountViewModel, + onUserResolved = { user -> + nav.nav(routeFor(user)) + searchBarViewModel.clear() + }, + modifier = Modifier.padding(horizontal = 10.dp), + ) SearchFilterRow(searchBarViewModel) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt index 8199baf54d..f0738c3956 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/OnchainZapSendDialog.kt @@ -69,6 +69,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.onchain.OnchainZapSendResult import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User +import com.vitorpamplona.amethyst.ui.components.namecoin.NamecoinResolutionRow import com.vitorpamplona.amethyst.ui.navigation.navs.EmptyNav import com.vitorpamplona.amethyst.ui.note.UserPicture import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.ShowUserSuggestionList diff --git a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/NamecoinResolutionRowTest.kt b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRowTest.kt similarity index 99% rename from amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/NamecoinResolutionRowTest.kt rename to amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRowTest.kt index 2554afbdaf..af91222256 100644 --- a/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/wallet/NamecoinResolutionRowTest.kt +++ b/amethyst/src/test/java/com/vitorpamplona/amethyst/ui/components/namecoin/NamecoinResolutionRowTest.kt @@ -18,7 +18,7 @@ * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -package com.vitorpamplona.amethyst.ui.screen.loggedIn.wallet +package com.vitorpamplona.amethyst.ui.components.namecoin import com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.namecoin.NamecoinResolveState import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNostrResult