Adds Home-only empty feed screen.

This commit is contained in:
Vitor Pamplona 2024-03-02 15:57:49 -05:00
parent 16c171ec40
commit a301421915
9 changed files with 85 additions and 47 deletions

View File

@ -54,7 +54,7 @@ fun RefreshingChatroomFeedView(
scrollStateKey: String? = null,
enablePullRefresh: Boolean = true,
) {
RefresheableView(viewModel, enablePullRefresh) {
RefresheableBox(viewModel, enablePullRefresh) {
SaveableFeedState(viewModel, scrollStateKey) { listState ->
RenderChatroomFeedView(
viewModel,

View File

@ -45,7 +45,7 @@ fun ChatroomListFeedView(
nav: (String) -> Unit,
markAsRead: MutableState<Boolean>,
) {
RefresheableView(viewModel, true) { CrossFadeState(viewModel, accountViewModel, nav, markAsRead) }
RefresheableBox(viewModel, true) { CrossFadeState(viewModel, accountViewModel, nav, markAsRead) }
}
@Composable

View File

@ -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)) }
}
}

View File

@ -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

View File

@ -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

View File

@ -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 ->

View File

@ -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) },

View File

@ -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,

View File

@ -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,