Implement LoadStateIndicator
This commit is contained in:
1 parent
2a667e62f4
commit
a4e9291bf9
4 files changed
+140
-89
No files matched your search
+101
-89
@@ -1,4 +1,4 @@
|
||||
package com.hippo.ehviewer.ui.scene
|
||||
package com.hippo.ehviewer.ui.main
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -12,12 +12,14 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridItemScope
|
||||
import androidx.compose.foundation.lazy.grid.LazyGridState
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridItemSpan
|
||||
import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.Icon
|
||||
@@ -28,7 +30,6 @@ import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.pulltorefresh.PullToRefreshState
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.SideEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
@@ -57,6 +58,8 @@ import com.hippo.ehviewer.collectAsState
|
||||
import com.hippo.ehviewer.icons.EhIcons
|
||||
import com.hippo.ehviewer.icons.big.SadAndroid
|
||||
import com.hippo.ehviewer.ui.WebViewActivity
|
||||
import com.hippo.ehviewer.ui.scene.collectDetailSizeAsState
|
||||
import com.hippo.ehviewer.ui.scene.navAnimated
|
||||
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalGrid
|
||||
import com.hippo.ehviewer.ui.tools.FastScrollLazyVerticalStaggeredGrid
|
||||
import com.hippo.ehviewer.ui.tools.LocalDialogState
|
||||
@@ -69,15 +72,15 @@ fun GalleryList(
|
||||
data: LazyPagingItems<BaseGalleryInfo>,
|
||||
contentModifier: Modifier = Modifier,
|
||||
contentPadding: PaddingValues = PaddingValues(0.dp),
|
||||
listMode: Int = 0,
|
||||
listMode: Int,
|
||||
detailListState: LazyGridState = rememberLazyGridState(),
|
||||
detailItemContent: @Composable (LazyGridItemScope.(BaseGalleryInfo) -> Unit),
|
||||
thumbListState: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
|
||||
thumbItemContent: @Composable (LazyStaggeredGridItemScope.(BaseGalleryInfo) -> Unit) = {},
|
||||
refreshState: PullToRefreshState? = null,
|
||||
onRefresh: suspend CoroutineScope.() -> Unit = {},
|
||||
onLoading: suspend CoroutineScope.() -> Unit = {},
|
||||
navigator: NavController? = null,
|
||||
thumbItemContent: @Composable (LazyStaggeredGridItemScope.(BaseGalleryInfo) -> Unit),
|
||||
refreshState: PullToRefreshState,
|
||||
onRefresh: suspend CoroutineScope.() -> Unit,
|
||||
onLoading: suspend CoroutineScope.() -> Unit,
|
||||
navigator: NavController,
|
||||
) {
|
||||
val layoutDirection = LocalLayoutDirection.current
|
||||
val marginH = dimensionResource(id = R.dimen.gallery_list_margin_h)
|
||||
@@ -88,11 +91,7 @@ fun GalleryList(
|
||||
start = contentPadding.calculateStartPadding(layoutDirection) + marginH,
|
||||
end = contentPadding.calculateEndPadding(layoutDirection) + marginH,
|
||||
)
|
||||
val combinedModifier = if (refreshState != null) {
|
||||
contentModifier.nestedScroll(refreshState.nestedScrollConnection)
|
||||
} else {
|
||||
contentModifier
|
||||
}
|
||||
val combinedModifier = contentModifier.nestedScroll(refreshState.nestedScrollConnection)
|
||||
Box(modifier = modifier.fillMaxSize()) {
|
||||
if (listMode == 0) {
|
||||
val columnWidth by collectDetailSizeAsState()
|
||||
@@ -114,6 +113,13 @@ fun GalleryList(
|
||||
detailItemContent(info)
|
||||
}
|
||||
}
|
||||
if (data.loadState.append !is LoadState.NotLoading) {
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
LoadStateIndicator(state = data.loadState.append) {
|
||||
data.retry()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val gridInterval = dimensionResource(R.dimen.gallery_grid_interval)
|
||||
@@ -136,92 +142,98 @@ fun GalleryList(
|
||||
thumbItemContent(info)
|
||||
}
|
||||
}
|
||||
if (data.loadState.append !is LoadState.NotLoading) {
|
||||
item(span = StaggeredGridItemSpan.FullLine) {
|
||||
LoadStateIndicator(state = data.loadState.append) {
|
||||
data.retry()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (refreshState != null) {
|
||||
if (refreshState.isRefreshing) {
|
||||
LaunchedEffect(Unit) {
|
||||
var isLoading by remember { mutableStateOf(false) }
|
||||
if (refreshState.isRefreshing) {
|
||||
LaunchedEffect(Unit) {
|
||||
if (data.loadState.prepend.endOfPaginationReached) {
|
||||
onRefresh()
|
||||
}
|
||||
}
|
||||
|
||||
val dialogState = LocalDialogState.current
|
||||
var refreshing by remember { mutableStateOf(false) }
|
||||
when (val state = data.loadState.refresh) {
|
||||
is LoadState.Loading -> if (!refreshState.isRefreshing) {
|
||||
LaunchedEffect(Unit) {
|
||||
onLoading()
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if (listMode == 0) {
|
||||
detailListState.scrollToItem(0)
|
||||
} else {
|
||||
thumbListState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
Surface {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
refreshing = true
|
||||
data.retry()
|
||||
}
|
||||
|
||||
is LoadState.Error -> {
|
||||
Surface {
|
||||
LaunchedEffect(state) {
|
||||
if (state.error.cause is CloudflareBypassException) {
|
||||
dialogState.awaitPermissionOrCancel(title = R.string.cloudflare_bypass_failed) {
|
||||
Text(text = stringResource(id = R.string.open_in_webview))
|
||||
}
|
||||
navigator?.navAnimated(R.id.webView, bundleOf(WebViewActivity.KEY_URL to EhUrl.host))
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.widthIn(max = 228.dp)
|
||||
.clip(ShapeDefaults.Small).clickable { data.retry() },
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = EhIcons.Big.Default.SadAndroid,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(16.dp).size(120.dp),
|
||||
tint = MaterialTheme.colorScheme.tertiary,
|
||||
)
|
||||
Text(
|
||||
text = ExceptionUtils.getReadableString(state.error),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
SideEffect {
|
||||
if (refreshing) {
|
||||
refreshState.endRefresh()
|
||||
refreshing = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LoadState.NotLoading -> SideEffect {
|
||||
// Don't use `refreshState.isRefreshing` here because recomposition may happen
|
||||
// before loading state changes
|
||||
if (refreshing) {
|
||||
}
|
||||
LaunchedEffect(data.loadState) {
|
||||
if (data.loadState.prepend is LoadState.Loading ||
|
||||
data.loadState.refresh is LoadState.Loading
|
||||
) {
|
||||
isLoading = true
|
||||
} else {
|
||||
if (isLoading) {
|
||||
refreshState.endRefresh()
|
||||
refreshing = false
|
||||
isLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val dialogState = LocalDialogState.current
|
||||
when (val state = data.loadState.refresh) {
|
||||
is LoadState.Loading -> if (!refreshState.isRefreshing) {
|
||||
LaunchedEffect(Unit) {
|
||||
onLoading()
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
if (listMode == 0) {
|
||||
detailListState.scrollToItem(0)
|
||||
} else {
|
||||
thumbListState.scrollToItem(0)
|
||||
}
|
||||
}
|
||||
Surface {
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
CircularProgressIndicator()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LoadState.Error -> {
|
||||
Surface {
|
||||
LaunchedEffect(state) {
|
||||
if (state.error.cause is CloudflareBypassException) {
|
||||
dialogState.awaitPermissionOrCancel(title = R.string.cloudflare_bypass_failed) {
|
||||
Text(text = stringResource(id = R.string.open_in_webview))
|
||||
}
|
||||
navigator.navAnimated(R.id.webView, bundleOf(WebViewActivity.KEY_URL to EhUrl.host))
|
||||
}
|
||||
}
|
||||
Box(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.widthIn(max = 228.dp)
|
||||
.clip(ShapeDefaults.Small).clickable { data.retry() },
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = EhIcons.Big.Default.SadAndroid,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.padding(16.dp).size(120.dp),
|
||||
tint = MaterialTheme.colorScheme.tertiary,
|
||||
)
|
||||
Text(
|
||||
text = ExceptionUtils.getReadableString(state.error),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
textAlign = TextAlign.Center,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LoadState.NotLoading -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package com.hippo.ehviewer.ui.main
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.LinearProgressIndicator
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.paging.LoadState
|
||||
import com.hippo.ehviewer.R
|
||||
import com.hippo.ehviewer.util.ExceptionUtils
|
||||
|
||||
@Composable
|
||||
fun LoadStateIndicator(
|
||||
modifier: Modifier = Modifier,
|
||||
state: LoadState,
|
||||
retry: () -> Unit,
|
||||
) {
|
||||
when (state) {
|
||||
is LoadState.Loading -> {
|
||||
LinearProgressIndicator(modifier = modifier)
|
||||
}
|
||||
|
||||
is LoadState.Error -> {
|
||||
Column(modifier = modifier, horizontalAlignment = Alignment.CenterHorizontally) {
|
||||
Text(text = ExceptionUtils.getReadableString(state.error))
|
||||
Button(onClick = retry) {
|
||||
Text(text = stringResource(id = R.string.action_retry))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
is LoadState.NotLoading -> Unit
|
||||
}
|
||||
}
|
||||
@@ -79,6 +79,7 @@ import com.hippo.ehviewer.ui.MainActivity
|
||||
import com.hippo.ehviewer.ui.main.FabLayout
|
||||
import com.hippo.ehviewer.ui.main.GalleryInfoGridItem
|
||||
import com.hippo.ehviewer.ui.main.GalleryInfoListItem
|
||||
import com.hippo.ehviewer.ui.main.GalleryList
|
||||
import com.hippo.ehviewer.ui.startDownload
|
||||
import com.hippo.ehviewer.ui.tools.LocalDialogState
|
||||
import com.hippo.ehviewer.ui.tools.rememberInVM
|
||||
|
||||
@@ -145,6 +145,7 @@ import com.hippo.ehviewer.ui.main.FAB_ANIMATE_TIME
|
||||
import com.hippo.ehviewer.ui.main.FabLayout
|
||||
import com.hippo.ehviewer.ui.main.GalleryInfoGridItem
|
||||
import com.hippo.ehviewer.ui.main.GalleryInfoListItem
|
||||
import com.hippo.ehviewer.ui.main.GalleryList
|
||||
import com.hippo.ehviewer.ui.main.ImageSearch
|
||||
import com.hippo.ehviewer.ui.main.NormalSearch
|
||||
import com.hippo.ehviewer.ui.main.SearchAdvanced
|
||||
|
||||
Reference in new issue
Block a user