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..6aad8f356 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/live/LiveStatusIndicator.kt @@ -0,0 +1,100 @@ +/** + * 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 androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +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.delay + +@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, +) { + var isOnline by remember { mutableStateOf(false) } + + LaunchedEffect(channel) { + while (true) { + isOnline = checkChannelIsOnline(channel, accountViewModel) + delay(30_000) // Check every 30 seconds + } + } + + LiveStatusIndicator(isOnline = isOnline, modifier = modifier) +} + +private suspend fun checkChannelIsOnline( + channel: Channel, + accountViewModel: AccountViewModel, +): Boolean = + 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 + } 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(