优化发现刷新逻辑

This commit is contained in:
HapeLee
2026-05-28 22:26:32 +08:00
parent 2896b47dc6
commit 9742171bf2
5 changed files with 152 additions and 59 deletions
@@ -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
@@ -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) }
)
}
}
@@ -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
}
}
@@ -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,
)
}
}
}
}