From 9d98d1a588da7a69d0cbd66f9a47860c6c896cf4 Mon Sep 17 00:00:00 2001 From: Horis <8674809+821938089@users.noreply.github.com> Date: Sun, 28 Jul 2024 21:01:11 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/model/CacheBook.kt | 26 +++ .../main/java/io/legado/app/model/ReadBook.kt | 163 ++++++++++++++++-- .../app/ui/book/read/ReadBookActivity.kt | 18 +- .../legado/app/ui/book/read/page/ReadView.kt | 4 +- 4 files changed, 198 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/io/legado/app/model/CacheBook.kt b/app/src/main/java/io/legado/app/model/CacheBook.kt index cdcbbf569..b7b1a3030 100644 --- a/app/src/main/java/io/legado/app/model/CacheBook.kt +++ b/app/src/main/java/io/legado/app/model/CacheBook.kt @@ -16,6 +16,7 @@ import io.legado.app.model.webBook.WebBook import io.legado.app.service.CacheBookService import io.legado.app.utils.postEvent import io.legado.app.utils.startService +import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineStart import kotlinx.coroutines.Dispatchers.IO @@ -308,6 +309,31 @@ object CacheBook { }.start() } + suspend fun downloadAwait(chapter: BookChapter): String { + postEvent(EventBus.UP_DOWNLOAD, book.bookUrl) + synchronized(this) { + onDownloadSet.add(chapter.index) + waitDownloadSet.remove(chapter.index) + } + try { + val content = WebBook.getContentAwait(bookSource, book, chapter) + onSuccess(chapter) + ReadBook.downloadedChapters.add(chapter.index) + ReadBook.downloadFailChapters.remove(chapter.index) + return content + } catch (e: Exception) { + if (e is CancellationException) { + onCancel(chapter.index) + } + onError(chapter, e) + ReadBook.downloadFailChapters[chapter.index] = + (ReadBook.downloadFailChapters[chapter.index] ?: 0) + 1 + return "获取正文失败\n${e.localizedMessage}" + } finally { + postEvent(EventBus.UP_DOWNLOAD, book.bookUrl) + } + } + @Synchronized fun download( scope: CoroutineScope, diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index 9e3b810ea..788a9d912 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/app/src/main/java/io/legado/app/model/ReadBook.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.ensureActive import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext import splitties.init.appCtx import kotlin.math.max import kotlin.math.min @@ -55,8 +56,20 @@ object ReadBook : CoroutineScope by MainScope() { var isLocalBook = true var chapterChanged = false var prevTextChapter: TextChapter? = null + set(value) { + field?.cancelLayout() + field = value + } var curTextChapter: TextChapter? = null + set(value) { + field?.cancelLayout() + field = value + } var nextTextChapter: TextChapter? = null + set(value) { + field?.cancelLayout() + field = value + } var bookSource: BookSource? = null var msg: String? = null private val loadingChapters = arrayListOf() @@ -164,7 +177,7 @@ object ReadBook : CoroutineScope by MainScope() { fun setProgress(progress: BookProgress) { if (progress.durChapterIndex < chapterSize && (durChapterIndex != progress.durChapterIndex - || durChapterPos != progress.durChapterPos) + || durChapterPos != progress.durChapterPos) ) { durChapterIndex = progress.durChapterIndex durChapterPos = progress.durChapterPos @@ -175,9 +188,6 @@ object ReadBook : CoroutineScope by MainScope() { } fun clearTextChapter() { - prevTextChapter?.cancelLayout() - curTextChapter?.cancelLayout() - nextTextChapter?.cancelLayout() prevTextChapter = null curTextChapter = null nextTextChapter = null @@ -245,7 +255,6 @@ object ReadBook : CoroutineScope by MainScope() { if (durChapterIndex < simulatedChapterSize - 1) { durChapterPos = 0 durChapterIndex++ - prevTextChapter?.cancelLayout() prevTextChapter = curTextChapter curTextChapter = nextTextChapter nextTextChapter = null @@ -269,6 +278,36 @@ object ReadBook : CoroutineScope by MainScope() { } } + suspend fun moveToNextChapterAwait( + upContent: Boolean, + upContentInPlace: Boolean = true + ): Boolean { + if (durChapterIndex < simulatedChapterSize - 1) { + durChapterPos = 0 + durChapterIndex++ + prevTextChapter = curTextChapter + curTextChapter = nextTextChapter + nextTextChapter = null + if (curTextChapter == null) { + AppLog.putDebug("moveToNextChapter-章节未加载,开始加载") + if (upContentInPlace) callBack?.upContentAwait() + loadContentAwait(durChapterIndex, upContent, resetPageOffset = false) + } else if (upContent && upContentInPlace) { + AppLog.putDebug("moveToNextChapter-章节已加载,刷新视图") + callBack?.upContentAwait() + } + loadContent(durChapterIndex.plus(1), upContent, false) + saveRead() + callBack?.upMenuView() + AppLog.putDebug("moveToNextChapter-curPageChanged()") + curPageChanged() + return true + } else { + AppLog.putDebug("跳转下一章失败,没有下一章") + return false + } + } + fun moveToPrevChapter( upContent: Boolean, toLast: Boolean = true, @@ -277,7 +316,6 @@ object ReadBook : CoroutineScope by MainScope() { if (durChapterIndex > 0) { durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0 durChapterIndex-- - nextTextChapter?.cancelLayout() nextTextChapter = curTextChapter curTextChapter = prevTextChapter prevTextChapter = null @@ -467,6 +505,27 @@ object ReadBook : CoroutineScope by MainScope() { } } + suspend fun loadContentAwait( + index: Int, + upContent: Boolean = true, + resetPageOffset: Boolean = false, + success: (() -> Unit)? = null + ) = withContext(IO) { + if (addLoading(index)) { + try { + val book = book!! + val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, index)!! + val content = BookHelp.getContent(book, chapter) ?: downloadAwait(chapter) + contentLoadFinishAwait(book, chapter, content, upContent, resetPageOffset) + success?.invoke() + } catch (e: Exception) { + AppLog.put("加载正文出错\n${e.localizedMessage}") + } finally { + removeLoading(index) + } + } + } + /** * 下载正文 */ @@ -519,6 +578,17 @@ object ReadBook : CoroutineScope by MainScope() { } } + private suspend fun downloadAwait(chapter: BookChapter): String { + val book = book!! + val bookSource = bookSource + if (bookSource != null) { + return CacheBook.getOrCreate(bookSource, book).downloadAwait(chapter) + } else { + val msg = if (book.isLocal) "无内容" else "没有书源" + return "加载正文失败\n$msg" + } + } + private fun addLoading(index: Int): Boolean { synchronized(this) { if (loadingChapters.contains(index)) return false @@ -561,7 +631,6 @@ object ReadBook : CoroutineScope by MainScope() { ) when (val offset = chapter.index - durChapterIndex) { 0 -> { - curTextChapter?.cancelLayout() curTextChapter = textChapter callBack?.upMenuView() var available = false @@ -586,14 +655,12 @@ object ReadBook : CoroutineScope by MainScope() { } -1 -> { - prevTextChapter?.cancelLayout() prevTextChapter = textChapter textChapter.layoutChannel.receiveAsFlow().collect() if (upContent) callBack?.upContent(offset, resetPageOffset) } 1 -> { - nextTextChapter?.cancelLayout() nextTextChapter = textChapter for (page in textChapter.layoutChannel) { if (page.index > 1) { @@ -613,6 +680,77 @@ object ReadBook : CoroutineScope by MainScope() { } } + suspend fun contentLoadFinishAwait( + book: Book, + chapter: BookChapter, + content: String, + upContent: Boolean = true, + resetPageOffset: Boolean + ) { + removeLoading(chapter.index) + if (chapter.index !in durChapterIndex - 1..durChapterIndex + 1) { + return + } + kotlin.runCatching { + val contentProcessor = ContentProcessor.get(book.name, book.origin) + val displayTitle = chapter.getDisplayTitle( + contentProcessor.getTitleReplaceRules(), + book.getUseReplaceRule() + ) + val contents = contentProcessor + .getContent(book, chapter, content, includeTitle = false) + val textChapter = ChapterProvider.getTextChapterAsync( + this@ReadBook, book, chapter, displayTitle, contents, simulatedChapterSize + ) + when (val offset = chapter.index - durChapterIndex) { + 0 -> { + curTextChapter = textChapter + callBack?.upMenuView() + var available = false + for (page in textChapter.layoutChannel) { + val index = page.index + if (!available && page.containPos(durChapterPos)) { + if (upContent) { + callBack?.upContent(offset, resetPageOffset) + } + available = true + } + if (upContent && isScroll) { + if (max(index - 3, 0) < durPageIndex) { + callBack?.upContent(offset, false) + } + } + callBack?.onLayoutPageCompleted(index, page) + } + if (upContent) callBack?.upContent(offset, !available && resetPageOffset) + curPageChanged() + callBack?.contentLoadFinish() + } + + -1 -> { + prevTextChapter = textChapter + textChapter.layoutChannel.receiveAsFlow().collect() + if (upContent) callBack?.upContent(offset, resetPageOffset) + } + + 1 -> { + nextTextChapter = textChapter + for (page in textChapter.layoutChannel) { + if (page.index > 1) { + continue + } + if (upContent) callBack?.upContent(offset, resetPageOffset) + } + } + } + + return + }.onFailure { + AppLog.put("ChapterProvider ERROR", it) + appCtx.toastOnUi("ChapterProvider ERROR:\n${it.stackTraceStr}") + } + } + @Synchronized fun upToc() { val bookSource = bookSource ?: return @@ -718,7 +856,6 @@ object ReadBook : CoroutineScope by MainScope() { downloadedChapters.clear() downloadFailChapters.clear() ImageProvider.clear() - curTextChapter?.cancelLayout() } interface CallBack : LayoutProgressListener { @@ -732,6 +869,12 @@ object ReadBook : CoroutineScope by MainScope() { success: (() -> Unit)? = null ) + suspend fun upContentAwait( + relativePosition: Int = 0, + resetPageOffset: Boolean = true, + success: (() -> Unit)? = null + ) + fun pageChanged() fun contentLoadFinish() diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 59d48b442..b2ceb327d 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -123,6 +123,7 @@ import io.legado.app.utils.throttle import io.legado.app.utils.toastOnUi import io.legado.app.utils.visible import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.ensureActive @@ -798,7 +799,9 @@ class ReadBookActivity : BaseReadBookActivity(), override fun onMenuItemSelected(itemId: Int): Boolean { when (itemId) { R.id.menu_aloud -> when (AppConfig.contentSelectSpeakMod) { - 1 -> binding.readView.aloudStartSelect() + 1 -> lifecycleScope.launch { + binding.readView.aloudStartSelect() + } else -> speak(binding.readView.getSelectText()) } @@ -965,6 +968,19 @@ class ReadBookActivity : BaseReadBookActivity(), } } + override suspend fun upContentAwait( + relativePosition: Int, + resetPageOffset: Boolean, + success: (() -> Unit)? + ) = withContext(Main.immediate) { + binding.readView.cancelSelect() + binding.readView.upContent(relativePosition, resetPageOffset) + if (relativePosition == 0) { + upSeekBarProgress() + } + loadStates = false + } + override fun upPageAnim(upRecorder: Boolean) { lifecycleScope.launch { binding.readView.upPageAnim(upRecorder) diff --git a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt index 2a09cda97..c09d316fd 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt @@ -622,14 +622,14 @@ class ReadView(context: Context, attrs: AttributeSet) : /** * 从选择位置开始朗读 */ - fun aloudStartSelect() { + suspend fun aloudStartSelect() { val selectStartPos = curPage.selectStartPos var pagePos = selectStartPos.relativePagePos val line = selectStartPos.lineIndex val column = selectStartPos.columnIndex while (pagePos > 0) { if (!ReadBook.moveToNextPage()) { - ReadBook.moveToNextChapter(false) + ReadBook.moveToNextChapterAwait(false) } pagePos-- }