refactor: simplify .bit search resolution per review feedback

Address @vitorpamplona's review comments on the last commit:

1. Remove NamecoinResolutionState sealed class and status banners
   — the existing NIP-05 resolver already handles .bit, no need for
   separate Namecoin-specific UI state (spinner, chain emoji, error)

2. Remove separate toNip05IdOrNull() resolution path
   — widen the existing `if (term.contains('@'))` gate in both
   SearchBarViewModel and UserSuggestionState to also accept bare
   .bit domains (synthesize _@domain.bit → existing NIP-05 flow)

3. Remove d/ and id/ identifier support from search
   — per feedback, only NIP-05 style resolution (.bit) in search

4. Remove special Namecoin result rendering in ImportFollowList
   — just show UserLine for all results, no separation needed

5. Keep a lightweight 'Namecoin' label on resolved users in search
   — namecoinResolvedUser StateFlow tracks the .bit-resolved user,
   shown as a small label above the UserCompose row

Net: -405 lines, +50 lines across 6 files.
This commit is contained in:
M
2026-03-26 08:43:39 +11:00
parent 0d83af8912
commit 67fd48c5b8
6 changed files with 50 additions and 405 deletions
@@ -30,33 +30,23 @@ import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.commons.model.nip05DnsIdentifiers.Nip05State
import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relayClient.searchCommand.UserSearchDataSourceSubscription
import com.vitorpamplona.amethyst.ui.layouts.listItem.SlimListItem
import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture
import com.vitorpamplona.amethyst.ui.note.ObserveAndRenderNIP05VerifiedSymbol
import com.vitorpamplona.amethyst.ui.note.UsernameDisplay
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.WatchAndDisplayNip05Row
import com.vitorpamplona.amethyst.ui.theme.DividerThickness
import com.vitorpamplona.amethyst.ui.theme.Font14SP
import com.vitorpamplona.amethyst.ui.theme.NIP05IconSize
import com.vitorpamplona.amethyst.ui.theme.Size55dp
import com.vitorpamplona.amethyst.ui.theme.nip05
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -155,51 +145,3 @@ fun UserLine(
},
)
}
@Composable
private fun WatchAndDisplayNip05Row(
user: User,
accountViewModel: AccountViewModel,
) {
val nip05StateMetadata by user.nip05State().flow.collectAsStateWithLifecycle()
when (val nip05State = nip05StateMetadata) {
is Nip05State.Exists -> {
NonClickableObserveAndDisplayNIP05(nip05State, accountViewModel)
}
else -> {
Text(
text = user.pubkeyDisplayHex(),
fontSize = Font14SP,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
}
}
@Composable
private fun NonClickableObserveAndDisplayNIP05(
nip05State: Nip05State.Exists,
accountViewModel: AccountViewModel,
) {
if (nip05State.nip05.name != "_") {
Text(
text = remember(nip05State) { AnnotatedString(nip05State.nip05.name) },
fontSize = Font14SP,
color = MaterialTheme.colorScheme.nip05,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
)
}
ObserveAndRenderNIP05VerifiedSymbol(nip05State, 1, NIP05IconSize, accountViewModel)
Text(
text = nip05State.nip05.domain,
style = LocalTextStyle.current.copy(color = MaterialTheme.colorScheme.nip05, fontSize = Font14SP),
maxLines = 1,
overflow = TextOverflow.Visible,
)
}
@@ -31,7 +31,6 @@ import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrlOrNull
import com.vitorpamplona.quartz.nip05DnsIdentifiers.INip05Client
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Id
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
import com.vitorpamplona.quartz.nip19Bech32.entities.NPub
@@ -58,44 +57,6 @@ val userUriPrefixes =
DualCase("nostr:nprofile"),
)
/** UI state for Namecoin resolution progress. */
sealed class NamecoinResolutionState {
data object Idle : NamecoinResolutionState()
data object Resolving : NamecoinResolutionState()
data class Resolved(
val user: User,
val namecoinName: String,
) : NamecoinResolutionState()
data class Error(
val message: String,
) : NamecoinResolutionState()
}
/** Returns a [Nip05Id] for identifiers that should go through NIP-05 / Namecoin resolution. */
private fun toNip05IdOrNull(prefix: String): Nip05Id? =
when {
prefix.contains('@') -> {
Nip05Id.parse(prefix)
}
NamecoinNameResolver.isNamecoinIdentifier(prefix) -> {
if (prefix.endsWith(".bit", ignoreCase = true)) {
// Bare .bit domain → synthesize _@domain.bit so Nip05Client routes to Namecoin
Nip05Id("_", prefix.lowercase())
} else {
// d/ or id/ — wrap as NIP-05 so it reaches the resolver
Nip05Id("_", prefix.lowercase())
}
}
else -> {
null
}
}
@Stable
class UserSuggestionState(
val account: Account,
@@ -105,9 +66,6 @@ class UserSuggestionState(
val currentWord = MutableStateFlow("")
val searchDataSourceState = SearchQueryState(MutableStateFlow(""), account)
/** Tracks Namecoin resolution status for the UI. */
val namecoinState = MutableStateFlow<NamecoinResolutionState>(NamecoinResolutionState.Idle)
@OptIn(FlowPreview::class)
val searchTerm =
currentWord
@@ -124,38 +82,30 @@ class UserSuggestionState(
.map(::userSearchTermOrNull)
.map { prefix ->
if (prefix != null) {
val nip05 = toNip05IdOrNull(prefix)
// NIP-05 resolution: user@domain or bare .bit domain
val nip05 =
if (prefix.contains('@')) {
Nip05Id.parse(prefix)
} else if (prefix.endsWith(".bit", ignoreCase = true)) {
Nip05Id("_", prefix.lowercase())
} else {
null
}
if (nip05 != null) {
val isNamecoin = NamecoinNameResolver.isNamecoinIdentifier(nip05.toValue())
if (isNamecoin) namecoinState.emit(NamecoinResolutionState.Resolving)
val user =
runCatching {
nip05Client.get(nip05)?.let { info ->
val u = account.cache.checkGetOrCreateUser(info.pubkey)
if (u != null) {
info.relays.forEach {
it.normalizeRelayUrlOrNull()?.let { relay ->
account.cache.relayHints.addKey(u.pubkey(), relay)
}
runCatching {
nip05Client.get(nip05)?.let { info ->
val user = account.cache.checkGetOrCreateUser(info.pubkey)
if (user != null) {
info.relays.forEach {
it.normalizeRelayUrlOrNull()?.let { relay ->
account.cache.relayHints.addKey(user.pubkey(), relay)
}
}
u
}
}.getOrNull()
if (isNamecoin) {
if (user != null) {
namecoinState.emit(NamecoinResolutionState.Resolved(user, prefix))
} else {
namecoinState.emit(NamecoinResolutionState.Error("Could not resolve $prefix via Namecoin"))
user
}
} else {
namecoinState.emit(NamecoinResolutionState.Idle)
}
user
}.getOrNull()
} else if (prefix.startsWithAny(userUriPrefixes)) {
namecoinState.emit(NamecoinResolutionState.Idle)
runCatching {
Nip19Parser.uriToRoute(prefix)?.entity?.let { parsed ->
when (parsed) {
@@ -182,14 +132,11 @@ class UserSuggestionState(
}
}.getOrNull()
} else if (prefix.length == 64 && Hex.isHex64(prefix)) {
namecoinState.emit(NamecoinResolutionState.Idle)
account.cache.getOrCreateUser(prefix)
} else {
namecoinState.emit(NamecoinResolutionState.Idle)
null
}
} else {
namecoinState.emit(NamecoinResolutionState.Idle)
null
}
}.flowOn(Dispatchers.IO)
@@ -20,11 +20,6 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.newUser
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
@@ -42,7 +37,6 @@ import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.PersonAdd
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -73,7 +67,6 @@ import com.vitorpamplona.amethyst.service.relayClient.searchCommand.UserSearchDa
import com.vitorpamplona.amethyst.ui.navigation.navs.INav
import com.vitorpamplona.amethyst.ui.navigation.routes.Route
import com.vitorpamplona.amethyst.ui.navigation.topbars.TopBarExtensibleWithBackButton
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.NamecoinResolutionState
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.UserLine
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.UserSuggestionState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
@@ -107,10 +100,6 @@ class ImportFollowListSelectUserViewModel(
userSuggestions.results
.stateIn(viewModelScope, SharingStarted.Eagerly, emptyList())
val namecoinState =
userSuggestions.namecoinState
.stateIn(viewModelScope, SharingStarted.Eagerly, NamecoinResolutionState.Idle)
class Factory(
val account: Account,
val nip05Client: INip05Client,
@@ -177,10 +166,6 @@ private fun InputSelectUserBody(
supportingText = { Text(stringRes(R.string.supports_npub_nip_05_hex_and_namecoin_bit_d_id)) },
)
Spacer(Modifier.height(4.dp))
NamecoinStatusBanner(viewModel)
Spacer(Modifier.height(8.dp))
CustomShowUserSuggestionList(
@@ -200,88 +185,6 @@ private fun InputSelectUserBody(
}
}
@Composable
private fun NamecoinStatusBanner(viewModel: ImportFollowListSelectUserViewModel) {
val ncState by viewModel.namecoinState.collectAsStateWithLifecycle()
AnimatedVisibility(
visible = ncState !is NamecoinResolutionState.Idle,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
when (val state = ncState) {
is NamecoinResolutionState.Resolving -> {
Row(
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically,
) {
CircularProgressIndicator(
modifier = Modifier.size(16.dp),
strokeWidth = 2.dp,
color = MaterialTheme.colorScheme.primary,
)
Spacer(Modifier.width(8.dp))
Text(
"Resolving via Namecoin blockchain\u2026",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary,
)
}
}
is NamecoinResolutionState.Resolved -> {
Row(
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
"\u26D3\uFE0F",
style = MaterialTheme.typography.bodySmall,
)
Spacer(Modifier.width(6.dp))
Text(
"Resolved via Namecoin: ${state.namecoinName}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Medium,
)
}
}
is NamecoinResolutionState.Error -> {
Row(
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 4.dp, vertical = 6.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
"\u26A0\uFE0F",
style = MaterialTheme.typography.bodySmall,
)
Spacer(Modifier.width(6.dp))
Text(
state.message,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error,
)
}
}
is NamecoinResolutionState.Idle -> {
// nothing
}
}
}
}
@Composable
private fun ImportHeader() {
Column {
@@ -341,7 +244,6 @@ fun CustomWatchResponses(
modifier: Modifier = Modifier,
) {
val suggestions by viewModel.results.collectAsStateWithLifecycle()
val ncState by viewModel.namecoinState.collectAsStateWithLifecycle()
if (suggestions.isNotEmpty()) {
LazyColumn(
@@ -350,28 +252,7 @@ fun CustomWatchResponses(
state = viewModel.listState,
) {
itemsIndexed(suggestions, key = { _, item -> item.pubkeyHex }) { _, item ->
val isNamecoinResult =
ncState is NamecoinResolutionState.Resolved &&
(ncState as NamecoinResolutionState.Resolved).user == item
if (isNamecoinResult) {
Column {
Row(
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
"\u26D3\uFE0F Namecoin result",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Medium,
)
}
UserLine(item, accountViewModel) { onSelect(item) }
}
} else {
UserLine(item, accountViewModel) { onSelect(item) }
}
UserLine(item, accountViewModel) { onSelect(item) }
HorizontalDivider(
thickness = DividerThickness,
)
@@ -36,7 +36,6 @@ import com.vitorpamplona.amethyst.model.LocalCache
import com.vitorpamplona.amethyst.model.User
import com.vitorpamplona.amethyst.service.relayClient.searchCommand.SearchQueryState
import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.NamecoinResolutionState
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.userUriPrefixes
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.relaySetupInfoBuilder
import com.vitorpamplona.quartz.nip01Core.core.toHexKey
@@ -44,7 +43,6 @@ import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl
import com.vitorpamplona.quartz.nip01Core.relay.normalizer.normalizeRelayUrlOrNull
import com.vitorpamplona.quartz.nip05DnsIdentifiers.INip05Client
import com.vitorpamplona.quartz.nip05DnsIdentifiers.Nip05Id
import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver
import com.vitorpamplona.quartz.nip10Notes.content.findHashtags
import com.vitorpamplona.quartz.nip19Bech32.Nip19Parser
import com.vitorpamplona.quartz.nip19Bech32.entities.NProfile
@@ -59,6 +57,7 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.SharingStarted.Companion.WhileSubscribed
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
@@ -68,26 +67,6 @@ import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
/** Returns a [Nip05Id] for identifiers that should go through NIP-05 / Namecoin resolution. */
private fun toNip05IdOrNull(term: String): Nip05Id? =
when {
term.contains('@') -> {
Nip05Id.parse(term)
}
NamecoinNameResolver.isNamecoinIdentifier(term) -> {
if (term.endsWith(".bit", ignoreCase = true)) {
Nip05Id("_", term.lowercase())
} else {
Nip05Id("_", term.lowercase())
}
}
else -> {
null
}
}
@Stable
@OptIn(FlowPreview::class)
class SearchBarViewModel(
@@ -101,9 +80,6 @@ class SearchBarViewModel(
val invalidations = MutableStateFlow(0)
val searchValueFlow = MutableStateFlow("")
/** Tracks Namecoin resolution status for the UI. */
val namecoinState = MutableStateFlow<NamecoinResolutionState>(NamecoinResolutionState.Idle)
val searchTerm =
searchValueFlow
.debounce(300)
@@ -115,16 +91,27 @@ class SearchBarViewModel(
val listState: LazyListState = LazyListState(0, 0)
/** The user resolved via Namecoin (.bit), if any, for the current search. */
private val _namecoinResolvedUser = MutableStateFlow<User?>(null)
val namecoinResolvedUser: StateFlow<User?> = _namecoinResolvedUser
@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
val directNip05Resolver: Flow<User?> =
searchTerm
.debounce(400)
.mapLatest { term ->
val nip05 = toNip05IdOrNull(term)
// NIP-05 resolution: user@domain or bare .bit domain
val nip05 =
if (term.contains('@')) {
Nip05Id.parse(term)
} else if (term.endsWith(".bit", ignoreCase = true)) {
// Bare .bit domain → synthesize _@domain.bit
Nip05Id("_", term.lowercase())
} else {
null
}
val isNamecoin = nip05 != null && nip05.domain.endsWith(".bit", ignoreCase = true)
if (nip05 != null) {
val isNamecoin = NamecoinNameResolver.isNamecoinIdentifier(nip05.toValue())
if (isNamecoin) namecoinState.emit(NamecoinResolutionState.Resolving)
val user =
runCatching {
nip05Client.get(nip05)?.let { info ->
@@ -139,19 +126,10 @@ class SearchBarViewModel(
u
}
}.getOrNull()
if (isNamecoin) {
if (user != null) {
namecoinState.emit(NamecoinResolutionState.Resolved(user, term))
} else {
namecoinState.emit(NamecoinResolutionState.Error("Could not resolve $term via Namecoin"))
}
} else {
namecoinState.emit(NamecoinResolutionState.Idle)
}
_namecoinResolvedUser.value = if (isNamecoin) user else null
user
} else if (term.startsWithAny(userUriPrefixes)) {
namecoinState.emit(NamecoinResolutionState.Idle)
_namecoinResolvedUser.value = null
runCatching {
Nip19Parser.uriToRoute(term)?.entity?.let { parsed ->
when (parsed) {
@@ -178,10 +156,10 @@ class SearchBarViewModel(
}
}.getOrNull()
} else if (term.length == 64 && Hex.isHex64(term)) {
namecoinState.emit(NamecoinResolutionState.Idle)
_namecoinResolvedUser.value = null
account.cache.getOrCreateUser(term)
} else {
namecoinState.emit(NamecoinResolutionState.Idle)
_namecoinResolvedUser.value = null
null
}
}.flowOn(Dispatchers.IO)
@@ -20,29 +20,20 @@
*/
package com.vitorpamplona.amethyst.ui.screen.loggedIn.search
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.defaultMinSize
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.statusBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -76,7 +67,6 @@ import com.vitorpamplona.amethyst.ui.note.ClearTextIcon
import com.vitorpamplona.amethyst.ui.note.NoteCompose
import com.vitorpamplona.amethyst.ui.note.SearchIcon
import com.vitorpamplona.amethyst.ui.note.UserCompose
import com.vitorpamplona.amethyst.ui.note.creators.userSuggestions.NamecoinResolutionState
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.ChannelName
import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.common.BasicRelaySetupInfoClickableRow
@@ -170,10 +160,7 @@ private fun SearchBar(
}
}
Column {
SearchTextField(searchBarViewModel, Modifier.statusBarsPadding())
SearchNamecoinStatusBanner(searchBarViewModel)
}
SearchTextField(searchBarViewModel, Modifier.statusBarsPadding())
}
@Composable
@@ -229,88 +216,6 @@ private fun SearchTextField(
}
}
@Composable
private fun SearchNamecoinStatusBanner(searchBarViewModel: SearchBarViewModel) {
val ncState by searchBarViewModel.namecoinState.collectAsStateWithLifecycle()
AnimatedVisibility(
visible = ncState !is NamecoinResolutionState.Idle,
enter = fadeIn() + expandVertically(),
exit = fadeOut() + shrinkVertically(),
) {
when (val state = ncState) {
is NamecoinResolutionState.Resolving -> {
Row(
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
CircularProgressIndicator(
modifier = Modifier.size(14.dp),
strokeWidth = 2.dp,
color = MaterialTheme.colorScheme.primary,
)
Spacer(Modifier.width(8.dp))
Text(
"Resolving via Namecoin blockchain\u2026",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary,
)
}
}
is NamecoinResolutionState.Resolved -> {
Row(
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
"\u26D3\uFE0F",
style = MaterialTheme.typography.bodySmall,
)
Spacer(Modifier.width(6.dp))
Text(
"Resolved via Namecoin: ${state.namecoinName}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Medium,
)
}
}
is NamecoinResolutionState.Error -> {
Row(
modifier =
Modifier
.fillMaxWidth()
.padding(horizontal = 14.dp, vertical = 4.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
"\u26A0\uFE0F",
style = MaterialTheme.typography.bodySmall,
)
Spacer(Modifier.width(6.dp))
Text(
state.message,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.error,
)
}
}
is NamecoinResolutionState.Idle -> {
// nothing
}
}
}
}
@Composable
private fun DisplaySearchResults(
searchBarViewModel: SearchBarViewModel,
@@ -324,11 +229,11 @@ private fun DisplaySearchResults(
val hashTags by searchBarViewModel.hashtagResults.collectAsStateWithLifecycle()
val relays by searchBarViewModel.relayResults.collectAsStateWithLifecycle()
val users by searchBarViewModel.searchResultsUsers.collectAsStateWithLifecycle()
val namecoinUser by searchBarViewModel.namecoinResolvedUser.collectAsStateWithLifecycle()
val publicChatChannels by searchBarViewModel.searchResultsPublicChatChannels.collectAsStateWithLifecycle()
val ephemeralChannels by searchBarViewModel.searchResultsEphemeralChannels.collectAsStateWithLifecycle()
val liveActivityChannels by searchBarViewModel.searchResultsLiveActivityChannels.collectAsStateWithLifecycle()
val notes by searchBarViewModel.searchResultsNotes.collectAsStateWithLifecycle()
val ncState by searchBarViewModel.namecoinState.collectAsStateWithLifecycle()
LazyColumn(
modifier = Modifier.fillMaxHeight(),
@@ -351,22 +256,14 @@ private fun DisplaySearchResults(
users,
key = { _, item -> "u" + item.pubkeyHex },
) { _, item ->
val isNamecoinResult =
ncState is NamecoinResolutionState.Resolved &&
(ncState as NamecoinResolutionState.Resolved).user == item
if (isNamecoinResult) {
Row(
if (namecoinUser == item) {
Text(
"\u26D3\uFE0F Namecoin",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Medium,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 2.dp),
verticalAlignment = Alignment.CenterVertically,
) {
Text(
"\u26D3\uFE0F Namecoin result",
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.primary,
fontWeight = FontWeight.Medium,
)
}
)
}
UserCompose(item, accountViewModel = accountViewModel, nav = nav)
+1 -1
View File
@@ -1820,7 +1820,7 @@
<string name="select_users_to_follow">Select Users to Follow</string>
<string name="profile_to_import_from">Profile to import from</string>
<string name="name_search_npub1_alice_example_com">search, npub1…, alice@example.com</string>
<string name="supports_npub_nip_05_hex_and_namecoin_bit_d_id">Supports npub, nprofile, NIP-05, hex, and namecoin (.bit, d/, id/)</string>
<string name="supports_npub_nip_05_hex_and_namecoin_bit_d_id">Supports npub, nprofile, NIP-05, hex, and Namecoin (.bit)</string>
<string name="look_up_follow_list">Look Up Follow List</string>
<string name="tip">Tip</string>
<string name="accounts_found">%1$d accounts found</string>