Fix chapters counter

pull/350/head
Koitharu 3 years ago
parent a89ff4d15d
commit 259c845912
No known key found for this signature in database
GPG Key ID: 8E861F8CE6E7CE27

@ -1,6 +1,7 @@
package org.koitharu.kotatsu.core.model package org.koitharu.kotatsu.core.model
import androidx.core.os.LocaleListCompat import androidx.core.os.LocaleListCompat
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
import org.koitharu.kotatsu.parsers.model.Manga import org.koitharu.kotatsu.parsers.model.Manga
import org.koitharu.kotatsu.parsers.util.mapToSet import org.koitharu.kotatsu.parsers.util.mapToSet
import org.koitharu.kotatsu.parsers.util.toTitleCase import org.koitharu.kotatsu.parsers.util.toTitleCase
@ -8,6 +9,18 @@ import org.koitharu.kotatsu.utils.ext.iterator
fun Collection<Manga>.ids() = mapToSet { it.id } fun Collection<Manga>.ids() = mapToSet { it.id }
fun Collection<ChapterListItem>.countChaptersByBranch(): Int {
if (size <= 1) {
return size
}
val acc = HashMap<String?, Int>()
for (item in this) {
val branch = item.chapter.branch
acc[branch] = (acc[branch] ?: 0) + 1
}
return acc.values.max()
}
fun Manga.getPreferredBranch(history: MangaHistory?): String? { fun Manga.getPreferredBranch(history: MangaHistory?): String? {
val ch = chapters val ch = chapters
if (ch.isNullOrEmpty()) { if (ch.isNullOrEmpty()) {
@ -31,4 +44,4 @@ fun Manga.getPreferredBranch(history: MangaHistory?): String? {
} }
} }
return groups.maxByOrNull { it.value.size }?.key return groups.maxByOrNull { it.value.size }?.key
} }

@ -27,6 +27,7 @@ import org.koitharu.kotatsu.base.ui.list.decor.SpacingItemDecoration
import org.koitharu.kotatsu.base.ui.widgets.ChipsView import org.koitharu.kotatsu.base.ui.widgets.ChipsView
import org.koitharu.kotatsu.bookmarks.domain.Bookmark import org.koitharu.kotatsu.bookmarks.domain.Bookmark
import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter import org.koitharu.kotatsu.bookmarks.ui.adapter.BookmarksAdapter
import org.koitharu.kotatsu.core.model.countChaptersByBranch
import org.koitharu.kotatsu.core.parser.MangaTagHighlighter import org.koitharu.kotatsu.core.parser.MangaTagHighlighter
import org.koitharu.kotatsu.databinding.FragmentDetailsBinding import org.koitharu.kotatsu.databinding.FragmentDetailsBinding
import org.koitharu.kotatsu.details.ui.model.ChapterListItem import org.koitharu.kotatsu.details.ui.model.ChapterListItem
@ -177,12 +178,9 @@ class DetailsFragment :
if (chapters.isNullOrEmpty()) { if (chapters.isNullOrEmpty()) {
infoLayout.textViewChapters.isVisible = false infoLayout.textViewChapters.isVisible = false
} else { } else {
val count = chapters.countChaptersByBranch()
infoLayout.textViewChapters.isVisible = true infoLayout.textViewChapters.isVisible = true
infoLayout.textViewChapters.text = resources.getQuantityString( infoLayout.textViewChapters.text = resources.getQuantityString(R.plurals.chapters, count, count)
R.plurals.chapters,
chapters.size,
chapters.size,
)
} }
} }

