From c32dc61ea10ab7b0a142d3a965dea6f5ba540d49 Mon Sep 17 00:00:00 2001 From: Vitor Pamplona Date: Tue, 13 Aug 2024 17:32:15 -0400 Subject: [PATCH] Refactoring Moving feed status from ViewModel to State objects --- .../amethyst/ui/actions/NewPostView.kt | 4 +- .../ui/feeds/AccountFeedContentStates.kt | 52 +++++ .../amethyst/ui/feeds/FeedContentState.kt | 182 ++++++++++++++++++ .../amethyst/ui/feeds/FeedContentStateView.kt | 143 ++++++++++++++ .../amethyst/ui/feeds/FeedEmpty.kt | 47 +++++ .../amethyst/ui/feeds/FeedError.kt | 56 ++++++ .../amethyst/ui/feeds/FeedLoaded.kt | 69 +++++++ .../ui/{screen => feeds}/FeedState.kt | 2 +- .../amethyst/ui/feeds/InvalidatableContent.kt | 25 +++ .../amethyst/ui/feeds/LoadingFeed.kt | 43 +++++ .../amethyst/ui/feeds/RefresheableBox.kt | 81 ++++++++ .../RememberForeverStates.kt | 2 +- .../amethyst/ui/navigation/AppNavigation.kt | 58 ++---- .../amethyst/ui/navigation/AppTopBar.kt | 14 +- .../amethyst/ui/note/ChannelCardCompose.kt | 14 +- .../amethyst/ui/note/NoteCompose.kt | 2 +- .../amethyst/ui/note/types/ChannelMessage.kt | 2 +- .../amethyst/ui/note/types/ChatMessage.kt | 2 +- .../amethyst/ui/note/types/CommunityHeader.kt | 6 +- .../amethyst/ui/note/types/LiveActivity.kt | 9 +- .../ui/note/types/LiveActivityChatMessage.kt | 2 +- .../amethyst/ui/note/types/PrivateMessage.kt | 2 +- .../amethyst/ui/screen/CardFeedView.kt | 5 + .../amethyst/ui/screen/CardFeedViewModel.kt | 3 +- .../amethyst/ui/screen/FeedView.kt | 167 ++-------------- .../amethyst/ui/screen/FeedViewModel.kt | 109 +---------- .../amethyst/ui/screen/LnZapFeedView.kt | 3 + .../amethyst/ui/screen/RelayFeedView.kt | 4 +- .../amethyst/ui/screen/StringFeedView.kt | 2 + .../amethyst/ui/screen/StringFeedViewModel.kt | 3 +- .../amethyst/ui/screen/ThreadFeedView.kt | 6 +- .../amethyst/ui/screen/UserFeedView.kt | 4 + .../amethyst/ui/screen/UserFeedViewModel.kt | 7 +- .../ui/screen/loggedIn/AccountViewModel.kt | 3 + .../ui/screen/loggedIn/DraftListScreen.kt | 6 +- .../ui/screen/loggedIn/HiddenUsersScreen.kt | 2 +- .../amethyst/ui/screen/loggedIn/MainScreen.kt | 116 +---------- .../ui/screen/loggedIn/NotificationScreen.kt | 2 +- .../ui/screen/loggedIn/ProfileGallery.kt | 8 +- .../ui/screen/loggedIn/ProfileScreen.kt | 6 +- .../loggedIn/{ => chatrooms}/ChannelScreen.kt | 7 +- .../chatrooms}/ChatroomFeedView.kt | 9 +- .../chatrooms}/ChatroomListFeedView.kt | 20 +- .../{ => chatrooms}/ChatroomListScreen.kt | 52 +++-- .../{ => chatrooms}/ChatroomScreen.kt | 5 +- .../{ => discover}/CommunityScreen.kt | 3 +- .../loggedIn/{ => discover}/DiscoverScreen.kt | 119 ++++++------ .../NIP90ContentDiscoveryScreen.kt | 7 +- .../screen/loggedIn/{ => home}/HomeScreen.kt | 62 +++--- .../loggedIn/{ => video}/VideoScreen.kt | 55 +++--- 50 files changed, 986 insertions(+), 626 deletions(-) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/AccountFeedContentStates.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentState.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentStateView.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedEmpty.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedError.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedLoaded.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{screen => feeds}/FeedState.kt (97%) create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/InvalidatableContent.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/LoadingFeed.kt create mode 100644 amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RefresheableBox.kt rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/{screen => feeds}/RememberForeverStates.kt (99%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/{ => chatrooms}/ChannelScreen.kt (99%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/{ => loggedIn/chatrooms}/ChatroomFeedView.kt (93%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/{ => loggedIn/chatrooms}/ChatroomListFeedView.kt (82%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/{ => chatrooms}/ChatroomListScreen.kt (88%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/{ => chatrooms}/ChatroomScreen.kt (99%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/{ => discover}/CommunityScreen.kt (96%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/{ => discover}/DiscoverScreen.kt (76%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/{ => discover}/NIP90ContentDiscoveryScreen.kt (98%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/{ => home}/HomeScreen.kt (84%) rename amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/{ => video}/VideoScreen.kt (89%) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt index 804e91422..3601976ab 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/actions/NewPostView.kt @@ -145,10 +145,10 @@ import com.vitorpamplona.amethyst.ui.note.RegularPostIcon import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.note.ZapSplitIcon import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.MyTextField -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShowUserSuggestionList import com.vitorpamplona.amethyst.ui.screen.loggedIn.TextSpinner import com.vitorpamplona.amethyst.ui.screen.loggedIn.TitleExplainer +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.MyTextField +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ShowUserSuggestionList import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.ButtonBorder diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/AccountFeedContentStates.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/AccountFeedContentStates.kt new file mode 100644 index 000000000..c2e3bec13 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/AccountFeedContentStates.kt @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2024 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.feeds + +import androidx.lifecycle.viewModelScope +import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter +import com.vitorpamplona.amethyst.ui.dal.ChatroomListNewFeedFilter +import com.vitorpamplona.amethyst.ui.dal.DiscoverChatFeedFilter +import com.vitorpamplona.amethyst.ui.dal.DiscoverCommunityFeedFilter +import com.vitorpamplona.amethyst.ui.dal.DiscoverLiveFeedFilter +import com.vitorpamplona.amethyst.ui.dal.DiscoverMarketplaceFeedFilter +import com.vitorpamplona.amethyst.ui.dal.DiscoverNIP89FeedFilter +import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter +import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter +import com.vitorpamplona.amethyst.ui.dal.VideoFeedFilter +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel + +class AccountFeedContentStates( + val accountViewModel: AccountViewModel, +) { + val homeNewThreads = FeedContentState(HomeNewThreadFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) + val homeReplies = FeedContentState(HomeConversationsFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) + + val dmKnown = FeedContentState(ChatroomListKnownFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) + val dmNew = FeedContentState(ChatroomListNewFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) + + val videoFeed = FeedContentState(VideoFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) + + val discoverMarketplace = FeedContentState(DiscoverMarketplaceFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) + val discoverDVMs = FeedContentState(DiscoverNIP89FeedFilter(accountViewModel.account), accountViewModel.viewModelScope) + val discoverLive = FeedContentState(DiscoverLiveFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) + val discoverCommunities = FeedContentState(DiscoverCommunityFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) + val discoverPublicChats = FeedContentState(DiscoverChatFeedFilter(accountViewModel.account), accountViewModel.viewModelScope) +} 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 new file mode 100644 index 000000000..ec9841664 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentState.kt @@ -0,0 +1,182 @@ +/** + * Copyright (c) 2024 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.feeds + +import androidx.compose.runtime.Stable +import androidx.compose.runtime.mutableStateOf +import com.vitorpamplona.amethyst.model.Note +import com.vitorpamplona.amethyst.service.checkNotInMainThread +import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter +import com.vitorpamplona.amethyst.ui.dal.FeedFilter +import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists +import com.vitorpamplona.ammolite.relays.BundledInsert +import com.vitorpamplona.ammolite.relays.BundledUpdate +import com.vitorpamplona.quartz.events.DeletionEvent +import kotlinx.collections.immutable.ImmutableList +import kotlinx.collections.immutable.toImmutableList +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 +class FeedContentState( + val localFilter: FeedFilter, + val viewModelScope: CoroutineScope, +) : InvalidatableContent { + private val _feedContent = MutableStateFlow(FeedState.Loading) + val feedContent = _feedContent.asStateFlow() + + // Simple counter that changes when it needs to invalidate everything + private val _scrollToTop = MutableStateFlow(0) + val scrollToTop = _scrollToTop.asStateFlow() + var scrolltoTopPending = false + + private var lastFeedKey: String? = null + + fun sendToTop() { + if (scrolltoTopPending) return + + scrolltoTopPending = true + viewModelScope.launch(Dispatchers.IO) { _scrollToTop.emit(_scrollToTop.value + 1) } + } + + suspend fun sentToTop() { + scrolltoTopPending = false + } + + private fun refresh() { + viewModelScope.launch(Dispatchers.Default) { refreshSuspended() } + } + + fun refreshSuspended() { + checkNotInMainThread() + + lastFeedKey = localFilter.feedKey() + val notes = localFilter.loadTop().distinctBy { it.idHex }.toImmutableList() + + val oldNotesState = _feedContent.value + if (oldNotesState is FeedState.Loaded) { + if (!equalImmutableLists(notes, oldNotesState.feed.value)) { + updateFeed(notes) + } + } else { + updateFeed(notes) + } + } + + 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())) + } + } + } + } + + fun refreshFromOldState(newItems: Set) { + val oldNotesState = _feedContent.value + if (localFilter is AdditiveFeedFilter && lastFeedKey == localFilter.feedKey()) { + if (oldNotesState is FeedState.Loaded) { + val deletionEvents: List = + newItems.mapNotNull { + val noteEvent = it.event + if (noteEvent is DeletionEvent) noteEvent else null + } + + val oldList = + if (deletionEvents.isEmpty()) { + oldNotesState.feed.value + } else { + val deletedEventIds = deletionEvents.flatMapTo(HashSet()) { it.deleteEvents() } + val deletedEventAddresses = deletionEvents.flatMapTo(HashSet()) { it.deleteAddresses() } + oldNotesState.feed.value + .filter { !it.wasOrShouldBeDeletedBy(deletedEventIds, deletedEventAddresses) } + .toImmutableList() + } + + val newList = + localFilter + .updateListWith(oldList, newItems) + .distinctBy { it.idHex } + .toImmutableList() + if (!equalImmutableLists(newList, oldNotesState.feed.value)) { + updateFeed(newList) + } + } else if (oldNotesState is FeedState.Empty) { + val newList = + localFilter + .updateListWith(emptyList(), newItems) + .distinctBy { it.idHex } + .toImmutableList() + if (newList.isNotEmpty()) { + updateFeed(newList) + } + } else { + // Refresh Everything + refreshSuspended() + } + } else { + // Refresh Everything + refreshSuspended() + } + } + + private val bundler = BundledUpdate(250, Dispatchers.IO) + private val bundlerInsert = BundledInsert>(250, Dispatchers.IO) + + override fun invalidateData(ignoreIfDoing: Boolean) { + viewModelScope.launch(Dispatchers.IO) { + bundler.invalidate(ignoreIfDoing) { + // adds the time to perform the refresh into this delay + // holding off new updates in case of heavy refresh routines. + refreshSuspended() + } + } + } + + fun checkKeysInvalidateDataAndSendToTop() { + if (lastFeedKey != localFilter.feedKey()) { + bundler.invalidate(false) { + // adds the time to perform the refresh into this delay + // holding off new updates in case of heavy refresh routines. + refreshSuspended() + sendToTop() + } + } + } + + fun invalidateInsertData(newItems: Set) { + bundlerInsert.invalidateList(newItems) { refreshFromOldState(it.flatten().toSet()) } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentStateView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentStateView.kt new file mode 100644 index 000000000..930899da8 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedContentStateView.kt @@ -0,0 +1,143 @@ +/** + * Copyright (c) 2024 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.feeds + +import androidx.compose.animation.core.tween +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.grid.LazyGridState +import androidx.compose.foundation.lazy.grid.rememberLazyGridState +import androidx.compose.foundation.lazy.rememberLazyListState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel + +@Composable +fun RefresheableFeedContentStateView( + feedContentState: FeedContentState, + routeForLastRead: String?, + enablePullRefresh: Boolean = true, + scrollStateKey: String? = null, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + RefresheableBox(feedContentState, enablePullRefresh) { + SaveableFeedContentState(feedContentState, scrollStateKey) { listState -> + RenderFeedContentState(feedContentState, accountViewModel, listState, nav, routeForLastRead) + } + } +} + +@Composable +fun SaveableFeedContentState( + feedContentState: FeedContentState, + scrollStateKey: String? = null, + content: @Composable (LazyListState) -> Unit, +) { + val listState = + if (scrollStateKey != null) { + rememberForeverLazyListState(scrollStateKey) + } else { + rememberLazyListState() + } + + WatchScrollToTopFeedContentState(feedContentState, listState) + + content(listState) +} + +@Composable +fun SaveableGridFeedContentState( + feedContentState: FeedContentState, + scrollStateKey: String? = null, + content: @Composable (LazyGridState) -> Unit, +) { + val gridState = + if (scrollStateKey != null) { + rememberForeverLazyGridState(scrollStateKey) + } else { + rememberLazyGridState() + } + + WatchScrollToTopFeedContentState(feedContentState, gridState) + + content(gridState) +} + +@Composable +fun RenderFeedContentState( + feedContentState: FeedContentState, + accountViewModel: AccountViewModel, + listState: LazyListState, + nav: (String) -> Unit, + routeForLastRead: String?, + onLoaded: @Composable (FeedState.Loaded) -> Unit = { FeedLoaded(it, listState, routeForLastRead, accountViewModel, nav) }, + onEmpty: @Composable () -> Unit = { FeedEmpty(feedContentState::invalidateData) }, + onError: @Composable (String) -> Unit = { FeedError(it, feedContentState::invalidateData) }, + onLoading: @Composable () -> Unit = { LoadingFeed() }, +) { + val feedState by feedContentState.feedContent.collectAsStateWithLifecycle() + + CrossfadeIfEnabled( + targetState = feedState, + animationSpec = tween(durationMillis = 100), + accountViewModel = accountViewModel, + ) { state -> + when (state) { + is FeedState.Empty -> onEmpty() + is FeedState.FeedError -> onError(state.errorMessage) + is FeedState.Loaded -> onLoaded(state) + is FeedState.Loading -> onLoading() + } + } +} + +@Composable +private fun WatchScrollToTopFeedContentState( + feedContentState: FeedContentState, + listState: LazyListState, +) { + val scrollToTop by feedContentState.scrollToTop.collectAsStateWithLifecycle() + + LaunchedEffect(scrollToTop) { + if (scrollToTop > 0 && feedContentState.scrolltoTopPending) { + listState.scrollToItem(index = 0) + feedContentState.sentToTop() + } + } +} + +@Composable +private fun WatchScrollToTopFeedContentState( + feedContentState: FeedContentState, + listState: LazyGridState, +) { + val scrollToTop by feedContentState.scrollToTop.collectAsStateWithLifecycle() + + LaunchedEffect(scrollToTop) { + if (scrollToTop > 0 && feedContentState.scrolltoTopPending) { + listState.scrollToItem(index = 0) + feedContentState.sentToTop() + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedEmpty.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedEmpty.kt new file mode 100644 index 000000000..5e4349e41 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedEmpty.kt @@ -0,0 +1,47 @@ +/** + * Copyright (c) 2024 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.feeds + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.OutlinedButton +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer + +@Composable +fun FeedEmpty(onRefresh: () -> Unit) { + Column( + Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text(stringRes(R.string.feed_is_empty)) + Spacer(modifier = StdVertSpacer) + OutlinedButton(onClick = onRefresh) { Text(text = stringRes(R.string.refresh)) } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedError.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedError.kt new file mode 100644 index 000000000..596c94837 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedError.kt @@ -0,0 +1,56 @@ +/** + * Copyright (c) 2024 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.feeds + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes +import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer + +@Composable +fun FeedError( + errorMessage: String, + onRefresh: () -> Unit, +) { + Column( + Modifier.fillMaxHeight().fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text("${stringRes(R.string.error_loading_replies)} $errorMessage") + Spacer(modifier = StdVertSpacer) + Button( + modifier = Modifier.align(Alignment.CenterHorizontally), + onClick = onRefresh, + ) { + Text(text = stringRes(R.string.try_again)) + } + } +} 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 new file mode 100644 index 000000000..0242420a9 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedLoaded.kt @@ -0,0 +1,69 @@ +/** + * Copyright (c) 2024 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.feeds + +import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.lazy.LazyColumn +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.ui.Modifier +import com.vitorpamplona.amethyst.ui.note.NoteCompose +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.theme.DividerThickness +import com.vitorpamplona.amethyst.ui.theme.FeedPadding + +@OptIn(ExperimentalFoundationApi::class) +@Composable +fun FeedLoaded( + state: FeedState.Loaded, + listState: LazyListState, + routeForLastRead: String?, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + LazyColumn( + contentPadding = FeedPadding, + state = listState, + ) { + itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> + Row(Modifier.fillMaxWidth().animateItemPlacement()) { + NoteCompose( + item, + routeForLastRead = routeForLastRead, + modifier = Modifier.fillMaxWidth(), + isBoostedNote = false, + isHiddenFeed = state.showHidden.value, + quotesLeft = 3, + accountViewModel = accountViewModel, + nav = nav, + ) + } + + HorizontalDivider( + thickness = DividerThickness, + ) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedState.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedState.kt similarity index 97% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedState.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedState.kt index 3e92c707a..bd865915e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedState.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/FeedState.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.feeds import androidx.compose.runtime.MutableState import com.vitorpamplona.amethyst.model.Note diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/InvalidatableContent.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/InvalidatableContent.kt new file mode 100644 index 000000000..4837aaa4a --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/InvalidatableContent.kt @@ -0,0 +1,25 @@ +/** + * Copyright (c) 2024 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.feeds + +interface InvalidatableContent { + fun invalidateData(ignoreIfDoing: Boolean = false) +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/LoadingFeed.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/LoadingFeed.kt new file mode 100644 index 000000000..006834c90 --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/LoadingFeed.kt @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2024 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.feeds + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.stringRes + +@Composable +fun LoadingFeed() { + Column( + Modifier.fillMaxHeight().fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text(stringRes(R.string.loading_feed)) + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RefresheableBox.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RefresheableBox.kt new file mode 100644 index 000000000..bf7d22e9b --- /dev/null +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RefresheableBox.kt @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2024 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.feeds + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.pullrefresh.PullRefreshIndicator +import androidx.compose.material3.pullrefresh.pullRefresh +import androidx.compose.material3.pullrefresh.rememberPullRefreshState +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun RefresheableBox( + invalidateableContent: InvalidatableContent, + enablePullRefresh: Boolean = true, + content: @Composable () -> Unit, +) { + RefresheableBox( + enablePullRefresh = enablePullRefresh, + onRefresh = { invalidateableContent.invalidateData() }, + content = content, + ) +} + +@Composable +fun RefresheableBox( + enablePullRefresh: Boolean = true, + onRefresh: () -> Unit, + content: @Composable () -> Unit, +) { + var refreshing by remember { mutableStateOf(false) } + val refresh = { + refreshing = true + onRefresh() + refreshing = false + } + val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) + + val modifier = + if (enablePullRefresh) { + Modifier.fillMaxSize().pullRefresh(pullRefreshState) + } else { + Modifier.fillMaxSize() + } + + Box(modifier) { + content() + + if (enablePullRefresh) { + PullRefreshIndicator( + refreshing = refreshing, + state = pullRefreshState, + modifier = Modifier.align(Alignment.TopCenter), + ) + } + } +} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/RememberForeverStates.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/RememberForeverStates.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt index c44bafda9..db08e6770 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/RememberForeverStates.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/feeds/RememberForeverStates.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.feeds import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.lazy.LazyListState diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt index c023f0ebe..c9e5bb963 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppNavigation.kt @@ -42,39 +42,29 @@ import androidx.navigation.compose.composable import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.MainActivity import com.vitorpamplona.amethyst.ui.note.UserReactionsViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverMarketplaceFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverNIP89FeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.BookmarkListScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomListScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomScreenByAuthor -import com.vitorpamplona.amethyst.ui.screen.loggedIn.CommunityScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.DiscoverScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.DraftListScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.GeoHashScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.HashtagScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.HiddenUsersScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.HomeScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadRedirectScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.NIP90ContentDiscoveryScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.NotificationScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ProfileScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.SearchScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.SettingsScreen import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThreadScreen -import com.vitorpamplona.amethyst.ui.screen.loggedIn.VideoScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChatroomListScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChatroomScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChatroomScreenByAuthor +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.CommunityScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.DiscoverScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.NIP90ContentDiscoveryScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.HomeScreen +import com.vitorpamplona.amethyst.ui.screen.loggedIn.video.VideoScreen import com.vitorpamplona.amethyst.ui.uriToRoute import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -82,16 +72,6 @@ import java.net.URLDecoder @Composable fun AppNavigation( - homeFeedViewModel: NostrHomeFeedViewModel, - repliesFeedViewModel: NostrHomeRepliesFeedViewModel, - knownFeedViewModel: NostrChatroomListKnownFeedViewModel, - newFeedViewModel: NostrChatroomListNewFeedViewModel, - videoFeedViewModel: NostrVideoFeedViewModel, - discoverMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel, - discoverNip89FeedViewModel: NostrDiscoverNIP89FeedViewModel, - discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel, - discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel, - discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel, notifFeedViewModel: NotificationViewModel, userReactionsStatsModel: UserReactionsViewModel, navController: NavHostController, @@ -125,8 +105,8 @@ fun AppNavigation( val nip47 = it.arguments?.getString("nip47") HomeScreen( - homeFeedViewModel = homeFeedViewModel, - repliesFeedViewModel = repliesFeedViewModel, + newThreadsFeedState = accountViewModel.feedStates.homeNewThreads, + repliesFeedState = accountViewModel.feedStates.homeNewThreads, accountViewModel = accountViewModel, nav = nav, nip47 = nip47, @@ -148,8 +128,8 @@ fun AppNavigation( Route.Message.route, content = { ChatroomListScreen( - knownFeedViewModel, - newFeedViewModel, + accountViewModel.feedStates.dmKnown, + accountViewModel.feedStates.dmNew, accountViewModel, nav, ) @@ -162,7 +142,7 @@ fun AppNavigation( route.arguments, content = { VideoScreen( - videoFeedView = videoFeedViewModel, + videoFeedContentState = accountViewModel.feedStates.videoFeed, accountViewModel = accountViewModel, nav = nav, ) @@ -176,11 +156,11 @@ fun AppNavigation( route.arguments, content = { DiscoverScreen( - discoveryContentNIP89FeedViewModel = discoverNip89FeedViewModel, - discoveryMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel, - discoveryLiveFeedViewModel = discoveryLiveFeedViewModel, - discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel, - discoveryChatFeedViewModel = discoveryChatFeedViewModel, + discoveryContentNIP89FeedContentState = accountViewModel.feedStates.discoverDVMs, + discoveryMarketplaceFeedContentState = accountViewModel.feedStates.discoverMarketplace, + discoveryLiveFeedContentState = accountViewModel.feedStates.discoverLive, + discoveryCommunityFeedContentState = accountViewModel.feedStates.discoverCommunities, + discoveryChatFeedContentState = accountViewModel.feedStates.discoverPublicChats, accountViewModel = accountViewModel, nav = nav, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt index 0c347ca2f..db63dfed2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/navigation/AppTopBar.kt @@ -121,14 +121,14 @@ import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.screen.loggedIn.DislayGeoTagHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.GeoHashActionOptions import com.vitorpamplona.amethyst.ui.screen.loggedIn.HashtagActionOptions -import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadRoom -import com.vitorpamplona.amethyst.ui.screen.loggedIn.LoadRoomByAuthor -import com.vitorpamplona.amethyst.ui.screen.loggedIn.LongChannelHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.LongRoomHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.RoomNameOnlyDisplay -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ShortChannelHeader import com.vitorpamplona.amethyst.ui.screen.loggedIn.SpinnerSelectionDialog -import com.vitorpamplona.amethyst.ui.screen.loggedIn.observeAppDefinition +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LoadRoom +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LoadRoomByAuthor +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LongChannelHeader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LongRoomHeader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.RoomNameOnlyDisplay +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ShortChannelHeader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.observeAppDefinition import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BottomTopHeight import com.vitorpamplona.amethyst.ui.theme.DividerThickness diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt index 95ab46759..7c1d6541c 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/ChannelCardCompose.kt @@ -73,13 +73,13 @@ import com.vitorpamplona.amethyst.ui.layouts.LeftPictureLayout import com.vitorpamplona.amethyst.ui.note.elements.BannerImage import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.CheckIfUrlIsOnline -import com.vitorpamplona.amethyst.ui.screen.loggedIn.EndedFlag -import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag -import com.vitorpamplona.amethyst.ui.screen.loggedIn.OfflineFlag -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag -import com.vitorpamplona.amethyst.ui.screen.loggedIn.observeAppDefinition +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelHeader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.EndedFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LiveFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.OfflineFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ScheduledFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.discover.observeAppDefinition +import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CheckIfUrlIsOnline import com.vitorpamplona.amethyst.ui.screen.loggedIn.showAmountAxis import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.HalfPadding diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt index b77ee2cdc..142adf506 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/NoteCompose.kt @@ -114,7 +114,7 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderTextModificationEvent import com.vitorpamplona.amethyst.ui.note.types.RenderWikiContent import com.vitorpamplona.amethyst.ui.note.types.VideoDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.RenderChannelHeader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.RenderChannelHeader import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.Font12SP diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChannelMessage.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChannelMessage.kt index 63eacda3f..34b42d4ec 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChannelMessage.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChannelMessage.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelHeader import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.quartz.events.ChannelMessageEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChatMessage.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChatMessage.kt index e581c7cfd..02039d96a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChatMessage.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/ChatMessage.kt @@ -35,7 +35,7 @@ import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomHeader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChatroomHeader import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.quartz.events.ChatroomKeyable diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt index 99c355e37..243c2835a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/CommunityHeader.kt @@ -62,9 +62,9 @@ import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.JoinCommunityButton -import com.vitorpamplona.amethyst.ui.screen.loggedIn.LeaveCommunityButton -import com.vitorpamplona.amethyst.ui.screen.loggedIn.NormalTimeAgo +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.JoinCommunityButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LeaveCommunityButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.NormalTimeAgo import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer import com.vitorpamplona.amethyst.ui.theme.HeaderPictureModifier diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt index 8db7ec845..9c3aa9e52 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivity.kt @@ -44,7 +44,6 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.min import coil.compose.AsyncImage import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.model.LocalCache @@ -58,10 +57,10 @@ import com.vitorpamplona.amethyst.ui.note.DisplayAuthorBanner import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.CheckIfUrlIsOnline -import com.vitorpamplona.amethyst.ui.screen.loggedIn.CrossfadeCheckIfUrlIsOnline -import com.vitorpamplona.amethyst.ui.screen.loggedIn.LiveFlag -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ScheduledFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.LiveFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ScheduledFlag +import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CheckIfUrlIsOnline +import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CrossfadeCheckIfUrlIsOnline import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.StdHorzSpacer import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonColumn diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivityChatMessage.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivityChatMessage.kt index 414b20103..403053983 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivityChatMessage.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/LiveActivityChatMessage.kt @@ -32,7 +32,7 @@ import androidx.compose.ui.unit.dp import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.components.GenericLoadable import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelHeader import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.replyModifier import com.vitorpamplona.quartz.events.LiveActivitiesChatMessageEvent diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PrivateMessage.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PrivateMessage.kt index a8511fb2d..1b406040b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PrivateMessage.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/note/types/PrivateMessage.kt @@ -42,7 +42,7 @@ import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.LoadDecryptedContent import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChatroomHeader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChatroomHeader import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.placeholderText diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt index ead1f9cd9..8f1f09bf3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedView.kt @@ -42,6 +42,11 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.BuildConfig import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.LoadNote +import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty +import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox +import com.vitorpamplona.amethyst.ui.feeds.rememberForeverLazyListState import com.vitorpamplona.amethyst.ui.note.BadgeCompose import com.vitorpamplona.amethyst.ui.note.MessageSetCompose import com.vitorpamplona.amethyst.ui.note.MultiSetCompose diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt index fa8642af1..123b8cf66 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/CardFeedViewModel.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.dal.AdditiveFeedFilter import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.NotificationFeedFilter +import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent import com.vitorpamplona.ammolite.relays.BundledInsert import com.vitorpamplona.ammolite.relays.BundledUpdate import com.vitorpamplona.quartz.events.BadgeAwardEvent @@ -74,7 +75,7 @@ class NotificationViewModel( open class CardFeedViewModel( val localFilter: FeedFilter, ) : ViewModel(), - InvalidatableViewModel { + InvalidatableContent { private val _feedContent = MutableStateFlow(CardFeedState.Loading) val feedContent = _feedContent.asStateFlow() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index fba565584..c1b644f01 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -21,45 +21,23 @@ package com.vitorpamplona.amethyst.ui.screen import androidx.compose.animation.core.tween -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.grid.LazyGridState import androidx.compose.foundation.lazy.grid.rememberLazyGridState -import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material3.Button -import androidx.compose.material3.HorizontalDivider -import androidx.compose.material3.OutlinedButton -import androidx.compose.material3.Text -import androidx.compose.material3.pullrefresh.PullRefreshIndicator -import androidx.compose.material3.pullrefresh.pullRefresh -import androidx.compose.material3.pullrefresh.rememberPullRefreshState 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.Alignment -import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle -import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled -import com.vitorpamplona.amethyst.ui.note.NoteCompose +import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty +import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.FeedState +import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox +import com.vitorpamplona.amethyst.ui.feeds.rememberForeverLazyGridState +import com.vitorpamplona.amethyst.ui.feeds.rememberForeverLazyListState import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.stringRes -import com.vitorpamplona.amethyst.ui.theme.DividerThickness -import com.vitorpamplona.amethyst.ui.theme.FeedPadding -import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer @Composable fun RefresheableFeedView( @@ -77,53 +55,6 @@ fun RefresheableFeedView( } } -@Composable -fun RefresheableBox( - viewModel: InvalidatableViewModel, - enablePullRefresh: Boolean = true, - content: @Composable () -> Unit, -) { - RefresheableBox( - enablePullRefresh = enablePullRefresh, - onRefresh = { viewModel.invalidateData() }, - content = content, - ) -} - -@Composable -fun RefresheableBox( - enablePullRefresh: Boolean = true, - onRefresh: () -> Unit, - content: @Composable () -> Unit, -) { - var refreshing by remember { mutableStateOf(false) } - val refresh = { - refreshing = true - onRefresh() - refreshing = false - } - val pullRefreshState = rememberPullRefreshState(refreshing, onRefresh = refresh) - - val modifier = - if (enablePullRefresh) { - Modifier.fillMaxSize().pullRefresh(pullRefreshState) - } else { - Modifier.fillMaxSize() - } - - Box(modifier) { - content() - - if (enablePullRefresh) { - PullRefreshIndicator( - refreshing = refreshing, - state = pullRefreshState, - modifier = Modifier.align(Alignment.TopCenter), - ) - } - } -} - @Composable fun SaveableFeedState( viewModel: FeedViewModel, @@ -167,7 +98,10 @@ fun RenderFeedState( listState: LazyListState, nav: (String) -> Unit, routeForLastRead: String?, - onLoaded: @Composable (FeedState.Loaded) -> Unit = { FeedLoaded(it, listState, routeForLastRead, accountViewModel, nav) }, + onLoaded: @Composable (FeedState.Loaded) -> Unit = { + com.vitorpamplona.amethyst.ui.feeds + .FeedLoaded(it, listState, routeForLastRead, accountViewModel, nav) + }, onEmpty: @Composable () -> Unit = { FeedEmpty { viewModel.invalidateData() } }, onError: @Composable (String) -> Unit = { FeedError(it) { viewModel.invalidateData() } }, onLoading: @Composable () -> Unit = { LoadingFeed() }, @@ -217,82 +151,3 @@ private fun WatchScrollToTop( } } } - -@OptIn(ExperimentalFoundationApi::class) -@Composable -private fun FeedLoaded( - state: FeedState.Loaded, - listState: LazyListState, - routeForLastRead: String?, - accountViewModel: AccountViewModel, - nav: (String) -> Unit, -) { - LazyColumn( - contentPadding = FeedPadding, - state = listState, - ) { - itemsIndexed(state.feed.value, key = { _, item -> item.idHex }) { _, item -> - Row(Modifier.fillMaxWidth().animateItemPlacement()) { - NoteCompose( - item, - routeForLastRead = routeForLastRead, - modifier = Modifier.fillMaxWidth(), - isBoostedNote = false, - isHiddenFeed = state.showHidden.value, - quotesLeft = 3, - accountViewModel = accountViewModel, - nav = nav, - ) - } - - HorizontalDivider( - thickness = DividerThickness, - ) - } - } -} - -@Composable -fun LoadingFeed() { - Column( - Modifier.fillMaxHeight().fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(stringRes(R.string.loading_feed)) - } -} - -@Composable -fun FeedError( - errorMessage: String, - onRefresh: () -> Unit, -) { - Column( - Modifier.fillMaxHeight().fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text("${stringRes(R.string.error_loading_replies)} $errorMessage") - Spacer(modifier = StdVertSpacer) - Button( - modifier = Modifier.align(Alignment.CenterHorizontally), - onClick = onRefresh, - ) { - Text(text = stringRes(R.string.try_again)) - } - } -} - -@Composable -fun FeedEmpty(onRefresh: () -> Unit) { - Column( - Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, - ) { - Text(stringRes(R.string.feed_is_empty)) - Spacer(modifier = StdVertSpacer) - OutlinedButton(onClick = onRefresh) { Text(text = stringRes(R.string.refresh)) } - } -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt index 3343d89d7..ca39f7797 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedViewModel.kt @@ -38,20 +38,11 @@ import com.vitorpamplona.amethyst.ui.dal.BookmarkPrivateFeedFilter import com.vitorpamplona.amethyst.ui.dal.BookmarkPublicFeedFilter import com.vitorpamplona.amethyst.ui.dal.ChannelFeedFilter import com.vitorpamplona.amethyst.ui.dal.ChatroomFeedFilter -import com.vitorpamplona.amethyst.ui.dal.ChatroomListKnownFeedFilter -import com.vitorpamplona.amethyst.ui.dal.ChatroomListNewFeedFilter import com.vitorpamplona.amethyst.ui.dal.CommunityFeedFilter -import com.vitorpamplona.amethyst.ui.dal.DiscoverChatFeedFilter -import com.vitorpamplona.amethyst.ui.dal.DiscoverCommunityFeedFilter -import com.vitorpamplona.amethyst.ui.dal.DiscoverLiveFeedFilter -import com.vitorpamplona.amethyst.ui.dal.DiscoverMarketplaceFeedFilter -import com.vitorpamplona.amethyst.ui.dal.DiscoverNIP89FeedFilter import com.vitorpamplona.amethyst.ui.dal.DraftEventsFeedFilter import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.GeoHashFeedFilter import com.vitorpamplona.amethyst.ui.dal.HashtagFeedFilter -import com.vitorpamplona.amethyst.ui.dal.HomeConversationsFeedFilter -import com.vitorpamplona.amethyst.ui.dal.HomeNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.NIP90ContentDiscoveryResponseFilter import com.vitorpamplona.amethyst.ui.dal.ThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileAppRecommendationsFeedFilter @@ -61,6 +52,8 @@ import com.vitorpamplona.amethyst.ui.dal.UserProfileGalleryFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileNewThreadFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter import com.vitorpamplona.amethyst.ui.dal.VideoFeedFilter +import com.vitorpamplona.amethyst.ui.feeds.FeedState +import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent import com.vitorpamplona.ammolite.relays.BundledInsert import com.vitorpamplona.ammolite.relays.BundledUpdate import com.vitorpamplona.quartz.events.ChatroomKey @@ -109,60 +102,6 @@ class NostrVideoFeedViewModel( } } -class NostrDiscoverMarketplaceFeedViewModel( - val account: Account, -) : FeedViewModel( - DiscoverMarketplaceFeedFilter(account), - ) { - class Factory( - val account: Account, - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): NostrDiscoverMarketplaceFeedViewModel = NostrDiscoverMarketplaceFeedViewModel(account) as NostrDiscoverMarketplaceFeedViewModel - } -} - -class NostrDiscoverNIP89FeedViewModel( - val account: Account, -) : FeedViewModel( - DiscoverNIP89FeedFilter(account), - ) { - class Factory( - val account: Account, - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): NostrDiscoverNIP89FeedViewModel = NostrDiscoverNIP89FeedViewModel(account) as NostrDiscoverNIP89FeedViewModel - } -} - -class NostrDiscoverLiveFeedViewModel( - val account: Account, -) : FeedViewModel(DiscoverLiveFeedFilter(account)) { - class Factory( - val account: Account, - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): NostrDiscoverLiveFeedViewModel = NostrDiscoverLiveFeedViewModel(account) as NostrDiscoverLiveFeedViewModel - } -} - -class NostrDiscoverCommunityFeedViewModel( - val account: Account, -) : FeedViewModel(DiscoverCommunityFeedFilter(account)) { - class Factory( - val account: Account, - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): NostrDiscoverCommunityFeedViewModel = NostrDiscoverCommunityFeedViewModel(account) as NostrDiscoverCommunityFeedViewModel - } -} - -class NostrDiscoverChatFeedViewModel( - val account: Account, -) : FeedViewModel(DiscoverChatFeedFilter(account)) { - class Factory( - val account: Account, - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): NostrDiscoverChatFeedViewModel = NostrDiscoverChatFeedViewModel(account) as NostrDiscoverChatFeedViewModel - } -} - class NostrThreadFeedViewModel( account: Account, noteId: String, @@ -277,48 +216,6 @@ class NostrUserProfileBookmarksFeedViewModel( } } -class NostrChatroomListKnownFeedViewModel( - val account: Account, -) : FeedViewModel(ChatroomListKnownFeedFilter(account)) { - class Factory( - val account: Account, - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): NostrChatroomListKnownFeedViewModel = NostrChatroomListKnownFeedViewModel(account) as NostrChatroomListKnownFeedViewModel - } -} - -class NostrChatroomListNewFeedViewModel( - val account: Account, -) : FeedViewModel(ChatroomListNewFeedFilter(account)) { - class Factory( - val account: Account, - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): NostrChatroomListNewFeedViewModel = NostrChatroomListNewFeedViewModel(account) as NostrChatroomListNewFeedViewModel - } -} - -@Stable -class NostrHomeFeedViewModel( - val account: Account, -) : FeedViewModel(HomeNewThreadFeedFilter(account)) { - class Factory( - val account: Account, - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): NostrHomeFeedViewModel = NostrHomeFeedViewModel(account) as NostrHomeFeedViewModel - } -} - -@Stable -class NostrHomeRepliesFeedViewModel( - val account: Account, -) : FeedViewModel(HomeConversationsFeedFilter(account)) { - class Factory( - val account: Account, - ) : ViewModelProvider.Factory { - override fun create(modelClass: Class): NostrHomeRepliesFeedViewModel = NostrHomeRepliesFeedViewModel(account) as NostrHomeRepliesFeedViewModel - } -} - @Stable class NostrBookmarkPublicFeedViewModel( val account: Account, @@ -383,7 +280,7 @@ class NostrUserAppRecommendationsFeedViewModel( abstract class FeedViewModel( val localFilter: FeedFilter, ) : ViewModel(), - InvalidatableViewModel { + InvalidatableContent { private val _feedContent = MutableStateFlow(FeedState.Loading) val feedContent = _feedContent.asStateFlow() diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt index 0a2c8d2b2..6712544cb 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/LnZapFeedView.kt @@ -32,6 +32,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty +import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed import com.vitorpamplona.amethyst.ui.note.ZapNoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DividerThickness diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt index 37889bcf8..764e54c26 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/RelayFeedView.kt @@ -38,6 +38,8 @@ import com.vitorpamplona.amethyst.model.RelayInfo import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.model.UserState import com.vitorpamplona.amethyst.ui.actions.relays.AllRelayListView +import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.ui.note.RelayCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DividerThickness @@ -52,7 +54,7 @@ import kotlinx.coroutines.launch @Stable class RelayFeedViewModel : ViewModel(), - InvalidatableViewModel { + InvalidatableContent { val order = compareByDescending { it.lastEvent } .thenByDescending { it.counter } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt index d35ba75c4..4c56e7f1e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt @@ -37,6 +37,8 @@ import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedViewModel.kt index fc04151a0..8f28d401f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedViewModel.kt @@ -31,6 +31,7 @@ import com.vitorpamplona.amethyst.model.LocalCache import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.dal.FeedFilter import com.vitorpamplona.amethyst.ui.dal.HiddenWordsFeedFilter +import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent import com.vitorpamplona.ammolite.relays.BundledUpdate import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -57,7 +58,7 @@ class NostrHiddenWordsFeedViewModel( open class StringFeedViewModel( val dataSource: FeedFilter, ) : ViewModel(), - InvalidatableViewModel { + InvalidatableContent { private val _feedContent = MutableStateFlow(StringFeedState.Loading) val feedContent = _feedContent.asStateFlow() 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 08658eb04..e49095d4d 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 @@ -79,6 +79,8 @@ import com.vitorpamplona.amethyst.ui.components.InlineCarrousel import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel +import com.vitorpamplona.amethyst.ui.feeds.FeedState +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.note.CheckAndDisplayEditStatus @@ -136,8 +138,8 @@ import com.vitorpamplona.amethyst.ui.note.types.RenderTextEvent import com.vitorpamplona.amethyst.ui.note.types.RenderTextModificationEvent import com.vitorpamplona.amethyst.ui.note.types.VideoDisplay import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ChannelHeader -import com.vitorpamplona.amethyst.ui.screen.loggedIn.ThinSendButton +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ChannelHeader +import com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms.ThinSendButton import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt index bc9812d17..aa04e0dac 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt @@ -29,6 +29,10 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty +import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.ui.note.UserCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DividerThickness diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt index 8cb420a1f..7a29730d2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedViewModel.kt @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.ui.dal.HiddenAccountsFeedFilter import com.vitorpamplona.amethyst.ui.dal.SpammerAccountsFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowersFeedFilter import com.vitorpamplona.amethyst.ui.dal.UserProfileFollowsFeedFilter +import com.vitorpamplona.amethyst.ui.feeds.InvalidatableContent import com.vitorpamplona.ammolite.relays.BundledUpdate import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList @@ -97,7 +98,7 @@ class NostrSpammerAccountsFeedViewModel( open class UserFeedViewModel( val dataSource: FeedFilter, ) : ViewModel(), - InvalidatableViewModel { + InvalidatableContent { private val _feedContent = MutableStateFlow(UserFeedState.Loading) val feedContent = _feedContent.asStateFlow() @@ -168,7 +169,3 @@ open class UserFeedViewModel( super.onCleared() } } - -interface InvalidatableViewModel { - fun invalidateData(ignoreIfDoing: Boolean = false) -} diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt index 3873701d0..92458c0aa 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/AccountViewModel.kt @@ -60,6 +60,7 @@ import com.vitorpamplona.amethyst.service.ZapPaymentHandler import com.vitorpamplona.amethyst.service.checkNotInMainThread import com.vitorpamplona.amethyst.ui.actions.Dao import com.vitorpamplona.amethyst.ui.components.UrlPreviewState +import com.vitorpamplona.amethyst.ui.feeds.AccountFeedContentStates import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.navigation.bottomNavigationItems import com.vitorpamplona.amethyst.ui.note.ZapAmountCommentNotification @@ -171,6 +172,8 @@ class AccountViewModel( val toasts = MutableSharedFlow(0, 3, onBufferOverflow = BufferOverflow.DROP_OLDEST) + val feedStates = AccountFeedContentStates(this) + val showSensitiveContentChanges = account.live.map { it.account.showSensitiveContent }.distinctUntilChanged() 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 14b2b2f26..2612f0f6f 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 @@ -49,13 +49,13 @@ import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.components.SwipeToDeleteContainer +import com.vitorpamplona.amethyst.ui.feeds.FeedState +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox +import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys.DRAFTS import com.vitorpamplona.amethyst.ui.note.NoteCompose -import com.vitorpamplona.amethyst.ui.screen.FeedState import com.vitorpamplona.amethyst.ui.screen.NostrDraftEventsFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.RefresheableBox import com.vitorpamplona.amethyst.ui.screen.RenderFeedState import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState -import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys.DRAFTS import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.maxWidthWithBackground diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt index d6392e561..02e6cd78a 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt @@ -67,11 +67,11 @@ import androidx.lifecycle.distinctUntilChanged import androidx.lifecycle.map import androidx.lifecycle.viewmodel.compose.viewModel import com.vitorpamplona.amethyst.R +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.ui.note.elements.AddButton import com.vitorpamplona.amethyst.ui.screen.NostrHiddenAccountsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrHiddenWordsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrSpammerAccountsFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.RefresheableBox import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView import com.vitorpamplona.amethyst.ui.screen.StringFeedView import com.vitorpamplona.amethyst.ui.stringRes diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt index 249f62186..86fd256f2 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/MainScreen.kt @@ -98,16 +98,6 @@ import com.vitorpamplona.amethyst.ui.navigation.getRouteWithArguments import com.vitorpamplona.amethyst.ui.note.UserReactionsViewModel import com.vitorpamplona.amethyst.ui.screen.AccountState import com.vitorpamplona.amethyst.ui.screen.AccountStateViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverMarketplaceFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverNIP89FeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.stringRes @@ -184,55 +174,6 @@ fun MainScreen( factory = FollowListViewModel.Factory(accountViewModel.account), ) - // Avoids creating ViewModels for performance reasons (up to 1 second delays) - val homeFeedViewModel: NostrHomeFeedViewModel = - viewModel( - key = "NostrHomeFeedViewModel", - factory = NostrHomeFeedViewModel.Factory(accountViewModel.account), - ) - - val repliesFeedViewModel: NostrHomeRepliesFeedViewModel = - viewModel( - key = "NostrHomeRepliesFeedViewModel", - factory = NostrHomeRepliesFeedViewModel.Factory(accountViewModel.account), - ) - - val videoFeedViewModel: NostrVideoFeedViewModel = - viewModel( - key = "NostrVideoFeedViewModel", - factory = NostrVideoFeedViewModel.Factory(accountViewModel.account), - ) - - val discoverMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel = - viewModel( - key = "NostrDiscoveryMarketplaceFeedViewModel", - factory = NostrDiscoverMarketplaceFeedViewModel.Factory(accountViewModel.account), - ) - - val discoverNIP89FeedViewModel: NostrDiscoverNIP89FeedViewModel = - viewModel( - key = "NostrDiscoveryNIP89FeedViewModel", - factory = NostrDiscoverNIP89FeedViewModel.Factory(accountViewModel.account), - ) - - val discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel = - viewModel( - key = "NostrDiscoveryLiveFeedViewModel", - factory = NostrDiscoverLiveFeedViewModel.Factory(accountViewModel.account), - ) - - val discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel = - viewModel( - key = "NostrDiscoveryCommunityFeedViewModel", - factory = NostrDiscoverCommunityFeedViewModel.Factory(accountViewModel.account), - ) - - val discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel = - viewModel( - key = "NostrDiscoveryChatFeedViewModel", - factory = NostrDiscoverChatFeedViewModel.Factory(accountViewModel.account), - ) - val notifFeedViewModel: NotificationViewModel = viewModel( key = "NotificationViewModel", @@ -245,18 +186,6 @@ fun MainScreen( factory = UserReactionsViewModel.Factory(accountViewModel.account), ) - val knownFeedViewModel: NostrChatroomListKnownFeedViewModel = - viewModel( - key = "NostrChatroomListKnownFeedViewModel", - factory = NostrChatroomListKnownFeedViewModel.Factory(accountViewModel.account), - ) - - val newFeedViewModel: NostrChatroomListNewFeedViewModel = - viewModel( - key = "NostrChatroomListNewFeedViewModel", - factory = NostrChatroomListNewFeedViewModel.Factory(accountViewModel.account), - ) - val navBottomRow = remember(navController) { { route: Route, selected: Boolean -> @@ -271,17 +200,18 @@ fun MainScreen( // and having to deal with all recompositions with scroll to top true when (route.base) { Route.Home.base -> { - homeFeedViewModel.sendToTop() - repliesFeedViewModel.sendToTop() + accountViewModel.feedStates.homeNewThreads.sendToTop() + accountViewModel.feedStates.homeReplies.sendToTop() } Route.Video.base -> { - videoFeedViewModel.sendToTop() + accountViewModel.feedStates.videoFeed.sendToTop() } Route.Discover.base -> { - discoverMarketplaceFeedViewModel.sendToTop() - discoveryLiveFeedViewModel.sendToTop() - discoveryCommunityFeedViewModel.sendToTop() - discoveryChatFeedViewModel.sendToTop() + accountViewModel.feedStates.discoverMarketplace.sendToTop() + accountViewModel.feedStates.discoverLive.sendToTop() + accountViewModel.feedStates.discoverCommunities.sendToTop() + accountViewModel.feedStates.discoverPublicChats.sendToTop() + accountViewModel.feedStates.discoverDVMs.sendToTop() } Route.Notification.base -> { notifFeedViewModel.invalidateDataAndSendToTop(true) @@ -314,16 +244,6 @@ fun MainScreen( accountStateViewModel = accountStateViewModel, userReactionsStatsModel = userReactionsStatsModel, followListsViewModel = followListsViewModel, - homeFeedViewModel = homeFeedViewModel, - repliesFeedViewModel = repliesFeedViewModel, - knownFeedViewModel = knownFeedViewModel, - newFeedViewModel = newFeedViewModel, - videoFeedViewModel = videoFeedViewModel, - discoverNIP89FeedViewModel = discoverNIP89FeedViewModel, - discoverMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel, - discoveryLiveFeedViewModel = discoveryLiveFeedViewModel, - discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel, - discoveryChatFeedViewModel = discoveryChatFeedViewModel, notifFeedViewModel = notifFeedViewModel, sharedPreferencesViewModel = sharedPreferencesViewModel, accountViewModel = accountViewModel, @@ -363,16 +283,6 @@ private fun MainScaffold( accountStateViewModel: AccountStateViewModel, userReactionsStatsModel: UserReactionsViewModel, followListsViewModel: FollowListViewModel, - homeFeedViewModel: NostrHomeFeedViewModel, - repliesFeedViewModel: NostrHomeRepliesFeedViewModel, - knownFeedViewModel: NostrChatroomListKnownFeedViewModel, - newFeedViewModel: NostrChatroomListNewFeedViewModel, - videoFeedViewModel: NostrVideoFeedViewModel, - discoverNIP89FeedViewModel: NostrDiscoverNIP89FeedViewModel, - discoverMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel, - discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel, - discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel, - discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel, notifFeedViewModel: NotificationViewModel, sharedPreferencesViewModel: SharedPreferencesViewModel, accountViewModel: AccountViewModel, @@ -497,16 +407,6 @@ private fun MainScaffold( .imePadding(), ) { AppNavigation( - homeFeedViewModel = homeFeedViewModel, - repliesFeedViewModel = repliesFeedViewModel, - knownFeedViewModel = knownFeedViewModel, - newFeedViewModel = newFeedViewModel, - videoFeedViewModel = videoFeedViewModel, - discoverNip89FeedViewModel = discoverNIP89FeedViewModel, - discoverMarketplaceFeedViewModel = discoverMarketplaceFeedViewModel, - discoveryLiveFeedViewModel = discoveryLiveFeedViewModel, - discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel, - discoveryChatFeedViewModel = discoveryChatFeedViewModel, notifFeedViewModel = notifFeedViewModel, userReactionsStatsModel = userReactionsStatsModel, navController = navController, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt index 3412afeaa..25a3b7180 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NotificationScreen.kt @@ -68,6 +68,7 @@ import com.patrykandpatrick.vico.core.chart.values.ChartValues import com.patrykandpatrick.vico.core.component.shape.shader.DynamicShaders import com.vitorpamplona.amethyst.service.NostrAccountDataSource import com.vitorpamplona.amethyst.ui.components.SelectNotificationProvider +import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.note.OneGiga import com.vitorpamplona.amethyst.ui.note.OneKilo @@ -79,7 +80,6 @@ import com.vitorpamplona.amethyst.ui.note.showAmount import com.vitorpamplona.amethyst.ui.note.showCount import com.vitorpamplona.amethyst.ui.screen.NotificationViewModel import com.vitorpamplona.amethyst.ui.screen.RefreshableCardView -import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import com.vitorpamplona.amethyst.ui.screen.SharedPreferencesViewModel import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange import com.vitorpamplona.amethyst.ui.theme.DividerThickness 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 b72c6913e..c290f98e8 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 @@ -60,6 +60,10 @@ import com.vitorpamplona.amethyst.ui.components.GalleryContentView import com.vitorpamplona.amethyst.ui.components.LoadNote import com.vitorpamplona.amethyst.ui.components.SensitivityWarning import com.vitorpamplona.amethyst.ui.components.mockAccountViewModel +import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty +import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.FeedState +import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport import com.vitorpamplona.amethyst.ui.note.ClickableNote import com.vitorpamplona.amethyst.ui.note.LongPressToQuickActionGallery @@ -67,11 +71,7 @@ import com.vitorpamplona.amethyst.ui.note.WatchAuthor import com.vitorpamplona.amethyst.ui.note.WatchNoteEvent import com.vitorpamplona.amethyst.ui.note.calculateBackgroundColor import com.vitorpamplona.amethyst.ui.note.elements.BannerImage -import com.vitorpamplona.amethyst.ui.screen.FeedEmpty -import com.vitorpamplona.amethyst.ui.screen.FeedError -import com.vitorpamplona.amethyst.ui.screen.FeedState import com.vitorpamplona.amethyst.ui.screen.FeedViewModel -import com.vitorpamplona.amethyst.ui.screen.LoadingFeed import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.HalfPadding 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 57b0a481b..254004354 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 @@ -132,6 +132,9 @@ import com.vitorpamplona.amethyst.ui.components.RobohashFallbackAsyncImage import com.vitorpamplona.amethyst.ui.components.TranslatableRichTextViewer import com.vitorpamplona.amethyst.ui.components.ZoomableImageDialog import com.vitorpamplona.amethyst.ui.dal.UserProfileReportsFeedFilter +import com.vitorpamplona.amethyst.ui.feeds.FeedState +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox +import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.note.ClickableUserPicture import com.vitorpamplona.amethyst.ui.note.DrawPlayName @@ -141,7 +144,6 @@ import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.note.externalLinkForUser import com.vitorpamplona.amethyst.ui.note.payViaIntent import com.vitorpamplona.amethyst.ui.qrcode.ShowQRDialog -import com.vitorpamplona.amethyst.ui.screen.FeedState import com.vitorpamplona.amethyst.ui.screen.LnZapFeedView import com.vitorpamplona.amethyst.ui.screen.NostrUserAppRecommendationsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileBookmarksFeedViewModel @@ -152,13 +154,11 @@ import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileGalleryFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileNewThreadsFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileReportFeedViewModel import com.vitorpamplona.amethyst.ui.screen.NostrUserProfileZapsFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.RefresheableBox import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView import com.vitorpamplona.amethyst.ui.screen.RefreshingFeedUserFeedView import com.vitorpamplona.amethyst.ui.screen.RelayFeedView import com.vitorpamplona.amethyst.ui.screen.RelayFeedViewModel import com.vitorpamplona.amethyst.ui.screen.SaveableGridFeedState -import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys import com.vitorpamplona.amethyst.ui.screen.UserFeedViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.BitcoinOrange diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChannelScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChannelScreen.kt index 1aaf078a9..3a53cb892 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChannelScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChannelScreen.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms import android.widget.Toast import androidx.compose.animation.animateContentSize @@ -61,8 +61,6 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.material3.TextFieldColors import androidx.compose.material3.TextFieldDefaults -import androidx.compose.material3.TextFieldDefaults.contentPaddingWithLabel -import androidx.compose.material3.TextFieldDefaults.contentPaddingWithoutLabel import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect @@ -138,8 +136,9 @@ import com.vitorpamplona.amethyst.ui.note.elements.DisplayUncitedHashtags import com.vitorpamplona.amethyst.ui.note.elements.MoreOptionsButton import com.vitorpamplona.amethyst.ui.note.timeAgoShort import com.vitorpamplona.amethyst.ui.screen.NostrChannelFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView import com.vitorpamplona.amethyst.ui.screen.equalImmutableLists +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.CrossfadeCheckIfUrlIsOnline import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.ButtonBorder import com.vitorpamplona.amethyst.ui.theme.ButtonPadding diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomFeedView.kt similarity index 93% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomFeedView.kt index b0dd65c34..1689b1a35 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomFeedView.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Row @@ -38,7 +38,14 @@ import androidx.compose.ui.text.font.FontWeight import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.model.Note import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty +import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.FeedState +import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.ui.note.ChatroomMessageCompose +import com.vitorpamplona.amethyst.ui.screen.FeedViewModel +import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.FeedPadding import com.vitorpamplona.amethyst.ui.theme.Font14SP diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomListFeedView.kt similarity index 82% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomListFeedView.kt index 56d30493f..35c907660 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomListFeedView.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms import androidx.compose.animation.core.tween import androidx.compose.foundation.layout.Row @@ -34,6 +34,12 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.feeds.FeedContentState +import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty +import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.FeedState +import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.ui.note.ChatroomHeaderCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.DividerThickness @@ -41,22 +47,22 @@ import com.vitorpamplona.amethyst.ui.theme.FeedPadding @Composable fun ChatroomListFeedView( - viewModel: FeedViewModel, + feedContentState: FeedContentState, accountViewModel: AccountViewModel, nav: (String) -> Unit, markAsRead: MutableState, ) { - RefresheableBox(viewModel, true) { CrossFadeState(viewModel, accountViewModel, nav, markAsRead) } + RefresheableBox(feedContentState, true) { CrossFadeState(feedContentState, accountViewModel, nav, markAsRead) } } @Composable private fun CrossFadeState( - viewModel: FeedViewModel, + feedContentState: FeedContentState, accountViewModel: AccountViewModel, nav: (String) -> Unit, markAsRead: MutableState, ) { - val feedState by viewModel.feedContent.collectAsStateWithLifecycle() + val feedState by feedContentState.feedContent.collectAsStateWithLifecycle() CrossfadeIfEnabled( targetState = feedState, @@ -65,10 +71,10 @@ private fun CrossFadeState( ) { state -> when (state) { is FeedState.Empty -> { - FeedEmpty { viewModel.invalidateData() } + FeedEmpty { feedContentState.invalidateData() } } is FeedState.FeedError -> { - FeedError(state.errorMessage) { viewModel.invalidateData() } + FeedError(state.errorMessage) { feedContentState.invalidateData() } } is FeedState.Loaded -> { FeedLoaded(state, accountViewModel, nav, markAsRead) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomListScreen.kt similarity index 88% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomListScreen.kt index 290f9b8c1..09f806fd7 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomListScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomListScreen.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box @@ -67,10 +67,8 @@ import com.google.accompanist.adaptive.TwoPane import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.NostrChatroomListDataSource import com.vitorpamplona.amethyst.ui.buttons.ChannelFabColumn -import com.vitorpamplona.amethyst.ui.screen.ChatroomListFeedView -import com.vitorpamplona.amethyst.ui.screen.FeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListKnownFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrChatroomListNewFeedViewModel +import com.vitorpamplona.amethyst.ui.feeds.FeedContentState +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.Size20dp @@ -81,8 +79,8 @@ import kotlinx.coroutines.launch @Composable fun ChatroomListScreen( - knownFeedViewModel: NostrChatroomListKnownFeedViewModel, - newFeedViewModel: NostrChatroomListNewFeedViewModel, + knownFeedContentState: FeedContentState, + newFeedContentState: FeedContentState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { @@ -102,16 +100,16 @@ fun ChatroomListScreen( if (twoPane && windowSizeClass != null) { ChatroomListTwoPane( - knownFeedViewModel = knownFeedViewModel, - newFeedViewModel = newFeedViewModel, + knownFeedContentState = knownFeedContentState, + newFeedContentState = newFeedContentState, widthSizeClass = windowSizeClass!!.widthSizeClass, accountViewModel = accountViewModel, nav = nav, ) } else { ChatroomListScreenOnlyList( - knownFeedViewModel = knownFeedViewModel, - newFeedViewModel = newFeedViewModel, + knownFeedContentState = knownFeedContentState, + newFeedContentState = newFeedContentState, accountViewModel = accountViewModel, nav = nav, ) @@ -125,8 +123,8 @@ data class RouteId( @Composable fun ChatroomListTwoPane( - knownFeedViewModel: NostrChatroomListKnownFeedViewModel, - newFeedViewModel: NostrChatroomListNewFeedViewModel, + knownFeedContentState: FeedContentState, + newFeedContentState: FeedContentState, widthSizeClass: WindowWidthSizeClass, accountViewModel: AccountViewModel, nav: (String) -> Unit, @@ -164,8 +162,8 @@ fun ChatroomListTwoPane( first = { Box(Modifier.fillMaxSize(), contentAlignment = Alignment.BottomEnd) { ChatroomListScreenOnlyList( - knownFeedViewModel, - newFeedViewModel, + knownFeedContentState, + newFeedContentState, accountViewModel, navInterceptor, ) @@ -209,8 +207,8 @@ fun ChatroomListTwoPane( @OptIn(ExperimentalFoundationApi::class) @Composable fun ChatroomListScreenOnlyList( - knownFeedViewModel: NostrChatroomListKnownFeedViewModel, - newFeedViewModel: NostrChatroomListNewFeedViewModel, + knownFeedContentState: FeedContentState, + newFeedContentState: FeedContentState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { @@ -221,7 +219,7 @@ fun ChatroomListScreenOnlyList( val markKnownAsRead = remember { mutableStateOf(false) } val markNewAsRead = remember { mutableStateOf(false) } - WatchAccountForListScreen(knownFeedViewModel, newFeedViewModel, accountViewModel) + WatchAccountForListScreen(knownFeedContentState, newFeedContentState, accountViewModel) val lifeCycleOwner = LocalLifecycleOwner.current DisposableEffect(lifeCycleOwner) { @@ -238,11 +236,11 @@ fun ChatroomListScreenOnlyList( } val tabs by - remember(knownFeedViewModel, markKnownAsRead) { + remember(knownFeedContentState, markKnownAsRead) { derivedStateOf { listOf( - ChatroomListTabItem(R.string.known, knownFeedViewModel, markKnownAsRead), - ChatroomListTabItem(R.string.new_requests, newFeedViewModel, markNewAsRead), + ChatroomListTabItem(R.string.known, knownFeedContentState, markKnownAsRead), + ChatroomListTabItem(R.string.new_requests, newFeedContentState, markNewAsRead), ) } } @@ -290,7 +288,7 @@ fun ChatroomListScreenOnlyList( modifier = Modifier.fillMaxSize(), ) { page -> ChatroomListFeedView( - viewModel = tabs[page].viewModel, + feedContentState = tabs[page].feedContentState, accountViewModel = accountViewModel, nav = nav, markAsRead = tabs[page].markAsRead, @@ -301,16 +299,16 @@ fun ChatroomListScreenOnlyList( @Composable fun WatchAccountForListScreen( - knownFeedViewModel: NostrChatroomListKnownFeedViewModel, - newFeedViewModel: NostrChatroomListNewFeedViewModel, + knownFeedContentState: FeedContentState, + newFeedContentState: FeedContentState, accountViewModel: AccountViewModel, ) { LaunchedEffect(accountViewModel) { launch(Dispatchers.IO) { NostrChatroomListDataSource.account = accountViewModel.account NostrChatroomListDataSource.start() - knownFeedViewModel.invalidateData(true) - newFeedViewModel.invalidateData(true) + knownFeedContentState.invalidateData(true) + newFeedContentState.invalidateData(true) } } } @@ -318,7 +316,7 @@ fun WatchAccountForListScreen( @Immutable class ChatroomListTabItem( val resource: Int, - val viewModel: FeedViewModel, + val feedContentState: FeedContentState, val markAsRead: MutableState, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomScreen.kt similarity index 99% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomScreen.kt index 8ebe5a13d..39e59aa59 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/ChatroomScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/chatrooms/ChatroomScreen.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.screen.loggedIn.chatrooms import android.widget.Toast import androidx.compose.foundation.clickable @@ -106,7 +106,8 @@ import com.vitorpamplona.amethyst.ui.note.UsernameDisplay import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMs import com.vitorpamplona.amethyst.ui.note.elements.ObserveRelayListForDMsAndDisplayIfNotFound import com.vitorpamplona.amethyst.ui.screen.NostrChatroomFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.RefreshingChatroomFeedView +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.UserLine import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.EditFieldBorder diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CommunityScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/CommunityScreen.kt similarity index 96% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CommunityScreen.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/CommunityScreen.kt index 9a5cb0b12..c11039fa5 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/CommunityScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/CommunityScreen.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -35,6 +35,7 @@ import com.vitorpamplona.amethyst.service.NostrCommunityDataSource import com.vitorpamplona.amethyst.ui.note.LoadAddressableNote import com.vitorpamplona.amethyst.ui.screen.NostrCommunityFeedViewModel import com.vitorpamplona.amethyst.ui.screen.RefresheableFeedView +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel @Composable fun CommunityScreen( diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt similarity index 76% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt index b239c5bd5..92d04aa49 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/DiscoverScreen.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi @@ -58,24 +58,21 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.NostrDiscoveryDataSource import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.feeds.FeedContentState +import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty +import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.FeedState +import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed +import com.vitorpamplona.amethyst.ui.feeds.PagerStateKeys +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox +import com.vitorpamplona.amethyst.ui.feeds.SaveableFeedContentState +import com.vitorpamplona.amethyst.ui.feeds.SaveableGridFeedContentState +import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys +import com.vitorpamplona.amethyst.ui.feeds.rememberForeverPagerState import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.note.ChannelCardCompose -import com.vitorpamplona.amethyst.ui.screen.FeedEmpty -import com.vitorpamplona.amethyst.ui.screen.FeedError -import com.vitorpamplona.amethyst.ui.screen.FeedState -import com.vitorpamplona.amethyst.ui.screen.FeedViewModel -import com.vitorpamplona.amethyst.ui.screen.LoadingFeed -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverChatFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverCommunityFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverLiveFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverMarketplaceFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrDiscoverNIP89FeedViewModel -import com.vitorpamplona.amethyst.ui.screen.PagerStateKeys -import com.vitorpamplona.amethyst.ui.screen.RefresheableBox -import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState -import com.vitorpamplona.amethyst.ui.screen.SaveableGridFeedState -import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys -import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel +import com.vitorpamplona.amethyst.ui.screen.loggedIn.home.TabItem import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DividerThickness import com.vitorpamplona.amethyst.ui.theme.FeedPadding @@ -92,11 +89,11 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable fun DiscoverScreen( - discoveryContentNIP89FeedViewModel: NostrDiscoverNIP89FeedViewModel, - discoveryMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel, - discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel, - discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel, - discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel, + discoveryContentNIP89FeedContentState: FeedContentState, + discoveryMarketplaceFeedContentState: FeedContentState, + discoveryLiveFeedContentState: FeedContentState, + discoveryCommunityFeedContentState: FeedContentState, + discoveryChatFeedContentState: FeedContentState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { @@ -104,44 +101,46 @@ fun DiscoverScreen( val tabs by remember( - discoveryContentNIP89FeedViewModel, - discoveryLiveFeedViewModel, - discoveryCommunityFeedViewModel, - discoveryChatFeedViewModel, + discoveryContentNIP89FeedContentState, + discoveryLiveFeedContentState, + discoveryCommunityFeedContentState, + discoveryChatFeedContentState, + discoveryMarketplaceFeedContentState, ) { mutableStateOf( listOf( TabItem( R.string.discover_content, - discoveryContentNIP89FeedViewModel, + discoveryContentNIP89FeedContentState, Route.Discover.base + "DiscoverContent", ScrollStateKeys.DISCOVER_CONTENT, AppDefinitionEvent.KIND, ), TabItem( R.string.discover_live, - discoveryLiveFeedViewModel, + discoveryLiveFeedContentState, Route.Discover.base + "Live", ScrollStateKeys.DISCOVER_LIVE, LiveActivitiesEvent.KIND, ), TabItem( R.string.discover_community, - discoveryCommunityFeedViewModel, + discoveryCommunityFeedContentState, Route.Discover.base + "Community", ScrollStateKeys.DISCOVER_COMMUNITY, CommunityDefinitionEvent.KIND, ), TabItem( R.string.discover_marketplace, - discoveryMarketplaceFeedViewModel, + discoveryMarketplaceFeedContentState, Route.Discover.base + "Marketplace", ScrollStateKeys.DISCOVER_MARKETPLACE, ClassifiedsEvent.KIND, + useGridLayout = true, ), TabItem( R.string.discover_chat, - discoveryChatFeedViewModel, + discoveryChatFeedContentState, Route.Discover.base + "Chats", ScrollStateKeys.DISCOVER_CHATS, ChannelCreateEvent.KIND, @@ -153,11 +152,11 @@ fun DiscoverScreen( val pagerState = rememberForeverPagerState(key = PagerStateKeys.DISCOVER_SCREEN) { tabs.size } WatchAccountForDiscoveryScreen( - discoverNIP89FeedViewModel = discoveryContentNIP89FeedViewModel, - discoverMarketplaceFeedViewModel = discoveryMarketplaceFeedViewModel, - discoveryLiveFeedViewModel = discoveryLiveFeedViewModel, - discoveryCommunityFeedViewModel = discoveryCommunityFeedViewModel, - discoveryChatFeedViewModel = discoveryChatFeedViewModel, + discoveryContentNIP89FeedContentState = discoveryContentNIP89FeedContentState, + discoveryMarketplaceFeedContentState = discoveryMarketplaceFeedContentState, + discoveryLiveFeedContentState = discoveryLiveFeedContentState, + discoveryCommunityFeedContentState = discoveryCommunityFeedContentState, + discoveryChatFeedContentState = discoveryChatFeedContentState, accountViewModel = accountViewModel, ) @@ -214,11 +213,11 @@ private fun DiscoverPages( } HorizontalPager(state = pagerState) { page -> - RefresheableBox(tabs[page].viewModel, true) { - if (tabs[page].viewModel is NostrDiscoverMarketplaceFeedViewModel) { - SaveableGridFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { listState -> + RefresheableBox(tabs[page].feedState, true) { + if (tabs[page].useGridLayout) { + SaveableGridFeedContentState(tabs[page].feedState, scrollStateKey = tabs[page].scrollStateKey) { listState -> RenderDiscoverFeed( - viewModel = tabs[page].viewModel, + feedContentState = tabs[page].feedState, routeForLastRead = tabs[page].routeForLastRead, forceEventKind = tabs[page].forceEventKind, listState = listState, @@ -227,9 +226,9 @@ private fun DiscoverPages( ) } } else { - SaveableFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { listState -> + SaveableFeedContentState(tabs[page].feedState, scrollStateKey = tabs[page].scrollStateKey) { listState -> RenderDiscoverFeed( - viewModel = tabs[page].viewModel, + feedContentState = tabs[page].feedState, routeForLastRead = tabs[page].routeForLastRead, forceEventKind = tabs[page].forceEventKind, listState = listState, @@ -244,14 +243,14 @@ private fun DiscoverPages( @Composable private fun RenderDiscoverFeed( - viewModel: FeedViewModel, + feedContentState: FeedContentState, routeForLastRead: String?, forceEventKind: Int?, listState: LazyGridState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val feedState by viewModel.feedContent.collectAsStateWithLifecycle() + val feedState by feedContentState.feedContent.collectAsStateWithLifecycle() CrossfadeIfEnabled( targetState = feedState, @@ -261,10 +260,10 @@ private fun RenderDiscoverFeed( ) { state -> when (state) { is FeedState.Empty -> { - FeedEmpty { viewModel.invalidateData() } + FeedEmpty(feedContentState::invalidateData) } is FeedState.FeedError -> { - FeedError(state.errorMessage) { viewModel.invalidateData() } + FeedError(state.errorMessage, feedContentState::invalidateData) } is FeedState.Loaded -> { DiscoverFeedColumnsLoaded( @@ -285,14 +284,14 @@ private fun RenderDiscoverFeed( @Composable private fun RenderDiscoverFeed( - viewModel: FeedViewModel, + feedContentState: FeedContentState, routeForLastRead: String?, forceEventKind: Int?, listState: LazyListState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val feedState by viewModel.feedContent.collectAsStateWithLifecycle() + val feedState by feedContentState.feedContent.collectAsStateWithLifecycle() CrossfadeIfEnabled( targetState = feedState, @@ -302,10 +301,10 @@ private fun RenderDiscoverFeed( ) { state -> when (state) { is FeedState.Empty -> { - FeedEmpty { viewModel.invalidateData() } + FeedEmpty(feedContentState::invalidateData) } is FeedState.FeedError -> { - FeedError(state.errorMessage) { viewModel.invalidateData() } + FeedError(state.errorMessage, feedContentState::invalidateData) } is FeedState.Loaded -> { DiscoverFeedLoaded( @@ -326,22 +325,22 @@ private fun RenderDiscoverFeed( @Composable fun WatchAccountForDiscoveryScreen( - discoverNIP89FeedViewModel: NostrDiscoverNIP89FeedViewModel, - discoverMarketplaceFeedViewModel: NostrDiscoverMarketplaceFeedViewModel, - discoveryLiveFeedViewModel: NostrDiscoverLiveFeedViewModel, - discoveryCommunityFeedViewModel: NostrDiscoverCommunityFeedViewModel, - discoveryChatFeedViewModel: NostrDiscoverChatFeedViewModel, + discoveryContentNIP89FeedContentState: FeedContentState, + discoveryMarketplaceFeedContentState: FeedContentState, + discoveryLiveFeedContentState: FeedContentState, + discoveryCommunityFeedContentState: FeedContentState, + discoveryChatFeedContentState: FeedContentState, accountViewModel: AccountViewModel, ) { val listState by accountViewModel.account.liveDiscoveryFollowLists.collectAsStateWithLifecycle() LaunchedEffect(accountViewModel, listState) { NostrDiscoveryDataSource.resetFilters() - discoverNIP89FeedViewModel.checkKeysInvalidateDataAndSendToTop() - discoverMarketplaceFeedViewModel.checkKeysInvalidateDataAndSendToTop() - discoveryLiveFeedViewModel.checkKeysInvalidateDataAndSendToTop() - discoveryCommunityFeedViewModel.checkKeysInvalidateDataAndSendToTop() - discoveryChatFeedViewModel.checkKeysInvalidateDataAndSendToTop() + discoveryContentNIP89FeedContentState.checkKeysInvalidateDataAndSendToTop() + discoveryMarketplaceFeedContentState.checkKeysInvalidateDataAndSendToTop() + discoveryLiveFeedContentState.checkKeysInvalidateDataAndSendToTop() + discoveryCommunityFeedContentState.checkKeysInvalidateDataAndSendToTop() + discoveryChatFeedContentState.checkKeysInvalidateDataAndSendToTop() } } diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/NIP90ContentDiscoveryScreen.kt similarity index 98% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/NIP90ContentDiscoveryScreen.kt index f5f1e09d3..7f693f13b 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/NIP90ContentDiscoveryScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/discover/NIP90ContentDiscoveryScreen.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.screen.loggedIn.discover import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.layout.Arrangement @@ -66,6 +66,8 @@ import com.vitorpamplona.amethyst.model.User import com.vitorpamplona.amethyst.service.ZapPaymentHandler import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.components.LoadNote +import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox import com.vitorpamplona.amethyst.ui.navigation.routeToMessage import com.vitorpamplona.amethyst.ui.note.DVMCard import com.vitorpamplona.amethyst.ui.note.ErrorMessageDialog @@ -78,11 +80,10 @@ import com.vitorpamplona.amethyst.ui.note.ZapIcon import com.vitorpamplona.amethyst.ui.note.ZappedIcon import com.vitorpamplona.amethyst.ui.note.elements.customZapClick import com.vitorpamplona.amethyst.ui.note.payViaIntent -import com.vitorpamplona.amethyst.ui.screen.FeedEmpty import com.vitorpamplona.amethyst.ui.screen.NostrNIP90ContentDiscoveryFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.RefresheableBox import com.vitorpamplona.amethyst.ui.screen.RenderFeedState import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.DoubleVertSpacer import com.vitorpamplona.amethyst.ui.theme.ModifierWidth3dp diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeScreen.kt similarity index 84% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeScreen.kt index 23c757953..7c9a288f3 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/home/HomeScreen.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.screen.loggedIn.home import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Arrangement @@ -54,17 +54,16 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.service.NostrHomeDataSource import com.vitorpamplona.amethyst.service.OnlineChecker import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled +import com.vitorpamplona.amethyst.ui.feeds.FeedContentState +import com.vitorpamplona.amethyst.ui.feeds.PagerStateKeys +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox +import com.vitorpamplona.amethyst.ui.feeds.RenderFeedContentState +import com.vitorpamplona.amethyst.ui.feeds.SaveableFeedContentState +import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys +import com.vitorpamplona.amethyst.ui.feeds.rememberForeverPagerState import com.vitorpamplona.amethyst.ui.navigation.Route import com.vitorpamplona.amethyst.ui.note.UpdateZapAmountDialog -import com.vitorpamplona.amethyst.ui.screen.FeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrHomeFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.NostrHomeRepliesFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.PagerStateKeys -import com.vitorpamplona.amethyst.ui.screen.RefresheableBox -import com.vitorpamplona.amethyst.ui.screen.RenderFeedState -import com.vitorpamplona.amethyst.ui.screen.SaveableFeedState -import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys -import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.TabRowHeight @@ -76,19 +75,19 @@ import kotlinx.coroutines.launch @OptIn(ExperimentalFoundationApi::class) @Composable fun HomeScreen( - homeFeedViewModel: NostrHomeFeedViewModel, - repliesFeedViewModel: NostrHomeRepliesFeedViewModel, + newThreadsFeedState: FeedContentState, + repliesFeedState: FeedContentState, accountViewModel: AccountViewModel, nav: (String) -> Unit, nip47: String? = null, ) { ResolveNIP47(nip47, accountViewModel) - WatchAccountForHomeScreen(homeFeedViewModel, repliesFeedViewModel, accountViewModel) + WatchAccountForHomeScreen(newThreadsFeedState, repliesFeedState, accountViewModel) WatchLifeCycleChanges(accountViewModel) - AssembleHomeTabs(homeFeedViewModel, repliesFeedViewModel) { pagerState, tabItems -> + AssembleHomeTabs(newThreadsFeedState, repliesFeedState) { pagerState, tabItems -> AssembleHomePage(pagerState, tabItems, accountViewModel, nav) } } @@ -96,25 +95,25 @@ fun HomeScreen( @OptIn(ExperimentalFoundationApi::class) @Composable private fun AssembleHomeTabs( - homeFeedViewModel: NostrHomeFeedViewModel, - repliesFeedViewModel: NostrHomeRepliesFeedViewModel, + newThreadsFeedState: FeedContentState, + repliesFeedState: FeedContentState, inner: @Composable (PagerState, ImmutableList) -> Unit, ) { val pagerState = rememberForeverPagerState(key = PagerStateKeys.HOME_SCREEN) { 2 } val tabs by - remember(homeFeedViewModel, repliesFeedViewModel) { + remember(newThreadsFeedState, repliesFeedState) { mutableStateOf( listOf( TabItem( R.string.new_threads, - homeFeedViewModel, + newThreadsFeedState, Route.Home.base + "Follows", ScrollStateKeys.HOME_FOLLOWS, ), TabItem( R.string.conversations, - repliesFeedViewModel, + repliesFeedState, Route.Home.base + "FollowsReplies", ScrollStateKeys.HOME_REPLIES, ), @@ -194,7 +193,7 @@ private fun HomePages( HorizontalPager(state = pagerState, userScrollEnabled = false) { page -> HomeFeeds( - viewModel = tabs[page].viewModel, + feedState = tabs[page].feedState, routeForLastRead = tabs[page].routeForLastRead, scrollStateKey = tabs[page].scrollStateKey, accountViewModel = accountViewModel, @@ -205,22 +204,22 @@ private fun HomePages( @Composable fun HomeFeeds( - viewModel: FeedViewModel, + feedState: FeedContentState, routeForLastRead: String?, enablePullRefresh: Boolean = true, scrollStateKey: String? = null, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - RefresheableBox(viewModel, enablePullRefresh) { - SaveableFeedState(viewModel, scrollStateKey) { listState -> - RenderFeedState( - viewModel = viewModel, + RefresheableBox(feedState, enablePullRefresh) { + SaveableFeedContentState(feedState, scrollStateKey) { listState -> + RenderFeedContentState( + feedContentState = feedState, accountViewModel = accountViewModel, listState = listState, nav = nav, routeForLastRead = routeForLastRead, - onEmpty = { HomeFeedEmpty { viewModel.invalidateData() } }, + onEmpty = { HomeFeedEmpty(feedState::invalidateData) }, ) } } @@ -303,8 +302,8 @@ fun CrossfadeCheckIfUrlIsOnline( @Composable fun WatchAccountForHomeScreen( - homeFeedViewModel: NostrHomeFeedViewModel, - repliesFeedViewModel: NostrHomeRepliesFeedViewModel, + newThreadsFeedState: FeedContentState, + repliesFeedState: FeedContentState, accountViewModel: AccountViewModel, ) { val homeFollowList by accountViewModel.account.liveHomeFollowLists.collectAsStateWithLifecycle() @@ -312,16 +311,17 @@ fun WatchAccountForHomeScreen( LaunchedEffect(accountViewModel, homeFollowList) { NostrHomeDataSource.account = accountViewModel.account NostrHomeDataSource.invalidateFilters() - homeFeedViewModel.checkKeysInvalidateDataAndSendToTop() - repliesFeedViewModel.checkKeysInvalidateDataAndSendToTop() + newThreadsFeedState.checkKeysInvalidateDataAndSendToTop() + repliesFeedState.checkKeysInvalidateDataAndSendToTop() } } @Immutable class TabItem( val resource: Int, - val viewModel: FeedViewModel, + val feedState: FeedContentState, val routeForLastRead: String, val scrollStateKey: String, val forceEventKind: Int? = null, + val useGridLayout: Boolean = false, ) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt similarity index 89% rename from amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt rename to amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt index db66f5e30..3c56d8214 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/video/VideoScreen.kt @@ -18,7 +18,7 @@ * 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 +package com.vitorpamplona.amethyst.ui.screen.loggedIn.video import androidx.compose.animation.core.tween import androidx.compose.foundation.ExperimentalFoundationApi @@ -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.layout.size import androidx.compose.foundation.pager.PagerState import androidx.compose.foundation.pager.VerticalPager import androidx.compose.foundation.pager.rememberPagerState @@ -64,6 +63,14 @@ import com.vitorpamplona.amethyst.ui.actions.CrossfadeIfEnabled import com.vitorpamplona.amethyst.ui.actions.NewPostView import com.vitorpamplona.amethyst.ui.components.ClickableBox import com.vitorpamplona.amethyst.ui.components.ObserveDisplayNip05Status +import com.vitorpamplona.amethyst.ui.feeds.FeedContentState +import com.vitorpamplona.amethyst.ui.feeds.FeedEmpty +import com.vitorpamplona.amethyst.ui.feeds.FeedError +import com.vitorpamplona.amethyst.ui.feeds.FeedState +import com.vitorpamplona.amethyst.ui.feeds.LoadingFeed +import com.vitorpamplona.amethyst.ui.feeds.RefresheableBox +import com.vitorpamplona.amethyst.ui.feeds.ScrollStateKeys +import com.vitorpamplona.amethyst.ui.feeds.rememberForeverPagerState import com.vitorpamplona.amethyst.ui.navigation.routeFor import com.vitorpamplona.amethyst.ui.note.BoostReaction import com.vitorpamplona.amethyst.ui.note.CheckHiddenFeedWatchBlockAndReport @@ -77,15 +84,7 @@ import com.vitorpamplona.amethyst.ui.note.elements.NoteDropDownMenu import com.vitorpamplona.amethyst.ui.note.types.FileHeaderDisplay import com.vitorpamplona.amethyst.ui.note.types.FileStorageHeaderDisplay import com.vitorpamplona.amethyst.ui.note.types.JustVideoDisplay -import com.vitorpamplona.amethyst.ui.screen.FeedEmpty -import com.vitorpamplona.amethyst.ui.screen.FeedError -import com.vitorpamplona.amethyst.ui.screen.FeedState -import com.vitorpamplona.amethyst.ui.screen.FeedViewModel -import com.vitorpamplona.amethyst.ui.screen.LoadingFeed -import com.vitorpamplona.amethyst.ui.screen.NostrVideoFeedViewModel -import com.vitorpamplona.amethyst.ui.screen.RefresheableBox -import com.vitorpamplona.amethyst.ui.screen.ScrollStateKeys -import com.vitorpamplona.amethyst.ui.screen.rememberForeverPagerState +import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.stringRes import com.vitorpamplona.amethyst.ui.theme.AuthorInfoVideoFeed import com.vitorpamplona.amethyst.ui.theme.DoubleHorzSpacer @@ -105,13 +104,13 @@ import kotlinx.collections.immutable.ImmutableList @Composable fun VideoScreen( - videoFeedView: NostrVideoFeedViewModel, + videoFeedContentState: FeedContentState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { val lifeCycleOwner = LocalLifecycleOwner.current - WatchAccountForVideoScreen(videoFeedView = videoFeedView, accountViewModel = accountViewModel) + WatchAccountForVideoScreen(videoFeedContentState = videoFeedContentState, accountViewModel = accountViewModel) DisposableEffect(lifeCycleOwner) { val observer = @@ -128,7 +127,7 @@ fun VideoScreen( Column(Modifier.fillMaxHeight()) { RenderPage( - videoFeedView = videoFeedView, + videoFeedContentState = videoFeedContentState, pagerStateKey = ScrollStateKeys.VIDEO_SCREEN, accountViewModel = accountViewModel, nav = nav, @@ -138,7 +137,7 @@ fun VideoScreen( @Composable fun WatchAccountForVideoScreen( - videoFeedView: NostrVideoFeedViewModel, + videoFeedContentState: FeedContentState, accountViewModel: AccountViewModel, ) { val listState by accountViewModel.account.liveStoriesFollowLists.collectAsStateWithLifecycle() @@ -146,34 +145,34 @@ fun WatchAccountForVideoScreen( LaunchedEffect(accountViewModel, listState, hiddenUsers) { NostrVideoDataSource.resetFilters() - videoFeedView.checkKeysInvalidateDataAndSendToTop() + videoFeedContentState.checkKeysInvalidateDataAndSendToTop() } } @OptIn(ExperimentalFoundationApi::class) @Composable public fun WatchScrollToTop( - viewModel: FeedViewModel, + videoFeedContentState: FeedContentState, pagerState: PagerState, ) { - val scrollToTop by viewModel.scrollToTop.collectAsStateWithLifecycle() + val scrollToTop by videoFeedContentState.scrollToTop.collectAsStateWithLifecycle() LaunchedEffect(scrollToTop) { - if (scrollToTop > 0 && viewModel.scrolltoTopPending) { + if (scrollToTop > 0 && videoFeedContentState.scrolltoTopPending) { pagerState.scrollToPage(page = 0) - viewModel.sentToTop() + videoFeedContentState.sentToTop() } } } @Composable fun RenderPage( - videoFeedView: NostrVideoFeedViewModel, + videoFeedContentState: FeedContentState, pagerStateKey: String?, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - val feedState by videoFeedView.feedContent.collectAsStateWithLifecycle() + val feedState by videoFeedContentState.feedContent.collectAsStateWithLifecycle() CrossfadeIfEnabled( targetState = feedState, @@ -183,13 +182,13 @@ fun RenderPage( ) { state -> when (state) { is FeedState.Empty -> { - FeedEmpty {} + FeedEmpty(videoFeedContentState::invalidateData) } is FeedState.FeedError -> { - FeedError(state.errorMessage) {} + FeedError(state.errorMessage, videoFeedContentState::invalidateData) } is FeedState.Loaded -> { - LoadedState(state, pagerStateKey, videoFeedView, accountViewModel, nav) + LoadedState(state, pagerStateKey, videoFeedContentState, accountViewModel, nav) } is FeedState.Loading -> { LoadingFeed() @@ -203,7 +202,7 @@ fun RenderPage( private fun LoadedState( state: FeedState.Loaded, pagerStateKey: String?, - videoFeedView: NostrVideoFeedViewModel, + videoFeedContentState: FeedContentState, accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { @@ -214,9 +213,9 @@ private fun LoadedState( rememberPagerState { state.feed.value.size } } - WatchScrollToTop(videoFeedView, pagerState) + WatchScrollToTop(videoFeedContentState, pagerState) - RefresheableBox(viewModel = videoFeedView) { + RefresheableBox(invalidateableContent = videoFeedContentState) { SlidingCarousel( state.feed, pagerState,