优化发现刷新逻辑
This commit is contained in:
@@ -41,6 +41,7 @@ sealed interface ExploreShowIntent {
|
|||||||
) : ExploreShowIntent
|
) : ExploreShowIntent
|
||||||
|
|
||||||
data object LoadMore : ExploreShowIntent
|
data object LoadMore : ExploreShowIntent
|
||||||
|
data object ForceLoadNext : ExploreShowIntent
|
||||||
data object Refresh : ExploreShowIntent
|
data object Refresh : ExploreShowIntent
|
||||||
data class SwitchKind(val kind: ExploreKind) : ExploreShowIntent
|
data class SwitchKind(val kind: ExploreKind) : ExploreShowIntent
|
||||||
data object ToggleLayout : ExploreShowIntent
|
data object ToggleLayout : ExploreShowIntent
|
||||||
|
|||||||
@@ -330,7 +330,8 @@ fun ExploreShowScreen(
|
|||||||
isLoading = state.isLoading && !state.isRefreshing,
|
isLoading = state.isLoading && !state.isRefreshing,
|
||||||
errorMsg = state.errorMsg,
|
errorMsg = state.errorMsg,
|
||||||
isEnd = state.isEnd,
|
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,
|
isLoading = state.isLoading && !state.isRefreshing,
|
||||||
errorMsg = state.errorMsg,
|
errorMsg = state.errorMsg,
|
||||||
isEnd = state.isEnd,
|
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.asStateFlow
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
|
|
||||||
@@ -44,6 +45,12 @@ class ExploreShowViewModel(
|
|||||||
private var sourceUrl: String? = null
|
private var sourceUrl: String? = null
|
||||||
private var exploreUrl: String? = null
|
private var exploreUrl: String? = null
|
||||||
private var page = 1
|
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(
|
private val _uiState = MutableStateFlow(
|
||||||
ExploreShowUiState(
|
ExploreShowUiState(
|
||||||
@@ -65,6 +72,7 @@ class ExploreShowViewModel(
|
|||||||
when (intent) {
|
when (intent) {
|
||||||
is ExploreShowIntent.InitData -> initData(intent.sourceUrl, intent.exploreUrl)
|
is ExploreShowIntent.InitData -> initData(intent.sourceUrl, intent.exploreUrl)
|
||||||
ExploreShowIntent.LoadMore -> loadMore()
|
ExploreShowIntent.LoadMore -> loadMore()
|
||||||
|
ExploreShowIntent.ForceLoadNext -> loadMore(forceLoad = true)
|
||||||
ExploreShowIntent.Refresh -> loadMore(isRefresh = true)
|
ExploreShowIntent.Refresh -> loadMore(isRefresh = true)
|
||||||
is ExploreShowIntent.SwitchKind -> switchKind(intent.kind)
|
is ExploreShowIntent.SwitchKind -> switchKind(intent.kind)
|
||||||
ExploreShowIntent.ToggleLayout -> toggleLayout()
|
ExploreShowIntent.ToggleLayout -> toggleLayout()
|
||||||
@@ -161,6 +169,7 @@ class ExploreShowViewModel(
|
|||||||
sourceUrl = incomingSourceUrl
|
sourceUrl = incomingSourceUrl
|
||||||
exploreUrl = incomingExploreUrl
|
exploreUrl = incomingExploreUrl
|
||||||
page = 1
|
page = 1
|
||||||
|
autoPageCount = 0
|
||||||
bookSource = null
|
bookSource = null
|
||||||
_rawBooks.value = emptyList()
|
_rawBooks.value = emptyList()
|
||||||
_isEnd.value = false
|
_isEnd.value = false
|
||||||
@@ -191,6 +200,7 @@ class ExploreShowViewModel(
|
|||||||
_selectedKindTitle.value = kind.title
|
_selectedKindTitle.value = kind.title
|
||||||
exploreUrl = kind.url
|
exploreUrl = kind.url
|
||||||
_isEnd.value = false
|
_isEnd.value = false
|
||||||
|
autoPageCount = 0
|
||||||
loadMore(isRefresh = true)
|
loadMore(isRefresh = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -207,10 +217,10 @@ class ExploreShowViewModel(
|
|||||||
_uiState.update { it.copy(gridCount = count) }
|
_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 source = bookSource
|
||||||
val url = exploreUrl ?: source?.exploreUrl
|
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 {
|
viewModelScope.launch {
|
||||||
_isLoading.value = true
|
_isLoading.value = true
|
||||||
@@ -219,14 +229,33 @@ class ExploreShowViewModel(
|
|||||||
if (isRefresh) {
|
if (isRefresh) {
|
||||||
page = 1
|
page = 1
|
||||||
_isEnd.value = false
|
_isEnd.value = false
|
||||||
|
autoPageCount = 0
|
||||||
_rawBooks.value = emptyList()
|
_rawBooks.value = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (forceLoad) {
|
||||||
|
autoPageCount = 0
|
||||||
|
_isEnd.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchPage(source, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchPage(source: BookSource, url: String) {
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
exploreBooksUseCase.execute(source.bookSourceUrl, url, args = null, page)
|
exploreBooksUseCase.execute(source.bookSourceUrl, url, args = null, page)
|
||||||
}.onSuccess { result ->
|
}.onSuccess { result ->
|
||||||
if (result.books.isEmpty()) {
|
if (result.books.isEmpty()) {
|
||||||
|
page++
|
||||||
|
autoPageCount++
|
||||||
|
if (autoPageCount >= MAX_AUTO_PAGES) {
|
||||||
_isEnd.value = true
|
_isEnd.value = true
|
||||||
|
_isLoading.value = false
|
||||||
|
} else {
|
||||||
|
delay(AUTO_PAGE_DELAY_MS)
|
||||||
|
fetchPage(source, url)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
saveSearchBooksUseCase.save(result.books)
|
saveSearchBooksUseCase.save(result.books)
|
||||||
|
|
||||||
@@ -238,17 +267,25 @@ class ExploreShowViewModel(
|
|||||||
.distinctBy { it.bookUrl }
|
.distinctBy { it.bookUrl }
|
||||||
|
|
||||||
if (uniqueNewBooks.isEmpty()) {
|
if (uniqueNewBooks.isEmpty()) {
|
||||||
|
page++
|
||||||
|
autoPageCount++
|
||||||
|
if (autoPageCount >= MAX_AUTO_PAGES) {
|
||||||
_isEnd.value = true
|
_isEnd.value = true
|
||||||
|
_isLoading.value = false
|
||||||
|
} else {
|
||||||
|
delay(AUTO_PAGE_DELAY_MS)
|
||||||
|
fetchPage(source, url)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
_rawBooks.value = currentList + uniqueNewBooks
|
_rawBooks.value = currentList + uniqueNewBooks
|
||||||
page++
|
page++
|
||||||
|
autoPageCount = 0
|
||||||
_isEnd.value = false
|
_isEnd.value = false
|
||||||
|
_isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
_errorMsg.value = it.stackTraceStr
|
_errorMsg.value = it.stackTraceStr
|
||||||
}
|
|
||||||
|
|
||||||
_isLoading.value = false
|
_isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,7 +42,8 @@ fun LoadMoreFooter(
|
|||||||
isLoading: Boolean,
|
isLoading: Boolean,
|
||||||
errorMsg: String?,
|
errorMsg: String?,
|
||||||
isEnd: Boolean,
|
isEnd: Boolean,
|
||||||
onRetry: () -> Unit
|
onRetry: () -> Unit,
|
||||||
|
onLoadMore: (() -> Unit)? = null,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
var showFullError by remember { mutableStateOf<String?>(null) }
|
var showFullError by remember { mutableStateOf<String?>(null) }
|
||||||
@@ -179,30 +180,24 @@ fun LoadMoreFooter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
end -> {
|
end -> {
|
||||||
|
if (onLoadMore != null) {
|
||||||
GlassCard(
|
GlassCard(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth(),
|
|
||||||
containerColor = LegadoTheme.colorScheme.surfaceContainer,
|
containerColor = LegadoTheme.colorScheme.surfaceContainer,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(modifier = Modifier.fillMaxWidth()) {
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(
|
.padding(all = 16.dp),
|
||||||
all = 16.dp
|
|
||||||
),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(12.dp)
|
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,
|
||||||
@@ -212,6 +207,64 @@ fun LoadMoreFooter(
|
|||||||
overflow = TextOverflow.Ellipsis,
|
overflow = TextOverflow.Ellipsis,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
HorizontalDivider(
|
||||||
|
color = LegadoTheme.colorScheme.outlineVariant.copy(alpha = 0.3f)
|
||||||
|
)
|
||||||
|
|
||||||
|
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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -900,7 +900,7 @@
|
|||||||
<string name="show_hide">显示/隐藏</string>
|
<string name="show_hide">显示/隐藏</string>
|
||||||
<string name="header_footer">页眉与页脚</string>
|
<string name="header_footer">页眉与页脚</string>
|
||||||
<string name="rule_subscription">规则</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="get_book_progress">拉取云端进度</string>
|
||||||
<string name="cover_book_progress">覆盖云端进度</string>
|
<string name="cover_book_progress">覆盖云端进度</string>
|
||||||
<string name="current_progress_exceeds_cloud">当前进度超过云端进度,是否同步?</string>
|
<string name="current_progress_exceeds_cloud">当前进度超过云端进度,是否同步?</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user