From 6f5606b9e1dcfbb3bbc2a72fef259bbff018f41f Mon Sep 17 00:00:00 2001 From: HapeLee <1321903405@qq.com> Date: Sun, 30 Nov 2025 02:46:42 +0800 Subject: [PATCH] =?UTF-8?q?[=E6=96=B0=E5=A2=9E]=20=E5=8F=91=E7=8E=B0?= =?UTF-8?q?=E9=A1=B5=E5=BF=AB=E9=80=9F=E9=80=89=E6=8B=A9=E5=88=86=E7=B1=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../app/data/repository/ExploreRepository.kt | 9 ++ .../app/ui/book/explore/ExploreShowScreen.kt | 89 ++++++++++++++++++- .../ui/book/explore/ExploreShowViewModel.kt | 19 +++- 3 files changed, 112 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/io/legado/app/data/repository/ExploreRepository.kt b/app/src/main/java/io/legado/app/data/repository/ExploreRepository.kt index 7f2be3ab5..e13f22051 100644 --- a/app/src/main/java/io/legado/app/data/repository/ExploreRepository.kt +++ b/app/src/main/java/io/legado/app/data/repository/ExploreRepository.kt @@ -3,9 +3,12 @@ package io.legado.app.data.repository import io.legado.app.data.AppDatabase import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.SearchBook +import io.legado.app.data.entities.rule.ExploreKind import io.legado.app.help.book.isNotShelf +import io.legado.app.help.source.exploreKinds import io.legado.app.model.webBook.WebBook import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.map import kotlinx.coroutines.withContext @@ -15,6 +18,7 @@ interface ExploreRepository { suspend fun getBookSource(url: String): BookSource? suspend fun exploreBook(source: BookSource, url: String, page: Int): Result> suspend fun saveSearchBooks(books: List) + suspend fun getSourceExploreKinds(sourceUrl: String): List } class ExploreRepositoryImpl( @@ -44,6 +48,11 @@ class ExploreRepositoryImpl( } } + override suspend fun getSourceExploreKinds(sourceUrl: String): List = withContext(IO) { + val source = appDb.bookSourceDao.getBookSource(sourceUrl) + return@withContext source?.exploreKinds() ?: emptyList() + } + override suspend fun saveSearchBooks(books: List) { appDb.searchBookDao.insert(*books.toTypedArray()) } diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt index 484320fd3..e1f5596ff 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowScreen.kt @@ -5,6 +5,7 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer @@ -18,19 +19,23 @@ import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.automirrored.filled.List import androidx.compose.material.icons.filled.Check import androidx.compose.material.icons.filled.FilterList import androidx.compose.material3.DropdownMenu import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3ExpressiveApi +import androidx.compose.material3.FilterChip import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LoadingIndicator import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MediumTopAppBar +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Scaffold import androidx.compose.material3.Surface import androidx.compose.material3.Text @@ -55,7 +60,9 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import io.legado.app.data.entities.SearchBook +import io.legado.app.data.entities.rule.ExploreKind import io.legado.app.model.BookShelfState +import io.legado.app.ui.widget.components.AnimatedTextLine import io.legado.app.ui.widget.components.Cover import org.koin.androidx.compose.koinViewModel @@ -74,21 +81,61 @@ fun ExploreShowScreen( } val books by viewModel.uiBooks.collectAsState() + val kinds by viewModel.kinds.collectAsState() val isLoading by viewModel.isLoading.collectAsState() val errorMsg by viewModel.errorMsg.collectAsState() val filterState by viewModel.filterState.collectAsState() + val selectedTitle by viewModel.selectedKindTitle.collectAsState() val listState = rememberLazyListState() - val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior() + var showKindSheet by remember { mutableStateOf(false) } + + if (showKindSheet) { + val scrollState = rememberScrollState() // 记住滚动状态 + + ModalBottomSheet( + onDismissRequest = { showKindSheet = false } + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .verticalScroll(scrollState) + .padding(12.dp) + ) { + Text( + "选择分类", + style = MaterialTheme.typography.titleMedium, + modifier = Modifier + .padding(bottom = 8.dp) + ) + + FlowRow( + modifier = Modifier.fillMaxWidth() + ) { + kinds.forEach { kind -> + KindListItem( + kind = kind, + currentTitle = selectedTitle ?: title, + onClick = { + showKindSheet = false + viewModel.switchExploreUrl(kind) + } + ) + } + } + } + } + } Scaffold( modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection), topBar = { ExploreTopBar( - title = title, + title = selectedTitle ?: title, filterState = filterState, onBack = onBack, onFilterSelect = viewModel::setFilterState, + onSelectKindClick = { showKindSheet = true }, scrollBehavior = scrollBehavior ) } @@ -147,6 +194,37 @@ fun ExploreShowScreen( } } +@Composable +fun KindListItem( + kind: ExploreKind, + currentTitle: String?, + onClick: () -> Unit +) { + val isClickable = !kind.url.isNullOrBlank() + val isSelected = kind.title == currentTitle + FilterChip( + onClick = { if (isClickable) onClick() }, + enabled = isClickable, + selected = isSelected, + label = { + Text( + text = kind.title, + fontSize = 14.sp, + modifier = Modifier.fillMaxWidth(), + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + }, + modifier = Modifier + .then( + if (!isClickable) Modifier.fillMaxWidth() + else Modifier.fillMaxWidth(1 / 3f) + ) + .padding(horizontal = 4.dp) + ) +} + @OptIn(ExperimentalMaterial3Api::class) @Composable fun ExploreTopBar( @@ -154,12 +232,13 @@ fun ExploreTopBar( filterState: BookFilterState, onBack: () -> Unit, onFilterSelect: (BookFilterState) -> Unit, + onSelectKindClick: () -> Unit, scrollBehavior: TopAppBarScrollBehavior? = null ) { var showMenu by remember { mutableStateOf(false) } MediumTopAppBar( - title = { Text(title, maxLines = 1, overflow = TextOverflow.Ellipsis) }, + title = { AnimatedTextLine(title, maxLines = 1, overflow = TextOverflow.Ellipsis) }, navigationIcon = { IconButton(onClick = onBack) { Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") @@ -169,7 +248,9 @@ fun ExploreTopBar( IconButton(onClick = { showMenu = true }) { Icon(Icons.Default.FilterList, contentDescription = "Filter") } - + IconButton(onClick = { onSelectKindClick() }) { + Icon(Icons.AutoMirrored.Filled.List, contentDescription = "分类") + } DropdownMenu( expanded = showMenu, onDismissRequest = { showMenu = false } diff --git a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt index 322627e7a..80710e935 100644 --- a/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt @@ -5,6 +5,7 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.SearchBook +import io.legado.app.data.entities.rule.ExploreKind import io.legado.app.data.repository.ExploreRepository import io.legado.app.help.config.AppConfig import io.legado.app.model.BookShelfState @@ -52,7 +53,10 @@ class ExploreShowViewModel( private var page = 1 private var isEnd = false private val _bookshelf = MutableStateFlow>(emptySet()) - + private val _kinds = MutableStateFlow>(emptyList()) + val kinds = _kinds.asStateFlow() + private val _selectedKindTitle = MutableStateFlow(null) + val selectedKindTitle = _selectedKindTitle.asStateFlow() val uiBooks = combine( _rawBooks, _filterState, @@ -96,11 +100,24 @@ class ExploreShowViewModel( viewModelScope.launch { if (bookSource == null && sourceUrl != null) { bookSource = repository.getBookSource(sourceUrl) + loadKinds(sourceUrl) } loadMore(isRefresh = true) } } + fun loadKinds(sourceUrl: String) { + viewModelScope.launch { + _kinds.value = repository.getSourceExploreKinds(sourceUrl) + } + } + + fun switchExploreUrl(kind: ExploreKind) { + _selectedKindTitle.value = kind.title + exploreUrl = kind.url + loadMore(isRefresh = true) + } + fun setFilterState(state: BookFilterState) { _filterState.value = state AppConfig.exploreFilterState = state.id