diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentState.kt index 7b65a94f0..002f78f5a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentState.kt @@ -22,7 +22,6 @@ package com.vitorpamplona.amethyst.ui.feeds import android.util.Log import androidx.compose.runtime.Stable -import androidx.compose.runtime.mutableStateOf import androidx.lifecycle.viewModelScope import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.service.checkNotInMainThread @@ -38,7 +37,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch @Stable @@ -79,7 +77,7 @@ class FeedContentState( val oldNotesState = _feedContent.value if (oldNotesState is FeedState.Loaded) { - if (!equalImmutableLists(notes, oldNotesState.feed.value)) { + if (!equalImmutableLists(notes, oldNotesState.feed.value.list)) { updateFeed(notes) } } else { @@ -88,21 +86,15 @@ class FeedContentState( } private fun updateFeed(notes: ImmutableList) { - viewModelScope.launch(Dispatchers.Main) { - val currentState = _feedContent.value - if (notes.isEmpty()) { - _feedContent.update { FeedState.Empty } - } else if (currentState is FeedState.Loaded) { - // updates the current list - if (currentState.showHidden.value != localFilter.showHiddenKey()) { - currentState.showHidden.value = localFilter.showHiddenKey() - } - currentState.feed.value = notes - } else { - _feedContent.update { - FeedState.Loaded(mutableStateOf(notes), mutableStateOf(localFilter.showHiddenKey())) - } - } + val currentState = _feedContent.value + if (notes.isEmpty()) { + _feedContent.tryEmit(FeedState.Empty) + } else if (currentState is FeedState.Loaded) { + currentState.feed.tryEmit(LoadedFeedState(notes, localFilter.showHiddenKey())) + } else { + _feedContent.tryEmit( + FeedState.Loaded(MutableStateFlow(LoadedFeedState(notes, localFilter.showHiddenKey()))), + ) } } @@ -118,11 +110,11 @@ class FeedContentState( val oldList = if (deletionEvents.isEmpty()) { - oldNotesState.feed.value + oldNotesState.feed.value.list } else { val deletedEventIds = deletionEvents.flatMapTo(HashSet()) { it.deleteEvents() } val deletedEventAddresses = deletionEvents.flatMapTo(HashSet()) { it.deleteAddresses() } - oldNotesState.feed.value + oldNotesState.feed.value.list .filter { !it.wasOrShouldBeDeletedBy(deletedEventIds, deletedEventAddresses) } .toImmutableList() } @@ -132,7 +124,7 @@ class FeedContentState( .updateListWith(oldList, newItems) .distinctBy { it.idHex } .toImmutableList() - if (!equalImmutableLists(newList, oldNotesState.feed.value)) { + if (!equalImmutableLists(newList, oldNotesState.feed.value.list)) { updateFeed(newList) } } else if (oldNotesState is FeedState.Empty) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedLoaded.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedLoaded.kt index 9cd1045f5..3be3e025a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedLoaded.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedLoaded.kt @@ -28,7 +28,9 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier +import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DividerThickness @@ -37,24 +39,26 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding @OptIn(ExperimentalFoundationApi::class) @Composable fun FeedLoaded( - state: FeedState.Loaded, + loaded: FeedState.Loaded, listState: LazyListState, routeForLastRead: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { + val items by loaded.feed.collectAsStateWithLifecycle() + LazyColumn( contentPadding = FeedPadding, state = listState, ) { - itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> + itemsIndexed(items.list, key = { _, item -> item.idHex }) { _, item -> Row(Modifier.fillMaxWidth().animateItemPlacement()) { NoteCompose( item, modifier = Modifier.fillMaxWidth(), routeForLastRead = routeForLastRead, isBoostedNote = false, - isHiddenFeed = state.showHidden.value, + isHiddenFeed = items.showHidden, quotesLeft = 3, accountViewModel = accountViewModel, nav = nav, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedState.kt index bd865915e..4e46038eb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedState.kt @@ -20,16 +20,17 @@ */ package com.vitorpamplona.amethyst.ui.feeds -import androidx.compose.runtime.MutableState +import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.model.Note import kotlinx.collections.immutable.ImmutableList +import kotlinx.coroutines.flow.MutableStateFlow +@Stable sealed class FeedState { object Loading : FeedState() class Loaded( - val feed: MutableState>, - val showHidden: MutableState, + val feed: MutableStateFlow>, ) : FeedState() object Empty : FeedState() @@ -38,3 +39,9 @@ sealed class FeedState { val errorMessage: String, ) : FeedState() } + +@Stable +class LoadedFeedState( + val list: ImmutableList, + val showHidden: Boolean, +) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt index a5a15d055..b9167c0c0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/ThreadFeedView.kt @@ -69,6 +69,7 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.em import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle import coil.compose.AsyncImage import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.LocalCache @@ -224,22 +225,24 @@ fun ThreadFeedView( @Composable fun RenderThreadFeed( noteId: String, - state: FeedState.Loaded, + loaded: FeedState.Loaded, listState: LazyListState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { + val items by loaded.feed.collectAsStateWithLifecycle() + LaunchedEffect(noteId) { // waits to load the thread to scroll to item. delay(100) val noteForPosition = - state.feed.value + items.list .filter { it.idHex == noteId } .firstOrNull() - var position = state.feed.value.indexOf(noteForPosition) + var position = items.list.indexOf(noteForPosition) if (position >= 0) { - if (position >= 1 && position < state.feed.value.size - 1) { + if (position >= 1 && position < items.list.size - 1) { position-- // show the replying note } @@ -251,7 +254,7 @@ fun RenderThreadFeed( contentPadding = FeedPadding, state = listState, ) { - itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { index, item -> + itemsIndexed(items.list, key = { _, item -> item.idHex }) { index, item -> if (index == 0) { ProvideTextStyle(TextStyle(fontSize = 18.sp, lineHeight = 1.20.em)) { NoteMaster( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DraftListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DraftListScreen.kt index dfa7f05a5..34f04d877 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DraftListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DraftListScreen.kt @@ -46,6 +46,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.components.SwipeToDeleteContainer @@ -119,12 +120,14 @@ private fun RenderDraftListScreen( @OptIn(ExperimentalFoundationApi::class) @Composable private fun DraftFeedLoaded( - state: FeedState.Loaded, + loaded: FeedState.Loaded, listState: LazyListState, routeForLastRead: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { + val items by loaded.feed.collectAsStateWithLifecycle() + var showDeleteDialog by remember { mutableStateOf(false) } if (showDeleteDialog) { @@ -141,7 +144,7 @@ private fun DraftFeedLoaded( confirmButton = { TextButton( onClick = { - accountViewModel.delete(state.feed.value) + accountViewModel.delete(items.list) showDeleteDialog = false }, ) { @@ -177,7 +180,7 @@ private fun DraftFeedLoaded( } } } - itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> + itemsIndexed(items.list, key = { _, item -> item.idHex }) { _, item -> Row( Modifier .fillMaxWidth() @@ -196,7 +199,7 @@ private fun DraftFeedLoaded( modifier = MaterialTheme.colorScheme.maxWidthWithBackground, routeForLastRead = routeForLastRead, isBoostedNote = false, - isHiddenFeed = state.showHidden.value, + isHiddenFeed = items.showHidden, quotesLeft = 3, accountViewModel = accountViewModel, nav = nav, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt index 9b229b0c0..2c63313a6 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileGallery.kt @@ -40,7 +40,6 @@ import androidx.compose.runtime.Immutable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.remember import androidx.compose.ui.Alignment.Companion.BottomStart import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip @@ -118,26 +117,21 @@ fun RenderGalleryFeed( @OptIn(ExperimentalFoundationApi::class) @Composable private fun GalleryFeedLoaded( - state: FeedState.Loaded, + loaded: FeedState.Loaded, routeForLastRead: String?, listState: LazyGridState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { + val items by loaded.feed.collectAsStateWithLifecycle() + LazyVerticalGrid( columns = GridCells.Fixed(3), contentPadding = FeedPadding, state = listState, ) { - itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> - val defaultModifier = - remember { - Modifier - .fillMaxWidth() - .animateItemPlacement() - } - - Row(defaultModifier) { + itemsIndexed(items.list, key = { _, item -> item.idHex }) { _, item -> + Row(Modifier.fillMaxWidth().animateItemPlacement()) { GalleryCardCompose( baseNote = item, routeForLastRead = routeForLastRead, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt index 9333beb03..bc0020a54 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ProfileScreen.kt @@ -1238,12 +1238,7 @@ private fun DisplayAppRecommendations( Column { Text(stringRes(id = R.string.recommended_apps)) - FlowRow( - verticalArrangement = Arrangement.Center, - modifier = Modifier.padding(vertical = 5.dp), - ) { - state.feed.value.forEach { app -> WatchApp(app, nav) } - } + Recommends(state, nav) } } else -> {} @@ -1251,6 +1246,21 @@ private fun DisplayAppRecommendations( } } +@Composable +@OptIn(ExperimentalLayoutApi::class) +private fun Recommends( + loaded: FeedState.Loaded, + nav: (String) -> Unit, +) { + val items by loaded.feed.collectAsStateWithLifecycle() + FlowRow( + verticalArrangement = Arrangement.Center, + modifier = Modifier.padding(vertical = 5.dp), + ) { + items.list.forEach { app -> WatchApp(app, nav) } + } +} + @Composable private fun WatchApp( baseApp: Note, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomFeedView.kt index 2c0e02b04..a52074ea4 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomFeedView.kt @@ -122,7 +122,7 @@ fun RenderChatroomFeedView( @Composable fun ChatroomFeedLoaded( - state: FeedState.Loaded, + loaded: FeedState.Loaded, accountViewModel: AccountViewModel, listState: LazyListState, nav: (String) -> Unit, @@ -131,7 +131,9 @@ fun ChatroomFeedLoaded( onWantsToEditDraft: (Note) -> Unit, avoidDraft: String? = null, ) { - LaunchedEffect(state.feed.value.firstOrNull()) { + val items by loaded.feed.collectAsStateWithLifecycle() + + LaunchedEffect(items.list.firstOrNull()) { if (listState.firstVisibleItemIndex <= 1) { listState.animateScrollToItem(0) } @@ -143,7 +145,7 @@ fun ChatroomFeedLoaded( reverseLayout = true, state = listState, ) { - itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> + itemsIndexed(items.list, key = { _, item -> item.idHex }) { _, item -> val noteEvent = item.event if (avoidDraft == null || noteEvent !is DraftEvent || noteEvent.dTag() != avoidDraft) { ChatroomMessageCompose( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomListFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomListFeedView.kt index 35c907660..d82458b54 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomListFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomListFeedView.kt @@ -88,16 +88,18 @@ private fun CrossFadeState( @Composable private fun FeedLoaded( - state: FeedState.Loaded, + loaded: FeedState.Loaded, accountViewModel: AccountViewModel, nav: (String) -> Unit, markAsRead: MutableState, ) { + val items by loaded.feed.collectAsStateWithLifecycle() + val listState = rememberLazyListState() LaunchedEffect(key1 = markAsRead.value) { if (markAsRead.value) { - accountViewModel.markAllAsRead(state.feed.value) { markAsRead.value = false } + accountViewModel.markAllAsRead(items.list) { markAsRead.value = false } } } @@ -106,7 +108,7 @@ private fun FeedLoaded( state = listState, ) { itemsIndexed( - state.feed.value, + items.list, key = { index, item -> if (index == 0) index else item.idHex }, ) { _, item -> Row(Modifier.fillMaxWidth()) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt index 92d04aa49..7f837c81f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt @@ -347,18 +347,20 @@ fun WatchAccountForDiscoveryScreen( @OptIn(ExperimentalFoundationApi::class) @Composable private fun DiscoverFeedLoaded( - state: FeedState.Loaded, + loaded: FeedState.Loaded, routeForLastRead: String?, listState: LazyListState, forceEventKind: Int?, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { + val items by loaded.feed.collectAsStateWithLifecycle() + LazyColumn( contentPadding = FeedPadding, state = listState, ) { - itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> + itemsIndexed(items.list, key = { _, item -> item.idHex }) { _, item -> val defaultModifier = remember { Modifier.fillMaxWidth().animateItemPlacement() } Row(defaultModifier) { @@ -382,19 +384,21 @@ private fun DiscoverFeedLoaded( @OptIn(ExperimentalFoundationApi::class) @Composable private fun DiscoverFeedColumnsLoaded( - state: FeedState.Loaded, + loaded: FeedState.Loaded, routeForLastRead: String?, listState: LazyGridState, forceEventKind: Int?, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { + val items by loaded.feed.collectAsStateWithLifecycle() + LazyVerticalGrid( columns = GridCells.Fixed(2), contentPadding = FeedPadding, state = listState, ) { - itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> + itemsIndexed(items.list, key = { _, item -> item.idHex }) { _, item -> val defaultModifier = remember { Modifier.fillMaxWidth().animateItemPlacement() } Row(defaultModifier) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt index 3490ac696..b01cd84c7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedContentState.kt @@ -23,7 +23,6 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications import android.util.Log import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable -import androidx.compose.runtime.mutableStateOf import com.vitorpamplona.amethyst.model.Account import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.model.Note @@ -34,6 +33,7 @@ import com.vitorpamplona.amethyst.ui.dal.DefaultFeedOrderCard import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent +import com.vitorpamplona.amethyst.ui.feeds.LoadedFeedState import com.vitorpamplona.ammolite.relays.BundledInsert import com.vitorpamplona.ammolite.relays.BundledUpdate import com.vitorpamplona.quartz.events.BadgeAwardEvent @@ -51,7 +51,6 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import java.time.Instant import java.time.ZoneId @@ -109,13 +108,13 @@ class CardFeedContentState( lastAccount = (localFilter as? NotificationFeedFilter)?.account val updatedCards = - (oldNotesState.feed.value + newCards) + (oldNotesState.feed.value.list + newCards) .distinctBy { it.id() } .sortedWith(DefaultFeedOrderCard) .take(localFilter.limit()) .toImmutableList() - if (!equalImmutableLists(oldNotesState.feed.value, updatedCards)) { + if (!equalImmutableLists(oldNotesState.feed.value.list, updatedCards)) { updateFeed(updatedCards) } } @@ -291,21 +290,15 @@ class CardFeedContentState( } private fun updateFeed(notes: ImmutableList) { - viewModelScope.launch(Dispatchers.Main) { - val currentState = _feedContent.value - - if (notes.isEmpty()) { - _feedContent.update { CardFeedState.Empty } - } else if (currentState is CardFeedState.Loaded) { - if (currentState.showHidden.value != localFilter.showHiddenKey()) { - currentState.showHidden.value = localFilter.showHiddenKey() - } - currentState.feed.value = notes - } else { - _feedContent.update { - CardFeedState.Loaded(mutableStateOf(notes), mutableStateOf(localFilter.showHiddenKey())) - } - } + val currentState = _feedContent.value + if (notes.isEmpty()) { + _feedContent.tryEmit(CardFeedState.Empty) + } else if (currentState is CardFeedState.Loaded) { + currentState.feed.tryEmit(LoadedFeedState(notes, localFilter.showHiddenKey())) + } else { + _feedContent.tryEmit( + CardFeedState.Loaded(MutableStateFlow(LoadedFeedState(notes, localFilter.showHiddenKey()))), + ) } } @@ -336,14 +329,14 @@ class CardFeedContentState( lastAccount = (localFilter as? NotificationFeedFilter)?.account val updatedCards = - (oldNotesState.feed.value + newCards) + (oldNotesState.feed.value.list + newCards) .distinctBy { it.id() } .sortedWith(compareBy({ it.createdAt() }, { it.id() })) .reversed() .take(localFilter.limit()) .toImmutableList() - if (!equalImmutableLists(oldNotesState.feed.value, updatedCards)) { + if (!equalImmutableLists(oldNotesState.feed.value.list, updatedCards)) { updateFeed(updatedCards) } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedState.kt index 030bc86ba..c23686910 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedState.kt @@ -21,15 +21,16 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn.notifications import androidx.compose.runtime.Immutable -import androidx.compose.runtime.MutableState import androidx.compose.runtime.Stable import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.firstFullCharOrEmoji +import com.vitorpamplona.amethyst.ui.feeds.LoadedFeedState import com.vitorpamplona.quartz.events.ImmutableListOfLists import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toImmutableMap +import kotlinx.coroutines.flow.MutableStateFlow @Immutable abstract class Card { @@ -119,8 +120,7 @@ sealed class CardFeedState { @Stable class Loaded( - val feed: MutableState>, - val showHidden: MutableState, + val feed: MutableStateFlow>, ) : CardFeedState() @Immutable object Empty : CardFeedState() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedView.kt index 6461e8a8d..44d04a6d7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/notifications/CardFeedView.kt @@ -33,7 +33,6 @@ import androidx.compose.material3.HorizontalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -115,7 +114,7 @@ fun RenderCardFeed( } is CardFeedState.Loaded -> { FeedLoaded( - state = state, + loaded = state, listState = listState, routeForLastRead = routeForLastRead, accountViewModel = accountViewModel, @@ -132,12 +131,14 @@ fun RenderCardFeed( @OptIn(ExperimentalFoundationApi::class) @Composable private fun FeedLoaded( - state: CardFeedState.Loaded, + loaded: CardFeedState.Loaded, listState: LazyListState, routeForLastRead: String, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { + val items by loaded.feed.collectAsStateWithLifecycle() + LazyColumn( modifier = Modifier.fillMaxSize(), contentPadding = FeedPadding, @@ -148,22 +149,15 @@ private fun FeedLoaded( } itemsIndexed( - items = state.feed.value, + items = items.list, key = { _, item -> item.id() }, contentType = { _, item -> item.javaClass.simpleName }, ) { _, item -> - val defaultModifier = - remember { - Modifier - .fillMaxWidth() - .animateItemPlacement() - } - - Row(defaultModifier) { + Row(Modifier.fillMaxWidth().animateItemPlacement()) { RenderCardItem( item, routeForLastRead, - showHidden = state.showHidden.value, + showHidden = items.showHidden, accountViewModel, nav, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt index ee7e5776b..10fc17ab0 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt @@ -32,7 +32,6 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.VerticalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material.icons.Icons @@ -42,7 +41,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.MutableState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -101,7 +99,6 @@ import com.vitorpamplona.amethyst.ui.theme.placeholderText import com.vitorpamplona.quartz.events.FileHeaderEvent import com.vitorpamplona.quartz.events.FileStorageHeaderEvent import com.vitorpamplona.quartz.events.VideoEvent -import kotlinx.collections.immutable.ImmutableList @Composable fun VideoScreen( @@ -183,28 +180,18 @@ fun RenderPage( } @Composable -@OptIn(ExperimentalFoundationApi::class) private fun LoadedState( - state: FeedState.Loaded, + loaded: FeedState.Loaded, pagerStateKey: String?, videoFeedContentState: FeedContentState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val pagerState = - if (pagerStateKey != null) { - rememberForeverPagerState(pagerStateKey) { state.feed.value.size } - } else { - rememberPagerState { state.feed.value.size } - } - - WatchScrollToTop(videoFeedContentState, pagerState) - RefresheableBox(invalidateableContent = videoFeedContentState) { SlidingCarousel( - state.feed, - pagerState, - state.showHidden.value, + loaded, + pagerStateKey, + videoFeedContentState, accountViewModel, nav, ) @@ -214,25 +201,36 @@ private fun LoadedState( @OptIn(ExperimentalFoundationApi::class) @Composable fun SlidingCarousel( - feed: MutableState>, - pagerState: PagerState, - showHidden: Boolean, + loaded: FeedState.Loaded, + pagerStateKey: String?, + videoFeedContentState: FeedContentState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { + val items by loaded.feed.collectAsStateWithLifecycle() + + val pagerState = + if (pagerStateKey != null) { + rememberForeverPagerState(pagerStateKey, items.list.size) { items.list.size } + } else { + rememberPagerState(items.list.size) { items.list.size } + } + + WatchScrollToTop(videoFeedContentState, pagerState) + VerticalPager( state = pagerState, beyondBoundsPageCount = 1, modifier = Modifier.fillMaxSize(), - key = { index -> feed.value.getOrNull(index)?.idHex ?: "$index" }, + key = { index -> items.list.getOrNull(index)?.idHex ?: "$index" }, ) { index -> - feed.value.getOrNull(index)?.let { note -> + items.list.getOrNull(index)?.let { note -> Box(Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { CheckHiddenFeedWatchBlockAndReport( note = note, modifier = Modifier.fillMaxWidth(), showHiddenWarning = true, - ignoreAllBlocksAndReports = showHidden, + ignoreAllBlocksAndReports = items.showHidden, accountViewModel = accountViewModel, nav = nav, ) {