界面优化
This commit is contained in:
@@ -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(
|
||||||
|
|||||||
@@ -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(
|
||||||
ChangeSourceSheet(
|
data = singleChangeSourceBook,
|
||||||
show = true,
|
onDismissRequest = { singleChangeSourceBook = null },
|
||||||
oldBook = book,
|
onReplace = { source: BookSource, newBook: Book, toc: List<BookChapter>, options: ChangeSourceMigrationOptions ->
|
||||||
onDismissRequest = { singleChangeSourceBook = null },
|
viewModel.dispatch(
|
||||||
onReplace = { source, newBook, toc, options ->
|
BookshelfManageScreenIntent.ChangeBookSource(
|
||||||
viewModel.dispatch(
|
oldBookUrl = singleChangeSourceBook?.bookUrl ?: "",
|
||||||
BookshelfManageScreenIntent.ChangeBookSource(
|
source = source,
|
||||||
oldBookUrl = book.bookUrl,
|
book = newBook,
|
||||||
source = source,
|
chapters = toc,
|
||||||
book = newBook,
|
options = options,
|
||||||
chapters = toc,
|
|
||||||
options = options,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
singleChangeSourceBook = null
|
)
|
||||||
},
|
singleChangeSourceBook = null
|
||||||
onAddAsNew = { _, _ ->
|
},
|
||||||
context.toastOnUi("请在书籍详情页添加为新书")
|
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(),
|
data = state.batchChangePreviewItems.takeIf { it.isNotEmpty() },
|
||||||
items = state.batchChangePreviewItems,
|
|
||||||
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(
|
||||||
ChangeSourceSheet(
|
data = manualSearchPreviewBook,
|
||||||
show = true,
|
onDismissRequest = { manualSearchPreviewBook = null },
|
||||||
oldBook = book,
|
onReplace = { source: BookSource, newBook: Book, toc: List<BookChapter>, _ ->
|
||||||
onDismissRequest = { manualSearchPreviewBook = null },
|
viewModel.dispatch(
|
||||||
onReplace = { source, newBook, toc, _ ->
|
BookshelfManageScreenIntent.UpdatePreviewItem(
|
||||||
viewModel.dispatch(
|
oldBookUrl = manualSearchPreviewBook?.bookUrl ?: "",
|
||||||
BookshelfManageScreenIntent.UpdatePreviewItem(
|
source = source,
|
||||||
oldBookUrl = book.bookUrl,
|
book = newBook,
|
||||||
source = source,
|
chapterCount = toc.size,
|
||||||
book = newBook,
|
|
||||||
chapterCount = toc.size,
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
manualSearchPreviewBook = null
|
)
|
||||||
},
|
manualSearchPreviewBook = null
|
||||||
onAddAsNew = { _, _ ->
|
},
|
||||||
context.toastOnUi("请先选择替换候选后再新增至书架")
|
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,
|
data: List<BatchChangeSourcePreviewItem>?,
|
||||||
items: 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),
|
horizontalArrangement = Arrangement.SpaceBetween
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
) {
|
) {
|
||||||
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,
|
color = LegadoTheme.colorScheme.error
|
||||||
maxLines = 2,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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 = "其他源信息",
|
||||||
) {
|
) { currentItem ->
|
||||||
val currentItem = item
|
LazyColumn(
|
||||||
if (currentItem != null) {
|
modifier = Modifier
|
||||||
LazyColumn(
|
.fillMaxWidth()
|
||||||
modifier = Modifier
|
.heightIn(max = 560.dp),
|
||||||
.fillMaxWidth()
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
.heightIn(max = 560.dp),
|
) {
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
items(currentItem.candidates.indices.toList(), key = { it }) { index ->
|
||||||
) {
|
val candidate = currentItem.candidates[index]
|
||||||
items(currentItem.candidates.indices.toList(), key = { it }) { index ->
|
SelectionItemCard(
|
||||||
val candidate = currentItem.candidates[index]
|
title = candidate.source.bookSourceName,
|
||||||
SelectionItemCard(
|
subtitle = "${candidate.book.name} · ${candidate.chapterCount}章",
|
||||||
title = candidate.source.bookSourceName,
|
supportingContent = {
|
||||||
subtitle = "${candidate.book.name} · ${candidate.chapterCount}章",
|
AppText(
|
||||||
supportingContent = {
|
text = candidate.book.getRealAuthor(),
|
||||||
AppText(
|
style = LegadoTheme.typography.bodySmall,
|
||||||
text = candidate.book.getRealAuthor(),
|
)
|
||||||
style = LegadoTheme.typography.bodySmall,
|
},
|
||||||
)
|
isSelected = index == currentItem.selectedCandidateIndex,
|
||||||
},
|
onToggleSelection = { onSelect(currentItem.oldBook.bookUrl, index) },
|
||||||
isSelected = index == currentItem.selectedCandidateIndex,
|
trailingAction = {
|
||||||
onToggleSelection = { onSelect(currentItem.oldBook.bookUrl, index) },
|
SmallTonalIconButton(
|
||||||
trailingAction = {
|
onClick = { onOpenBook(candidate.book) },
|
||||||
SmallTonalIconButton(
|
imageVector = Icons.Default.Info,
|
||||||
onClick = { onOpenBook(candidate.book) },
|
contentDescription = null,
|
||||||
imageVector = Icons.Default.Info,
|
)
|
||||||
contentDescription = null,
|
},
|
||||||
)
|
containerColor = LegadoTheme.colorScheme.onSheetContent,
|
||||||
},
|
)
|
||||||
containerColor = LegadoTheme.colorScheme.onSheetContent,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Spacer(modifier = Modifier.height(16.dp))
|
Spacer(modifier = Modifier.height(16.dp))
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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),
|
.padding(horizontal = 4.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
val coverCount = BookshelfConfig.bookshelfGroupCoverCount
|
val coverCount = BookshelfConfig.bookshelfGroupCoverCount
|
||||||
previewBooks.take(coverCount).forEach { bookUi ->
|
previewBooks.take(coverCount).forEach { bookUi ->
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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()
|
appDb.bookDao.flowSystemGroupCounts(),
|
||||||
) { groups, bookGroupStyle, systemCounts ->
|
appDb.bookDao.flowAllBookShelfCount()
|
||||||
Triple(groups, bookGroupStyle, systemCounts.associate { it.groupId to it.count })
|
) { groups, bookGroupStyle, systemCounts, totalCount ->
|
||||||
}.flatMapLatest { (groups, bookGroupStyle, systemCountsMap) ->
|
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 ->
|
flowOf(GroupPreviewState(persistentMapOf(), persistentMapOf(), allBookCount))
|
||||||
GroupPreviewState(persistentMapOf(), persistentMapOf(), count)
|
|
||||||
}
|
|
||||||
} 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) {
|
subtitle = when {
|
||||||
context.getString(R.string.bookshelf_total_count, previews.allBookCount)
|
interaction.isEditMode -> {
|
||||||
} else {
|
context.getString(R.string.bookshelf_total_count, previews.allBookCount)
|
||||||
null
|
}
|
||||||
|
|
||||||
|
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(),
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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(
|
if (text != null) {
|
||||||
text = text,
|
AppText(
|
||||||
style = LegadoTheme.typography.labelMedium
|
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(
|
if (text != null) {
|
||||||
text = text,
|
AppText(
|
||||||
style = LegadoTheme.typography.labelMedium
|
text = text,
|
||||||
)
|
style = LegadoTheme.typography.labelMedium
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-1
@@ -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,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
|
||||||
Reference in New Issue
Block a user