[新增] 发现页新增网格布局
This commit is contained in:
4 files changed
+140
-1
No files matched your search
@@ -222,4 +222,6 @@ object PreferKey {
|
|||||||
const val shouldShowExpandButton = "shouldShowExpandButton"
|
const val shouldShowExpandButton = "shouldShowExpandButton"
|
||||||
|
|
||||||
const val exploreFilterState = "explore_filter_state"
|
const val exploreFilterState = "explore_filter_state"
|
||||||
|
|
||||||
|
const val exploreLayoutState = "exploreLayoutState"
|
||||||
}
|
}
|
||||||
@@ -902,5 +902,12 @@ object AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {
|
|||||||
set(value) {
|
set(value) {
|
||||||
appCtx.putPrefInt(PreferKey.exploreFilterState, value)
|
appCtx.putPrefInt(PreferKey.exploreFilterState, value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var exploreLayoutState: Int
|
||||||
|
get() = appCtx.getPrefInt(PreferKey.exploreLayoutState, 0)
|
||||||
|
set(value) {
|
||||||
|
appCtx.putPrefInt(PreferKey.exploreLayoutState, value)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,20 +1,29 @@
|
|||||||
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.Crossfade
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
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.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.FlowRow
|
import androidx.compose.foundation.layout.FlowRow
|
||||||
|
import androidx.compose.foundation.layout.IntrinsicSize
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.RowScope
|
||||||
import androidx.compose.foundation.layout.Spacer
|
import androidx.compose.foundation.layout.Spacer
|
||||||
|
import androidx.compose.foundation.layout.aspectRatio
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
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.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.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.items
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
@@ -23,8 +32,12 @@ import androidx.compose.foundation.verticalScroll
|
|||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.automirrored.filled.List
|
import androidx.compose.material.icons.automirrored.filled.List
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.Sort
|
||||||
|
import androidx.compose.material.icons.automirrored.filled.ViewList
|
||||||
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.ViewList
|
||||||
import androidx.compose.material3.DropdownMenu
|
import androidx.compose.material3.DropdownMenu
|
||||||
import androidx.compose.material3.DropdownMenuItem
|
import androidx.compose.material3.DropdownMenuItem
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
@@ -52,6 +65,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
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.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
@@ -90,7 +104,8 @@ fun ExploreShowScreen(
|
|||||||
val listState = rememberLazyListState()
|
val listState = rememberLazyListState()
|
||||||
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
|
||||||
var showKindSheet by remember { mutableStateOf(false) }
|
var showKindSheet by remember { mutableStateOf(false) }
|
||||||
|
val layoutState by viewModel.layoutState.collectAsState()
|
||||||
|
val isGridMode = layoutState == 1
|
||||||
if (showKindSheet) {
|
if (showKindSheet) {
|
||||||
val scrollState = rememberScrollState() // 记住滚动状态
|
val scrollState = rememberScrollState() // 记住滚动状态
|
||||||
|
|
||||||
@@ -137,6 +152,8 @@ fun ExploreShowScreen(
|
|||||||
onBack = onBack,
|
onBack = onBack,
|
||||||
onFilterSelect = viewModel::setFilterState,
|
onFilterSelect = viewModel::setFilterState,
|
||||||
onSelectKindClick = { showKindSheet = true },
|
onSelectKindClick = { showKindSheet = true },
|
||||||
|
onToggleGridMode = { viewModel.setLayout() },
|
||||||
|
isGridMode = isGridMode,
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -150,6 +167,46 @@ fun ExploreShowScreen(
|
|||||||
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
|
||||||
EmptyMessageView(message = "暂无书籍")
|
EmptyMessageView(message = "暂无书籍")
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Crossfade(
|
||||||
|
targetState = isGridMode,
|
||||||
|
animationSpec = tween(250),
|
||||||
|
label = "LayoutCrossfade"
|
||||||
|
) { isGrid ->
|
||||||
|
if (isGrid) {
|
||||||
|
LazyVerticalGrid(
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
columns = GridCells.Adaptive(minSize = 90.dp),
|
||||||
|
contentPadding = PaddingValues(
|
||||||
|
start = 12.dp,
|
||||||
|
end = 12.dp,
|
||||||
|
top = 8.dp,
|
||||||
|
bottom = 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 {
|
||||||
|
LoadMoreFooter(
|
||||||
|
isLoading = isLoading,
|
||||||
|
errorMsg = errorMsg,
|
||||||
|
onRetry = viewModel::loadMore
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
state = listState,
|
state = listState,
|
||||||
@@ -177,6 +234,8 @@ fun ExploreShowScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val shouldLoadMore = remember {
|
val shouldLoadMore = remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@@ -234,6 +293,8 @@ fun ExploreTopBar(
|
|||||||
onBack: () -> Unit,
|
onBack: () -> Unit,
|
||||||
onFilterSelect: (BookFilterState) -> Unit,
|
onFilterSelect: (BookFilterState) -> Unit,
|
||||||
onSelectKindClick: () -> Unit,
|
onSelectKindClick: () -> Unit,
|
||||||
|
onToggleGridMode: () -> Unit,
|
||||||
|
isGridMode: Boolean,
|
||||||
scrollBehavior: TopAppBarScrollBehavior? = null
|
scrollBehavior: TopAppBarScrollBehavior? = null
|
||||||
) {
|
) {
|
||||||
var showMenu by remember { mutableStateOf(false) }
|
var showMenu by remember { mutableStateOf(false) }
|
||||||
@@ -252,6 +313,12 @@ fun ExploreTopBar(
|
|||||||
IconButton(onClick = { onSelectKindClick() }) {
|
IconButton(onClick = { onSelectKindClick() }) {
|
||||||
Icon(Icons.AutoMirrored.Filled.List, contentDescription = "分类")
|
Icon(Icons.AutoMirrored.Filled.List, contentDescription = "分类")
|
||||||
}
|
}
|
||||||
|
IconButton(onClick = { onToggleGridMode() }) {
|
||||||
|
Icon(
|
||||||
|
imageVector = if (isGridMode) Icons.AutoMirrored.Filled.ViewList else Icons.Default.GridView,
|
||||||
|
contentDescription = "切换布局"
|
||||||
|
)
|
||||||
|
}
|
||||||
DropdownMenu(
|
DropdownMenu(
|
||||||
expanded = showMenu,
|
expanded = showMenu,
|
||||||
onDismissRequest = { showMenu = false }
|
onDismissRequest = { showMenu = false }
|
||||||
@@ -386,6 +453,59 @@ fun ExploreBookItem(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun ExploreBookGridItem(
|
||||||
|
book: SearchBook,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
shelfState: BookShelfState,
|
||||||
|
modifier: Modifier = Modifier
|
||||||
|
) {
|
||||||
|
|
||||||
|
val badgeText: String? = when (shelfState) {
|
||||||
|
BookShelfState.IN_SHELF -> "已在书架"
|
||||||
|
BookShelfState.SAME_NAME_AUTHOR -> "同名书籍"
|
||||||
|
BookShelfState.NOT_IN_SHELF -> null
|
||||||
|
}
|
||||||
|
|
||||||
|
val content: (@Composable RowScope.() -> Unit)? = if (!badgeText.isNullOrBlank()) {
|
||||||
|
{
|
||||||
|
Text(
|
||||||
|
text = badgeText,
|
||||||
|
style = MaterialTheme.typography.labelSmall.copy(fontWeight = FontWeight.Bold, fontSize = 9.sp)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
.width(IntrinsicSize.Min)
|
||||||
|
.clip(RoundedCornerShape(4.dp))
|
||||||
|
.clickable(onClick = onClick)
|
||||||
|
.padding(4.dp)
|
||||||
|
) {
|
||||||
|
|
||||||
|
Cover(
|
||||||
|
path = book.coverUrl,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.aspectRatio(12 / 17f),
|
||||||
|
badgeContent = content
|
||||||
|
)
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(4.dp))
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = book.name,
|
||||||
|
style = MaterialTheme.typography.bodySmall,
|
||||||
|
fontWeight = FontWeight.Bold,
|
||||||
|
maxLines = 2,
|
||||||
|
overflow = TextOverflow.Ellipsis
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BookshelfStatusBadge(
|
fun BookshelfStatusBadge(
|
||||||
shelfState: BookShelfState,
|
shelfState: BookShelfState,
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import io.legado.app.model.BookShelfState
|
|||||||
import io.legado.app.ui.book.search.BookKey
|
import io.legado.app.ui.book.search.BookKey
|
||||||
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.asStateFlow
|
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
|
||||||
@@ -57,6 +58,9 @@ class ExploreShowViewModel(
|
|||||||
val kinds = _kinds.asStateFlow()
|
val kinds = _kinds.asStateFlow()
|
||||||
private val _selectedKindTitle = MutableStateFlow<String?>(null)
|
private val _selectedKindTitle = MutableStateFlow<String?>(null)
|
||||||
val selectedKindTitle = _selectedKindTitle.asStateFlow()
|
val selectedKindTitle = _selectedKindTitle.asStateFlow()
|
||||||
|
private val _layoutState = MutableStateFlow(AppConfig.exploreLayoutState) // 0=列表, 1=网格
|
||||||
|
val layoutState: StateFlow<Int> = _layoutState.asStateFlow()
|
||||||
|
|
||||||
val uiBooks = combine(
|
val uiBooks = combine(
|
||||||
_rawBooks,
|
_rawBooks,
|
||||||
_filterState,
|
_filterState,
|
||||||
@@ -111,6 +115,12 @@ class ExploreShowViewModel(
|
|||||||
AppConfig.exploreFilterState = state.id
|
AppConfig.exploreFilterState = state.id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setLayout() {
|
||||||
|
val newState = if (_layoutState.value == 0) 1 else 0
|
||||||
|
_layoutState.value = newState
|
||||||
|
AppConfig.exploreLayoutState = newState
|
||||||
|
}
|
||||||
|
|
||||||
fun loadMore(isRefresh: Boolean = false) {
|
fun loadMore(isRefresh: Boolean = false) {
|
||||||
if (_isLoading.value || (isEnd && !isRefresh)) return
|
if (_isLoading.value || (isEnd && !isRefresh)) return
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user