diff --git a/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt b/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt index 09e945e2b..b5063e67c 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/model/AntiSpamFilter.kt @@ -79,7 +79,7 @@ class AntiSpamFilter { logOffender(hash, event) if (relay != null) { - RelayStats.newSpam(relay.url, "Potential SPAM Message ${event.id} nostr:${Nip19Bech32.createNEvent(event.id, event.pubKey, event.kind, relay.url)}") + RelayStats.newSpam(relay.url, "https://njump.me/${Nip19Bech32.createNEvent(event.id, event.pubKey, event.kind, relay.url)}") } liveSpam.invalidateData() diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt index 780d7d271..7ccc0cc44 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/actions/relays/RelayInformationDialog.kt @@ -31,15 +31,18 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp @@ -48,18 +51,24 @@ import androidx.compose.ui.window.Dialog import androidx.compose.ui.window.DialogProperties import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.RelayBriefInfoCache +import com.vitorpamplona.amethyst.service.relays.RelayStats import com.vitorpamplona.amethyst.ui.actions.CloseButton import com.vitorpamplona.amethyst.ui.components.ClickableEmail import com.vitorpamplona.amethyst.ui.components.ClickableUrl +import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.note.LoadUser import com.vitorpamplona.amethyst.ui.note.RenderRelayIcon import com.vitorpamplona.amethyst.ui.note.UserCompose +import com.vitorpamplona.amethyst.ui.note.timeAgo import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.StdPadding +import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.largeRelayIconModifier import com.vitorpamplona.quartz.encoders.Nip11RelayInformation +import com.vitorpamplona.quartz.events.EmptyTagList +import kotlinx.collections.immutable.toImmutableList @OptIn(ExperimentalLayoutApi::class) @Composable @@ -70,6 +79,11 @@ fun RelayInformationDialog( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { + val messages = + remember(relayBriefInfo) { + RelayStats.get(url = relayBriefInfo.url).messages.snapshot().values.sortedByDescending { it.time }.toImmutableList() + } + val automaticallyShowProfilePicture = remember { accountViewModel.settings.showProfilePictures.value @@ -84,162 +98,204 @@ fun RelayInformationDialog( ), ) { Surface { - val scrollState = rememberScrollState() + val color = mutableStateOf(Color.Transparent) + val context = LocalContext.current - Column( - modifier = Modifier.padding(10.dp).fillMaxSize().verticalScroll(scrollState), + LazyColumn( + modifier = + Modifier + .padding(10.dp) + .fillMaxSize(), ) { - Row( - modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - ) { - CloseButton(onPress = { onClose() }) - } - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - modifier = StdPadding.fillMaxWidth(), - ) { - Column { - RenderRelayIcon( - relayBriefInfo.displayUrl, - relayInfo.icon ?: relayBriefInfo.favIcon, - automaticallyShowProfilePicture, - MaterialTheme.colorScheme.largeRelayIconModifier, - ) - } - - Spacer(modifier = DoubleHorzSpacer) - - Column(horizontalAlignment = Alignment.CenterHorizontally) { - Row { Title(relayInfo.name?.trim() ?: "") } - - Row { SubtitleContent(relayInfo.description?.trim() ?: "") } + item { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically, + ) { + CloseButton(onPress = { onClose() }) } } + item { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center, + modifier = StdPadding.fillMaxWidth(), + ) { + Column { + RenderRelayIcon( + relayBriefInfo.displayUrl, + relayInfo.icon ?: relayBriefInfo.favIcon, + automaticallyShowProfilePicture, + MaterialTheme.colorScheme.largeRelayIconModifier, + ) + } - Section(stringResource(R.string.owner)) + Spacer(modifier = DoubleHorzSpacer) - relayInfo.pubkey?.let { - DisplayOwnerInformation(it, accountViewModel) { - onClose() - nav(it) + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Row { Title(relayInfo.name?.trim() ?: "") } + + Row { SubtitleContent(relayInfo.description?.trim() ?: "") } + } } } + item { + Section(stringResource(R.string.owner)) - Section(stringResource(R.string.software)) + relayInfo.pubkey?.let { + DisplayOwnerInformation(it, accountViewModel) { + onClose() + nav(it) + } + } + } + item { + Section(stringResource(R.string.software)) - DisplaySoftwareInformation(relayInfo) + DisplaySoftwareInformation(relayInfo) - Section(stringResource(R.string.version)) + Section(stringResource(R.string.version)) - SectionContent(relayInfo.version ?: "") + SectionContent(relayInfo.version ?: "") + } + item { + Section(stringResource(R.string.contact)) - Section(stringResource(R.string.contact)) + Box(modifier = Modifier.padding(start = 10.dp)) { + relayInfo.contact?.let { + if (it.startsWith("https:")) { + ClickableUrl(urlText = it, url = it) + } else if (it.startsWith("mailto:") || it.contains('@')) { + ClickableEmail(it) + } else { + SectionContent(it) + } + } + } + } + item { + Section(stringResource(R.string.supports)) - Box(modifier = Modifier.padding(start = 10.dp)) { - relayInfo.contact?.let { - if (it.startsWith("https:")) { - ClickableUrl(urlText = it, url = it) - } else if (it.startsWith("mailto:") || it.contains('@')) { - ClickableEmail(it) - } else { - SectionContent(it) + DisplaySupportedNips(relayInfo) + } + item { + relayInfo.fees?.admission?.let { + if (it.isNotEmpty()) { + Section(stringResource(R.string.admission_fees)) + + it.forEach { item -> SectionContent("${item.amount?.div(1000) ?: 0} sats") } + } + } + + relayInfo.payments_url?.let { + Section(stringResource(R.string.payments_url)) + + Box(modifier = Modifier.padding(start = 10.dp)) { + ClickableUrl( + urlText = it, + url = it, + ) + } + } + } + item { + relayInfo.limitation?.let { + Section(stringResource(R.string.limitations)) + val authRequiredText = + if (it.auth_required ?: false) stringResource(R.string.yes) else stringResource(R.string.no) + + val paymentRequiredText = + 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( + "${stringResource(R.string.message_length)}: ${it.max_message_length ?: 0}", + ) + SectionContent( + "${stringResource(R.string.subscriptions)}: ${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}", + ) + SectionContent("${stringResource(R.string.minimum_prefix)}: ${it.min_prefix ?: 0}") + SectionContent( + "${stringResource(R.string.maximum_event_tags)}: ${it.max_event_tags ?: 0}", + ) + 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") + } + } + } + item { + relayInfo.relay_countries?.let { + Section(stringResource(R.string.countries)) + + FlowRow { it.forEach { item -> SectionContent(item) } } + } + } + item { + relayInfo.language_tags?.let { + Section(stringResource(R.string.languages)) + + FlowRow { it.forEach { item -> SectionContent(item) } } + } + } + item { + relayInfo.tags?.let { + Section(stringResource(R.string.tags)) + + FlowRow { it.forEach { item -> SectionContent(item) } } + } + } + item { + relayInfo.posting_policy?.let { + Section(stringResource(R.string.posting_policy)) + + Box(Modifier.padding(10.dp)) { + ClickableUrl( + it, + it, + ) } } } - Section(stringResource(R.string.supports)) - - DisplaySupportedNips(relayInfo) - - relayInfo.fees?.admission?.let { - if (it.isNotEmpty()) { - Section(stringResource(R.string.admission_fees)) - - it.forEach { item -> SectionContent("${item.amount?.div(1000) ?: 0} sats") } - } + item { + Section(stringResource(R.string.relay_error_messages)) } - relayInfo.payments_url?.let { - Section(stringResource(R.string.payments_url)) - - Box(modifier = Modifier.padding(start = 10.dp)) { - ClickableUrl( - urlText = it, - url = it, + items(messages) { msg -> + Row { + TranslatableRichTextViewer( + content = + remember { + "${timeAgo(msg.time, context)}, ${msg.type.name}: ${msg.message}" + }, + canPreview = false, + quotesLeft = 0, + modifier = Modifier.fillMaxWidth(), + tags = EmptyTagList, + backgroundColor = color, + id = msg.hashCode().toString(), + accountViewModel = accountViewModel, + nav = nav, ) } - } - relayInfo.limitation?.let { - Section(stringResource(R.string.limitations)) - val authRequiredText = - if (it.auth_required ?: false) stringResource(R.string.yes) else stringResource(R.string.no) - - val paymentRequiredText = - 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( - "${stringResource(R.string.message_length)}: ${it.max_message_length ?: 0}", - ) - SectionContent( - "${stringResource(R.string.subscriptions)}: ${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}", - ) - SectionContent("${stringResource(R.string.minimum_prefix)}: ${it.min_prefix ?: 0}") - SectionContent( - "${stringResource(R.string.maximum_event_tags)}: ${it.max_event_tags ?: 0}", - ) - 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") - } - } - - relayInfo.relay_countries?.let { - Section(stringResource(R.string.countries)) - - FlowRow { it.forEach { item -> SectionContent(item) } } - } - - relayInfo.language_tags?.let { - Section(stringResource(R.string.languages)) - - FlowRow { it.forEach { item -> SectionContent(item) } } - } - - relayInfo.tags?.let { - Section(stringResource(R.string.tags)) - - FlowRow { it.forEach { item -> SectionContent(item) } } - } - - relayInfo.posting_policy?.let { - Section(stringResource(R.string.posting_policy)) - - Box(Modifier.padding(10.dp)) { - ClickableUrl( - it, - it, - ) - } + Spacer(modifier = StdVertSpacer) } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b270824b8..22cf695d1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -463,6 +463,7 @@ Languages Tags Posting policy + Errors and Notices from this Relay Message length Subscriptions Filters