[优化] 依旧优化某些界面的OOM问题

This commit is contained in:
HapeLee
2026-04-28 01:52:21 +08:00
parent c2e1cc9971
commit e9461d4a52
6 changed files with 251 additions and 73 deletions
@@ -6,6 +6,7 @@ import androidx.room.OnConflictStrategy
import androidx.room.Query
import androidx.room.Update
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.model.BookChapterCacheInfo
import kotlinx.coroutines.flow.Flow
@Dao
@@ -20,6 +21,9 @@ interface BookChapterDao {
@Query("select * from chapters where bookUrl = :bookUrl order by `index`")
fun getChapterList(bookUrl: String): List<BookChapter>
@Query("select url, title, isVolume, `index` from chapters where bookUrl = :bookUrl order by `index`")
fun getChapterCacheInfoList(bookUrl: String): List<BookChapterCacheInfo>
@Query("select * from chapters where bookUrl = :bookUrl order by `index`")
fun getChapterListFlow(bookUrl: String): Flow<List<BookChapter>>
@@ -0,0 +1,8 @@
package io.legado.app.data.model
data class BookChapterCacheInfo(
val url: String,
val title: String,
val isVolume: Boolean,
val index: Int,
)
@@ -184,6 +184,19 @@ object CacheBook {
}
}
fun removeBook(bookUrl: String): Boolean {
val model = cacheBookMap.remove(bookUrl) ?: return false
model.stop()
updateSummary()
_queueChangedFlow.tryEmit(bookUrl)
postEvent(EventBus.UP_DOWNLOAD, bookUrl)
return true
}
fun removeChapter(bookUrl: String, chapterIndex: Int): Boolean {
return cacheBookMap[bookUrl]?.removeDownload(chapterIndex) == true
}
fun stop(context: Context) {
if (CacheBookService.isRun) {
context.startService<CacheBookService> {
@@ -246,6 +259,8 @@ object CacheBook {
private val waitDownloadSet = linkedSetOf<Int>()
private val onDownloadSet = linkedSetOf<Int>()
private val pausedDownloadSet = hashSetOf<Int>()
private val chapterTasks = hashMapOf<Int, Coroutine<*>>()
private val tasks = CompositeCoroutine()
private var isStopped = false
private var waitingRetry = false
@@ -295,6 +310,8 @@ object CacheBook {
@Synchronized
fun stop() {
waitDownloadSet.clear()
pausedDownloadSet.clear()
chapterTasks.clear()
tasks.clear()
isStopped = true
isLoading = false
@@ -312,6 +329,7 @@ object CacheBook {
fun addDownloads(indices: Iterable<Int>) {
isStopped = false
for (i in indices) {
pausedDownloadSet.remove(i)
if (!onDownloadSet.contains(i)) {
waitDownloadSet.add(i)
}
@@ -329,6 +347,7 @@ object CacheBook {
@Synchronized
private fun onSuccess(chapter: BookChapter) {
onDownloadSet.remove(chapter.index)
chapterTasks.remove(chapter.index)
successDownloadSet.add(chapter.primaryStr())
errorDownloadMap.remove(chapter.primaryStr())
errorIndexMap[book.bookUrl]?.remove(chapter.index)
@@ -346,6 +365,7 @@ object CacheBook {
.add(chapter.index)
}
onDownloadSet.remove(chapter.index)
chapterTasks.remove(chapter.index)
}
@Synchronized
@@ -370,7 +390,8 @@ object CacheBook {
@Synchronized
private fun onCancel(index: Int) {
onDownloadSet.remove(index)
if (!isStopped) waitDownloadSet.add(index)
chapterTasks.remove(index)
if (!isStopped && !pausedDownloadSet.remove(index)) waitDownloadSet.add(index)
notifyDownloadSetChanged()
}
@@ -385,6 +406,28 @@ object CacheBook {
notifyDownloadSetChanged()
}
@Synchronized
fun removeDownload(index: Int): Boolean {
val removedWaiting = waitDownloadSet.remove(index)
val task = chapterTasks.remove(index)
val removedRunning = onDownloadSet.contains(index) || task != null
if (removedRunning) {
pausedDownloadSet.add(index)
task?.let {
tasks.delete(it)
it.cancel()
}
}
if (!removedWaiting && !removedRunning) return false
notifyDownloadSetChanged()
if (waitDownloadSet.isEmpty() && onDownloadSet.isEmpty()) {
CacheBook.onTaskRemoved(book.bookUrl)
} else {
CacheBook.onTaskQueuesChanged(book.bookUrl)
}
return true
}
/**
* 从待下载列表内取第一条下载
*/
@@ -421,7 +464,7 @@ object CacheBook {
notifyDownloadSetChanged()
if (BookHelp.hasContent(book, chapter)) {
Coroutine.async(scope, context, executeContext = context) {
val task = Coroutine.async(scope, context, executeContext = context) {
BookHelp.getContent(book, chapter)?.let {
BookHelp.saveImages(bookSource, book, chapter, it, 1)
}
@@ -434,12 +477,15 @@ object CacheBook {
}.onCancel {
onCancel(chapterIndex)
}.onFinally {
chapterTasks.remove(chapterIndex)?.let { tasks.delete(it) }
onFinally()
}
chapterTasks[chapterIndex] = task
tasks.add(task)
return
}
WebBook.getContent(
val task = WebBook.getContent(
scope,
bookSource,
book,
@@ -458,10 +504,12 @@ object CacheBook {
}.onCancel {
onCancel(chapterIndex)
}.onFinally {
chapterTasks.remove(chapterIndex)?.let { tasks.delete(it) }
onFinally()
}.apply {
tasks.add(this)
}.start()
}
chapterTasks[chapterIndex] = task
tasks.add(task)
task.start()
}
suspend fun downloadAwait(chapter: BookChapter): String {
@@ -98,8 +98,7 @@ class CacheBookService : BaseService() {
}
IntentAction.remove -> {
val bookUrl = intent.getStringExtra("bookUrl")
CacheBook.cacheBookMap[bookUrl]?.stop()
CacheBook.cacheBookMap.remove(bookUrl)
bookUrl?.let { CacheBook.removeBook(it) }
}
IntentAction.stop -> stopSelf()
}
@@ -19,6 +19,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.filled.Download
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material.icons.filled.Stop
import androidx.compose.material3.Checkbox
@@ -44,6 +45,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import io.legado.app.R
import io.legado.app.ui.theme.LegadoTheme
import io.legado.app.ui.theme.adaptiveContentPadding
import io.legado.app.ui.theme.adaptiveHorizontalPadding
import io.legado.app.ui.widget.components.AppLinearProgressIndicator
import io.legado.app.ui.widget.components.AppScaffold
import io.legado.app.ui.widget.components.alert.AppAlertDialog
@@ -187,7 +189,7 @@ private fun BookCacheManageScreen(
DeleteBookCacheDialog(
item = pendingDeleteBook,
onConfirm = { item ->
onIntent(BookCacheManageIntent.DeleteBookCache(item.book.bookUrl))
onIntent(BookCacheManageIntent.DeleteBookCache(item.bookUrl))
pendingDeleteBook = null
},
onDismiss = { pendingDeleteBook = null }
@@ -197,8 +199,10 @@ private fun BookCacheManageScreen(
onConfirm = { book, chapter ->
onIntent(
BookCacheManageIntent.DeleteChapterCache(
book.book.bookUrl,
chapter.chapter.url
book.bookUrl,
chapter.chapterUrl,
chapter.title,
chapter.index,
)
)
pendingDeleteChapter = null
@@ -221,18 +225,23 @@ private fun LazyListScope.cacheSection(
item(key = "$title-header") {
AppText(
text = title,
modifier = Modifier.padding(start = 4.dp, top = 12.dp, bottom = 4.dp),
modifier = Modifier.padding(horizontal = 12.dp, vertical = 4.dp),
style = LegadoTheme.typography.titleSmallEmphasized,
color = LegadoTheme.colorScheme.primary
)
}
if (books.isEmpty()) {
item(key = "$title-empty") {
TextCard(text = emptyText, modifier = Modifier.fillMaxWidth())
TextCard(
text = emptyText,
modifier = Modifier.fillMaxWidth(),
verticalPadding = 12.dp,
horizontalPadding = 12.dp
)
}
} else {
books.forEach { item ->
val bookUrl = item.book.bookUrl
val bookUrl = item.bookUrl
val expanded = expandedBookUrls.contains(bookUrl)
item(key = "$title-book-$bookUrl") {
BookCacheBookCard(
@@ -247,7 +256,7 @@ private fun LazyListScope.cacheSection(
if (expanded) {
items(
items = chaptersByBookUrl[bookUrl].orEmpty(),
key = { chapter -> "$title-chapter-$bookUrl-${chapter.chapter.url}" }
key = { chapter -> "$title-chapter-$bookUrl-${chapter.chapterUrl}" }
) { chapter ->
BookCacheChapterRow(
item = chapter,
@@ -256,7 +265,15 @@ private fun LazyListScope.cacheSection(
onIntent(
BookCacheManageIntent.DownloadChapter(
bookUrl,
chapter.chapter.index
chapter.index
)
)
},
onStop = {
onIntent(
BookCacheManageIntent.StopChapterDownload(
bookUrl,
chapter.index
)
)
},
@@ -284,7 +301,7 @@ private fun BookCacheBookCard(
NormalCard(
modifier = modifier.fillMaxWidth(),
onClick = onToggleExpanded,
containerColor = LegadoTheme.colorScheme.surfaceContainerLow
containerColor = LegadoTheme.colorScheme.surfaceContainer
) {
Column(
modifier = Modifier
@@ -306,20 +323,23 @@ private fun BookCacheBookCard(
)
Column(modifier = Modifier.weight(1f)) {
AppText(
text = item.book.name,
text = item.name,
style = LegadoTheme.typography.titleSmallEmphasized,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
AppText(
text = item.book.getRealAuthor(),
style = LegadoTheme.typography.bodySmall,
text = item.author,
style = LegadoTheme.typography.labelSmallEmphasized,
color = LegadoTheme.colorScheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
TextCard(text = "${item.cachedCount}/${item.totalCount}")
TextCard(
text = "${item.cachedCount}/${item.totalCount}",
backgroundColor = LegadoTheme.colorScheme.cardContainer,
)
}
AppLinearProgressIndicator(
progress = item.progress,
@@ -333,9 +353,22 @@ private fun BookCacheBookCard(
AppText(
text = "下载中 ${item.downloadingCount} · 等待 ${item.waitingCount} · 失败 ${item.errorCount}",
modifier = Modifier.weight(1f),
style = LegadoTheme.typography.bodySmall,
style = LegadoTheme.typography.labelMediumEmphasized,
color = LegadoTheme.colorScheme.onSurfaceVariant
)
if (item.isDownloading || item.cachedCount < item.totalCount) {
SmallTonalIconButton(
onClick = {
if (item.isDownloading) {
onIntent(BookCacheManageIntent.StopBookDownload(item.bookUrl))
} else {
onIntent(BookCacheManageIntent.StartBookDownload(item.bookUrl))
}
},
imageVector = if (item.isDownloading) Icons.Default.Stop else Icons.Default.PlayArrow,
contentDescription = if (item.isDownloading) "暂停本书下载" else "开始本书下载"
)
}
SmallTonalIconButton(
onClick = { onDeleteBook(item) },
imageVector = Icons.Default.Delete,
@@ -351,21 +384,25 @@ private fun BookCacheChapterRow(
item: BookCacheChapterItem,
modifier: Modifier = Modifier,
onDownload: () -> Unit,
onStop: () -> Unit,
onDelete: () -> Unit,
) {
Row(
modifier = modifier
.fillMaxWidth()
.padding(horizontal = 8.dp),
.padding(
horizontal = 12.dp,
vertical = 4.dp
),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalAlignment = Alignment.CenterVertically
) {
Column(modifier = Modifier.weight(1f)) {
AppText(
text = item.chapter.title,
text = item.title,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
style = LegadoTheme.typography.bodyMedium
style = LegadoTheme.typography.titleSmallEmphasized
)
AppText(
text = chapterStatusText(item),
@@ -380,11 +417,19 @@ private fun BookCacheChapterRow(
}
)
}
SmallTonalIconButton(
onClick = onDownload,
imageVector = Icons.Default.Download,
contentDescription = null
)
if (item.isWaiting || item.isDownloading) {
SmallTonalIconButton(
onClick = onStop,
imageVector = Icons.Default.Stop,
contentDescription = "暂停章节下载"
)
} else if (!item.isCached) {
SmallTonalIconButton(
onClick = onDownload,
imageVector = Icons.Default.Download,
contentDescription = "下载章节"
)
}
SmallTonalIconButton(
onClick = onDelete,
imageVector = Icons.Default.Delete,
@@ -413,7 +458,7 @@ private fun DeleteBookCacheDialog(
show = item != null,
onDismissRequest = onDismiss,
title = stringResource(R.string.delete),
text = "删除《${item?.book?.name.orEmpty()}》的全部缓存,并从下载队列移除?",
text = "删除《${item?.name.orEmpty()}》的全部缓存,并从下载队列移除?",
confirmText = stringResource(android.R.string.ok),
onConfirm = { item?.let(onConfirm) },
dismissText = stringResource(android.R.string.cancel),
@@ -431,7 +476,7 @@ private fun DeleteChapterCacheDialog(
show = item != null,
onDismissRequest = onDismiss,
title = stringResource(R.string.delete),
text = "删除章节缓存:${item?.second?.chapter?.title.orEmpty()}",
text = "删除章节缓存:${item?.second?.title.orEmpty()}",
confirmText = stringResource(android.R.string.ok),
onConfirm = { item?.let { onConfirm(it.first, it.second) } },
dismissText = stringResource(android.R.string.cancel),
@@ -7,6 +7,7 @@ import io.legado.app.data.dao.BookChapterDao
import io.legado.app.data.dao.BookDao
import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.model.BookChapterCacheInfo
import io.legado.app.domain.usecase.CacheBookChaptersUseCase
import io.legado.app.domain.usecase.ClearBookCacheUseCase
import io.legado.app.help.book.BookHelp
@@ -33,12 +34,14 @@ data class BookCacheManageUiState(
val notShelfBooks: List<BookCacheBookItem> = emptyList(),
val expandedBookUrls: Set<String> = emptySet(),
val chaptersByBookUrl: Map<String, List<BookCacheChapterItem>> = emptyMap(),
val downloadSummary: String = CacheBook.downloadSummary,
val downloadSummary: String = "",
val version: Long = 0,
)
data class BookCacheBookItem(
val book: Book,
val bookUrl: String,
val name: String,
val author: String,
val totalCount: Int,
val cachedCount: Int,
val waitingCount: Int,
@@ -51,7 +54,9 @@ data class BookCacheBookItem(
}
data class BookCacheChapterItem(
val chapter: BookChapter,
val chapterUrl: String,
val title: String,
val index: Int,
val isCached: Boolean,
val isWaiting: Boolean,
val isDownloading: Boolean,
@@ -63,10 +68,18 @@ sealed interface BookCacheManageIntent {
data object Refresh : BookCacheManageIntent
data object StartAllDownloads : BookCacheManageIntent
data object StopAllDownloads : BookCacheManageIntent
data class StartBookDownload(val bookUrl: String) : BookCacheManageIntent
data class StopBookDownload(val bookUrl: String) : BookCacheManageIntent
data class ToggleBookExpanded(val bookUrl: String) : BookCacheManageIntent
data class DeleteBookCache(val bookUrl: String) : BookCacheManageIntent
data class DownloadChapter(val bookUrl: String, val chapterIndex: Int) : BookCacheManageIntent
data class DeleteChapterCache(val bookUrl: String, val chapterUrl: String) : BookCacheManageIntent
data class StopChapterDownload(val bookUrl: String, val chapterIndex: Int) : BookCacheManageIntent
data class DeleteChapterCache(
val bookUrl: String,
val chapterUrl: String,
val chapterTitle: String,
val chapterIndex: Int,
) : BookCacheManageIntent
}
sealed interface BookCacheManageEffect {
@@ -97,15 +110,23 @@ class BookCacheManageViewModel(
BookCacheManageIntent.Refresh -> reloadAll(forceDatabase = true)
BookCacheManageIntent.StartAllDownloads -> startAllDownloads()
BookCacheManageIntent.StopAllDownloads -> stopAllDownloads()
is BookCacheManageIntent.StartBookDownload -> startBookDownload(intent.bookUrl)
is BookCacheManageIntent.StopBookDownload -> stopBookDownload(intent.bookUrl)
is BookCacheManageIntent.ToggleBookExpanded -> toggleBookExpanded(intent.bookUrl)
is BookCacheManageIntent.DeleteBookCache -> deleteBookCache(intent.bookUrl)
is BookCacheManageIntent.DownloadChapter -> downloadChapter(
intent.bookUrl,
intent.chapterIndex
)
is BookCacheManageIntent.StopChapterDownload -> stopChapterDownload(
intent.bookUrl,
intent.chapterIndex,
)
is BookCacheManageIntent.DeleteChapterCache -> deleteChapterCache(
intent.bookUrl,
intent.chapterUrl
intent.chapterUrl,
intent.chapterTitle,
intent.chapterIndex,
)
}
}
@@ -138,9 +159,12 @@ class BookCacheManageViewModel(
}
}
viewModelScope.launch {
CacheBook.downloadSummaryFlow.collect { summary ->
CacheBook.downloadSummaryFlow.collect {
_uiState.update {
it.copy(downloadSummary = summary, version = it.version + 1)
it.copy(
downloadSummary = buildDownloadSummary(it.shelfBooks + it.notShelfBooks),
version = it.version + 1,
)
}
}
}
@@ -165,12 +189,12 @@ class BookCacheManageViewModel(
item.cachedCount > 0 || item.isDownloading || item.errorCount > 0
}
)
val booksByUrl = items.associateBy { it.book.bookUrl }
val booksByUrl = items.associateBy { it.bookUrl }
val retainedExpandedBookUrls = expandedBookUrls.filterTo(linkedSetOf()) {
booksByUrl.containsKey(it)
}
val chaptersByBookUrl = retainedExpandedBookUrls.associateWith { bookUrl ->
buildChapterItems(booksByUrl.getValue(bookUrl).book)
buildChapterItems(bookUrl)
}
LoadedCacheState(
items = items,
@@ -185,7 +209,7 @@ class BookCacheManageViewModel(
notShelfBooks = result.items.filter { item -> item.isNotShelf },
expandedBookUrls = result.expandedBookUrls,
chaptersByBookUrl = result.chaptersByBookUrl,
downloadSummary = CacheBook.downloadSummary,
downloadSummary = buildDownloadSummary(result.items),
version = state.version + 1,
)
}
@@ -217,7 +241,7 @@ class BookCacheManageViewModel(
it.cachedCount > 0 || it.isDownloading || it.errorCount > 0
}
val chapters = if (expanded && item != null) {
buildChapterItems(item.book)
buildChapterItems(item.bookUrl)
} else {
null
}
@@ -225,7 +249,7 @@ class BookCacheManageViewModel(
}
_uiState.update { state ->
val combinedBooks = (state.shelfBooks + state.notShelfBooks)
.filterNot { it.book.bookUrl == bookUrl }
.filterNot { it.bookUrl == bookUrl }
.let { items ->
result.item?.let { items + it } ?: items
}
@@ -245,7 +269,7 @@ class BookCacheManageViewModel(
notShelfBooks = sortedBooks.filter { item -> item.isNotShelf },
expandedBookUrls = expandedBookUrls,
chaptersByBookUrl = chaptersByBookUrl,
downloadSummary = CacheBook.downloadSummary,
downloadSummary = buildDownloadSummary(sortedBooks),
version = state.version + 1,
)
}
@@ -264,7 +288,9 @@ class BookCacheManageViewModel(
return null
}
return BookCacheBookItem(
book = book,
bookUrl = book.bookUrl,
name = book.name,
author = book.getRealAuthor(),
totalCount = totalCount,
cachedCount = cachedCount,
waitingCount = waitingIndices.size,
@@ -274,16 +300,19 @@ class BookCacheManageViewModel(
)
}
private fun buildChapterItems(book: Book): List<BookCacheChapterItem> {
val chapters = bookChapterDao.getChapterList(book.bookUrl)
private fun buildChapterItems(bookUrl: String): List<BookCacheChapterItem> {
val book = bookDao.getBook(bookUrl) ?: return emptyList()
val chapters = bookChapterDao.getChapterCacheInfoList(bookUrl)
val cacheFiles = BookHelp.getChapterFiles(book)
val model = CacheBook.cacheBookMap[book.bookUrl]
val model = CacheBook.cacheBookMap[bookUrl]
val waitingIndices = model?.waitingIndices().orEmpty()
val downloadingIndices = model?.downloadingIndices().orEmpty()
val errorIndices = errorIndices(book.bookUrl)
val errorIndices = errorIndices(bookUrl)
return chapters.map { chapter ->
BookCacheChapterItem(
chapter = chapter,
chapterUrl = chapter.url,
title = chapter.title,
index = chapter.index,
isCached = cacheFiles.contains(chapter.getFileName()) || chapter.isVolume,
isWaiting = waitingIndices.contains(chapter.index),
isDownloading = downloadingIndices.contains(chapter.index),
@@ -320,9 +349,8 @@ class BookCacheManageViewModel(
private fun loadBookChapters(bookUrl: String) {
viewModelScope.launch {
val chapters = withContext(Dispatchers.IO) {
val book = findBook(bookUrl) ?: bookDao.getBook(bookUrl) ?: return@withContext null
buildChapterItems(book)
} ?: return@launch
buildChapterItems(bookUrl)
}
_uiState.update { state ->
if (!state.expandedBookUrls.contains(bookUrl)) {
state
@@ -336,23 +364,22 @@ class BookCacheManageViewModel(
}
}
private fun findBook(bookUrl: String): Book? {
return (uiState.value.shelfBooks + uiState.value.notShelfBooks)
.firstOrNull { it.book.bookUrl == bookUrl }
?.book
}
private fun stopAllDownloads() {
CacheBook.stop(context)
reloadAll(forceDatabase = true)
}
private fun stopBookDownload(bookUrl: String) {
CacheBook.removeBook(bookUrl)
scheduleBookReload(bookUrl, debounceMillis = 0)
}
private fun startAllDownloads() {
val items = uiState.value.shelfBooks + uiState.value.notShelfBooks
execute {
items.sumOf { item ->
val chapterIndices = downloadableChapterIndices(item.book)
cacheBookChaptersUseCase.execute(item.book.bookUrl, chapterIndices)
val chapterIndices = downloadableChapterIndices(item.bookUrl)
cacheBookChaptersUseCase.execute(item.bookUrl, chapterIndices)
}
}.onSuccess { count ->
if (count > 0) {
@@ -367,12 +394,29 @@ class BookCacheManageViewModel(
}
}
private fun downloadableChapterIndices(book: Book): List<Int> {
private fun startBookDownload(bookUrl: String) {
execute {
cacheBookChaptersUseCase.execute(bookUrl, downloadableChapterIndices(bookUrl))
}.onSuccess { count ->
if (count > 0) {
_effects.tryEmit(BookCacheManageEffect.ShowMessage("已加入缓存队列: $count"))
} else {
_effects.tryEmit(BookCacheManageEffect.ShowMessage("没有可缓存的章节"))
}
}.onError {
_effects.tryEmit(BookCacheManageEffect.ShowMessage("加入缓存队列失败\n${it.localizedMessage}"))
}.onFinally {
scheduleBookReload(bookUrl, debounceMillis = 0)
}
}
private fun downloadableChapterIndices(bookUrl: String): List<Int> {
val book = bookDao.getBook(bookUrl) ?: return emptyList()
val cacheFiles = BookHelp.getChapterFiles(book)
val model = CacheBook.cacheBookMap[book.bookUrl]
val model = CacheBook.cacheBookMap[bookUrl]
val waitingIndices = model?.waitingIndices().orEmpty()
val downloadingIndices = model?.downloadingIndices().orEmpty()
return bookChapterDao.getChapterList(book.bookUrl)
return bookChapterDao.getChapterCacheInfoList(bookUrl)
.asSequence()
.filterNot { chapter ->
chapter.isVolume ||
@@ -409,13 +453,25 @@ class BookCacheManageViewModel(
}
}
private fun deleteChapterCache(bookUrl: String, chapterUrl: String) {
val book = findBook(bookUrl) ?: bookDao.getBook(bookUrl) ?: return
val chapter = uiState.value.chaptersByBookUrl[bookUrl]
?.firstOrNull { it.chapter.url == chapterUrl }
?.chapter
?: bookChapterDao.getChapterList(bookUrl).firstOrNull { it.url == chapterUrl }
?: return
private fun stopChapterDownload(bookUrl: String, chapterIndex: Int) {
if (CacheBook.removeChapter(bookUrl, chapterIndex)) {
scheduleBookReload(bookUrl, debounceMillis = 0)
}
}
private fun deleteChapterCache(
bookUrl: String,
chapterUrl: String,
chapterTitle: String,
chapterIndex: Int,
) {
val book = bookDao.getBook(bookUrl) ?: return
val chapter = BookChapter(
url = chapterUrl,
title = chapterTitle,
bookUrl = bookUrl,
index = chapterIndex,
)
execute {
BookHelp.delContent(book, chapter)
}.onSuccess {
@@ -430,7 +486,16 @@ class BookCacheManageViewModel(
private fun sortItems(items: List<BookCacheBookItem>): List<BookCacheBookItem> {
return items.sortedWith(compareByDescending<BookCacheBookItem> { it.isDownloading }
.thenByDescending { it.cachedCount }
.thenBy { it.book.name })
.thenBy { it.name })
}
private fun buildDownloadSummary(items: List<BookCacheBookItem>): String {
if (items.isEmpty()) return ""
val downloadingCount = items.sumOf { it.downloadingCount }
val waitingCount = items.sumOf { it.waitingCount }
val errorCount = items.sumOf { it.errorCount }
val cachedCount = items.sumOf { it.cachedCount }
return "正在下载:$downloadingCount | 等待中:$waitingCount | 失败:$errorCount | 成功:$cachedCount"
}
private data class LoadedCacheState(
@@ -444,3 +509,12 @@ class BookCacheManageViewModel(
val chapters: List<BookCacheChapterItem>?,
)
}
private fun BookChapterCacheInfo.getFileName(): String {
return BookChapter(
url = url,
title = title,
isVolume = isVolume,
index = index,
).getFileName()
}