[优化] 优化发现页相关逻辑
This commit is contained in:
@@ -1,8 +1,14 @@
|
|||||||
package io.legado.app.ui.book.explore
|
package io.legado.app.ui.book.explore
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
|
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.horizontalScroll
|
import androidx.compose.foundation.horizontalScroll
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@@ -19,9 +25,11 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.grid.GridCells
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
import androidx.compose.foundation.lazy.grid.items
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
@@ -36,6 +44,7 @@ import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
|
|||||||
import androidx.compose.material.icons.filled.Check
|
import androidx.compose.material.icons.filled.Check
|
||||||
import androidx.compose.material.icons.filled.FilterList
|
import androidx.compose.material.icons.filled.FilterList
|
||||||
import androidx.compose.material.icons.filled.GridView
|
import androidx.compose.material.icons.filled.GridView
|
||||||
|
import androidx.compose.material.icons.filled.Warning
|
||||||
import androidx.compose.material.icons.outlined.FilterAlt
|
import androidx.compose.material.icons.outlined.FilterAlt
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
@@ -48,12 +57,14 @@ import androidx.compose.material3.LoadingIndicator
|
|||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.MediumTopAppBar
|
import androidx.compose.material3.MediumTopAppBar
|
||||||
import androidx.compose.material3.ModalBottomSheet
|
import androidx.compose.material3.ModalBottomSheet
|
||||||
|
import androidx.compose.material3.OutlinedButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.material3.TopAppBarScrollBehavior
|
import androidx.compose.material3.TopAppBarScrollBehavior
|
||||||
|
import androidx.compose.material3.rememberModalBottomSheetState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
@@ -75,6 +86,7 @@ import androidx.compose.ui.unit.sp
|
|||||||
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.model.BookShelfState
|
import io.legado.app.model.BookShelfState
|
||||||
|
import io.legado.app.ui.widget.components.AnimatedTextButton
|
||||||
import io.legado.app.ui.widget.components.AnimatedTextLine
|
import io.legado.app.ui.widget.components.AnimatedTextLine
|
||||||
import io.legado.app.ui.widget.components.Cover
|
import io.legado.app.ui.widget.components.Cover
|
||||||
import io.legado.app.ui.widget.components.EmptyMessageView
|
import io.legado.app.ui.widget.components.EmptyMessageView
|
||||||
@@ -95,6 +107,8 @@ fun ExploreShowScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val books by viewModel.uiBooks.collectAsState()
|
val books by viewModel.uiBooks.collectAsState()
|
||||||
|
val isBookEnd by viewModel.isRawDataEmptyAndEnd.collectAsState()
|
||||||
|
val shouldTriggerAutoLoad by viewModel.shouldTriggerAutoLoad.collectAsState()
|
||||||
val kinds by viewModel.kinds.collectAsState()
|
val kinds by viewModel.kinds.collectAsState()
|
||||||
val isLoading by viewModel.isLoading.collectAsState()
|
val isLoading by viewModel.isLoading.collectAsState()
|
||||||
val errorMsg by viewModel.errorMsg.collectAsState()
|
val errorMsg by viewModel.errorMsg.collectAsState()
|
||||||
@@ -106,6 +120,7 @@ fun ExploreShowScreen(
|
|||||||
var showKindSheet by remember { mutableStateOf(false) }
|
var showKindSheet by remember { mutableStateOf(false) }
|
||||||
val layoutState by viewModel.layoutState.collectAsState()
|
val layoutState by viewModel.layoutState.collectAsState()
|
||||||
val isGridMode = layoutState == 1
|
val isGridMode = layoutState == 1
|
||||||
|
|
||||||
val shouldLoadMore = remember {
|
val shouldLoadMore = remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
val totalItems: Int
|
val totalItems: Int
|
||||||
@@ -117,7 +132,7 @@ fun ExploreShowScreen(
|
|||||||
totalItems = listState.layoutInfo.totalItemsCount
|
totalItems = listState.layoutInfo.totalItemsCount
|
||||||
lastVisibleIndex = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
lastVisibleIndex = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
||||||
}
|
}
|
||||||
totalItems > 0 && lastVisibleIndex >= totalItems - 3
|
totalItems > 0 && lastVisibleIndex >= totalItems - 6
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -127,10 +142,29 @@ fun ExploreShowScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (showKindSheet) {
|
LaunchedEffect(shouldTriggerAutoLoad) {
|
||||||
val scrollState = rememberScrollState() // 记住滚动状态
|
if (shouldTriggerAutoLoad) {
|
||||||
|
viewModel.loadMore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(isGridMode) {
|
||||||
|
if (isGridMode) {
|
||||||
|
if (listState.firstVisibleItemIndex > 0) {
|
||||||
|
gridState.scrollToItem(listState.firstVisibleItemIndex)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (gridState.firstVisibleItemIndex > 0) {
|
||||||
|
listState.scrollToItem(gridState.firstVisibleItemIndex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showKindSheet) {
|
||||||
|
val scrollState = rememberScrollState()
|
||||||
|
val sheetState = rememberModalBottomSheetState()
|
||||||
ModalBottomSheet(
|
ModalBottomSheet(
|
||||||
|
sheetState = sheetState,
|
||||||
onDismissRequest = { showKindSheet = false }
|
onDismissRequest = { showKindSheet = false }
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
@@ -142,13 +176,10 @@ fun ExploreShowScreen(
|
|||||||
Text(
|
Text(
|
||||||
"选择分类",
|
"选择分类",
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
modifier = Modifier
|
modifier = Modifier.padding(bottom = 8.dp)
|
||||||
.padding(bottom = 8.dp)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
FlowRow(
|
FlowRow(modifier = Modifier.fillMaxWidth()) {
|
||||||
modifier = Modifier.fillMaxWidth()
|
|
||||||
) {
|
|
||||||
kinds.forEach { kind ->
|
kinds.forEach { kind ->
|
||||||
KindListItem(
|
KindListItem(
|
||||||
kind = kind,
|
kind = kind,
|
||||||
@@ -184,7 +215,7 @@ fun ExploreShowScreen(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
if (!isLoading && books.isEmpty() && errorMsg == null) {
|
if (!isLoading && isBookEnd && errorMsg == null) {
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
EmptyMessageView(message = "暂无书籍")
|
EmptyMessageView(message = "暂无书籍")
|
||||||
}
|
}
|
||||||
@@ -199,12 +230,7 @@ fun ExploreShowScreen(
|
|||||||
state = gridState,
|
state = gridState,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
columns = GridCells.Adaptive(minSize = 90.dp),
|
columns = GridCells.Adaptive(minSize = 90.dp),
|
||||||
contentPadding = PaddingValues(
|
contentPadding = PaddingValues(12.dp),
|
||||||
start = 12.dp,
|
|
||||||
end = 12.dp,
|
|
||||||
top = 8.dp,
|
|
||||||
bottom = 12.dp
|
|
||||||
),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
@@ -221,7 +247,7 @@ fun ExploreShowScreen(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
item {
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
LoadMoreFooter(
|
LoadMoreFooter(
|
||||||
isLoading = isLoading,
|
isLoading = isLoading,
|
||||||
errorMsg = errorMsg,
|
errorMsg = errorMsg,
|
||||||
@@ -376,25 +402,44 @@ fun ExploreBookItem(
|
|||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
val badge: (@Composable RowScope.() -> Unit)?
|
||||||
|
= when (shelfState) {
|
||||||
|
|
||||||
|
BookShelfState.IN_SHELF -> {
|
||||||
|
{
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Check,
|
||||||
|
contentDescription = "已在书架",
|
||||||
|
modifier = Modifier.size(12.dp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BookShelfState.SAME_NAME_AUTHOR -> {
|
||||||
|
{
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Warning,
|
||||||
|
contentDescription = "同名书籍",
|
||||||
|
modifier = Modifier.size(12.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BookShelfState.NOT_IN_SHELF -> null
|
||||||
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.clickable(onClick = onClick)
|
.clickable(onClick = onClick)
|
||||||
.padding(horizontal = 16.dp, vertical = 8.dp)
|
.padding(horizontal = 16.dp, vertical = 8.dp)
|
||||||
) {
|
) {
|
||||||
Column {
|
|
||||||
|
|
||||||
Cover(path = book.coverUrl)
|
Cover(
|
||||||
|
path = book.coverUrl,
|
||||||
if (shelfState != BookShelfState.NOT_IN_SHELF) {
|
badgeContent = badge)
|
||||||
Spacer(modifier = Modifier.height(4.dp))
|
|
||||||
BookshelfStatusBadge(
|
|
||||||
shelfState = shelfState,
|
|
||||||
modifier = Modifier
|
|
||||||
.width(48.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(modifier = Modifier.width(8.dp))
|
Spacer(modifier = Modifier.width(8.dp))
|
||||||
|
|
||||||
@@ -514,35 +559,6 @@ fun ExploreBookGridItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun BookshelfStatusBadge(
|
|
||||||
shelfState: BookShelfState,
|
|
||||||
modifier: Modifier = Modifier
|
|
||||||
) {
|
|
||||||
val text = when (shelfState) {
|
|
||||||
BookShelfState.IN_SHELF -> "已在书架"
|
|
||||||
BookShelfState.SAME_NAME_AUTHOR -> "同名书籍"
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (text != null) {
|
|
||||||
Surface(
|
|
||||||
modifier = modifier,
|
|
||||||
shape = RoundedCornerShape(4.dp),
|
|
||||||
color = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.5f),
|
|
||||||
contentColor = MaterialTheme.colorScheme.onTertiaryContainer,
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = text,
|
|
||||||
style = MaterialTheme.typography.labelSmall.copy(fontWeight = FontWeight.Bold, fontSize = 9.sp),
|
|
||||||
textAlign = TextAlign.Center,
|
|
||||||
modifier = Modifier
|
|
||||||
.padding(horizontal = 4.dp, vertical = 2.dp)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 简单的标签组件
|
// 简单的标签组件
|
||||||
@Composable
|
@Composable
|
||||||
fun TagChip(text: String) {
|
fun TagChip(text: String) {
|
||||||
@@ -572,23 +588,35 @@ fun LoadMoreFooter(
|
|||||||
.padding(32.dp),
|
.padding(32.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
when {
|
|
||||||
isLoading -> LoadingIndicator()
|
|
||||||
|
|
||||||
errorMsg != null -> {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
|
||||||
Text("加载失败: $errorMsg", color = Color.Red)
|
|
||||||
TextButton(onClick = onRetry) { Text("重试") }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
AnimatedContent(
|
||||||
|
targetState = when {
|
||||||
|
isLoading -> "加载中…"
|
||||||
|
errorMsg != null -> "加载失败: $errorMsg"
|
||||||
|
else -> "已经到底了~"
|
||||||
|
},
|
||||||
|
label = "FooterTextChange"
|
||||||
|
) { text ->
|
||||||
Text(
|
Text(
|
||||||
text = "已经到底了~",
|
text = text,
|
||||||
color = Color.Gray,
|
color = when {
|
||||||
|
errorMsg != null -> Color.Red
|
||||||
|
else -> Color.Gray
|
||||||
|
},
|
||||||
style = MaterialTheme.typography.bodySmall
|
style = MaterialTheme.typography.bodySmall
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(8.dp))
|
||||||
|
|
||||||
|
AnimatedTextButton(
|
||||||
|
isLoading = isLoading,
|
||||||
|
onClick = onRetry,
|
||||||
|
text = if (errorMsg != null) "重试" else "再试一次",
|
||||||
|
modifier = Modifier.padding(top = 4.dp)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.legado.app.ui.book.explore
|
package io.legado.app.ui.book.explore
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import androidx.compose.runtime.snapshotFlow
|
||||||
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.BookSource
|
||||||
@@ -52,7 +53,7 @@ class ExploreShowViewModel(
|
|||||||
private var bookSource: BookSource? = null
|
private var bookSource: BookSource? = null
|
||||||
private var exploreUrl: String? = null
|
private var exploreUrl: String? = null
|
||||||
private var page = 1
|
private var page = 1
|
||||||
private var isEnd = false
|
private val _isEndStateFlow = MutableStateFlow(false)
|
||||||
private val _bookshelf = MutableStateFlow<Set<BookKey>>(emptySet())
|
private val _bookshelf = MutableStateFlow<Set<BookKey>>(emptySet())
|
||||||
private val _kinds = MutableStateFlow<List<ExploreKind>>(emptyList())
|
private val _kinds = MutableStateFlow<List<ExploreKind>>(emptyList())
|
||||||
val kinds = _kinds.asStateFlow()
|
val kinds = _kinds.asStateFlow()
|
||||||
@@ -71,6 +72,21 @@ class ExploreShowViewModel(
|
|||||||
}
|
}
|
||||||
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), emptyList())
|
||||||
|
|
||||||
|
val isRawDataEmptyAndEnd = combine(
|
||||||
|
_rawBooks,
|
||||||
|
_isEndStateFlow
|
||||||
|
) { rawBooks, isEndValue ->
|
||||||
|
rawBooks.isEmpty() && isEndValue
|
||||||
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
||||||
|
|
||||||
|
val shouldTriggerAutoLoad = combine(
|
||||||
|
_isLoading,
|
||||||
|
uiBooks,
|
||||||
|
_rawBooks,
|
||||||
|
_isEndStateFlow
|
||||||
|
) { loading, uiBooks, rawBooks, isEnd ->
|
||||||
|
!loading && uiBooks.isEmpty() && rawBooks.isNotEmpty() && !isEnd
|
||||||
|
}.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000), false)
|
||||||
|
|
||||||
val isLoading = _isLoading.asStateFlow()
|
val isLoading = _isLoading.asStateFlow()
|
||||||
val errorMsg = _errorMsg.asStateFlow()
|
val errorMsg = _errorMsg.asStateFlow()
|
||||||
@@ -107,6 +123,7 @@ class ExploreShowViewModel(
|
|||||||
fun switchExploreUrl(kind: ExploreKind) {
|
fun switchExploreUrl(kind: ExploreKind) {
|
||||||
_selectedKindTitle.value = kind.title
|
_selectedKindTitle.value = kind.title
|
||||||
exploreUrl = kind.url
|
exploreUrl = kind.url
|
||||||
|
_isEndStateFlow.value = false
|
||||||
loadMore(isRefresh = true)
|
loadMore(isRefresh = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +139,7 @@ class ExploreShowViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadMore(isRefresh: Boolean = false) {
|
fun loadMore(isRefresh: Boolean = false) {
|
||||||
if (_isLoading.value || (isEnd && !isRefresh)) return
|
if (_isLoading.value || (_isEndStateFlow.value && !isRefresh)) return
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_isLoading.value = true
|
_isLoading.value = true
|
||||||
@@ -139,20 +156,26 @@ class ExploreShowViewModel(
|
|||||||
|
|
||||||
if (isRefresh) {
|
if (isRefresh) {
|
||||||
page = 1
|
page = 1
|
||||||
isEnd = false
|
_isEndStateFlow.value = false
|
||||||
_rawBooks.value = emptyList()
|
_rawBooks.value = emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
repository.exploreBook(source, url, page)
|
repository.exploreBook(source, url, page)
|
||||||
.onSuccess { newBooks ->
|
.onSuccess { newBooks ->
|
||||||
if (newBooks.isEmpty()) {
|
if (newBooks.isEmpty()) {
|
||||||
isEnd = true
|
_isEndStateFlow.value = true
|
||||||
} else {
|
} else {
|
||||||
repository.saveSearchBooks(newBooks)
|
repository.saveSearchBooks(newBooks)
|
||||||
val currentList = if (page == 1) emptyList() else _rawBooks.value
|
val currentList = if (page == 1) emptyList() else _rawBooks.value
|
||||||
val combined = (currentList + newBooks).distinctBy { it.bookUrl }
|
if (page == 1) {
|
||||||
_rawBooks.value = combined
|
_rawBooks.value = newBooks.distinctBy { it.bookUrl }
|
||||||
|
} else {
|
||||||
|
val existingUrls = currentList.map { it.bookUrl }.toSet()
|
||||||
|
val uniqueNewBooks = newBooks.filter { it.bookUrl !in existingUrls }
|
||||||
|
_rawBooks.value = currentList + uniqueNewBooks
|
||||||
|
}
|
||||||
page++
|
page++
|
||||||
|
_isEndStateFlow.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package io.legado.app.ui.widget.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.animateContentSize
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.togetherWith
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi
|
||||||
|
import androidx.compose.material3.LoadingIndicator
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||||
|
@Composable
|
||||||
|
fun AnimatedTextButton(
|
||||||
|
isLoading: Boolean,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
text: String,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = onClick,
|
||||||
|
enabled = !isLoading,
|
||||||
|
modifier = modifier.animateContentSize()
|
||||||
|
) {
|
||||||
|
AnimatedContent(
|
||||||
|
targetState = isLoading,
|
||||||
|
contentAlignment = Alignment.Center,
|
||||||
|
transitionSpec = {
|
||||||
|
fadeIn(tween(200)) togetherWith fadeOut(tween(200))
|
||||||
|
},
|
||||||
|
label = "ButtonLoading"
|
||||||
|
) { loading ->
|
||||||
|
if (loading) {
|
||||||
|
LoadingIndicator()
|
||||||
|
} else {
|
||||||
|
Text(text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -81,14 +81,14 @@ fun Cover(
|
|||||||
Surface(
|
Surface(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.TopEnd)
|
.align(Alignment.TopEnd)
|
||||||
.padding(4.dp),
|
.padding(2.dp),
|
||||||
shape = RoundedCornerShape(4.dp),
|
shape = RoundedCornerShape(4.dp),
|
||||||
color = MaterialTheme.colorScheme.secondaryContainer,
|
color = MaterialTheme.colorScheme.secondaryContainer,
|
||||||
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
tonalElevation = 2.dp
|
tonalElevation = 2.dp
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp),
|
modifier = Modifier.padding(2.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.Center,
|
horizontalArrangement = Arrangement.Center,
|
||||||
content = badgeContent
|
content = badgeContent
|
||||||
|
|||||||
Reference in New Issue
Block a user