diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowContract.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowContract.kt index 6791a83b8..0bcc0e6b1 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowContract.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowContract.kt @@ -41,6 +41,7 @@ sealed interface ExploreShowIntent { ) : ExploreShowIntent data object LoadMore : ExploreShowIntent + data object ForceLoadNext : ExploreShowIntent data object Refresh : ExploreShowIntent data class SwitchKind(val kind: ExploreKind) : ExploreShowIntent data object ToggleLayout : ExploreShowIntent diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt index 7d94035d6..b827e9990 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt @@ -330,7 +330,8 @@ fun ExploreShowScreen( isLoading = state.isLoading && !state.isRefreshing, errorMsg = state.errorMsg, isEnd = state.isEnd, - onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) } + onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) }, + onLoadMore = { viewModel.onIntent(ExploreShowIntent.ForceLoadNext) } ) } } @@ -380,7 +381,8 @@ fun ExploreShowScreen( isLoading = state.isLoading && !state.isRefreshing, errorMsg = state.errorMsg, isEnd = state.isEnd, - onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) } + onRetry = { viewModel.onIntent(ExploreShowIntent.LoadMore) }, + onLoadMore = { viewModel.onIntent(ExploreShowIntent.ForceLoadNext) } ) } } diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt index 44b0d9b61..0d85b22b6 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt @@ -21,6 +21,7 @@ import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.update +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import splitties.init.appCtx @@ -44,6 +45,12 @@ class ExploreShowViewModel( private var sourceUrl: String? = null private var exploreUrl: String? = null private var page = 1 + private var autoPageCount = 0 + + companion object { + private const val MAX_AUTO_PAGES = 3 + private const val AUTO_PAGE_DELAY_MS = 500L + } private val _uiState = MutableStateFlow( ExploreShowUiState( @@ -65,6 +72,7 @@ class ExploreShowViewModel( when (intent) { is ExploreShowIntent.InitData -> initData(intent.sourceUrl, intent.exploreUrl) ExploreShowIntent.LoadMore -> loadMore() + ExploreShowIntent.ForceLoadNext -> loadMore(forceLoad = true) ExploreShowIntent.Refresh -> loadMore(isRefresh = true) is ExploreShowIntent.SwitchKind -> switchKind(intent.kind) ExploreShowIntent.ToggleLayout -> toggleLayout() @@ -161,6 +169,7 @@ class ExploreShowViewModel( sourceUrl = incomingSourceUrl exploreUrl = incomingExploreUrl page = 1 + autoPageCount = 0 bookSource = null _rawBooks.value = emptyList() _isEnd.value = false @@ -191,6 +200,7 @@ class ExploreShowViewModel( _selectedKindTitle.value = kind.title exploreUrl = kind.url _isEnd.value = false + autoPageCount = 0 loadMore(isRefresh = true) } @@ -207,10 +217,10 @@ class ExploreShowViewModel( _uiState.update { it.copy(gridCount = count) } } - private fun loadMore(isRefresh: Boolean = false) { + 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)) return + if (source == null || url == null || _isLoading.value || (_isEnd.value && !isRefresh && !forceLoad)) return viewModelScope.launch { _isLoading.value = true @@ -219,36 +229,63 @@ class ExploreShowViewModel( if (isRefresh) { page = 1 _isEnd.value = false + autoPageCount = 0 _rawBooks.value = emptyList() } - kotlin.runCatching { - exploreBooksUseCase.execute(source.bookSourceUrl, url, args = null, page) - }.onSuccess { result -> - if (result.books.isEmpty()) { - _isEnd.value = true - } else { - 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()) { - _isEnd.value = true - } else { - _rawBooks.value = currentList + uniqueNewBooks - page++ - _isEnd.value = false - } - } - }.onFailure { - _errorMsg.value = it.stackTraceStr + if (forceLoad) { + autoPageCount = 0 + _isEnd.value = false } + fetchPage(source, url) + } + } + + private suspend fun fetchPage(source: BookSource, url: String) { + kotlin.runCatching { + exploreBooksUseCase.execute(source.bookSourceUrl, 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 { + 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 } } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/LoadMoreFooter.kt b/app/src/main/java/io/legado/app/ui/widget/components/LoadMoreFooter.kt index eefb363e0..3de08feca 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/LoadMoreFooter.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/LoadMoreFooter.kt @@ -42,7 +42,8 @@ fun LoadMoreFooter( isLoading: Boolean, errorMsg: String?, isEnd: Boolean, - onRetry: () -> Unit + onRetry: () -> Unit, + onLoadMore: (() -> Unit)? = null, ) { val context = LocalContext.current var showFullError by remember { mutableStateOf(null) } @@ -179,38 +180,90 @@ fun LoadMoreFooter( } end -> { - GlassCard( - modifier = Modifier - .fillMaxWidth(), - containerColor = LegadoTheme.colorScheme.surfaceContainer, - ) { - Column( - modifier = Modifier.fillMaxWidth() + if (onLoadMore != null) { + GlassCard( + modifier = Modifier.fillMaxWidth(), + containerColor = LegadoTheme.colorScheme.surfaceContainer, ) { - 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 + ) + AppText( + text = "已经到底了~", + color = LegadoTheme.colorScheme.onSurface, + style = LegadoTheme.typography.bodySmall, + modifier = Modifier.weight(1f), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } - AppIcon( - imageVector = Icons.Outlined.Info, - contentDescription = null, - tint = LegadoTheme.colorScheme.onSurface + HorizontalDivider( + color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.3f) ) - AppText( - text = "已经到底了~", - color = LegadoTheme.colorScheme.onSurface, - style = LegadoTheme.typography.bodySmall, - modifier = Modifier.weight(1f), - maxLines = 2, - overflow = TextOverflow.Ellipsis, - ) + Box( + modifier = Modifier + .fillMaxWidth() + .clickable(onClick = onLoadMore) + .padding(vertical = 10.dp), + contentAlignment = Alignment.Center + ) { + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp), + verticalAlignment = Alignment.CenterVertically + ) { + AppIcon( + imageVector = Icons.Default.Refresh, + contentDescription = null, + tint = LegadoTheme.colorScheme.primary + ) + AppText( + text = "尝试加载下一页", + color = LegadoTheme.colorScheme.primary, + style = LegadoTheme.typography.labelMedium + ) + } + } + } + } + } else { + GlassCard( + modifier = Modifier.fillMaxWidth(), + containerColor = LegadoTheme.colorScheme.surfaceContainer, + ) { + 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 + ) + AppText( + text = "已经到底了~", + color = LegadoTheme.colorScheme.onSurface, + style = LegadoTheme.typography.bodySmall, + modifier = Modifier.weight(1f), + maxLines = 2, + overflow = TextOverflow.Ellipsis, + ) + } } } } diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 5cab84642..9da628f3f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -900,7 +900,7 @@ 显示/隐藏 页眉与页脚 规则 - 添加大佬们提供的规则导入地址\n添加后点击可导入规则 + 还没有规则订阅! 拉取云端进度 覆盖云端进度 当前进度超过云端进度,是否同步?