Explorar o código

fix: 缓存管理的筛选

HapeLee hai 3 meses
pai
achega
10d9975020

+ 101 - 22
app/src/main/java/io/legado/app/ui/book/cache/manage/BookCacheManageScreen.kt

@@ -2,6 +2,7 @@ package io.legado.app.ui.book.cache.manage
 
 import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.fillMaxSize
@@ -42,6 +43,7 @@ import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
+import io.legado.app.data.entities.BookGroup
 import io.legado.app.ui.theme.LegadoTheme
 import io.legado.app.ui.theme.adaptiveContentPadding
 import io.legado.app.ui.widget.components.AppFloatingActionButton
@@ -51,16 +53,27 @@ import io.legado.app.ui.widget.components.button.series.SmallTonalButton
 import io.legado.app.ui.widget.components.card.NormalCard
 import io.legado.app.ui.widget.components.card.TextCard
 import io.legado.app.ui.widget.components.icon.AppIcon
+import io.legado.app.ui.widget.components.icon.AppIcons
+import io.legado.app.ui.widget.components.list.ListUiState
+import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenu
+import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
 import io.legado.app.ui.widget.components.progressIndicator.AppLinearProgressIndicator
 import io.legado.app.ui.widget.components.text.AppText
-import io.legado.app.ui.widget.components.topbar.GlassMediumFlexibleTopAppBar
+import io.legado.app.ui.widget.components.topbar.DynamicTopAppBar
 import io.legado.app.ui.widget.components.topbar.GlassTopAppBarDefaults
 import io.legado.app.ui.widget.components.topbar.TopBarActionButton
-import io.legado.app.ui.widget.components.topbar.TopBarNavigationButton
 import io.legado.app.utils.toastOnUi
 import org.koin.androidx.compose.koinViewModel
 
