Merge branch 'gedoor:master' into master

This commit is contained in:
lff283
2024-02-22 21:56:04 +08:00
committed by GitHub
10 changed files with 121 additions and 70 deletions
@@ -19,7 +19,9 @@ import io.legado.app.model.localBook.TextFile
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.service.BaseReadAloudService import io.legado.app.service.BaseReadAloudService
import io.legado.app.ui.book.read.page.entities.TextChapter import io.legado.app.ui.book.read.page.entities.TextChapter
import io.legado.app.ui.book.read.page.entities.TextPage
import io.legado.app.ui.book.read.page.provider.ChapterProvider import io.legado.app.ui.book.read.page.provider.ChapterProvider
import io.legado.app.ui.book.read.page.provider.LayoutProgressListener
import io.legado.app.utils.stackTraceStr import io.legado.app.utils.stackTraceStr
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@@ -230,7 +232,7 @@ object ReadBook : CoroutineScope by MainScope() {
callBack?.upMenuView() callBack?.upMenuView()
AppLog.putDebug("moveToNextChapter-curPageChanged()") AppLog.putDebug("moveToNextChapter-curPageChanged()")
curPageChanged() curPageChanged()
curTextChapter?.let { callBack?.onCurrentTextChapterChanged(it) } curTextChapter?.let { callBack?.onCurrentTextChapterChanged(it, upContent) }
return true return true
} else { } else {
AppLog.putDebug("跳转下一章失败,没有下一章") AppLog.putDebug("跳转下一章失败,没有下一章")
@@ -260,7 +262,7 @@ object ReadBook : CoroutineScope by MainScope() {
saveRead() saveRead()
callBack?.upMenuView() callBack?.upMenuView()
curPageChanged() curPageChanged()
curTextChapter?.let { callBack?.onCurrentTextChapterChanged(it) } curTextChapter?.let { callBack?.onCurrentTextChapterChanged(it, upContent) }
return true return true
} else { } else {
return false return false
@@ -292,14 +294,27 @@ object ReadBook : CoroutineScope by MainScope() {
curPageChanged(true) curPageChanged(true)
} }
fun openChapter(index: Int, durChapterPos: Int = 0, success: (() -> Unit)? = null) {
if (index < chapterSize) {
clearTextChapter()
callBack?.upContent()
durChapterIndex = index
ReadBook.durChapterPos = durChapterPos
saveRead()
loadContent(resetPageOffset = true) {
success?.invoke()
}
}
}
/** /**
* 当前页面变化 * 当前页面变化
*/ */
private fun curPageChanged(pauseReadAloud: Boolean = false) { private fun curPageChanged(pageChanged: Boolean = false) {
callBack?.pageChanged() callBack?.pageChanged()
if (BaseReadAloudService.isRun) { if (BaseReadAloudService.isRun) {
val scrollPageAnim = pageAnim() == 3 val scrollPageAnim = pageAnim() == 3
if (scrollPageAnim && pauseReadAloud) { if (scrollPageAnim && pageChanged) {
ReadAloud.pause(appCtx) ReadAloud.pause(appCtx)
} else { } else {
readAloud(!BaseReadAloudService.pause) readAloud(!BaseReadAloudService.pause)
@@ -484,38 +499,49 @@ object ReadBook : CoroutineScope by MainScope() {
) )
val contents = contentProcessor val contents = contentProcessor
.getContent(book, chapter, content, includeTitle = false) .getContent(book, chapter, content, includeTitle = false)
val textChapter = ChapterProvider.getTextChapterAsync( val textChapter =
book, ChapterProvider.getTextChapterAsync(chapter, displayTitle, contents, chapterSize)
chapter,
displayTitle,
contents,
chapterSize,
this@ReadBook
)
when (val offset = chapter.index - durChapterIndex) { when (val offset = chapter.index - durChapterIndex) {
0 -> { 0 -> {
curTextChapter?.cancelLayout() curTextChapter?.cancelLayout()
curTextChapter = textChapter curTextChapter = textChapter
if (upContent) callBack?.upContent(offset, resetPageOffset) if (resetPageOffset) {
callBack?.resetPageOffset()
}
callBack?.upMenuView() callBack?.upMenuView()
curPageChanged() curPageChanged()
callBack?.contentLoadFinish() callBack?.contentLoadFinish()
callBack?.onCurrentTextChapterChanged(textChapter) callBack?.onCurrentTextChapterChanged(textChapter, upContent)
} }
-1 -> { -1 -> {
prevTextChapter?.cancelLayout() prevTextChapter?.cancelLayout()
prevTextChapter = textChapter prevTextChapter = textChapter
if (upContent) callBack?.upContent(offset, resetPageOffset) if (upContent) {
textChapter.setProgressListener(object : LayoutProgressListener {
override fun onLayoutCompleted() {
callBack?.upContent(offset, resetPageOffset)
}
})
}
} }
1 -> { 1 -> {
nextTextChapter?.cancelLayout() nextTextChapter?.cancelLayout()
nextTextChapter = textChapter nextTextChapter = textChapter
if (upContent) callBack?.upContent(offset, resetPageOffset) if (upContent) {
textChapter.setProgressListener(object : LayoutProgressListener {
override fun onLayoutPageCompleted(index: Int, page: TextPage) {
if (index > 1) {
return
}
callBack?.upContent(offset, resetPageOffset)
}
})
}
} }
} }
Unit textChapter.createLayout(this@ReadBook, book, contents)
}.onError { }.onError {
AppLog.put("ChapterProvider ERROR", it) AppLog.put("ChapterProvider ERROR", it)
appCtx.toastOnUi("ChapterProvider ERROR:\n${it.stackTraceStr}") appCtx.toastOnUi("ChapterProvider ERROR:\n${it.stackTraceStr}")
@@ -650,7 +676,9 @@ object ReadBook : CoroutineScope by MainScope() {
fun notifyBookChanged() fun notifyBookChanged()
fun onCurrentTextChapterChanged(textChapter: TextChapter) fun onCurrentTextChapterChanged(textChapter: TextChapter, upContent: Boolean = true)
fun resetPageOffset()
} }
} }
@@ -126,6 +126,7 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlin.math.max
/** /**
* 阅读界面 * 阅读界面
@@ -233,6 +234,7 @@ class ReadBookActivity : BaseReadBookActivity(),
binding.readMenu.upSeekBar() binding.readMenu.upSeekBar()
} }
} }
private var upContent = true
//恢复跳转前进度对话框的交互结果 //恢复跳转前进度对话框的交互结果
private var confirmRestoreProcess: Boolean? = null private var confirmRestoreProcess: Boolean? = null
@@ -936,7 +938,9 @@ class ReadBookActivity : BaseReadBookActivity(),
) { ) {
lifecycleScope.launch { lifecycleScope.launch {
binding.readView.upContent(relativePosition, resetPageOffset) binding.readView.upContent(relativePosition, resetPageOffset)
upSeekBarProgress() if (relativePosition == 0) {
upSeekBarProgress()
}
loadStates = false loadStates = false
success?.invoke() success?.invoke()
} }
@@ -1369,16 +1373,38 @@ class ReadBookActivity : BaseReadBookActivity(),
binding.readView.autoPager.resume() binding.readView.autoPager.resume()
} }
override fun onCurrentTextChapterChanged(textChapter: TextChapter) { override fun onCurrentTextChapterChanged(textChapter: TextChapter, upContent: Boolean) {
this.upContent = upContent
textChapter.setProgressListener(this) textChapter.setProgressListener(this)
} }
override fun onLayoutPageCompleted(index: Int, page: TextPage) { override fun onLayoutPageCompleted(index: Int, page: TextPage) {
upSeekBarThrottle.invoke() upSeekBarThrottle.invoke()
if (upContent) {
val durChapterPos = ReadBook.durChapterPos
if (page.containPos(durChapterPos)) {
runOnUiThread {
binding.readView.upContent(0, resetPageOffset = false)
}
}
if (isScroll) {
val pageIndex = ReadBook.durPageIndex
if (max(index - 3, 0) < pageIndex) {
runOnUiThread {
binding.readView.upContent(0, resetPageOffset = false)
}
}
}
}
binding.readView.onLayoutPageCompleted(index, page) binding.readView.onLayoutPageCompleted(index, page)
} }
override fun onLayoutCompleted() { override fun onLayoutCompleted() {
if (upContent) {
runOnUiThread {
binding.readView.upContent(0, resetPageOffset = false)
}
}
binding.readView.onLayoutCompleted() binding.readView.onLayoutCompleted()
} }
@@ -1388,6 +1414,10 @@ class ReadBookActivity : BaseReadBookActivity(),
binding.readView.onLayoutException(e) binding.readView.onLayoutException(e)
} }
override fun resetPageOffset() {
binding.readView.resetPageOffset()
}
/* 全文搜索跳转 */ /* 全文搜索跳转 */
private fun skipToSearch(searchResult: SearchResult) { private fun skipToSearch(searchResult: SearchResult) {
val previousResult = binding.searchMenu.previousSearchResult val previousResult = binding.searchMenu.previousSearchResult
@@ -317,16 +317,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
} }
fun openChapter(index: Int, durChapterPos: Int = 0, success: (() -> Unit)? = null) { fun openChapter(index: Int, durChapterPos: Int = 0, success: (() -> Unit)? = null) {
if (index < ReadBook.chapterSize) { ReadBook.openChapter(index, durChapterPos, success)
ReadBook.clearTextChapter()
ReadBook.callBack?.upContent()
ReadBook.durChapterIndex = index
ReadBook.durChapterPos = durChapterPos
ReadBook.saveRead()
ReadBook.loadContent(resetPageOffset = true) {
success?.invoke()
}
}
} }
fun removeFromBookshelf(success: (() -> Unit)?) { fun removeFromBookshelf(success: (() -> Unit)?) {
@@ -288,6 +288,10 @@ class PageView(context: Context) : FrameLayout(context) {
binding.contentTextView.setContent(textPage) binding.contentTextView.setContent(textPage)
} }
fun invalidateContentView() {
binding.contentTextView.invalidate()
}
/** /**
* 设置无障碍文本 * 设置无障碍文本
*/ */
@@ -527,7 +527,11 @@ class ReadView(context: Context, attrs: AttributeSet) :
curPage.setContentDescription(pageFactory.curPage.text) curPage.setContentDescription(pageFactory.curPage.text)
} }
if (isScroll && !isAutoPage) { if (isScroll && !isAutoPage) {
curPage.setContent(pageFactory.curPage, resetPageOffset) if (relativePosition == 0) {
curPage.setContent(pageFactory.curPage, resetPageOffset)
} else {
curPage.invalidateContentView()
}
} else { } else {
when (relativePosition) { when (relativePosition) {
-1 -> prevPage.setContent(pageFactory.prevPage) -1 -> prevPage.setContent(pageFactory.prevPage)
@@ -662,34 +666,11 @@ class ReadView(context: Context, attrs: AttributeSet) :
} }
override fun onLayoutPageCompleted(index: Int, page: TextPage) { override fun onLayoutPageCompleted(index: Int, page: TextPage) {
val line = page.lines.first()
val durChapterPos = ReadBook.durChapterPos
val startPos = line.chapterPosition
val endPos = startPos + line.charSize
if (durChapterPos in startPos..<endPos) {
post {
upContent(resetPageOffset = false)
}
}
if (isScroll) {
val pageIndex = ReadBook.durPageIndex
if (index - 3 < pageIndex) {
post {
upContent(resetPageOffset = false)
}
}
}
upProgressThrottle.invoke() upProgressThrottle.invoke()
} }
override fun onLayoutCompleted() { fun resetPageOffset() {
post { curPage.resetPageOffset()
upContent(resetPageOffset = false)
}
}
override fun onLayoutException(e: Throwable) {
// no op
} }
override val currentChapter: TextChapter? override val currentChapter: TextChapter?
@@ -266,11 +266,11 @@ data class TextChapter(
) )
} }
fun setProgressListener(l: LayoutProgressListener) { fun setProgressListener(l: LayoutProgressListener?) {
if (isCompleted) { if (isCompleted) {
// no op // no op
} else if (layout?.exception != null) { } else if (layout?.exception != null) {
l.onLayoutException(layout?.exception!!) l?.onLayoutException(layout?.exception!!)
} else { } else {
listener = l listener = l
} }
@@ -287,12 +287,15 @@ data class TextChapter(
} }
override fun onLayoutException(e: Throwable) { override fun onLayoutException(e: Throwable) {
isCompleted = true
listener?.onLayoutException(e) listener?.onLayoutException(e)
listener = null listener = null
} }
fun cancelLayout() { fun cancelLayout() {
layout?.cancel() layout?.cancel()
isCompleted = true
listener = null
} }
companion object { companion object {
@@ -47,6 +47,7 @@ data class TextPage(
var doublePage = false var doublePage = false
var paddingTop = 0 var paddingTop = 0
var isCompleted = false var isCompleted = false
@JvmField @JvmField
var textChapter = emptyTextChapter var textChapter = emptyTextChapter
val pageSize get() = textChapter.pageSize val pageSize get() = textChapter.pageSize
@@ -256,6 +257,19 @@ data class TextPage(
return textChapter return textChapter
} }
/**
* 判断章节字符位置是否在这一页中
*
* @param chapterPos 章节字符位置
* @return
*/
fun containPos(chapterPos: Int): Boolean {
val line = lines.first()
val startPos = line.chapterPosition
val endPos = startPos + charSize
return chapterPos in startPos..<endPos
}
fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) { fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) {
render(view) render(view)
canvas.withTranslation(0f, relativeOffset + paddingTop) { canvas.withTranslation(0f, relativeOffset + paddingTop) {
@@ -32,7 +32,6 @@ import io.legado.app.utils.postEvent
import io.legado.app.utils.spToPx import io.legado.app.utils.spToPx
import io.legado.app.utils.splitNotBlank import io.legado.app.utils.splitNotBlank
import io.legado.app.utils.textHeight import io.legado.app.utils.textHeight
import kotlinx.coroutines.CoroutineScope
import splitties.init.appCtx import splitties.init.appCtx
import java.util.LinkedList import java.util.LinkedList
import java.util.Locale import java.util.Locale
@@ -307,12 +306,10 @@ object ChapterProvider {
} }
fun getTextChapterAsync( fun getTextChapterAsync(
book: Book,
bookChapter: BookChapter, bookChapter: BookChapter,
displayTitle: String, displayTitle: String,
bookContent: BookContent, bookContent: BookContent,
chapterSize: Int, chapterSize: Int,
scope: CoroutineScope
): TextChapter { ): TextChapter {
val textChapter = TextChapter( val textChapter = TextChapter(
@@ -323,9 +320,7 @@ object ChapterProvider {
bookChapter.isVip, bookChapter.isVip,
bookChapter.isPay, bookChapter.isPay,
bookContent.effectiveReplaceRules bookContent.effectiveReplaceRules
).apply { )
createLayout(scope, book, bookContent)
}
return textChapter return textChapter
} }
@@ -7,16 +7,16 @@ interface LayoutProgressListener {
/** /**
* 单页排版完成 * 单页排版完成
*/ */
fun onLayoutPageCompleted(index: Int, page: TextPage) fun onLayoutPageCompleted(index: Int, page: TextPage) {}
/** /**
* 全部排版完成 * 全部排版完成
*/ */
fun onLayoutCompleted() fun onLayoutCompleted() {}
/** /**
* 排版出现异常 * 排版出现异常
*/ */
fun onLayoutException(e: Throwable) fun onLayoutException(e: Throwable) {}
} }
@@ -90,12 +90,12 @@ class TextChapterLayout(
} }
} }
fun setProgressListener(l: LayoutProgressListener) { fun setProgressListener(l: LayoutProgressListener?) {
try { try {
if (isCompleted) { if (isCompleted) {
// no op // no op
} else if (exception != null) { } else if (exception != null) {
l.onLayoutException(exception!!) l?.onLayoutException(exception!!)
} else { } else {
listener = l listener = l
} }
@@ -107,6 +107,7 @@ class TextChapterLayout(
fun cancel() { fun cancel() {
job.cancel() job.cancel()
listener = null
} }
private fun onPageCompleted() { private fun onPageCompleted() {
@@ -141,6 +142,7 @@ class TextChapterLayout(
private fun onException(e: Throwable) { private fun onException(e: Throwable) {
if (e is CancellationException) { if (e is CancellationException) {
listener = null
return return
} }
try { try {
@@ -280,6 +282,7 @@ class TextChapterLayout(
textPage.height += endPadding textPage.height += endPadding
} }
textPage.text = stringBuilder.toString() textPage.text = stringBuilder.toString()
coroutineContext.ensureActive()
onPageCompleted() onPageCompleted()
onCompleted() onCompleted()
} }
@@ -308,6 +311,7 @@ class TextChapterLayout(
} }
textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" } textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" }
stringBuilder.clear() stringBuilder.clear()
coroutineContext.ensureActive()
onPageCompleted() onPageCompleted()
textPages.add(TextPage()) textPages.add(TextPage())
durY = 0f durY = 0f
@@ -342,6 +346,7 @@ class TextChapterLayout(
} }
textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" } textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" }
stringBuilder.clear() stringBuilder.clear()
coroutineContext.ensureActive()
onPageCompleted() onPageCompleted()
textPages.add(TextPage()) textPages.add(TextPage())
} }
@@ -424,7 +429,6 @@ class TextChapterLayout(
else -> y else -> y
} }
for (lineIndex in 0 until layout.lineCount) { for (lineIndex in 0 until layout.lineCount) {
coroutineContext.ensureActive()
val textLine = TextLine(isTitle = isTitle) val textLine = TextLine(isTitle = isTitle)
if (durY + textHeight > visibleHeight) { if (durY + textHeight > visibleHeight) {
val textPage = textPages.last() val textPage = textPages.last()
@@ -438,6 +442,7 @@ class TextChapterLayout(
textPage.leftLineSize = textPage.lineSize textPage.leftLineSize = textPage.lineSize
} }
textPage.text = stringBuilder.toString() textPage.text = stringBuilder.toString()
coroutineContext.ensureActive()
onPageCompleted() onPageCompleted()
//新建页面 //新建页面
textPages.add(TextPage()) textPages.add(TextPage())