[优化] 合并下载和原本的书架管理界面

This commit is contained in:
HapeLee
2026-04-26 15:13:50 +08:00
parent c1d9d57203
commit 1e3ea159e7
10 changed files with 1007 additions and 310 deletions
@@ -17,7 +17,11 @@ import io.legado.app.data.repository.SearchRepository
import io.legado.app.data.repository.SearchRepositoryImpl
import io.legado.app.data.repository.SearchContentRepository
import io.legado.app.data.repository.UploadRepository
import io.legado.app.domain.usecase.BatchCacheDownloadUseCase
import io.legado.app.domain.usecase.ClearBookCacheUseCase
import io.legado.app.domain.usecase.DeleteBooksUseCase
import io.legado.app.domain.usecase.ExploreKindUiUseCase
import io.legado.app.domain.usecase.UpdateBooksGroupUseCase
import io.legado.app.help.coil.CoverFetcher
import io.legado.app.help.coil.CoverInterceptor
import io.legado.app.help.http.okHttpClient
@@ -67,6 +71,7 @@ val appModule = module {
single { get<AppDatabase>().bookDao }
single { get<AppDatabase>().bookChapterDao }
single { get<AppDatabase>().bookGroupDao }
single { get<AppDatabase>().bookSourceDao }
singleOf(::ReadRecordRepository)
singleOf(::BookRepository)
@@ -74,6 +79,10 @@ val appModule = module {
singleOf(::SearchContentRepository)
singleOf(::RemoteBookRepository)
singleOf(::ExploreKindUiUseCase)
singleOf(::BatchCacheDownloadUseCase)
singleOf(::ClearBookCacheUseCase)
singleOf(::DeleteBooksUseCase)
singleOf(::UpdateBooksGroupUseCase)
singleOf(::CacheConfig)
single<UploadRepository> { DirectLinkUploadRepository() }
@@ -125,7 +134,19 @@ val appModule = module {
viewModelOf(::ExploreViewModel)
viewModelOf(::RssViewModel)
viewModelOf(::SearchViewModel)
viewModelOf(::CacheViewModel)
viewModel {
CacheViewModel(
application = get(),
bookDao = get(),
bookGroupDao = get(),
bookChapterDao = get(),
cacheConfig = get(),
batchCacheDownloadUseCase = get(),
clearBookCacheUseCase = get(),
deleteBooksUseCase = get(),
updateBooksGroupUseCase = get()
)
}
viewModel { (route: ReplaceEditRoute) ->
ReplaceEditViewModel(
@@ -0,0 +1,63 @@
package io.legado.app.domain.usecase
import android.content.Context
import io.legado.app.constant.BookType
import io.legado.app.data.dao.BookDao
import io.legado.app.data.entities.Book
import io.legado.app.model.CacheBook
class BatchCacheDownloadUseCase(
private val bookDao: BookDao
) {
suspend fun execute(
context: Context,
bookUrls: Set<String>,
downloadAllChapters: Boolean,
skipAudioBooks: Boolean = false
): Int {
if (bookUrls.isEmpty()) return 0
var count = 0
bookUrls.forEach { bookUrl ->
val book = bookDao.getBook(bookUrl) ?: return@forEach
if (startIfNeeded(context, book, downloadAllChapters, skipAudioBooks)) {
count++
}
}
return count
}
fun execute(
context: Context,
books: List<Book>,
downloadAllChapters: Boolean,
skipAudioBooks: Boolean = false
): Int {
if (books.isEmpty()) return 0
var count = 0
books.forEach { book ->
if (startIfNeeded(context, book, downloadAllChapters, skipAudioBooks)) {
count++
}
}
return count
}
private fun startIfNeeded(
context: Context,
book: Book,
downloadAllChapters: Boolean,
skipAudioBooks: Boolean
): Boolean {
if (book.type and BookType.local > 0) return false
if (skipAudioBooks && book.type and BookType.audio > 0) return false
val indices = if (downloadAllChapters) {
(0..book.lastChapterIndex).toList()
} else {
(book.durChapterIndex..book.lastChapterIndex).toList()
}
if (indices.isEmpty()) return false
CacheBook.start(context, book, indices)
return true
}
}
@@ -0,0 +1,22 @@
package io.legado.app.domain.usecase
import io.legado.app.data.dao.BookDao
import io.legado.app.data.entities.Book
import io.legado.app.help.book.BookHelp
class ClearBookCacheUseCase(
private val bookDao: BookDao
) {
fun execute(book: Book): String {
BookHelp.clearCache(book)
return book.bookUrl
}
suspend fun execute(bookUrls: Set<String>): List<String> {
if (bookUrls.isEmpty()) return emptyList()
return bookUrls.mapNotNull { bookUrl ->
bookDao.getBook(bookUrl)?.let { execute(it) }
}
}
}
@@ -0,0 +1,33 @@
package io.legado.app.domain.usecase
import io.legado.app.data.dao.BookChapterDao
import io.legado.app.data.dao.BookDao
import io.legado.app.data.dao.BookSourceDao
import io.legado.app.help.book.isLocal
import io.legado.app.model.SourceCallBack
import io.legado.app.model.localBook.LocalBook
class DeleteBooksUseCase(
private val bookDao: BookDao,
private val bookChapterDao: BookChapterDao,
private val bookSourceDao: BookSourceDao
) {
suspend fun execute(bookUrls: Set<String>, deleteOriginal: Boolean): List<String> {
if (bookUrls.isEmpty()) return emptyList()
val books = bookUrls.mapNotNull { bookDao.getBook(it) }
books.forEach { book ->
if (book.isLocal) {
LocalBook.deleteBook(book, deleteOriginal)
} else {
val source = bookSourceDao.getBookSource(book.origin)
SourceCallBack.callBackBook(SourceCallBack.DEL_BOOK_SHELF, source, book)
}
bookChapterDao.delByBook(book.bookUrl)
}
if (books.isNotEmpty()) {
bookDao.delete(*books.toTypedArray())
}
return books.map { it.bookUrl }
}
}
@@ -0,0 +1,29 @@
package io.legado.app.domain.usecase
import io.legado.app.data.dao.BookDao
class UpdateBooksGroupUseCase(
private val bookDao: BookDao
) {
suspend fun replaceGroup(bookUrls: Set<String>, groupId: Long) {
updateGroups(bookUrls) { groupId }
}
suspend fun updateGroups(bookUrls: Set<String>, transform: (Long) -> Long) {
if (bookUrls.isEmpty()) return
val updateBooks = bookUrls.mapNotNull { bookUrl ->
bookDao.getBook(bookUrl)?.let { book ->
val targetGroup = transform(book.group)
if (targetGroup == book.group) {
null
} else {
book.copy(group = targetGroup)
}
}
}
if (updateBooks.isNotEmpty()) {
bookDao.update(*updateBooks.toTypedArray())
}
}
}
File diff suppressed because it is too large Load Diff
@@ -9,6 +9,10 @@ import io.legado.app.data.dao.BookGroupDao
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookGroup
import io.legado.app.domain.usecase.BatchCacheDownloadUseCase
import io.legado.app.domain.usecase.ClearBookCacheUseCase
import io.legado.app.domain.usecase.DeleteBooksUseCase
import io.legado.app.domain.usecase.UpdateBooksGroupUseCase
import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.isAudio
import io.legado.app.help.book.isLocal
@@ -61,6 +65,10 @@ sealed interface 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
data class SetExportUseReplace(val enabled: Boolean) : CacheIntent
data class SetEnableCustomExport(val enabled: Boolean) : CacheIntent
data class SetExportNoChapterName(val enabled: Boolean) : CacheIntent
@@ -83,7 +91,11 @@ class CacheViewModel(
private val bookDao: BookDao,
private val bookGroupDao: BookGroupDao,
private val bookChapterDao: BookChapterDao,
val cacheConfig: CacheConfig
val cacheConfig: CacheConfig,
private val batchCacheDownloadUseCase: BatchCacheDownloadUseCase,
private val clearBookCacheUseCase: ClearBookCacheUseCase,
private val deleteBooksUseCase: DeleteBooksUseCase,
private val updateBooksGroupUseCase: UpdateBooksGroupUseCase
) : BaseViewModel(application) {
private val _uiState = MutableStateFlow(CacheUiState())
@@ -110,6 +122,10 @@ class CacheViewModel(
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.SetExportUseReplace -> {
cacheConfig.exportUseReplace = intent.enabled
syncExportConfig()
@@ -317,15 +333,16 @@ class CacheViewModel(
}
private fun startDownloadForVisibleBooks(books: List<Book>, downloadAllChapters: Boolean) {
books.forEach { book ->
val indices = if (downloadAllChapters) {
(0..book.lastChapterIndex).toList()
} else {
(book.durChapterIndex..book.lastChapterIndex).toList()
}
CacheBook.start(context, book, indices)
execute {
batchCacheDownloadUseCase.execute(
context = context,
books = books,
downloadAllChapters = downloadAllChapters,
skipAudioBooks = true
)
}.onFinally {
syncDownloadRunning()
}
syncDownloadRunning()
}
private fun toggleBookDownload(book: Book) {
@@ -338,10 +355,68 @@ class CacheViewModel(
syncDownloadRunning()
}
private fun moveBooksToGroup(bookUrls: Set<String>, groupId: Long) {
if (bookUrls.isEmpty()) return
val safeGroupId = groupId.coerceAtLeast(0L)
execute {
updateBooksGroupUseCase.replaceGroup(bookUrls, safeGroupId)
}.onError {
_effects.tryEmit(CacheEffect.ShowMessage("移动分组失败\n${it.localizedMessage}"))
}
}
private fun deleteBooks(bookUrls: Set<String>, deleteOriginal: Boolean) {
if (bookUrls.isEmpty()) return
execute {
deleteBooksUseCase.execute(bookUrls, deleteOriginal)
}.onSuccess { deletedBookUrls ->
deletedBookUrls.forEach { cacheChapters.remove(it) }
_effects.tryEmit(CacheEffect.ShowMessage("删除成功"))
}.onError {
_effects.tryEmit(CacheEffect.ShowMessage("删除失败\n${it.localizedMessage}"))
}
}
private fun clearCachesForBooks(bookUrls: Set<String>) {
if (bookUrls.isEmpty()) return
execute {
clearBookCacheUseCase.execute(bookUrls)
}.onSuccess { clearedBookUrls ->
clearedBookUrls.forEach { bookUrl ->
cacheChapters[bookUrl] = hashSetOf()
emitBookChanged(bookUrl)
}
_effects.tryEmit(CacheEffect.ShowMessage("缓存已清理"))
}.onError {
_effects.tryEmit(CacheEffect.ShowMessage("清理缓存失败\n${it.localizedMessage}"))
}
}
private fun downloadBooks(bookUrls: Set<String>, downloadAllChapters: Boolean) {
if (bookUrls.isEmpty()) return
execute {
batchCacheDownloadUseCase.execute(
context = context,
bookUrls = bookUrls,
downloadAllChapters = downloadAllChapters,
skipAudioBooks = true
)
}.onSuccess { count ->
if (count > 0) {
_effects.tryEmit(CacheEffect.ShowMessage("已加入缓存队列: $count"))
} else {
_effects.tryEmit(CacheEffect.ShowMessage("没有可缓存的书籍"))
}
syncDownloadRunning()
}.onError {
_effects.tryEmit(CacheEffect.ShowMessage("批量缓存失败\n${it.localizedMessage}"))
}
}
private fun clearCacheForBook(book: Book) {
BookHelp.clearCache(book)
cacheChapters[book.bookUrl] = hashSetOf()
emitBookChanged(book.bookUrl)
val bookUrl = clearBookCacheUseCase.execute(book)
cacheChapters[bookUrl] = hashSetOf()
emitBookChanged(bookUrl)
}
private fun emitBookChanged(bookUrl: String) {
@@ -6,16 +6,20 @@ import androidx.activity.compose.BackHandler
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.clickable
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
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
@@ -79,13 +83,16 @@ import io.legado.app.ui.theme.adaptiveHorizontalPaddingTab
import io.legado.app.ui.widget.components.EmptyMessage
import io.legado.app.ui.widget.components.button.SmallOutlinedIconToggleButton
import io.legado.app.ui.widget.components.button.TopBarActionButton
import io.legado.app.ui.widget.components.alert.AppAlertDialog
import io.legado.app.ui.widget.components.card.NormalCard
import io.legado.app.ui.widget.components.card.TextCard
import io.legado.app.ui.widget.components.divider.PillHeaderDivider
import io.legado.app.ui.widget.components.filePicker.FilePickerSheet
import io.legado.app.ui.widget.components.icon.AppIcons
import io.legado.app.ui.widget.components.importComponents.SourceInputDialog
import io.legado.app.ui.widget.components.lazylist.FastScrollLazyVerticalGrid
import io.legado.app.ui.widget.components.list.ListScaffold
import io.legado.app.ui.widget.components.list.TopFloatingStickyItem
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
@@ -124,6 +131,7 @@ fun BookshelfScreen(
var showLogSheet by remember { mutableStateOf(false) }
var showGroupMenu by remember { mutableStateOf(false) }
var showGroupSelectSheet by remember { mutableStateOf(false) }
var showBatchDownloadConfirmDialog by remember { mutableStateOf(false) }
var isEditMode by remember { mutableStateOf(false) }
var selectedBookUrls by remember { mutableStateOf<Set<String>>(emptySet()) }
@@ -289,18 +297,14 @@ fun BookshelfScreen(
else -> stringResource(R.string.bookshelf)
}
val title = if (isEditMode) {
"$currentGroupBookCount · ${selectedBookUrls.size}"
stringResource(R.string.bookshelf)
} else if (uiState.upBooksCount > 0) {
"$baseTitle (${uiState.upBooksCount})"
} else {
baseTitle
}
val subtitle = if (isEditMode) {
if (bookGroupStyle == 0) {
"${allGroupsBookCount}"
} else {
"${currentGroupName ?: stringResource(R.string.bookshelf)} · 共${allGroupsBookCount}"
}
"${allGroupsBookCount}"
} else {
null
}
@@ -323,13 +327,22 @@ fun BookshelfScreen(
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(
selectedCount = selectedBookUrls.size,
currentGroupTotalCount = currentGroupBookCount,
groupName = currentGroupName,
showGroupName = bookGroupStyle != 0
)
} else {
null
}
ListScaffold(
title = title,
subtitle = subtitle,
state = uiState,
onBackClick = if (isEditMode) exitEditMode else null,
backNavigationIcon = if (isEditMode) AppIcons.Close else AppIcons.Back,
showSearchAction = true,
onSearchToggle = { active ->
viewModel.setSearchMode(active)
@@ -353,7 +366,7 @@ fun BookshelfScreen(
}
},
topBarActions = {
if (isEditMode) {
AnimatedVisibility(visible = isEditMode) {
TopBarActionButton(
onClick = {
selectedBookUrls = uiState.items.mapTo(hashSetOf()) { it.bookUrl }
@@ -361,6 +374,8 @@ fun BookshelfScreen(
imageVector = Icons.Default.SelectAll,
contentDescription = stringResource(R.string.select_all)
)
}
AnimatedVisibility(visible = isEditMode) {
TopBarActionButton(
onClick = {
val visibleBookUrls = uiState.items.mapTo(hashSetOf()) { it.bookUrl }
@@ -369,6 +384,19 @@ fun BookshelfScreen(
imageVector = Icons.Default.Refresh,
contentDescription = stringResource(R.string.revert_selection)
)
}
AnimatedVisibility(visible = isEditMode) {
TopBarActionButton(
onClick = {
if (selectedBookUrls.isNotEmpty()) {
showBatchDownloadConfirmDialog = true
}
},
imageVector = Icons.Default.Download,
contentDescription = stringResource(R.string.action_download)
)
}
AnimatedVisibility(visible = isEditMode) {
TopBarActionButton(
onClick = {
if (selectedBookUrls.isNotEmpty()) {
@@ -719,6 +747,97 @@ fun BookshelfScreen(
}
}
TopFloatingStickyItem(
item = editStickySummary,
modifier = Modifier
.align(Alignment.TopCenter)
.padding(top = paddingValues.calculateTopPadding() + 6.dp),
) { summary ->
Box {
NormalCard(
cornerRadius = 32.dp,
containerColor = LegadoTheme.colorScheme.surfaceContainer,
contentColor = LegadoTheme.colorScheme.onCardContainer
) {
Row(
modifier = Modifier.padding(all = 8.dp),
verticalAlignment = Alignment.CenterVertically
) {
TextCard(
icon = AppIcons.Close,
backgroundColor = LegadoTheme.colorScheme.surfaceContainerHighest,
cornerRadius = 16.dp,
verticalPadding = 8.dp,
horizontalPadding = 8.dp,
onClick = exitEditMode
)
Spacer(modifier = Modifier.width(8.dp))
AppText(
text = "已选${summary.selectedCount}",
style = LegadoTheme.typography.labelSmallEmphasized
)
AppText(
text = " · 共${summary.currentGroupTotalCount}",
style = LegadoTheme.typography.labelSmallEmphasized
)
Spacer(modifier = Modifier.width(8.dp))
if (summary.showGroupName && !summary.groupName.isNullOrBlank()) {
TextCard(
text = summary.groupName,
textStyle = LegadoTheme.typography.labelSmallEmphasized,
backgroundColor = LegadoTheme.colorScheme.surfaceContainerHighest,
cornerRadius = 16.dp,
verticalPadding = 8.dp,
horizontalPadding = 12.dp,
onClick = { showGroupMenu = true }
)
}
}
}
if (summary.showGroupName) {
RoundDropdownMenu(
expanded = showGroupMenu,
onDismissRequest = { showGroupMenu = false }
) { dismiss ->
uiState.groups.forEach { group ->
RoundDropdownMenuItem(
text = group.groupName,
onClick = {
val targetIndex =
uiState.groups.indexOfFirst { it.groupId == group.groupId }
if (targetIndex >= 0) {
scope.launch {
if (pagerState.currentPage != targetIndex) {
pagerState.animateScrollToPage(targetIndex)
}
}
}
if (uiState.isSearch || uiState.selectedGroupId != group.groupId) {
viewModel.changeGroup(group.groupId)
}
if (bookGroupStyle == 2) {
isInFolderRoot = false
}
dismiss()
},
trailingIcon = {
if (currentMenuGroupId == group.groupId) {
Icon(
Icons.Default.Check,
null,
modifier = Modifier.size(18.dp)
)
}
}
)
}
}
}
}
}
PullToRefreshDefaults.LoadingIndicator(
state = pullToRefreshState,
isRefreshing = isRefreshing,
@@ -794,6 +913,20 @@ fun BookshelfScreen(
onDismissRequest = { showLogSheet = false }
)
AppAlertDialog(
show = showBatchDownloadConfirmDialog,
onDismissRequest = { showBatchDownloadConfirmDialog = false },
title = stringResource(R.string.draw),
text = stringResource(R.string.sure_cache_book),
confirmText = stringResource(android.R.string.ok),
onConfirm = {
showBatchDownloadConfirmDialog = false
viewModel.downloadBooks(selectedBookUrls)
},
dismissText = stringResource(android.R.string.cancel),
onDismiss = { showBatchDownloadConfirmDialog = false }
)
if (uiState.isLoading) {
Dialog(onDismissRequest = {}) {
NormalCard(
@@ -818,6 +951,13 @@ fun BookshelfScreen(
}
}
private data class BookshelfEditStickySummary(
val selectedCount: Int,
val currentGroupTotalCount: Int,
val groupName: String?,
val showGroupName: Boolean,
)
@Composable
fun BookshelfPage(
paddingValues: PaddingValues,
@@ -19,6 +19,8 @@ import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.BookSourcePart
import io.legado.app.data.repository.BookGroupRepository
import io.legado.app.data.repository.UploadRepository
import io.legado.app.domain.usecase.BatchCacheDownloadUseCase
import io.legado.app.domain.usecase.UpdateBooksGroupUseCase
import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.addType
@@ -91,7 +93,9 @@ import kotlin.math.min
class BookshelfViewModel(
application: Application,
private val bookGroupRepository: BookGroupRepository,
private val uploadRepository: UploadRepository
private val uploadRepository: UploadRepository,
private val batchCacheDownloadUseCase: BatchCacheDownloadUseCase,
private val updateBooksGroupUseCase: UpdateBooksGroupUseCase
) : BaseViewModel(application) {
var addBookJob: Coroutine<*>? = null
@@ -377,33 +381,34 @@ class BookshelfViewModel(
}
fun moveBooksToGroup(bookUrls: Set<String>, groupId: Long) {
updateBooksGroup(bookUrls) { groupId }
}
private fun updateBooksGroup(
bookUrls: Set<String>,
transform: (Long) -> Long
) {
if (bookUrls.isEmpty()) return
execute {
val updateBooks = bookUrls.mapNotNull { url ->
appDb.bookDao.getBook(url)?.let { book ->
val targetGroup = transform(book.group)
if (targetGroup != book.group) {
book.copy(group = targetGroup)
} else {
null
}
}
}
if (updateBooks.isNotEmpty()) {
appDb.bookDao.update(*updateBooks.toTypedArray())
}
updateBooksGroupUseCase.replaceGroup(bookUrls, groupId)
}.onError {
context.toastOnUi("更新分组失败\n${it.localizedMessage}")
}
}
fun downloadBooks(bookUrls: Set<String>, downloadAllChapters: Boolean = false) {
if (bookUrls.isEmpty()) return
execute {
batchCacheDownloadUseCase.execute(
context = context,
bookUrls = bookUrls,
downloadAllChapters = downloadAllChapters,
skipAudioBooks = true
)
}.onSuccess { count ->
if (count > 0) {
context.toastOnUi("已加入缓存队列: $count")
} else {
context.toastOnUi(R.string.no_download)
}
}.onError {
context.toastOnUi("批量缓存失败\n${it.localizedMessage}")
}
}
fun gotoTop() {
scrollTrigger.tryEmit(Unit)
}
@@ -25,7 +25,7 @@ import io.legado.app.ui.widget.components.text.AnimatedTextLine
@Composable
fun TextCard(
modifier: Modifier = Modifier,
text: String,
text: String? = null,
icon: ImageVector? = null,
onClick: (() -> Unit)? = null,
backgroundColor: Color? = null,
@@ -65,16 +65,20 @@ fun TextCard(
tint = finalContentColor,
modifier = Modifier.size(iconSize)
)
Spacer(modifier = Modifier.width(spacing))
}
AnimatedTextLine(
text = text,
style = textStyle,
color = finalContentColor,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
if (icon != null && text != null)
Spacer(modifier = Modifier.width(spacing))
text?.let {
AnimatedTextLine(
text = it,
style = textStyle,
color = finalContentColor,
maxLines = 2,
overflow = TextOverflow.Ellipsis
)
}
}
}
}