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