Horis 1 год назад
Родитель
Сommit
64f82edc2e

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

@@ -136,6 +136,8 @@ object ReadBook : CoroutineScope by MainScope() {
         TextFile.clear()
         TextFile.clear()
         synchronized(this) {
         synchronized(this) {
             loadingChapters.clear()
             loadingChapters.clear()
+            downloadedChapters.clear()
+            downloadFailChapters.clear()
         }
         }
     }
     }
 
 
@@ -954,8 +956,6 @@ object ReadBook : CoroutineScope by MainScope() {
         preDownloadTask?.cancel()
         preDownloadTask?.cancel()
         downloadScope.coroutineContext.cancelChildren()
         downloadScope.coroutineContext.cancelChildren()
         coroutineContext.cancelChildren()
         coroutineContext.cancelChildren()
-        downloadedChapters.clear()
-        downloadFailChapters.clear()
         ImageProvider.clear()
         ImageProvider.clear()
         if (!CacheBookService.isRun) {
         if (!CacheBookService.isRun) {
             CacheBook.close()
             CacheBook.close()

+ 168 - 112
app/src/main/java/io/legado/app/model/ReadManga.kt

@@ -20,6 +20,7 @@ import io.legado.app.help.config.AppConfig
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.coroutine.Coroutine
 import io.legado.app.help.globalExecutor
 import io.legado.app.help.globalExecutor
 import io.legado.app.model.webBook.WebBook
 import io.legado.app.model.webBook.WebBook
+import io.legado.app.ui.book.manga.entities.MangaChapter
 import io.legado.app.ui.book.manga.entities.MangaContent
 import io.legado.app.ui.book.manga.entities.MangaContent
 import io.legado.app.ui.book.manga.entities.ReaderLoading
 import io.legado.app.ui.book.manga.entities.ReaderLoading
 import io.legado.app.utils.NetworkUtils
 import io.legado.app.utils.NetworkUtils
@@ -50,19 +51,23 @@ object ReadManga : CoroutineScope by MainScope() {
     var chapterSize = 0//总章节
     var chapterSize = 0//总章节
     var durChapterImageCount = 0
     var durChapterImageCount = 0
     var durChapterPos = 0
     var durChapterPos = 0
+    var prevMangaChapter: MangaChapter? = null
+    var curMangaChapter: MangaChapter? = null
+    var nextMangaChapter: MangaChapter? = null
     var bookSource: BookSource? = null
     var bookSource: BookSource? = null
-    var chapterTitle: String = ""
     var readStartTime: Long = System.currentTimeMillis()
     var readStartTime: Long = System.currentTimeMillis()
     private val readRecord = ReadRecord()
     private val readRecord = ReadRecord()
     private val loadingChapters = arrayListOf<Int>()
     private val loadingChapters = arrayListOf<Int>()
     var simulatedChapterSize = 0
     var simulatedChapterSize = 0
     var mCallback: Callback? = null
     var mCallback: Callback? = null
-    var gameOver = false
     var preDownloadTask: Job? = null
     var preDownloadTask: Job? = null
     val downloadedChapters = hashSetOf<Int>()
     val downloadedChapters = hashSetOf<Int>()
     val downloadFailChapters = hashMapOf<Int, Int>()
     val downloadFailChapters = hashMapOf<Int, Int>()
     val downloadScope = CoroutineScope(SupervisorJob() + IO)
     val downloadScope = CoroutineScope(SupervisorJob() + IO)
     var rateLimiter = ConcurrentRateLimiter(null)
     var rateLimiter = ConcurrentRateLimiter(null)
+    val durChapterAbsPos get() = (prevMangaChapter?.contents?.size ?: 0) + durChapterPos + 1
+    val mangaContents get() = buildContentList()
+    val hasNextChapter get() = durChapterIndex < simulatedChapterSize - 1
 
 
     fun saveRead(pageChanged: Boolean = false) {
     fun saveRead(pageChanged: Boolean = false) {
         executor.execute {
         executor.execute {
@@ -115,9 +120,12 @@ object ReadManga : CoroutineScope by MainScope() {
         }
         }
         durChapterIndex = book.durChapterIndex
         durChapterIndex = book.durChapterIndex
         durChapterPos = book.durChapterPos
         durChapterPos = book.durChapterPos
+        clearMangaChapter()
         upWebBook(book)
         upWebBook(book)
         synchronized(this) {
         synchronized(this) {
             loadingChapters.clear()
             loadingChapters.clear()
+            downloadedChapters.clear()
+            downloadFailChapters.clear()
         }
         }
     }
     }
 
 
@@ -130,6 +138,11 @@ object ReadManga : CoroutineScope by MainScope() {
         }
         }
     }
     }
 
 
+    fun clearMangaChapter() {
+        prevMangaChapter = null
+        curMangaChapter = null
+        nextMangaChapter = null
+    }
 
 
     //每次切换章节更新阅读记录
     //每次切换章节更新阅读记录
     fun upReadTime() {
     fun upReadTime() {
@@ -158,12 +171,6 @@ object ReadManga : CoroutineScope by MainScope() {
         }
         }
     }
     }
 
 
-    fun loading(index: Int): Boolean {
-        synchronized(this) {
-            return loadingChapters.contains(index)
-        }
-    }
-
     /**
     /**
      * 获取正文
      * 获取正文
      */
      */
@@ -188,11 +195,25 @@ object ReadManga : CoroutineScope by MainScope() {
 
 
     fun loadContent() {
     fun loadContent() {
         loadContent(durChapterIndex)
         loadContent(durChapterIndex)
+        loadContent(durChapterIndex - 1)
+        loadContent(durChapterIndex + 1)
     }
     }
 
 
-    fun loadContent(
-        index: Int,
-    ) {
+    fun loadOrUpContent() {
+        if (curMangaChapter == null) {
+            loadContent(durChapterIndex)
+        } else {
+            mCallback?.upContent(true)
+        }
+        if (nextMangaChapter == null) {
+            loadContent(durChapterIndex + 1)
+        }
+        if (prevMangaChapter == null) {
+            loadContent(durChapterIndex - 1)
+        }
+    }
+
+    fun loadContent(index: Int) {
         if (chapterChanged) {
         if (chapterChanged) {
             removeLoading(index)
             removeLoading(index)
         }
         }
@@ -225,25 +246,20 @@ object ReadManga : CoroutineScope by MainScope() {
     /**
     /**
      * 取消注册回调
      * 取消注册回调
      */
      */
-    fun unregister() {
+    fun unregister(cb: Callback) {
+        if (mCallback === cb) {
+            mCallback = null
+        }
         inBookshelf = false
         inBookshelf = false
         tocChanged = false
         tocChanged = false
         chapterChanged = false
         chapterChanged = false
-        book = null
         durChapterIndex = 0
         durChapterIndex = 0
         chapterSize = 0
         chapterSize = 0
         durChapterPos = 0
         durChapterPos = 0
         durChapterImageCount = 0
         durChapterImageCount = 0
-        bookSource = null
-        chapterTitle = ""
-        loadingChapters.clear()
         simulatedChapterSize = 0
         simulatedChapterSize = 0
-        mCallback = null
-        gameOver = false
         preDownloadTask?.cancel()
         preDownloadTask?.cancel()
         preDownloadTask = null
         preDownloadTask = null
-        downloadedChapters.clear()
-        downloadFailChapters.clear()
         downloadScope.coroutineContext.cancelChildren()
         downloadScope.coroutineContext.cancelChildren()
         coroutineContext.cancelChildren()
         coroutineContext.cancelChildren()
     }
     }
@@ -256,120 +272,117 @@ object ReadManga : CoroutineScope by MainScope() {
         content: String?,
         content: String?,
         book: Book,
         book: Book,
     ) {
     ) {
-        chapterTitle = chapter.title
         removeLoading(chapter.index)
         removeLoading(chapter.index)
         if (chapter.index !in durChapterIndex - 1..durChapterIndex + 1) {
         if (chapter.index !in durChapterIndex - 1..durChapterIndex + 1) {
             return
             return
         }
         }
-        if (content == null) {
-            mCallback?.loadFail("加载内容失败")
-            return
-        }
-        if (content.isNotEmpty()) {
-            val list = flow {
-                val matcher = AppPattern.imgPattern.matcher(content)
-                while (matcher.find()) {
-                    val src = matcher.group(1) ?: continue
-                    val mSrc = NetworkUtils.getAbsoluteURL(chapter.url, src)
-                    emit(mSrc)
+        when (chapter.index - durChapterIndex) {
+            0 -> {
+                if (content == null) {
+                    mCallback?.loadFail("加载内容失败")
+                    return
+                }
+                if (content.isEmpty()) {
+                    mCallback?.loadFail("正文内容为空")
+                    return
                 }
                 }
-            }.distinctUntilChanged().mapIndexed { index, src ->
-                MangaContent(
-                    chapterSize = chapterSize,
-                    mChapterIndex = durChapterIndex,
-                    nextChapterIndex = durChapterIndex.plus(1),
-                    mImageUrl = src,
-                    mDurChapterPos = index,
-                    mChapterName = chapterTitle
-                )
-            }.toList().apply {
-                this.forEach {
-                    it.mDurChapterCount = this.size
+                val mangaChapter = getManageChapter(chapter, content)
+                if (mangaChapter.imageCount == 0) {
+                    mCallback?.loadFail("正文没有图片")
+                    return
                 }
                 }
-                durChapterImageCount = this.size
+                durChapterImageCount = mangaChapter.imageCount
+                curMangaChapter = mangaChapter
+                mCallback?.upContent(true)
+                mCallback?.contentLoadFinish()
+            }
+
+            -1 -> {
+                if (content == null || content.isEmpty()) {
+                    return
+                }
+                val mangaChapter = getManageChapter(chapter, content)
+                if (mangaChapter.imageCount == 0) {
+                    return
+                }
+                prevMangaChapter = mangaChapter
+                mCallback?.upContent()
+            }
+
+            1 -> {
+                if (content == null || content.isEmpty()) {
+                    return
+                }
+                val mangaChapter = getManageChapter(chapter, content)
+                if (mangaChapter.imageCount == 0) {
+                    return
+                }
+                nextMangaChapter = mangaChapter
+                mCallback?.upContent()
             }
             }
-            val contentList = mutableListOf<Any>()
-            contentList.add(
-                ReaderLoading(
-                    durChapterIndex,
-                    "阅读 ${chapter.title}",
-                    mNextChapterIndex = durChapterIndex.plus(1)
-                )
-            )
-            contentList.addAll(list)
-            contentList.add(
-                ReaderLoading(
-                    durChapterIndex,
-                    "已读完 ${chapter.title}",
-                    mNextChapterIndex = durChapterIndex.plus(1)
-                )
-            )
-            mCallback?.loadContentFinish(contentList)
         }
         }
-        mCallback?.loadComplete()
+    }
+
+    private fun buildContentList(): List<Any> {
+        val list = arrayListOf<Any>()
+        prevMangaChapter?.let {
+            list.addAll(it.contents)
+        }
+        curMangaChapter?.let {
+            list.addAll(it.contents)
+        }
+        nextMangaChapter?.let {
+            list.addAll(it.contents)
+        }
+        return list
     }
     }
 
 
     /**
     /**
      * 加载下一章
      * 加载下一章
      */
      */
-    fun moveToNextChapter(index: Int) {
-
-        if (loading(index)) {
-            return
-        }
-
-        if (index > chapterSize - 1) {
-            upToc(index)
-            return
-        }
+    fun moveToNextChapter(): Boolean {
         if (durChapterIndex < simulatedChapterSize - 1) {
         if (durChapterIndex < simulatedChapterSize - 1) {
-            durChapterPos = 0
-            durChapterIndex = index
+            durChapterIndex++
+            prevMangaChapter = curMangaChapter
+            curMangaChapter = nextMangaChapter
+            nextMangaChapter = null
+            if (curMangaChapter == null) {
+                loadContent(durChapterIndex)
+            } else {
+                mCallback?.upContent(true)
+            }
+            loadContent(durChapterIndex + 1)
             saveRead()
             saveRead()
-            loadContent(durChapterIndex)
             AppLog.putDebug("moveToNextChapter-curPageChanged()")
             AppLog.putDebug("moveToNextChapter-curPageChanged()")
             curPageChanged()
             curPageChanged()
+            return true
         } else {
         } else {
             AppLog.putDebug("跳转下一章失败,没有下一章")
             AppLog.putDebug("跳转下一章失败,没有下一章")
+            return false
         }
         }
     }
     }
 
 
-    fun curPageChanged() {
-        upReadTime()
-        preDownload()
-    }
-
-    @Synchronized
-    fun upToc(index: Int) {
-        val bookSource = bookSource ?: return
-        val book = book ?: return
-        if (!book.canUpdate) return
-        if (System.currentTimeMillis() - book.lastCheckTime < 600000) {
-            mCallback?.noData()
-            return
-        }
-        book.lastCheckTime = System.currentTimeMillis()
-        WebBook.getChapterList(this, bookSource, book).onSuccess(IO) { cList ->
-            if (book.bookUrl == ReadManga.book?.bookUrl
-                && cList.size > chapterSize
-            ) {
-                appDb.bookChapterDao.delByBook(book.bookUrl)
-                appDb.bookChapterDao.insert(*cList.toTypedArray())
-                saveRead()
-                durChapterPos = 0
-                durChapterIndex = index
-                chapterSize = cList.size
-                simulatedChapterSize = book.simulatedTotalChapterNum()
+    fun moveToPrevChapter(): Boolean {
+        if (durChapterIndex > 0) {
+            durChapterIndex--
+            nextMangaChapter = curMangaChapter
+            curMangaChapter = prevMangaChapter
+            prevMangaChapter = null
+            if (curMangaChapter == null) {
                 loadContent(durChapterIndex)
                 loadContent(durChapterIndex)
             } else {
             } else {
-                durChapterIndex = durChapterIndex.minus(1)
-                saveRead()
-                gameOver = true
-                mCallback?.noData()
+                mCallback?.upContent(true)
             }
             }
-        }.onError {
-            mCallback?.noData()
+            loadContent(durChapterIndex - 1)
+            saveRead()
+            return true
         }
         }
+        return false
+    }
+
+    fun curPageChanged() {
+        upReadTime()
+        preDownload()
     }
     }
 
 
     private fun downloadNetworkContent(
     private fun downloadNetworkContent(
@@ -480,7 +493,6 @@ object ReadManga : CoroutineScope by MainScope() {
         }
         }
     }
     }
 
 
-
     /**
     /**
      * 同步阅读进度
      * 同步阅读进度
      * 如果当前进度快于服务器进度或者没有进度进行上传,如果慢与服务器进度则执行传入动作
      * 如果当前进度快于服务器进度或者没有进度进行上传,如果慢与服务器进度则执行传入动作
@@ -536,11 +548,55 @@ object ReadManga : CoroutineScope by MainScope() {
         }
         }
     }
     }
 
 
+    private suspend fun getManageChapter(chapter: BookChapter, content: String): MangaChapter {
+        val list = flow {
+            val matcher = AppPattern.imgPattern.matcher(content)
+            while (matcher.find()) {
+                val src = matcher.group(1) ?: continue
+                val mSrc = NetworkUtils.getAbsoluteURL(chapter.url, src)
+                emit(mSrc)
+            }
+        }.distinctUntilChanged().mapIndexed { index, src ->
+            MangaContent(
+                chapterSize = chapterSize,
+                mChapterIndex = chapter.index,
+                nextChapterIndex = chapter.index.plus(1),
+                mImageUrl = src,
+                mDurChapterPos = index,
+                mChapterName = chapter.title
+            )
+        }.toList()
+
+        val imageCount = list.size
+
+        list.forEach {
+            it.mDurChapterImageCount = imageCount
+        }
+
+        val contentList = mutableListOf<Any>()
+        contentList.add(
+            ReaderLoading(
+                chapter.index,
+                "阅读 ${chapter.title}",
+                mNextChapterIndex = chapter.index.plus(1)
+            )
+        )
+        contentList.addAll(list)
+        contentList.add(
+            ReaderLoading(
+                chapter.index,
+                "已读完 ${chapter.title}",
+                mNextChapterIndex = chapter.index.plus(1)
+            )
+        )
+
+        return MangaChapter(chapter, contentList, imageCount)
+    }
+
     interface Callback {
     interface Callback {
-        fun loadContentFinish(list: MutableList<Any>)
-        fun loadComplete()
+        fun upContent(finish: Boolean = false)
+        fun contentLoadFinish()
         fun loadFail(msg: String)
         fun loadFail(msg: String)
-        fun noData()
         fun adjustProgress()
         fun adjustProgress()
         fun sureNewProgress(progress: BookProgress)
         fun sureNewProgress(progress: BookProgress)
     }
     }

+ 74 - 67
app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt

@@ -39,13 +39,12 @@ import io.legado.app.help.config.AppConfig
 import io.legado.app.help.storage.Backup
 import io.legado.app.help.storage.Backup
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.lib.dialogs.alert
 import io.legado.app.model.ReadManga
 import io.legado.app.model.ReadManga
-import io.legado.app.ui.book.manga.entities.MangaContent
-import io.legado.app.ui.book.manga.entities.ReaderLoading
 import io.legado.app.receiver.NetworkChangedListener
 import io.legado.app.receiver.NetworkChangedListener
 import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
 import io.legado.app.ui.book.changesource.ChangeBookSourceDialog
 import io.legado.app.ui.book.info.BookInfoActivity
 import io.legado.app.ui.book.info.BookInfoActivity
 import io.legado.app.ui.book.manga.config.MangaFooterConfig
 import io.legado.app.ui.book.manga.config.MangaFooterConfig
 import io.legado.app.ui.book.manga.config.MangaFooterSettingDialog
 import io.legado.app.ui.book.manga.config.MangaFooterSettingDialog
+import io.legado.app.ui.book.manga.entities.MangaContent
 import io.legado.app.ui.book.manga.recyclerview.MangaAdapter
 import io.legado.app.ui.book.manga.recyclerview.MangaAdapter
 import io.legado.app.ui.book.read.MangaMenu
 import io.legado.app.ui.book.read.MangaMenu
 import io.legado.app.ui.book.read.ReadBookActivity.Companion.RESULT_DELETED
 import io.legado.app.ui.book.read.ReadBookActivity.Companion.RESULT_DELETED
@@ -64,9 +63,12 @@ import io.legado.app.utils.toastOnUi
 import io.legado.app.utils.toggleStatusBar
 import io.legado.app.utils.toggleStatusBar
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import io.legado.app.utils.viewbindingdelegate.viewBinding
 import io.legado.app.utils.visible
 import io.legado.app.utils.visible
+import kotlinx.coroutines.Dispatchers.IO
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+import java.text.DecimalFormat
 
 
-class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, MangaViewModel>(),
+class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewModel>(),
     ReadManga.Callback, ChangeBookSourceDialog.CallBack, MangaMenu.CallBack {
     ReadManga.Callback, ChangeBookSourceDialog.CallBack, MangaMenu.CallBack {
 
 
     private val mLayoutManager by lazy {
     private val mLayoutManager by lazy {
@@ -140,7 +142,11 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, MangaViewModel>()
             }
             }
         }
         }
     override val binding by viewBinding(ActivityMangaBinding::inflate)
     override val binding by viewBinding(ActivityMangaBinding::inflate)
-    override val viewModel by viewModels<MangaViewModel>()
+    override val viewModel by viewModels<ReadMangaViewModel>()
+    private val loadingViewVisible get() = binding.flLoading.isVisible
+    private val df by lazy {
+        DecimalFormat("0.0%")
+    }
 
 
     override fun onCreate(savedInstanceState: Bundle?) {
     override fun onCreate(savedInstanceState: Bundle?) {
         upLayoutInDisplayCutoutMode()
         upLayoutInDisplayCutoutMode()
@@ -154,15 +160,15 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, MangaViewModel>()
         binding.tvRetry.setOnClickListener {
         binding.tvRetry.setOnClickListener {
             binding.llLoading.isVisible = true
             binding.llLoading.isVisible = true
             binding.llRetry.isGone = true
             binding.llRetry.isGone = true
-            ReadManga.loadContent()
+            ReadManga.loadOrUpContent()
         }
         }
-
         mAdapter.addFooterView {
         mAdapter.addFooterView {
             ViewLoadMoreBinding.bind(loadMoreView)
             ViewLoadMoreBinding.bind(loadMoreView)
         }
         }
         loadMoreView.setOnClickListener {
         loadMoreView.setOnClickListener {
-            if (!loadMoreView.isLoading && !ReadManga.gameOver) {
-                scrollToBottom(true, ReadManga.durChapterIndex)
+            if (!loadMoreView.isLoading && ReadManga.hasNextChapter) {
+                loadMoreView.startLoad()
+                ReadManga.loadOrUpContent()
             }
             }
         }
         }
         loadMoreView.gone()
         loadMoreView.gone()
@@ -175,11 +181,11 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, MangaViewModel>()
         observeEvent<MangaFooterConfig>(EventBus.UP_MANGA_CONFIG) {
         observeEvent<MangaFooterConfig>(EventBus.UP_MANGA_CONFIG) {
             mMangaFooterConfig = it
             mMangaFooterConfig = it
             upInfoBar(
             upInfoBar(
-                ReadManga.durChapterIndex.plus(1),
+                ReadManga.durChapterIndex,
                 ReadManga.chapterSize,
                 ReadManga.chapterSize,
-                ReadManga.durChapterPos.plus(1),
+                ReadManga.durChapterPos,
                 ReadManga.durChapterImageCount,
                 ReadManga.durChapterImageCount,
-                ReadManga.chapterTitle
+                ReadManga.curMangaChapter?.chapter?.title ?: ""
             )
             )
         }
         }
     }
     }
@@ -197,28 +203,28 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, MangaViewModel>()
             singlePagerScroller(AppConfig.enableMangaHorizontalScroll)
             singlePagerScroller(AppConfig.enableMangaHorizontalScroll)
             disabledClickScroller(AppConfig.disableClickScroll)
             disabledClickScroller(AppConfig.disableClickScroll)
             disableMangaScaling(AppConfig.disableMangaScale)
             disableMangaScaling(AppConfig.disableMangaScale)
-            setPreScrollListener { _, dx, dy, position ->
-                if ((dy > 0 || dx > 0) && position + 2 > mAdapter.getCurrentList().size - 3) {
-                    if (mAdapter.getCurrentList().last() is ReaderLoading) {
-                        val nextIndex =
-                            (mAdapter.getCurrentList().last() as ReaderLoading).mNextChapterIndex
-                        if (nextIndex != -1) {
-                            scrollToBottom(false, nextIndex)
-                        }
-                    }
-                }
-            }
-            setNestedPreScrollListener { _, _, _, position ->
+            setPreScrollListener { _, _, _, position ->
                 if (mAdapter.isNotEmpty()) {
                 if (mAdapter.isNotEmpty()) {
                     val content = mAdapter.getItem(position)
                     val content = mAdapter.getItem(position)
                     if (content is MangaContent) {
                     if (content is MangaContent) {
-                        ReadManga.durChapterIndex = content.mChapterIndex
-                        ReadManga.durChapterPos = content.mDurChapterPos
+                        if (ReadManga.durChapterIndex < content.mChapterIndex) {
+                            ReadManga.moveToNextChapter()
+                            if (ReadManga.hasNextChapter) {
+                                loadMoreView.startLoad()
+                            } else {
+                                loadMoreView.noMore("暂无章节了!")
+                            }
+                        } else if (ReadManga.durChapterIndex > content.mChapterIndex) {
+                            ReadManga.moveToPrevChapter()
+                        } else {
+                            ReadManga.durChapterPos = content.mDurChapterPos
+                            ReadManga.curPageChanged()
+                        }
                         upInfoBar(
                         upInfoBar(
-                            content.mChapterIndex + 1,
+                            content.mChapterIndex,
                             content.chapterSize,
                             content.chapterSize,
-                            content.mDurChapterPos + 1,
-                            content.mDurChapterCount,
+                            content.mDurChapterPos,
+                            content.mDurChapterImageCount,
                             content.mChapterName
                             content.mChapterName
                         )
                         )
                     }
                     }
@@ -244,13 +250,6 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, MangaViewModel>()
         viewModel.initData(intent)
         viewModel.initData(intent)
     }
     }
 
 
-    private fun scrollToBottom(forceLoad: Boolean = false, index: Int) {
-        if ((loadMoreView.hasMore && !loadMoreView.isLoading) && !ReadManga.gameOver || forceLoad) {
-            loadMoreView.hasMore()
-            ReadManga.moveToNextChapter(index)
-        }
-    }
-
     override fun onPostCreate(savedInstanceState: Bundle?) {
     override fun onPostCreate(savedInstanceState: Bundle?) {
         super.onPostCreate(savedInstanceState)
         super.onPostCreate(savedInstanceState)
         Looper.myQueue().addIdleHandler {
         Looper.myQueue().addIdleHandler {
@@ -260,41 +259,47 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, MangaViewModel>()
         justInitData = true
         justInitData = true
     }
     }
 
 
-    override fun loadContentFinish(list: MutableList<Any>) {
+    override fun upContent(finish: Boolean) {
         lifecycleScope.launch {
         lifecycleScope.launch {
             setTitle(ReadManga.book?.name)
             setTitle(ReadManga.book?.name)
-            val isEmpty = mAdapter.isEmpty()
+            val list = withContext(IO) { ReadManga.mangaContents }
             mAdapter.submitList(list) {
             mAdapter.submitList(list) {
-                if (isEmpty) {
+                if (loadingViewVisible && finish) {
                     binding.infobar.isVisible = true
                     binding.infobar.isVisible = true
                     upInfoBar(
                     upInfoBar(
-                        ReadManga.durChapterIndex.plus(1),
+                        ReadManga.durChapterIndex,
                         ReadManga.chapterSize,
                         ReadManga.chapterSize,
-                        ReadManga.durChapterPos.plus(1),
+                        ReadManga.durChapterPos,
                         ReadManga.durChapterImageCount,
                         ReadManga.durChapterImageCount,
-                        ReadManga.chapterTitle
+                        ReadManga.curMangaChapter!!.chapter.title
                     )
                     )
+                    binding.mRecyclerManga.scrollToPosition(ReadManga.durChapterAbsPos)
+                    binding.flLoading.isGone = true
+                    loadMoreView.visible()
+                }
 
 
-                    if (ReadManga.durChapterPos + 2 > mAdapter.getCurrentList().size - 3) {
-                        val nextIndex =
-                            (mAdapter.getCurrentList().last() as ReaderLoading).mNextChapterIndex
-                        scrollToBottom(index = nextIndex)
-                    } else {
-                        binding.mRecyclerManga.scrollToPosition(ReadManga.durChapterPos)
+                if (finish) {
+                    loadMoreView.stopLoad()
+                    if (!ReadManga.hasNextChapter) {
+                        loadMoreView.noMore("暂无章节了!")
                     }
                     }
                 }
                 }
 
 
                 if (ReadManga.chapterChanged) {
                 if (ReadManga.chapterChanged) {
-                    binding.mRecyclerManga.scrollToPosition(ReadManga.durChapterPos)
+                    binding.mRecyclerManga.scrollToPosition(ReadManga.durChapterAbsPos)
                 }
                 }
 
 
                 ReadManga.chapterChanged = false
                 ReadManga.chapterChanged = false
-                loadMoreView.visible()
-                loadMoreView.stopLoad()
             }
             }
         }
         }
     }
     }
 
 
+    override fun contentLoadFinish() {
+        lifecycleScope.launch {
+            loadMoreView.stopLoad()
+        }
+    }
+
     private fun upInfoBar(
     private fun upInfoBar(
         chapterIndex: Int,
         chapterIndex: Int,
         chapterSize: Int,
         chapterSize: Int,
@@ -315,21 +320,35 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, MangaViewModel>()
                 if (!hidePageNumberLabel) {
                 if (!hidePageNumberLabel) {
                     mLabelBuilder.append(getString(R.string.manga_check_page_number))
                     mLabelBuilder.append(getString(R.string.manga_check_page_number))
                 }
                 }
-                mLabelBuilder.append("${chapterPos}/${chapterImageCount}").append(" ")
+                mLabelBuilder.append("${chapterPos + 1}/${chapterImageCount}").append(" ")
             }
             }
 
 
             if (!hideChapter) {
             if (!hideChapter) {
                 if (!hideChapterLabel) {
                 if (!hideChapterLabel) {
                     mLabelBuilder.append(getString(R.string.manga_check_chapter))
                     mLabelBuilder.append(getString(R.string.manga_check_chapter))
                 }
                 }
-                mLabelBuilder.append("${chapterIndex}/${chapterSize}").append(" ")
+                mLabelBuilder.append("${chapterIndex + 1}/${chapterSize}").append(" ")
             }
             }
 
 
             if (!hideProgressRatio) {
             if (!hideProgressRatio) {
                 if (!hideProgressRatioLabel) {
                 if (!hideProgressRatioLabel) {
                     mLabelBuilder.append(getString(R.string.manga_check_progress))
                     mLabelBuilder.append(getString(R.string.manga_check_progress))
                 }
                 }
-                mLabelBuilder.append("${chapterIndex.div(chapterSize).times(100)}%")
+                val percent = if (chapterSize == 0 || chapterImageCount == 0 && chapterIndex == 0) {
+                    "0.0%"
+                } else if (chapterImageCount == 0) {
+                    df.format((chapterIndex + 1.0f) / chapterSize.toDouble())
+                } else {
+                    var percent =
+                        df.format(
+                            chapterIndex * 1.0f / chapterSize + 1.0f / chapterSize * (chapterPos + 1) / chapterImageCount.toDouble()
+                        )
+                    if (percent == "100.0%" && (chapterIndex + 1 != chapterSize || chapterPos + 1 != chapterImageCount)) {
+                        percent = "99.9%"
+                    }
+                    percent
+                }
+                mLabelBuilder.append(percent)
             }
             }
         }
         }
         binding.infobar.update(
         binding.infobar.update(
@@ -366,15 +385,9 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, MangaViewModel>()
         stopAutoPage()
         stopAutoPage()
     }
     }
 
 
-    override fun loadComplete() {
-        lifecycleScope.launch {
-            binding.flLoading.isGone = true
-        }
-    }
-
     override fun loadFail(msg: String) {
     override fun loadFail(msg: String) {
         lifecycleScope.launch {
         lifecycleScope.launch {
-            if (mAdapter.isEmpty() || ReadManga.chapterChanged) {
+            if (loadingViewVisible) {
                 binding.llLoading.isGone = true
                 binding.llLoading.isGone = true
                 binding.llRetry.isVisible = true
                 binding.llRetry.isVisible = true
                 binding.tvMsg.text = msg
                 binding.tvMsg.text = msg
@@ -384,12 +397,6 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, MangaViewModel>()
         }
         }
     }
     }
 
 
-    override fun noData() {
-        lifecycleScope.launch {
-            loadMoreView.noMore("暂无章节了!")
-        }
-    }
-
     override fun adjustProgress() {
     override fun adjustProgress() {
         if (ReadManga.chapterChanged) {
         if (ReadManga.chapterChanged) {
             binding.mRecyclerManga.scrollToPosition(ReadManga.durChapterPos)
             binding.mRecyclerManga.scrollToPosition(ReadManga.durChapterPos)
@@ -398,7 +405,7 @@ class ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, MangaViewModel>()
     }
     }
 
 
     override fun onDestroy() {
     override fun onDestroy() {
-        ReadManga.unregister()
+        ReadManga.unregister(this)
         super.onDestroy()
         super.onDestroy()
     }
     }
 
 

+ 8 - 3
app/src/main/java/io/legado/app/ui/book/manga/MangaViewModel.kt → app/src/main/java/io/legado/app/ui/book/manga/ReadMangaViewModel.kt

@@ -33,7 +33,7 @@ import kotlinx.coroutines.flow.onEmpty
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.take
 import kotlinx.coroutines.flow.take
 
 
-class MangaViewModel(application: Application) : BaseViewModel(application) {
+class ReadMangaViewModel(application: Application) : BaseViewModel(application) {
 
 
     private var changeSourceCoroutine: Coroutine<*>? = null
     private var changeSourceCoroutine: Coroutine<*>? = null
 
 
@@ -84,7 +84,12 @@ class MangaViewModel(application: Application) : BaseViewModel(application) {
         ensureChapterExist()
         ensureChapterExist()
 
 
         //开始加载内容
         //开始加载内容
-        ReadManga.loadContent()
+        if (!isSameBook) {
+            ReadManga.loadContent()
+        } else {
+            ReadManga.loadOrUpContent()
+        }
+
 
 
         //自动换源
         //自动换源
         if (!book.isLocal && ReadManga.bookSource == null) {
         if (!book.isLocal && ReadManga.bookSource == null) {
@@ -224,7 +229,7 @@ class MangaViewModel(application: Application) : BaseViewModel(application) {
             ReadManga.durChapterIndex = index
             ReadManga.durChapterIndex = index
             ReadManga.durChapterPos = durChapterPos
             ReadManga.durChapterPos = durChapterPos
             ReadManga.saveRead()
             ReadManga.saveRead()
-            ReadManga.loadContent(index)
+            ReadManga.loadContent()
         }
         }
     }
     }
 
 

+ 9 - 0
app/src/main/java/io/legado/app/ui/book/manga/entities/MangaChapter.kt

@@ -0,0 +1,9 @@
+package io.legado.app.ui.book.manga.entities
+
+import io.legado.app.data.entities.BookChapter
+
+data class MangaChapter(
+    val chapter: BookChapter,
+    val contents: List<Any>,
+    val imageCount: Int
+)

+ 1 - 1
app/src/main/java/io/legado/app/ui/book/manga/entities/MangaContent.kt

@@ -6,6 +6,6 @@ data class MangaContent(
     var nextChapterIndex: Int = 0,//下一章
     var nextChapterIndex: Int = 0,//下一章
     val mImageUrl: String = "",//当前URL
     val mImageUrl: String = "",//当前URL
     var mDurChapterPos: Int = 0,//当前章节位置
     var mDurChapterPos: Int = 0,//当前章节位置
-    var mDurChapterCount: Int = 0,//当前章节内容总数
+    var mDurChapterImageCount: Int = 0,//当前章节内容总数
     var mChapterName: String = "",//章节名称
     var mChapterName: String = "",//章节名称
 )
 )

+ 4 - 13
app/src/main/java/io/legado/app/ui/book/manga/recyclerview/MangaAdapter.kt

@@ -67,17 +67,8 @@ class MangaAdapter(private val context: Context) :
     fun isNotEmpty() = !isEmpty()
     fun isNotEmpty() = !isEmpty()
 
 
     //全部替换数据
     //全部替换数据
-    fun submitList(contents: MutableList<Any>, runnable: Runnable) {
-        val list = if (ReadManga.chapterChanged) {
-            contents
-        } else {
-            val currentList = mDiffer.currentList.toMutableList()
-            currentList.addAll(contents)
-            currentList
-        }
-        mDiffer.submitList(list) {
-            runnable.run()
-        }
+    fun submitList(contents: List<Any>, runnable: Runnable? = null) {
+        mDiffer.submitList(contents, runnable)
     }
     }
 
 
     inner class PageViewHolder(binding: BookComicRvBinding) :
     inner class PageViewHolder(binding: BookComicRvBinding) :
@@ -94,13 +85,13 @@ class MangaAdapter(private val context: Context) :
             binding.retry.setOnClickListener {
             binding.retry.setOnClickListener {
                 val item = mDiffer.currentList[layoutPosition]
                 val item = mDiffer.currentList[layoutPosition]
                 if (item is MangaContent) {
                 if (item is MangaContent) {
-                    loadImageWithRetry(item.mImageUrl, isHorizontal)
+                    loadImageWithRetry(item.mImageUrl, isHorizontal, mDiffer.currentList.size == 3)
                 }
                 }
             }
             }
         }
         }
 
 
         fun onBind(item: MangaContent) {
         fun onBind(item: MangaContent) {
-            loadImageWithRetry(item.mImageUrl, isHorizontal)
+            loadImageWithRetry(item.mImageUrl, isHorizontal, mDiffer.currentList.size == 3)
         }
         }
     }
     }
 
 

+ 6 - 2
app/src/main/java/io/legado/app/ui/book/manga/recyclerview/MangaVH.kt

@@ -50,7 +50,7 @@ open class MangaVH<VB : ViewBinding>(val binding: VB, private val context: Conte
     }
     }
 
 
     @SuppressLint("CheckResult")
     @SuppressLint("CheckResult")
-    fun loadImageWithRetry(imageUrl: String, isHorizontal: Boolean) {
+    fun loadImageWithRetry(imageUrl: String, isHorizontal: Boolean, singleImage: Boolean) {
         mFlProgress.isVisible = true
         mFlProgress.isVisible = true
         mLoading.isVisible = true
         mLoading.isVisible = true
         mRetry?.isGone = true
         mRetry?.isGone = true
@@ -95,7 +95,11 @@ open class MangaVH<VB : ViewBinding>(val binding: VB, private val context: Conte
                     mFlProgress.isGone = true
                     mFlProgress.isGone = true
                     if (!isHorizontal) {
                     if (!isHorizontal) {
                         itemView.updateLayoutParams<ViewGroup.LayoutParams> {
                         itemView.updateLayoutParams<ViewGroup.LayoutParams> {
-                            height = ViewGroup.LayoutParams.WRAP_CONTENT
+                            height = if (singleImage) {
+                                ViewGroup.LayoutParams.MATCH_PARENT
+                            } else {
+                                ViewGroup.LayoutParams.WRAP_CONTENT
+                            }
                         }
                         }
                     } else {
                     } else {
                         mImage.updateLayoutParams<FrameLayout.LayoutParams> {
                         mImage.updateLayoutParams<FrameLayout.LayoutParams> {

+ 5 - 3
app/src/main/java/io/legado/app/utils/RecyclerViewExtensions.kt

@@ -4,11 +4,13 @@ import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.recyclerview.widget.RecyclerView
 import androidx.recyclerview.widget.RecyclerView
 
 
 fun RecyclerView.findCenterViewPosition(): Int {
 fun RecyclerView.findCenterViewPosition(): Int {
-    return getChildAdapterPosition(findChildViewUnder(width / 2f, height / 2f) ?: return RecyclerView.NO_POSITION)
+    return getChildAdapterPosition(
+        findChildViewUnder(width / 2f, height / 2f) ?: return RecyclerView.NO_POSITION
+    )
 }
 }
 
 
 fun RecyclerView.findViewPosition(x: Float, y: Float): Int {
 fun RecyclerView.findViewPosition(x: Float, y: Float): Int {
-     return getChildAdapterPosition(findChildViewUnder(x, y) ?: return RecyclerView.NO_POSITION)
+    return getChildAdapterPosition(findChildViewUnder(x, y) ?: return RecyclerView.NO_POSITION)
 }
 }
 
 
 fun RecyclerView.findFirstVisibleViewPosition(): Int {
 fun RecyclerView.findFirstVisibleViewPosition(): Int {
@@ -21,7 +23,7 @@ fun RecyclerView.findFirstVisibleViewPosition(): Int {
 
 
 fun RecyclerView.findLastVisibleViewPosition(): Int {
 fun RecyclerView.findLastVisibleViewPosition(): Int {
     var pos = -1
     var pos = -1
-    if ( layoutManager is LinearLayoutManager) {
+    if (layoutManager is LinearLayoutManager) {
         pos = (layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
         pos = (layoutManager as LinearLayoutManager).findLastVisibleItemPosition()
     }
     }
     return pos
     return pos