mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2026-08-09 08:04:45 +00:00
feat: resolve bare .bit domains and show Namecoin resolution status in UI
Widen the existing NIP-05 resolution gate in UserSuggestionState and SearchBarViewModel to also accept bare .bit domains and d//id/ Namecoin identifiers. A synthesised Nip05Id is passed to the existing nip05Client.get() path — which already routes .bit to the NamecoinNameResolver — so no separate resolution logic is needed. Add NamecoinResolutionState (Idle/Resolving/Resolved/Error) as a StateFlow so the UI can show progress. ImportFollowListSelectUserScreen and SearchScreen display an animated status banner (spinner while resolving, chain emoji on success, warning on error) and label Namecoin-resolved profiles in the results list.
This commit is contained in:
+68
-8
@@ -31,6 +31,7 @@ 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
|
||||
@@ -57,6 +58,44 @@ 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,
|
||||
@@ -66,6 +105,9 @@ 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
|
||||
@@ -82,23 +124,38 @@ class UserSuggestionState(
|
||||
.map(::userSearchTermOrNull)
|
||||
.map { prefix ->
|
||||
if (prefix != null) {
|
||||
if (prefix.contains('@')) {
|
||||
runCatching {
|
||||
Nip05Id.parse(prefix)?.let { nip05 ->
|
||||
val nip05 = toNip05IdOrNull(prefix)
|
||||
if (nip05 != null) {
|
||||
val isNamecoin = NamecoinNameResolver.isNamecoinIdentifier(nip05.toValue())
|
||||
if (isNamecoin) namecoinState.emit(NamecoinResolutionState.Resolving)
|
||||
|
||||
val user =
|
||||
runCatching {
|
||||
nip05Client.get(nip05)?.let { info ->
|
||||
val user = account.cache.checkGetOrCreateUser(info.pubkey)
|
||||
if (user != null) {
|
||||
val u = account.cache.checkGetOrCreateUser(info.pubkey)
|
||||
if (u != null) {
|
||||
info.relays.forEach {
|
||||
it.normalizeRelayUrlOrNull()?.let { relay ->
|
||||
account.cache.relayHints.addKey(user.pubkey(), relay)
|
||||
account.cache.relayHints.addKey(u.pubkey(), relay)
|
||||
}
|
||||
}
|
||||
}
|
||||
user
|
||||
u
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
if (isNamecoin) {
|
||||
if (user != null) {
|
||||
namecoinState.emit(NamecoinResolutionState.Resolved(user, prefix))
|
||||
} else {
|
||||
namecoinState.emit(NamecoinResolutionState.Error("Could not resolve $prefix via Namecoin"))
|
||||
}
|
||||
}.getOrNull()
|
||||
} else {
|
||||
namecoinState.emit(NamecoinResolutionState.Idle)
|
||||
}
|
||||
user
|
||||
} else if (prefix.startsWithAny(userUriPrefixes)) {
|
||||
namecoinState.emit(NamecoinResolutionState.Idle)
|
||||
runCatching {
|
||||
Nip19Parser.uriToRoute(prefix)?.entity?.let { parsed ->
|
||||
when (parsed) {
|
||||
@@ -125,11 +182,14 @@ 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)
|
||||
|
||||
+120
-1
@@ -20,6 +20,11 @@
|
||||
*/
|
||||
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
|
||||
@@ -37,6 +42,7 @@ 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
|
||||
@@ -67,6 +73,7 @@ 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
|
||||
@@ -100,6 +107,10 @@ 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,
|
||||
@@ -166,6 +177,10 @@ 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(
|
||||
@@ -185,6 +200,88 @@ 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 {
|
||||
@@ -244,6 +341,7 @@ fun CustomWatchResponses(
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val suggestions by viewModel.results.collectAsStateWithLifecycle()
|
||||
val ncState by viewModel.namecoinState.collectAsStateWithLifecycle()
|
||||
|
||||
if (suggestions.isNotEmpty()) {
|
||||
LazyColumn(
|
||||
@@ -252,7 +350,28 @@ fun CustomWatchResponses(
|
||||
state = viewModel.listState,
|
||||
) {
|
||||
itemsIndexed(suggestions, key = { _, item -> item.pubkeyHex }) { _, item ->
|
||||
UserLine(item, accountViewModel) { onSelect(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) }
|
||||
}
|
||||
HorizontalDivider(
|
||||
thickness = DividerThickness,
|
||||
)
|
||||
|
||||
+50
-8
@@ -36,6 +36,7 @@ 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
|
||||
@@ -43,6 +44,7 @@ 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
|
||||
@@ -66,6 +68,26 @@ 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(
|
||||
@@ -79,6 +101,9 @@ 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)
|
||||
@@ -95,23 +120,38 @@ class SearchBarViewModel(
|
||||
searchTerm
|
||||
.debounce(400)
|
||||
.mapLatest { term ->
|
||||
if (term.contains('@')) {
|
||||
runCatching {
|
||||
Nip05Id.parse(term)?.let { nip05 ->
|
||||
val nip05 = toNip05IdOrNull(term)
|
||||
if (nip05 != null) {
|
||||
val isNamecoin = NamecoinNameResolver.isNamecoinIdentifier(nip05.toValue())
|
||||
if (isNamecoin) namecoinState.emit(NamecoinResolutionState.Resolving)
|
||||
|
||||
val user =
|
||||
runCatching {
|
||||
nip05Client.get(nip05)?.let { info ->
|
||||
val user = account.cache.checkGetOrCreateUser(info.pubkey)
|
||||
if (user != null) {
|
||||
val u = account.cache.checkGetOrCreateUser(info.pubkey)
|
||||
if (u != null) {
|
||||
info.relays.forEach {
|
||||
it.normalizeRelayUrlOrNull()?.let { relay ->
|
||||
account.cache.relayHints.addKey(user.pubkey(), relay)
|
||||
account.cache.relayHints.addKey(u.pubkey(), relay)
|
||||
}
|
||||
}
|
||||
}
|
||||
user
|
||||
u
|
||||
}
|
||||
}.getOrNull()
|
||||
|
||||
if (isNamecoin) {
|
||||
if (user != null) {
|
||||
namecoinState.emit(NamecoinResolutionState.Resolved(user, term))
|
||||
} else {
|
||||
namecoinState.emit(NamecoinResolutionState.Error("Could not resolve $term via Namecoin"))
|
||||
}
|
||||
}.getOrNull()
|
||||
} else {
|
||||
namecoinState.emit(NamecoinResolutionState.Idle)
|
||||
}
|
||||
user
|
||||
} else if (term.startsWithAny(userUriPrefixes)) {
|
||||
namecoinState.emit(NamecoinResolutionState.Idle)
|
||||
runCatching {
|
||||
Nip19Parser.uriToRoute(term)?.entity?.let { parsed ->
|
||||
when (parsed) {
|
||||
@@ -138,8 +178,10 @@ class SearchBarViewModel(
|
||||
}
|
||||
}.getOrNull()
|
||||
} else if (term.length == 64 && Hex.isHex64(term)) {
|
||||
namecoinState.emit(NamecoinResolutionState.Idle)
|
||||
account.cache.getOrCreateUser(term)
|
||||
} else {
|
||||
namecoinState.emit(NamecoinResolutionState.Idle)
|
||||
null
|
||||
}
|
||||
}.flowOn(Dispatchers.IO)
|
||||
|
||||
+115
-1
@@ -20,20 +20,29 @@
|
||||
*/
|
||||
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
|
||||
@@ -67,6 +76,7 @@ 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
|
||||
@@ -160,7 +170,10 @@ private fun SearchBar(
|
||||
}
|
||||
}
|
||||
|
||||
SearchTextField(searchBarViewModel, Modifier.statusBarsPadding())
|
||||
Column {
|
||||
SearchTextField(searchBarViewModel, Modifier.statusBarsPadding())
|
||||
SearchNamecoinStatusBanner(searchBarViewModel)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
@@ -216,6 +229,88 @@ 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,
|
||||
@@ -233,6 +328,7 @@ private fun DisplaySearchResults(
|
||||
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(),
|
||||
@@ -255,6 +351,24 @@ 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(
|
||||
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)
|
||||
|
||||
HorizontalDivider(
|
||||
|
||||
Reference in New Issue
Block a user