From d65ffd197857ddc4b45536c25e70a947ba1d647a Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 27 Mar 2026 15:55:02 -0400 Subject: [PATCH 1/5] feat: display NIP-66 relay monitor reports in relay information screen Subscribe to kind 30166 (RelayDiscoveryEvent) events for the relay being viewed and display monitoring data from multiple providers. Shows RTT metrics (open/read/write), network type, relay type, supported NIPs, and access requirements reported by each monitor. https://claude.ai/code/session_01KWuXTp3Z7HST9cu6RWQ4G5 --- .../RelayInfoNip66FilterAssembler.kt | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssembler.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssembler.kt new file mode 100644 index 000000000..8a2654284 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssembler.kt @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.datasource + +import com.vitorpamplona.amethyst.commons.relayClient.composeSubscriptionManagers.ComposeSubscriptionManager +import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl + +class RelayInfoNip66QueryState( + val relayUrl: NormalizedRelayUrl, + val relays: Set, +) + +class RelayInfoNip66FilterAssembler( + client: INostrClient, +) : ComposeSubscriptionManager() { + val group = + listOf( + RelayInfoNip66FilterSubAssembler(client, ::allKeys), + ) + + override fun invalidateKeys() = invalidateFilters() + + override fun invalidateFilters() = group.forEach { it.invalidateFilters() } + + override fun destroy() = group.forEach { it.destroy() } +} From 0a44bb49043747774062a0da0506c3b07ea84eb3 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 27 Mar 2026 15:55:19 -0400 Subject: [PATCH 2/5] feat: add NIP-66 relay discovery filter sub-assembler --- .../RelayInfoNip66FilterSubAssembler.kt | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterSubAssembler.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterSubAssembler.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterSubAssembler.kt new file mode 100644 index 000000000..676288ee0 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterSubAssembler.kt @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.datasource + +import com.vitorpamplona.amethyst.service.relayClient.eoseManagers.PerUniqueIdEoseManager +import com.vitorpamplona.amethyst.service.relays.SincePerRelayMap +import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient +import com.vitorpamplona.quartz.nip01Core.relay.client.pool.RelayBasedFilter +import com.vitorpamplona.quartz.nip01Core.relay.filters.Filter +import com.vitorpamplona.quartz.nip66RelayMonitor.discovery.RelayDiscoveryEvent + +class RelayInfoNip66FilterSubAssembler( + client: INostrClient, + allKeys: () -> Set, +) : PerUniqueIdEoseManager(client, allKeys) { + override fun updateFilter( + key: RelayInfoNip66QueryState, + since: SincePerRelayMap?, + ): List { + val relayUrl = key.relayUrl.url + + return key.relays.map { relay -> + RelayBasedFilter( + relay = relay, + filter = + Filter( + kinds = listOf(RelayDiscoveryEvent.KIND), + tags = mapOf("d" to listOf(relayUrl)), + limit = 20, + since = since?.get(relay)?.time, + ), + ) + } + } + + override fun id(key: RelayInfoNip66QueryState) = key.relayUrl.url +} From 3387e119d5e677ad7c6c2054b299f4bbe46fbcb7 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 27 Mar 2026 15:55:25 -0400 Subject: [PATCH 3/5] feat: add NIP-66 relay info subscription composable --- ...layInfoNip66FilterAssemblerSubscription.kt | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssemblerSubscription.kt diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssemblerSubscription.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssemblerSubscription.kt new file mode 100644 index 000000000..772278573 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/datasource/RelayInfoNip66FilterAssemblerSubscription.kt @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 Vitor Pamplona + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the + * Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.datasource + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import com.vitorpamplona.amethyst.commons.relayClient.subscriptions.KeyDataSourceSubscription +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.quartz.nip01Core.relay.normalizer.NormalizedRelayUrl + +@Composable +fun RelayInfoNip66FilterAssemblerSubscription( + relayUrl: NormalizedRelayUrl, + accountViewModel: AccountViewModel, +) { + val state = + remember(relayUrl) { + RelayInfoNip66QueryState(relayUrl, accountViewModel.account.followOutboxesOrProxy.flow.value) + } + + KeyDataSourceSubscription(state, accountViewModel.dataSources().relayInfoNip66) +} From aa3202de4beeb331b8ba694b93f7aa44668fe27a Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Fri, 27 Mar 2026 15:56:19 -0400 Subject: [PATCH 4/5] feat: register NIP-66 filter assembler in coordinator --- .../relayClient/reqCommand/RelaySubscriptionsCoordinator.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt index 6798df8ac..dc175afd3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/relayClient/reqCommand/RelaySubscriptionsCoordinator.kt @@ -39,6 +39,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.hashtag.datasource.HashtagF import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.datasource.HomeFilterAssembler import com.vitorpamplona.amethyst.ui.screen.loggedIn.profile.datasource.UserProfileFilterAssembler import com.vitorpamplona.amethyst.ui.screen.loggedIn.relay.datasource.RelayFeedFilterAssembler +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.datasource.RelayInfoNip66FilterAssembler import com.vitorpamplona.amethyst.ui.screen.loggedIn.threadview.datasources.ThreadFilterAssembler import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.datasource.VideoFilterAssembler import com.vitorpamplona.quartz.nip01Core.relay.client.INostrClient @@ -80,6 +81,7 @@ class RelaySubscriptionsCoordinator( val hashtags = HashtagFilterAssembler(client) val geohashes = GeoHashFilterAssembler(client) val relayFeed = RelayFeedFilterAssembler(client) + val relayInfoNip66 = RelayInfoNip66FilterAssembler(client) val followPacks = FollowPackFeedFilterAssembler(client) val chess = ChessFilterAssembler(client) @@ -105,6 +107,7 @@ class RelaySubscriptionsCoordinator( hashtags, geohashes, relayFeed, + relayInfoNip66, chess, nwc, ) From 77656ffc8432e2e593ca4ec11ee10ce903e48730 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 27 Mar 2026 19:58:26 +0000 Subject: [PATCH 5/5] feat: add NIP-66 monitor reports UI and string resources Add relay monitor report section to RelayInformationScreen showing discovery events from multiple NIP-66 monitors. Add string resources for the monitor reports UI. https://claude.ai/code/session_01KWuXTp3Z7HST9cu6RWQ4G5 --- .../loggedIn/relays/RelayInformationScreen.kt | 251 +++++++++++++++--- amethyst/src/main/res/values/strings.xml | 108 +------- 2 files changed, 231 insertions(+), 128 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationScreen.kt index 3ef38c4d9..09d504b81 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/relays/RelayInformationScreen.kt @@ -62,7 +62,6 @@ import androidx.compose.material.icons.filled.Language import androidx.compose.material.icons.filled.Lock import androidx.compose.material.icons.filled.Payment import androidx.compose.material.icons.filled.PrivacyTip -import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Storage import androidx.compose.material.icons.filled.Tag import androidx.compose.material.icons.filled.Topic @@ -81,7 +80,9 @@ import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.material3.TopAppBar import androidx.compose.runtime.Composable +import androidx.compose.runtime.State import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -118,6 +119,7 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.chats.rooms.LoadUser import com.vitorpamplona.amethyst.ui.screen.loggedIn.mockAccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.qrcode.BackButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.relays.datasource.RelayInfoNip66FilterAssemblerSubscription import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.Height25Modifier @@ -255,7 +257,6 @@ import com.vitorpamplona.quartz.nip72ModCommunities.follow.CommunityListEvent import com.vitorpamplona.quartz.nip75ZapGoals.GoalEvent import com.vitorpamplona.quartz.nip78AppData.AppSpecificDataEvent import com.vitorpamplona.quartz.nip84Highlights.HighlightEvent -import com.vitorpamplona.quartz.nip86RelayManagement.Nip86Client import com.vitorpamplona.quartz.nip88Polls.poll.PollEvent import com.vitorpamplona.quartz.nip88Polls.response.PollResponseEvent import com.vitorpamplona.quartz.nip89AppHandlers.definition.AppDefinitionEvent @@ -328,8 +329,12 @@ fun RelayInformationScreen( ) }, ) { pad -> + RelayInfoNip66FilterAssemblerSubscription(relay, accountViewModel) + val relayInfo by loadRelayInfo(relay) + val discoveryEvents by loadRelayDiscoveryEvents(relay) + val messages = remember(relay) { Amethyst.instance.relayStats @@ -341,7 +346,7 @@ fun RelayInformationScreen( .toImmutableList() } - RelayInformationBody(relay, relayInfo, Amethyst.instance.relayStats.get(relay), messages, pad, accountViewModel, nav) + RelayInformationBody(relay, relayInfo, discoveryEvents, Amethyst.instance.relayStats.get(relay), messages, pad, accountViewModel, nav) } } @@ -349,6 +354,7 @@ fun RelayInformationScreen( fun RelayInformationBody( relay: NormalizedRelayUrl, relayInfo: Nip11RelayInformation, + discoveryEvents: ImmutableList, relayStats: RelayStat, messages: ImmutableList, pad: PaddingValues, @@ -428,6 +434,13 @@ fun RelayInformationBody( item { SoftwareCard(relayInfo) } } + if (discoveryEvents.isNotEmpty()) { + item { SectionHeader(stringRes(R.string.relay_monitor_reports)) } + items(discoveryEvents, key = { it.id }) { event -> + RelayMonitorReportCard(event, accountViewModel, nav) + } + } + if (usedBy.isNotEmpty()) { item { SectionHeader(stringRes(R.string.used_by)) @@ -528,7 +541,7 @@ fun RelayInformationBody( // Active subscriptions + outbox display // --------------------------------------------------------------------------- -fun kindDisplayName(kind: Int): Int = +private fun kindDisplayName(kind: Int): Int = when (kind) { AdvertisedRelayListEvent.KIND -> R.string.kind_outbox_relays AppDefinitionEvent.KIND -> R.string.kind_apps @@ -1105,35 +1118,18 @@ private fun RelayHeader( color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.Center, ) - Row( + OutlinedButton( modifier = Modifier.padding(horizontal = 30.dp), - horizontalArrangement = Arrangement.spacedBy(10.dp), + shape = ButtonBorder, + onClick = { nav.nav(Route.RelayFeed(url = relay.url)) }, ) { - OutlinedButton( - shape = ButtonBorder, - onClick = { nav.nav(Route.RelayFeed(url = relay.url)) }, - ) { - Icon( - imageVector = Icons.AutoMirrored.Filled.Feed, - contentDescription = null, - modifier = Modifier.size(18.dp), - ) - Spacer(modifier = Modifier.width(4.dp)) - Text(text = stringRes(R.string.see_relay_feed)) - } - - if (Nip86Client.supportsNip86(relayInfo.supported_nips)) { - OutlinedButton( - onClick = { nav.nav(Route.RelayManagement(relay.url)) }, - shape = ButtonBorder, - ) { - Icon( - Icons.Default.Settings, - contentDescription = stringRes(R.string.manage), - modifier = Height25Modifier, - ) - } - } + Icon( + imageVector = Icons.AutoMirrored.Filled.Feed, + contentDescription = null, + modifier = Modifier.size(18.dp), + ) + Spacer(modifier = Modifier.width(4.dp)) + Text(text = stringRes(R.string.see_relay_feed)) } } } @@ -1554,6 +1550,198 @@ fun PoliciesCard(relay: Nip11RelayInformation) { } } +@Composable +fun loadRelayDiscoveryEvents(relay: NormalizedRelayUrl): State> = + produceState>(persistentListOf(), relay) { + LocalCache + .observeEvents( + Filter( + kinds = listOf(RelayDiscoveryEvent.KIND), + tags = mapOf("d" to listOf(relay.url)), + ), + ).collect { events -> + value = + events + .filterIsInstance() + .sortedByDescending { it.createdAt } + .toImmutableList() + } + } + +@Composable +private fun RelayMonitorReportCard( + event: RelayDiscoveryEvent, + accountViewModel: AccountViewModel, + nav: INav, +) { + val context = LocalContext.current + + OutlinedCard( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant), + ) { + Column(modifier = Modifier.padding(16.dp), verticalArrangement = Arrangement.spacedBy(8.dp)) { + // Monitor author + timestamp + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.fillMaxWidth(), + ) { + LoadUser(baseUserHex = event.pubKey, accountViewModel) { user -> + if (user != null) { + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(8.dp), + modifier = Modifier.weight(1f), + ) { + UserPicture( + user = user, + size = Size25dp, + accountViewModel = accountViewModel, + nav = nav, + ) + UserCompose( + baseUser = user, + accountViewModel = accountViewModel, + nav = nav, + ) + } + } + } + + Text( + text = timeAgoNoDot(event.createdAt, context), + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f), + ) + } + + HorizontalDivider() + + // RTT metrics + val rttOpen = event.rttOpen() + val rttRead = event.rttRead() + val rttWrite = event.rttWrite() + + if (rttOpen != null || rttRead != null || rttWrite != null) { + Text( + stringRes(R.string.relay_monitor_rtt), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary, + ) + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly, + ) { + rttOpen?.let { + RttChip(stringRes(R.string.relay_monitor_rtt_open), it) + } + rttRead?.let { + RttChip(stringRes(R.string.relay_monitor_rtt_read), it) + } + rttWrite?.let { + RttChip(stringRes(R.string.relay_monitor_rtt_write), it) + } + } + } + + // Network type + val networkTypes = event.networkTypes() + if (networkTypes.isNotEmpty()) { + InfoRow( + Icons.Default.Language, + stringRes(R.string.relay_monitor_network), + networkTypes.joinToString { it.code }, + ) + } + + // Relay type + val relayTypes = event.relayTypes() + if (relayTypes.isNotEmpty()) { + InfoRow( + Icons.Default.Dns, + stringRes(R.string.relay_monitor_relay_type), + relayTypes.joinToString(), + ) + } + + // Supported NIPs + val supportedNips = event.supportedNips() + if (supportedNips.isNotEmpty()) { + Text( + stringRes(R.string.relay_monitor_supported_nips), + style = MaterialTheme.typography.labelMedium, + color = MaterialTheme.colorScheme.primary, + ) + val uri = LocalUriHandler.current + FlowRow( + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalArrangement = Arrangement.spacedBy(4.dp), + ) { + supportedNips.forEach { nip -> + val nipStr = nip.toString().padStart(2, '0') + SuggestionChip( + onClick = { + runCatching { + uri.openUri(nipLink(nipStr)) + } + }, + label = { Text(nipStr) }, + ) + } + } + } + + // Requirements + val requirements = event.requirements() + if (requirements.isNotEmpty()) { + InfoRow( + Icons.Default.Lock, + stringRes(R.string.relay_monitor_requirements), + requirements.joinToString { req -> + if (req.negated) "!${req.value}" else req.value + }, + ) + } + } + } +} + +@Composable +private fun RttChip( + label: String, + ms: Long, +) { + val color = + when { + ms < 200 -> Color(0xFF4CAF50) + + // green + ms < 500 -> Color(0xFFFFC107) + + // amber + else -> MaterialTheme.colorScheme.error + } + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text( + text = label, + style = MaterialTheme.typography.labelSmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + Surface( + shape = RoundedCornerShape(50), + color = color.copy(alpha = 0.15f), + ) { + Text( + text = stringRes(R.string.relay_monitor_ms, ms.toInt()), + modifier = Modifier.padding(horizontal = 10.dp, vertical = 4.dp), + style = MaterialTheme.typography.labelMedium, + fontWeight = FontWeight.Bold, + color = color, + ) + } + } +} + @Composable fun SectionHeader(title: String) { Text( @@ -1678,6 +1866,7 @@ fun RelayHeaderPreview() { ), supported_grasps = listOf("GRASP-01"), ), + discoveryEvents = persistentListOf(), pad = PaddingValues(0.dp), relayStats = RelayStat(), messages = diff --git a/amethyst/src/main/res/values/strings.xml b/amethyst/src/main/res/values/strings.xml index f00b50e99..ab0c21344 100644 --- a/amethyst/src/main/res/values/strings.xml +++ b/amethyst/src/main/res/values/strings.xml @@ -418,10 +418,6 @@ Remove from Private Bookmarks Remove from Public Bookmarks - Pinned Notes - Pin to Profile - Unpin from Profile - Bookmark Lists Icon for bookmark list New Bookmark List @@ -827,6 +823,17 @@ Terms & Conditions N/A Errors and Notices from this Relay + Relay Monitor Reports + Open + Read + Write + RTT + Network + Type + Supported NIPs + Requirements + Last check + %1$d ms Active Subscriptions Pending Outbox Events REQ Subscriptions (%1$d) @@ -1974,97 +1981,4 @@ Delete Delete this web bookmark? Open URL - - - This goal has closed - %1$s funded of %2$s sats goal - Goal amount (sats) - 100000 - Describe your goal - What are you fundraising for? - Short summary - Brief description shown in previews - Image URL (optional) - https://example.com/image.jpg - Website URL (optional) - https://example.com - Deadline (optional) - Set a deadline - New Goal - Create Goal - Request to Vanish - Request relays to permanently delete all your data up to the selected date. This action is based on NIP-62 and is legally binding in some jurisdictions. - Select a relay - Target Relays - ALL RELAYS - This will request ALL relays to delete everything associated with your key up to the selected date. This event will be broadcast as widely as possible. This action cannot be undone. - Delete data up to - All your events created before this date will be requested for deletion from the selected relay. - Reason (optional) - Reason or legal notice for the relay operator - Send Vanish Request - Confirm Vanish Request - You are about to request %1$s to permanently delete all your data created before the selected date. This cannot be undone. - You are about to request EVERY relay to permanently delete all your data created before the selected date. This will be broadcast everywhere and cannot be undone. - Vanish request sent - Select date - Select time - - Vanish History - Refresh - These are your past Request to Vanish events found on connected relays. Relays tagged in these events should not hold any of your data from before the event date. - No vanish requests found - You haven\'t sent any Request to Vanish events yet. - Target Relays - This request targets all relays. Use the Request to Vanish screen to test specific relays for compliance. - Test - Compliant - Non-compliant - Error - Vanish History - - Manage %1$s - Loading relay management capabilities… - Unable to connect to relay management - No management methods available - Dismiss - Users - Events - Kinds - IPs - Settings - Banned Users - No banned users - Allowed Users - No allowed users - Moderation Queue - No events needing moderation - Banned Events - No banned events - Allowed Kinds - No allowed kinds - Blocked IPs - No blocked IPs - Add - Remove - Allow - Ban - Ban Pubkey - Allow Pubkey - Ban Event - Allow Kind - Block IP - Pubkey (hex) - Event ID (hex) - Kind number - IP address - Reason (optional) - Confirm - Cancel - Apply - Relay Name - Relay Description - Relay Icon URL - Manage Relay - Manage