HapeLee 5 месяцев назад
Родитель
Сommit
5987e3414c

+ 38 - 9
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.Image
 import androidx.compose.material.icons.outlined.Settings
 import androidx.compose.material.icons.outlined.Settings
 import androidx.compose.material3.Icon
 import androidx.compose.material3.Icon
-import io.legado.app.ui.widget.components.progressIndicator.AppCircularProgressIndicator
 import androidx.compose.material3.Text
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.mutableLongStateOf
 import androidx.compose.runtime.mutableLongStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.remember
@@ -48,10 +46,8 @@ 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.draw.clip
-import androidx.compose.ui.geometry.CornerRadius
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.stringResource
-import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
 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.edit.BookSourceEditActivity
 import io.legado.app.ui.book.source.manage.BookSourceActivity
 import io.legado.app.ui.book.source.manage.BookSourceActivity
 import io.legado.app.ui.theme.LegadoTheme
 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.AppTextField
 import io.legado.app.ui.widget.components.EmptyMessage
 import io.legado.app.ui.widget.components.EmptyMessage
 import io.legado.app.ui.widget.components.alert.AppAlertDialog
 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.RoundDropdownMenu
 import io.legado.app.ui.widget.components.menuItem.RoundDropdownMenuItem
 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.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.ui.widget.components.text.AppText
 import io.legado.app.utils.StartActivityContract
 import io.legado.app.utils.StartActivityContract
 import io.legado.app.utils.startActivity
 import io.legado.app.utils.startActivity
