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:
+1
-59
@@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
+18
-71
@@ -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)
|
||||
|
||||
+1
-120
@@ -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,
|
||||
)
|
||||
|
||||
+20
-42
@@ -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)
|
||||
|
||||
+9
-112
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user