HapeLee 5 месяцев назад
Родитель
Сommit
9742171bf2

+ 1 - 0
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

+ 4 - 2
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) }
                             )
                         }
                     }

+ 56 - 19
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()) {
+            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 {
-                    saveSearchBooksUseCase.save(result.books)
+                    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 currentList = _rawBooks.value
+                val existingUrls = currentList.map { it.bookUrl }.toSet()
 
-                    val uniqueNewBooks = result.books
-                        .filter { it.bookUrl !in existingUrls }
-                        .distinctBy { it.bookUrl }
+                val uniqueNewBooks = result.books
+                    .filter { it.bookUrl !in existingUrls }
+                    .distinctBy { it.bookUrl }
 
-                    if (uniqueNewBooks.isEmpty()) {
+                if (uniqueNewBooks.isEmpty()) {
+                    page++
+                    autoPageCount++
+                    if (autoPageCount >= MAX_AUTO_PAGES) {
                         _isEnd.value = true
+                        _isLoading.value = false
                     } else {
-                        _rawBooks.value = currentList + uniqueNewBooks
-                        page++
-                        _isEnd.value = false
+                        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
             }
-
+        }.onFailure {
+            _errorMsg.value = it.stackTraceStr
             _isLoading.value = false
         }
     }

+ 82 - 29
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<String?>(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,
+                                    )
+                                }
                             }
                         }
                     }

+ 1 - 1
app/src/main/res/values-zh-rCN/strings.xml

@@ -900,7 +900,7 @@
     <string name="show_hide">显示/隐藏</string>
     <string name="header_footer">页眉与页脚</string>
     <string name="rule_subscription">规则</string>
-    <string name="rule_sub_empty_msg">添加大佬们提供的规则导入地址\n添加后点击可导入规则</string>
+    <string name="rule_sub_empty_msg">还没有规则订阅!</string>
     <string name="get_book_progress">拉取云端进度</string>
     <string name="cover_book_progress">覆盖云端进度</string>
     <string name="current_progress_exceeds_cloud">当前进度超过云端进度,是否同步?</string>