@@ -103,7 +99,11 @@ fun WebFileSheet(
 ) {
 ) {
     AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest, title = title) {
     AppModalBottomSheet(show = show, onDismissRequest = onDismissRequest, title = title) {
         if (files.isEmpty()) {
         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))
                 Text(text = stringResource(R.string.empty))
             }
             }
         } else {
         } else {
@@ -111,7 +111,9 @@ fun WebFileSheet(
                 items(files, key = { it.name }) { file ->
                 items(files, key = { it.name }) { file ->
                     GlassCard(onClick = { onSelect(file) }) {
                     GlassCard(onClick = { onSelect(file) }) {
                         Row(
                         Row(
-                            modifier = Modifier.fillMaxWidth().padding(16.dp),
+                            modifier = Modifier
+                                .fillMaxWidth()
+                                .padding(16.dp),
                             horizontalArrangement = Arrangement.spacedBy(12.dp),
                             horizontalArrangement = Arrangement.spacedBy(12.dp),
                             verticalAlignment = Alignment.CenterVertically,
                             verticalAlignment = Alignment.CenterVertically,
                         ) {
                         ) {
@@ -269,6 +271,31 @@ fun ChangeCoverSheet(
     }
     }
 }
 }
 
 
+@Composable
+fun ChangeSourceSheet(
+    data: Book?,
+    onDismissRequest: () -> Unit,
+    onReplace: (BookSource, Book, List<BookChapter>, ChangeSourceMigrationOptions) -> Unit,
+    onAddAsNew: (Book, List<BookChapter>) -> 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
 @Composable
 fun ChangeSourceSheet(
 fun ChangeSourceSheet(
     show: Boolean,
     show: Boolean,
@@ -426,7 +453,9 @@ fun ChangeSourceSheet(
 
 
         if (items.isEmpty()) {
         if (items.isEmpty()) {
             Box(
             Box(
-                modifier = Modifier.fillMaxWidth().padding(vertical = 40.dp),
+                modifier = Modifier
+                    .fillMaxWidth()
+                    .padding(vertical = 40.dp),
                 contentAlignment = Alignment.Center
                 contentAlignment = Alignment.Center
             ) {
             ) {
                 EmptyMessage(
                 EmptyMessage(

+ 94 - 97
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.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ExperimentalLayoutApi
 import androidx.compose.foundation.layout.ExperimentalLayoutApi
-import androidx.compose.foundation.layout.FlowRow
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxSize
 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.Icons
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Bookmarks
 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.Delete
 import androidx.compose.material.icons.filled.Download
 import androidx.compose.material.icons.filled.Download
 import androidx.compose.material.icons.filled.Info
 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.Refresh
 import androidx.compose.material.icons.filled.Search
 import androidx.compose.material.icons.filled.Search
 import androidx.compose.material.icons.filled.SelectAll
 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.Stop
 import androidx.compose.material.icons.filled.Upload
 import androidx.compose.material.icons.filled.Upload
 import androidx.compose.material3.Checkbox
 import androidx.compose.material3.Checkbox
@@ -54,16 +53,19 @@ import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import io.legado.app.R
 import io.legado.app.R
 import io.legado.app.constant.IntentAction
 import io.legado.app.constant.IntentAction
 import io.legado.app.data.appDb
 import io.legado.app.data.appDb
 import io.legado.app.data.entities.Book
 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.BookSource
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.data.entities.BookSourcePart
 import io.legado.app.domain.usecase.BatchChangeSourcePreviewItem
 import io.legado.app.domain.usecase.BatchChangeSourcePreviewItem
 import io.legado.app.domain.usecase.BatchChangeSourcePreviewStatus
 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.getExportFileName
 import io.legado.app.help.book.isLocal
 import io.legado.app.help.book.isLocal
 import io.legado.app.help.book.tryParesExportFileName
 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.AppTextField
 import io.legado.app.ui.widget.components.FabMenuItem
 import io.legado.app.ui.widget.components.FabMenuItem
 import io.legado.app.ui.widget.components.alert.AppAlertDialog
 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.SmallTonalIconButton
 import io.legado.app.ui.widget.components.button.SmallTonalTextButton
 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.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.ReorderableSelectionItem
 import io.legado.app.ui.widget.components.card.SelectionItemCard
 import io.legado.app.ui.widget.components.card.SelectionItemCard
 import io.legado.app.ui.widget.components.card.TextCard
 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<BookChapter>, 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<BookChapter> ->
+            context.toastOnUi("请在书籍详情页添加为新书")
+        },
+    )
 
 
     BookSourcePickerSheet(
     BookSourcePickerSheet(
         show = showBatchSourcePickerSheet,
         show = showBatchSourcePickerSheet,
@@ -857,8 +854,7 @@ private fun BookshelfManageScreen(
     )
     )
 
 
     BatchChangePreviewSheet(
     BatchChangePreviewSheet(
-        show = state.batchChangePreviewItems.isNotEmpty(),
-        items = state.batchChangePreviewItems,
+        data = state.batchChangePreviewItems.takeIf { it.isNotEmpty() },
         onDismissRequest = { viewModel.dispatch(BookshelfManageScreenIntent.DismissBatchChangePreview) },
         onDismissRequest = { viewModel.dispatch(BookshelfManageScreenIntent.DismissBatchChangePreview) },
         onOpenBook = { book, inBookshelf ->
         onOpenBook = { book, inBookshelf ->
             viewModel.dispatch(BookshelfManageScreenIntent.OpenBookInfoPreview(book, inBookshelf))
             viewModel.dispatch(BookshelfManageScreenIntent.OpenBookInfoPreview(book, inBookshelf))
@@ -871,29 +867,27 @@ private fun BookshelfManageScreen(
         },
         },
         onShowOtherSources = { item -> otherSourcePreviewItem = item },
         onShowOtherSources = { item -> otherSourcePreviewItem = item },
         onMigrateAll = { viewModel.dispatch(BookshelfManageScreenIntent.MigrateAllPreviewItems) },
         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<BookChapter>, _ ->
+            viewModel.dispatch(
+                BookshelfManageScreenIntent.UpdatePreviewItem(
+                    oldBookUrl = manualSearchPreviewBook?.bookUrl ?: "",
+                    source = source,
+                    book = newBook,
+                    chapterCount = toc.size,
                 )
                 )
-                manualSearchPreviewBook = null
-            },
-            onAddAsNew = { _, _ ->
-                context.toastOnUi("请先选择替换候选后再新增至书架")
-            },
-        )
-    }
+            )
+            manualSearchPreviewBook = null
+        },
+        onAddAsNew = { _: Book, _: List<BookChapter> ->
+            context.toastOnUi("请先选择替换候选后再新增至书架")
+        },
+    )
 
 
     OtherSourceOptionsSheet(
     OtherSourceOptionsSheet(
         item = otherSourcePreviewItem,
         item = otherSourcePreviewItem,
@@ -1392,8 +1386,7 @@ private fun SourcePickerItem(
 
 
 @Composable
 @Composable
 private fun BatchChangePreviewSheet(
 private fun BatchChangePreviewSheet(
-    show: Boolean,
-    items: List<BatchChangeSourcePreviewItem>,
+    data: List<BatchChangeSourcePreviewItem>?,
     onDismissRequest: () -> Unit,
     onDismissRequest: () -> Unit,
     onOpenBook: (Book, Boolean) -> Unit,
     onOpenBook: (Book, Boolean) -> Unit,
     onManualSearch: (Book) -> Unit,
     onManualSearch: (Book) -> Unit,
@@ -1402,11 +1395,19 @@ private fun BatchChangePreviewSheet(
     onAddToShelf: (String) -> Unit,
     onAddToShelf: (String) -> Unit,
     onShowOtherSources: (BatchChangeSourcePreviewItem) -> Unit,
     onShowOtherSources: (BatchChangeSourcePreviewItem) -> Unit,
     onMigrateAll: () -> Unit,
     onMigrateAll: () -> Unit,
+    onAddAllToShelf: () -> Unit,
 ) {
 ) {
     AppModalBottomSheet(
     AppModalBottomSheet(
-        show = show,
+        data = data,
         onDismissRequest = onDismissRequest,
         onDismissRequest = onDismissRequest,
         title = "批量换源预览",
         title = "批量换源预览",
+        startAction = {
+            SmallTonalTextButton(
+                text = "新增全部",
+                imageVector = Icons.Default.Add,
+                onClick = onAddAllToShelf,
+            )
+        },
         endAction = {
         endAction = {
             SmallTonalTextButton(
             SmallTonalTextButton(
                 text = "迁移全部",
                 text = "迁移全部",
@@ -1414,7 +1415,7 @@ private fun BatchChangePreviewSheet(
                 onClick = onMigrateAll,
                 onClick = onMigrateAll,
             )
             )
         }
         }
-    ) {
+    ) { items ->
         LazyColumn(
         LazyColumn(
             modifier = Modifier
             modifier = Modifier
                 .fillMaxWidth()
                 .fillMaxWidth()
@@ -1449,15 +1450,15 @@ private fun BatchChangePreviewRow(
     onShowOtherSources: (BatchChangeSourcePreviewItem) -> Unit,
     onShowOtherSources: (BatchChangeSourcePreviewItem) -> Unit,
 ) {
 ) {
     val candidate = item.selectedCandidate
     val candidate = item.selectedCandidate
-    NormalCard(
+    GlassCard(
         modifier = Modifier.fillMaxWidth(),
         modifier = Modifier.fillMaxWidth(),
-        containerColor = LegadoTheme.colorScheme.surfaceContainerLow,
+        containerColor = LegadoTheme.colorScheme.onSheetContent,
     ) {
     ) {
         Column(
         Column(
             modifier = Modifier
             modifier = Modifier
                 .fillMaxWidth()
                 .fillMaxWidth()
                 .padding(12.dp),
                 .padding(12.dp),
-            verticalArrangement = Arrangement.spacedBy(10.dp),
+            verticalArrangement = Arrangement.spacedBy(12.dp),
         ) {
         ) {
             Row(
             Row(
                 modifier = Modifier.fillMaxWidth(),
                 modifier = Modifier.fillMaxWidth(),
@@ -1493,27 +1494,26 @@ private fun BatchChangePreviewRow(
                     modifier = Modifier.weight(1f),
                     modifier = Modifier.weight(1f),
                 )
                 )
             }
             }
-            FlowRow(
+            Row(
                 modifier = Modifier.fillMaxWidth(),
                 modifier = Modifier.fillMaxWidth(),
-                horizontalArrangement = Arrangement.spacedBy(8.dp),
-                verticalArrangement = Arrangement.spacedBy(8.dp),
+                horizontalArrangement = Arrangement.SpaceBetween
             ) {
             ) {
-                MediumIconButton(
+                SmallTonalTextButton(
                     imageVector = Icons.Default.Search,
                     imageVector = Icons.Default.Search,
                     onClick = { onManualSearch(item.oldBook) },
                     onClick = { onManualSearch(item.oldBook) },
                 )
                 )
                 SmallTonalTextButton(
                 SmallTonalTextButton(
                     text = "不迁移",
                     text = "不迁移",
-                    imageVector = Icons.Default.SkipNext,
+                    imageVector = Icons.Default.Clear,
                     onClick = { onSkip(item.oldBook.bookUrl) },
                     onClick = { onSkip(item.oldBook.bookUrl) },
                 )
                 )
                 SmallTonalTextButton(
                 SmallTonalTextButton(
-                    text = "开始迁移",
+                    text = "迁移",
                     imageVector = Icons.Default.PlayArrow,
                     imageVector = Icons.Default.PlayArrow,
                     onClick = { onMigrate(item.oldBook.bookUrl) },
                     onClick = { onMigrate(item.oldBook.bookUrl) },
                 )
                 )
                 SmallTonalTextButton(
                 SmallTonalTextButton(
-                    text = "新增至书架",
+                    text = "新增",
                     imageVector = Icons.Default.Add,
                     imageVector = Icons.Default.Add,
                     onClick = { onAddToShelf(item.oldBook.bookUrl) },
                     onClick = { onAddToShelf(item.oldBook.bookUrl) },
                 )
                 )
@@ -1553,7 +1553,8 @@ private fun PreviewBookInfo(
             AppText(
             AppText(
                 text = book.name,
                 text = book.name,
                 style = LegadoTheme.typography.labelMedium,
                 style = LegadoTheme.typography.labelMedium,
-                maxLines = 1,
+                maxLines = 2,
+                overflow = TextOverflow.Ellipsis
             )
             )
             AppText(
             AppText(
                 text = "${book.getRealAuthor()} · ${chapterCount ?: 0}章",
                 text = "${book.getRealAuthor()} · ${chapterCount ?: 0}章",
@@ -1566,8 +1567,7 @@ private fun PreviewBookInfo(
             AppText(
             AppText(
                 text = title,
                 text = title,
                 style = LegadoTheme.typography.labelMedium,
                 style = LegadoTheme.typography.labelMedium,
-                color = LegadoTheme.colorScheme.error,
-                maxLines = 2,
+                color = LegadoTheme.colorScheme.error
             )
             )
         }
         }
     }
     }
@@ -1581,41 +1581,38 @@ private fun OtherSourceOptionsSheet(
     onOpenBook: (Book) -> Unit,
     onOpenBook: (Book) -> Unit,
 ) {
 ) {
     AppModalBottomSheet(
     AppModalBottomSheet(
-        show = item != null,
+        data = item,
         onDismissRequest = onDismissRequest,
         onDismissRequest = onDismissRequest,
         title = "其他源信息",
         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))
         Spacer(modifier = Modifier.height(16.dp))

+ 48 - 7
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 android.app.Application
 import androidx.lifecycle.viewModelScope
 import androidx.lifecycle.viewModelScope
 import io.legado.app.base.BaseViewModel
 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.BookChapterDao
 import io.legado.app.data.dao.BookDao
 import io.legado.app.data.dao.BookDao
 import io.legado.app.data.dao.BookGroupDao
 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.Book
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookChapter
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookGroup
 import io.legado.app.data.entities.BookSource
 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.BatchChangeSourceCandidate
 import io.legado.app.domain.usecase.BatchChangeSourcePreviewItem
 import io.legado.app.domain.usecase.BatchChangeSourcePreviewItem
 import io.legado.app.domain.usecase.BatchChangeSourcePreviewStatus
 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.CacheBookChaptersUseCase
 import io.legado.app.domain.usecase.ChangeBookSourceUseCase
 import io.legado.app.domain.usecase.ChangeBookSourceUseCase
 import io.legado.app.domain.usecase.ChangeSourceMigrationOptions
 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.DeleteBooksUseCase
 import io.legado.app.domain.usecase.UpdateBooksGroupUseCase
 import io.legado.app.domain.usecase.UpdateBooksGroupUseCase
 import io.legado.app.help.book.BookHelp
 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.isLocal
 import io.legado.app.help.book.removeType
 import io.legado.app.help.book.removeType
-import io.legado.app.model.CacheBook
 import io.legado.app.help.config.LocalConfig
 import io.legado.app.help.config.LocalConfig
+import io.legado.app.model.CacheBook
 import io.legado.app.service.ExportBookService
 import io.legado.app.service.ExportBookService
 import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
 import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
 import io.legado.app.ui.config.bookshelfConfig.BookshelfManageScreenConfig
 import io.legado.app.ui.config.bookshelfConfig.BookshelfManageScreenConfig
@@ -39,17 +38,17 @@ import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.ensureActive
 import kotlinx.coroutines.ensureActive
-import kotlinx.coroutines.isActive
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.isActive
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
+import java.util.concurrent.ConcurrentHashMap
 import kotlin.math.max
 import kotlin.math.max
 import kotlin.math.min
 import kotlin.math.min
-import java.util.concurrent.ConcurrentHashMap
 
 
 data class BookshelfManageScreenExportConfig(
 data class BookshelfManageScreenExportConfig(
     val exportUseReplace: Boolean = true,
     val exportUseReplace: Boolean = true,
@@ -123,6 +122,7 @@ sealed interface BookshelfManageScreenIntent {
     data class AddPreviewItemToShelf(val oldBookUrl: String) : BookshelfManageScreenIntent
     data class AddPreviewItemToShelf(val oldBookUrl: String) : BookshelfManageScreenIntent
     data class OpenBookInfoPreview(val book: Book, val inBookshelf: Boolean) : BookshelfManageScreenIntent
     data class OpenBookInfoPreview(val book: Book, val inBookshelf: Boolean) : BookshelfManageScreenIntent
     data object MigrateAllPreviewItems : BookshelfManageScreenIntent
     data object MigrateAllPreviewItems : BookshelfManageScreenIntent
+    data object AddAllPreviewItemsToShelf : BookshelfManageScreenIntent
     data object DismissChangeSourceStatus : BookshelfManageScreenIntent
     data object DismissChangeSourceStatus : BookshelfManageScreenIntent
     data object DismissBatchChangePreview : BookshelfManageScreenIntent
     data object DismissBatchChangePreview : BookshelfManageScreenIntent
     data class SetExportUseReplace(val enabled: Boolean) : BookshelfManageScreenIntent
     data class SetExportUseReplace(val enabled: Boolean) : BookshelfManageScreenIntent
@@ -233,6 +233,8 @@ class BookshelfManageScreenViewModel(
 
 
             BookshelfManageScreenIntent.MigrateAllPreviewItems -> migrateAllPreviewItems()
             BookshelfManageScreenIntent.MigrateAllPreviewItems -> migrateAllPreviewItems()
 
 
+            BookshelfManageScreenIntent.AddAllPreviewItemsToShelf -> addAllPreviewItemsToShelf()
+
             BookshelfManageScreenIntent.DismissChangeSourceStatus -> {
             BookshelfManageScreenIntent.DismissChangeSourceStatus -> {
                 _uiState.update {
                 _uiState.update {
                     it.copy(
                     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) {
     private fun clearCacheForBook(book: Book) {
         execute {
         execute {
             clearBookCacheUseCase.execute(book.bookUrl)
             clearBookCacheUseCase.execute(book.bookUrl)

+ 9 - 10
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,
                         textAlign = if (titleCenter) TextAlign.Center else TextAlign.Start,
                         modifier = Modifier
                         modifier = Modifier
                             .fillMaxWidth()
                             .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(
             NormalCard(
                 modifier = modifier
                 modifier = modifier
                     .fillMaxWidth()
                     .fillMaxWidth()
-                    .padding(all = 4.dp),
+                    .padding(vertical = 4.dp),
                 cornerRadius = 8.dp,
                 cornerRadius = 8.dp,
                 containerColor = if (isSelected) {
                 containerColor = if (isSelected) {
                     LegadoTheme.colorScheme.secondaryContainer
                     LegadoTheme.colorScheme.secondaryContainer
@@ -194,7 +194,6 @@ fun BookshelfItem(
                         modifier = Modifier
                         modifier = Modifier
                             .align(Alignment.Top)
                             .align(Alignment.Top)
                             .width(if (!isCompact) 84.dp else 56.dp)
                             .width(if (!isCompact) 84.dp else 56.dp)
-                            .padding(end = 8.dp)
                     ) {
                     ) {
                         Box(
                         Box(
                             modifier = Modifier
                             modifier = Modifier
@@ -215,7 +214,7 @@ fun BookshelfItem(
                     Column(
                     Column(
                         modifier = Modifier
                         modifier = Modifier
                             .weight(1f)
                             .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
                         verticalArrangement = Arrangement.Center
                     ) {
                     ) {
                     Row(
                     Row(
@@ -524,13 +523,13 @@ fun BookGroupItemHorizontalCovers(
         ) {
         ) {
             Column(
             Column(
                 modifier = Modifier
                 modifier = Modifier
-                    .padding(vertical = 12.dp),
+                    .padding(vertical = 4.dp),
                 verticalArrangement = Arrangement.spacedBy(8.dp)
                 verticalArrangement = Arrangement.spacedBy(8.dp)
             ) {
             ) {
                 Row(
                 Row(
                     modifier = Modifier
                     modifier = Modifier
                         .fillMaxWidth()
                         .fillMaxWidth()
-                        .padding(start = 12.dp, top = 4.dp, bottom = 4.dp),
+                        .padding(start = 4.dp, top = 4.dp, bottom = 4.dp),
                     horizontalArrangement = Arrangement.SpaceBetween,
                     horizontalArrangement = Arrangement.SpaceBetween,
                     verticalAlignment = Alignment.CenterVertically
                     verticalAlignment = Alignment.CenterVertically
                 ) {
                 ) {
@@ -549,7 +548,7 @@ fun BookGroupItemHorizontalCovers(
                         )
                         )
                     }
                     }
                     AppIcon(
                     AppIcon(
-                        modifier = Modifier.padding(end = 6.dp),
+                        modifier = Modifier.padding(end = 4.dp),
                         imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
                         imageVector = Icons.AutoMirrored.Filled.KeyboardArrowRight,
                         contentDescription = "",
                         contentDescription = "",
                         tint = LegadoTheme.colorScheme.onSurfaceVariant
                         tint = LegadoTheme.colorScheme.onSurfaceVariant
@@ -558,8 +557,8 @@ fun BookGroupItemHorizontalCovers(
                 Row(
                 Row(
                     modifier = Modifier
                     modifier = Modifier
                         .fillMaxWidth()
                         .fillMaxWidth()
-                        .padding(horizontal = 12.dp),
-                    horizontalArrangement = Arrangement.spacedBy(8.dp)
+                        .padding(horizontal = 4.dp),
+                    horizontalArrangement = Arrangement.spacedBy(4.dp)
                 ) {
                 ) {
                     val coverCount = BookshelfConfig.bookshelfGroupCoverCount
                     val coverCount = BookshelfConfig.bookshelfGroupCoverCount
                     previewBooks.take(coverCount).forEach { bookUi ->
                     previewBooks.take(coverCount).forEach { bookUi ->

+ 2 - 2
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfScreen.kt

@@ -781,7 +781,7 @@ fun BookshelfScreen(
                         contentPadding = adaptiveContentPaddingBookshelf(
                         contentPadding = adaptiveContentPaddingBookshelf(
                             top = paddingValues.calculateTopPadding(),
                             top = paddingValues.calculateTopPadding(),
                             bottom = if (ThemeConfig.useFloatingBottomBar || ThemeConfig.enableBlur) 120.dp else 8.dp,
                             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),
                         verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
                         horizontalArrangement = 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(
             contentPadding = adaptiveContentPaddingBookshelf(
                 top = paddingValues.calculateTopPadding(),
                 top = paddingValues.calculateTopPadding(),
                 bottom = if (ThemeConfig.useFloatingBottomBar || ThemeConfig.enableBlur) 120.dp else 8.dp,
                 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),
             verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
             horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
             horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),

+ 34 - 14
app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt

@@ -175,6 +175,13 @@ class BookshelfViewModel(
         val allBookCount: Int
         val allBookCount: Int
     )
     )
 
 
+    private data class DataForPreviews(
+        val groups: List<BookGroup>,
+        val bookGroupStyle: Int,
+        val systemCountsMap: Map<Long, Int>,
+        val allBookCount: Int
+    )
+
     val groupSelectorState: StateFlow<BookshelfGroupSelectorState> = combine(
     val groupSelectorState: StateFlow<BookshelfGroupSelectorState> = combine(
         groupsFlow,
         groupsFlow,
         groupIdFlow
         groupIdFlow
@@ -262,16 +269,25 @@ class BookshelfViewModel(
     private val groupPreviewsFlow = combine(
     private val groupPreviewsFlow = combine(
         groupsFlow,
         groupsFlow,
         bookGroupStyleFlow,
         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) {
         if (bookGroupStyle !in 2..3) {
-            appDb.bookDao.flowAllBookShelfCount().map { count ->
-                GroupPreviewState(persistentMapOf(), persistentMapOf(), count)
-            }
+            flowOf(GroupPreviewState(persistentMapOf(), persistentMapOf(), allBookCount))
         } else if (groups.isEmpty()) {
         } else if (groups.isEmpty()) {
-            flowOf(GroupPreviewState(persistentMapOf(), persistentMapOf(), 0))
+            flowOf(GroupPreviewState(persistentMapOf(), persistentMapOf(), allBookCount))
         } else {
         } else {
             val groupFlows = groups.map { group ->
             val groupFlows = groups.map { group ->
                 val countFlow: Flow<Int> = if (group.groupId > 0) {
                 val countFlow: Flow<Int> = if (group.groupId > 0) {
@@ -287,11 +303,9 @@ class BookshelfViewModel(
             combine(groupFlows) { results ->
             combine(groupFlows) { results ->
                 var previews = persistentMapOf<Long, ImmutableList<BookUiItem>>()
                 var previews = persistentMapOf<Long, ImmutableList<BookUiItem>>()
                 var counts = persistentMapOf<Long, Int>()
                 var counts = persistentMapOf<Long, Int>()
-                var allBookCount = 0
                 results.forEach { (groupId, count, preview) ->
                 results.forEach { (groupId, count, preview) ->
                     counts = counts.put(groupId, count)
                     counts = counts.put(groupId, count)
                     previews = previews.put(groupId, preview.toImmutableList())
                     previews = previews.put(groupId, preview.toImmutableList())
-                    if (groupId == BookGroup.IdAll) allBookCount = count
                 }
                 }
                 GroupPreviewState(previews, counts, allBookCount)
                 GroupPreviewState(previews, counts, allBookCount)
             }
             }
@@ -480,10 +494,16 @@ class BookshelfViewModel(
             bookshelfSort = internal.sortConfig.sort,
             bookshelfSort = internal.sortConfig.sort,
             bookshelfSortOrder = internal.sortConfig.sortOrder,
             bookshelfSortOrder = internal.sortConfig.sortOrder,
             title = title,
             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,
             currentGroupName = currentGroupName,
             draggingBooks = interaction.draggingBooks?.toImmutableList(),
             draggingBooks = interaction.draggingBooks?.toImmutableList(),

+ 2 - 1
app/src/main/java/io/legado/app/ui/theme/AdaptivePadding.kt

@@ -86,7 +86,8 @@ fun adaptiveContentPaddingBookshelf(
     horizontal: Dp
     horizontal: Dp
 ): PaddingValues {
 ): PaddingValues {
     val adjustedTop = if (ThemeResolver.isMiuixEngine(composeEngine)) top + 12.dp else top + 8.dp
     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(
     return PaddingValues(
         top = adjustedTop,
         top = adjustedTop,
         bottom = bottom,
         bottom = bottom,

+ 2 - 2
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
 import io.legado.app.ui.theme.LegadoTheme.composeEngine
 import io.legado.app.ui.theme.LegadoTheme.composeEngine
 import io.legado.app.ui.theme.ThemeResolver
 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.Icon as MiuixIcon
 import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
 import top.yukonga.miuix.kmp.basic.IconButton as MiuixIconButton
 import top.yukonga.miuix.kmp.basic.Text as MiuixText
 import top.yukonga.miuix.kmp.basic.Text as MiuixText
-import top.yukonga.miuix.kmp.theme.MiuixTheme
 
 
 private val SmallMiuixButtonSize = 32.dp
 private val SmallMiuixButtonSize = 32.dp
 private val SmallMiuixIconSize = 18.dp
 private val SmallMiuixIconSize = 18.dp
@@ -74,7 +74,7 @@ fun SmallIconButton(
             MiuixIcon(
             MiuixIcon(
                 imageVector = imageVector,
                 imageVector = imageVector,
                 contentDescription = contentDescription,
                 contentDescription = contentDescription,
-                modifier = Modifier.size(smallIconSize),
+                modifier = Modifier.size(16.dp),
             )
             )
         }
         }
     } else {
     } else {

+ 13 - 13
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.PaddingValues
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 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.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
 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 io.legado.app.ui.widget.components.text.AppText
 import top.yukonga.miuix.kmp.basic.Card
 import top.yukonga.miuix.kmp.basic.Card
 import top.yukonga.miuix.kmp.basic.CardDefaults
 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
 import top.yukonga.miuix.kmp.basic.Icon as MiuixIcon
 
 
 @Composable
 @Composable
@@ -85,7 +81,7 @@ fun SmallTextButton(
 
 
 @Composable
 @Composable
 fun SmallTonalTextButton(
 fun SmallTonalTextButton(
-    text: String,
+    text: String? = null,
     imageVector: ImageVector,
     imageVector: ImageVector,
     modifier: Modifier = Modifier,
     modifier: Modifier = Modifier,
     onClick: () -> Unit
     onClick: () -> Unit
@@ -111,10 +107,12 @@ fun SmallTonalTextButton(
                     contentDescription = null,
                     contentDescription = null,
                     modifier = Modifier.size(16.dp)
                     modifier = Modifier.size(16.dp)
                 )
                 )
-                AppText(
-                    text = text,
-                    style = LegadoTheme.typography.labelMedium
-                )
+                if (text != null) {
+                    AppText(
+                        text = text,
+                        style = LegadoTheme.typography.labelMedium
+                    )
+                }
             }
             }
         }
         }
     } else {
     } else {
@@ -129,10 +127,12 @@ fun SmallTonalTextButton(
                 modifier = Modifier.size(16.dp)
                 modifier = Modifier.size(16.dp)
             )
             )
             Spacer(Modifier.width(4.dp))
             Spacer(Modifier.width(4.dp))
-            AppText(
-                text = text,
-                style = LegadoTheme.typography.labelMedium
-            )
+            if (text != null) {
+                AppText(
+                    text = text,
+                    style = LegadoTheme.typography.labelMedium
+                )
+            }
         }
         }
     }
     }
 }
 }

+ 40 - 1
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.material3.rememberModalBottomSheetState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 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.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalWindowInfo
 import androidx.compose.ui.platform.LocalWindowInfo
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.text.style.TextAlign
@@ -165,3 +168,39 @@ fun AppModalBottomSheet(
         }
         }
     }
     }
 }
 }
+
+/**
+ * 专为 nullable 数据设计的 AppModalBottomSheet 重载。
+ * 当 [data] 不为 null 时显示弹窗;当 [data] 变为 null 时,自动缓存最后一次数据并播放退出动画。
+ */
+@Composable
+fun <T> 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)
+            }
+        }
+    )
+}

+ 1 - 1
app/version.properties

@@ -1,5 +1,5 @@
 VERSION_MAJOR=3
 VERSION_MAJOR=3
 VERSION_MINOR=26
 VERSION_MINOR=26
 VERSION_PATCH=11
 VERSION_PATCH=11
-VERSION_SUFFIX=1
+VERSION_SUFFIX=0
 # 1 = Pre, 0 = Release
 # 1 = Pre, 0 = Release