Horis 2 лет назад
Родитель
Сommit
497601f5b5

+ 26 - 0
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,

+ 153 - 10
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<Int>()
@@ -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()

+ 17 - 1
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)

+ 2 - 2
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--
         }