Merge branch 'gedoor:master' into master
This commit is contained in:
@@ -52,4 +52,6 @@ object AppPattern {
|
|||||||
val spaceRegex = "\\s+".toRegex()
|
val spaceRegex = "\\s+".toRegex()
|
||||||
|
|
||||||
val regexCharRegex = "[{}()\\[\\].+*?^$\\\\|]".toRegex()
|
val regexCharRegex = "[{}()\\[\\].+*?^$\\\\|]".toRegex()
|
||||||
|
|
||||||
|
val LFRegex = "\n".toRegex()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package io.legado.app.constant
|
|||||||
|
|
||||||
import androidx.annotation.IntDef
|
import androidx.annotation.IntDef
|
||||||
|
|
||||||
|
@Suppress("ConstPropertyName")
|
||||||
object PageAnim {
|
object PageAnim {
|
||||||
|
|
||||||
const val coverPageAnim = 0
|
const val coverPageAnim = 0
|
||||||
|
|||||||
@@ -16,10 +16,12 @@ import io.legado.app.help.book.BookHelp
|
|||||||
import io.legado.app.help.book.ContentProcessor
|
import io.legado.app.help.book.ContentProcessor
|
||||||
import io.legado.app.help.book.isEpub
|
import io.legado.app.help.book.isEpub
|
||||||
import io.legado.app.help.book.isImage
|
import io.legado.app.help.book.isImage
|
||||||
|
import io.legado.app.help.book.isLocal
|
||||||
import io.legado.app.help.book.isPdf
|
import io.legado.app.help.book.isPdf
|
||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.config.ReadBookConfig
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
import io.legado.app.model.ReadBook
|
import io.legado.app.model.ReadBook
|
||||||
|
import io.legado.app.model.localBook.LocalBook
|
||||||
import io.legado.app.utils.GSON
|
import io.legado.app.utils.GSON
|
||||||
import io.legado.app.utils.MD5Utils
|
import io.legado.app.utils.MD5Utils
|
||||||
import io.legado.app.utils.fromJsonObject
|
import io.legado.app.utils.fromJsonObject
|
||||||
@@ -286,6 +288,10 @@ data class Book(
|
|||||||
return appDb.bookSourceDao.getBookSource(origin)
|
return appDb.bookSourceDao.getBookSource(origin)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isLocalModified(): Boolean {
|
||||||
|
return isLocal && LocalBook.getLastModified(this).getOrDefault(0L) > latestChapterTime
|
||||||
|
}
|
||||||
|
|
||||||
fun toSearchBook() = SearchBook(
|
fun toSearchBook() = SearchBook(
|
||||||
name = name,
|
name = name,
|
||||||
author = author,
|
author = author,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.legado.app.model
|
package io.legado.app.model
|
||||||
|
|
||||||
import io.legado.app.constant.AppLog
|
import io.legado.app.constant.AppLog
|
||||||
|
import io.legado.app.constant.PageAnim.scrollPageAnim
|
||||||
import io.legado.app.data.appDb
|
import io.legado.app.data.appDb
|
||||||
import io.legado.app.data.entities.Book
|
import io.legado.app.data.entities.Book
|
||||||
import io.legado.app.data.entities.BookChapter
|
import io.legado.app.data.entities.BookChapter
|
||||||
@@ -32,6 +33,7 @@ import kotlinx.coroutines.cancelChildren
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import splitties.init.appCtx
|
import splitties.init.appCtx
|
||||||
|
import kotlin.math.max
|
||||||
import kotlin.math.min
|
import kotlin.math.min
|
||||||
|
|
||||||
|
|
||||||
@@ -88,7 +90,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
readRecord.readTime = appDb.readRecordDao.getReadTime(book.name) ?: 0
|
readRecord.readTime = appDb.readRecordDao.getReadTime(book.name) ?: 0
|
||||||
chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
|
chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)
|
||||||
contentProcessor = ContentProcessor.get(book)
|
contentProcessor = ContentProcessor.get(book)
|
||||||
durChapterIndex = book.durChapterIndex
|
durChapterIndex = min(book.durChapterIndex, chapterSize - 1).coerceAtLeast(0)
|
||||||
durChapterPos = book.durChapterPos
|
durChapterPos = book.durChapterPos
|
||||||
isLocalBook = book.isLocal
|
isLocalBook = book.isLocal
|
||||||
clearTextChapter()
|
clearTextChapter()
|
||||||
@@ -111,6 +113,15 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
durChapterPos = book.durChapterPos
|
durChapterPos = book.durChapterPos
|
||||||
clearTextChapter()
|
clearTextChapter()
|
||||||
}
|
}
|
||||||
|
if (curTextChapter?.isCompleted == false) {
|
||||||
|
curTextChapter = null
|
||||||
|
}
|
||||||
|
if (nextTextChapter?.isCompleted == false) {
|
||||||
|
nextTextChapter = null
|
||||||
|
}
|
||||||
|
if (prevTextChapter?.isCompleted == false) {
|
||||||
|
prevTextChapter = null
|
||||||
|
}
|
||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
upWebBook(book)
|
upWebBook(book)
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
@@ -221,18 +232,17 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
nextTextChapter = null
|
nextTextChapter = null
|
||||||
if (curTextChapter == null) {
|
if (curTextChapter == null) {
|
||||||
AppLog.putDebug("moveToNextChapter-章节未加载,开始加载")
|
AppLog.putDebug("moveToNextChapter-章节未加载,开始加载")
|
||||||
if (upContentInPlace) callBack?.upContent(resetPageOffset = false)
|
if (upContentInPlace) callBack?.upContent()
|
||||||
loadContent(durChapterIndex, upContent, resetPageOffset = false)
|
loadContent(durChapterIndex, upContent, resetPageOffset = false)
|
||||||
} else if (upContent && upContentInPlace) {
|
} else if (upContent && upContentInPlace) {
|
||||||
AppLog.putDebug("moveToNextChapter-章节已加载,刷新视图")
|
AppLog.putDebug("moveToNextChapter-章节已加载,刷新视图")
|
||||||
callBack?.upContent(resetPageOffset = false)
|
callBack?.upContent()
|
||||||
}
|
}
|
||||||
loadContent(durChapterIndex.plus(1), upContent, false)
|
loadContent(durChapterIndex.plus(1), upContent, false)
|
||||||
saveRead()
|
saveRead()
|
||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
AppLog.putDebug("moveToNextChapter-curPageChanged()")
|
AppLog.putDebug("moveToNextChapter-curPageChanged()")
|
||||||
curPageChanged()
|
curPageChanged()
|
||||||
curTextChapter?.let { callBack?.onCurrentTextChapterChanged(it, upContent) }
|
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
AppLog.putDebug("跳转下一章失败,没有下一章")
|
AppLog.putDebug("跳转下一章失败,没有下一章")
|
||||||
@@ -253,16 +263,15 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
curTextChapter = prevTextChapter
|
curTextChapter = prevTextChapter
|
||||||
prevTextChapter = null
|
prevTextChapter = null
|
||||||
if (curTextChapter == null) {
|
if (curTextChapter == null) {
|
||||||
if (upContentInPlace) callBack?.upContent(resetPageOffset = false)
|
if (upContentInPlace) callBack?.upContent()
|
||||||
loadContent(durChapterIndex, upContent, resetPageOffset = false)
|
loadContent(durChapterIndex, upContent, resetPageOffset = false)
|
||||||
} else if (upContent && upContentInPlace) {
|
} else if (upContent && upContentInPlace) {
|
||||||
callBack?.upContent(resetPageOffset = false)
|
callBack?.upContent()
|
||||||
}
|
}
|
||||||
loadContent(durChapterIndex.minus(1), upContent, false)
|
loadContent(durChapterIndex.minus(1), upContent, false)
|
||||||
saveRead()
|
saveRead()
|
||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
curPageChanged()
|
curPageChanged()
|
||||||
curTextChapter?.let { callBack?.onCurrentTextChapterChanged(it, upContent) }
|
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
return false
|
return false
|
||||||
@@ -312,7 +321,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
*/
|
*/
|
||||||
private fun curPageChanged(pageChanged: Boolean = false) {
|
private fun curPageChanged(pageChanged: Boolean = false) {
|
||||||
callBack?.pageChanged()
|
callBack?.pageChanged()
|
||||||
if (BaseReadAloudService.isRun) {
|
if (BaseReadAloudService.isRun && isLayoutAvailable) {
|
||||||
val scrollPageAnim = pageAnim() == 3
|
val scrollPageAnim = pageAnim() == 3
|
||||||
if (scrollPageAnim && pageChanged) {
|
if (scrollPageAnim && pageChanged) {
|
||||||
ReadAloud.pause(appCtx)
|
ReadAloud.pause(appCtx)
|
||||||
@@ -341,6 +350,13 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
return curTextChapter?.getPageIndexByCharIndex(durChapterPos) ?: durChapterPos
|
return curTextChapter?.getPageIndexByCharIndex(durChapterPos) ?: durChapterPos
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否排版到了当前阅读位置
|
||||||
|
*/
|
||||||
|
val isLayoutAvailable inline get() = durPageIndex >= 0
|
||||||
|
|
||||||
|
val isScroll inline get() = pageAnim() == scrollPageAnim
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* chapterOnDur: 0为当前页,1为下一页,-1为上一页
|
* chapterOnDur: 0为当前页,1为下一页,-1为上一页
|
||||||
*/
|
*/
|
||||||
@@ -369,6 +385,20 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
loadContent(durChapterIndex - 1, resetPageOffset = resetPageOffset)
|
loadContent(durChapterIndex - 1, resetPageOffset = resetPageOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun loadOrUpContent() {
|
||||||
|
if (curTextChapter == null) {
|
||||||
|
loadContent(durChapterIndex)
|
||||||
|
} else {
|
||||||
|
callBack?.upContent()
|
||||||
|
}
|
||||||
|
if (nextTextChapter == null) {
|
||||||
|
loadContent(durChapterIndex + 1)
|
||||||
|
}
|
||||||
|
if (prevTextChapter == null) {
|
||||||
|
loadContent(durChapterIndex - 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载章节内容
|
* 加载章节内容
|
||||||
* @param index 章节序号
|
* @param index 章节序号
|
||||||
@@ -392,10 +422,9 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
chapter,
|
chapter,
|
||||||
it,
|
it,
|
||||||
upContent,
|
upContent,
|
||||||
resetPageOffset
|
resetPageOffset,
|
||||||
) {
|
success
|
||||||
success?.invoke()
|
)
|
||||||
}
|
|
||||||
} ?: download(
|
} ?: download(
|
||||||
downloadScope,
|
downloadScope,
|
||||||
chapter,
|
chapter,
|
||||||
@@ -456,9 +485,8 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
chapter,
|
chapter,
|
||||||
"加载正文失败\n$msg",
|
"加载正文失败\n$msg",
|
||||||
resetPageOffset = resetPageOffset,
|
resetPageOffset = resetPageOffset,
|
||||||
) {
|
success = success
|
||||||
success?.invoke()
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,48 +533,83 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
0 -> {
|
0 -> {
|
||||||
curTextChapter?.cancelLayout()
|
curTextChapter?.cancelLayout()
|
||||||
curTextChapter = textChapter
|
curTextChapter = textChapter
|
||||||
if (resetPageOffset) {
|
|
||||||
callBack?.resetPageOffset()
|
|
||||||
}
|
|
||||||
callBack?.upMenuView()
|
callBack?.upMenuView()
|
||||||
curPageChanged()
|
textChapter.setProgressListener(object : LayoutProgressListener {
|
||||||
callBack?.contentLoadFinish()
|
var available = false
|
||||||
callBack?.onCurrentTextChapterChanged(textChapter, upContent)
|
|
||||||
|
override fun onLayoutPageCompleted(index: Int, page: TextPage) {
|
||||||
|
if (!available && page.containPos(durChapterPos)) {
|
||||||
|
if (upContent) {
|
||||||
|
callBack?.upContent(offset, resetPageOffset)
|
||||||
|
}
|
||||||
|
curPageChanged()
|
||||||
|
callBack?.contentLoadFinish()
|
||||||
|
available = true
|
||||||
|
}
|
||||||
|
if (upContent && isScroll) {
|
||||||
|
if (max(index - 3, 0) < durPageIndex) {
|
||||||
|
callBack?.upContent(offset, resetPageOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
callBack?.onLayoutPageCompleted(index, page)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutCompleted() {
|
||||||
|
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
||||||
|
success?.invoke()
|
||||||
|
callBack?.onLayoutCompleted()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutException(e: Throwable) {
|
||||||
|
AppLog.put("ChapterProvider ERROR", e)
|
||||||
|
appCtx.toastOnUi("ChapterProvider ERROR:\n${e.stackTraceStr}")
|
||||||
|
callBack?.onLayoutException(e)
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
-1 -> {
|
-1 -> {
|
||||||
prevTextChapter?.cancelLayout()
|
prevTextChapter?.cancelLayout()
|
||||||
prevTextChapter = textChapter
|
prevTextChapter = textChapter
|
||||||
if (upContent) {
|
textChapter.setProgressListener(object : LayoutProgressListener {
|
||||||
textChapter.setProgressListener(object : LayoutProgressListener {
|
override fun onLayoutCompleted() {
|
||||||
override fun onLayoutCompleted() {
|
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
||||||
callBack?.upContent(offset, resetPageOffset)
|
success?.invoke()
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
override fun onLayoutException(e: Throwable) {
|
||||||
|
AppLog.put("ChapterProvider ERROR", e)
|
||||||
|
appCtx.toastOnUi("ChapterProvider ERROR:\n${e.stackTraceStr}")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
1 -> {
|
1 -> {
|
||||||
nextTextChapter?.cancelLayout()
|
nextTextChapter?.cancelLayout()
|
||||||
nextTextChapter = textChapter
|
nextTextChapter = textChapter
|
||||||
if (upContent) {
|
textChapter.setProgressListener(object : LayoutProgressListener {
|
||||||
textChapter.setProgressListener(object : LayoutProgressListener {
|
override fun onLayoutPageCompleted(index: Int, page: TextPage) {
|
||||||
override fun onLayoutPageCompleted(index: Int, page: TextPage) {
|
if (index > 1) {
|
||||||
if (index > 1) {
|
return
|
||||||
return
|
|
||||||
}
|
|
||||||
callBack?.upContent(offset, resetPageOffset)
|
|
||||||
}
|
}
|
||||||
})
|
if (upContent) callBack?.upContent(offset, resetPageOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLayoutCompleted() {
|
||||||
|
success?.invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onLayoutException(e: Throwable) {
|
||||||
|
AppLog.put("ChapterProvider ERROR", e)
|
||||||
|
appCtx.toastOnUi("ChapterProvider ERROR:\n${e.stackTraceStr}")
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
textChapter.createLayout(this@ReadBook, book, contents)
|
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}")
|
||||||
}.onSuccess {
|
|
||||||
success?.invoke()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -655,9 +718,10 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
downloadedChapters.clear()
|
downloadedChapters.clear()
|
||||||
downloadFailChapters.clear()
|
downloadFailChapters.clear()
|
||||||
ImageProvider.clear()
|
ImageProvider.clear()
|
||||||
|
curTextChapter?.cancelLayout()
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CallBack {
|
interface CallBack : LayoutProgressListener {
|
||||||
fun upMenuView()
|
fun upMenuView()
|
||||||
|
|
||||||
fun loadChapterList(book: Book)
|
fun loadChapterList(book: Book)
|
||||||
@@ -675,10 +739,6 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
fun upPageAnim()
|
fun upPageAnim()
|
||||||
|
|
||||||
fun notifyBookChanged()
|
fun notifyBookChanged()
|
||||||
|
|
||||||
fun onCurrentTextChapterChanged(textChapter: TextChapter, upContent: Boolean = true)
|
|
||||||
|
|
||||||
fun resetPageOffset()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package io.legado.app.model.webBook
|
package io.legado.app.model.webBook
|
||||||
|
|
||||||
import io.legado.app.R
|
import io.legado.app.R
|
||||||
|
import io.legado.app.constant.AppPattern
|
||||||
import io.legado.app.data.appDb
|
import io.legado.app.data.appDb
|
||||||
import io.legado.app.data.entities.Book
|
import io.legado.app.data.entities.Book
|
||||||
import io.legado.app.data.entities.BookChapter
|
import io.legado.app.data.entities.BookChapter
|
||||||
@@ -131,7 +132,10 @@ object BookContent {
|
|||||||
//全文替换
|
//全文替换
|
||||||
val replaceRegex = contentRule.replaceRegex
|
val replaceRegex = contentRule.replaceRegex
|
||||||
if (!replaceRegex.isNullOrEmpty()) {
|
if (!replaceRegex.isNullOrEmpty()) {
|
||||||
|
contentStr = contentStr.split(AppPattern.LFRegex)
|
||||||
|
.joinToString("\n") { it.trim { c -> c <= ' ' || c == ' ' } }
|
||||||
contentStr = analyzeRule.getString(replaceRegex, contentStr)
|
contentStr = analyzeRule.getString(replaceRegex, contentStr)
|
||||||
|
contentStr = contentStr.split(AppPattern.LFRegex).joinToString("\n") { " $it" }
|
||||||
}
|
}
|
||||||
Debug.log(bookSource.bookSourceUrl, "┌获取章节名称")
|
Debug.log(bookSource.bookSourceUrl, "┌获取章节名称")
|
||||||
Debug.log(bookSource.bookSourceUrl, "└${bookChapter.title}")
|
Debug.log(bookSource.bookSourceUrl, "└${bookChapter.title}")
|
||||||
|
|||||||
@@ -77,7 +77,6 @@ import io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_DIVIDER_C
|
|||||||
import io.legado.app.ui.book.read.page.ContentTextView
|
import io.legado.app.ui.book.read.page.ContentTextView
|
||||||
import io.legado.app.ui.book.read.page.ReadView
|
import io.legado.app.ui.book.read.page.ReadView
|
||||||
import io.legado.app.ui.book.read.page.entities.PageDirection
|
import io.legado.app.ui.book.read.page.entities.PageDirection
|
||||||
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.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.ui.book.read.page.provider.LayoutProgressListener
|
||||||
@@ -115,7 +114,6 @@ import io.legado.app.utils.observeEvent
|
|||||||
import io.legado.app.utils.observeEventSticky
|
import io.legado.app.utils.observeEventSticky
|
||||||
import io.legado.app.utils.postEvent
|
import io.legado.app.utils.postEvent
|
||||||
import io.legado.app.utils.showDialogFragment
|
import io.legado.app.utils.showDialogFragment
|
||||||
import io.legado.app.utils.stackTraceStr
|
|
||||||
import io.legado.app.utils.startActivity
|
import io.legado.app.utils.startActivity
|
||||||
import io.legado.app.utils.sysScreenOffTime
|
import io.legado.app.utils.sysScreenOffTime
|
||||||
import io.legado.app.utils.throttle
|
import io.legado.app.utils.throttle
|
||||||
@@ -126,7 +124,6 @@ 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
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 阅读界面
|
* 阅读界面
|
||||||
@@ -1373,51 +1370,11 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
binding.readView.autoPager.resume()
|
binding.readView.autoPager.resume()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCurrentTextChapterChanged(textChapter: TextChapter, upContent: Boolean) {
|
|
||||||
this.upContent = upContent
|
|
||||||
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() {
|
|
||||||
if (upContent) {
|
|
||||||
runOnUiThread {
|
|
||||||
binding.readView.upContent(0, resetPageOffset = false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding.readView.onLayoutCompleted()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onLayoutException(e: Throwable) {
|
|
||||||
AppLog.put("ChapterProvider ERROR", e)
|
|
||||||
toastOnUi("ChapterProvider ERROR:\n${e.stackTraceStr}")
|
|
||||||
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
|
||||||
|
|||||||
@@ -109,21 +109,12 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
|
|||||||
} else {
|
} else {
|
||||||
loadChapterList(book)
|
loadChapterList(book)
|
||||||
}
|
}
|
||||||
} else if (book.isLocal
|
} else if (book.isLocalModified()) {
|
||||||
&& LocalBook.getLastModified(book).getOrDefault(0L) > book.latestChapterTime
|
|
||||||
) {
|
|
||||||
loadChapterList(book)
|
loadChapterList(book)
|
||||||
} else if (isSameBook) {
|
} else if (isSameBook) {
|
||||||
if (ReadBook.curTextChapter != null) {
|
ReadBook.loadOrUpContent()
|
||||||
ReadBook.callBack?.upContent(resetPageOffset = false)
|
|
||||||
} else {
|
|
||||||
ReadBook.loadContent(resetPageOffset = true)
|
|
||||||
}
|
|
||||||
checkLocalBookFileExist(book)
|
checkLocalBookFileExist(book)
|
||||||
} else {
|
} else {
|
||||||
if (ReadBook.durChapterIndex > ReadBook.chapterSize - 1) {
|
|
||||||
ReadBook.durChapterIndex = ReadBook.chapterSize - 1
|
|
||||||
}
|
|
||||||
ReadBook.loadContent(resetPageOffset = false)
|
ReadBook.loadContent(resetPageOffset = false)
|
||||||
checkLocalBookFileExist(book)
|
checkLocalBookFileExist(book)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
|
|||||||
clearRepeat(tipValue)
|
clearRepeat(tipValue)
|
||||||
ReadTipConfig.tipHeaderLeft = tipValue
|
ReadTipConfig.tipHeaderLeft = tipValue
|
||||||
tvHeaderLeft.text = ReadTipConfig.tipNames[i]
|
tvHeaderLeft.text = ReadTipConfig.tipNames[i]
|
||||||
postEvent(EventBus.UP_CONFIG, arrayOf(2))
|
postEvent(EventBus.UP_CONFIG, arrayOf(2, 6))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
llHeaderMiddle.setOnClickListener {
|
llHeaderMiddle.setOnClickListener {
|
||||||
@@ -139,7 +139,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
|
|||||||
clearRepeat(tipValue)
|
clearRepeat(tipValue)
|
||||||
ReadTipConfig.tipHeaderMiddle = tipValue
|
ReadTipConfig.tipHeaderMiddle = tipValue
|
||||||
tvHeaderMiddle.text = ReadTipConfig.tipNames[i]
|
tvHeaderMiddle.text = ReadTipConfig.tipNames[i]
|
||||||
postEvent(EventBus.UP_CONFIG, arrayOf(2))
|
postEvent(EventBus.UP_CONFIG, arrayOf(2, 6))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
llHeaderRight.setOnClickListener {
|
llHeaderRight.setOnClickListener {
|
||||||
@@ -148,7 +148,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
|
|||||||
clearRepeat(tipValue)
|
clearRepeat(tipValue)
|
||||||
ReadTipConfig.tipHeaderRight = tipValue
|
ReadTipConfig.tipHeaderRight = tipValue
|
||||||
tvHeaderRight.text = ReadTipConfig.tipNames[i]
|
tvHeaderRight.text = ReadTipConfig.tipNames[i]
|
||||||
postEvent(EventBus.UP_CONFIG, arrayOf(2))
|
postEvent(EventBus.UP_CONFIG, arrayOf(2, 6))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
llFooterLeft.setOnClickListener {
|
llFooterLeft.setOnClickListener {
|
||||||
@@ -157,7 +157,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
|
|||||||
clearRepeat(tipValue)
|
clearRepeat(tipValue)
|
||||||
ReadTipConfig.tipFooterLeft = tipValue
|
ReadTipConfig.tipFooterLeft = tipValue
|
||||||
tvFooterLeft.text = ReadTipConfig.tipNames[i]
|
tvFooterLeft.text = ReadTipConfig.tipNames[i]
|
||||||
postEvent(EventBus.UP_CONFIG, arrayOf(2))
|
postEvent(EventBus.UP_CONFIG, arrayOf(2, 6))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
llFooterMiddle.setOnClickListener {
|
llFooterMiddle.setOnClickListener {
|
||||||
@@ -166,7 +166,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
|
|||||||
clearRepeat(tipValue)
|
clearRepeat(tipValue)
|
||||||
ReadTipConfig.tipFooterMiddle = tipValue
|
ReadTipConfig.tipFooterMiddle = tipValue
|
||||||
tvFooterMiddle.text = ReadTipConfig.tipNames[i]
|
tvFooterMiddle.text = ReadTipConfig.tipNames[i]
|
||||||
postEvent(EventBus.UP_CONFIG, arrayOf(2))
|
postEvent(EventBus.UP_CONFIG, arrayOf(2, 6))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
llFooterRight.setOnClickListener {
|
llFooterRight.setOnClickListener {
|
||||||
@@ -175,7 +175,7 @@ class TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {
|
|||||||
clearRepeat(tipValue)
|
clearRepeat(tipValue)
|
||||||
ReadTipConfig.tipFooterRight = tipValue
|
ReadTipConfig.tipFooterRight = tipValue
|
||||||
tvFooterRight.text = ReadTipConfig.tipNames[i]
|
tvFooterRight.text = ReadTipConfig.tipNames[i]
|
||||||
postEvent(EventBus.UP_CONFIG, arrayOf(2))
|
postEvent(EventBus.UP_CONFIG, arrayOf(2, 6))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
llTipColor.setOnClickListener {
|
llTipColor.setOnClickListener {
|
||||||
|
|||||||
@@ -536,6 +536,9 @@ class ContentTextView(context: Context, attrs: AttributeSet?) : View(context, at
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun upSelectChars() {
|
private fun upSelectChars() {
|
||||||
|
if (!selectStart.isSelected() || !selectEnd.isSelected()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
val last = if (callBack.isScroll) 2 else 0
|
val last = if (callBack.isScroll) 2 else 0
|
||||||
val textPos = TextPos(0, 0, 0)
|
val textPos = TextPos(0, 0, 0)
|
||||||
for (relativePos in 0..last) {
|
for (relativePos in 0..last) {
|
||||||
|
|||||||
@@ -320,7 +320,7 @@ class PageView(context: Context) : FrameLayout(context) {
|
|||||||
tvPageAndTotal?.setTextIfNotEqual("${index.plus(1)}/$pageSize $readProgress")
|
tvPageAndTotal?.setTextIfNotEqual("${index.plus(1)}/$pageSize $readProgress")
|
||||||
tvPage?.setTextIfNotEqual("${index.plus(1)}/$pageSize")
|
tvPage?.setTextIfNotEqual("${index.plus(1)}/$pageSize")
|
||||||
} else {
|
} else {
|
||||||
val pageSizeInt = pageSize - 1
|
val pageSizeInt = pageSize
|
||||||
val pageSize = if (pageSizeInt <= 0) "-" else "~$pageSizeInt"
|
val pageSize = if (pageSizeInt <= 0) "-" else "~$pageSizeInt"
|
||||||
tvPageAndTotal?.setTextIfNotEqual("${index.plus(1)}/$pageSize $readProgress")
|
tvPageAndTotal?.setTextIfNotEqual("${index.plus(1)}/$pageSize $readProgress")
|
||||||
tvPage?.setTextIfNotEqual("${index.plus(1)}/$pageSize")
|
tvPage?.setTextIfNotEqual("${index.plus(1)}/$pageSize")
|
||||||
|
|||||||
@@ -669,10 +669,6 @@ class ReadView(context: Context, attrs: AttributeSet) :
|
|||||||
upProgressThrottle.invoke()
|
upProgressThrottle.invoke()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetPageOffset() {
|
|
||||||
curPage.resetPageOffset()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val currentChapter: TextChapter?
|
override val currentChapter: TextChapter?
|
||||||
get() {
|
get() {
|
||||||
return if (callBack.isInitFinish) ReadBook.textChapter(0) else null
|
return if (callBack.isInitFinish) ReadBook.textChapter(0) else null
|
||||||
|
|||||||
@@ -96,8 +96,7 @@ data class TextChapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun isLastIndexCurrent(index: Int): Boolean {
|
fun isLastIndexCurrent(index: Int): Boolean {
|
||||||
// 未完成排版时,最后一页是正在排版中的,需要去掉
|
return index >= pages.size - 1
|
||||||
return index >= if (isCompleted) pages.size - 1 else pages.size - 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,6 +104,7 @@ data class TextChapter(
|
|||||||
* @return 已读长度
|
* @return 已读长度
|
||||||
*/
|
*/
|
||||||
fun getReadLength(pageIndex: Int): Int {
|
fun getReadLength(pageIndex: Int): Int {
|
||||||
|
if (pageIndex < 0) return 0
|
||||||
return pages[min(pageIndex, lastIndex)].lines.first().chapterPosition
|
return pages[min(pageIndex, lastIndex)].lines.first().chapterPosition
|
||||||
/*
|
/*
|
||||||
var length = 0
|
var length = 0
|
||||||
@@ -212,18 +212,15 @@ data class TextChapter(
|
|||||||
if (pageSize == 0) {
|
if (pageSize == 0) {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
val size = if (isCompleted) pageSize else pageSize - 1
|
val bIndex = pages.fastBinarySearchBy(charIndex, 0, pageSize) {
|
||||||
val bIndex = pages.fastBinarySearchBy(charIndex, 0, size) {
|
|
||||||
it.lines.first().chapterPosition
|
it.lines.first().chapterPosition
|
||||||
}
|
}
|
||||||
val index = abs(bIndex + 1) - 1
|
val index = abs(bIndex + 1) - 1
|
||||||
if (index == -1) {
|
|
||||||
return -1
|
|
||||||
}
|
|
||||||
// 判断是否已经排版到 charIndex ,没有则返回 -1
|
// 判断是否已经排版到 charIndex ,没有则返回 -1
|
||||||
if (!isCompleted && index == size - 1) {
|
if (!isCompleted && index == pageSize - 1) {
|
||||||
val line = pages[index].lines.first()
|
val page = pages[index]
|
||||||
val pageEndPos = line.chapterPosition + line.charSize
|
val line = page.lines.first()
|
||||||
|
val pageEndPos = line.chapterPosition + page.charSize
|
||||||
if (charIndex > pageEndPos) {
|
if (charIndex > pageEndPos) {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
@@ -287,14 +284,12 @@ 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
|
listener = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -69,4 +69,8 @@ data class TextPos(
|
|||||||
isLast = false
|
isLast = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isSelected(): Boolean {
|
||||||
|
return lineIndex >= 0 && columnIndex >= 0
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,8 @@ class TextChapterLayout(
|
|||||||
private val indentCharWidth = ChapterProvider.indentCharWidth
|
private val indentCharWidth = ChapterProvider.indentCharWidth
|
||||||
private val stringBuilder = StringBuilder()
|
private val stringBuilder = StringBuilder()
|
||||||
|
|
||||||
|
private var pendingTextPage = TextPage()
|
||||||
|
|
||||||
private var isCompleted = false
|
private var isCompleted = false
|
||||||
private val job: Coroutine<*>
|
private val job: Coroutine<*>
|
||||||
private val bookChapter inline get() = textChapter.chapter
|
private val bookChapter inline get() = textChapter.chapter
|
||||||
@@ -167,7 +169,6 @@ class TextChapterLayout(
|
|||||||
val contents = bookContent.textList
|
val contents = bookContent.textList
|
||||||
var absStartX = paddingLeft
|
var absStartX = paddingLeft
|
||||||
var durY = 0f
|
var durY = 0f
|
||||||
textPages.add(TextPage())
|
|
||||||
if (ReadBookConfig.titleMode != 2 || bookChapter.isVolume) {
|
if (ReadBookConfig.titleMode != 2 || bookChapter.isVolume) {
|
||||||
//标题非隐藏
|
//标题非隐藏
|
||||||
displayTitle.splitNotBlank("\n").forEach { text ->
|
displayTitle.splitNotBlank("\n").forEach { text ->
|
||||||
@@ -185,7 +186,7 @@ class TextChapterLayout(
|
|||||||
durY = it.second
|
durY = it.second
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
textPages.last().lines.last().isParagraphEnd = true
|
pendingTextPage.lines.last().isParagraphEnd = true
|
||||||
stringBuilder.append("\n")
|
stringBuilder.append("\n")
|
||||||
durY += titleBottomSpacing
|
durY += titleBottomSpacing
|
||||||
}
|
}
|
||||||
@@ -244,7 +245,6 @@ class TextChapterLayout(
|
|||||||
matcher.group(1)!!,
|
matcher.group(1)!!,
|
||||||
absStartX,
|
absStartX,
|
||||||
durY,
|
durY,
|
||||||
textPages,
|
|
||||||
contentPaintTextHeight,
|
contentPaintTextHeight,
|
||||||
stringBuilder,
|
stringBuilder,
|
||||||
book.getImageStyle()
|
book.getImageStyle()
|
||||||
@@ -270,10 +270,11 @@ class TextChapterLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
textPages.last().lines.last().isParagraphEnd = true
|
pendingTextPage.lines.last().isParagraphEnd = true
|
||||||
stringBuilder.append("\n")
|
stringBuilder.append("\n")
|
||||||
}
|
}
|
||||||
val textPage = textPages.last()
|
textPages.add(pendingTextPage)
|
||||||
|
val textPage = pendingTextPage
|
||||||
val endPadding = 20.dpToPx()
|
val endPadding = 20.dpToPx()
|
||||||
val durYPadding = durY + endPadding
|
val durYPadding = durY + endPadding
|
||||||
if (textPage.height < durYPadding) {
|
if (textPage.height < durYPadding) {
|
||||||
@@ -295,7 +296,6 @@ class TextChapterLayout(
|
|||||||
src: String,
|
src: String,
|
||||||
x: Int,
|
x: Int,
|
||||||
y: Float,
|
y: Float,
|
||||||
textPages: ArrayList<TextPage>,
|
|
||||||
textHeight: Float,
|
textHeight: Float,
|
||||||
stringBuilder: StringBuilder,
|
stringBuilder: StringBuilder,
|
||||||
imageStyle: String?,
|
imageStyle: String?,
|
||||||
@@ -305,15 +305,16 @@ class TextChapterLayout(
|
|||||||
val size = ImageProvider.getImageSize(book, src, ReadBook.bookSource)
|
val size = ImageProvider.getImageSize(book, src, ReadBook.bookSource)
|
||||||
if (size.width > 0 && size.height > 0) {
|
if (size.width > 0 && size.height > 0) {
|
||||||
if (durY > visibleHeight) {
|
if (durY > visibleHeight) {
|
||||||
val textPage = textPages.last()
|
val textPage = pendingTextPage
|
||||||
if (textPage.height < durY) {
|
if (textPage.height < durY) {
|
||||||
textPage.height = durY
|
textPage.height = durY
|
||||||
}
|
}
|
||||||
textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" }
|
textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" }
|
||||||
stringBuilder.clear()
|
stringBuilder.clear()
|
||||||
|
textPages.add(textPage)
|
||||||
coroutineContext.ensureActive()
|
coroutineContext.ensureActive()
|
||||||
onPageCompleted()
|
onPageCompleted()
|
||||||
textPages.add(TextPage())
|
pendingTextPage = TextPage()
|
||||||
durY = 0f
|
durY = 0f
|
||||||
}
|
}
|
||||||
var height = size.height
|
var height = size.height
|
||||||
@@ -334,7 +335,11 @@ class TextChapterLayout(
|
|||||||
height = visibleHeight
|
height = visibleHeight
|
||||||
}
|
}
|
||||||
if (durY + height > visibleHeight) {
|
if (durY + height > visibleHeight) {
|
||||||
val textPage = textPages.last()
|
val textPage = pendingTextPage
|
||||||
|
// 双页的 durY 不正确,可能会小于实际高度
|
||||||
|
if (textPage.height < durY) {
|
||||||
|
textPage.height = durY
|
||||||
|
}
|
||||||
if (doublePage && absStartX < viewWidth / 2) {
|
if (doublePage && absStartX < viewWidth / 2) {
|
||||||
//当前页面左列结束
|
//当前页面左列结束
|
||||||
textPage.leftLineSize = textPage.lineSize
|
textPage.leftLineSize = textPage.lineSize
|
||||||
@@ -346,13 +351,10 @@ class TextChapterLayout(
|
|||||||
}
|
}
|
||||||
textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" }
|
textPage.text = stringBuilder.toString().ifEmpty { "本页无文字内容" }
|
||||||
stringBuilder.clear()
|
stringBuilder.clear()
|
||||||
|
textPages.add(textPage)
|
||||||
coroutineContext.ensureActive()
|
coroutineContext.ensureActive()
|
||||||
onPageCompleted()
|
onPageCompleted()
|
||||||
textPages.add(TextPage())
|
pendingTextPage = TextPage()
|
||||||
}
|
|
||||||
// 双页的 durY 不正确,可能会小于实际高度
|
|
||||||
if (textPage.height < durY) {
|
|
||||||
textPage.height = durY
|
|
||||||
}
|
}
|
||||||
durY = 0f
|
durY = 0f
|
||||||
}
|
}
|
||||||
@@ -373,7 +375,7 @@ class TextChapterLayout(
|
|||||||
)
|
)
|
||||||
calcTextLinePosition(textPages, textLine, stringBuilder.length)
|
calcTextLinePosition(textPages, textLine, stringBuilder.length)
|
||||||
stringBuilder.append(" ") // 确保翻页时索引计算正确
|
stringBuilder.append(" ") // 确保翻页时索引计算正确
|
||||||
textPages.last().addLine(textLine)
|
pendingTextPage.addLine(textLine)
|
||||||
}
|
}
|
||||||
return absStartX to durY + textHeight * paragraphSpacing / 10f
|
return absStartX to durY + textHeight * paragraphSpacing / 10f
|
||||||
}
|
}
|
||||||
@@ -403,8 +405,8 @@ class TextChapterLayout(
|
|||||||
}
|
}
|
||||||
var durY = when {
|
var durY = when {
|
||||||
//标题y轴居中
|
//标题y轴居中
|
||||||
emptyContent && textPages.size == 1 -> {
|
emptyContent && textPages.isEmpty() -> {
|
||||||
val textPage = textPages.last()
|
val textPage = pendingTextPage
|
||||||
if (textPage.lineSize == 0) {
|
if (textPage.lineSize == 0) {
|
||||||
val ty = (visibleHeight - layout.lineCount * textHeight) / 2
|
val ty = (visibleHeight - layout.lineCount * textHeight) / 2
|
||||||
if (ty > titleTopSpacing) ty else titleTopSpacing.toFloat()
|
if (ty > titleTopSpacing) ty else titleTopSpacing.toFloat()
|
||||||
@@ -423,7 +425,7 @@ class TextChapterLayout(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isTitle && textPages.size == 1 && textPages.last().lines.isEmpty() ->
|
isTitle && textPages.isEmpty() && pendingTextPage.lines.isEmpty() ->
|
||||||
y + titleTopSpacing
|
y + titleTopSpacing
|
||||||
|
|
||||||
else -> y
|
else -> y
|
||||||
@@ -431,7 +433,10 @@ class TextChapterLayout(
|
|||||||
for (lineIndex in 0 until layout.lineCount) {
|
for (lineIndex in 0 until layout.lineCount) {
|
||||||
val textLine = TextLine(isTitle = isTitle)
|
val textLine = TextLine(isTitle = isTitle)
|
||||||
if (durY + textHeight > visibleHeight) {
|
if (durY + textHeight > visibleHeight) {
|
||||||
val textPage = textPages.last()
|
val textPage = pendingTextPage
|
||||||
|
if (textPage.height < durY) {
|
||||||
|
textPage.height = durY
|
||||||
|
}
|
||||||
if (doublePage && absStartX < viewWidth / 2) {
|
if (doublePage && absStartX < viewWidth / 2) {
|
||||||
//当前页面左列结束
|
//当前页面左列结束
|
||||||
textPage.leftLineSize = textPage.lineSize
|
textPage.leftLineSize = textPage.lineSize
|
||||||
@@ -442,16 +447,14 @@ class TextChapterLayout(
|
|||||||
textPage.leftLineSize = textPage.lineSize
|
textPage.leftLineSize = textPage.lineSize
|
||||||
}
|
}
|
||||||
textPage.text = stringBuilder.toString()
|
textPage.text = stringBuilder.toString()
|
||||||
|
textPages.add(textPage)
|
||||||
coroutineContext.ensureActive()
|
coroutineContext.ensureActive()
|
||||||
onPageCompleted()
|
onPageCompleted()
|
||||||
//新建页面
|
//新建页面
|
||||||
textPages.add(TextPage())
|
pendingTextPage = TextPage()
|
||||||
stringBuilder.clear()
|
stringBuilder.clear()
|
||||||
absStartX = paddingLeft
|
absStartX = paddingLeft
|
||||||
}
|
}
|
||||||
if (textPage.height < durY) {
|
|
||||||
textPage.height = durY
|
|
||||||
}
|
|
||||||
durY = 0f
|
durY = 0f
|
||||||
}
|
}
|
||||||
val lineStart = layout.getLineStart(lineIndex)
|
val lineStart = layout.getLineStart(lineIndex)
|
||||||
@@ -514,7 +517,7 @@ class TextChapterLayout(
|
|||||||
calcTextLinePosition(textPages, textLine, stringBuilder.length)
|
calcTextLinePosition(textPages, textLine, stringBuilder.length)
|
||||||
stringBuilder.append(lineText)
|
stringBuilder.append(lineText)
|
||||||
textLine.upTopBottom(durY, textHeight, fontMetrics)
|
textLine.upTopBottom(durY, textHeight, fontMetrics)
|
||||||
val textPage = textPages.last()
|
val textPage = pendingTextPage
|
||||||
textPage.addLine(textLine)
|
textPage.addLine(textLine)
|
||||||
durY += textHeight * lineSpacingExtra
|
durY += textHeight * lineSpacingExtra
|
||||||
if (textPage.height < durY) {
|
if (textPage.height < durY) {
|
||||||
@@ -530,8 +533,8 @@ class TextChapterLayout(
|
|||||||
textLine: TextLine,
|
textLine: TextLine,
|
||||||
sbLength: Int
|
sbLength: Int
|
||||||
) {
|
) {
|
||||||
val lastLine = textPages.last().lines.lastOrNull { it.paragraphNum > 0 }
|
val lastLine = pendingTextPage.lines.lastOrNull { it.paragraphNum > 0 }
|
||||||
?: textPages.getOrNull(textPages.lastIndex - 1)?.lines?.lastOrNull { it.paragraphNum > 0 }
|
?: textPages.lastOrNull()?.lines?.lastOrNull { it.paragraphNum > 0 }
|
||||||
val paragraphNum = when {
|
val paragraphNum = when {
|
||||||
lastLine == null -> 1
|
lastLine == null -> 1
|
||||||
lastLine.isParagraphEnd -> lastLine.paragraphNum + 1
|
lastLine.isParagraphEnd -> lastLine.paragraphNum + 1
|
||||||
@@ -539,7 +542,7 @@ class TextChapterLayout(
|
|||||||
}
|
}
|
||||||
textLine.paragraphNum = paragraphNum
|
textLine.paragraphNum = paragraphNum
|
||||||
textLine.chapterPosition =
|
textLine.chapterPosition =
|
||||||
(textPages.getOrNull(textPages.lastIndex - 1)?.lines?.lastOrNull()?.run {
|
(textPages.lastOrNull()?.lines?.lastOrNull()?.run {
|
||||||
chapterPosition + charSize + if (isParagraphEnd) 1 else 0
|
chapterPosition + charSize + if (isParagraphEnd) 1 else 0
|
||||||
} ?: 0) + sbLength
|
} ?: 0) + sbLength
|
||||||
textLine.pagePosition = sbLength
|
textLine.pagePosition = sbLength
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import android.graphics.Canvas
|
|||||||
import android.graphics.Paint
|
import android.graphics.Paint
|
||||||
import android.graphics.Rect
|
import android.graphics.Rect
|
||||||
import android.graphics.Typeface
|
import android.graphics.Typeface
|
||||||
|
import android.text.SpannableStringBuilder
|
||||||
|
import android.text.Spanned
|
||||||
import android.text.StaticLayout
|
import android.text.StaticLayout
|
||||||
|
import android.text.style.LineHeightSpan
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import androidx.annotation.ColorInt
|
import androidx.annotation.ColorInt
|
||||||
import androidx.appcompat.widget.AppCompatTextView
|
import androidx.appcompat.widget.AppCompatTextView
|
||||||
@@ -25,6 +28,13 @@ class BatteryView @JvmOverloads constructor(
|
|||||||
private val outFrame = Rect()
|
private val outFrame = Rect()
|
||||||
private val polar = Rect()
|
private val polar = Rect()
|
||||||
private val canvasRecorder = CanvasRecorderFactory.create()
|
private val canvasRecorder = CanvasRecorderFactory.create()
|
||||||
|
private val batterySpan = LineHeightSpan { _, _, _, _, _, fm ->
|
||||||
|
fm.top = -22
|
||||||
|
fm.ascent = -28
|
||||||
|
fm.descent = 7
|
||||||
|
fm.bottom = 1
|
||||||
|
fm.leading = 0
|
||||||
|
}
|
||||||
var isBattery = false
|
var isBattery = false
|
||||||
set(value) {
|
set(value) {
|
||||||
field = value
|
field = value
|
||||||
@@ -58,42 +68,62 @@ class BatteryView @JvmOverloads constructor(
|
|||||||
fun setBattery(battery: Int, text: String? = null) {
|
fun setBattery(battery: Int, text: String? = null) {
|
||||||
this.battery = battery
|
this.battery = battery
|
||||||
if (text.isNullOrEmpty()) {
|
if (text.isNullOrEmpty()) {
|
||||||
setText(battery.toString())
|
setText(getBatteryText(battery.toString()))
|
||||||
} else {
|
} else {
|
||||||
setText("$text $battery")
|
setText(getBatteryText("$text $battery"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||||
|
super.onLayout(changed, left, top, right, bottom)
|
||||||
|
canvasRecorder.invalidate()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDraw(canvas: Canvas) {
|
override fun onDraw(canvas: Canvas) {
|
||||||
canvasRecorder.recordIfNeededThenDraw(canvas, width, height) {
|
canvasRecorder.recordIfNeededThenDraw(canvas, width, height) {
|
||||||
super.onDraw(this)
|
super.onDraw(this)
|
||||||
if (!isBattery) return@recordIfNeededThenDraw
|
if (!isBattery) return@recordIfNeededThenDraw
|
||||||
layout.getLineBounds(0, outFrame)
|
drawBattery(this)
|
||||||
val batteryStart = layout
|
|
||||||
.getPrimaryHorizontal(text.length - battery.toString().length)
|
|
||||||
.toInt() + 2.dpToPx()
|
|
||||||
val batteryEnd = batteryStart +
|
|
||||||
StaticLayout.getDesiredWidth(battery.toString(), paint).toInt() + 4.dpToPx()
|
|
||||||
outFrame.set(
|
|
||||||
batteryStart,
|
|
||||||
2.dpToPx(),
|
|
||||||
batteryEnd,
|
|
||||||
height - 2.dpToPx()
|
|
||||||
)
|
|
||||||
val dj = (outFrame.bottom - outFrame.top) / 3
|
|
||||||
polar.set(
|
|
||||||
batteryEnd,
|
|
||||||
outFrame.top + dj,
|
|
||||||
batteryEnd + 2.dpToPx(),
|
|
||||||
outFrame.bottom - dj
|
|
||||||
)
|
|
||||||
batteryPaint.style = Paint.Style.STROKE
|
|
||||||
drawRect(outFrame, batteryPaint)
|
|
||||||
batteryPaint.style = Paint.Style.FILL
|
|
||||||
drawRect(polar, batteryPaint)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun drawBattery(canvas: Canvas) {
|
||||||
|
layout.getLineBounds(0, outFrame)
|
||||||
|
val batteryStart = layout
|
||||||
|
.getPrimaryHorizontal(text.length - battery.toString().length)
|
||||||
|
.toInt() + 2.dpToPx()
|
||||||
|
val batteryEnd = batteryStart +
|
||||||
|
StaticLayout.getDesiredWidth(battery.toString(), paint).toInt() + 4.dpToPx()
|
||||||
|
outFrame.set(
|
||||||
|
batteryStart,
|
||||||
|
2.dpToPx(),
|
||||||
|
batteryEnd,
|
||||||
|
height - 2.dpToPx()
|
||||||
|
)
|
||||||
|
val dj = (outFrame.bottom - outFrame.top) / 3
|
||||||
|
polar.set(
|
||||||
|
batteryEnd,
|
||||||
|
outFrame.top + dj,
|
||||||
|
batteryEnd + 2.dpToPx(),
|
||||||
|
outFrame.bottom - dj
|
||||||
|
)
|
||||||
|
batteryPaint.style = Paint.Style.STROKE
|
||||||
|
canvas.drawRect(outFrame, batteryPaint)
|
||||||
|
batteryPaint.style = Paint.Style.FILL
|
||||||
|
canvas.drawRect(polar, batteryPaint)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBatteryText(text: CharSequence?): SpannableStringBuilder? {
|
||||||
|
if (text == null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return SpannableStringBuilder(text).apply {
|
||||||
|
setSpan(batterySpan, 0, text.length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun invalidate() {
|
override fun invalidate() {
|
||||||
super.invalidate()
|
super.invalidate()
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
|
|||||||
Reference in New Issue
Block a user