diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt index bf5720b83..da5cf1f55 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomFeedView.kt @@ -54,7 +54,7 @@ fun RefreshingChatroomFeedView( scrollStateKey: String? = null, enablePullRefresh: Boolean = true, ) { - RefresheableView(viewModel, enablePullRefresh) { + RefresheableBox(viewModel, enablePullRefresh) { SaveableFeedState(viewModel, scrollStateKey) { listState -> RenderChatroomFeedView( viewModel, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt index ffa1a6f7e..43fa223fc 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/ChatroomListFeedView.kt @@ -45,7 +45,7 @@ fun ChatroomListFeedView( nav: (String) -> Unit, markAsRead: MutableState, ) { - RefresheableView(viewModel, true) { CrossFadeState(viewModel, accountViewModel, nav, markAsRead) } + RefresheableBox(viewModel, true) { CrossFadeState(viewModel, accountViewModel, nav, markAsRead) } } @Composable diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt index f1064990c..920977642 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/FeedView.kt @@ -27,6 +27,7 @@ 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 @@ -56,6 +57,7 @@ import com.vitorpamplona.amethyst.R import com.vitorpamplona.amethyst.ui.note.NoteCompose import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel import com.vitorpamplona.amethyst.ui.theme.FeedPadding +import com.vitorpamplona.amethyst.ui.theme.StdVertSpacer import kotlin.time.ExperimentalTime @Composable @@ -67,13 +69,15 @@ fun RefresheableFeedView( accountViewModel: AccountViewModel, nav: (String) -> Unit, ) { - RefresheableView(viewModel, enablePullRefresh) { - SaveableFeedState(viewModel, routeForLastRead, scrollStateKey, accountViewModel, nav) + RefresheableBox(viewModel, enablePullRefresh) { + SaveableFeedState(viewModel, scrollStateKey) { listState -> + RenderFeedState(viewModel, accountViewModel, listState, nav, routeForLastRead) + } } } @Composable -fun RefresheableView( +fun RefresheableBox( viewModel: InvalidatableViewModel, enablePullRefresh: Boolean = true, content: @Composable () -> Unit, @@ -108,19 +112,6 @@ fun RefresheableView( } } -@Composable -private fun SaveableFeedState( - viewModel: FeedViewModel, - routeForLastRead: String?, - scrollStateKey: String? = null, - accountViewModel: AccountViewModel, - nav: (String) -> Unit, -) { - SaveableFeedState(viewModel, scrollStateKey) { listState -> - RenderFeed(viewModel, accountViewModel, listState, nav, routeForLastRead) - } -} - @Composable fun SaveableFeedState( viewModel: FeedViewModel, @@ -158,12 +149,16 @@ fun SaveableGridFeedState( } @Composable -private fun RenderFeed( +fun RenderFeedState( viewModel: FeedViewModel, accountViewModel: AccountViewModel, listState: LazyListState, nav: (String) -> Unit, routeForLastRead: String?, + onLoaded: @Composable (FeedState.Loaded) -> Unit = { FeedLoaded(it, listState, routeForLastRead, accountViewModel, nav) }, + onEmpty: @Composable () -> Unit = { FeedEmpty { viewModel.invalidateData() } }, + onError: @Composable (String) -> Unit = { FeedError(it) { viewModel.invalidateData() } }, + onLoading: @Composable () -> Unit = { LoadingFeed() }, ) { val feedState by viewModel.feedContent.collectAsStateWithLifecycle() @@ -172,24 +167,10 @@ private fun RenderFeed( animationSpec = tween(durationMillis = 100), ) { state -> when (state) { - is FeedState.Empty -> { - FeedEmpty { viewModel.invalidateData() } - } - is FeedState.FeedError -> { - FeedError(state.errorMessage) { viewModel.invalidateData() } - } - is FeedState.Loaded -> { - FeedLoaded( - state = state, - listState = listState, - routeForLastRead = routeForLastRead, - accountViewModel = accountViewModel, - nav = nav, - ) - } - is FeedState.Loading -> { - LoadingFeed() - } + is FeedState.Empty -> onEmpty() + is FeedState.FeedError -> onError(state.errorMessage) + is FeedState.Loaded -> onLoaded(state) + is FeedState.Loading -> onLoading() } } } @@ -277,6 +258,7 @@ fun FeedError( verticalArrangement = Arrangement.Center, ) { Text("${stringResource(R.string.error_loading_replies)} $errorMessage") + Spacer(modifier = StdVertSpacer) Button( modifier = Modifier.align(Alignment.CenterHorizontally), onClick = onRefresh, @@ -294,6 +276,7 @@ fun FeedEmpty(onRefresh: () -> Unit) { verticalArrangement = Arrangement.Center, ) { Text(stringResource(R.string.feed_is_empty)) + Spacer(modifier = StdVertSpacer) OutlinedButton(onClick = onRefresh) { Text(text = stringResource(R.string.refresh)) } } } diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt index f4c355fdc..e1f3a41d6 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/StringFeedView.kt @@ -45,7 +45,7 @@ fun RefreshingFeedStringFeedView( enablePullRefresh: Boolean = true, inner: @Composable (String) -> Unit, ) { - RefresheableView(viewModel, enablePullRefresh) { StringFeedView(viewModel, inner = inner) } + RefresheableBox(viewModel, enablePullRefresh) { StringFeedView(viewModel, inner = inner) } } @Composable diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt index 6fc453d92..bc52f5c4a 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/UserFeedView.kt @@ -39,7 +39,7 @@ fun RefreshingFeedUserFeedView( nav: (String) -> Unit, enablePullRefresh: Boolean = true, ) { - RefresheableView(viewModel, enablePullRefresh) { UserFeedView(viewModel, accountViewModel, nav) } + RefresheableBox(viewModel, enablePullRefresh) { UserFeedView(viewModel, accountViewModel, nav) } } @Composable diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt index 10ff2a720..a5f134cf1 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/DiscoverScreen.kt @@ -69,7 +69,7 @@ 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.PagerStateKeys -import com.vitorpamplona.amethyst.ui.screen.RefresheableView +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 @@ -196,7 +196,7 @@ private fun DiscoverPages( } HorizontalPager(state = pagerState) { page -> - RefresheableView(tabs[page].viewModel, true) { + RefresheableBox(tabs[page].viewModel, true) { if (tabs[page].viewModel is NostrDiscoverMarketplaceFeedViewModel) { SaveableGridFeedState(tabs[page].viewModel, scrollStateKey = tabs[page].scrollStateKey) { listState -> diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt index 1381ee22b..73346dd8d 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HiddenUsersScreen.kt @@ -71,7 +71,7 @@ import com.vitorpamplona.amethyst.ui.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.RefresheableView +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.screen.UserFeedViewModel @@ -216,7 +216,7 @@ private fun HiddenWordsFeed( hiddenWordsViewModel: NostrHiddenWordsFeedViewModel, accountViewModel: AccountViewModel, ) { - RefresheableView(hiddenWordsViewModel, false) { + RefresheableBox(hiddenWordsViewModel, false) { StringFeedView( hiddenWordsViewModel, post = { AddMuteWordTextField(accountViewModel) }, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt index 7773764f6..24c5a7ede 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/HomeScreen.kt @@ -22,11 +22,15 @@ package com.vitorpamplona.amethyst.ui.screen.loggedIn import androidx.compose.animation.Crossfade import androidx.compose.foundation.ExperimentalFoundationApi +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.fillMaxSize import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.PagerState import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.OutlinedButton import androidx.compose.material3.Tab import androidx.compose.material3.TabRow import androidx.compose.material3.Text @@ -39,9 +43,11 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.compose.collectAsStateWithLifecycle @@ -54,10 +60,14 @@ 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.RefresheableFeedView +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.theme.StdVertSpacer import com.vitorpamplona.amethyst.ui.theme.TabRowHeight +import com.vitorpamplona.amethyst.ui.theme.ThemeComparisonRow import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.toImmutableList import kotlinx.coroutines.launch @@ -181,7 +191,7 @@ private fun HomePages( } HorizontalPager(state = pagerState, userScrollEnabled = false) { page -> - RefresheableFeedView( + HomeFeeds( viewModel = tabs[page].viewModel, routeForLastRead = tabs[page].routeForLastRead, scrollStateKey = tabs[page].scrollStateKey, @@ -191,6 +201,51 @@ private fun HomePages( } } +@Composable +fun HomeFeeds( + viewModel: FeedViewModel, + routeForLastRead: String?, + enablePullRefresh: Boolean = true, + scrollStateKey: String? = null, + accountViewModel: AccountViewModel, + nav: (String) -> Unit, +) { + RefresheableBox(viewModel, enablePullRefresh) { + SaveableFeedState(viewModel, scrollStateKey) { listState -> + RenderFeedState( + viewModel = viewModel, + accountViewModel = accountViewModel, + listState = listState, + nav = nav, + routeForLastRead = routeForLastRead, + onEmpty = { HomeFeedEmpty { viewModel.invalidateData() } }, + ) + } + } +} + +@Preview +@Composable +fun HomeFeedEmptyPreview() { + ThemeComparisonRow( + onDark = { HomeFeedEmpty {} }, + onLight = { HomeFeedEmpty {} }, + ) +} + +@Composable +fun HomeFeedEmpty(onRefresh: () -> Unit) { + Column( + Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center, + ) { + Text(stringResource(R.string.feed_is_empty)) + Spacer(modifier = StdVertSpacer) + OutlinedButton(onClick = onRefresh) { Text(text = stringResource(R.string.refresh)) } + } +} + @Composable fun CheckIfUrlIsOnline( url: String, diff --git a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt index 1dface6d1..0e5b4eeb4 100644 --- a/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt +++ b/app/src/main/java/com/vitorpamplona/amethyst/ui/screen/loggedIn/VideoScreen.kt @@ -86,7 +86,7 @@ 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.RefresheableView +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.theme.Size35Modifier @@ -214,7 +214,7 @@ private fun LoadedState( WatchScrollToTop(videoFeedView, pagerState) - RefresheableView(viewModel = videoFeedView) { + RefresheableBox(viewModel = videoFeedView) { SlidingCarousel( state.feed, pagerState,