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