[优化] 正确处理发现页的一些情况
This commit is contained in:
6 files changed
+206
-104
No files matched your search
@@ -204,6 +204,10 @@ object PreferKey {
|
|||||||
const val sharedElementEnterTransitionEnable = "sharedElementEnterTransitionEnable"
|
const val sharedElementEnterTransitionEnable = "sharedElementEnterTransitionEnable"
|
||||||
const val bookshelfLayoutGridLandscape = "bookshelfLayoutGridLandscape"
|
const val bookshelfLayoutGridLandscape = "bookshelfLayoutGridLandscape"
|
||||||
const val bookshelfLayoutGridPortrait = "bookshelfLayoutGridPortrait"
|
const val bookshelfLayoutGridPortrait = "bookshelfLayoutGridPortrait"
|
||||||
|
|
||||||
|
const val exploreLayoutGridLandscape = "exploreLayoutGridLandscape"
|
||||||
|
|
||||||
|
const val exploreLayoutGridPortrait = "exploreLayoutGridPortrait"
|
||||||
const val tabletInterface = "tabletInterface"
|
const val tabletInterface = "tabletInterface"
|
||||||
const val pureBlack = "pure_black"
|
const val pureBlack = "pure_black"
|
||||||
const val labelVisibilityMode = "labelVisibilityMode"
|
const val labelVisibilityMode = "labelVisibilityMode"
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ class ExploreRepositoryImpl(
|
|||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun exploreBook(source: BookSource, url: String, page: Int): Result<List<SearchBook>> {
|
override suspend fun exploreBook(source: BookSource, url: String, page: Int): Result<List<SearchBook>> {
|
||||||
return withContext(Dispatchers.IO) {
|
return withContext(IO) {
|
||||||
try {
|
try {
|
||||||
val books = WebBook.exploreBookSuspend(source, url, page)
|
val books = WebBook.exploreBookSuspend(source, url, page)
|
||||||
Result.success(books)
|
Result.success(books)
|
||||||
|
|||||||
@@ -237,6 +237,18 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
|||||||
appCtx.putPrefInt(PreferKey.bookshelfLayoutGridPortrait, value)
|
appCtx.putPrefInt(PreferKey.bookshelfLayoutGridPortrait, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var exploreLayoutGridLandscape: Int
|
||||||
|
get() = appCtx.getPrefInt(PreferKey.exploreLayoutGridLandscape, 7)
|
||||||
|
set(value) {
|
||||||
|
appCtx.putPrefInt(PreferKey.exploreLayoutGridLandscape, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
var exploreLayoutGridPortrait: Int
|
||||||
|
get() = appCtx.getPrefInt(PreferKey.exploreLayoutGridPortrait, 3)
|
||||||
|
set(value) {
|
||||||
|
appCtx.putPrefInt(PreferKey.exploreLayoutGridPortrait, value)
|
||||||
|
}
|
||||||
|
|
||||||
var bookshelfLayoutGridLandscape: Int
|
var bookshelfLayoutGridLandscape: Int
|
||||||
get() = appCtx.getPrefInt(PreferKey.bookshelfLayoutGridLandscape, 7)
|
get() = appCtx.getPrefInt(PreferKey.bookshelfLayoutGridLandscape, 7)
|
||||||
set(value) {
|
set(value) {
|
||||||
|
|||||||
@@ -3,8 +3,12 @@ package io.legado.app.ui.book.explore
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
|
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.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
|
||||||
@@ -40,6 +44,7 @@ import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
|||||||
import androidx.compose.material.icons.automirrored.outlined.FormatListBulleted
|
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.Grid4x4
|
||||||
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.filled.Warning
|
||||||
import androidx.compose.material.icons.outlined.FilterAlt
|
import androidx.compose.material.icons.outlined.FilterAlt
|
||||||
@@ -53,8 +58,10 @@ import androidx.compose.material3.IconButton
|
|||||||
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.SheetValue
|
import androidx.compose.material3.SheetValue
|
||||||
|
import androidx.compose.material3.Slider
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
@@ -74,7 +81,6 @@ import androidx.compose.ui.draw.clip
|
|||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
@@ -87,7 +93,7 @@ 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
|
||||||
import io.legado.app.utils.bookshelfLayoutGrid
|
import kotlinx.coroutines.delay
|
||||||
import org.koin.androidx.compose.koinViewModel
|
import org.koin.androidx.compose.koinViewModel
|
||||||
|
|
||||||
@SuppressLint("LocalContextConfigurationRead", "ConfigurationScreenWidthHeight")
|
@SuppressLint("LocalContextConfigurationRead", "ConfigurationScreenWidthHeight")
|
||||||
@@ -106,7 +112,7 @@ fun ExploreShowScreen(
|
|||||||
}
|
}
|
||||||
|
|
||||||
val books by viewModel.uiBooks.collectAsState()
|
val books by viewModel.uiBooks.collectAsState()
|
||||||
val isBookEnd by viewModel.isRawDataEmptyAndEnd.collectAsState()
|
val isBookEnd by viewModel.isEnd.collectAsState()
|
||||||
val shouldTriggerAutoLoad by viewModel.shouldTriggerAutoLoad.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()
|
||||||
@@ -119,16 +125,14 @@ 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 context = LocalContext.current
|
var showGridCountSheet by remember { mutableStateOf(false) }
|
||||||
val gridColumnCount = remember(context.resources.configuration.orientation) {
|
val gridColumnCount by viewModel.gridCount.collectAsState()
|
||||||
context.bookshelfLayoutGrid
|
|
||||||
}
|
|
||||||
|
|
||||||
val shouldLoadMoreList = remember {
|
val shouldLoadMoreList = remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
val total = listState.layoutInfo.totalItemsCount
|
val total = listState.layoutInfo.totalItemsCount
|
||||||
val last = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
val last = listState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
||||||
total > 0 && last >= total - 6
|
total > 0 && last >= total - 3
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,7 +140,7 @@ fun ExploreShowScreen(
|
|||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
val total = gridState.layoutInfo.totalItemsCount
|
val total = gridState.layoutInfo.totalItemsCount
|
||||||
val last = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
val last = gridState.layoutInfo.visibleItemsInfo.lastOrNull()?.index ?: 0
|
||||||
total > 0 && last >= total - 6
|
total > 0 && last >= total - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -166,6 +170,50 @@ fun ExploreShowScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (showGridCountSheet) {
|
||||||
|
val sheetState = rememberModalBottomSheetState(
|
||||||
|
skipPartiallyExpanded = true
|
||||||
|
)
|
||||||
|
|
||||||
|
ModalBottomSheet(
|
||||||
|
sheetState = sheetState,
|
||||||
|
onDismissRequest = { showGridCountSheet = false }
|
||||||
|
) {
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(24.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = "当前:$gridColumnCount 列",
|
||||||
|
style = MaterialTheme.typography.titleLarge,
|
||||||
|
modifier = Modifier.padding(bottom = 12.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Slider(
|
||||||
|
value = gridColumnCount.toFloat(),
|
||||||
|
onValueChange = {
|
||||||
|
val col = it.toInt().coerceIn(1, 10)
|
||||||
|
viewModel.saveGridCount(col)
|
||||||
|
},
|
||||||
|
valueRange = 1f..10f,
|
||||||
|
steps = 8,
|
||||||
|
modifier = Modifier.padding(horizontal = 8.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(Modifier.height(20.dp))
|
||||||
|
|
||||||
|
OutlinedButton (
|
||||||
|
onClick = { showGridCountSheet = false },
|
||||||
|
modifier = Modifier.fillMaxWidth()
|
||||||
|
) {
|
||||||
|
Text("完成")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (showKindSheet) {
|
if (showKindSheet) {
|
||||||
val scrollState = rememberScrollState()
|
val scrollState = rememberScrollState()
|
||||||
val sheetState = rememberModalBottomSheetState(
|
val sheetState = rememberModalBottomSheetState(
|
||||||
@@ -218,6 +266,7 @@ fun ExploreShowScreen(
|
|||||||
onSelectKindClick = { showKindSheet = true },
|
onSelectKindClick = { showKindSheet = true },
|
||||||
onToggleGridMode = { viewModel.setLayout() },
|
onToggleGridMode = { viewModel.setLayout() },
|
||||||
isGridMode = isGridMode,
|
isGridMode = isGridMode,
|
||||||
|
onGridCountClick = { showGridCountSheet = true },
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -227,74 +276,72 @@ fun ExploreShowScreen(
|
|||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.padding(paddingValues)
|
.padding(paddingValues)
|
||||||
) {
|
) {
|
||||||
if (!isLoading && isBookEnd && errorMsg == null) {
|
|
||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
|
||||||
EmptyMessageView(message = "暂无书籍")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Crossfade(
|
|
||||||
targetState = isGridMode,
|
|
||||||
animationSpec = tween(250),
|
|
||||||
label = "LayoutCrossfade"
|
|
||||||
) { isGrid ->
|
|
||||||
if (isGrid) {
|
|
||||||
LazyVerticalGrid(
|
|
||||||
state = gridState,
|
|
||||||
modifier = Modifier.fillMaxSize(),
|
|
||||||
columns = GridCells.Fixed(gridColumnCount),
|
|
||||||
contentPadding = PaddingValues(12.dp),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
items = books,
|
|
||||||
key = { it.bookUrl }
|
|
||||||
) { book ->
|
|
||||||
val shelfState = viewModel.getCurrentBookShelfState(book)
|
|
||||||
ExploreBookGridItem(
|
|
||||||
book = book,
|
|
||||||
shelfState = shelfState,
|
|
||||||
onClick = { onBookClick(book) },
|
|
||||||
modifier = Modifier.animateItem()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
Crossfade(
|
||||||
LoadMoreFooter(
|
targetState = isGridMode,
|
||||||
isLoading = isLoading,
|
animationSpec = tween(250),
|
||||||
errorMsg = errorMsg,
|
label = "LayoutCrossfade"
|
||||||
onRetry = viewModel::loadMore
|
) { isGrid ->
|
||||||
)
|
if (isGrid) {
|
||||||
}
|
LazyVerticalGrid(
|
||||||
|
state = gridState,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
columns = GridCells.Fixed(gridColumnCount),
|
||||||
|
contentPadding = PaddingValues(12.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = books,
|
||||||
|
key = { it.bookUrl }
|
||||||
|
) { book ->
|
||||||
|
val shelfState = viewModel.getCurrentBookShelfState(book)
|
||||||
|
ExploreBookGridItem(
|
||||||
|
book = book,
|
||||||
|
shelfState = shelfState,
|
||||||
|
onClick = { onBookClick(book) },
|
||||||
|
modifier = Modifier.animateItem()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
LazyColumn(
|
|
||||||
state = listState,
|
|
||||||
contentPadding = PaddingValues(bottom = 16.dp)
|
|
||||||
) {
|
|
||||||
items(
|
|
||||||
items = books,
|
|
||||||
key = { it.bookUrl }
|
|
||||||
) { book ->
|
|
||||||
val shelfState = viewModel.getCurrentBookShelfState(book)
|
|
||||||
ExploreBookItem(
|
|
||||||
book = book,
|
|
||||||
shelfState = shelfState,
|
|
||||||
onClick = { onBookClick(book) },
|
|
||||||
modifier = Modifier.animateItem()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
item {
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
LoadMoreFooter(
|
LoadMoreFooter(
|
||||||
isLoading = isLoading,
|
isLoading = isLoading,
|
||||||
errorMsg = errorMsg,
|
errorMsg = errorMsg,
|
||||||
onRetry = viewModel::loadMore
|
isEnd = isBookEnd,
|
||||||
)
|
onRetry = viewModel::loadMore
|
||||||
}
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LazyColumn(
|
||||||
|
state = listState,
|
||||||
|
contentPadding = PaddingValues(bottom = 16.dp)
|
||||||
|
) {
|
||||||
|
items(
|
||||||
|
items = books,
|
||||||
|
key = { it.bookUrl }
|
||||||
|
) { book ->
|
||||||
|
val shelfState = viewModel.getCurrentBookShelfState(book)
|
||||||
|
ExploreBookItem(
|
||||||
|
book = book,
|
||||||
|
shelfState = shelfState,
|
||||||
|
onClick = { onBookClick(book) },
|
||||||
|
modifier = Modifier.animateItem()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
item {
|
||||||
|
LoadMoreFooter(
|
||||||
|
isLoading = isLoading,
|
||||||
|
errorMsg = errorMsg,
|
||||||
|
isEnd = isBookEnd,
|
||||||
|
onRetry = viewModel::loadMore
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -341,6 +388,7 @@ fun ExploreTopBar(
|
|||||||
onSelectKindClick: () -> Unit,
|
onSelectKindClick: () -> Unit,
|
||||||
onToggleGridMode: () -> Unit,
|
onToggleGridMode: () -> Unit,
|
||||||
isGridMode: Boolean,
|
isGridMode: Boolean,
|
||||||
|
onGridCountClick: () -> Unit,
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
) {
|
) {
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
@@ -353,11 +401,25 @@ fun ExploreTopBar(
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions = {
|
actions = {
|
||||||
IconButton(onClick = { showMenu = true }) {
|
Row(
|
||||||
Icon(Icons.Default.FilterList, contentDescription = "Filter")
|
horizontalArrangement = Arrangement.End,
|
||||||
}
|
modifier = Modifier.animateContentSize(tween(300))
|
||||||
IconButton(onClick = { onSelectKindClick() }) {
|
) {
|
||||||
Icon(Icons.Outlined.FilterAlt, contentDescription = "分类")
|
IconButton(onClick = { showMenu = true }) {
|
||||||
|
Icon(Icons.Default.FilterList, contentDescription = "Filter")
|
||||||
|
}
|
||||||
|
IconButton(onClick = { onSelectKindClick() }) {
|
||||||
|
Icon(Icons.Outlined.FilterAlt, contentDescription = "分类")
|
||||||
|
}
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = isGridMode,
|
||||||
|
enter = fadeIn(tween(300)),
|
||||||
|
exit = fadeOut(tween(300))
|
||||||
|
) {
|
||||||
|
IconButton(onClick = onGridCountClick) {
|
||||||
|
Icon(Icons.Default.Grid4x4, contentDescription = "列数设置")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
IconButton(onClick = { onToggleGridMode() }) {
|
IconButton(onClick = { onToggleGridMode() }) {
|
||||||
Icon(
|
Icon(
|
||||||
@@ -592,22 +654,33 @@ fun TagChip(text: String) {
|
|||||||
fun LoadMoreFooter(
|
fun LoadMoreFooter(
|
||||||
isLoading: Boolean,
|
isLoading: Boolean,
|
||||||
errorMsg: String?,
|
errorMsg: String?,
|
||||||
|
isEnd: Boolean,
|
||||||
onRetry: () -> Unit
|
onRetry: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
||||||
|
LaunchedEffect(isLoading, errorMsg, isEnd) {
|
||||||
|
if (!isLoading && errorMsg == null && !isEnd) {
|
||||||
|
while (true) {
|
||||||
|
onRetry()
|
||||||
|
delay(1000L)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(32.dp),
|
.padding(32.dp),
|
||||||
contentAlignment = Alignment.Center
|
contentAlignment = Alignment.Center
|
||||||
) {
|
) {
|
||||||
|
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
|
|
||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
targetState = when {
|
targetState = when {
|
||||||
isLoading -> "加载中…"
|
isLoading -> "加载中…"
|
||||||
errorMsg != null -> "加载失败: $errorMsg"
|
errorMsg != null -> "加载失败: $errorMsg"
|
||||||
else -> "已经到底了~"
|
isEnd -> "已经到底了~"
|
||||||
|
else -> "我爱你"
|
||||||
},
|
},
|
||||||
label = "FooterTextChange"
|
label = "FooterTextChange"
|
||||||
) { text ->
|
) { text ->
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import io.legado.app.data.repository.ExploreRepository
|
|||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.model.BookShelfState
|
import io.legado.app.model.BookShelfState
|
||||||
import io.legado.app.ui.book.search.BookKey
|
import io.legado.app.ui.book.search.BookKey
|
||||||
|
import io.legado.app.utils.exploreLayoutGrid
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
@@ -17,6 +18,7 @@ import kotlinx.coroutines.flow.asStateFlow
|
|||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.stateIn
|
import kotlinx.coroutines.flow.stateIn
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import splitties.init.appCtx
|
||||||
|
|
||||||
sealed class BookFilterState(val id: Int) {
|
sealed class BookFilterState(val id: Int) {
|
||||||
data object SHOW_ALL : BookFilterState(0)
|
data object SHOW_ALL : BookFilterState(0)
|
||||||
@@ -60,7 +62,13 @@ class ExploreShowViewModel(
|
|||||||
val selectedKindTitle = _selectedKindTitle.asStateFlow()
|
val selectedKindTitle = _selectedKindTitle.asStateFlow()
|
||||||
private val _layoutState = MutableStateFlow(AppConfig.exploreLayoutState) // 0=列表, 1=网格
|
private val _layoutState = MutableStateFlow(AppConfig.exploreLayoutState) // 0=列表, 1=网格
|
||||||
val layoutState: StateFlow<Int> = _layoutState.asStateFlow()
|
val layoutState: StateFlow<Int> = _layoutState.asStateFlow()
|
||||||
|
private val _gridCount = MutableStateFlow(appCtx.exploreLayoutGrid)
|
||||||
|
val gridCount = _gridCount.asStateFlow()
|
||||||
|
val isEnd: StateFlow<Boolean> = _isEndStateFlow.asStateFlow()
|
||||||
|
fun saveGridCount(value: Int) {
|
||||||
|
appCtx.exploreLayoutGrid = value
|
||||||
|
_gridCount.value = value
|
||||||
|
}
|
||||||
val uiBooks = combine(
|
val uiBooks = combine(
|
||||||
_rawBooks,
|
_rawBooks,
|
||||||
_filterState,
|
_filterState,
|
||||||
@@ -71,13 +79,6 @@ 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(
|
val shouldTriggerAutoLoad = combine(
|
||||||
_isLoading,
|
_isLoading,
|
||||||
uiBooks,
|
uiBooks,
|
||||||
@@ -138,21 +139,14 @@ class ExploreShowViewModel(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun loadMore(isRefresh: Boolean = false) {
|
fun loadMore(isRefresh: Boolean = false) {
|
||||||
if (_isLoading.value || (_isEndStateFlow.value && !isRefresh)) return
|
val source = bookSource
|
||||||
|
val url = exploreUrl
|
||||||
|
if (source == null || url == null || _isLoading.value || (_isEndStateFlow.value && !isRefresh)) return
|
||||||
|
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
_isLoading.value = true
|
_isLoading.value = true
|
||||||
_errorMsg.value = null
|
_errorMsg.value = null
|
||||||
|
|
||||||
val source = bookSource
|
|
||||||
val url = exploreUrl
|
|
||||||
|
|
||||||
if (source == null || url == null) {
|
|
||||||
_isLoading.value = false
|
|
||||||
_errorMsg.value = "源或URL为空"
|
|
||||||
return@launch
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isRefresh) {
|
if (isRefresh) {
|
||||||
page = 1
|
page = 1
|
||||||
_isEndStateFlow.value = false
|
_isEndStateFlow.value = false
|
||||||
@@ -165,16 +159,21 @@ class ExploreShowViewModel(
|
|||||||
_isEndStateFlow.value = true
|
_isEndStateFlow.value = true
|
||||||
} else {
|
} else {
|
||||||
repository.saveSearchBooks(newBooks)
|
repository.saveSearchBooks(newBooks)
|
||||||
val currentList = if (page == 1) emptyList() else _rawBooks.value
|
|
||||||
if (page == 1) {
|
val currentList = _rawBooks.value
|
||||||
_rawBooks.value = newBooks.distinctBy { it.bookUrl }
|
val existingUrls = currentList.map { it.bookUrl }.toSet()
|
||||||
|
|
||||||
|
val uniqueNewBooks = newBooks
|
||||||
|
.filter { it.bookUrl !in existingUrls }
|
||||||
|
.distinctBy { it.bookUrl }
|
||||||
|
|
||||||
|
if (uniqueNewBooks.isEmpty()) {
|
||||||
|
_isEndStateFlow.value = true
|
||||||
} else {
|
} else {
|
||||||
val existingUrls = currentList.map { it.bookUrl }.toSet()
|
|
||||||
val uniqueNewBooks = newBooks.filter { it.bookUrl !in existingUrls }
|
|
||||||
_rawBooks.value = currentList + uniqueNewBooks
|
_rawBooks.value = currentList + uniqueNewBooks
|
||||||
|
page++
|
||||||
|
_isEndStateFlow.value = false
|
||||||
}
|
}
|
||||||
page++
|
|
||||||
_isEndStateFlow.value = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onFailure {
|
.onFailure {
|
||||||
|
|||||||
@@ -434,6 +434,20 @@ val Context.bookshelfLayoutGrid: Int
|
|||||||
AppConfig.bookshelfLayoutGridPortrait
|
AppConfig.bookshelfLayoutGridPortrait
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var Context.exploreLayoutGrid: Int
|
||||||
|
get() = if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
AppConfig.exploreLayoutGridLandscape
|
||||||
|
} else {
|
||||||
|
AppConfig.exploreLayoutGridPortrait
|
||||||
|
}
|
||||||
|
set(value) {
|
||||||
|
if (resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||||
|
AppConfig.exploreLayoutGridLandscape = value
|
||||||
|
} else {
|
||||||
|
AppConfig.exploreLayoutGridPortrait = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Context.themeColor(attr: Int): Int {
|
fun Context.themeColor(attr: Int): Int {
|
||||||
val typedValue = TypedValue()
|
val typedValue = TypedValue()
|
||||||
theme.resolveAttribute(attr, typedValue, true)
|
theme.resolveAttribute(attr, typedValue, true)
|
||||||
|
|||||||
Reference in new issue
Block a user