diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoSheets.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoSheets.kt index b0439abf7..3c120ef71 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoSheets.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoSheets.kt @@ -33,13 +33,11 @@ import androidx.compose.material.icons.outlined.FolderZip import androidx.compose.material.icons.outlined.Image import androidx.compose.material.icons.outlined.Settings import androidx.compose.material3.Icon -import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableLongStateOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -48,10 +46,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.geometry.CornerRadius import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.lifecycle.Lifecycle import androidx.lifecycle.LifecycleEventObserver @@ -73,7 +69,6 @@ import io.legado.app.ui.book.search.ScopeSelectSheet import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.theme.LegadoTheme -import io.legado.app.ui.widget.components.progressIndicator.AppLinearProgressIndicator import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.EmptyMessage import io.legado.app.ui.widget.components.alert.AppAlertDialog @@ -86,7 +81,8 @@ import io.legado.app.ui.widget.components.cover.CoilBookCover 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.modalBottomSheet.AppModalBottomSheet -import io.legado.app.ui.widget.components.tabRow.AppTabRow +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.utils.StartActivityContract import io.legado.app.utils.startActivity @@ -103,7 +99,11 @@ fun WebFileSheet( ) { AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest, title = title) { if (files.isEmpty()) { - Box(modifier = Modifier.fillMaxWidth().padding(24.dp), contentAlignment = Alignment.Center) { + Box( + modifier = Modifier + .fillMaxWidth() + .padding(24.dp), contentAlignment = Alignment.Center + ) { Text(text = stringResource(R.string.empty)) } } else { @@ -111,7 +111,9 @@ fun WebFileSheet( items(files, key = { it.name }) { file -> GlassCard(onClick = { onSelect(file) }) { Row( - modifier = Modifier.fillMaxWidth().padding(16.dp), + modifier = Modifier + .fillMaxWidth() + .padding(16.dp), horizontalArrangement = Arrangement.spacedBy(12.dp), verticalAlignment = Alignment.CenterVertically, ) { @@ -269,6 +271,31 @@ fun ChangeCoverSheet( } } +@Composable +fun ChangeSourceSheet( + data: Book?, + onDismissRequest: () -> Unit, + onReplace: (BookSource, Book, List, ChangeSourceMigrationOptions) -> Unit, + onAddAsNew: (Book, List) -> Unit, +) { + var cachedData by remember { mutableStateOf(data) } + + if (data != null) { + cachedData = data + } + + val currentData = cachedData + if (currentData != null) { + ChangeSourceSheet( + show = data != null, + oldBook = currentData, + onDismissRequest = onDismissRequest, + onReplace = onReplace, + onAddAsNew = onAddAsNew, + ) + } +} + @Composable fun ChangeSourceSheet( show: Boolean, @@ -426,7 +453,9 @@ fun ChangeSourceSheet( if (items.isEmpty()) { Box( - modifier = Modifier.fillMaxWidth().padding(vertical = 40.dp), + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 40.dp), contentAlignment = Alignment.Center ) { EmptyMessage( diff --git a/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreen.kt b/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreen.kt index 5c48ec84f..d503b7192 100644 --- a/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreen.kt +++ b/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreen.kt @@ -10,7 +10,6 @@ import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ExperimentalLayoutApi -import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize @@ -27,6 +26,7 @@ import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Bookmarks +import androidx.compose.material.icons.filled.Clear import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Download import androidx.compose.material.icons.filled.Info @@ -35,7 +35,6 @@ import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.Refresh import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.SelectAll -import androidx.compose.material.icons.filled.SkipNext import androidx.compose.material.icons.filled.Stop import androidx.compose.material.icons.filled.Upload import androidx.compose.material3.Checkbox @@ -54,16 +53,19 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource +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.constant.IntentAction import io.legado.app.data.appDb import io.legado.app.data.entities.Book +import io.legado.app.data.entities.BookChapter import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSourcePart import io.legado.app.domain.usecase.BatchChangeSourcePreviewItem import io.legado.app.domain.usecase.BatchChangeSourcePreviewStatus +import io.legado.app.domain.usecase.ChangeSourceMigrationOptions import io.legado.app.help.book.getExportFileName import io.legado.app.help.book.isLocal import io.legado.app.help.book.tryParesExportFileName @@ -80,11 +82,9 @@ import io.legado.app.ui.widget.components.AppFloatingActionButtonMenu import io.legado.app.ui.widget.components.AppTextField import io.legado.app.ui.widget.components.FabMenuItem import io.legado.app.ui.widget.components.alert.AppAlertDialog -import io.legado.app.ui.widget.components.button.MediumIconButton import io.legado.app.ui.widget.components.button.SmallTonalIconButton import io.legado.app.ui.widget.components.button.SmallTonalTextButton import io.legado.app.ui.widget.components.card.GlassCard -import io.legado.app.ui.widget.components.card.NormalCard import io.legado.app.ui.widget.components.card.ReorderableSelectionItem import io.legado.app.ui.widget.components.card.SelectionItemCard import io.legado.app.ui.widget.components.card.TextCard @@ -805,28 +805,25 @@ private fun BookshelfManageScreen( } } - singleChangeSourceBook?.let { book -> - ChangeSourceSheet( - show = true, - oldBook = book, - onDismissRequest = { singleChangeSourceBook = null }, - onReplace = { source, newBook, toc, options -> - viewModel.dispatch( - BookshelfManageScreenIntent.ChangeBookSource( - oldBookUrl = book.bookUrl, - source = source, - book = newBook, - chapters = toc, - options = options, - ) + ChangeSourceSheet( + data = singleChangeSourceBook, + onDismissRequest = { singleChangeSourceBook = null }, + onReplace = { source: BookSource, newBook: Book, toc: List, options: ChangeSourceMigrationOptions -> + viewModel.dispatch( + BookshelfManageScreenIntent.ChangeBookSource( + oldBookUrl = singleChangeSourceBook?.bookUrl ?: "", + source = source, + book = newBook, + chapters = toc, + options = options, ) - singleChangeSourceBook = null - }, - onAddAsNew = { _, _ -> - context.toastOnUi("请在书籍详情页添加为新书") - }, - ) - } + ) + singleChangeSourceBook = null + }, + onAddAsNew = { _: Book, _: List -> + context.toastOnUi("请在书籍详情页添加为新书") + }, + ) BookSourcePickerSheet( show = showBatchSourcePickerSheet, @@ -857,8 +854,7 @@ private fun BookshelfManageScreen( ) BatchChangePreviewSheet( - show = state.batchChangePreviewItems.isNotEmpty(), - items = state.batchChangePreviewItems, + data = state.batchChangePreviewItems.takeIf { it.isNotEmpty() }, onDismissRequest = { viewModel.dispatch(BookshelfManageScreenIntent.DismissBatchChangePreview) }, onOpenBook = { book, inBookshelf -> viewModel.dispatch(BookshelfManageScreenIntent.OpenBookInfoPreview(book, inBookshelf)) @@ -871,29 +867,27 @@ private fun BookshelfManageScreen( }, onShowOtherSources = { item -> otherSourcePreviewItem = item }, onMigrateAll = { viewModel.dispatch(BookshelfManageScreenIntent.MigrateAllPreviewItems) }, + onAddAllToShelf = { viewModel.dispatch(BookshelfManageScreenIntent.AddAllPreviewItemsToShelf) }, ) - manualSearchPreviewBook?.let { book -> - ChangeSourceSheet( - show = true, - oldBook = book, - onDismissRequest = { manualSearchPreviewBook = null }, - onReplace = { source, newBook, toc, _ -> - viewModel.dispatch( - BookshelfManageScreenIntent.UpdatePreviewItem( - oldBookUrl = book.bookUrl, - source = source, - book = newBook, - chapterCount = toc.size, - ) + ChangeSourceSheet( + data = manualSearchPreviewBook, + onDismissRequest = { manualSearchPreviewBook = null }, + onReplace = { source: BookSource, newBook: Book, toc: List, _ -> + viewModel.dispatch( + BookshelfManageScreenIntent.UpdatePreviewItem( + oldBookUrl = manualSearchPreviewBook?.bookUrl ?: "", + source = source, + book = newBook, + chapterCount = toc.size, ) - manualSearchPreviewBook = null - }, - onAddAsNew = { _, _ -> - context.toastOnUi("请先选择替换候选后再新增至书架") - }, - ) - } + ) + manualSearchPreviewBook = null + }, + onAddAsNew = { _: Book, _: List -> + context.toastOnUi("请先选择替换候选后再新增至书架") + }, + ) OtherSourceOptionsSheet( item = otherSourcePreviewItem, @@ -1392,8 +1386,7 @@ private fun SourcePickerItem( @Composable private fun BatchChangePreviewSheet( - show: Boolean, - items: List, + data: List?, onDismissRequest: () -> Unit, onOpenBook: (Book, Boolean) -> Unit, onManualSearch: (Book) -> Unit, @@ -1402,11 +1395,19 @@ private fun BatchChangePreviewSheet( onAddToShelf: (String) -> Unit, onShowOtherSources: (BatchChangeSourcePreviewItem) -> Unit, onMigrateAll: () -> Unit, + onAddAllToShelf: () -> Unit, ) { AppModalBottomSheet( - show = show, + data = data, onDismissRequest = onDismissRequest, title = "批量换源预览", + startAction = { + SmallTonalTextButton( + text = "新增全部", + imageVector = Icons.Default.Add, + onClick = onAddAllToShelf, + ) + }, endAction = { SmallTonalTextButton( text = "迁移全部", @@ -1414,7 +1415,7 @@ private fun BatchChangePreviewSheet( onClick = onMigrateAll, ) } - ) { + ) { items -> LazyColumn( modifier = Modifier .fillMaxWidth() @@ -1449,15 +1450,15 @@ private fun BatchChangePreviewRow( onShowOtherSources: (BatchChangeSourcePreviewItem) -> Unit, ) { val candidate = item.selectedCandidate - NormalCard( + GlassCard( modifier = Modifier.fillMaxWidth(), - containerColor = LegadoTheme.colorScheme.surfaceContainerLow, + containerColor = LegadoTheme.colorScheme.onSheetContent, ) { Column( modifier = Modifier .fillMaxWidth() .padding(12.dp), - verticalArrangement = Arrangement.spacedBy(10.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), ) { Row( modifier = Modifier.fillMaxWidth(), @@ -1493,27 +1494,26 @@ private fun BatchChangePreviewRow( modifier = Modifier.weight(1f), ) } - FlowRow( + Row( modifier = Modifier.fillMaxWidth(), - horizontalArrangement = Arrangement.spacedBy(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.SpaceBetween ) { - MediumIconButton( + SmallTonalTextButton( imageVector = Icons.Default.Search, onClick = { onManualSearch(item.oldBook) }, ) SmallTonalTextButton( text = "不迁移", - imageVector = Icons.Default.SkipNext, + imageVector = Icons.Default.Clear, onClick = { onSkip(item.oldBook.bookUrl) }, ) SmallTonalTextButton( - text = "开始迁移", + text = "迁移", imageVector = Icons.Default.PlayArrow, onClick = { onMigrate(item.oldBook.bookUrl) }, ) SmallTonalTextButton( - text = "新增至书架", + text = "新增", imageVector = Icons.Default.Add, onClick = { onAddToShelf(item.oldBook.bookUrl) }, ) @@ -1553,7 +1553,8 @@ private fun PreviewBookInfo( AppText( text = book.name, style = LegadoTheme.typography.labelMedium, - maxLines = 1, + maxLines = 2, + overflow = TextOverflow.Ellipsis ) AppText( text = "${book.getRealAuthor()} · ${chapterCount ?: 0}章", @@ -1566,8 +1567,7 @@ private fun PreviewBookInfo( AppText( text = title, style = LegadoTheme.typography.labelMedium, - color = LegadoTheme.colorScheme.error, - maxLines = 2, + color = LegadoTheme.colorScheme.error ) } } @@ -1581,41 +1581,38 @@ private fun OtherSourceOptionsSheet( onOpenBook: (Book) -> Unit, ) { AppModalBottomSheet( - show = item != null, + data = item, onDismissRequest = onDismissRequest, title = "其他源信息", - ) { - val currentItem = item - if (currentItem != null) { - LazyColumn( - modifier = Modifier - .fillMaxWidth() - .heightIn(max = 560.dp), - verticalArrangement = Arrangement.spacedBy(8.dp), - ) { - items(currentItem.candidates.indices.toList(), key = { it }) { index -> - val candidate = currentItem.candidates[index] - SelectionItemCard( - title = candidate.source.bookSourceName, - subtitle = "${candidate.book.name} · ${candidate.chapterCount}章", - supportingContent = { - AppText( - text = candidate.book.getRealAuthor(), - style = LegadoTheme.typography.bodySmall, - ) - }, - isSelected = index == currentItem.selectedCandidateIndex, - onToggleSelection = { onSelect(currentItem.oldBook.bookUrl, index) }, - trailingAction = { - SmallTonalIconButton( - onClick = { onOpenBook(candidate.book) }, - imageVector = Icons.Default.Info, - contentDescription = null, - ) - }, - containerColor = LegadoTheme.colorScheme.onSheetContent, - ) - } + ) { currentItem -> + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .heightIn(max = 560.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + items(currentItem.candidates.indices.toList(), key = { it }) { index -> + val candidate = currentItem.candidates[index] + SelectionItemCard( + title = candidate.source.bookSourceName, + subtitle = "${candidate.book.name} · ${candidate.chapterCount}章", + supportingContent = { + AppText( + text = candidate.book.getRealAuthor(), + style = LegadoTheme.typography.bodySmall, + ) + }, + isSelected = index == currentItem.selectedCandidateIndex, + onToggleSelection = { onSelect(currentItem.oldBook.bookUrl, index) }, + trailingAction = { + SmallTonalIconButton( + onClick = { onOpenBook(candidate.book) }, + imageVector = Icons.Default.Info, + contentDescription = null, + ) + }, + containerColor = LegadoTheme.colorScheme.onSheetContent, + ) } } Spacer(modifier = Modifier.height(16.dp)) diff --git a/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreenViewModel.kt b/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreenViewModel.kt index 8f0891190..b4fab0fd1 100644 --- a/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreenViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageScreenViewModel.kt @@ -3,19 +3,19 @@ package io.legado.app.ui.book.manage import android.app.Application import androidx.lifecycle.viewModelScope import io.legado.app.base.BaseViewModel +import io.legado.app.constant.BookType +import io.legado.app.data.appDb 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.appDb 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.entities.BookSource -import io.legado.app.constant.BookType +import io.legado.app.domain.usecase.BatchCacheDownloadUseCase import io.legado.app.domain.usecase.BatchChangeSourceCandidate import io.legado.app.domain.usecase.BatchChangeSourcePreviewItem import io.legado.app.domain.usecase.BatchChangeSourcePreviewStatus -import io.legado.app.domain.usecase.BatchCacheDownloadUseCase import io.legado.app.domain.usecase.CacheBookChaptersUseCase import io.legado.app.domain.usecase.ChangeBookSourceUseCase import io.legado.app.domain.usecase.ChangeSourceMigrationOptions @@ -23,11 +23,10 @@ import io.legado.app.domain.usecase.ClearBookCacheUseCase import io.legado.app.domain.usecase.DeleteBooksUseCase import io.legado.app.domain.usecase.UpdateBooksGroupUseCase import io.legado.app.help.book.BookHelp -import io.legado.app.help.book.isAudio import io.legado.app.help.book.isLocal import io.legado.app.help.book.removeType -import io.legado.app.model.CacheBook import io.legado.app.help.config.LocalConfig +import io.legado.app.model.CacheBook import io.legado.app.service.ExportBookService import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig import io.legado.app.ui.config.bookshelfConfig.BookshelfManageScreenConfig @@ -39,17 +38,17 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job 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.map import kotlinx.coroutines.flow.update +import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import java.util.concurrent.ConcurrentHashMap import kotlin.math.max import kotlin.math.min -import java.util.concurrent.ConcurrentHashMap data class BookshelfManageScreenExportConfig( val exportUseReplace: Boolean = true, @@ -123,6 +122,7 @@ sealed interface BookshelfManageScreenIntent { data class AddPreviewItemToShelf(val oldBookUrl: String) : BookshelfManageScreenIntent data class OpenBookInfoPreview(val book: Book, val inBookshelf: Boolean) : BookshelfManageScreenIntent data object MigrateAllPreviewItems : BookshelfManageScreenIntent + data object AddAllPreviewItemsToShelf : BookshelfManageScreenIntent data object DismissChangeSourceStatus : BookshelfManageScreenIntent data object DismissBatchChangePreview : BookshelfManageScreenIntent data class SetExportUseReplace(val enabled: Boolean) : BookshelfManageScreenIntent @@ -233,6 +233,8 @@ class BookshelfManageScreenViewModel( BookshelfManageScreenIntent.MigrateAllPreviewItems -> migrateAllPreviewItems() + BookshelfManageScreenIntent.AddAllPreviewItemsToShelf -> addAllPreviewItemsToShelf() + BookshelfManageScreenIntent.DismissChangeSourceStatus -> { _uiState.update { it.copy( @@ -958,6 +960,45 @@ class BookshelfManageScreenViewModel( } } + private fun addAllPreviewItemsToShelf() { + val items = uiState.value.batchChangePreviewItems.filter { it.canMigrate } + if (items.isEmpty()) return + execute { + _uiState.update { + it.copy(isChangingSource = true, changeSourceProgress = "0 / ${items.size}") + } + items.forEachIndexed { index, item -> + _uiState.update { + it.copy(changeSourceProgress = "${index + 1} / ${items.size} ${item.oldBook.name}") + } + val candidate = item.selectedCandidate ?: return@forEachIndexed + val chapters = changeBookSourceUseCase.loadCandidateChapters( + candidate.source, + candidate.book + ) ?: return@forEachIndexed + candidate.book.removeType(BookType.notShelf) + if (candidate.book.order == 0) { + candidate.book.order = bookDao.minOrder - 1 + } + bookDao.insert(candidate.book) + bookChapterDao.insert(*chapters.toTypedArray()) + } + }.onSuccess { + _uiState.update { it.copy(batchChangePreviewItems = emptyList()) } + _effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("批量添加完成")) + }.onError { + _effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("批量添加失败\n${it.localizedMessage}")) + }.onFinally { + _uiState.update { + it.copy( + isChangingSource = false, + changeSourceProgress = null, + cacheVersion = it.cacheVersion + 1 + ) + } + } + } + private fun clearCacheForBook(book: Book) { execute { clearBookCacheUseCase.execute(book.bookUrl) diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt index 60aa48622..038f417ea 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookItem.kt @@ -151,7 +151,7 @@ fun BookshelfItem( ) ) ) - .padding(horizontal = 6.dp, vertical = 6.dp) + .padding(all = 4.dp) ) } } @@ -165,7 +165,7 @@ fun BookshelfItem( textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start, modifier = Modifier .fillMaxWidth() - .padding(top = 4.dp, start = 4.dp, end = 4.dp, bottom = 8.dp) + .padding(start = 4.dp, end = 4.dp, bottom = 4.dp) ) } } @@ -175,7 +175,7 @@ fun BookshelfItem( NormalCard( modifier = modifier .fillMaxWidth() - .padding(all = 4.dp), + .padding(vertical = 4.dp), cornerRadius = 8.dp, containerColor = if (isSelected) { LegadoTheme.colorScheme.secondaryContainer @@ -194,7 +194,6 @@ fun BookshelfItem( modifier = Modifier .align(Alignment.Top) .width(if (!isCompact) 84.dp else 56.dp) - .padding(end = 8.dp) ) { Box( modifier = Modifier @@ -215,7 +214,7 @@ fun BookshelfItem( Column( modifier = Modifier .weight(1f) - .padding(top = 8.dp, bottom = 8.dp, end = 8.dp), + .padding(top = 4.dp, bottom = 4.dp, end = 4.dp), verticalArrangement = Arrangement.Center ) { Row( @@ -524,13 +523,13 @@ fun BookGroupItemHorizontalCovers( ) { Column( modifier = Modifier - .padding(vertical = 12.dp), + .padding(vertical = 4.dp), verticalArrangement = Arrangement.spacedBy(8.dp) ) { Row( modifier = Modifier .fillMaxWidth() - .padding(start = 12.dp, top = 4.dp, bottom = 4.dp), + .padding(start = 4.dp, top = 4.dp, bottom = 4.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically ) { @@ -549,7 +548,7 @@ fun BookGroupItemHorizontalCovers( ) } AppIcon( - modifier = Modifier.padding(end = 6.dp), + modifier = Modifier.padding(end = 4.dp), imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight, contentDescription = "", tint = LegadoTheme.colorScheme.onSurfaceVariant @@ -558,8 +557,8 @@ fun BookGroupItemHorizontalCovers( Row( modifier = Modifier .fillMaxWidth() - .padding(horizontal = 12.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) + .padding(horizontal = 4.dp), + horizontalArrangement = Arrangement.spacedBy(4.dp) ) { val coverCount = BookshelfConfig.bookshelfGroupCoverCount previewBooks.take(coverCount).forEach { bookUi -> diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt index d90bbaa1a..88c7a98e2 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt @@ -781,7 +781,7 @@ fun BookshelfScreen( contentPadding = adaptiveContentPaddingBookshelf( top = paddingValues.calculateTopPadding(), bottom = if (ThemeConfig.useFloatingBottomBar || ThemeConfig.enableBlur) 120.dp else 8.dp, - horizontal = if (isGridMode) 8.dp else 4.dp + horizontal = 4.dp ), verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp), horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp), @@ -1361,7 +1361,7 @@ fun BookshelfPage( contentPadding = adaptiveContentPaddingBookshelf( top = paddingValues.calculateTopPadding(), bottom = if (ThemeConfig.useFloatingBottomBar || ThemeConfig.enableBlur) 120.dp else 8.dp, - horizontal = if (isGridMode) 8.dp else 4.dp + horizontal = 8.dp ), verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp), horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp), diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt index c1665e5ec..e99f87c84 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt @@ -175,6 +175,13 @@ class BookshelfViewModel( val allBookCount: Int ) + private data class DataForPreviews( + val groups: List, + val bookGroupStyle: Int, + val systemCountsMap: Map, + val allBookCount: Int + ) + val groupSelectorState: StateFlow = combine( groupsFlow, groupIdFlow @@ -262,16 +269,25 @@ class BookshelfViewModel( private val groupPreviewsFlow = combine( groupsFlow, bookGroupStyleFlow, - appDb.bookDao.flowSystemGroupCounts() - ) { groups, bookGroupStyle, systemCounts -> - Triple(groups, bookGroupStyle, systemCounts.associate { it.groupId to it.count }) - }.flatMapLatest { (groups, bookGroupStyle, systemCountsMap) -> + appDb.bookDao.flowSystemGroupCounts(), + appDb.bookDao.flowAllBookShelfCount() + ) { groups, bookGroupStyle, systemCounts, totalCount -> + DataForPreviews( + groups, + bookGroupStyle, + systemCounts.associate { it.groupId to it.count }, + totalCount + ) + }.flatMapLatest { data -> + val groups = data.groups + val bookGroupStyle = data.bookGroupStyle + val systemCountsMap = data.systemCountsMap + val allBookCount = data.allBookCount + if (bookGroupStyle !in 2..3) { - appDb.bookDao.flowAllBookShelfCount().map { count -> - GroupPreviewState(persistentMapOf(), persistentMapOf(), count) - } + flowOf(GroupPreviewState(persistentMapOf(), persistentMapOf(), allBookCount)) } else if (groups.isEmpty()) { - flowOf(GroupPreviewState(persistentMapOf(), persistentMapOf(), 0)) + flowOf(GroupPreviewState(persistentMapOf(), persistentMapOf(), allBookCount)) } else { val groupFlows = groups.map { group -> val countFlow: Flow = if (group.groupId > 0) { @@ -287,11 +303,9 @@ class BookshelfViewModel( combine(groupFlows) { results -> var previews = persistentMapOf>() var counts = persistentMapOf() - var allBookCount = 0 results.forEach { (groupId, count, preview) -> counts = counts.put(groupId, count) previews = previews.put(groupId, preview.toImmutableList()) - if (groupId == BookGroup.IdAll) allBookCount = count } GroupPreviewState(previews, counts, allBookCount) } @@ -480,10 +494,16 @@ class BookshelfViewModel( bookshelfSort = internal.sortConfig.sort, bookshelfSortOrder = internal.sortConfig.sortOrder, title = title, - subtitle = if (interaction.isEditMode) { - context.getString(R.string.bookshelf_total_count, previews.allBookCount) - } else { - null + subtitle = when { + interaction.isEditMode -> { + context.getString(R.string.bookshelf_total_count, previews.allBookCount) + } + + internal.isSearchMode -> { + context.getString(R.string.bookshelf_total_count, filteredBooks.size) + } + + else -> null }, currentGroupName = currentGroupName, draggingBooks = interaction.draggingBooks?.toImmutableList(), diff --git a/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt b/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt index c6a560eb1..504b0634a 100644 --- a/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt +++ b/app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt @@ -86,7 +86,8 @@ fun adaptiveContentPaddingBookshelf( horizontal: Dp ): PaddingValues { val adjustedTop = if (ThemeResolver.isMiuixEngine(composeEngine)) top + 12.dp else top + 8.dp - val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp + horizontal else 4.dp + horizontal + val horizontal = + if (ThemeResolver.isMiuixEngine(composeEngine)) 6.dp + horizontal else 4.dp + horizontal return PaddingValues( top = adjustedTop, bottom = bottom, diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt index d33333e46..4bb659a67 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/button/SmallIconButton.kt @@ -32,10 +32,10 @@ import androidx.compose.ui.unit.dp import io.legado.app.ui.theme.LegadoTheme import io.legado.app.ui.theme.LegadoTheme.composeEngine import io.legado.app.ui.theme.ThemeResolver +import top.yukonga.miuix.kmp.theme.MiuixTheme import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton import top.yukonga.miuix.kmp.basic.Text as MiuixText -import top.yukonga.miuix.kmp.theme.MiuixTheme private val SmallMiuixButtonSize = 32.dp private val SmallMiuixIconSize = 18.dp @@ -74,7 +74,7 @@ fun SmallIconButton( MiuixIcon( imageVector = imageVector, contentDescription = contentDescription, - modifier = Modifier.size(smallIconSize), + modifier = Modifier.size(16.dp), ) } } else { diff --git a/app/src/main/java/io/legado/app/ui/widget/components/button/SmallTextButton.kt b/app/src/main/java/io/legado/app/ui/widget/components/button/SmallTextButton.kt index a67fd5221..3aa34c830 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/button/SmallTextButton.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/button/SmallTextButton.kt @@ -4,8 +4,6 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width @@ -24,8 +22,6 @@ import io.legado.app.ui.theme.ThemeResolver import io.legado.app.ui.widget.components.text.AppText import top.yukonga.miuix.kmp.basic.Card import top.yukonga.miuix.kmp.basic.CardDefaults -import top.yukonga.miuix.kmp.basic.Button as MiuixButton -import top.yukonga.miuix.kmp.basic.ButtonDefaults as MiuixButtonDefaults import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon @Composable @@ -85,7 +81,7 @@ fun SmallTextButton( @Composable fun SmallTonalTextButton( - text: String, + text: String? = null, imageVector: ImageVector, modifier: Modifier = Modifier, onClick: () -> Unit @@ -111,10 +107,12 @@ fun SmallTonalTextButton( contentDescription = null, modifier = Modifier.size(16.dp) ) - AppText( - text = text, - style = LegadoTheme.typography.labelMedium - ) + if (text != null) { + AppText( + text = text, + style = LegadoTheme.typography.labelMedium + ) + } } } } else { @@ -129,10 +127,12 @@ fun SmallTonalTextButton( modifier = Modifier.size(16.dp) ) Spacer(Modifier.width(4.dp)) - AppText( - text = text, - style = LegadoTheme.typography.labelMedium - ) + if (text != null) { + AppText( + text = text, + style = LegadoTheme.typography.labelMedium + ) + } } } } diff --git a/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt b/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt index fefb7b05e..26d1645cb 100644 --- a/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt +++ b/app/src/main/java/io/legado/app/ui/widget/components/modalBottomSheet/AppModalBottomSheet.kt @@ -19,9 +19,12 @@ import androidx.compose.material3.Typography import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.text.style.TextAlign @@ -165,3 +168,39 @@ fun AppModalBottomSheet( } } } + +/** + * 专为 nullable 数据设计的 AppModalBottomSheet 重载。 + * 当 [data] 不为 null 时显示弹窗;当 [data] 变为 null 时,自动缓存最后一次数据并播放退出动画。 + */ +@Composable +fun AppModalBottomSheet( + data: T?, + onDismissRequest: () -> Unit, + modifier: Modifier = Modifier, + title: String? = null, + startAction: @Composable (() -> Unit)? = null, + endAction: @Composable (() -> Unit)? = null, + content: @Composable ColumnScope.(T) -> Unit +) { + var cachedData by remember { mutableStateOf(data) } + + if (data != null) { + cachedData = data + } + + val currentData = cachedData + AppModalBottomSheet( + show = data != null, + onDismissRequest = onDismissRequest, + modifier = modifier, + title = title, + startAction = startAction, + endAction = endAction, + content = { + if (currentData != null) { + content(currentData) + } + } + ) +} diff --git a/app/version.properties b/app/version.properties index 270eb3383..17bf4b4e5 100644 --- a/app/version.properties +++ b/app/version.properties @@ -1,5 +1,5 @@ VERSION_MAJOR=3 VERSION_MINOR=26 VERSION_PATCH=11 -VERSION_SUFFIX=1 +VERSION_SUFFIX=0 # 1 = Pre, 0 = Release \ No newline at end of file