优化
This commit is contained in:
@@ -19,7 +19,6 @@ interface ExploreRepository {
|
||||
fun getExploreGroups(): Flow<List<String>>
|
||||
fun getExploreSources(query: String, selectedGroup: String): Flow<List<BookSourcePart>>
|
||||
suspend fun getBookSource(sourceUrl: String): BookSource?
|
||||
suspend fun saveSearchBooks(books: List<SearchBook>)
|
||||
suspend fun getSourceExploreKinds(sourceUrl: String): List<ExploreKind>
|
||||
suspend fun topSource(bookSource: BookSourcePart)
|
||||
suspend fun deleteSource(sourceUrl: String)
|
||||
@@ -91,10 +90,6 @@ class ExploreRepositoryImpl(
|
||||
return@withContext source?.exploreKinds() ?: emptyList()
|
||||
}
|
||||
|
||||
override suspend fun saveSearchBooks(books: List<SearchBook>) {
|
||||
appDb.searchBookDao.insert(*books.toTypedArray())
|
||||
}
|
||||
|
||||
override suspend fun topSource(bookSource: BookSourcePart) {
|
||||
val minOrder = appDb.bookSourceDao.minOrder
|
||||
appDb.bookSourceDao.upOrder(bookSource.copy(customOrder = minOrder - 1))
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
package io.legado.app.domain.usecase
|
||||
|
||||
import io.legado.app.constant.BookType
|
||||
import io.legado.app.data.appDb
|
||||
import io.legado.app.data.entities.SearchBook
|
||||
import io.legado.app.data.repository.BookRepository
|
||||
import io.legado.app.help.book.removeType
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class AddToBookshelfUseCase {
|
||||
class AddToBookshelfUseCase(
|
||||
private val bookRepository: BookRepository,
|
||||
) {
|
||||
|
||||
suspend fun execute(book: SearchBook) = withContext(Dispatchers.IO) {
|
||||
suspend fun execute(book: SearchBook) {
|
||||
val b = book.toBook()
|
||||
b.removeType(BookType.notShelf)
|
||||
if (b.order == 0) {
|
||||
b.order = appDb.bookDao.minOrder - 1
|
||||
b.order = bookRepository.getMinOrder() - 1
|
||||
}
|
||||
b.save()
|
||||
bookRepository.insert(b)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ sealed interface ExploreShowSheet {
|
||||
|
||||
sealed interface ExploreShowIntent {
|
||||
data class InitData(
|
||||
val sourceUrl: String?,
|
||||
val sourceUrl: String,
|
||||
val exploreUrl: String?,
|
||||
) : ExploreShowIntent
|
||||
|
||||
|
||||
@@ -135,30 +135,38 @@ fun ExploreShowScreen(
|
||||
val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
|
||||
val isGridMode = state.layoutState == 1
|
||||
val hazeState = remember { HazeState() }
|
||||
val shouldLoadMoreList = remember {
|
||||
val showLoadMoreFooter = !state.isRefreshing &&
|
||||
(state.isLoading || state.errorMsg != null || state.isEnd)
|
||||
val canLoadMore = state.books.isNotEmpty() &&
|
||||
!state.isLoading &&
|
||||
!state.isRefreshing &&
|
||||
!state.isEnd &&
|
||||
state.errorMsg == null
|
||||
val shouldLoadMore by remember(isGridMode) {
|
||||
derivedStateOf {
|
||||
val total = listState.layoutInfo.totalItemsCount
|
||||
val last = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
||||
total > 0 && last >= total - 3
|
||||
if (isGridMode) {
|
||||
val total = gridState.layoutInfo.totalItemsCount
|
||||
val last = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
||||
total > 0 && last >= total - 1
|
||||
} else {
|
||||
val total = listState.layoutInfo.totalItemsCount
|
||||
val last = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
||||
total > 0 && last >= total - 3
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val shouldLoadMoreGrid = remember {
|
||||
derivedStateOf {
|
||||
val total = gridState.layoutInfo.totalItemsCount
|
||||
val last = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
||||
total > 0 && last >= total - 1
|
||||
LaunchedEffect(
|
||||
shouldLoadMore,
|
||||
isGridMode,
|
||||
canLoadMore,
|
||||
state.books.size,
|
||||
) {
|
||||
if (shouldLoadMore && canLoadMore) {
|
||||
viewModel.onIntent(ExploreShowIntent.LoadMore)
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(shouldLoadMoreList.value, isGridMode) {
|
||||
if (!isGridMode && shouldLoadMoreList.value) viewModel.onIntent(ExploreShowIntent.LoadMore)
|
||||
}
|
||||
|
||||
LaunchedEffect(shouldLoadMoreGrid.value, isGridMode) {
|
||||
if (isGridMode && shouldLoadMoreGrid.value) viewModel.onIntent(ExploreShowIntent.LoadMore)
|
||||
}
|
||||
|
||||
LaunchedEffect(isGridMode) {
|
||||
if (isGridMode) {
|
||||
if (listState.firstVisibleItemIndex > 0) {
|
||||
@@ -325,14 +333,14 @@ fun ExploreShowScreen(
|
||||
)
|
||||
}
|
||||
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
LoadMoreFooter(
|
||||
isLoading = state.isLoading && !state.isRefreshing,
|
||||
errorMsg = state.errorMsg,
|
||||
isEnd = state.isEnd,
|
||||
onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) },
|
||||
onLoadMore = { viewModel.onIntent(ExploreShowIntent.ForceLoadNext) }
|
||||
)
|
||||
if (showLoadMoreFooter) {
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
ExploreShowLoadMoreFooter(
|
||||
state = state,
|
||||
onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) },
|
||||
onLoadMore = { viewModel.onIntent(ExploreShowIntent.ForceLoadNext) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -376,14 +384,14 @@ fun ExploreShowScreen(
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
LoadMoreFooter(
|
||||
isLoading = state.isLoading && !state.isRefreshing,
|
||||
errorMsg = state.errorMsg,
|
||||
isEnd = state.isEnd,
|
||||
onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) },
|
||||
onLoadMore = { viewModel.onIntent(ExploreShowIntent.ForceLoadNext) }
|
||||
)
|
||||
if (showLoadMoreFooter) {
|
||||
item {
|
||||
ExploreShowLoadMoreFooter(
|
||||
state = state,
|
||||
onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) },
|
||||
onLoadMore = { viewModel.onIntent(ExploreShowIntent.ForceLoadNext) },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -410,6 +418,22 @@ fun ExploreShowScreen(
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun ExploreShowLoadMoreFooter(
|
||||
state: ExploreShowUiState,
|
||||
onRetry: () -> Unit,
|
||||
onLoadMore: () -> Unit,
|
||||
) {
|
||||
LoadMoreFooter(
|
||||
isLoading = state.isLoading,
|
||||
errorMsg = state.errorMsg,
|
||||
isEnd = state.isEnd,
|
||||
onRetry = onRetry,
|
||||
onLoadMore = onLoadMore,
|
||||
autoLoad = false,
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
fun ExploreBookItem(
|
||||
|
||||
@@ -2,7 +2,6 @@ package io.legado.app.ui.book.explore
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import io.legado.app.data.entities.BookSource
|
||||
import io.legado.app.data.entities.SearchBook
|
||||
import io.legado.app.data.entities.rule.ExploreKind
|
||||
import io.legado.app.data.repository.ExploreRepository
|
||||
@@ -25,6 +24,25 @@ import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import splitties.init.appCtx
|
||||
|
||||
private data class ExploreShowLoadState(
|
||||
val isLoading: Boolean = false,
|
||||
val isRefreshing: Boolean = false,
|
||||
val isEnd: Boolean = false,
|
||||
val errorMsg: String? = null,
|
||||
)
|
||||
|
||||
private data class ExploreShowKindState(
|
||||
val kinds: List<ExploreKind> = emptyList(),
|
||||
val selectedKindTitle: String? = null,
|
||||
)
|
||||
|
||||
private data class ExploreShowDisplayState(
|
||||
val sourceUrl: String? = null,
|
||||
val layoutState: Int,
|
||||
val gridCount: Int,
|
||||
val sheet: ExploreShowSheet = ExploreShowSheet.None,
|
||||
)
|
||||
|
||||
class ExploreShowViewModel(
|
||||
private val repository: ExploreRepository,
|
||||
private val resolveBookShelfStateUseCase: ResolveBookShelfStateUseCase,
|
||||
@@ -35,15 +53,18 @@ class ExploreShowViewModel(
|
||||
|
||||
private val _rawBooks = MutableStateFlow<List<SearchBook>>(emptyList())
|
||||
private val _bookshelf = MutableStateFlow<Set<BookShelfKey>>(emptySet())
|
||||
private val _isLoading = MutableStateFlow(false)
|
||||
private val _isEnd = MutableStateFlow(false)
|
||||
private val _errorMsg = MutableStateFlow<String?>(null)
|
||||
private val _kinds = MutableStateFlow<List<ExploreKind>>(emptyList())
|
||||
private val _selectedKindTitle = MutableStateFlow<String?>(null)
|
||||
private val _loadState = MutableStateFlow(ExploreShowLoadState())
|
||||
private val _kindState = MutableStateFlow(ExploreShowKindState())
|
||||
private val _displayState = MutableStateFlow(
|
||||
ExploreShowDisplayState(
|
||||
layoutState = AppConfig.exploreLayoutState,
|
||||
gridCount = appCtx.exploreLayoutGrid,
|
||||
)
|
||||
)
|
||||
|
||||
private var bookSource: BookSource? = null
|
||||
private var sourceUrl: String? = null
|
||||
private var exploreUrl: String? = null
|
||||
private var initialized = false
|
||||
private var page = 1
|
||||
private var autoPageCount = 0
|
||||
|
||||
@@ -54,8 +75,8 @@ class ExploreShowViewModel(
|
||||
|
||||
private val _uiState = MutableStateFlow(
|
||||
ExploreShowUiState(
|
||||
layoutState = AppConfig.exploreLayoutState,
|
||||
gridCount = appCtx.exploreLayoutGrid,
|
||||
layoutState = _displayState.value.layoutState,
|
||||
gridCount = _displayState.value.gridCount,
|
||||
)
|
||||
)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
@@ -77,8 +98,8 @@ class ExploreShowViewModel(
|
||||
is ExploreShowIntent.SwitchKind -> switchKind(intent.kind)
|
||||
ExploreShowIntent.ToggleLayout -> toggleLayout()
|
||||
is ExploreShowIntent.SaveGridCount -> saveGridCount(intent.count)
|
||||
is ExploreShowIntent.ShowSheet -> _uiState.update { it.copy(sheet = intent.sheet) }
|
||||
ExploreShowIntent.DismissSheet -> _uiState.update { it.copy(sheet = ExploreShowSheet.None) }
|
||||
is ExploreShowIntent.ShowSheet -> _displayState.update { it.copy(sheet = intent.sheet) }
|
||||
ExploreShowIntent.DismissSheet -> _displayState.update { it.copy(sheet = ExploreShowSheet.None) }
|
||||
is ExploreShowIntent.OpenBook -> emitEffect(
|
||||
ExploreShowEffect.OpenBookInfo(
|
||||
name = intent.book.name,
|
||||
@@ -111,21 +132,10 @@ class ExploreShowViewModel(
|
||||
combine(
|
||||
_rawBooks,
|
||||
_bookshelf,
|
||||
_isLoading,
|
||||
_isEnd,
|
||||
_errorMsg,
|
||||
_kinds,
|
||||
_selectedKindTitle,
|
||||
) { values ->
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val rawBooks = values[0] as List<SearchBook>
|
||||
val bookshelf = values[1] as Set<BookShelfKey>
|
||||
val isLoading = values[2] as Boolean
|
||||
val isEnd = values[3] as Boolean
|
||||
val errorMsg = values[4] as String?
|
||||
val kinds = values[5] as List<ExploreKind>
|
||||
val selectedKindTitle = values[6] as String?
|
||||
|
||||
_loadState,
|
||||
_kindState,
|
||||
_displayState,
|
||||
) { rawBooks, bookshelf, loadState, kindState, displayState ->
|
||||
val books = rawBooks.map { item ->
|
||||
ExploreBookItemUi(
|
||||
book = item,
|
||||
@@ -139,154 +149,154 @@ class ExploreShowViewModel(
|
||||
}
|
||||
|
||||
ExploreShowUiState(
|
||||
sourceUrl = sourceUrl,
|
||||
sourceUrl = displayState.sourceUrl,
|
||||
books = books.toImmutableList(),
|
||||
kinds = kinds.toImmutableList(),
|
||||
selectedKindTitle = selectedKindTitle,
|
||||
layoutState = _uiState.value.layoutState,
|
||||
gridCount = _uiState.value.gridCount,
|
||||
isLoading = isLoading,
|
||||
isRefreshing = isLoading && page == 1,
|
||||
isEnd = isEnd,
|
||||
errorMsg = errorMsg,
|
||||
sheet = _uiState.value.sheet,
|
||||
kinds = kindState.kinds.toImmutableList(),
|
||||
selectedKindTitle = kindState.selectedKindTitle,
|
||||
layoutState = displayState.layoutState,
|
||||
gridCount = displayState.gridCount,
|
||||
isLoading = loadState.isLoading,
|
||||
isRefreshing = loadState.isRefreshing,
|
||||
isEnd = loadState.isEnd,
|
||||
errorMsg = loadState.errorMsg,
|
||||
sheet = displayState.sheet,
|
||||
)
|
||||
}.collect { newState ->
|
||||
val oldState = _uiState.value
|
||||
_uiState.value = newState
|
||||
|
||||
if (newState.books.isEmpty() && _rawBooks.value.isNotEmpty() && !newState.isEnd && !newState.isLoading) {
|
||||
loadMore()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initData(incomingSourceUrl: String?, incomingExploreUrl: String?) {
|
||||
if (sourceUrl == incomingSourceUrl && exploreUrl == incomingExploreUrl && bookSource != null) {
|
||||
private fun initData(incomingSourceUrl: String, incomingExploreUrl: String?) {
|
||||
if (initialized && sourceUrl == incomingSourceUrl && exploreUrl == incomingExploreUrl) {
|
||||
return
|
||||
}
|
||||
initialized = true
|
||||
sourceUrl = incomingSourceUrl
|
||||
exploreUrl = incomingExploreUrl
|
||||
page = 1
|
||||
autoPageCount = 0
|
||||
bookSource = null
|
||||
_rawBooks.value = emptyList()
|
||||
_isEnd.value = false
|
||||
_errorMsg.value = null
|
||||
_selectedKindTitle.value = null
|
||||
_kinds.value = emptyList()
|
||||
|
||||
viewModelScope.launch {
|
||||
if (bookSource == null && incomingSourceUrl != null) {
|
||||
bookSource = repository.getBookSource(incomingSourceUrl)
|
||||
}
|
||||
|
||||
if (exploreUrl == null && bookSource != null) {
|
||||
loadKinds(incomingSourceUrl!!)
|
||||
}
|
||||
|
||||
loadMore(isRefresh = true)
|
||||
_loadState.value = ExploreShowLoadState()
|
||||
_kindState.value = ExploreShowKindState()
|
||||
_displayState.update {
|
||||
it.copy(
|
||||
sourceUrl = incomingSourceUrl,
|
||||
sheet = ExploreShowSheet.None,
|
||||
)
|
||||
}
|
||||
|
||||
if (incomingExploreUrl == null) {
|
||||
viewModelScope.launch {
|
||||
loadKinds(incomingSourceUrl)
|
||||
}
|
||||
}
|
||||
|
||||
loadMore(isRefresh = true)
|
||||
}
|
||||
|
||||
private fun loadKinds(sourceUrl: String) {
|
||||
viewModelScope.launch {
|
||||
_kinds.value = repository.getSourceExploreKinds(sourceUrl)
|
||||
}
|
||||
private suspend fun loadKinds(sourceUrl: String) {
|
||||
_kindState.update { it.copy(kinds = repository.getSourceExploreKinds(sourceUrl)) }
|
||||
}
|
||||
|
||||
private fun switchKind(kind: ExploreKind) {
|
||||
_selectedKindTitle.value = kind.title
|
||||
_kindState.update { it.copy(selectedKindTitle = kind.title) }
|
||||
exploreUrl = kind.url
|
||||
_isEnd.value = false
|
||||
_loadState.update { it.copy(isEnd = false) }
|
||||
autoPageCount = 0
|
||||
loadMore(isRefresh = true)
|
||||
}
|
||||
|
||||
private fun toggleLayout() {
|
||||
_uiState.update {
|
||||
val newState = if (it.layoutState == 0) 1 else 0
|
||||
AppConfig.exploreLayoutState = newState
|
||||
it.copy(layoutState = newState)
|
||||
_displayState.update {
|
||||
val layoutState = if (it.layoutState == 0) 1 else 0
|
||||
AppConfig.exploreLayoutState = layoutState
|
||||
it.copy(layoutState = layoutState)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveGridCount(count: Int) {
|
||||
appCtx.exploreLayoutGrid = count
|
||||
_uiState.update { it.copy(gridCount = count) }
|
||||
_displayState.update { it.copy(gridCount = count) }
|
||||
}
|
||||
|
||||
private fun loadMore(isRefresh: Boolean = false, forceLoad: Boolean = false) {
|
||||
val source = bookSource
|
||||
val url = exploreUrl ?: source?.exploreUrl
|
||||
if (source == null || url == null || _isLoading.value || (_isEnd.value && !isRefresh && !forceLoad)) return
|
||||
val source = sourceUrl
|
||||
val url = exploreUrl
|
||||
val loadState = _loadState.value
|
||||
if (source == null || loadState.isLoading || (loadState.isEnd && !isRefresh && !forceLoad)) return
|
||||
|
||||
_loadState.update {
|
||||
it.copy(
|
||||
isLoading = true,
|
||||
isRefreshing = isRefresh,
|
||||
isEnd = if (isRefresh || forceLoad) false else it.isEnd,
|
||||
errorMsg = null,
|
||||
)
|
||||
}
|
||||
|
||||
viewModelScope.launch {
|
||||
_isLoading.value = true
|
||||
_errorMsg.value = null
|
||||
|
||||
if (isRefresh) {
|
||||
page = 1
|
||||
_isEnd.value = false
|
||||
autoPageCount = 0
|
||||
_rawBooks.value = emptyList()
|
||||
}
|
||||
|
||||
if (forceLoad) {
|
||||
autoPageCount = 0
|
||||
_isEnd.value = false
|
||||
}
|
||||
|
||||
fetchPage(source, url)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchPage(source: BookSource, url: String) {
|
||||
private suspend fun fetchPage(sourceUrl: String, url: String?) {
|
||||
kotlin.runCatching {
|
||||
exploreBooksUseCase.execute(source.bookSourceUrl, url, args = null, page)
|
||||
exploreBooksUseCase.execute(sourceUrl, url, args = null, page)
|
||||
}.onSuccess { result ->
|
||||
if (result.books.isEmpty()) {
|
||||
page++
|
||||
autoPageCount++
|
||||
if (autoPageCount >= MAX_AUTO_PAGES) {
|
||||
_isEnd.value = true
|
||||
_isLoading.value = false
|
||||
} else {
|
||||
delay(AUTO_PAGE_DELAY_MS)
|
||||
fetchPage(source, url)
|
||||
}
|
||||
} else {
|
||||
val currentList = _rawBooks.value
|
||||
val existingUrls = currentList.map { it.bookUrl }.toSet()
|
||||
val uniqueNewBooks = result.books
|
||||
.filter { it.bookUrl !in existingUrls }
|
||||
.distinctBy { it.bookUrl }
|
||||
|
||||
if (result.books.isNotEmpty()) {
|
||||
saveSearchBooksUseCase.save(result.books)
|
||||
|
||||
val currentList = _rawBooks.value
|
||||
val existingUrls = currentList.map { it.bookUrl }.toSet()
|
||||
|
||||
val uniqueNewBooks = result.books
|
||||
.filter { it.bookUrl !in existingUrls }
|
||||
.distinctBy { it.bookUrl }
|
||||
|
||||
if (uniqueNewBooks.isEmpty()) {
|
||||
page++
|
||||
autoPageCount++
|
||||
if (autoPageCount >= MAX_AUTO_PAGES) {
|
||||
_isEnd.value = true
|
||||
_isLoading.value = false
|
||||
} else {
|
||||
delay(AUTO_PAGE_DELAY_MS)
|
||||
fetchPage(source, url)
|
||||
}
|
||||
} else {
|
||||
_rawBooks.value = currentList + uniqueNewBooks
|
||||
page++
|
||||
autoPageCount = 0
|
||||
_isEnd.value = false
|
||||
_isLoading.value = false
|
||||
}
|
||||
}
|
||||
}.onFailure {
|
||||
_errorMsg.value = it.stackTraceStr
|
||||
_isLoading.value = false
|
||||
|
||||
if (uniqueNewBooks.isEmpty()) {
|
||||
fetchNextAutoPageOrFinish(sourceUrl, url)
|
||||
} else {
|
||||
_rawBooks.value = currentList + uniqueNewBooks
|
||||
page++
|
||||
autoPageCount = 0
|
||||
_loadState.update { it.copy(isEnd = false) }
|
||||
finishLoading()
|
||||
}
|
||||
}.onFailure { throwable ->
|
||||
_loadState.update { it.copy(errorMsg = throwable.stackTraceStr) }
|
||||
finishLoading()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun fetchNextAutoPageOrFinish(sourceUrl: String, url: String?) {
|
||||
page++
|
||||
autoPageCount++
|
||||
if (autoPageCount >= MAX_AUTO_PAGES) {
|
||||
_loadState.update { it.copy(isEnd = true) }
|
||||
finishLoading()
|
||||
} else {
|
||||
delay(AUTO_PAGE_DELAY_MS)
|
||||
fetchPage(sourceUrl, url)
|
||||
}
|
||||
}
|
||||
|
||||
private fun finishLoading() {
|
||||
_loadState.update {
|
||||
it.copy(
|
||||
isLoading = false,
|
||||
isRefreshing = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -899,6 +899,7 @@ private fun ExpandedSourceSheet(
|
||||
title = sourceName,
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
val showLoadMoreFooter = isLoading || errorMsg != null || isEnd
|
||||
|
||||
val shouldLoadMore by remember {
|
||||
derivedStateOf {
|
||||
@@ -935,13 +936,16 @@ private fun ExpandedSourceSheet(
|
||||
)
|
||||
}
|
||||
|
||||
item {
|
||||
LoadMoreFooter(
|
||||
isLoading = isLoading,
|
||||
errorMsg = errorMsg,
|
||||
isEnd = isEnd,
|
||||
onRetry = onLoadMore,
|
||||
)
|
||||
if (showLoadMoreFooter) {
|
||||
item {
|
||||
LoadMoreFooter(
|
||||
isLoading = isLoading,
|
||||
errorMsg = errorMsg,
|
||||
isEnd = isEnd,
|
||||
onRetry = onLoadMore,
|
||||
autoLoad = false,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +135,8 @@ fun RssArticlesPage(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
topPadding = paddingValues.calculateTopPadding()
|
||||
) {
|
||||
val showLoadMoreFooter = !loadState.isRefreshing &&
|
||||
(loadState.isLoadingMore || loadState.errorMessage != null || !loadState.hasMore)
|
||||
when (layout) {
|
||||
RssArticleLayout.List, RssArticleLayout.LargeCard -> {
|
||||
val listState = rememberLazyListState()
|
||||
@@ -160,13 +162,16 @@ fun RssArticlesPage(
|
||||
onClick = onRead
|
||||
)
|
||||
}
|
||||
item {
|
||||
LoadMoreFooter(
|
||||
isLoading = loadState.isRefreshing || loadState.isLoadingMore,
|
||||
errorMsg = loadState.errorMessage,
|
||||
isEnd = !loadState.hasMore,
|
||||
onRetry = { rssSource?.let(viewModel::loadMore) }
|
||||
)
|
||||
if (showLoadMoreFooter) {
|
||||
item {
|
||||
LoadMoreFooter(
|
||||
isLoading = loadState.isLoadingMore,
|
||||
errorMsg = loadState.errorMessage,
|
||||
isEnd = !loadState.hasMore,
|
||||
onRetry = { rssSource?.let(viewModel::loadMore) },
|
||||
autoLoad = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -197,13 +202,16 @@ fun RssArticlesPage(
|
||||
onClick = onRead
|
||||
)
|
||||
}
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
LoadMoreFooter(
|
||||
isLoading = loadState.isRefreshing || loadState.isLoadingMore,
|
||||
errorMsg = loadState.errorMessage,
|
||||
isEnd = !loadState.hasMore,
|
||||
onRetry = { rssSource?.let(viewModel::loadMore) }
|
||||
)
|
||||
if (showLoadMoreFooter) {
|
||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||
LoadMoreFooter(
|
||||
isLoading = loadState.isLoadingMore,
|
||||
errorMsg = loadState.errorMessage,
|
||||
isEnd = !loadState.hasMore,
|
||||
onRetry = { rssSource?.let(viewModel::loadMore) },
|
||||
autoLoad = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,13 +242,16 @@ fun RssArticlesPage(
|
||||
onClick = onRead
|
||||
)
|
||||
}
|
||||
item(span = StaggeredGridItemSpan.FullLine) {
|
||||
LoadMoreFooter(
|
||||
isLoading = loadState.isRefreshing || loadState.isLoadingMore,
|
||||
errorMsg = loadState.errorMessage,
|
||||
isEnd = !loadState.hasMore,
|
||||
onRetry = { rssSource?.let(viewModel::loadMore) }
|
||||
)
|
||||
if (showLoadMoreFooter) {
|
||||
item(span = StaggeredGridItemSpan.FullLine) {
|
||||
LoadMoreFooter(
|
||||
isLoading = loadState.isLoadingMore,
|
||||
errorMsg = loadState.errorMessage,
|
||||
isEnd = !loadState.hasMore,
|
||||
onRetry = { rssSource?.let(viewModel::loadMore) },
|
||||
autoLoad = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,16 +44,23 @@ fun LoadMoreFooter(
|
||||
isEnd: Boolean,
|
||||
onRetry: () -> Unit,
|
||||
onLoadMore: (() -> Unit)? = null,
|
||||
autoLoad: Boolean = true,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var showFullError by remember { mutableStateOf<String?>(null) }
|
||||
|
||||
LaunchedEffect(isLoading, errorMsg, isEnd) {
|
||||
if (!isLoading && errorMsg == null && !isEnd) {
|
||||
onRetry()
|
||||
if (autoLoad) {
|
||||
LaunchedEffect(isLoading, errorMsg, isEnd) {
|
||||
if (!isLoading && errorMsg == null && !isEnd) {
|
||||
onRetry()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (autoLoad && !isLoading && errorMsg == null && !isEnd) {
|
||||
return
|
||||
}
|
||||
|
||||
AppAlertDialog(
|
||||
data = showFullError,
|
||||
onDismissRequest = { showFullError = null },
|
||||
@@ -270,39 +277,41 @@ fun LoadMoreFooter(
|
||||
}
|
||||
|
||||
else -> {
|
||||
GlassCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
containerColor = LegadoTheme.colorScheme.surfaceContainer,
|
||||
onClick = onRetry
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
if (!autoLoad) {
|
||||
GlassCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth(),
|
||||
containerColor = LegadoTheme.colorScheme.surfaceContainer,
|
||||
onClick = onRetry
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
all = 16.dp
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(
|
||||
all = 16.dp
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
||||
) {
|
||||
|
||||
AppIcon(
|
||||
imageVector = Icons.Outlined.Info,
|
||||
contentDescription = null,
|
||||
tint = LegadoTheme.colorScheme.onSurface
|
||||
)
|
||||
AppIcon(
|
||||
imageVector = Icons.Outlined.Info,
|
||||
contentDescription = null,
|
||||
tint = LegadoTheme.colorScheme.onSurface
|
||||
)
|
||||
|
||||
AppText(
|
||||
text = "加载更多",
|
||||
color = LegadoTheme.colorScheme.onSurface,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
modifier = Modifier.weight(1f),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
AppText(
|
||||
text = "加载更多",
|
||||
color = LegadoTheme.colorScheme.onSurface,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
modifier = Modifier.weight(1f),
|
||||
maxLines = 2,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user