diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/FeedFilterSpinner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/FeedFilterSpinner.kt index 0913899df1..8a0dc7cef7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/FeedFilterSpinner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/topbars/FeedFilterSpinner.kt @@ -52,6 +52,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.semantics.Role +import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.onClick import androidx.compose.ui.semantics.role import androidx.compose.ui.semantics.semantics @@ -71,6 +72,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.topNavFeeds.TopFilter import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.dvm_offline import com.vitorpamplona.amethyst.commons.resources.feed_filter_select_an_option import com.vitorpamplona.amethyst.commons.resources.feed_filter_selected import com.vitorpamplona.amethyst.commons.resources.lack_location_permissions @@ -181,11 +183,18 @@ fun FeedFilterSpinner( val favoriteAlgoFeedAddress = (selected?.name as? FavoriteAlgoFeedName)?.note?.address if (favoriteAlgoFeedAddress != null) { val heartbeatFresh by rememberDvmHeartbeatFresh(favoriteAlgoFeedAddress, accountViewModel) + val offlineLabel = stringRes(Res.string.dvm_offline) Text( text = if (heartbeatFresh) currentText else "$currentText \u2022", color = if (heartbeatFresh) Color.Unspecified else MaterialTheme.colorScheme.onSurfaceVariant, maxLines = 1, overflow = TextOverflow.Ellipsis, + modifier = + if (heartbeatFresh) { + Modifier + } else { + Modifier.semantics { contentDescription = "$currentText, $offlineLabel" } + }, ) } else { Text( @@ -390,6 +399,7 @@ fun RenderOption( } else { true } + val offlineLabel = stringRes(Res.string.dvm_offline) Text( text = if (heartbeatFresh) name else "$name \u2022", fontSize = Font14SP, @@ -399,6 +409,12 @@ fun RenderOption( } else { MaterialTheme.colorScheme.onSurfaceVariant }, + modifier = + if (heartbeatFresh) { + Modifier + } else { + Modifier.semantics { contentDescription = "$name, $offlineLabel" } + }, ) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmHeartbeatObservation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmHeartbeatObservation.kt index fcd4790796..db80c0a3b7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmHeartbeatObservation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/DvmHeartbeatObservation.kt @@ -78,19 +78,18 @@ fun rememberDvmHeartbeatFresh( } } - val resolved = heartbeatNote ?: return remember(heartbeatAddressTag) { mutableStateOf(false) } + val observed = + heartbeatNote?.let { observeNoteAndMap(it, accountViewModel) { it.event as? DvmHeartbeatEvent } } - val heartbeat by observeNoteAndMap(resolved, accountViewModel) { it.event as? DvmHeartbeatEvent } - - var now by remember(resolved) { mutableLongStateOf(TimeUtils.now()) } - LaunchedEffect(resolved) { + var now by remember(heartbeatNote) { mutableLongStateOf(TimeUtils.now()) } + LaunchedEffect(heartbeatNote) { while (isActive) { delay(HEARTBEAT_RECHECK_MILLIS) now = TimeUtils.now() } } - val beat = heartbeat + val beat = observed?.value return rememberUpdatedState(beat != null && beat.isFreshAt(now)) } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/favorites/FavoriteAlgoFeedsListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/favorites/FavoriteAlgoFeedsListScreen.kt index 4cbbe46792..f043cc8483 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/favorites/FavoriteAlgoFeedsListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/dvms/favorites/FavoriteAlgoFeedsListScreen.kt @@ -52,6 +52,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment.Companion.BottomStart import androidx.compose.ui.Modifier import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.semantics.contentDescription import androidx.compose.ui.semantics.semantics import androidx.compose.ui.text.Placeholder import androidx.compose.ui.text.PlaceholderVerticalAlign @@ -67,6 +68,7 @@ import com.vitorpamplona.amethyst.commons.icons.symbols.Icon import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols import com.vitorpamplona.amethyst.commons.model.AddressableNote import com.vitorpamplona.amethyst.commons.resources.Res +import com.vitorpamplona.amethyst.commons.resources.dvm_offline import com.vitorpamplona.amethyst.commons.resources.favorite_dvms_add_more import com.vitorpamplona.amethyst.commons.resources.favorite_dvms_empty_cta import com.vitorpamplona.amethyst.commons.resources.favorite_dvms_empty_headline @@ -285,12 +287,19 @@ private fun FavoriteAlgoFeedRow( ) { val heartbeatFresh by rememberDvmHeartbeatFresh(feedNote.address, accountViewModel) val displayName = card.name.ifBlank { feedNote.dTag() } + val offlineLabel = stringRes(Res.string.dvm_offline) Text( text = if (heartbeatFresh) displayName else "$displayName \u2022", fontWeight = FontWeight.Bold, maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyLarge, + modifier = + if (heartbeatFresh) { + Modifier + } else { + Modifier.semantics { contentDescription = "$displayName, $offlineLabel" } + }, ) card.description?.takeIf { it.isNotBlank() }?.let { Spacer(modifier = StdVertSpacer) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AlgoFeedStatusBanner.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AlgoFeedStatusBanner.kt index 82f9107925..90f0fc5344 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AlgoFeedStatusBanner.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/AlgoFeedStatusBanner.kt @@ -200,10 +200,13 @@ private fun AllFavoriteAlgoFeedsBanner( if (addresses.isEmpty()) return + // Map every address to its freshness first (non-short-circuiting) so per-address + // composable call sites stay stable instead of appearing/disappearing with freshness. val anyHeartbeatFresh = - addresses.any { address -> - rememberDvmHeartbeatFresh(address, accountViewModel).value - } + addresses + .map { address -> + rememberDvmHeartbeatFresh(address, accountViewModel).value + }.any { it } if (!anyHeartbeatFresh) { BannerCard(modifier) { BannerMessageRow(