mirror of
https://github.com/vitorpamplona/amethyst.git
synced 2025-11-10 23:56:43 +01:00
Improves relay list NIP-11 caching, updates NIP-11 properties in the document and adds icon rendering from the NIP11 document.
This commit is contained in:
@@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.service
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import com.vitorpamplona.quartz.encoders.Nip11RelayInformation
|
import com.vitorpamplona.quartz.encoders.Nip11RelayInformation
|
||||||
|
import com.vitorpamplona.quartz.utils.TimeUtils
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import okhttp3.Call
|
import okhttp3.Call
|
||||||
import okhttp3.Callback
|
import okhttp3.Callback
|
||||||
@@ -31,9 +32,21 @@ import okhttp3.Response
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
|
||||||
object Nip11CachedRetriever {
|
object Nip11CachedRetriever {
|
||||||
val relayInformationDocumentCache = LruCache<String, Nip11RelayInformation>(100)
|
open class RetrieveResult(val time: Long)
|
||||||
|
|
||||||
|
class RetrieveResultError(val error: Nip11Retriever.ErrorCode, val msg: String? = null) : RetrieveResult(TimeUtils.now())
|
||||||
|
|
||||||
|
class RetrieveResultSuccess(val data: Nip11RelayInformation) : RetrieveResult(TimeUtils.now())
|
||||||
|
|
||||||
|
val relayInformationDocumentCache = LruCache<String, RetrieveResult?>(100)
|
||||||
val retriever = Nip11Retriever()
|
val retriever = Nip11Retriever()
|
||||||
|
|
||||||
|
fun getFromCache(dirtyUrl: String): Nip11RelayInformation? {
|
||||||
|
val result = relayInformationDocumentCache.get(retriever.cleanUrl(dirtyUrl)) ?: return null
|
||||||
|
if (result is RetrieveResultSuccess) return result.data
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun loadRelayInfo(
|
suspend fun loadRelayInfo(
|
||||||
dirtyUrl: String,
|
dirtyUrl: String,
|
||||||
onInfo: (Nip11RelayInformation) -> Unit,
|
onInfo: (Nip11RelayInformation) -> Unit,
|
||||||
@@ -43,17 +56,40 @@ object Nip11CachedRetriever {
|
|||||||
val doc = relayInformationDocumentCache.get(url)
|
val doc = relayInformationDocumentCache.get(url)
|
||||||
|
|
||||||
if (doc != null) {
|
if (doc != null) {
|
||||||
onInfo(doc)
|
if (doc is RetrieveResultSuccess) {
|
||||||
|
onInfo(doc.data)
|
||||||
|
} else if (doc is RetrieveResultError) {
|
||||||
|
if (TimeUtils.now() - doc.time < TimeUtils.ONE_HOUR) {
|
||||||
|
onError(dirtyUrl, doc.error, null)
|
||||||
} else {
|
} else {
|
||||||
Nip11Retriever()
|
Nip11Retriever()
|
||||||
.loadRelayInfo(
|
.loadRelayInfo(
|
||||||
url,
|
url = url,
|
||||||
dirtyUrl,
|
dirtyUrl = dirtyUrl,
|
||||||
onInfo = {
|
onInfo = {
|
||||||
relayInformationDocumentCache.put(url, it)
|
relayInformationDocumentCache.put(url, RetrieveResultSuccess(it))
|
||||||
onInfo(it)
|
onInfo(it)
|
||||||
},
|
},
|
||||||
onError,
|
onError = { dirtyUrl, code, errorMsg ->
|
||||||
|
relayInformationDocumentCache.put(url, RetrieveResultError(code, errorMsg))
|
||||||
|
onError(url, code, errorMsg)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Nip11Retriever()
|
||||||
|
.loadRelayInfo(
|
||||||
|
url = url,
|
||||||
|
dirtyUrl = dirtyUrl,
|
||||||
|
onInfo = {
|
||||||
|
relayInformationDocumentCache.put(url, RetrieveResultSuccess(it))
|
||||||
|
onInfo(it)
|
||||||
|
},
|
||||||
|
onError = { dirtyUrl, code, errorMsg ->
|
||||||
|
relayInformationDocumentCache.put(url, RetrieveResultError(code, errorMsg))
|
||||||
|
onError(url, code, errorMsg)
|
||||||
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,6 +117,7 @@ class Nip11Retriever {
|
|||||||
onInfo: (Nip11RelayInformation) -> Unit,
|
onInfo: (Nip11RelayInformation) -> Unit,
|
||||||
onError: (String, ErrorCode, String?) -> Unit,
|
onError: (String, ErrorCode, String?) -> Unit,
|
||||||
) {
|
) {
|
||||||
|
checkNotInMainThread()
|
||||||
try {
|
try {
|
||||||
val request: Request =
|
val request: Request =
|
||||||
Request.Builder().header("Accept", "application/nostr+json").url(url).build()
|
Request.Builder().header("Accept", "application/nostr+json").url(url).build()
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
|
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
|
||||||
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
import com.vitorpamplona.amethyst.model.RelaySetupInfo
|
||||||
|
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||||
import com.vitorpamplona.amethyst.service.relays.Constants
|
import com.vitorpamplona.amethyst.service.relays.Constants
|
||||||
import com.vitorpamplona.amethyst.service.relays.Constants.defaultRelays
|
import com.vitorpamplona.amethyst.service.relays.Constants.defaultRelays
|
||||||
@@ -193,8 +194,10 @@ fun NewRelayListView(
|
|||||||
onToggleSearch = { postViewModel.toggleSearch(it) },
|
onToggleSearch = { postViewModel.toggleSearch(it) },
|
||||||
onDelete = { postViewModel.deleteRelay(it) },
|
onDelete = { postViewModel.deleteRelay(it) },
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
nav = nav,
|
) {
|
||||||
)
|
onClose()
|
||||||
|
nav(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -410,9 +413,14 @@ fun ServerConfigClickableLine(
|
|||||||
modifier = Modifier.padding(vertical = 5.dp),
|
modifier = Modifier.padding(vertical = 5.dp),
|
||||||
) {
|
) {
|
||||||
Column(Modifier.clickable(onClick = onClick)) {
|
Column(Modifier.clickable(onClick = onClick)) {
|
||||||
|
val iconUrlFromRelayInfoDoc =
|
||||||
|
remember(item) {
|
||||||
|
Nip11CachedRetriever.getFromCache(item.url)?.icon
|
||||||
|
}
|
||||||
|
|
||||||
RenderRelayIcon(
|
RenderRelayIcon(
|
||||||
item.briefInfo.displayUrl,
|
item.briefInfo.displayUrl,
|
||||||
item.briefInfo.favIcon,
|
iconUrlFromRelayInfoDoc ?: item.briefInfo.favIcon,
|
||||||
loadProfilePicture,
|
loadProfilePicture,
|
||||||
MaterialTheme.colorScheme.largeRelayIconModifier,
|
MaterialTheme.colorScheme.largeRelayIconModifier,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -62,7 +62,9 @@ class NewRelayListViewModel : ViewModel() {
|
|||||||
_relays.value.forEach { item ->
|
_relays.value.forEach { item ->
|
||||||
Nip11CachedRetriever.loadRelayInfo(
|
Nip11CachedRetriever.loadRelayInfo(
|
||||||
dirtyUrl = item.url,
|
dirtyUrl = item.url,
|
||||||
onInfo = { togglePaidRelay(item, it.limitation?.payment_required ?: false) },
|
onInfo = {
|
||||||
|
togglePaidRelay(item, it.limitation?.payment_required ?: false)
|
||||||
|
},
|
||||||
onError = { url, errorCode, exceptionMessage -> },
|
onError = { url, errorCode, exceptionMessage -> },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,7 @@ fun RelayInformationDialog(
|
|||||||
Column {
|
Column {
|
||||||
RenderRelayIcon(
|
RenderRelayIcon(
|
||||||
relayBriefInfo.displayUrl,
|
relayBriefInfo.displayUrl,
|
||||||
relayBriefInfo.favIcon,
|
relayInfo.icon ?: relayBriefInfo.favIcon,
|
||||||
automaticallyShowProfilePicture,
|
automaticallyShowProfilePicture,
|
||||||
MaterialTheme.colorScheme.largeRelayIconModifier,
|
MaterialTheme.colorScheme.largeRelayIconModifier,
|
||||||
)
|
)
|
||||||
@@ -121,7 +121,12 @@ fun RelayInformationDialog(
|
|||||||
|
|
||||||
Section(stringResource(R.string.owner))
|
Section(stringResource(R.string.owner))
|
||||||
|
|
||||||
relayInfo.pubkey?.let { DisplayOwnerInformation(it, accountViewModel, nav) }
|
relayInfo.pubkey?.let {
|
||||||
|
DisplayOwnerInformation(it, accountViewModel) {
|
||||||
|
onClose()
|
||||||
|
nav(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Section(stringResource(R.string.software))
|
Section(stringResource(R.string.software))
|
||||||
|
|
||||||
@@ -170,12 +175,14 @@ fun RelayInformationDialog(
|
|||||||
|
|
||||||
relayInfo.limitation?.let {
|
relayInfo.limitation?.let {
|
||||||
Section(stringResource(R.string.limitations))
|
Section(stringResource(R.string.limitations))
|
||||||
val authRequired = it.auth_required ?: false
|
|
||||||
val authRequiredText =
|
val authRequiredText =
|
||||||
if (authRequired) stringResource(R.string.yes) else stringResource(R.string.no)
|
if (it.auth_required ?: false) stringResource(R.string.yes) else stringResource(R.string.no)
|
||||||
val paymentRequired = it.payment_required ?: false
|
|
||||||
val paymentRequiredText =
|
val paymentRequiredText =
|
||||||
if (paymentRequired) stringResource(R.string.yes) else stringResource(R.string.no)
|
if (it.payment_required ?: false) stringResource(R.string.yes) else stringResource(R.string.no)
|
||||||
|
|
||||||
|
val restrictedWritesText =
|
||||||
|
if (it.restricted_writes ?: false) stringResource(R.string.yes) else stringResource(R.string.no)
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
SectionContent(
|
SectionContent(
|
||||||
@@ -184,7 +191,7 @@ fun RelayInformationDialog(
|
|||||||
SectionContent(
|
SectionContent(
|
||||||
"${stringResource(R.string.subscriptions)}: ${it.max_subscriptions ?: 0}",
|
"${stringResource(R.string.subscriptions)}: ${it.max_subscriptions ?: 0}",
|
||||||
)
|
)
|
||||||
SectionContent("${stringResource(R.string.filters)}: ${it.max_subscriptions ?: 0}")
|
SectionContent("${stringResource(R.string.filters)}: ${it.max_filters ?: 0}")
|
||||||
SectionContent(
|
SectionContent(
|
||||||
"${stringResource(R.string.subscription_id_length)}: ${it.max_subid_length ?: 0}",
|
"${stringResource(R.string.subscription_id_length)}: ${it.max_subid_length ?: 0}",
|
||||||
)
|
)
|
||||||
@@ -195,9 +202,13 @@ fun RelayInformationDialog(
|
|||||||
SectionContent(
|
SectionContent(
|
||||||
"${stringResource(R.string.content_length)}: ${it.max_content_length ?: 0}",
|
"${stringResource(R.string.content_length)}: ${it.max_content_length ?: 0}",
|
||||||
)
|
)
|
||||||
|
SectionContent(
|
||||||
|
"${stringResource(R.string.max_limit)}: ${it.max_limit ?: 0}",
|
||||||
|
)
|
||||||
SectionContent("${stringResource(R.string.minimum_pow)}: ${it.min_pow_difficulty ?: 0}")
|
SectionContent("${stringResource(R.string.minimum_pow)}: ${it.min_pow_difficulty ?: 0}")
|
||||||
SectionContent("${stringResource(R.string.auth)}: $authRequiredText")
|
SectionContent("${stringResource(R.string.auth)}: $authRequiredText")
|
||||||
SectionContent("${stringResource(R.string.payment)}: $paymentRequiredText")
|
SectionContent("${stringResource(R.string.payment)}: $paymentRequiredText")
|
||||||
|
SectionContent("${stringResource(R.string.restricted_writes)}: $restrictedWritesText")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import androidx.compose.runtime.MutableState
|
|||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.livedata.observeAsState
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.produceState
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@@ -51,6 +52,7 @@ import androidx.lifecycle.map
|
|||||||
import com.vitorpamplona.amethyst.R
|
import com.vitorpamplona.amethyst.R
|
||||||
import com.vitorpamplona.amethyst.model.Note
|
import com.vitorpamplona.amethyst.model.Note
|
||||||
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
|
import com.vitorpamplona.amethyst.model.RelayBriefInfoCache
|
||||||
|
import com.vitorpamplona.amethyst.service.Nip11CachedRetriever
|
||||||
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
import com.vitorpamplona.amethyst.service.Nip11Retriever
|
||||||
import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog
|
import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog
|
||||||
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage
|
||||||
@@ -61,7 +63,6 @@ import com.vitorpamplona.amethyst.ui.theme.Size15dp
|
|||||||
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
import com.vitorpamplona.amethyst.ui.theme.StdStartPadding
|
||||||
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
import com.vitorpamplona.amethyst.ui.theme.placeholderText
|
||||||
import com.vitorpamplona.amethyst.ui.theme.relayIconModifier
|
import com.vitorpamplona.amethyst.ui.theme.relayIconModifier
|
||||||
import com.vitorpamplona.quartz.encoders.Nip11RelayInformation
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
public fun RelayBadgesHorizontal(
|
public fun RelayBadgesHorizontal(
|
||||||
@@ -132,11 +133,27 @@ fun RenderRelay(
|
|||||||
accountViewModel: AccountViewModel,
|
accountViewModel: AccountViewModel,
|
||||||
nav: (String) -> Unit,
|
nav: (String) -> Unit,
|
||||||
) {
|
) {
|
||||||
var relayInfo: Nip11RelayInformation? by remember { mutableStateOf(null) }
|
val relayInfo by
|
||||||
|
produceState(
|
||||||
|
initialValue = Nip11CachedRetriever.getFromCache(relay.url),
|
||||||
|
) {
|
||||||
|
if (value == null) {
|
||||||
|
accountViewModel.retrieveRelayDocument(
|
||||||
|
relay.url,
|
||||||
|
onInfo = {
|
||||||
|
value = it
|
||||||
|
},
|
||||||
|
onError = { url, errorCode, exceptionMessage ->
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (relayInfo != null) {
|
var openRelayDialog by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
if (openRelayDialog && relayInfo != null) {
|
||||||
RelayInformationDialog(
|
RelayInformationDialog(
|
||||||
onClose = { relayInfo = null },
|
onClose = { openRelayDialog = false },
|
||||||
relayInfo = relayInfo!!,
|
relayInfo = relayInfo!!,
|
||||||
relayBriefInfo = relay,
|
relayBriefInfo = relay,
|
||||||
accountViewModel = accountViewModel,
|
accountViewModel = accountViewModel,
|
||||||
@@ -149,11 +166,6 @@ fun RenderRelay(
|
|||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
val ripple = rememberRipple(bounded = false, radius = Size15dp)
|
val ripple = rememberRipple(bounded = false, radius = Size15dp)
|
||||||
|
|
||||||
val automaticallyShowProfilePicture =
|
|
||||||
remember {
|
|
||||||
accountViewModel.settings.showProfilePictures.value
|
|
||||||
}
|
|
||||||
|
|
||||||
val clickableModifier =
|
val clickableModifier =
|
||||||
remember(relay) {
|
remember(relay) {
|
||||||
Modifier
|
Modifier
|
||||||
@@ -166,7 +178,9 @@ fun RenderRelay(
|
|||||||
onClick = {
|
onClick = {
|
||||||
accountViewModel.retrieveRelayDocument(
|
accountViewModel.retrieveRelayDocument(
|
||||||
relay.url,
|
relay.url,
|
||||||
onInfo = { relayInfo = it },
|
onInfo = {
|
||||||
|
openRelayDialog = true
|
||||||
|
},
|
||||||
onError = { url, errorCode, exceptionMessage ->
|
onError = { url, errorCode, exceptionMessage ->
|
||||||
val msg =
|
val msg =
|
||||||
when (errorCode) {
|
when (errorCode) {
|
||||||
@@ -213,14 +227,18 @@ fun RenderRelay(
|
|||||||
modifier = clickableModifier,
|
modifier = clickableModifier,
|
||||||
contentAlignment = Alignment.Center,
|
contentAlignment = Alignment.Center,
|
||||||
) {
|
) {
|
||||||
RenderRelayIcon(relay.displayUrl, relay.favIcon, automaticallyShowProfilePicture)
|
RenderRelayIcon(
|
||||||
|
displayUrl = relay.displayUrl,
|
||||||
|
iconUrl = relayInfo?.icon ?: relay.favIcon,
|
||||||
|
loadProfilePicture = accountViewModel.settings.showProfilePictures.value,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun RenderRelayIcon(
|
fun RenderRelayIcon(
|
||||||
displayUrl: String,
|
displayUrl: String,
|
||||||
iconUrl: String,
|
iconUrl: String?,
|
||||||
loadProfilePicture: Boolean,
|
loadProfilePicture: Boolean,
|
||||||
iconModifier: Modifier = MaterialTheme.colorScheme.relayIconModifier,
|
iconModifier: Modifier = MaterialTheme.colorScheme.relayIconModifier,
|
||||||
) {
|
) {
|
||||||
|
|||||||
@@ -774,4 +774,6 @@
|
|||||||
<string name="this_version_brought_to_you_by">This version was brought to you by:</string>
|
<string name="this_version_brought_to_you_by">This version was brought to you by:</string>
|
||||||
<string name="version_name">Version %1$s</string>
|
<string name="version_name">Version %1$s</string>
|
||||||
<string name="thank_you">Thank you!</string>
|
<string name="thank_you">Thank you!</string>
|
||||||
|
<string name="max_limit">Max Limit</string>
|
||||||
|
<string name="restricted_writes">Restricted Writes</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ class Nip11RelayInformation(
|
|||||||
val id: String?,
|
val id: String?,
|
||||||
val name: String?,
|
val name: String?,
|
||||||
val description: String?,
|
val description: String?,
|
||||||
|
val icon: String?,
|
||||||
val pubkey: String?,
|
val pubkey: String?,
|
||||||
val contact: String?,
|
val contact: String?,
|
||||||
val supported_nips: List<Int>?,
|
val supported_nips: List<Int>?,
|
||||||
@@ -41,7 +42,9 @@ class Nip11RelayInformation(
|
|||||||
val tags: List<String>?,
|
val tags: List<String>?,
|
||||||
val posting_policy: String?,
|
val posting_policy: String?,
|
||||||
val payments_url: String?,
|
val payments_url: String?,
|
||||||
|
val retention: List<RelayInformationRetentionData>?,
|
||||||
val fees: RelayInformationFees?,
|
val fees: RelayInformationFees?,
|
||||||
|
val nip50: List<String>?,
|
||||||
) {
|
) {
|
||||||
companion object {
|
companion object {
|
||||||
val mapper =
|
val mapper =
|
||||||
@@ -63,7 +66,6 @@ class RelayInformationFees(
|
|||||||
val admission: List<RelayInformationFee>?,
|
val admission: List<RelayInformationFee>?,
|
||||||
val subscription: List<RelayInformationFee>?,
|
val subscription: List<RelayInformationFee>?,
|
||||||
val publication: List<RelayInformationFee>?,
|
val publication: List<RelayInformationFee>?,
|
||||||
val retention: List<RelayInformationFee>?,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
class RelayInformationLimitation(
|
class RelayInformationLimitation(
|
||||||
@@ -78,4 +80,13 @@ class RelayInformationLimitation(
|
|||||||
val min_pow_difficulty: Int?,
|
val min_pow_difficulty: Int?,
|
||||||
val auth_required: Boolean?,
|
val auth_required: Boolean?,
|
||||||
val payment_required: Boolean?,
|
val payment_required: Boolean?,
|
||||||
|
val restricted_writes: Boolean?,
|
||||||
|
val created_at_lower_limit: Int?,
|
||||||
|
val created_at_upper_limit: Int?,
|
||||||
|
)
|
||||||
|
|
||||||
|
class RelayInformationRetentionData(
|
||||||
|
val kinds: ArrayList<Int>,
|
||||||
|
val tiem: Int?,
|
||||||
|
val count: Int?,
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user