+private data class BookCacheManageListState(
+    override val items: List<BookCacheBookItem> = emptyList(),
+    override val selectedIds: Set<Any> = emptySet(),
+    override val searchKey: String = "",
+    override val isSearch: Boolean = false,
+    override val isLoading: Boolean = false,
+) : ListUiState<BookCacheBookItem>
+
 @Composable
 fun BookCacheManageRouteScreen(
     onBackClick: () -> Unit,
@@ -95,24 +108,86 @@ private fun BookCacheManageScreen(
     val scrollBehavior = GlassTopAppBarDefaults.defaultScrollBehavior()
     var pendingDeleteBook by remember { mutableStateOf<BookCacheBookItem?>(null) }
     var pendingDeleteChapter by remember { mutableStateOf<Pair<BookCacheBookItem, BookCacheChapterItem>?>(null) }
+    var isSearchMode by remember { mutableStateOf(false) }
+    var searchKey by remember { mutableStateOf("") }
+    var selectedGroupId by remember { mutableStateOf(BookGroup.IdAll) }
+    var showGroupMenu by remember { mutableStateOf(false) }
     val allBooks = state.shelfBooks + state.notShelfBooks
-    val hasRunningDownload = allBooks.any { it.hasActiveDownload }
-    val hasDownloadTarget = allBooks.any { it.cachedCount < it.totalCount }
+    val filteredBooks = remember(allBooks, searchKey, isSearchMode, selectedGroupId) {
+        allBooks.filter { book ->
+            val matchesGroup = when (selectedGroupId) {
+                BookGroup.IdAll -> true
+                BookGroup.IdNetNone -> !book.isNotShelf && book.group == 0L
+                else -> !book.isNotShelf && (book.group and selectedGroupId) > 0L
+            }
+            val matchesSearch = !isSearchMode || searchKey.isBlank() ||
+                    book.name.contains(searchKey.trim(), ignoreCase = true) ||
+                    book.author.contains(searchKey.trim(), ignoreCase = true)
+            matchesGroup && matchesSearch
+        }
+    }
+    val filteredShelfBooks = remember(filteredBooks) { filteredBooks.filterNot { it.isNotShelf } }
+    val filteredNotShelfBooks = remember(filteredBooks) { filteredBooks.filter { it.isNotShelf } }
+    val hasRunningDownload = filteredBooks.any { it.hasActiveDownload }
+    val hasDownloadTarget = filteredBooks.any { it.cachedCount < it.totalCount }
+    val listUiState = remember(filteredBooks, searchKey, isSearchMode) {
+        BookCacheManageListState(
+            items = filteredBooks,
+            searchKey = searchKey,
+            isSearch = isSearchMode,
+        )
+    }
     val bookshelfSectionTitle = stringResource(R.string.cache_section_bookshelf)
     val bookshelfSectionEmptyText = stringResource(R.string.cache_empty_bookshelf)
     val notBookshelfSectionTitle = stringResource(R.string.cache_section_not_in_bookshelf)
     val notBookshelfSectionEmptyText = stringResource(R.string.cache_empty_not_in_bookshelf)
+    val allGroupText = stringResource(R.string.all)
+    val noGroupText = stringResource(R.string.net_no_group)
+    val groupOptions = remember(state.groups, allGroupText, noGroupText) {
+        listOf(
+            BookGroup.IdAll to allGroupText,
+            BookGroup.IdNetNone to noGroupText,
+        ) + state.groups.map { it.groupId to it.groupName }
+    }
 
     AppScaffold(
         modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
-            GlassMediumFlexibleTopAppBar(
+            DynamicTopAppBar(
                 title = stringResource(R.string.cache_management),
                 subtitle = state.downloadSummary.takeIf { it.isNotBlank() },
-                navigationIcon = {
-                    TopBarNavigationButton(onClick = onBackClick)
+                state = listUiState,
+                onBackClick = onBackClick,
+                onSearchToggle = { active ->
+                    isSearchMode = active
+                    if (!active) searchKey = ""
                 },
-                actions = {
+                onSearchQueryChange = { searchKey = it },
+                searchPlaceholder = "筛选书名/作者",
+                onClearSelection = {},
+                topBarActions = {
+                    Box {
+                        TopBarActionButton(
+                            onClick = { showGroupMenu = true },
+                            imageVector = AppIcons.Filter,
+                            contentDescription = "分组筛选"
+                        )
+                        RoundDropdownMenu(
+                            expanded = showGroupMenu,
+                            onDismissRequest = { showGroupMenu = false }
+                        ) { dismiss ->
+                            groupOptions.forEach { (groupId, groupName) ->
+                                RoundDropdownMenuItem(
+                                    text = groupName,
+                                    isSelected = groupId == selectedGroupId,
+                                    onClick = {
+                                        selectedGroupId = groupId
+                                        dismiss()
+                                    }
+                                )
+                            }
+                        }
+                    }
                     TopBarActionButton(
                         onClick = { onIntent(BookCacheManageIntent.Refresh) },
                         imageVector = Icons.Default.Refresh,
@@ -166,20 +241,7 @@ private fun BookCacheManageScreen(
                 cacheSection(
                     title = bookshelfSectionTitle,
                     emptyText = bookshelfSectionEmptyText,
-                    books = state.shelfBooks,
-                    expandedBookUrls = state.expandedBookUrls,
-                    chaptersByBookUrl = state.chaptersByBookUrl,
-                    onToggleExpanded = { bookUrl ->
-                        onIntent(BookCacheManageIntent.ToggleBookExpanded(bookUrl))
-                    },
-                    onIntent = onIntent,
-                    onDeleteBook = { pendingDeleteBook = it },
-                    onDeleteChapter = { book, chapter -> pendingDeleteChapter = book to chapter }
-                )
-                cacheSection(
-                    title = notBookshelfSectionTitle,
-                    emptyText = notBookshelfSectionEmptyText,
-                    books = state.notShelfBooks,
+                    books = filteredShelfBooks,
                     expandedBookUrls = state.expandedBookUrls,
                     chaptersByBookUrl = state.chaptersByBookUrl,
                     onToggleExpanded = { bookUrl ->
@@ -189,6 +251,23 @@ private fun BookCacheManageScreen(
                     onDeleteBook = { pendingDeleteBook = it },
                     onDeleteChapter = { book, chapter -> pendingDeleteChapter = book to chapter }
                 )
+                if (selectedGroupId == BookGroup.IdAll) {
+                    cacheSection(
+                        title = notBookshelfSectionTitle,
+                        emptyText = notBookshelfSectionEmptyText,
+                        books = filteredNotShelfBooks,
+                        expandedBookUrls = state.expandedBookUrls,
+                        chaptersByBookUrl = state.chaptersByBookUrl,
+                        onToggleExpanded = { bookUrl ->
+                            onIntent(BookCacheManageIntent.ToggleBookExpanded(bookUrl))
+                        },
+                        onIntent = onIntent,
+                        onDeleteBook = { pendingDeleteBook = it },
+                        onDeleteChapter = { book, chapter ->
+                            pendingDeleteChapter = book to chapter
+                        }
+                    )
+                }
             }
         }
     }

+ 16 - 1
app/src/main/java/io/legado/app/ui/book/cache/manage/BookCacheManageViewModel.kt

@@ -5,8 +5,10 @@ import androidx.lifecycle.viewModelScope
 import io.legado.app.base.BaseViewModel
 import io.legado.app.data.dao.BookChapterDao
 import io.legado.app.data.dao.BookDao
+import io.legado.app.data.dao.BookGroupDao
 import io.legado.app.data.entities.Book
 import io.legado.app.data.entities.BookChapter
+import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.model.BookChapterCacheInfo
 import io.legado.app.domain.usecase.CacheBookChaptersUseCase
 import io.legado.app.domain.usecase.ClearBookCacheUseCase
@@ -25,12 +27,12 @@ import kotlinx.coroutines.Job
 import kotlinx.coroutines.currentCoroutineContext
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.ensureActive
-import kotlinx.coroutines.isActive
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 import java.util.concurrent.ConcurrentHashMap
@@ -40,6 +42,7 @@ data class BookCacheManageUiState(
     val isLoading: Boolean = true,
     val shelfBooks: List<BookCacheBookItem> = emptyList(),
     val notShelfBooks: List<BookCacheBookItem> = emptyList(),
+    val groups: List<BookGroup> = emptyList(),
     val expandedBookUrls: Set<String> = emptySet(),
     val chaptersByBookUrl: Map<String, List<BookCacheChapterItem>> = emptyMap(),
     val downloadSummary: String = "",
@@ -59,6 +62,7 @@ data class BookCacheBookItem(
     val pausedCount: Int,
     val errorCount: Int,
     val isNotShelf: Boolean,
+    val group: Long,
 ) {
     val progress: Float get() = if (totalCount == 0) 0f else cachedCount.toFloat() / totalCount
     val hasActiveDownload: Boolean get() = waitingCount > 0 || downloadingCount > 0
@@ -107,6 +111,7 @@ class BookCacheManageViewModel(
     application: Application,
     private val bookDao: BookDao,
     private val bookChapterDao: BookChapterDao,
+    private val bookGroupDao: BookGroupDao,
     private val cacheBookChaptersUseCase: CacheBookChaptersUseCase,
     private val clearBookCacheUseCase: ClearBookCacheUseCase,
 ) : BaseViewModel(application) {
@@ -163,6 +168,15 @@ class BookCacheManageViewModel(
                 reloadAll(books = books, forceDatabase = false)
             }
         }
+        viewModelScope.launch {
+            bookGroupDao.flowSelect().collect { groups ->
+                _uiState.update { state ->
+                    state.copy(
+                        groups = groups.filter { it.groupId > 0L }
+                    )
+                }
+            }
+        }
         viewModelScope.launch {
             CacheBook.cacheSuccessFlow.collect { chapter ->
                 scheduleDownloadStatusRefresh(chapter.bookUrl)
@@ -381,6 +395,7 @@ class BookCacheManageViewModel(
             pausedCount = pausedCount,
             errorCount = errorIndices.size,
             isNotShelf = book.isNotShelf,
+            group = book.group,
         )
     }