This commit is contained in:
Horis
2025-03-06 22:59:37 +08:00
parent 896980e5b2
commit 64f82edc2e
9 changed files with 278 additions and 204 deletions
@@ -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()
+169 -113
View File
@@ -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( fun loadOrUpContent() {
index: Int, 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,122 +272,119 @@ 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) { when (chapter.index - durChapterIndex) {
mCallback?.loadFail("加载内容失败") 0 -> {
return if (content == null) {
} mCallback?.loadFail("加载内容失败")
if (content.isNotEmpty()) { return
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 -> if (content.isEmpty()) {
MangaContent( mCallback?.loadFail("正文内容为空")
chapterSize = chapterSize, return
mChapterIndex = durChapterIndex,
nextChapterIndex = durChapterIndex.plus(1),
mImageUrl = src,
mDurChapterPos = index,
mChapterName = chapterTitle
)
}.toList().apply {
this.forEach {
it.mDurChapterCount = this.size
} }
durChapterImageCount = this.size val mangaChapter = getManageChapter(chapter, content)
if (mangaChapter.imageCount == 0) {
mCallback?.loadFail("正文没有图片")
return
}
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) { fun moveToNextChapter(): Boolean {
if (loading(index)) {
return
}
if (index > chapterSize - 1) {
upToc(index)
return
}
if (durChapterIndex < simulatedChapterSize - 1) { if (durChapterIndex < simulatedChapterSize - 1) {
durChapterPos = 0 durChapterIndex++
durChapterIndex = index 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 moveToPrevChapter(): Boolean {
if (durChapterIndex > 0) {
durChapterIndex--
nextMangaChapter = curMangaChapter
curMangaChapter = prevMangaChapter
prevMangaChapter = null
if (curMangaChapter == null) {
loadContent(durChapterIndex)
} else {
mCallback?.upContent(true)
}
loadContent(durChapterIndex - 1)
saveRead()
return true
}
return false
}
fun curPageChanged() { fun curPageChanged() {
upReadTime() upReadTime()
preDownload() 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()
loadContent(durChapterIndex)
} else {
durChapterIndex = durChapterIndex.minus(1)
saveRead()
gameOver = true
mCallback?.noData()
}
}.onError {
mCallback?.noData()
}
}
private fun downloadNetworkContent( private fun downloadNetworkContent(
bookSource: BookSource, bookSource: BookSource,
scope: CoroutineScope, scope: CoroutineScope,
@@ -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 upContent(finish: Boolean = false)
fun loadComplete() fun contentLoadFinish()
fun loadFail(msg: String) fun loadFail(msg: String)
fun noData()
fun adjustProgress() fun adjustProgress()
fun sureNewProgress(progress: BookProgress) fun sureNewProgress(progress: BookProgress)
} }
@@ -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) { if (!loadMoreView.isLoading && ReadManga.hasNextChapter) {
scrollToBottom(true, ReadManga.durChapterIndex) 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 -> setPreScrollListener { _, _, _, 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 ->
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 if (ReadManga.durChapterIndex < content.mChapterIndex) {
ReadManga.durChapterPos = content.mDurChapterPos 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.mDurChapterPos,
content.mDurChapterCount, 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) { if (finish) {
val nextIndex = loadMoreView.stopLoad()
(mAdapter.getCurrentList().last() as ReaderLoading).mNextChapterIndex if (!ReadManga.hasNextChapter) {
scrollToBottom(index = nextIndex) loadMoreView.noMore("暂无章节了!")
} else {
binding.mRecyclerManga.scrollToPosition(ReadManga.durChapterPos)
} }
} }
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()
} }
@@ -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()
} }
} }
@@ -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
)
@@ -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 = "",//章节名称
) )
@@ -67,17 +67,8 @@ class MangaAdapter(private val context: Context) :
fun isNotEmpty() = !isEmpty() fun isNotEmpty() = !isEmpty()
//全部替换数据 //全部替换数据
fun submitList(contents: MutableList<Any>, runnable: Runnable) { fun submitList(contents: List<Any>, runnable: Runnable? = null) {
val list = if (ReadManga.chapterChanged) { mDiffer.submitList(contents, runnable)
contents
} else {
val currentList = mDiffer.currentList.toMutableList()
currentList.addAll(contents)
currentList
}
mDiffer.submitList(list) {
runnable.run()
}
} }
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)
} }
} }
@@ -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> {
@@ -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