[优化] 优化书架界面,增加开关以控制搜索按钮优先打开搜索页还是筛选栏
This commit is contained in:
@@ -241,6 +241,7 @@ object PreferKey {
|
||||
const val bookshelfTitleCenter = "bookshelfTitleCenter"
|
||||
const val bookshelfTitleMaxLines = "bookshelfTitleMaxLines"
|
||||
const val bookshelfCoverShadow = "bookshelfCoverShadow"
|
||||
const val bookshelfSearchActionDirectToSearch = "bookshelfSearchActionDirectToSearch"
|
||||
|
||||
const val exploreLayoutGridLandscape = "exploreLayoutGridLandscape"
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ import io.legado.app.ui.book.bookmark.AllBookmarkViewModel
|
||||
import io.legado.app.ui.book.changecover.ChangeCoverViewModel
|
||||
import io.legado.app.ui.book.changesource.ChangeBookSourceComposeViewModel
|
||||
import io.legado.app.ui.book.changesource.ChangeBookSourceViewModel
|
||||
import io.legado.app.ui.book.cache.CacheViewModel
|
||||
import io.legado.app.ui.book.manage.BookshelfManageScreenViewModel
|
||||
import io.legado.app.ui.book.explore.ExploreShowViewModel
|
||||
import io.legado.app.ui.book.group.GroupViewModel
|
||||
import io.legado.app.ui.book.import.local.ImportBookViewModel
|
||||
@@ -72,7 +72,7 @@ import io.legado.app.ui.book.searchContent.SearchContentViewModel
|
||||
import io.legado.app.ui.book.toc.TocViewModel
|
||||
import io.legado.app.ui.book.toc.rule.TxtTocRuleViewModel
|
||||
import io.legado.app.ui.config.backupConfig.BackupConfigViewModel
|
||||
import io.legado.app.ui.config.cacheConfig.CacheConfig
|
||||
import io.legado.app.ui.config.bookshelfConfig.BookshelfManageScreenConfig
|
||||
import io.legado.app.ui.config.coverConfig.CoverConfigViewModel
|
||||
import io.legado.app.ui.config.otherConfig.OtherConfigViewModel
|
||||
import io.legado.app.ui.config.readConfig.ReadConfigViewModel
|
||||
@@ -124,7 +124,7 @@ val appModule = module {
|
||||
singleOf(::ResolveBookShelfStateUseCase)
|
||||
singleOf(::ShrinkDatabaseUseCase)
|
||||
singleOf(::WebDavBackupUseCase)
|
||||
singleOf(::CacheConfig)
|
||||
singleOf(::BookshelfManageScreenConfig)
|
||||
|
||||
single<UploadRepository> { DirectLinkUploadRepository() }
|
||||
single<AppStartupGateway> { AppStartupRepository(get()) }
|
||||
@@ -188,12 +188,12 @@ val appModule = module {
|
||||
viewModelOf(::RssViewModel)
|
||||
viewModelOf(::SearchViewModel)
|
||||
viewModel {
|
||||
CacheViewModel(
|
||||
BookshelfManageScreenViewModel(
|
||||
application = get(),
|
||||
bookDao = get(),
|
||||
bookGroupDao = get(),
|
||||
bookChapterDao = get(),
|
||||
cacheConfig = get(),
|
||||
bookshelfManageScreenConfig = get(),
|
||||
batchCacheDownloadUseCase = get(),
|
||||
cacheBookChaptersUseCase = get(),
|
||||
changeBookSourceUseCase = get(),
|
||||
|
||||
@@ -260,6 +260,7 @@ fun ChangeSourceSheet(
|
||||
var loadingAction by remember { mutableStateOf(false) }
|
||||
var showOptionsMenu by rememberSaveable { mutableStateOf(false) }
|
||||
var showFilterMenu by rememberSaveable { mutableStateOf(false) }
|
||||
val bookAddedToShelfText = stringResource(R.string.book_added_to_shelf)
|
||||
|
||||
val editSourceResult = rememberLauncherForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {
|
||||
val origin = it.data?.getStringExtra("origin") ?: return@rememberLauncherForActivityResult
|
||||
@@ -490,7 +491,7 @@ fun ChangeSourceSheet(
|
||||
pendingMigration = PendingSourceMigration(source, book, toc)
|
||||
} else {
|
||||
onAddAsNew(book, toc)
|
||||
context.toastOnUi(context.getString(R.string.book_added_to_shelf))
|
||||
context.toastOnUi(bookAddedToShelfText)
|
||||
}
|
||||
actionBook = null
|
||||
}, {
|
||||
|
||||
+188
-157
@@ -1,4 +1,4 @@
|
||||
package io.legado.app.ui.book.cache
|
||||
package io.legado.app.ui.book.manage
|
||||
|
||||
import androidx.compose.ui.focus.focusRequester
|
||||
import androidx.activity.compose.BackHandler
|
||||
@@ -120,15 +120,16 @@ import io.legado.app.utils.verificationField
|
||||
import io.legado.app.ui.theme.adaptiveHorizontalPadding
|
||||
import io.legado.app.ui.widget.components.button.MediumIconButton
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import sh.calvin.reorderable.ReorderableItem
|
||||
import sh.calvin.reorderable.rememberReorderableLazyListState
|
||||
|
||||
data class CacheFabAction(
|
||||
data class BookshelfManageFabAction(
|
||||
val icon: androidx.compose.ui.graphics.vector.ImageVector,
|
||||
val label: String,
|
||||
val action: () -> Unit
|
||||
)
|
||||
|
||||
private data class CacheListState(
|
||||
private data class BookshelfManageListState(
|
||||
override val items: List<Book> = emptyList(),
|
||||
override val selectedIds: Set<Any> = emptySet(),
|
||||
override val searchKey: String = "",
|
||||
@@ -137,21 +138,21 @@ private data class CacheListState(
|
||||
) : ListUiState<Book>
|
||||
|
||||
@Composable
|
||||
fun CacheRouteScreen(
|
||||
fun BookshelfManageRouteScreen(
|
||||
groupId: Long,
|
||||
onBackClick: () -> Unit,
|
||||
viewModel: CacheViewModel = koinViewModel()
|
||||
viewModel: BookshelfManageScreenViewModel = koinViewModel()
|
||||
) {
|
||||
LaunchedEffect(groupId) {
|
||||
viewModel.dispatch(CacheIntent.Initialize(groupId))
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.Initialize(groupId))
|
||||
}
|
||||
CacheScreen(viewModel = viewModel, onBackClick = onBackClick)
|
||||
BookshelfManageScreen(viewModel = viewModel, onBackClick = onBackClick)
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
|
||||
@Composable
|
||||
private fun CacheScreen(
|
||||
viewModel: CacheViewModel,
|
||||
private fun BookshelfManageScreen(
|
||||
viewModel: BookshelfManageScreenViewModel,
|
||||
onBackClick: () -> Unit
|
||||
) {
|
||||
val state by viewModel.uiState.collectAsStateWithLifecycle()
|
||||
@@ -203,6 +204,7 @@ private fun CacheScreen(
|
||||
val exportFileNameText = stringResource(R.string.export_file_name)
|
||||
val resultAnalyzedText = stringResource(R.string.result_analyzed)
|
||||
val errorScopeInputText = stringResource(R.string.error_scope_input)
|
||||
val noGroupText = stringResource(R.string.no_group)
|
||||
val exportFileNameHintText = "书名:《{name}》 作者:{author}"
|
||||
val exportFileNameHelpText = """
|
||||
支持变量:{name}(书名)、{author}(作者)、{group}(分组)、{source}(书源)、{remark}(备注)。
|
||||
@@ -218,15 +220,15 @@ private fun CacheScreen(
|
||||
val booksByUrl = remember(state.books) { state.books.associateBy { it.bookUrl } }
|
||||
val userGroups = remember(state.groupList) { state.groupList.filter { it.groupId > 0L } }
|
||||
|
||||
val groupNameResolver: (Book) -> String = remember(userGroups, context) {
|
||||
val groupNameResolver: (Book) -> String = remember(userGroups, noGroupText) {
|
||||
{ book ->
|
||||
if (book.group <= 0L) {
|
||||
context.getString(R.string.no_group)
|
||||
noGroupText
|
||||
} else {
|
||||
val groups = userGroups.filter {
|
||||
(book.group and it.groupId) > 0L
|
||||
}
|
||||
if (groups.isEmpty()) context.getString(R.string.no_group)
|
||||
if (groups.isEmpty()) noGroupText
|
||||
else groups.joinToString("、") { it.groupName }
|
||||
}
|
||||
}
|
||||
@@ -245,7 +247,7 @@ private fun CacheScreen(
|
||||
}
|
||||
}
|
||||
val listUiState = remember(filteredBooks, selectedBookUrls, searchKey, isSearchMode) {
|
||||
CacheListState(
|
||||
BookshelfManageListState(
|
||||
items = filteredBooks,
|
||||
selectedIds = selectedBookUrls.mapTo(linkedSetOf()) { it as Any },
|
||||
searchKey = searchKey,
|
||||
@@ -327,9 +329,9 @@ private fun CacheScreen(
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.effects.collect { effect ->
|
||||
when (effect) {
|
||||
is CacheEffect.ShowMessage -> context.toastOnUi(effect.message)
|
||||
is CacheEffect.NotifyBookChanged -> Unit
|
||||
is CacheEffect.OpenBookInfo -> context.startActivity<BookInfoActivity> {
|
||||
is BookshelfManageScreenEffect.ShowMessage -> context.toastOnUi(effect.message)
|
||||
is BookshelfManageScreenEffect.NotifyBookChanged -> Unit
|
||||
is BookshelfManageScreenEffect.OpenBookInfo -> context.startActivity<BookInfoActivity> {
|
||||
putExtra("name", effect.name)
|
||||
putExtra("author", effect.author)
|
||||
putExtra("bookUrl", effect.bookUrl)
|
||||
@@ -398,20 +400,20 @@ private fun CacheScreen(
|
||||
return if (targetBooks.all { it.group == firstGroup }) firstGroup else 0L
|
||||
}
|
||||
val fabItems = listOf(
|
||||
CacheFabAction(
|
||||
BookshelfManageFabAction(
|
||||
Icons.Default.SelectAll,
|
||||
stringResource(R.string.select_all)
|
||||
) {
|
||||
selectedBookUrls = state.books.mapTo(hashSetOf()) { it.bookUrl }
|
||||
},
|
||||
CacheFabAction(
|
||||
BookshelfManageFabAction(
|
||||
Icons.Default.Refresh,
|
||||
stringResource(R.string.revert_selection)
|
||||
) {
|
||||
val visibleBookUrls = booksByUrl.keys
|
||||
selectedBookUrls = visibleBookUrls - selectedBookUrls
|
||||
},
|
||||
CacheFabAction(
|
||||
BookshelfManageFabAction(
|
||||
Icons.Default.Download,
|
||||
"缓存选中"
|
||||
) {
|
||||
@@ -419,7 +421,7 @@ private fun CacheScreen(
|
||||
showBatchDownloadConfirmDialog = true
|
||||
}
|
||||
},
|
||||
CacheFabAction(
|
||||
BookshelfManageFabAction(
|
||||
Icons.Default.Refresh,
|
||||
"批量换源"
|
||||
) {
|
||||
@@ -427,7 +429,7 @@ private fun CacheScreen(
|
||||
showBatchSourcePickerSheet = true
|
||||
}
|
||||
},
|
||||
CacheFabAction(
|
||||
BookshelfManageFabAction(
|
||||
Icons.Default.Bookmarks,
|
||||
stringResource(R.string.move_to_group)
|
||||
) {
|
||||
@@ -437,20 +439,20 @@ private fun CacheScreen(
|
||||
showGroupSelectSheet = true
|
||||
}
|
||||
},
|
||||
CacheFabAction(
|
||||
BookshelfManageFabAction(
|
||||
Icons.Default.Upload,
|
||||
"导出选中"
|
||||
) {
|
||||
exportSelected()
|
||||
},
|
||||
CacheFabAction(
|
||||
BookshelfManageFabAction(
|
||||
Icons.Default.Delete,
|
||||
stringResource(R.string.clear_cache)
|
||||
) {
|
||||
viewModel.dispatch(CacheIntent.ClearCachesForBooks(selectedBookUrls))
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.ClearCachesForBooks(selectedBookUrls))
|
||||
clearSelection()
|
||||
},
|
||||
CacheFabAction(
|
||||
BookshelfManageFabAction(
|
||||
Icons.Default.Delete,
|
||||
stringResource(R.string.delete)
|
||||
) {
|
||||
@@ -462,6 +464,17 @@ private fun CacheScreen(
|
||||
}
|
||||
)
|
||||
val listState = rememberLazyListState()
|
||||
val canReorderBooks = state.bookSort == 3 && !isSearchMode && selectedBookUrls.isEmpty()
|
||||
val reorderableState = rememberReorderableLazyListState(listState) { from, to ->
|
||||
if (canReorderBooks) {
|
||||
viewModel.dispatch(
|
||||
BookshelfManageScreenIntent.MoveBookOrder(
|
||||
fromIndex = from.index,
|
||||
toIndex = to.index
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
ListScaffold(
|
||||
title = if (inSelectionMode) {
|
||||
"已选 ${selectedBookUrls.size}/${filteredBooks.size}"
|
||||
@@ -495,7 +508,7 @@ private fun CacheScreen(
|
||||
isSelected = group.groupId == state.groupId,
|
||||
onClick = {
|
||||
dismiss()
|
||||
viewModel.dispatch(CacheIntent.ChangeGroup(group.groupId))
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.ChangeGroup(group.groupId))
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -508,7 +521,7 @@ private fun CacheScreen(
|
||||
onClick = {
|
||||
dismiss()
|
||||
if (state.isDownloadRunning) {
|
||||
viewModel.dispatch(CacheIntent.StopDownload)
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.StopDownload)
|
||||
} else {
|
||||
showDownloadAllConfirmDialog = true
|
||||
}
|
||||
@@ -553,7 +566,7 @@ private fun CacheScreen(
|
||||
onClick = {
|
||||
dismiss()
|
||||
viewModel.dispatch(
|
||||
CacheIntent.SetExportUseReplace(!state.exportConfig.exportUseReplace)
|
||||
BookshelfManageScreenIntent.SetExportUseReplace(!state.exportConfig.exportUseReplace)
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -563,7 +576,7 @@ private fun CacheScreen(
|
||||
onClick = {
|
||||
dismiss()
|
||||
viewModel.dispatch(
|
||||
CacheIntent.SetEnableCustomExport(!state.exportConfig.enableCustomExport)
|
||||
BookshelfManageScreenIntent.SetEnableCustomExport(!state.exportConfig.enableCustomExport)
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -573,7 +586,7 @@ private fun CacheScreen(
|
||||
onClick = {
|
||||
dismiss()
|
||||
viewModel.dispatch(
|
||||
CacheIntent.SetExportNoChapterName(!state.exportConfig.exportNoChapterName)
|
||||
BookshelfManageScreenIntent.SetExportNoChapterName(!state.exportConfig.exportNoChapterName)
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -583,7 +596,7 @@ private fun CacheScreen(
|
||||
onClick = {
|
||||
dismiss()
|
||||
viewModel.dispatch(
|
||||
CacheIntent.SetExportToWebDav(!state.exportConfig.exportToWebDav)
|
||||
BookshelfManageScreenIntent.SetExportToWebDav(!state.exportConfig.exportToWebDav)
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -593,7 +606,7 @@ private fun CacheScreen(
|
||||
onClick = {
|
||||
dismiss()
|
||||
viewModel.dispatch(
|
||||
CacheIntent.SetExportPictureFile(!state.exportConfig.exportPictureFile)
|
||||
BookshelfManageScreenIntent.SetExportPictureFile(!state.exportConfig.exportPictureFile)
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -603,7 +616,7 @@ private fun CacheScreen(
|
||||
onClick = {
|
||||
dismiss()
|
||||
viewModel.dispatch(
|
||||
CacheIntent.SetParallelExportBook(!state.exportConfig.parallelExportBook)
|
||||
BookshelfManageScreenIntent.SetParallelExportBook(!state.exportConfig.parallelExportBook)
|
||||
)
|
||||
}
|
||||
)
|
||||
@@ -680,119 +693,137 @@ private fun CacheScreen(
|
||||
val exportMsg = remember(renderVersion, book.bookUrl) {
|
||||
ExportBookService.exportMsg[book.bookUrl]
|
||||
}
|
||||
NormalCard(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = { toggleBookSelection(book) },
|
||||
onLongClick = { toggleBookSelection(book) },
|
||||
containerColor = if (isSelected) {
|
||||
LegadoTheme.colorScheme.surfaceContainerHigh
|
||||
} else {
|
||||
LegadoTheme.colorScheme.surfaceContainerLow
|
||||
}
|
||||
ReorderableItem(
|
||||
state = reorderableState,
|
||||
key = book.bookUrl,
|
||||
enabled = canReorderBooks
|
||||
) {
|
||||
Column(
|
||||
NormalCard(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.adaptiveHorizontalPadding(vertical = 12.dp),
|
||||
verticalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
AppText(
|
||||
text = book.name,
|
||||
style = LegadoTheme.typography.titleSmallEmphasized,
|
||||
maxLines = 1
|
||||
)
|
||||
AppText(
|
||||
text = book.getRealAuthor(),
|
||||
style = LegadoTheme.typography.bodySmall
|
||||
)
|
||||
AppText(
|
||||
text = "${groupNameResolver(book)} | ${book.originName.ifBlank { book.origin }}",
|
||||
style = LegadoTheme.typography.labelSmallEmphasized.copy(color = LegadoTheme.colorScheme.primary)
|
||||
)
|
||||
if (exportMsg != null) {
|
||||
AppText(text = exportMsg, modifier = Modifier.padding(top = 2.dp))
|
||||
}
|
||||
}
|
||||
TextCard(
|
||||
text = if (book.isLocal) {
|
||||
stringResource(R.string.local_book)
|
||||
.then(
|
||||
if (canReorderBooks) {
|
||||
Modifier.longPressDraggableHandle()
|
||||
} else {
|
||||
stringResource(
|
||||
R.string.download_count,
|
||||
cacheCount,
|
||||
book.totalChapterNum
|
||||
)
|
||||
Modifier
|
||||
}
|
||||
)
|
||||
),
|
||||
onClick = { toggleBookSelection(book) },
|
||||
onLongClick = { toggleBookSelection(book) },
|
||||
containerColor = if (isSelected) {
|
||||
LegadoTheme.colorScheme.surfaceContainerHigh
|
||||
} else {
|
||||
LegadoTheme.colorScheme.surfaceContainerLow
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.adaptiveHorizontalPadding(vertical = 12.dp),
|
||||
verticalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
SmallTonalIconButton(
|
||||
onClick = {
|
||||
if (!book.isLocal) {
|
||||
viewModel.dispatch(CacheIntent.ToggleBookDownload(book))
|
||||
}
|
||||
},
|
||||
imageVector = if (isDownloading) Icons.Default.Stop else Icons.Default.Download,
|
||||
contentDescription = "download"
|
||||
)
|
||||
SmallTonalIconButton(
|
||||
onClick = { exportBook(book) },
|
||||
imageVector = Icons.Default.Upload,
|
||||
contentDescription = "upload"
|
||||
)
|
||||
SmallTonalIconButton(
|
||||
onClick = {
|
||||
pendingMoveGroupBookUrl = book.bookUrl
|
||||
groupPickerCurrentGroupId = book.group.coerceAtLeast(0L)
|
||||
showGroupSelectSheet = true
|
||||
},
|
||||
imageVector = Icons.Default.Bookmarks,
|
||||
contentDescription = "group"
|
||||
)
|
||||
SmallTonalIconButton(
|
||||
onClick = { moreMenuBookUrl = book.bookUrl },
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = "more"
|
||||
)
|
||||
RoundDropdownMenu(
|
||||
expanded = moreMenuBookUrl == book.bookUrl,
|
||||
onDismissRequest = { moreMenuBookUrl = null }
|
||||
) { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
text = "换源",
|
||||
onClick = {
|
||||
singleChangeSourceBook = book
|
||||
dismiss()
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
AppText(
|
||||
text = book.name,
|
||||
style = LegadoTheme.typography.titleSmallEmphasized,
|
||||
maxLines = 1
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = "删除书籍",
|
||||
onClick = {
|
||||
pendingDeleteBookUrls = setOf(book.bookUrl)
|
||||
deleteOriginalBookFile = state.deleteBookOriginal
|
||||
showDeleteBookConfirmDialog = true
|
||||
dismiss()
|
||||
}
|
||||
AppText(
|
||||
text = book.getRealAuthor(),
|
||||
style = LegadoTheme.typography.bodySmall
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = "删除缓存",
|
||||
onClick = {
|
||||
viewModel.dispatch(CacheIntent.ClearCachesForBooks(setOf(book.bookUrl)))
|
||||
dismiss()
|
||||
}
|
||||
AppText(
|
||||
text = "${groupNameResolver(book)} | ${book.originName.ifBlank { book.origin }}",
|
||||
style = LegadoTheme.typography.labelSmallEmphasized.copy(color = LegadoTheme.colorScheme.primary)
|
||||
)
|
||||
if (exportMsg != null) {
|
||||
AppText(text = exportMsg, modifier = Modifier.padding(top = 2.dp))
|
||||
}
|
||||
}
|
||||
TextCard(
|
||||
text = if (book.isLocal) {
|
||||
stringResource(R.string.local_book)
|
||||
} else {
|
||||
stringResource(
|
||||
R.string.download_count,
|
||||
cacheCount,
|
||||
book.totalChapterNum
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.End
|
||||
) {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(6.dp)) {
|
||||
SmallTonalIconButton(
|
||||
onClick = {
|
||||
if (!book.isLocal) {
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.ToggleBookDownload(book))
|
||||
}
|
||||
},
|
||||
imageVector = if (isDownloading) Icons.Default.Stop else Icons.Default.Download,
|
||||
contentDescription = "download"
|
||||
)
|
||||
SmallTonalIconButton(
|
||||
onClick = { exportBook(book) },
|
||||
imageVector = Icons.Default.Upload,
|
||||
contentDescription = "upload"
|
||||
)
|
||||
SmallTonalIconButton(
|
||||
onClick = {
|
||||
pendingMoveGroupBookUrl = book.bookUrl
|
||||
groupPickerCurrentGroupId = book.group.coerceAtLeast(0L)
|
||||
showGroupSelectSheet = true
|
||||
},
|
||||
imageVector = Icons.Default.Bookmarks,
|
||||
contentDescription = "group"
|
||||
)
|
||||
SmallTonalIconButton(
|
||||
onClick = { moreMenuBookUrl = book.bookUrl },
|
||||
imageVector = Icons.Default.MoreVert,
|
||||
contentDescription = "more"
|
||||
)
|
||||
RoundDropdownMenu(
|
||||
expanded = moreMenuBookUrl == book.bookUrl,
|
||||
onDismissRequest = { moreMenuBookUrl = null }
|
||||
) { dismiss ->
|
||||
RoundDropdownMenuItem(
|
||||
text = "换源",
|
||||
onClick = {
|
||||
singleChangeSourceBook = book
|
||||
dismiss()
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = "删除书籍",
|
||||
onClick = {
|
||||
pendingDeleteBookUrls = setOf(book.bookUrl)
|
||||
deleteOriginalBookFile = state.deleteBookOriginal
|
||||
showDeleteBookConfirmDialog = true
|
||||
dismiss()
|
||||
}
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = "删除缓存",
|
||||
onClick = {
|
||||
viewModel.dispatch(
|
||||
BookshelfManageScreenIntent.ClearCachesForBooks(
|
||||
setOf(book.bookUrl)
|
||||
)
|
||||
)
|
||||
dismiss()
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -809,7 +840,7 @@ private fun CacheScreen(
|
||||
onDismissRequest = { singleChangeSourceBook = null },
|
||||
onReplace = { source, newBook, toc, options ->
|
||||
viewModel.dispatch(
|
||||
CacheIntent.ChangeBookSource(
|
||||
BookshelfManageScreenIntent.ChangeBookSource(
|
||||
oldBookUrl = book.bookUrl,
|
||||
source = source,
|
||||
book = newBook,
|
||||
@@ -842,7 +873,7 @@ private fun CacheScreen(
|
||||
onDismissRequest = { pendingBatchSources = emptyList() },
|
||||
onConfirm = { options ->
|
||||
viewModel.dispatch(
|
||||
CacheIntent.BatchChangeBookSource(
|
||||
BookshelfManageScreenIntent.BatchChangeBookSource(
|
||||
bookUrls = selectedBookUrls,
|
||||
sources = pendingBatchSources,
|
||||
options = options,
|
||||
@@ -856,18 +887,18 @@ private fun CacheScreen(
|
||||
BatchChangePreviewSheet(
|
||||
show = state.batchChangePreviewItems.isNotEmpty(),
|
||||
items = state.batchChangePreviewItems,
|
||||
onDismissRequest = { viewModel.dispatch(CacheIntent.DismissBatchChangePreview) },
|
||||
onDismissRequest = { viewModel.dispatch(BookshelfManageScreenIntent.DismissBatchChangePreview) },
|
||||
onOpenBook = { book, inBookshelf ->
|
||||
viewModel.dispatch(CacheIntent.OpenBookInfoPreview(book, inBookshelf))
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.OpenBookInfoPreview(book, inBookshelf))
|
||||
},
|
||||
onManualSearch = { book -> manualSearchPreviewBook = book },
|
||||
onSkip = { bookUrl -> viewModel.dispatch(CacheIntent.SkipPreviewItem(bookUrl)) },
|
||||
onMigrate = { bookUrl -> viewModel.dispatch(CacheIntent.MigratePreviewItem(bookUrl)) },
|
||||
onSkip = { bookUrl -> viewModel.dispatch(BookshelfManageScreenIntent.SkipPreviewItem(bookUrl)) },
|
||||
onMigrate = { bookUrl -> viewModel.dispatch(BookshelfManageScreenIntent.MigratePreviewItem(bookUrl)) },
|
||||
onAddToShelf = { bookUrl ->
|
||||
viewModel.dispatch(CacheIntent.AddPreviewItemToShelf(bookUrl))
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.AddPreviewItemToShelf(bookUrl))
|
||||
},
|
||||
onShowOtherSources = { item -> otherSourcePreviewItem = item },
|
||||
onMigrateAll = { viewModel.dispatch(CacheIntent.MigrateAllPreviewItems) },
|
||||
onMigrateAll = { viewModel.dispatch(BookshelfManageScreenIntent.MigrateAllPreviewItems) },
|
||||
)
|
||||
|
||||
manualSearchPreviewBook?.let { book ->
|
||||
@@ -877,7 +908,7 @@ private fun CacheScreen(
|
||||
onDismissRequest = { manualSearchPreviewBook = null },
|
||||
onReplace = { source, newBook, toc, _ ->
|
||||
viewModel.dispatch(
|
||||
CacheIntent.UpdatePreviewItem(
|
||||
BookshelfManageScreenIntent.UpdatePreviewItem(
|
||||
oldBookUrl = book.bookUrl,
|
||||
source = source,
|
||||
book = newBook,
|
||||
@@ -896,11 +927,11 @@ private fun CacheScreen(
|
||||
item = otherSourcePreviewItem,
|
||||
onDismissRequest = { otherSourcePreviewItem = null },
|
||||
onSelect = { oldBookUrl, index ->
|
||||
viewModel.dispatch(CacheIntent.SelectPreviewCandidate(oldBookUrl, index))
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.SelectPreviewCandidate(oldBookUrl, index))
|
||||
otherSourcePreviewItem = null
|
||||
},
|
||||
onOpenBook = { book ->
|
||||
viewModel.dispatch(CacheIntent.OpenBookInfoPreview(book, false))
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.OpenBookInfoPreview(book, false))
|
||||
}
|
||||
)
|
||||
|
||||
@@ -908,7 +939,7 @@ private fun CacheScreen(
|
||||
show = state.isChangingSource || state.changeSourceError != null,
|
||||
onDismissRequest = {
|
||||
if (!state.isChangingSource) {
|
||||
viewModel.dispatch(CacheIntent.DismissChangeSourceStatus)
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.DismissChangeSourceStatus)
|
||||
}
|
||||
},
|
||||
title = stringResource(R.string.change_source_batch),
|
||||
@@ -936,7 +967,7 @@ private fun CacheScreen(
|
||||
onConfirm = if (state.isChangingSource) {
|
||||
null
|
||||
} else {
|
||||
{ viewModel.dispatch(CacheIntent.DismissChangeSourceStatus) }
|
||||
{ viewModel.dispatch(BookshelfManageScreenIntent.DismissChangeSourceStatus) }
|
||||
},
|
||||
)
|
||||
|
||||
@@ -959,7 +990,7 @@ private fun CacheScreen(
|
||||
onConfirm = {
|
||||
showBatchDownloadConfirmDialog = false
|
||||
viewModel.dispatch(
|
||||
CacheIntent.DownloadBooks(
|
||||
BookshelfManageScreenIntent.DownloadBooks(
|
||||
bookUrls = selectedBookUrls,
|
||||
downloadAllChapters = false
|
||||
)
|
||||
@@ -999,7 +1030,7 @@ private fun CacheScreen(
|
||||
onConfirm = {
|
||||
showDeleteBookConfirmDialog = false
|
||||
viewModel.dispatch(
|
||||
CacheIntent.DeleteBooks(
|
||||
BookshelfManageScreenIntent.DeleteBooks(
|
||||
bookUrls = pendingDeleteBookUrls,
|
||||
deleteOriginal = deleteOriginalBookFile
|
||||
)
|
||||
@@ -1019,7 +1050,7 @@ private fun CacheScreen(
|
||||
val moveSet = pendingMoveGroupBookUrl?.let { setOf(it) } ?: selectedBookUrls
|
||||
val targetGroupId = groupId.coerceAtLeast(0L)
|
||||
viewModel.dispatch(
|
||||
CacheIntent.MoveBooksToGroup(
|
||||
BookshelfManageScreenIntent.MoveBooksToGroup(
|
||||
bookUrls = moveSet,
|
||||
groupId = targetGroupId
|
||||
)
|
||||
@@ -1040,7 +1071,7 @@ private fun CacheScreen(
|
||||
onConfirm = {
|
||||
showDownloadAllConfirmDialog = false
|
||||
viewModel.dispatch(
|
||||
CacheIntent.StartDownloadForVisibleBooks(
|
||||
BookshelfManageScreenIntent.StartDownloadForVisibleBooks(
|
||||
books = state.books,
|
||||
downloadAllChapters = true
|
||||
)
|
||||
@@ -1060,7 +1091,7 @@ private fun CacheScreen(
|
||||
icon = if (type == "epub") Icons.Default.Upload else Icons.Default.Download,
|
||||
text = type,
|
||||
onClick = {
|
||||
viewModel.dispatch(CacheIntent.SetExportType(index))
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.SetExportType(index))
|
||||
showExportTypeDialog = false
|
||||
}
|
||||
)
|
||||
@@ -1085,7 +1116,7 @@ private fun CacheScreen(
|
||||
},
|
||||
confirmText = stringResource(android.R.string.ok),
|
||||
onConfirm = {
|
||||
viewModel.dispatch(CacheIntent.SetBookExportFileName(exportFileNameInput))
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.SetBookExportFileName(exportFileNameInput))
|
||||
showExportFileNameDialog = false
|
||||
},
|
||||
dismissText = stringResource(android.R.string.cancel),
|
||||
@@ -1107,7 +1138,7 @@ private fun CacheScreen(
|
||||
confirmText = stringResource(android.R.string.ok),
|
||||
onConfirm = {
|
||||
viewModel.dispatch(
|
||||
CacheIntent.SetExportCharset(
|
||||
BookshelfManageScreenIntent.SetExportCharset(
|
||||
exportCharsetInput.ifBlank { "UTF-8" }
|
||||
)
|
||||
)
|
||||
@@ -1228,7 +1259,7 @@ private fun CacheScreen(
|
||||
customEpisodeExportNameInput
|
||||
)
|
||||
) {
|
||||
viewModel.dispatch(CacheIntent.SetEpisodeExportFileName(customEpisodeExportNameInput))
|
||||
viewModel.dispatch(BookshelfManageScreenIntent.SetEpisodeExportFileName(customEpisodeExportNameInput))
|
||||
}
|
||||
val epubSize = customEpubSizeInput.toIntOrNull()?.coerceAtLeast(1) ?: 1
|
||||
context.startService<ExportBookService> {
|
||||
+177
-127
@@ -1,4 +1,4 @@
|
||||
package io.legado.app.ui.book.cache
|
||||
package io.legado.app.ui.book.manage
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -29,10 +29,12 @@ import io.legado.app.help.book.removeType
|
||||
import io.legado.app.model.CacheBook
|
||||
import io.legado.app.help.config.LocalConfig
|
||||
import io.legado.app.service.ExportBookService
|
||||
import io.legado.app.ui.config.cacheConfig.CacheConfig
|
||||
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
|
||||
import io.legado.app.ui.config.bookshelfConfig.BookshelfManageScreenConfig
|
||||
import io.legado.app.ui.config.otherConfig.OtherConfig
|
||||
import io.legado.app.ui.main.bookshelf.toLightBook
|
||||
import io.legado.app.utils.cnCompare
|
||||
import io.legado.app.utils.move
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
@@ -44,7 +46,7 @@ import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.max
|
||||
|
||||
data class CacheExportConfig(
|
||||
data class BookshelfManageScreenExportConfig(
|
||||
val exportUseReplace: Boolean = true,
|
||||
val enableCustomExport: Boolean = false,
|
||||
val exportNoChapterName: Boolean = false,
|
||||
@@ -57,11 +59,13 @@ data class CacheExportConfig(
|
||||
val episodeExportFileName: String = ""
|
||||
)
|
||||
|
||||
data class CacheUiState(
|
||||
data class BookshelfManageScreenUiState(
|
||||
val groupId: Long = -1,
|
||||
val groupName: String? = null,
|
||||
val groupList: List<BookGroup> = emptyList(),
|
||||
val books: List<Book> = emptyList(),
|
||||
val bookSort: Int = BookshelfConfig.bookshelfSort,
|
||||
val bookSortOrder: Int = BookshelfConfig.bookshelfSortOrder,
|
||||
val isDownloadRunning: Boolean = false,
|
||||
val isChangingSource: Boolean = false,
|
||||
val changeSourceProgress: String? = null,
|
||||
@@ -71,74 +75,75 @@ data class CacheUiState(
|
||||
val batchChangeOptions: ChangeSourceMigrationOptions = ChangeSourceMigrationOptions(),
|
||||
val cacheVersion: Long = 0,
|
||||
val deleteBookOriginal: Boolean = LocalConfig.deleteBookOriginal,
|
||||
val exportConfig: CacheExportConfig = CacheExportConfig()
|
||||
val exportConfig: BookshelfManageScreenExportConfig = BookshelfManageScreenExportConfig()
|
||||
)
|
||||
|
||||
sealed interface CacheIntent {
|
||||
data class Initialize(val groupId: Long) : CacheIntent
|
||||
data class ChangeGroup(val groupId: Long) : CacheIntent
|
||||
sealed interface BookshelfManageScreenIntent {
|
||||
data class Initialize(val groupId: Long) : BookshelfManageScreenIntent
|
||||
data class ChangeGroup(val groupId: Long) : BookshelfManageScreenIntent
|
||||
data class StartDownloadForVisibleBooks(
|
||||
val books: List<Book>,
|
||||
val downloadAllChapters: Boolean
|
||||
) : CacheIntent
|
||||
data object StopDownload : CacheIntent
|
||||
data class ToggleBookDownload(val book: Book) : CacheIntent
|
||||
data class DeleteBookDownload(val bookUrl: String) : CacheIntent
|
||||
data class ClearBookCache(val book: Book) : CacheIntent
|
||||
data class MoveBooksToGroup(val bookUrls: Set<String>, val groupId: Long) : CacheIntent
|
||||
data class DeleteBooks(val bookUrls: Set<String>, val deleteOriginal: Boolean) : CacheIntent
|
||||
data class ClearCachesForBooks(val bookUrls: Set<String>) : CacheIntent
|
||||
data class DownloadBooks(val bookUrls: Set<String>, val downloadAllChapters: Boolean) : CacheIntent
|
||||
) : BookshelfManageScreenIntent
|
||||
data object StopDownload : BookshelfManageScreenIntent
|
||||
data class ToggleBookDownload(val book: Book) : BookshelfManageScreenIntent
|
||||
data class DeleteBookDownload(val bookUrl: String) : BookshelfManageScreenIntent
|
||||
data class ClearBookCache(val book: Book) : BookshelfManageScreenIntent
|
||||
data class MoveBooksToGroup(val bookUrls: Set<String>, val groupId: Long) : BookshelfManageScreenIntent
|
||||
data class DeleteBooks(val bookUrls: Set<String>, val deleteOriginal: Boolean) : BookshelfManageScreenIntent
|
||||
data class ClearCachesForBooks(val bookUrls: Set<String>) : BookshelfManageScreenIntent
|
||||
data class MoveBookOrder(val fromIndex: Int, val toIndex: Int) : BookshelfManageScreenIntent
|
||||
data class DownloadBooks(val bookUrls: Set<String>, val downloadAllChapters: Boolean) : BookshelfManageScreenIntent
|
||||
data class ChangeBookSource(
|
||||
val oldBookUrl: String,
|
||||
val source: BookSource,
|
||||
val book: Book,
|
||||
val chapters: List<BookChapter>,
|
||||
val options: ChangeSourceMigrationOptions,
|
||||
) : CacheIntent
|
||||
) : BookshelfManageScreenIntent
|
||||
data class BatchChangeBookSource(
|
||||
val bookUrls: Set<String>,
|
||||
val sources: List<BookSource>,
|
||||
val options: ChangeSourceMigrationOptions,
|
||||
) : CacheIntent
|
||||
data class MigratePreviewItem(val oldBookUrl: String) : CacheIntent
|
||||
data class SkipPreviewItem(val oldBookUrl: String) : CacheIntent
|
||||
data class SelectPreviewCandidate(val oldBookUrl: String, val candidateIndex: Int) : CacheIntent
|
||||
) : BookshelfManageScreenIntent
|
||||
data class MigratePreviewItem(val oldBookUrl: String) : BookshelfManageScreenIntent
|
||||
data class SkipPreviewItem(val oldBookUrl: String) : BookshelfManageScreenIntent
|
||||
data class SelectPreviewCandidate(val oldBookUrl: String, val candidateIndex: Int) : BookshelfManageScreenIntent
|
||||
data class UpdatePreviewItem(
|
||||
val oldBookUrl: String,
|
||||
val source: BookSource,
|
||||
val book: Book,
|
||||
val chapters: List<BookChapter>,
|
||||
) : CacheIntent
|
||||
data class AddPreviewItemToShelf(val oldBookUrl: String) : CacheIntent
|
||||
data class OpenBookInfoPreview(val book: Book, val inBookshelf: Boolean) : CacheIntent
|
||||
data object MigrateAllPreviewItems : CacheIntent
|
||||
data object DismissChangeSourceStatus : CacheIntent
|
||||
data object DismissBatchChangePreview : CacheIntent
|
||||
data class SetExportUseReplace(val enabled: Boolean) : CacheIntent
|
||||
data class SetEnableCustomExport(val enabled: Boolean) : CacheIntent
|
||||
data class SetExportNoChapterName(val enabled: Boolean) : CacheIntent
|
||||
data class SetExportToWebDav(val enabled: Boolean) : CacheIntent
|
||||
data class SetExportPictureFile(val enabled: Boolean) : CacheIntent
|
||||
data class SetParallelExportBook(val enabled: Boolean) : CacheIntent
|
||||
data class SetExportType(val type: Int) : CacheIntent
|
||||
data class SetExportCharset(val charset: String) : CacheIntent
|
||||
data class SetBookExportFileName(val fileName: String?) : CacheIntent
|
||||
data class SetEpisodeExportFileName(val fileName: String) : CacheIntent
|
||||
) : BookshelfManageScreenIntent
|
||||
data class AddPreviewItemToShelf(val oldBookUrl: String) : BookshelfManageScreenIntent
|
||||
data class OpenBookInfoPreview(val book: Book, val inBookshelf: Boolean) : BookshelfManageScreenIntent
|
||||
data object MigrateAllPreviewItems : BookshelfManageScreenIntent
|
||||
data object DismissChangeSourceStatus : BookshelfManageScreenIntent
|
||||
data object DismissBatchChangePreview : BookshelfManageScreenIntent
|
||||
data class SetExportUseReplace(val enabled: Boolean) : BookshelfManageScreenIntent
|
||||
data class SetEnableCustomExport(val enabled: Boolean) : BookshelfManageScreenIntent
|
||||
data class SetExportNoChapterName(val enabled: Boolean) : BookshelfManageScreenIntent
|
||||
data class SetExportToWebDav(val enabled: Boolean) : BookshelfManageScreenIntent
|
||||
data class SetExportPictureFile(val enabled: Boolean) : BookshelfManageScreenIntent
|
||||
data class SetParallelExportBook(val enabled: Boolean) : BookshelfManageScreenIntent
|
||||
data class SetExportType(val type: Int) : BookshelfManageScreenIntent
|
||||
data class SetExportCharset(val charset: String) : BookshelfManageScreenIntent
|
||||
data class SetBookExportFileName(val fileName: String?) : BookshelfManageScreenIntent
|
||||
data class SetEpisodeExportFileName(val fileName: String) : BookshelfManageScreenIntent
|
||||
}
|
||||
|
||||
sealed interface CacheEffect {
|
||||
data class NotifyBookChanged(val bookUrl: String) : CacheEffect
|
||||
data class ShowMessage(val message: String) : CacheEffect
|
||||
data class OpenBookInfo(val bookUrl: String, val name: String, val author: String) : CacheEffect
|
||||
sealed interface BookshelfManageScreenEffect {
|
||||
data class NotifyBookChanged(val bookUrl: String) : BookshelfManageScreenEffect
|
||||
data class ShowMessage(val message: String) : BookshelfManageScreenEffect
|
||||
data class OpenBookInfo(val bookUrl: String, val name: String, val author: String) : BookshelfManageScreenEffect
|
||||
}
|
||||
|
||||
class CacheViewModel(
|
||||
class BookshelfManageScreenViewModel(
|
||||
application: Application,
|
||||
private val bookDao: BookDao,
|
||||
private val bookGroupDao: BookGroupDao,
|
||||
private val bookChapterDao: BookChapterDao,
|
||||
val cacheConfig: CacheConfig,
|
||||
val bookshelfManageScreenConfig: BookshelfManageScreenConfig,
|
||||
private val batchCacheDownloadUseCase: BatchCacheDownloadUseCase,
|
||||
private val cacheBookChaptersUseCase: CacheBookChaptersUseCase,
|
||||
private val changeBookSourceUseCase: ChangeBookSourceUseCase,
|
||||
@@ -147,10 +152,10 @@ class CacheViewModel(
|
||||
private val updateBooksGroupUseCase: UpdateBooksGroupUseCase
|
||||
) : BaseViewModel(application) {
|
||||
|
||||
private val _uiState = MutableStateFlow(CacheUiState())
|
||||
private val _uiState = MutableStateFlow(BookshelfManageScreenUiState())
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
private val _effects = MutableSharedFlow<CacheEffect>(extraBufferCapacity = 32)
|
||||
private val _effects = MutableSharedFlow<BookshelfManageScreenEffect>(extraBufferCapacity = 32)
|
||||
val effects = _effects.asSharedFlow()
|
||||
|
||||
private val cacheChapters = hashMapOf<String, HashSet<String>>()
|
||||
@@ -158,24 +163,25 @@ class CacheViewModel(
|
||||
private var groupsJob: Job? = null
|
||||
private var observersStarted = false
|
||||
|
||||
fun dispatch(intent: CacheIntent) {
|
||||
fun dispatch(intent: BookshelfManageScreenIntent) {
|
||||
when (intent) {
|
||||
is CacheIntent.Initialize -> initialize(intent.groupId)
|
||||
is CacheIntent.ChangeGroup -> changeGroup(intent.groupId)
|
||||
is CacheIntent.StartDownloadForVisibleBooks -> startDownloadForVisibleBooks(
|
||||
is BookshelfManageScreenIntent.Initialize -> initialize(intent.groupId)
|
||||
is BookshelfManageScreenIntent.ChangeGroup -> changeGroup(intent.groupId)
|
||||
is BookshelfManageScreenIntent.StartDownloadForVisibleBooks -> startDownloadForVisibleBooks(
|
||||
intent.books,
|
||||
intent.downloadAllChapters
|
||||
)
|
||||
|
||||
CacheIntent.StopDownload -> CacheBook.stop(context)
|
||||
is CacheIntent.ToggleBookDownload -> toggleBookDownload(intent.book)
|
||||
is CacheIntent.DeleteBookDownload -> CacheBook.remove(context, intent.bookUrl)
|
||||
is CacheIntent.ClearBookCache -> clearCacheForBook(intent.book)
|
||||
is CacheIntent.MoveBooksToGroup -> moveBooksToGroup(intent.bookUrls, intent.groupId)
|
||||
is CacheIntent.DeleteBooks -> deleteBooks(intent.bookUrls, intent.deleteOriginal)
|
||||
is CacheIntent.ClearCachesForBooks -> clearCachesForBooks(intent.bookUrls)
|
||||
is CacheIntent.DownloadBooks -> downloadBooks(intent.bookUrls, intent.downloadAllChapters)
|
||||
is CacheIntent.ChangeBookSource -> changeBookSource(
|
||||
BookshelfManageScreenIntent.StopDownload -> CacheBook.stop(context)
|
||||
is BookshelfManageScreenIntent.ToggleBookDownload -> toggleBookDownload(intent.book)
|
||||
is BookshelfManageScreenIntent.DeleteBookDownload -> CacheBook.remove(context, intent.bookUrl)
|
||||
is BookshelfManageScreenIntent.ClearBookCache -> clearCacheForBook(intent.book)
|
||||
is BookshelfManageScreenIntent.MoveBooksToGroup -> moveBooksToGroup(intent.bookUrls, intent.groupId)
|
||||
is BookshelfManageScreenIntent.DeleteBooks -> deleteBooks(intent.bookUrls, intent.deleteOriginal)
|
||||
is BookshelfManageScreenIntent.ClearCachesForBooks -> clearCachesForBooks(intent.bookUrls)
|
||||
is BookshelfManageScreenIntent.MoveBookOrder -> moveBookOrder(intent.fromIndex, intent.toIndex)
|
||||
is BookshelfManageScreenIntent.DownloadBooks -> downloadBooks(intent.bookUrls, intent.downloadAllChapters)
|
||||
is BookshelfManageScreenIntent.ChangeBookSource -> changeBookSource(
|
||||
intent.oldBookUrl,
|
||||
intent.source,
|
||||
intent.book,
|
||||
@@ -183,35 +189,35 @@ class CacheViewModel(
|
||||
intent.options
|
||||
)
|
||||
|
||||
is CacheIntent.BatchChangeBookSource -> batchChangeBookSource(
|
||||
is BookshelfManageScreenIntent.BatchChangeBookSource -> batchChangeBookSource(
|
||||
intent.bookUrls,
|
||||
intent.sources,
|
||||
intent.options
|
||||
)
|
||||
|
||||
is CacheIntent.MigratePreviewItem -> migratePreviewItem(intent.oldBookUrl)
|
||||
is CacheIntent.SkipPreviewItem -> skipPreviewItem(intent.oldBookUrl)
|
||||
is CacheIntent.SelectPreviewCandidate -> selectPreviewCandidate(
|
||||
is BookshelfManageScreenIntent.MigratePreviewItem -> migratePreviewItem(intent.oldBookUrl)
|
||||
is BookshelfManageScreenIntent.SkipPreviewItem -> skipPreviewItem(intent.oldBookUrl)
|
||||
is BookshelfManageScreenIntent.SelectPreviewCandidate -> selectPreviewCandidate(
|
||||
intent.oldBookUrl,
|
||||
intent.candidateIndex
|
||||
)
|
||||
|
||||
is CacheIntent.UpdatePreviewItem -> updatePreviewItem(
|
||||
is BookshelfManageScreenIntent.UpdatePreviewItem -> updatePreviewItem(
|
||||
intent.oldBookUrl,
|
||||
intent.source,
|
||||
intent.book,
|
||||
intent.chapters
|
||||
)
|
||||
|
||||
is CacheIntent.AddPreviewItemToShelf -> addPreviewItemToShelf(intent.oldBookUrl)
|
||||
is CacheIntent.OpenBookInfoPreview -> openBookInfoPreview(
|
||||
is BookshelfManageScreenIntent.AddPreviewItemToShelf -> addPreviewItemToShelf(intent.oldBookUrl)
|
||||
is BookshelfManageScreenIntent.OpenBookInfoPreview -> openBookInfoPreview(
|
||||
intent.book,
|
||||
intent.inBookshelf
|
||||
)
|
||||
|
||||
CacheIntent.MigrateAllPreviewItems -> migrateAllPreviewItems()
|
||||
BookshelfManageScreenIntent.MigrateAllPreviewItems -> migrateAllPreviewItems()
|
||||
|
||||
CacheIntent.DismissChangeSourceStatus -> {
|
||||
BookshelfManageScreenIntent.DismissChangeSourceStatus -> {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
changeSourceProgress = null,
|
||||
@@ -221,59 +227,59 @@ class CacheViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
CacheIntent.DismissBatchChangePreview -> {
|
||||
BookshelfManageScreenIntent.DismissBatchChangePreview -> {
|
||||
_uiState.update { it.copy(batchChangePreviewItems = emptyList()) }
|
||||
}
|
||||
|
||||
is CacheIntent.SetExportUseReplace -> {
|
||||
cacheConfig.exportUseReplace = intent.enabled
|
||||
is BookshelfManageScreenIntent.SetExportUseReplace -> {
|
||||
bookshelfManageScreenConfig.exportUseReplace = intent.enabled
|
||||
syncExportConfig()
|
||||
val msg = if (intent.enabled) "替换净化功能已开启" else "替换净化功能已关闭"
|
||||
_effects.tryEmit(CacheEffect.ShowMessage(msg))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage(msg))
|
||||
}
|
||||
|
||||
is CacheIntent.SetEnableCustomExport -> {
|
||||
cacheConfig.enableCustomExport = intent.enabled
|
||||
is BookshelfManageScreenIntent.SetEnableCustomExport -> {
|
||||
bookshelfManageScreenConfig.enableCustomExport = intent.enabled
|
||||
syncExportConfig()
|
||||
}
|
||||
|
||||
is CacheIntent.SetExportNoChapterName -> {
|
||||
cacheConfig.exportNoChapterName = intent.enabled
|
||||
is BookshelfManageScreenIntent.SetExportNoChapterName -> {
|
||||
bookshelfManageScreenConfig.exportNoChapterName = intent.enabled
|
||||
syncExportConfig()
|
||||
}
|
||||
|
||||
is CacheIntent.SetExportToWebDav -> {
|
||||
cacheConfig.exportToWebDav = intent.enabled
|
||||
is BookshelfManageScreenIntent.SetExportToWebDav -> {
|
||||
bookshelfManageScreenConfig.exportToWebDav = intent.enabled
|
||||
syncExportConfig()
|
||||
}
|
||||
|
||||
is CacheIntent.SetExportPictureFile -> {
|
||||
cacheConfig.exportPictureFile = intent.enabled
|
||||
is BookshelfManageScreenIntent.SetExportPictureFile -> {
|
||||
bookshelfManageScreenConfig.exportPictureFile = intent.enabled
|
||||
syncExportConfig()
|
||||
}
|
||||
|
||||
is CacheIntent.SetParallelExportBook -> {
|
||||
cacheConfig.parallelExportBook = intent.enabled
|
||||
is BookshelfManageScreenIntent.SetParallelExportBook -> {
|
||||
bookshelfManageScreenConfig.parallelExportBook = intent.enabled
|
||||
syncExportConfig()
|
||||
}
|
||||
|
||||
is CacheIntent.SetExportType -> {
|
||||
cacheConfig.exportType = intent.type
|
||||
is BookshelfManageScreenIntent.SetExportType -> {
|
||||
bookshelfManageScreenConfig.exportType = intent.type
|
||||
syncExportConfig()
|
||||
}
|
||||
|
||||
is CacheIntent.SetExportCharset -> {
|
||||
cacheConfig.exportCharset = intent.charset
|
||||
is BookshelfManageScreenIntent.SetExportCharset -> {
|
||||
bookshelfManageScreenConfig.exportCharset = intent.charset
|
||||
syncExportConfig()
|
||||
}
|
||||
|
||||
is CacheIntent.SetBookExportFileName -> {
|
||||
cacheConfig.bookExportFileName = intent.fileName
|
||||
is BookshelfManageScreenIntent.SetBookExportFileName -> {
|
||||
bookshelfManageScreenConfig.bookExportFileName = intent.fileName
|
||||
syncExportConfig()
|
||||
}
|
||||
|
||||
is CacheIntent.SetEpisodeExportFileName -> {
|
||||
cacheConfig.episodeExportFileName = intent.fileName
|
||||
is BookshelfManageScreenIntent.SetEpisodeExportFileName -> {
|
||||
bookshelfManageScreenConfig.episodeExportFileName = intent.fileName
|
||||
syncExportConfig()
|
||||
}
|
||||
}
|
||||
@@ -316,18 +322,38 @@ class CacheViewModel(
|
||||
booksJob = viewModelScope.launch {
|
||||
bookDao.flowBookShelfByGroup(groupId).map { books ->
|
||||
val booksDownload = books.filter { !it.isAudio }.map { it.toLightBook() }
|
||||
when (cacheConfig.getBookSortByGroupId(groupId)) {
|
||||
1 -> booksDownload.sortedByDescending { it.latestChapterTime }
|
||||
2 -> booksDownload.sortedWith { o1, o2 -> o1.name.cnCompare(o2.name) }
|
||||
3 -> booksDownload.sortedBy { it.order }
|
||||
4 -> booksDownload.sortedByDescending {
|
||||
val bookSort = bookshelfManageScreenConfig.getBookSortByGroupId(groupId)
|
||||
val isDescending = bookshelfManageScreenConfig.bookshelfSortOrder == 1
|
||||
bookSort to when (bookSort) {
|
||||
1 -> if (isDescending) booksDownload.sortedByDescending { it.latestChapterTime }
|
||||
else booksDownload.sortedBy { it.latestChapterTime }
|
||||
|
||||
2 -> if (isDescending) {
|
||||
booksDownload.sortedWith { o1, o2 -> o2.name.cnCompare(o1.name) }
|
||||
} else {
|
||||
booksDownload.sortedWith { o1, o2 -> o1.name.cnCompare(o2.name) }
|
||||
}
|
||||
|
||||
3 -> if (isDescending) booksDownload.sortedByDescending { it.order }
|
||||
else booksDownload.sortedBy { it.order }
|
||||
|
||||
4 -> if (isDescending) booksDownload.sortedByDescending {
|
||||
max(it.latestChapterTime, it.durChapterTime)
|
||||
} else booksDownload.sortedBy {
|
||||
max(it.latestChapterTime, it.durChapterTime)
|
||||
}
|
||||
|
||||
else -> booksDownload.sortedByDescending { it.durChapterTime }
|
||||
else -> if (isDescending) booksDownload.sortedByDescending { it.durChapterTime }
|
||||
else booksDownload.sortedBy { it.durChapterTime }
|
||||
}
|
||||
}.collect { (bookSort, books) ->
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
books = books,
|
||||
bookSort = bookSort,
|
||||
bookSortOrder = bookshelfManageScreenConfig.bookshelfSortOrder
|
||||
)
|
||||
}
|
||||
}.collect { books ->
|
||||
_uiState.update { it.copy(books = books) }
|
||||
loadCacheFiles(books)
|
||||
}
|
||||
}
|
||||
@@ -372,17 +398,17 @@ class CacheViewModel(
|
||||
private fun syncExportConfig() {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
exportConfig = CacheExportConfig(
|
||||
exportUseReplace = cacheConfig.exportUseReplace,
|
||||
enableCustomExport = cacheConfig.enableCustomExport,
|
||||
exportNoChapterName = cacheConfig.exportNoChapterName,
|
||||
exportToWebDav = cacheConfig.exportToWebDav,
|
||||
exportPictureFile = cacheConfig.exportPictureFile,
|
||||
parallelExportBook = cacheConfig.parallelExportBook,
|
||||
exportType = cacheConfig.exportType,
|
||||
exportCharset = cacheConfig.exportCharset,
|
||||
bookExportFileName = cacheConfig.bookExportFileName,
|
||||
episodeExportFileName = cacheConfig.episodeExportFileName
|
||||
exportConfig = BookshelfManageScreenExportConfig(
|
||||
exportUseReplace = bookshelfManageScreenConfig.exportUseReplace,
|
||||
enableCustomExport = bookshelfManageScreenConfig.enableCustomExport,
|
||||
exportNoChapterName = bookshelfManageScreenConfig.exportNoChapterName,
|
||||
exportToWebDav = bookshelfManageScreenConfig.exportToWebDav,
|
||||
exportPictureFile = bookshelfManageScreenConfig.exportPictureFile,
|
||||
parallelExportBook = bookshelfManageScreenConfig.parallelExportBook,
|
||||
exportType = bookshelfManageScreenConfig.exportType,
|
||||
exportCharset = bookshelfManageScreenConfig.exportCharset,
|
||||
bookExportFileName = bookshelfManageScreenConfig.bookExportFileName,
|
||||
episodeExportFileName = bookshelfManageScreenConfig.episodeExportFileName
|
||||
)
|
||||
)
|
||||
}
|
||||
@@ -463,7 +489,7 @@ class CacheViewModel(
|
||||
execute {
|
||||
updateBooksGroupUseCase.replaceGroup(bookUrls, safeGroupId)
|
||||
}.onError {
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("移动分组失败\n${it.localizedMessage}"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("移动分组失败\n${it.localizedMessage}"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -475,9 +501,9 @@ class CacheViewModel(
|
||||
}.onSuccess { deletedBookUrls ->
|
||||
_uiState.update { it.copy(deleteBookOriginal = deleteOriginal) }
|
||||
deletedBookUrls.forEach { cacheChapters.remove(it) }
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("删除成功"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("删除成功"))
|
||||
}.onError {
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("删除失败\n${it.localizedMessage}"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("删除失败\n${it.localizedMessage}"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -490,9 +516,33 @@ class CacheViewModel(
|
||||
cacheChapters[bookUrl] = hashSetOf()
|
||||
emitBookChanged(bookUrl)
|
||||
}
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("缓存已清理"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("缓存已清理"))
|
||||
}.onError {
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("清理缓存失败\n${it.localizedMessage}"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("清理缓存失败\n${it.localizedMessage}"))
|
||||
}
|
||||
}
|
||||
|
||||
private fun moveBookOrder(fromIndex: Int, toIndex: Int) {
|
||||
val books = uiState.value.books
|
||||
if (fromIndex !in books.indices || toIndex !in books.indices || fromIndex == toIndex) {
|
||||
return
|
||||
}
|
||||
val reorderedBooks = books.toMutableList().apply { move(fromIndex, toIndex) }
|
||||
val isDescending = uiState.value.bookSortOrder == 1
|
||||
val maxOrder = reorderedBooks.size
|
||||
reorderedBooks.forEachIndexed { index, book ->
|
||||
book.order = if (isDescending) maxOrder - index else index + 1
|
||||
}
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
books = reorderedBooks,
|
||||
cacheVersion = it.cacheVersion + 1
|
||||
)
|
||||
}
|
||||
execute {
|
||||
bookDao.update(*reorderedBooks.toTypedArray())
|
||||
}.onError {
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("排序保存失败\n${it.localizedMessage}"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -506,13 +556,13 @@ class CacheViewModel(
|
||||
)
|
||||
}.onSuccess { count ->
|
||||
if (count > 0) {
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("已加入缓存队列: $count 本"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("已加入缓存队列: $count 本"))
|
||||
} else {
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("没有可缓存的书籍"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("没有可缓存的书籍"))
|
||||
}
|
||||
syncDownloadRunning()
|
||||
}.onError {
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("批量缓存失败\n${it.localizedMessage}"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("批量缓存失败\n${it.localizedMessage}"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -531,9 +581,9 @@ class CacheViewModel(
|
||||
cacheChapters.remove(result.oldBookUrl)
|
||||
cacheChapters[result.book.bookUrl] = hashSetOf()
|
||||
emitBookChanged(result.book.bookUrl)
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("换源完成"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("换源完成"))
|
||||
}.onError {
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("换源失败\n${it.localizedMessage}"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("换源失败\n${it.localizedMessage}"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -626,9 +676,9 @@ class CacheViewModel(
|
||||
cacheChapters[result.book.bookUrl] = hashSetOf()
|
||||
removePreviewItem(oldBookUrl)
|
||||
emitBookChanged(result.book.bookUrl)
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("迁移完成"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("迁移完成"))
|
||||
}.onError {
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("迁移失败\n${it.localizedMessage}"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("迁移失败\n${it.localizedMessage}"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,9 +754,9 @@ class CacheViewModel(
|
||||
bookChapterDao.insert(*candidate.chapters.toTypedArray())
|
||||
candidate.book
|
||||
}.onSuccess {
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("已添加到书架"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("已添加到书架"))
|
||||
}.onError {
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("添加书籍失败\n${it.localizedMessage}"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("添加书籍失败\n${it.localizedMessage}"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,7 +767,7 @@ class CacheViewModel(
|
||||
}
|
||||
book
|
||||
}.onSuccess {
|
||||
_effects.tryEmit(CacheEffect.OpenBookInfo(it.bookUrl, it.name, it.author))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.OpenBookInfo(it.bookUrl, it.name, it.author))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -755,9 +805,9 @@ class CacheViewModel(
|
||||
}.onSuccess {
|
||||
cacheChapters.clear()
|
||||
_uiState.update { it.copy(batchChangePreviewItems = emptyList()) }
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("批量迁移完成"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("批量迁移完成"))
|
||||
}.onError {
|
||||
_effects.tryEmit(CacheEffect.ShowMessage("批量迁移失败\n${it.localizedMessage}"))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.ShowMessage("批量迁移失败\n${it.localizedMessage}"))
|
||||
}.onFinally {
|
||||
_uiState.update {
|
||||
it.copy(
|
||||
@@ -781,7 +831,7 @@ class CacheViewModel(
|
||||
|
||||
private fun emitBookChanged(bookUrl: String) {
|
||||
_uiState.update { it.copy(cacheVersion = it.cacheVersion + 1) }
|
||||
_effects.tryEmit(CacheEffect.NotifyBookChanged(bookUrl))
|
||||
_effects.tryEmit(BookshelfManageScreenEffect.NotifyBookChanged(bookUrl))
|
||||
}
|
||||
|
||||
}
|
||||
@@ -138,6 +138,14 @@ object BookshelfConfig {
|
||||
*/
|
||||
var bookshelfCoverShadow by prefDelegate(PreferKey.bookshelfCoverShadow, false)
|
||||
|
||||
/**
|
||||
* 书架搜索按钮是否直接跳转搜索页
|
||||
*/
|
||||
var bookshelfSearchActionDirectToSearch by prefDelegate(
|
||||
PreferKey.bookshelfSearchActionDirectToSearch,
|
||||
true
|
||||
)
|
||||
|
||||
/**
|
||||
* 启动时自动刷新书架
|
||||
*/
|
||||
|
||||
+6
-5
@@ -1,10 +1,10 @@
|
||||
package io.legado.app.ui.config.cacheConfig
|
||||
package io.legado.app.ui.config.bookshelfConfig
|
||||
|
||||
import io.legado.app.constant.PreferKey
|
||||
import io.legado.app.data.dao.BookGroupDao
|
||||
import io.legado.app.ui.config.prefDelegate
|
||||
|
||||
class CacheConfig(
|
||||
class BookshelfManageScreenConfig(
|
||||
private val bookGroupDao: BookGroupDao
|
||||
) {
|
||||
|
||||
@@ -28,9 +28,10 @@ class CacheConfig(
|
||||
|
||||
var parallelExportBook by prefDelegate(PreferKey.parallelExportBook, false)
|
||||
|
||||
var bookshelfSort by prefDelegate(PreferKey.bookshelfSort, 0)
|
||||
|
||||
fun getBookSortByGroupId(groupId: Long): Int {
|
||||
return bookGroupDao.getByID(groupId)?.getRealBookSort() ?: bookshelfSort
|
||||
return bookGroupDao.getByID(groupId)?.getRealBookSort() ?: BookshelfConfig.bookshelfSort
|
||||
}
|
||||
|
||||
val bookshelfSortOrder: Int
|
||||
get() = BookshelfConfig.bookshelfSortOrder
|
||||
}
|
||||
@@ -47,7 +47,7 @@ import io.legado.app.ui.book.search.SearchIntent
|
||||
import io.legado.app.ui.book.search.SearchScreen
|
||||
import io.legado.app.ui.book.search.SearchViewModel
|
||||
import io.legado.app.ui.book.source.manage.BookSourceActivity
|
||||
import io.legado.app.ui.book.cache.CacheRouteScreen
|
||||
import io.legado.app.ui.book.manage.BookshelfManageRouteScreen
|
||||
import io.legado.app.ui.book.read.ReadBookActivity
|
||||
import io.legado.app.ui.config.ConfigNavScreen
|
||||
import io.legado.app.ui.config.ConfigTag
|
||||
@@ -161,7 +161,7 @@ open class MainActivity : BaseComposeActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fun createCacheIntent(
|
||||
fun createBookshelfManageScreenIntent(
|
||||
context: Context,
|
||||
groupId: Long = -1L
|
||||
): Intent {
|
||||
@@ -171,6 +171,11 @@ open class MainActivity : BaseComposeActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fun createCacheIntent(
|
||||
context: Context,
|
||||
groupId: Long = -1L
|
||||
): Intent = createBookshelfManageScreenIntent(context, groupId)
|
||||
|
||||
fun createSearchIntent(
|
||||
context: Context,
|
||||
key: String? = null,
|
||||
@@ -463,7 +468,7 @@ open class MainActivity : BaseComposeActivity() {
|
||||
}
|
||||
|
||||
entry<MainRouteCache> { route ->
|
||||
CacheRouteScreen(
|
||||
BookshelfManageRouteScreen(
|
||||
groupId = route.groupId,
|
||||
onBackClick = { navigateBack(backStack) }
|
||||
)
|
||||
|
||||
@@ -35,6 +35,7 @@ import io.legado.app.constant.BookType
|
||||
import io.legado.app.data.entities.BookGroup
|
||||
import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.widget.components.card.GlassCard
|
||||
import io.legado.app.ui.widget.components.card.TextCard
|
||||
import io.legado.app.ui.widget.components.cover.CoilBookCover
|
||||
import io.legado.app.ui.widget.components.cover.BookshelfCover
|
||||
@@ -55,6 +56,7 @@ fun BookshelfItem(
|
||||
title: String,
|
||||
modifier: Modifier = Modifier,
|
||||
isSelected: Boolean = false,
|
||||
titleEnd: @Composable (() -> Unit)? = null,
|
||||
subTitle: String? = null,
|
||||
desc: String? = null,
|
||||
extra: @Composable (RowScope.() -> Unit)? = null,
|
||||
@@ -63,12 +65,12 @@ fun BookshelfItem(
|
||||
titleMaxLines: Int = 2,
|
||||
coverShadow: Boolean = false,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit
|
||||
onLongClick: (() -> Unit)?
|
||||
) {
|
||||
val selectedColor = if (isSelected) {
|
||||
LegadoTheme.colorScheme.secondaryContainer.copy(alpha = 0.55f)
|
||||
LegadoTheme.colorScheme.secondaryContainer
|
||||
} else {
|
||||
Color.Transparent
|
||||
LegadoTheme.colorScheme.surface
|
||||
}
|
||||
if (isGrid) {
|
||||
Box(
|
||||
@@ -142,59 +144,66 @@ fun BookshelfItem(
|
||||
} else {
|
||||
// 列表布局
|
||||
Column {
|
||||
Row(
|
||||
GlassCard(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(MaterialTheme.shapes.small)
|
||||
.background(selectedColor)
|
||||
.combinedClickable(
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
)
|
||||
.padding(horizontal = 16.dp, vertical = 8.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
.padding(all = 4.dp),
|
||||
cornerRadius = 8.dp,
|
||||
containerColor = selectedColor,
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick
|
||||
) {
|
||||
cover(
|
||||
Modifier
|
||||
.width(if (!isCompact) 80.dp else 56.dp)
|
||||
.padding(end = 12.dp)
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.Center
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(all = 4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AppText(
|
||||
text = title,
|
||||
style = LegadoTheme.typography.titleMedium.copy(fontWeight = FontWeight.Bold),
|
||||
maxLines = if (!isCompact) 2 else 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
cover(
|
||||
Modifier
|
||||
.width(if (!isCompact) 80.dp else 56.dp)
|
||||
.padding(end = 12.dp)
|
||||
)
|
||||
subTitle?.let {
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
AppText(
|
||||
text = it,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
text = title,
|
||||
style = LegadoTheme.typography.titleMediumEmphasized,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
modifier = Modifier.weight(1f)
|
||||
)
|
||||
titleEnd?.invoke()
|
||||
}
|
||||
if (!isCompact) {
|
||||
desc?.let {
|
||||
subTitle?.let {
|
||||
AppText(
|
||||
text = it,
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.padding(top = 2.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
extra?.let {
|
||||
Row(
|
||||
modifier = Modifier.padding(top = 2.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
content = it
|
||||
)
|
||||
if (!isCompact) {
|
||||
desc?.let {
|
||||
AppText(
|
||||
text = it,
|
||||
style = LegadoTheme.typography.labelSmallEmphasized,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
extra?.let {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
content = it
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -325,7 +334,7 @@ fun BookGroupItemGrid(
|
||||
coverShadow: Boolean = false,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit
|
||||
onLongClick: (() -> Unit)?
|
||||
) {
|
||||
BookshelfItem(
|
||||
isGrid = true,
|
||||
@@ -362,7 +371,7 @@ fun BookGroupItemList(
|
||||
coverShadow: Boolean = false,
|
||||
modifier: Modifier = Modifier,
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit
|
||||
onLongClick: (() -> Unit)?
|
||||
) {
|
||||
BookshelfItem(
|
||||
isGrid = false,
|
||||
@@ -397,9 +406,10 @@ fun BookItem(
|
||||
isSearchMode: Boolean = false,
|
||||
searchKey: String = "",
|
||||
onClick: () -> Unit,
|
||||
onLongClick: () -> Unit
|
||||
onLongClick: (() -> Unit)?
|
||||
) {
|
||||
val unreadCount = book.getUnreadChapterNum()
|
||||
val unreadText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null
|
||||
val bookTypeLabel = if (BookshelfConfig.showTip) {
|
||||
when {
|
||||
book.isAudio -> stringResource(R.string.audio)
|
||||
@@ -427,6 +437,16 @@ fun BookItem(
|
||||
isCompact = isCompact,
|
||||
isSelected = isSelected,
|
||||
modifier = modifier,
|
||||
titleEnd = if (layoutMode == 0 && unreadText != null) {
|
||||
{
|
||||
TextCard(
|
||||
text = unreadText,
|
||||
cornerRadius = 4.dp,
|
||||
horizontalPadding = 4.dp,
|
||||
verticalPadding = 0.dp
|
||||
)
|
||||
}
|
||||
} else null,
|
||||
cover = { modifier ->
|
||||
BookshelfCover(
|
||||
name = book.name,
|
||||
@@ -434,7 +454,7 @@ fun BookItem(
|
||||
path = book.getDisplayCover(),
|
||||
isUpdating = isUpdating,
|
||||
modifier = modifier,
|
||||
badgeText = if (BookshelfConfig.showUnread && unreadCount > 0) unreadCount.toString() else null,
|
||||
badgeText = if (layoutMode != 0) unreadText else null,
|
||||
showBadgeDot = BookshelfConfig.showUnread && BookshelfConfig.showUnreadNew && book.isNew,
|
||||
leftBottomText = matchedSourceLabel ?: bookTypeLabel
|
||||
)
|
||||
@@ -445,19 +465,19 @@ fun BookItem(
|
||||
} else {
|
||||
book.author
|
||||
},
|
||||
desc = stringResource(R.string.read_dur_progress, book.durChapterTitle ?: ""),
|
||||
desc = book.durChapterTitle ?: "",
|
||||
extra = {
|
||||
if (BookshelfConfig.showLastUpdateTime && !book.isLocal) {
|
||||
AppText(
|
||||
text = book.latestChapterTime.toTimeAgo(),
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
color = if (layoutMode != 0 || !isCompact) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.secondary,
|
||||
style = LegadoTheme.typography.labelSmallEmphasized,
|
||||
color = if (layoutMode != 0 || !isCompact) MaterialTheme.colorScheme.outline else MaterialTheme.colorScheme.outline,
|
||||
modifier = Modifier.padding(end = 4.dp)
|
||||
)
|
||||
}
|
||||
AppText(
|
||||
text = book.latestChapterTitle ?: "",
|
||||
style = LegadoTheme.typography.bodySmall,
|
||||
style = LegadoTheme.typography.labelSmallEmphasized.copy(color = LegadoTheme.colorScheme.outline),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
modifier = Modifier.weight(1f)
|
||||
|
||||
@@ -191,6 +191,13 @@ fun BookshelfConfigSheet(
|
||||
onCheckedChange = { BookshelfConfig.bookshelfCoverShadow = it }
|
||||
)
|
||||
|
||||
CompactSwitchSettingItem(
|
||||
title = "搜索按钮优先打开筛选栏",
|
||||
checked = BookshelfConfig.bookshelfSearchActionDirectToSearch,
|
||||
color = MaterialTheme.colorScheme.surface,
|
||||
onCheckedChange = { BookshelfConfig.bookshelfSearchActionDirectToSearch = it }
|
||||
)
|
||||
|
||||
// Switches
|
||||
CompactSwitchSettingItem(
|
||||
title = stringResource(R.string.show_unread),
|
||||
|
||||
@@ -23,12 +23,14 @@ import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.grid.GridCells
|
||||
import androidx.compose.foundation.lazy.grid.items
|
||||
import androidx.compose.foundation.lazy.grid.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||
import androidx.compose.foundation.pager.HorizontalPager
|
||||
import androidx.compose.foundation.pager.rememberPagerState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.FormatListBulleted
|
||||
import androidx.compose.material.icons.filled.Bookmarks
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
import androidx.compose.material.icons.filled.CloudDownload
|
||||
import androidx.compose.material.icons.filled.Download
|
||||
import androidx.compose.material.icons.filled.Edit
|
||||
import androidx.compose.material.icons.filled.GridView
|
||||
@@ -38,6 +40,7 @@ import androidx.compose.material.icons.filled.Link
|
||||
import androidx.compose.material.icons.filled.Refresh
|
||||
import androidx.compose.material.icons.filled.Save
|
||||
import androidx.compose.material.icons.filled.SelectAll
|
||||
import androidx.compose.material.icons.filled.UploadFile
|
||||
import androidx.compose.material.icons.filled.Wifi
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||
@@ -61,10 +64,12 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.hapticfeedback.HapticFeedbackType
|
||||
import androidx.compose.ui.platform.ClipEntry
|
||||
import androidx.compose.ui.platform.LocalClipboard
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalHapticFeedback
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.window.Dialog
|
||||
@@ -77,6 +82,7 @@ import io.legado.app.ui.config.bookshelfConfig.BookshelfConfig
|
||||
import io.legado.app.ui.theme.LegadoTheme
|
||||
import io.legado.app.ui.theme.ThemeResolver
|
||||
import io.legado.app.ui.theme.adaptiveContentPadding
|
||||
import io.legado.app.ui.theme.adaptiveContentPaddingBookshelf
|
||||
import io.legado.app.ui.theme.adaptiveHorizontalPadding
|
||||
import io.legado.app.ui.theme.adaptiveHorizontalPaddingTab
|
||||
import io.legado.app.ui.widget.components.EmptyMessage
|
||||
@@ -96,12 +102,15 @@ 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.tabRow.AppTabRow
|
||||
import io.legado.app.ui.widget.components.text.AppText
|
||||
import io.legado.app.utils.move
|
||||
import io.legado.app.utils.readText
|
||||
import io.legado.app.utils.toastOnUi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.compose.koinViewModel
|
||||
import sh.calvin.reorderable.ReorderableItem
|
||||
import sh.calvin.reorderable.rememberReorderableLazyGridState
|
||||
|
||||
@OptIn(
|
||||
ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class,
|
||||
@@ -315,10 +324,6 @@ fun BookshelfScreen(
|
||||
if (isLandscape) BookshelfConfig.bookshelfLayoutGridLandscape else BookshelfConfig.bookshelfLayoutGridPortrait
|
||||
val bookshelfLayoutList =
|
||||
if (isLandscape) BookshelfConfig.bookshelfLayoutListLandscape else BookshelfConfig.bookshelfLayoutListPortrait
|
||||
val totalHorizontalPadding =
|
||||
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) 12.dp else 16.dp
|
||||
val gridContentHorizontalPadding = totalHorizontalPadding / 2
|
||||
val gridInnerHorizontalPadding = totalHorizontalPadding / 2
|
||||
val currentMenuGroupId = if (uiState.isSearch) uiState.selectedGroupId else currentTabGroupId
|
||||
val editStickySummary = if (isEditMode) {
|
||||
BookshelfEditStickySummary(
|
||||
@@ -337,9 +342,13 @@ fun BookshelfScreen(
|
||||
state = uiState,
|
||||
showSearchAction = true,
|
||||
onSearchToggle = { active ->
|
||||
viewModel.setSearchMode(active)
|
||||
if (!active && uiState.selectedGroupId != currentTabGroupId) {
|
||||
viewModel.changeGroup(currentTabGroupId)
|
||||
if (BookshelfConfig.bookshelfSearchActionDirectToSearch) {
|
||||
onNavigateToSearch(uiState.searchKey.trim())
|
||||
} else {
|
||||
viewModel.setSearchMode(active)
|
||||
if (!active && uiState.selectedGroupId != currentTabGroupId) {
|
||||
viewModel.changeGroup(currentTabGroupId)
|
||||
}
|
||||
}
|
||||
},
|
||||
onSearchQueryChange = { viewModel.setSearchKey(it) },
|
||||
@@ -441,14 +450,14 @@ fun BookshelfScreen(
|
||||
leadingIcon = { Icon(Icons.Default.Edit, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = stringResource(R.string.cache_export),
|
||||
text = stringResource(R.string.bookshelf_management),
|
||||
onClick = {
|
||||
val groupId =
|
||||
uiState.groups.getOrNull(uiState.selectedGroupIndex)?.groupId ?: -1L
|
||||
onNavigateToCache(groupId)
|
||||
dismiss()
|
||||
},
|
||||
leadingIcon = { Icon(Icons.Default.Download, null) }
|
||||
leadingIcon = { Icon(Icons.Default.Bookmarks, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = stringResource(R.string.export_bookshelf),
|
||||
@@ -456,11 +465,12 @@ fun BookshelfScreen(
|
||||
showExportSheet = true
|
||||
dismiss()
|
||||
},
|
||||
leadingIcon = { Icon(Icons.Default.ImportExport, null) }
|
||||
leadingIcon = { Icon(Icons.Default.UploadFile, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = stringResource(R.string.import_bookshelf),
|
||||
onClick = { showImportSheet = true; dismiss() }
|
||||
onClick = { showImportSheet = true; dismiss() },
|
||||
leadingIcon = { Icon(Icons.Default.CloudDownload, null) }
|
||||
)
|
||||
RoundDropdownMenuItem(
|
||||
text = stringResource(R.string.log),
|
||||
@@ -633,12 +643,11 @@ fun BookshelfScreen(
|
||||
FastScrollLazyVerticalGrid(
|
||||
columns = GridCells.Fixed(folderColumns.coerceAtLeast(1)),
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = if (isGridMode) gridInnerHorizontalPadding else 0.dp),
|
||||
contentPadding = adaptiveContentPadding(
|
||||
.fillMaxSize(),
|
||||
contentPadding = adaptiveContentPaddingBookshelf(
|
||||
top = paddingValues.calculateTopPadding(),
|
||||
bottom = 120.dp,
|
||||
horizontal = if (isGridMode) gridContentHorizontalPadding else 0.dp
|
||||
horizontal = if (isGridMode) 8.dp else 4.dp
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
|
||||
@@ -701,7 +710,9 @@ fun BookshelfScreen(
|
||||
bookshelfLayoutList = bookshelfLayoutList,
|
||||
isEditMode = isEditMode,
|
||||
selectedBookUrls = selectedBookUrls,
|
||||
canReorderBooks = false,
|
||||
onToggleBookSelection = { toggleBookSelection(it.bookUrl) },
|
||||
onSaveBookOrder = {},
|
||||
onGlobalSearch = { onNavigateToSearch(uiState.searchKey.trim()) },
|
||||
onBookClick = onBookClick,
|
||||
onBookLongClick = onBookLongClick
|
||||
@@ -725,15 +736,21 @@ fun BookshelfScreen(
|
||||
uiState = uiState,
|
||||
bookshelfLayoutMode = bookshelfLayoutMode,
|
||||
bookshelfLayoutGrid = bookshelfLayoutGrid,
|
||||
bookshelfLayoutList = bookshelfLayoutList,
|
||||
isEditMode = isEditMode,
|
||||
selectedBookUrls = selectedBookUrls,
|
||||
onToggleBookSelection = { toggleBookSelection(it.bookUrl) },
|
||||
onGlobalSearch = { onNavigateToSearch(uiState.searchKey.trim()) },
|
||||
onBookClick = onBookClick,
|
||||
onBookLongClick = onBookLongClick
|
||||
)
|
||||
}
|
||||
bookshelfLayoutList = bookshelfLayoutList,
|
||||
isEditMode = isEditMode,
|
||||
selectedBookUrls = selectedBookUrls,
|
||||
canReorderBooks = isEditMode &&
|
||||
!uiState.isSearch &&
|
||||
group.getRealBookSort() == 3,
|
||||
onToggleBookSelection = { toggleBookSelection(it.bookUrl) },
|
||||
onSaveBookOrder = { reorderedBooks ->
|
||||
viewModel.saveBookOrder(reorderedBooks)
|
||||
},
|
||||
onGlobalSearch = { onNavigateToSearch(uiState.searchKey.trim()) },
|
||||
onBookClick = onBookClick,
|
||||
onBookLongClick = onBookLongClick
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -960,7 +977,9 @@ fun BookshelfPage(
|
||||
bookshelfLayoutList: Int,
|
||||
isEditMode: Boolean,
|
||||
selectedBookUrls: Set<String>,
|
||||
canReorderBooks: Boolean,
|
||||
onToggleBookSelection: (BookShelfItem) -> Unit,
|
||||
onSaveBookOrder: (books: List<BookShelfItem>) -> Unit,
|
||||
onGlobalSearch: () -> Unit,
|
||||
onBookClick: (BookShelfItem) -> Unit,
|
||||
onBookLongClick: (BookShelfItem) -> Unit
|
||||
@@ -998,51 +1017,112 @@ fun BookshelfPage(
|
||||
if (ThemeResolver.isMiuixEngine(LegadoTheme.composeEngine)) 12.dp else 16.dp
|
||||
val gridContentHorizontalPadding = totalHorizontalPadding / 2
|
||||
val gridInnerHorizontalPadding = totalHorizontalPadding / 2
|
||||
val hapticFeedback = LocalHapticFeedback.current
|
||||
var draggingBooks by remember { mutableStateOf<List<BookShelfItem>?>(null) }
|
||||
var pendingSavedBooks by remember { mutableStateOf<List<BookShelfItem>?>(null) }
|
||||
val displayBooks = draggingBooks ?: pendingSavedBooks ?: books
|
||||
LaunchedEffect(books, pendingSavedBooks, canReorderBooks) {
|
||||
if (!canReorderBooks) {
|
||||
draggingBooks = null
|
||||
pendingSavedBooks = null
|
||||
return@LaunchedEffect
|
||||
}
|
||||
val pending = pendingSavedBooks ?: return@LaunchedEffect
|
||||
if (books.map { it.bookUrl } == pending.map { it.bookUrl }) {
|
||||
pendingSavedBooks = null
|
||||
}
|
||||
}
|
||||
val gridState = rememberLazyGridState()
|
||||
val reorderableState = rememberReorderableLazyGridState(gridState) { from, to ->
|
||||
if (canReorderBooks) {
|
||||
draggingBooks = displayBooks.toMutableList().apply {
|
||||
move(from.index, to.index)
|
||||
}
|
||||
hapticFeedback.performHapticFeedback(HapticFeedbackType.SegmentFrequentTick)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(reorderableState.isAnyItemDragging) {
|
||||
if (!reorderableState.isAnyItemDragging) {
|
||||
draggingBooks?.let { reorderedBooks ->
|
||||
pendingSavedBooks = reorderedBooks
|
||||
onSaveBookOrder(reorderedBooks)
|
||||
draggingBooks = null
|
||||
}
|
||||
}
|
||||
}
|
||||
FastScrollLazyVerticalGrid(
|
||||
columns = GridCells.Fixed(columns.coerceAtLeast(1)),
|
||||
state = gridState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = if (isGridMode) gridInnerHorizontalPadding else 0.dp),
|
||||
contentPadding = adaptiveContentPadding(
|
||||
.fillMaxSize(),
|
||||
contentPadding = adaptiveContentPaddingBookshelf(
|
||||
top = paddingValues.calculateTopPadding(),
|
||||
bottom = 120.dp,
|
||||
horizontal = if (isGridMode) gridContentHorizontalPadding else 0.dp
|
||||
horizontal = if (isGridMode) 8.dp else 4.dp
|
||||
),
|
||||
verticalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(if (isGridMode) 8.dp else 0.dp),
|
||||
showFastScroll = BookshelfConfig.showBookshelfFastScroller
|
||||
) {
|
||||
items(books, key = { it.bookUrl }) { book ->
|
||||
items(displayBooks, key = { it.bookUrl }) { book ->
|
||||
val isSelected = selectedBookUrls.contains(book.bookUrl)
|
||||
BookItem(
|
||||
book = book,
|
||||
modifier = Modifier.animateItem(),
|
||||
layoutMode = bookshelfLayoutMode,
|
||||
isSelected = isSelected,
|
||||
gridStyle = BookshelfConfig.bookshelfGridLayout,
|
||||
isCompact = BookshelfConfig.bookshelfLayoutCompact,
|
||||
isUpdating = uiState.updatingBooks.contains(book.bookUrl),
|
||||
titleSmallFont = BookshelfConfig.bookshelfTitleSmallFont,
|
||||
titleCenter = BookshelfConfig.bookshelfTitleCenter,
|
||||
titleMaxLines = BookshelfConfig.bookshelfTitleMaxLines,
|
||||
coverShadow = BookshelfConfig.bookshelfCoverShadow,
|
||||
isSearchMode = uiState.isSearch,
|
||||
searchKey = uiState.searchKey,
|
||||
onClick = {
|
||||
if (isEditMode) {
|
||||
onToggleBookSelection(book)
|
||||
ReorderableItem(
|
||||
state = reorderableState,
|
||||
key = book.bookUrl,
|
||||
enabled = canReorderBooks
|
||||
) {
|
||||
BookItem(
|
||||
book = book,
|
||||
modifier = Modifier.then(
|
||||
if (canReorderBooks) {
|
||||
Modifier.longPressDraggableHandle(
|
||||
onDragStarted = {
|
||||
draggingBooks = displayBooks
|
||||
hapticFeedback.performHapticFeedback(
|
||||
HapticFeedbackType.GestureThresholdActivate
|
||||
)
|
||||
},
|
||||
onDragStopped = {
|
||||
hapticFeedback.performHapticFeedback(
|
||||
HapticFeedbackType.GestureEnd
|
||||
)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
),
|
||||
layoutMode = bookshelfLayoutMode,
|
||||
isSelected = isSelected,
|
||||
gridStyle = BookshelfConfig.bookshelfGridLayout,
|
||||
isCompact = BookshelfConfig.bookshelfLayoutCompact,
|
||||
isUpdating = uiState.updatingBooks.contains(book.bookUrl),
|
||||
titleSmallFont = BookshelfConfig.bookshelfTitleSmallFont,
|
||||
titleCenter = BookshelfConfig.bookshelfTitleCenter,
|
||||
titleMaxLines = BookshelfConfig.bookshelfTitleMaxLines,
|
||||
coverShadow = BookshelfConfig.bookshelfCoverShadow,
|
||||
isSearchMode = uiState.isSearch,
|
||||
searchKey = uiState.searchKey,
|
||||
onClick = {
|
||||
if (isEditMode) {
|
||||
onToggleBookSelection(book)
|
||||
} else {
|
||||
onBookClick(book)
|
||||
}
|
||||
},
|
||||
onLongClick = if (canReorderBooks) {
|
||||
null
|
||||
} else {
|
||||
onBookClick(book)
|
||||
{
|
||||
if (isEditMode) {
|
||||
onToggleBookSelection(book)
|
||||
} else {
|
||||
onBookLongClick(book)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
if (isEditMode) {
|
||||
onToggleBookSelection(book)
|
||||
} else {
|
||||
onBookLongClick(book)
|
||||
}
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -392,6 +392,24 @@ class BookshelfViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
fun saveBookOrder(reorderedBooks: List<BookShelfItem>) {
|
||||
if (reorderedBooks.isEmpty()) return
|
||||
val isDescending = BookshelfConfig.bookshelfSortOrder == 1
|
||||
val maxOrder = reorderedBooks.size
|
||||
execute {
|
||||
val updates = reorderedBooks.mapIndexedNotNull { index, book ->
|
||||
appDb.bookDao.getBook(book.bookUrl)?.apply {
|
||||
order = if (isDescending) maxOrder - index else index + 1
|
||||
}
|
||||
}
|
||||
if (updates.isNotEmpty()) {
|
||||
appDb.bookDao.update(*updates.toTypedArray())
|
||||
}
|
||||
}.onError {
|
||||
context.toastOnUi("排序保存失败\n${it.localizedMessage}")
|
||||
}
|
||||
}
|
||||
|
||||
fun downloadBooks(bookUrls: Set<String>, downloadAllChapters: Boolean = false) {
|
||||
if (bookUrls.isEmpty()) return
|
||||
execute {
|
||||
|
||||
@@ -46,6 +46,8 @@ fun RssSortRouteScreen(
|
||||
var articleStyle by remember(sourceUrl) { mutableIntStateOf(0) }
|
||||
var redirectPolicy by remember(sourceUrl) { mutableStateOf(RedirectPolicy.ALLOW_ALL) }
|
||||
var screenTitle by remember(sourceUrl) { mutableStateOf("") }
|
||||
val setSourceVariableText = stringResource(R.string.set_source_variable)
|
||||
val errorText = stringResource(R.string.error)
|
||||
|
||||
var showReadRecordSheet by remember { mutableStateOf(false) }
|
||||
var readRecords by remember { mutableStateOf<List<RssReadRecord>>(emptyList()) }
|
||||
@@ -104,7 +106,7 @@ fun RssSortRouteScreen(
|
||||
val variable = withContext(Dispatchers.IO) { source.getVariable() }
|
||||
activity?.showDialogFragment(
|
||||
VariableDialog(
|
||||
context.getString(R.string.set_source_variable),
|
||||
setSourceVariableText,
|
||||
source.getKey(),
|
||||
variable,
|
||||
comment
|
||||
@@ -141,7 +143,7 @@ fun RssSortRouteScreen(
|
||||
viewModel.rssSource?.sourceUrl ?: sourceUrl.orEmpty()
|
||||
}
|
||||
if (openOrigin.isBlank()) {
|
||||
context.toastOnUi(context.getString(R.string.error))
|
||||
context.toastOnUi(errorText)
|
||||
} else {
|
||||
onOpenRead(record.title, openOrigin, null, record.record)
|
||||
}
|
||||
|
||||
@@ -78,3 +78,19 @@ fun adaptiveContentPadding(
|
||||
end = horizontal
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun adaptiveContentPaddingBookshelf(
|
||||
top: Dp,
|
||||
bottom: Dp,
|
||||
horizontal: Dp
|
||||
): PaddingValues {
|
||||
val adjustedTop = if (ThemeResolver.isMiuixEngine(composeEngine)) top + 12.dp else top + 8.dp
|
||||
val horizontal = if (ThemeResolver.isMiuixEngine(composeEngine)) 12.dp + horizontal else 4.dp + horizontal
|
||||
return PaddingValues(
|
||||
top = adjustedTop,
|
||||
bottom = bottom,
|
||||
start = horizontal,
|
||||
end = horizontal
|
||||
)
|
||||
}
|
||||
|
||||
@@ -136,27 +136,6 @@ fun TopBarNavigationButton(
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated(
|
||||
message = "Use TopBarNavigationButton for consistent TopBar naming.",
|
||||
replaceWith = ReplaceWith("TopBarNavigationButton(onClick, modifier, imageVector, contentDescription, style)")
|
||||
)
|
||||
@Composable
|
||||
fun TopbarNavigationButton(
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
imageVector: ImageVector = AppIcons.Back,
|
||||
contentDescription: String? = stringResource(id = R.string.back),
|
||||
style: TopBarButtonVariant = TopBarButtonVariant.Filled
|
||||
) {
|
||||
TopBarNavigationButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
imageVector = imageVector,
|
||||
contentDescription = contentDescription,
|
||||
style = style
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun TopBarActionButton(
|
||||
onClick: () -> Unit,
|
||||
|
||||
+1
-1
@@ -279,7 +279,7 @@ fun VerticalGridFastScroller(
|
||||
contentPadding: PaddingValues,
|
||||
modifier: Modifier = Modifier,
|
||||
thumbAllowed: () -> Boolean = { true },
|
||||
thumbColor: Color = MaterialTheme.colorScheme.primary,
|
||||
thumbColor: Color = LegadoTheme.colorScheme.primary,
|
||||
topContentPadding: Dp = Dp.Hairline,
|
||||
bottomContentPadding: Dp = Dp.Hairline,
|
||||
endContentPadding: Dp = Dp.Hairline,
|
||||
|
||||
Reference in New Issue
Block a user