Просмотр исходного кода

Merge branch 'gedoor:master' into master

huolibao 2 лет назад
Родитель
Сommit
431feea323

+ 2 - 0
app/src/main/java/io/legado/app/constant/AppPattern.kt

@@ -52,4 +52,6 @@ object AppPattern {
     val spaceRegex = "\\s+".toRegex()
     val spaceRegex = "\\s+".toRegex()
 
 
     val regexCharRegex = "[{}()\\[\\].+*?^$\\\\|]".toRegex()
     val regexCharRegex = "[{}()\\[\\].+*?^$\\\\|]".toRegex()
+
+    val LFRegex = "\n".toRegex()
 }
 }

+ 1 - 0
app/src/main/java/io/legado/app/constant/PageAnim.kt

@@ -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

+ 6 - 0
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.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,

+ 104 - 44
app/src/main/java/io/legado/app/model/ReadBook.kt

@@ -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
-                        ) {
-                            success?.invoke()
-                        }
+                            resetPageOffset,
+                            success
+                        )
                     } ?: 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?.invoke()
-            }
+                success = success
+            )
         }
         }
     }
     }
 
 
@@ -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()
-                    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 -> {
                 -1 -> {
                     prevTextChapter?.cancelLayout()
                     prevTextChapter?.cancelLayout()
                     prevTextChapter = textChapter
                     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 -> {
                 1 -> {
                     nextTextChapter?.cancelLayout()
                     nextTextChapter?.cancelLayout()
                     nextTextChapter = textChapter
                     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)
             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()
     }
     }
 
 
 }
 }

+ 4 - 0
app/src/main/java/io/legado/app/model/webBook/BookContent.kt

@@ -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}")

+ 0 - 43
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.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

+ 2 - 11
app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt

@@ -109,21 +109,12 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) {
             } else {
             } else {
                 loadChapterList(book)
                 loadChapterList(book)
             }
             }
-        } else if (book.isLocal
-            && LocalBook.getLastModified(book).getOrDefault(0L) > book.latestChapterTime
-        ) {
+        } else if (book.isLocalModified()) {
             loadChapterList(book)
             loadChapterList(book)
         } else if (isSameBook) {
         } else if (isSameBook) {
-            if (ReadBook.curTextChapter != null) {
-                ReadBook.callBack?.upContent(resetPageOffset = false)
-            } else {
-                ReadBook.loadContent(resetPageOffset = true)
-            }
+            ReadBook.loadOrUpContent()
             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)
         }
         }

+ 6 - 6
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)
                 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 {

+ 3 - 0
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() {
     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) {

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

+ 0 - 4
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()
         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

+ 7 - 12
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 {
     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 已读长度
      * @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, size) {
+        val bIndex = pages.fastBinarySearchBy(charIndex, 0, pageSize) {
             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) {
-            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) {
             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
     }
     }
 
 

+ 4 - 0
app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPos.kt

@@ -69,4 +69,8 @@ data class TextPos(
         isLast = false
         isLast = false
     }
     }
 
 
+    fun isSelected(): Boolean {
+        return lineIndex >= 0 && columnIndex >= 0
+    }
+
 }
 }

+ 30 - 27
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 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())
-                        }
-                        // 双页的 durY 不正确,可能会小于实际高度
-                        if (textPage.height < durY) {
-                            textPage.height = durY
+                            pendingTextPage = TextPage()
                         }
                         }
                         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 -> {
-                val textPage = textPages.last()
+            emptyContent && textPages.isEmpty() -> {
+                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 }
-            ?: 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 {
         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

+ 55 - 25
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.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)
-            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() {
     override fun invalidate() {
         super.invalidate()
         super.invalidate()
         kotlin.runCatching {
         kotlin.runCatching {