diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/LiveStatusIndicator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/LiveStatusIndicator.kt new file mode 100644 index 000000000..8a9c2966f --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/LiveStatusIndicator.kt @@ -0,0 +1,109 @@ +/** + * 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.home.live + +import android.util.Log +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.produceState +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import com.vitorpamplona.amethyst.model.Channel +import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel +import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel +import com.vitorpamplona.amethyst.service.OnlineChecker +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.withContext + +@Composable +fun LiveStatusIndicator( + isOnline: Boolean, + modifier: Modifier = Modifier, +) { + Canvas( + modifier = modifier.size(8.dp), + ) { + drawCircle( + color = if (isOnline) Color.Red else Color.Black, + radius = size.minDimension / 2, + ) + } +} + +@Composable +fun LiveStatusIndicatorForChannel( + channel: Channel, + accountViewModel: AccountViewModel, + modifier: Modifier = Modifier, +) { + val isOnline by produceState(initialValue = false, key1 = channel) { + while (true) { + value = checkChannelIsOnline(channel, accountViewModel) + delay(30_000) // Poll every 30 seconds + } + } + + LiveStatusIndicator( + isOnline = isOnline, + modifier = modifier, + ) +} + +private suspend fun checkChannelIsOnline( + channel: Channel, + accountViewModel: AccountViewModel, +): Boolean = + withContext(Dispatchers.IO) { + try { + when (channel) { + is LiveActivitiesChannel -> { + // Check if streaming URL is online, fall back to relay check + val streamingUrl = channel.info?.streaming() + if (!streamingUrl.isNullOrBlank()) { + accountViewModel.checkVideoIsOnline(streamingUrl) + } else { + // Check relay connection + val relayUrl = channel.relayHintUrl() + if (relayUrl != null) { + OnlineChecker.isOnline(relayUrl.url, accountViewModel.httpClientBuilder::okHttpClientForVideo) + } else { + false + } + } + } + is EphemeralChatChannel -> { + // Check relay connection for ephemeral chat + val relayUrl = channel.roomId.relayUrl + OnlineChecker.isOnline(relayUrl.url, accountViewModel.httpClientBuilder::okHttpClientForVideo) + } + else -> false + } + } catch (e: Exception) { + Log.d("LiveStatusIndicator", "Network error checking channel ${channel.toBestDisplayName()}: ${e.message}") + // Return false if any network error occurs + false + } + } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderEphemeralBubble.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderEphemeralBubble.kt index e56e00ac3..4a7886545 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderEphemeralBubble.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderEphemeralBubble.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.model.emphChat.EphemeralChatChannel @@ -48,6 +49,12 @@ fun RenderEphemeralBubble( nav.nav { routeFor(channel) } }, ) { + LiveStatusIndicatorForChannel( + channel = channel, + accountViewModel = accountViewModel, + modifier = Modifier.align(Alignment.CenterVertically), + ) + Spacer(StdHorzSpacer) RenderUsers(channel, accountViewModel, nav) Spacer(StdHorzSpacer) Text( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderLiveActivityBubble.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderLiveActivityBubble.kt index b2f0b7bda..7e85e3504 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderLiveActivityBubble.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/RenderLiveActivityBubble.kt @@ -26,6 +26,7 @@ import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.model.nip53LiveActivities.LiveActivitiesChannel @@ -48,6 +49,12 @@ fun RenderLiveActivityBubble( nav.nav { routeFor(channel) } }, ) { + LiveStatusIndicatorForChannel( + channel = channel, + accountViewModel = accountViewModel, + modifier = Modifier.align(Alignment.CenterVertically), + ) + Spacer(StdHorzSpacer) RenderUsers(channel, accountViewModel, nav) Spacer(StdHorzSpacer) Text( diff --git a/amethyst/src/main/res/values-nl-rNL/strings.xml b/amethyst/src/main/res/values-nl-rNL/strings.xml index 50d666ced..3db9ae5d1 100644 --- a/amethyst/src/main/res/values-nl-rNL/strings.xml +++ b/amethyst/src/main/res/values-nl-rNL/strings.xml @@ -148,6 +148,7 @@ De video is niet opgeslagen Afbeelding uploaden Neem een foto + Video opnemen Neem een bericht op Neem een bericht op Klik en houd vast om een bericht te opnemen @@ -163,6 +164,8 @@ Geblokkeerde gebruikers Nieuwe onderwerpen Discussies + Feed + Modwachtrij Notes Reacties Jouw @@ -437,6 +440,7 @@ Nee Volgerslijst Iedereen + Volg alle gebruikers Volgt via proxy In de buurt Globaal @@ -655,6 +659,8 @@ Uitleg aan leden De naam veranderen voor de nieuwe doelen. Plakken vanaf klembord + Invalide NIP-47 URI + De URI %1$s is geen geldige NIP-47 login-URI. Voor de interface van de app Donker-, Licht- of Systeemthema Automatisch afbeeldingen en GIF\'s laden @@ -672,6 +678,9 @@ Media toegevoegd aan je profielgalerij Gemaakt op Regels + Over ons + Richtlijnen + Moderators Login met Amber Status bijwerken Fout bij het parsen van foutberichten @@ -884,10 +893,13 @@ DM uploaden Relay-instellingen Openbare thuis relays + Gebruiker plaatst zijn content op deze relays Dit relay type slaat al je inhoud op. Amethyst stuurt je berichten hierheen en anderen gebruiken deze relays om je inhoud te vinden. Voeg tussen de 1 en 3 relays toe. Het gaat om persoonlijke relays, betaalde relays of openbare relays. Openbare inbox relays + Gebruiker ontvangt meldingen voor deze relays Dit type relay ontvangt alle relays, reacties, likes en zaps op je berichten. Ze kunnen betaalde of gratis relays zijn. Limieten ingesteld door de relay beheerder kunnen de meldingen die u ontvangt beperken voor het goede en het slechte. Als u bijvoorbeeld wordt aangevallen door spam, kunnen betaalde relays de spam filteren. Invoegen tussen de 1 en 3 relays. DM inbox relays + Gebruiker ontvangt DM\'s bij deze relays Voeg tussen de 1 en 3 relays toe om als uw privéinbox te dienen. Anderen gebruiken deze relays om DM\'s naar u te sturen. DM Inbox relays moeten een bericht van iedereen accepteren, maar alleen om berichten te downloaden. Goede opties zijn:\n - inbox.nostr.wine (betaald)\n - you.nostr1.com (persoonlijke relays - betaald) Privé relays Plaats tussen de 1 tot 3 relays om gebeurtenissen op te slaan die niemand anders kan zien, zoals uw Concepten en/of app instellingen. Idealiter zijn deze relays lokaal of vereist authenticatie voordat de inhoud van elke gebruiker wordt gedownload. @@ -1002,6 +1014,7 @@ Downloaden Kon bestand niet openen Geen torrent apps geïnstalleerd om het bestand te openen en te downloaden. + Evenement heeft niet genoeg informatie om een magnet-link te maken Selecteer een lijst om de feed te filteren Afmelden bij apparaatvergrendeling Privébericht @@ -1009,9 +1022,18 @@ Chat relay De relay die alle gebruikers van deze chat gebruiken om verbinding te maken Afbeelding delen… + Kan afbeelding niet delen, probeer het later opnieuw… Zoek hashtag: #%1$s Niet vertalen van Talen die hier getoond worden worden worden niet vertaald. Selecteer een taal om te verwijderen en het opnieuw te laten vertalen. Pauze Afspelen + Open dropdown-menu + Optie %1$s van %2$s + Feed-filter, %1$s geselecteerd + Feed-filter, %1$s + Crashrapport gevonden + Wilt u het recente crashrapport verzenden naar Amethyst in een DM? Er zal geen persoonlijke informatie gedeeld worden + Verzenden + Dit bericht zal verdwijnen in %1$d dagen