优化
This commit is contained in:
@@ -16,6 +16,7 @@ import io.legado.app.model.webBook.WebBook
|
|||||||
import io.legado.app.service.CacheBookService
|
import io.legado.app.service.CacheBookService
|
||||||
import io.legado.app.utils.postEvent
|
import io.legado.app.utils.postEvent
|
||||||
import io.legado.app.utils.startService
|
import io.legado.app.utils.startService
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.CoroutineStart
|
import kotlinx.coroutines.CoroutineStart
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
@@ -308,6 +309,31 @@ object CacheBook {
|
|||||||
}.start()
|
}.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
|
@Synchronized
|
||||||
fun download(
|
fun download(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ import kotlinx.coroutines.ensureActive
|
|||||||
import kotlinx.coroutines.flow.collect
|
import kotlinx.coroutines.flow.collect
|
||||||
import kotlinx.coroutines.flow.receiveAsFlow
|
import kotlinx.coroutines.flow.receiveAsFlow
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
@@ -55,8 +56,20 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
var isLocalBook = true
|
var isLocalBook = true
|
||||||
var chapterChanged = false
|
var chapterChanged = false
|
||||||
var prevTextChapter: TextChapter? = null
|
var prevTextChapter: TextChapter? = null
|
||||||
|
set(value) {
|
||||||
|
field?.cancelLayout()
|
||||||
|
field = value
|
||||||
|
}
|
||||||
var curTextChapter: TextChapter? = null
|
var curTextChapter: TextChapter? = null
|
||||||
|
set(value) {
|
||||||
|
field?.cancelLayout()
|
||||||
|
field = value
|
||||||
|
}
|
||||||
var nextTextChapter: TextChapter? = null
|
var nextTextChapter: TextChapter? = null
|
||||||
|
set(value) {
|
||||||
|
field?.cancelLayout()
|
||||||
|
field = value
|
||||||
|
}
|
||||||
var bookSource: BookSource? = null
|
var bookSource: BookSource? = null
|
||||||
var msg: String? = null
|
var msg: String? = null
|
||||||
private val loadingChapters = arrayListOf<Int>()
|
private val loadingChapters = arrayListOf<Int>()
|
||||||
@@ -175,9 +188,6 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun clearTextChapter() {
|
fun clearTextChapter() {
|
||||||
prevTextChapter?.cancelLayout()
|
|
||||||
curTextChapter?.cancelLayout()
|
|
||||||
nextTextChapter?.cancelLayout()
|
|
||||||
prevTextChapter = null
|
prevTextChapter = null
|
||||||
curTextChapter = null
|
curTextChapter = null
|
||||||
nextTextChapter = null
|
nextTextChapter = null
|
||||||
@@ -245,7 +255,6 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
if (durChapterIndex < simulatedChapterSize - 1) {
|
if (durChapterIndex < simulatedChapterSize - 1) {
|
||||||
durChapterPos = 0
|
durChapterPos = 0
|
||||||
durChapterIndex++
|
durChapterIndex++
|
||||||
prevTextChapter?.cancelLayout()
|
|
||||||
prevTextChapter = curTextChapter
|
prevTextChapter = curTextChapter
|
||||||
curTextChapter = nextTextChapter
|
curTextChapter = nextTextChapter
|
||||||
nextTextChapter = null
|
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(
|
fun moveToPrevChapter(
|
||||||
upContent: Boolean,
|
upContent: Boolean,
|
||||||
toLast: Boolean = true,
|
toLast: Boolean = true,
|
||||||
@@ -277,7 +316,6 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
if (durChapterIndex > 0) {
|
if (durChapterIndex > 0) {
|
||||||
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
|
durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0
|
||||||
durChapterIndex--
|
durChapterIndex--
|
||||||
nextTextChapter?.cancelLayout()
|
|
||||||
nextTextChapter = curTextChapter
|
nextTextChapter = curTextChapter
|
||||||
curTextChapter = prevTextChapter
|
curTextChapter = prevTextChapter
|
||||||
prevTextChapter = null
|
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 {
|
private fun addLoading(index: Int): Boolean {
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
if (loadingChapters.contains(index)) return false
|
if (loadingChapters.contains(index)) return false
|
||||||
@@ -561,7 +631,6 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
)
|
)
|
||||||
when (val offset = chapter.index - durChapterIndex) {
|
when (val offset = chapter.index - durChapterIndex) {
|
||||||
0 -> {
|
0 -> {
|
||||||
curTextChapter?.cancelLayout()
|
|
||||||
curTextChapter = textChapter
|
curTextChapter = textChapter
|
||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
var available = false
|
var available = false
|
||||||
@@ -586,14 +655,12 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
-1 -> {
|
-1 -> {
|
||||||
prevTextChapter?.cancelLayout()
|
|
||||||
prevTextChapter = textChapter
|
prevTextChapter = textChapter
|
||||||
textChapter.layoutChannel.receiveAsFlow().collect()
|
textChapter.layoutChannel.receiveAsFlow().collect()
|
||||||
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
nextTextChapter?.cancelLayout()
|
|
||||||
nextTextChapter = textChapter
|
nextTextChapter = textChapter
|
||||||
for (page in textChapter.layoutChannel) {
|
for (page in textChapter.layoutChannel) {
|
||||||
if (page.index > 1) {
|
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
|
@Synchronized
|
||||||
fun upToc() {
|
fun upToc() {
|
||||||
val bookSource = bookSource ?: return
|
val bookSource = bookSource ?: return
|
||||||
@@ -718,7 +856,6 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
downloadedChapters.clear()
|
downloadedChapters.clear()
|
||||||
downloadFailChapters.clear()
|
downloadFailChapters.clear()
|
||||||
ImageProvider.clear()
|
ImageProvider.clear()
|
||||||
curTextChapter?.cancelLayout()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CallBack : LayoutProgressListener {
|
interface CallBack : LayoutProgressListener {
|
||||||
@@ -732,6 +869,12 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
success: (() -> Unit)? = null
|
success: (() -> Unit)? = null
|
||||||
)
|
)
|
||||||
|
|
||||||
|
suspend fun upContentAwait(
|
||||||
|
relativePosition: Int = 0,
|
||||||
|
resetPageOffset: Boolean = true,
|
||||||
|
success: (() -> Unit)? = null
|
||||||
|
)
|
||||||
|
|
||||||
fun pageChanged()
|
fun pageChanged()
|
||||||
|
|
||||||
fun contentLoadFinish()
|
fun contentLoadFinish()
|
||||||
|
|||||||
@@ -123,6 +123,7 @@ import io.legado.app.utils.throttle
|
|||||||
import io.legado.app.utils.toastOnUi
|
import io.legado.app.utils.toastOnUi
|
||||||
import io.legado.app.utils.visible
|
import io.legado.app.utils.visible
|
||||||
import kotlinx.coroutines.Dispatchers.IO
|
import kotlinx.coroutines.Dispatchers.IO
|
||||||
|
import kotlinx.coroutines.Dispatchers.Main
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.ensureActive
|
import kotlinx.coroutines.ensureActive
|
||||||
@@ -798,7 +799,9 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
override fun onMenuItemSelected(itemId: Int): Boolean {
|
override fun onMenuItemSelected(itemId: Int): Boolean {
|
||||||
when (itemId) {
|
when (itemId) {
|
||||||
R.id.menu_aloud -> when (AppConfig.contentSelectSpeakMod) {
|
R.id.menu_aloud -> when (AppConfig.contentSelectSpeakMod) {
|
||||||
1 -> binding.readView.aloudStartSelect()
|
1 -> lifecycleScope.launch {
|
||||||
|
binding.readView.aloudStartSelect()
|
||||||
|
}
|
||||||
else -> speak(binding.readView.getSelectText())
|
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) {
|
override fun upPageAnim(upRecorder: Boolean) {
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
binding.readView.upPageAnim(upRecorder)
|
binding.readView.upPageAnim(upRecorder)
|
||||||
|
|||||||
@@ -622,14 +622,14 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
/**
|
/**
|
||||||
* 从选择位置开始朗读
|
* 从选择位置开始朗读
|
||||||
*/
|
*/
|
||||||
fun aloudStartSelect() {
|
suspend fun aloudStartSelect() {
|
||||||
val selectStartPos = curPage.selectStartPos
|
val selectStartPos = curPage.selectStartPos
|
||||||
var pagePos = selectStartPos.relativePagePos
|
var pagePos = selectStartPos.relativePagePos
|
||||||
val line = selectStartPos.lineIndex
|
val line = selectStartPos.lineIndex
|
||||||
val column = selectStartPos.columnIndex
|
val column = selectStartPos.columnIndex
|
||||||
while (pagePos > 0) {
|
while (pagePos > 0) {
|
||||||
if (!ReadBook.moveToNextPage()) {
|
if (!ReadBook.moveToNextPage()) {
|
||||||
ReadBook.moveToNextChapter(false)
|
ReadBook.moveToNextChapterAwait(false)
|
||||||
}
|
}
|
||||||
pagePos--
|
pagePos--
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user