diff --git a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip11RelayInfoRetriever.kt b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip11RelayInfoRetriever.kt index 020961330..fc5c5a1ff 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/service/Nip11RelayInfoRetriever.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/service/Nip11RelayInfoRetriever.kt @@ -23,6 +23,7 @@ package com.vitorpamplona.amethyst.service import android.util.Log import android.util.LruCache import com.vitorpamplona.quartz.encoders.Nip11RelayInformation +import com.vitorpamplona.quartz.utils.TimeUtils import kotlinx.coroutines.CancellationException import okhttp3.Call import okhttp3.Callback @@ -31,9 +32,21 @@ import okhttp3.Response import java.io.IOException object Nip11CachedRetriever { - val relayInformationDocumentCache = LruCache(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(100) 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( dirtyUrl: String, onInfo: (Nip11RelayInformation) -> Unit, @@ -43,17 +56,40 @@ object Nip11CachedRetriever { val doc = relayInformationDocumentCache.get(url) 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 { + 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) + }, + ) + } + } } else { Nip11Retriever() .loadRelayInfo( - url, - dirtyUrl, + url = url, + dirtyUrl = dirtyUrl, onInfo = { - relayInformationDocumentCache.put(url, it) + relayInformationDocumentCache.put(url, RetrieveResultSuccess(it)) onInfo(it) }, - onError, + onError = { dirtyUrl, code, errorMsg -> + relayInformationDocumentCache.put(url, RetrieveResultError(code, errorMsg)) + onError(url, code, errorMsg) + }, ) } } @@ -81,6 +117,7 @@ class Nip11Retriever { onInfo: (Nip11RelayInformation) -> Unit, onError: (String, ErrorCode, String?) -> Unit, ) { + checkNotInMainThread() try { val request: Request = Request.Builder().header("Accept", "application/nostr+json").url(url).build() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt index 731ced4ca..2e809ae02 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListView.kt @@ -79,6 +79,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.RelayBriefInfoCache import com.vitorpamplona.amethyst.model.RelaySetupInfo +import com.vitorpamplona.amethyst.service.Nip11CachedRetriever import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.service.relays.Constants import com.vitorpamplona.amethyst.service.relays.Constants.defaultRelays @@ -193,8 +194,10 @@ fun NewRelayListView( onToggleSearch = { postViewModel.toggleSearch(it) }, onDelete = { postViewModel.deleteRelay(it) }, accountViewModel = accountViewModel, - nav = nav, - ) + ) { + onClose() + nav(it) + } } } } @@ -410,9 +413,14 @@ fun ServerConfigClickableLine( modifier = Modifier.padding(vertical = 5.dp), ) { Column(Modifier.clickable(onClick = onClick)) { + val iconUrlFromRelayInfoDoc = + remember(item) { + Nip11CachedRetriever.getFromCache(item.url)?.icon + } + RenderRelayIcon( item.briefInfo.displayUrl, - item.briefInfo.favIcon, + iconUrlFromRelayInfoDoc ?: item.briefInfo.favIcon, loadProfilePicture, MaterialTheme.colorScheme.largeRelayIconModifier, ) diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt index eabe2b51a..a86574f67 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewRelayListViewModel.kt @@ -62,7 +62,9 @@ class NewRelayListViewModel : ViewModel() { _relays.value.forEach { item -> Nip11CachedRetriever.loadRelayInfo( dirtyUrl = item.url, - onInfo = { togglePaidRelay(item, it.limitation?.payment_required ?: false) }, + onInfo = { + togglePaidRelay(item, it.limitation?.payment_required ?: false) + }, onError = { url, errorCode, exceptionMessage -> }, ) } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt index c16763361..464d7ec82 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/RelayInformationDialog.kt @@ -104,7 +104,7 @@ fun RelayInformationDialog( Column { RenderRelayIcon( relayBriefInfo.displayUrl, - relayBriefInfo.favIcon, + relayInfo.icon ?: relayBriefInfo.favIcon, automaticallyShowProfilePicture, MaterialTheme.colorScheme.largeRelayIconModifier, ) @@ -121,7 +121,12 @@ fun RelayInformationDialog( 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)) @@ -170,12 +175,14 @@ fun RelayInformationDialog( relayInfo.limitation?.let { Section(stringResource(R.string.limitations)) - val authRequired = it.auth_required ?: false val authRequiredText = - if (authRequired) stringResource(R.string.yes) else stringResource(R.string.no) - val paymentRequired = it.payment_required ?: false + if (it.auth_required ?: false) stringResource(R.string.yes) else stringResource(R.string.no) + 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 { SectionContent( @@ -184,7 +191,7 @@ fun RelayInformationDialog( SectionContent( "${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( "${stringResource(R.string.subscription_id_length)}: ${it.max_subid_length ?: 0}", ) @@ -195,9 +202,13 @@ fun RelayInformationDialog( SectionContent( "${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.auth)}: $authRequiredText") SectionContent("${stringResource(R.string.payment)}: $paymentRequiredText") + SectionContent("${stringResource(R.string.restricted_writes)}: $restrictedWritesText") } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt index 14ce7540f..cac24a089 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/note/RelayListRow.kt @@ -39,6 +39,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -51,6 +52,7 @@ import androidx.lifecycle.map import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.RelayBriefInfoCache +import com.vitorpamplona.amethyst.service.Nip11CachedRetriever import com.vitorpamplona.amethyst.service.Nip11Retriever import com.vitorpamplona.amethyst.ui.actions.RelayInformationDialog 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.placeholderText import com.vitorpamplona.amethyst.ui.theme.relayIconModifier -import com.vitorpamplona.quartz.encoders.Nip11RelayInformation @Composable public fun RelayBadgesHorizontal( @@ -132,11 +133,27 @@ fun RenderRelay( accountViewModel: AccountViewModel, 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( - onClose = { relayInfo = null }, + onClose = { openRelayDialog = false }, relayInfo = relayInfo!!, relayBriefInfo = relay, accountViewModel = accountViewModel, @@ -149,11 +166,6 @@ fun RenderRelay( val interactionSource = remember { MutableInteractionSource() } val ripple = rememberRipple(bounded = false, radius = Size15dp) - val automaticallyShowProfilePicture = - remember { - accountViewModel.settings.showProfilePictures.value - } - val clickableModifier = remember(relay) { Modifier @@ -166,7 +178,9 @@ fun RenderRelay( onClick = { accountViewModel.retrieveRelayDocument( relay.url, - onInfo = { relayInfo = it }, + onInfo = { + openRelayDialog = true + }, onError = { url, errorCode, exceptionMessage -> val msg = when (errorCode) { @@ -213,14 +227,18 @@ fun RenderRelay( modifier = clickableModifier, contentAlignment = Alignment.Center, ) { - RenderRelayIcon(relay.displayUrl, relay.favIcon, automaticallyShowProfilePicture) + RenderRelayIcon( + displayUrl = relay.displayUrl, + iconUrl = relayInfo?.icon ?: relay.favIcon, + loadProfilePicture = accountViewModel.settings.showProfilePictures.value, + ) } } @Composable fun RenderRelayIcon( displayUrl: String, - iconUrl: String, + iconUrl: String?, loadProfilePicture: Boolean, iconModifier: Modifier = MaterialTheme.colorScheme.relayIconModifier, ) { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 24ff0b7f0..5b0e87fa9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -774,4 +774,6 @@ This version was brought to you by: Version %1$s Thank you! + Max Limit + Restricted Writes diff --git a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Nip11RelayInformation.kt b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Nip11RelayInformation.kt index 565729402..58f43b9dc 100644 --- a/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Nip11RelayInformation.kt +++ b/quartz/src/main/java/com/vitorpamplona/quartz/encoders/Nip11RelayInformation.kt @@ -29,6 +29,7 @@ class Nip11RelayInformation( val id: String?, val name: String?, val description: String?, + val icon: String?, val pubkey: String?, val contact: String?, val supported_nips: List?, @@ -41,7 +42,9 @@ class Nip11RelayInformation( val tags: List?, val posting_policy: String?, val payments_url: String?, + val retention: List?, val fees: RelayInformationFees?, + val nip50: List?, ) { companion object { val mapper = @@ -63,7 +66,6 @@ class RelayInformationFees( val admission: List?, val subscription: List?, val publication: List?, - val retention: List?, ) class RelayInformationLimitation( @@ -78,4 +80,13 @@ class RelayInformationLimitation( val min_pow_difficulty: Int?, val auth_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, + val tiem: Int?, + val count: Int?, )