diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt index 7def92c09..5b74e99c5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/AppModules.kt @@ -147,12 +147,14 @@ class AppModules( // Custom fetcher that considers tor settings and avoids forwarding. val nip05Fetcher = OkHttpNip05Fetcher(roleBasedHttpClientBuilder::okHttpClientForNip05) + val namecoinElectrumxClient = + ElectrumXClient( + socketFactory = { roleBasedHttpClientBuilder.socketFactoryForNip05() }, + ) + val namecoinResolver = NamecoinNameResolver( - electrumxClient = - ElectrumXClient( - socketFactory = { roleBasedHttpClientBuilder.socketFactoryForNip05() }, - ), + electrumxClient = namecoinElectrumxClient, serverListProvider = { if (roleBasedHttpClientBuilder.shouldUseTorForNIP05("https://electrumx.example.com")) { TOR_ELECTRUMX_SERVERS diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/namecoin/NamecoinNameService.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/namecoin/NamecoinNameService.kt index 348521b12..5c780e5b4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/namecoin/NamecoinNameService.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/namecoin/NamecoinNameService.kt @@ -20,6 +20,7 @@ */ package com.vitorpamplona.amethyst.service.namecoin +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.DEFAULT_ELECTRUMX_SERVERS import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumXClient import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.ElectrumxServer import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinLookupCache @@ -50,7 +51,7 @@ class NamecoinNameService( private val resolver = NamecoinNameResolver( electrumxClient = electrumxClient, - serverListProvider = { customServers.ifEmpty { ElectrumxClient.DEFAULT_SERVERS } }, + serverListProvider = { customServers.ifEmpty { DEFAULT_ELECTRUMX_SERVERS } }, ) private val cache = NamecoinLookupCache() @@ -63,7 +64,7 @@ class NamecoinNameService( "NamecoinNameService not initialized. Call init() first.", ) - fun init(electrumxClient: ElectrumxClient): NamecoinNameService = + fun init(electrumxClient: ElectrumXClient): NamecoinNameService = synchronized(this) { instance?.let { return it } NamecoinNameService(electrumxClient).also { instance = it } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt index 84ce76f89..942b66df8 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/search/SearchBarViewModel.kt @@ -34,10 +34,9 @@ import com.vitorpamplona.amethyst.commons.ui.feeds.InvalidatableContent import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.User -import com.vitorpamplona.amethyst.service.namecoin.NamecoinNameService -import com.vitorpamplona.quartz.nip05.namecoin.NamecoinLookupException import com.vitorpamplona.amethyst.service.relayClient.searchCommand.SearchQueryState import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrder +import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinLookupException import com.vitorpamplona.quartz.nip05DnsIdentifiers.namecoin.NamecoinNameResolver import com.vitorpamplona.quartz.nip10Notes.content.findHashtags import kotlinx.coroutines.Dispatchers @@ -45,6 +44,7 @@ import kotlinx.coroutines.FlowPreview 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 @@ -83,10 +83,21 @@ class SearchBarViewModel( */ sealed class NamecoinSearchState { data object Idle : NamecoinSearchState() + data object Loading : NamecoinSearchState() - data class Resolved(val user: User) : NamecoinSearchState() - data class NotFound(val name: String) : NamecoinSearchState() - data class Expired(val name: String) : NamecoinSearchState() + + data class Resolved( + val user: User, + ) : NamecoinSearchState() + + data class NotFound( + val name: String, + ) : NamecoinSearchState() + + data class Expired( + val name: String, + ) : NamecoinSearchState() + data object ServersUnreachable : NamecoinSearchState() } @@ -132,8 +143,7 @@ class SearchBarViewModel( is NamecoinSearchState.Resolved -> state.user else -> null } - } - .stateIn(viewModelScope, WhileSubscribed(5000), null) + }.stateIn(viewModelScope, WhileSubscribed(5000), null) val searchResultsUsers = combine( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ReactionsSettingsScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ReactionsSettingsScreen.kt index 968b6beff..15b5e583c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ReactionsSettingsScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/settings/ReactionsSettingsScreen.kt @@ -238,16 +238,14 @@ private fun ReactionRowItemCard( .fillMaxWidth() .onGloballyPositioned { coordinates -> onMeasured(coordinates.size.height.toFloat()) - } - .graphicsLayer { + }.graphicsLayer { translationY = dragOffsetY shadowElevation = elevation if (isDragging) { scaleX = 1.02f scaleY = 1.02f } - } - .padding(vertical = 8.dp), + }.padding(vertical = 8.dp), ) { Row( modifier = Modifier.fillMaxWidth(), diff --git a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/ElectrumXServer.kt b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/ElectrumXServer.kt index 67e093e07..f31edfe7d 100644 --- a/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/ElectrumXServer.kt +++ b/quartz/src/commonMain/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/ElectrumXServer.kt @@ -48,6 +48,30 @@ data class ElectrumxServer( val trustAllCerts: Boolean = false, ) +/** + * Specific exception types for Namecoin resolution failures. + * Allows callers to distinguish "name doesn't exist" from "servers unreachable". + */ +sealed class NamecoinLookupException( + message: String, + cause: Throwable? = null, +) : Exception(message, cause) { + /** The name was queried successfully but does not exist on the blockchain. */ + class NameNotFound( + val name: String, + ) : NamecoinLookupException("Name not found: $name") + + /** The name has expired (>36000 blocks since last update). */ + class NameExpired( + val name: String, + ) : NamecoinLookupException("Name expired: $name") + + /** All ElectrumX servers were unreachable or returned errors. */ + class ServersUnreachable( + val lastError: Throwable? = null, + ) : NamecoinLookupException("All ElectrumX servers unreachable", lastError) +} + /** Well-known public Namecoin ElectrumX servers (clearnet). */ val DEFAULT_ELECTRUMX_SERVERS = listOf( diff --git a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/ElectrumXClient.kt b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/ElectrumXClient.kt index 54486b552..c56dd72b1 100644 --- a/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/ElectrumXClient.kt +++ b/quartz/src/jvmAndroid/kotlin/com/vitorpamplona/quartz/nip05DnsIdentifiers/namecoin/ElectrumXClient.kt @@ -52,48 +52,6 @@ import javax.net.ssl.SSLSocketFactory import javax.net.ssl.TrustManager import javax.net.ssl.X509TrustManager -/** - * Result of an ElectrumX name_show query. - * - * Maps to the JSON fields returned by Namecoin Core / Electrum-NMC: - * { "name": "d/example", "value": "{...}", "txid": "abc...", "height": 12345, ... } - */ -@Serializable -data class NameShowResult( - val name: String, - val value: String, - val txid: String? = null, - val height: Int? = null, - val expiresIn: Int? = null, -) - -/** - * Specific exception types for Namecoin resolution failures. - * Allows callers to distinguish "name doesn't exist" from "servers unreachable". - */ -sealed class NamecoinLookupException(message: String, cause: Throwable? = null) : Exception(message, cause) { - /** The name was queried successfully but does not exist on the blockchain. */ - class NameNotFound(val name: String) : NamecoinLookupException("Name not found: $name") - - /** The name has expired (>36000 blocks since last update). */ - class NameExpired(val name: String) : NamecoinLookupException("Name expired: $name") - - /** All ElectrumX servers were unreachable or returned errors. */ - class ServersUnreachable(val lastError: Throwable? = null) : - NamecoinLookupException("All ElectrumX servers unreachable", lastError) -} - -/** - * Represents a single ElectrumX server endpoint. - */ -data class ElectrumxServer( - val host: String, - val port: Int, - val useSsl: Boolean = true, - /** If true, accept any certificate (self-signed, expired, etc.) */ - val trustAllCerts: Boolean = false, -) - /** * Lightweight, query-only ElectrumX client for Namecoin name resolution. * @@ -184,10 +142,6 @@ class ElectrumXClient( * * @param identifier Full Namecoin name, e.g. "d/example" * @param servers Ordered server list to try; defaults to [DEFAULT_ELECTRUMX_SERVERS] - */ - /** - * Try each server in order until one succeeds. - * * @throws NamecoinLookupException.NameNotFound if the name definitively doesn't exist * @throws NamecoinLookupException.NameExpired if the name has expired * @throws NamecoinLookupException.ServersUnreachable if all servers failed with connection errors