@ -95,13 +95,14 @@ class DetailsViewModel @Inject constructor(
val historyInfo: LiveData<HistoryInfo> = combine( val historyInfo: LiveData<HistoryInfo> = combine(
delegate.manga, delegate.manga,
delegate.selectedBranch,
history, history,
historyRepository.observeShouldSkip(delegate.manga), historyRepository.observeShouldSkip(delegate.manga),
) { m, h, im -> ) { m, b, h, im ->
HistoryInfo(m, h, im) HistoryInfo(m, b, h, im)
}.asFlowLiveData( }.asFlowLiveData(
context = viewModelScope.coroutineContext + Dispatchers.Default, context = viewModelScope.coroutineContext + Dispatchers.Default,
defaultValue = HistoryInfo(null, null, false), defaultValue = HistoryInfo(null, null, null, false),
) )
val bookmarks = delegate.manga.flatMapLatest { val bookmarks = delegate.manga.flatMapLatest {

@ -36,8 +36,13 @@ class HistoryInfo(
} }
} }
fun HistoryInfo(manga: Manga?, history: MangaHistory?, isIncognitoMode: Boolean): HistoryInfo { fun HistoryInfo(
val chapters = manga?.chapters manga: Manga?,
branch: String?,
history: MangaHistory?,
isIncognitoMode: Boolean
): HistoryInfo {
val chapters = manga?.getChapters(branch)
return HistoryInfo( return HistoryInfo(
totalChapters = chapters?.size ?: -1, totalChapters = chapters?.size ?: -1,
currentChapter = if (history != null && !chapters.isNullOrEmpty()) { currentChapter = if (history != null && !chapters.isNullOrEmpty()) {

@ -3,6 +3,7 @@ package org.koitharu.kotatsu.reader.domain
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import android.net.Uri import android.net.Uri
import androidx.annotation.AnyThread
import androidx.collection.LongSparseArray import androidx.collection.LongSparseArray
import androidx.collection.set import androidx.collection.set
import dagger.hilt.android.ActivityRetainedLifecycle import dagger.hilt.android.ActivityRetainedLifecycle
@ -76,6 +77,7 @@ class PageLoader @Inject constructor(
return repository is RemoteMangaRepository && settings.isPagesPreloadEnabled() return repository is RemoteMangaRepository && settings.isPagesPreloadEnabled()
} }
@AnyThread
fun prefetch(pages: List<ReaderPage>) = loaderScope.launch { fun prefetch(pages: List<ReaderPage>) = loaderScope.launch {
prefetchLock.withLock { prefetchLock.withLock {
for (page in pages.asReversed()) { for (page in pages.asReversed()) {

@ -15,9 +15,6 @@ import androidx.annotation.AttrRes
import androidx.core.graphics.ColorUtils import androidx.core.graphics.ColorUtils
import androidx.core.view.ViewCompat import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import com.google.android.material.R as materialR
import java.text.SimpleDateFormat
import java.util.*
import org.koitharu.kotatsu.R import org.koitharu.kotatsu.R
import org.koitharu.kotatsu.parsers.util.format import org.koitharu.kotatsu.parsers.util.format
import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState import org.koitharu.kotatsu.reader.ui.pager.ReaderUiState
@ -25,6 +22,9 @@ import org.koitharu.kotatsu.utils.ext.getThemeColor
import org.koitharu.kotatsu.utils.ext.measureDimension import org.koitharu.kotatsu.utils.ext.measureDimension
import org.koitharu.kotatsu.utils.ext.printStackTraceDebug import org.koitharu.kotatsu.utils.ext.printStackTraceDebug
import org.koitharu.kotatsu.utils.ext.resolveDp import org.koitharu.kotatsu.utils.ext.resolveDp
import java.text.SimpleDateFormat
import java.util.Date
import com.google.android.material.R as materialR
class ReaderInfoBarView @JvmOverloads constructor( class ReaderInfoBarView @JvmOverloads constructor(
context: Context, context: Context,
@ -121,15 +121,14 @@ class ReaderInfoBarView @JvmOverloads constructor(
fun update(state: ReaderUiState?) { fun update(state: ReaderUiState?) {
text = if (state != null) { text = if (state != null) {
val percent = state.computePercent()
context.getString( context.getString(
R.string.reader_info_pattern, R.string.reader_info_pattern,
state.chapterNumber, state.chapterNumber,
state.chaptersTotal, state.chaptersTotal,
state.currentPage + 1, state.currentPage + 1,
state.totalPages, state.totalPages,
) + if (percent in 0f..1f) { ) + if (state.percent in 0f..1f) {
" " + context.getString(R.string.percent_string_pattern, (percent * 100).format()) " " + context.getString(R.string.percent_string_pattern, (state.percent * 100).format())
} else { } else {
"" ""
} }

@ -4,6 +4,8 @@ import android.net.Uri
import android.util.LongSparseArray import android.util.LongSparseArray
import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.AnyThread import androidx.annotation.AnyThread
import androidx.annotation.MainThread
import androidx.annotation.WorkerThread
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
@ -13,6 +15,7 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.cancelAndJoin
import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
@ -80,6 +83,7 @@ class ReaderViewModel @Inject constructor(
private var loadingJob: Job? = null private var loadingJob: Job? = null
private var pageSaveJob: Job? = null private var pageSaveJob: Job? = null
private var bookmarkJob: Job? = null private var bookmarkJob: Job? = null
private var stateChangeJob: Job? = null
private val currentState = MutableStateFlow<ReaderState?>(savedStateHandle[ReaderActivity.EXTRA_STATE]) private val currentState = MutableStateFlow<ReaderState?>(savedStateHandle[ReaderActivity.EXTRA_STATE])
private val mangaData = MutableStateFlow(intent.manga) private val mangaData = MutableStateFlow(intent.manga)
private val chapters: LongSparseArray<MangaChapter> private val chapters: LongSparseArray<MangaChapter>
@ -143,7 +147,7 @@ class ReaderViewModel @Inject constructor(
settings.observe() settings.observe()
.onEach { key -> .onEach { key ->
if (key == AppSettings.KEY_READER_SLIDER) notifyStateChanged() if (key == AppSettings.KEY_READER_SLIDER) notifyStateChanged()
}.launchIn(viewModelScope) }.launchIn(viewModelScope + Dispatchers.Default)
} }
fun reload() { fun reload() {
@ -234,26 +238,31 @@ class ReaderViewModel @Inject constructor(
} }
} }
// TODO move to background? @MainThread
fun onCurrentPageChanged(position: Int) { fun onCurrentPageChanged(position: Int) {
val pages = content.value?.pages ?: return val prevJob = stateChangeJob
pages.getOrNull(position)?.let { page -> stateChangeJob = launchJob(Dispatchers.Default) {
currentState.update { cs -> prevJob?.cancelAndJoin()
cs?.copy(chapterId = page.chapterId, page = page.index) val pages = content.value?.pages ?: return@launchJob
pages.getOrNull(position)?.let { page ->
currentState.update { cs ->
cs?.copy(chapterId = page.chapterId, page = page.index)
}
}
notifyStateChanged()
if (pages.isEmpty() || loadingJob?.isActive == true) {
return@launchJob
}
ensureActive()
if (position <= BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.first().chapterId, isNext = false)
}
if (position >= pages.lastIndex - BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.last().chapterId, isNext = true)
}
if (pageLoader.isPrefetchApplicable()) {
pageLoader.prefetch(pages.trySublist(position + 1, position + PREFETCH_LIMIT))
} }
}
notifyStateChanged()
if (pages.isEmpty() || loadingJob?.isActive == true) {
return
}
if (position <= BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.first().chapterId, isNext = false)
}
if (position >= pages.size - BOUNDS_PAGE_OFFSET) {
loadPrevNextChapter(pages.last().chapterId, isNext = true)
}
if (pageLoader.isPrefetchApplicable()) {
pageLoader.prefetch(pages.trySublist(position + 1, position + PREFETCH_LIMIT))
} }
} }
@ -328,6 +337,7 @@ class ReaderViewModel @Inject constructor(
} }
} }
@AnyThread
private fun loadPrevNextChapter(currentId: Long, isNext: Boolean) { private fun loadPrevNextChapter(currentId: Long, isNext: Boolean) {
loadingJob = launchLoadingJob(Dispatchers.Default) { loadingJob = launchLoadingJob(Dispatchers.Default) {
chaptersLoader.loadPrevNextChapter(mangaData.requireValue(), currentId, isNext) chaptersLoader.loadPrevNextChapter(mangaData.requireValue(), currentId, isNext)
@ -365,7 +375,7 @@ class ReaderViewModel @Inject constructor(
}.getOrDefault(defaultMode) }.getOrDefault(defaultMode)
} }
@AnyThread @WorkerThread
private fun notifyStateChanged() { private fun notifyStateChanged() {
val state = getCurrentState() val state = getCurrentState()
val chapter = state?.chapterId?.let(chapters::get) val chapter = state?.chapterId?.let(chapters::get)
@ -373,10 +383,11 @@ class ReaderViewModel @Inject constructor(
mangaName = manga?.title, mangaName = manga?.title,
chapterName = chapter?.name, chapterName = chapter?.name,
chapterNumber = chapter?.number ?: 0, chapterNumber = chapter?.number ?: 0,
chaptersTotal = chapters.size(), chaptersTotal = manga?.getChapters(chapter?.branch)?.size ?: 0,
totalPages = if (chapter != null) chaptersLoader.getPagesCount(chapter.id) else 0, totalPages = if (chapter != null) chaptersLoader.getPagesCount(chapter.id) else 0,
currentPage = state?.page ?: 0, currentPage = state?.page ?: 0,
isSliderEnabled = settings.isReaderSliderEnabled, isSliderEnabled = settings.isReaderSliderEnabled,
percent = if (state != null) computePercent(state.chapterId, state.page) else PROGRESS_NONE,
) )
uiState.postValue(newState) uiState.postValue(newState)
} }

@ -7,17 +7,11 @@ data class ReaderUiState(
val chaptersTotal: Int, val chaptersTotal: Int,
val currentPage: Int, val currentPage: Int,
val totalPages: Int, val totalPages: Int,
val percent: Float,
private val isSliderEnabled: Boolean, private val isSliderEnabled: Boolean,
) { ) {
fun isSliderAvailable(): Boolean { fun isSliderAvailable(): Boolean {
return isSliderEnabled && totalPages > 1 && currentPage < totalPages return isSliderEnabled && totalPages > 1 && currentPage < totalPages
} }
fun computePercent(): Float {
val ppc = 1f / chaptersTotal
val chapterIndex = chapterNumber - 1
val pagePercent = (currentPage + 1) / totalPages.toFloat()
return ppc * chapterIndex + ppc * pagePercent
}
} }

Loading…
Cancel
Save