Refactor manga details loading
This commit is contained in:
@@ -2,16 +2,10 @@ package org.koitharu.kotatsu.reader.domain
|
||||
|
||||
import android.util.LongSparseArray
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.firstOrNull
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.details.domain.model.DoubleManga
|
||||
import org.koitharu.kotatsu.details.data.MangaDetails
|
||||
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||
import org.koitharu.kotatsu.reader.ui.pager.ReaderPage
|
||||
import javax.inject.Inject
|
||||
@@ -23,32 +17,24 @@ class ChaptersLoader @Inject constructor(
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
) {
|
||||
|
||||
private val chapters = MutableStateFlow(LongSparseArray<MangaChapter>(0))
|
||||
private val chapters = LongSparseArray<MangaChapter>()
|
||||
private val chapterPages = ChapterPages()
|
||||
private val mutex = Mutex()
|
||||
|
||||
val size: Int // TODO flow
|
||||
get() = chapters.value.size()
|
||||
val size: Int
|
||||
get() = chapters.size()
|
||||
|
||||
fun init(scope: CoroutineScope, manga: Flow<DoubleManga>) = scope.launch {
|
||||
manga.collect {
|
||||
val ch = it.chapters.orEmpty()
|
||||
val longSparseArray = LongSparseArray<MangaChapter>(ch.size)
|
||||
ch.forEach { x -> longSparseArray.put(x.id, x) }
|
||||
mutex.withLock {
|
||||
chapters.value = longSparseArray
|
||||
}
|
||||
suspend fun init(manga: MangaDetails) = mutex.withLock {
|
||||
chapters.clear()
|
||||
manga.allChapters.forEach {
|
||||
chapters.put(it.id, it)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun loadPrevNextChapter(manga: DoubleManga, currentId: Long, isNext: Boolean) {
|
||||
val chapters = manga.chapters ?: return
|
||||
suspend fun loadPrevNextChapter(manga: MangaDetails, currentId: Long, isNext: Boolean) {
|
||||
val chapters = manga.allChapters
|
||||
val predicate: (MangaChapter) -> Boolean = { it.id == currentId }
|
||||
val index = if (isNext) {
|
||||
chapters.indexOfFirst(predicate)
|
||||
} else {
|
||||
chapters.indexOfLast(predicate)
|
||||
}
|
||||
val index = if (isNext) chapters.indexOfFirst(predicate) else chapters.indexOfLast(predicate)
|
||||
if (index == -1) return
|
||||
val newChapter = chapters.getOrNull(if (isNext) index + 1 else index - 1) ?: return
|
||||
val newPages = loadChapter(newChapter.id)
|
||||
@@ -79,11 +65,7 @@ class ChaptersLoader @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun peekChapter(chapterId: Long): MangaChapter? = chapters.value[chapterId]
|
||||
|
||||
suspend fun awaitChapter(chapterId: Long): MangaChapter? = chapters.mapNotNull { x ->
|
||||
x[chapterId]
|
||||
}.firstOrNull()
|
||||
fun peekChapter(chapterId: Long): MangaChapter? = chapters[chapterId]
|
||||
|
||||
fun getPages(chapterId: Long): List<ReaderPage> {
|
||||
return chapterPages.subList(chapterId)
|
||||
@@ -100,7 +82,7 @@ class ChaptersLoader @Inject constructor(
|
||||
fun snapshot() = chapterPages.toList()
|
||||
|
||||
private suspend fun loadChapter(chapterId: Long): List<ReaderPage> {
|
||||
val chapter = checkNotNull(awaitChapter(chapterId)) { "Requested chapter not found" }
|
||||
val chapter = checkNotNull(chapters[chapterId]) { "Requested chapter not found" }
|
||||
val repo = mangaRepositoryFactory.create(chapter.source)
|
||||
return repo.getPages(chapter).mapIndexed { index, page ->
|
||||
ReaderPage(page, index, chapterId)
|
||||
|
||||
@@ -33,13 +33,11 @@ class ChaptersSheet : BaseAdaptiveSheet<SheetChaptersBinding>(),
|
||||
override fun onCreateViewBinding(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
): SheetChaptersBinding {
|
||||
return SheetChaptersBinding.inflate(inflater, container, false)
|
||||
}
|
||||
) = SheetChaptersBinding.inflate(inflater, container, false)
|
||||
|
||||
override fun onViewBindingCreated(binding: SheetChaptersBinding, savedInstanceState: Bundle?) {
|
||||
super.onViewBindingCreated(binding, savedInstanceState)
|
||||
val chapters = viewModel.manga?.chapters
|
||||
val chapters = viewModel.manga?.allChapters
|
||||
if (chapters.isNullOrEmpty()) {
|
||||
dismissAllowingStateLoss()
|
||||
return
|
||||
@@ -61,7 +59,7 @@ class ChaptersSheet : BaseAdaptiveSheet<SheetChaptersBinding>(),
|
||||
val offset =
|
||||
(resources.getDimensionPixelSize(R.dimen.chapter_list_item_height) * 0.6).roundToInt()
|
||||
adapter.setItems(
|
||||
items, RecyclerViewScrollCallback(binding.recyclerView, targetPosition, offset)
|
||||
items, RecyclerViewScrollCallback(binding.recyclerView, targetPosition, offset),
|
||||
)
|
||||
} else {
|
||||
adapter.items = items
|
||||
|
||||
@@ -189,7 +189,7 @@ class ReaderActivity :
|
||||
val state = viewModel.getCurrentState() ?: return false
|
||||
PagesThumbnailsSheet.show(
|
||||
supportFragmentManager,
|
||||
viewModel.manga?.any ?: return false,
|
||||
viewModel.manga?.toManga() ?: return false,
|
||||
state.chapterId,
|
||||
state.page,
|
||||
)
|
||||
|
||||
@@ -44,12 +44,11 @@ import org.koitharu.kotatsu.core.util.ext.call
|
||||
import org.koitharu.kotatsu.core.util.ext.ifNullOrEmpty
|
||||
import org.koitharu.kotatsu.core.util.ext.printStackTraceDebug
|
||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||
import org.koitharu.kotatsu.details.domain.DoubleMangaLoadUseCase
|
||||
import org.koitharu.kotatsu.details.domain.model.DoubleManga
|
||||
import org.koitharu.kotatsu.details.data.MangaDetails
|
||||
import org.koitharu.kotatsu.details.domain.DetailsLoadUseCase
|
||||
import org.koitharu.kotatsu.history.data.HistoryRepository
|
||||
import org.koitharu.kotatsu.history.data.PROGRESS_NONE
|
||||
import org.koitharu.kotatsu.history.domain.HistoryUpdateUseCase
|
||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||
import org.koitharu.kotatsu.reader.domain.ChaptersLoader
|
||||
@@ -74,7 +73,7 @@ class ReaderViewModel @Inject constructor(
|
||||
private val pageLoader: PageLoader,
|
||||
private val chaptersLoader: ChaptersLoader,
|
||||
private val appShortcutManager: AppShortcutManager,
|
||||
private val doubleMangaLoadUseCase: DoubleMangaLoadUseCase,
|
||||
private val detailsLoadUseCase: DetailsLoadUseCase,
|
||||
private val historyUpdateUseCase: HistoryUpdateUseCase,
|
||||
private val detectReaderModeUseCase: DetectReaderModeUseCase,
|
||||
) : BaseViewModel() {
|
||||
@@ -88,9 +87,9 @@ class ReaderViewModel @Inject constructor(
|
||||
private var bookmarkJob: Job? = null
|
||||
private var stateChangeJob: Job? = null
|
||||
private val currentState = MutableStateFlow<ReaderState?>(savedStateHandle[ReaderActivity.EXTRA_STATE])
|
||||
private val mangaData = MutableStateFlow(intent.manga?.let { DoubleManga(it) })
|
||||
private val mangaData = MutableStateFlow(intent.manga?.let { MangaDetails(it, null, null, false) })
|
||||
private val mangaFlow: Flow<Manga?>
|
||||
get() = mangaData.map { it?.any }
|
||||
get() = mangaData.map { it?.toManga() }
|
||||
|
||||
val readerMode = MutableStateFlow<ReaderMode?>(null)
|
||||
val onPageSaved = MutableEventFlow<Uri?>()
|
||||
@@ -98,7 +97,7 @@ class ReaderViewModel @Inject constructor(
|
||||
val uiState = MutableStateFlow<ReaderUiState?>(null)
|
||||
|
||||
val content = MutableStateFlow(ReaderContent(emptyList(), null))
|
||||
val manga: DoubleManga?
|
||||
val manga: MangaDetails?
|
||||
get() = mangaData.value
|
||||
|
||||
val pageAnimation = settings.observeAsStateFlow(
|
||||
@@ -148,7 +147,7 @@ class ReaderViewModel @Inject constructor(
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Lazily, false)
|
||||
|
||||
val isBookmarkAdded = currentState.flatMapLatest { state ->
|
||||
val manga = mangaData.value?.any
|
||||
val manga = mangaData.value?.toManga()
|
||||
if (state == null || manga == null) {
|
||||
flowOf(false)
|
||||
} else {
|
||||
@@ -178,7 +177,7 @@ class ReaderViewModel @Inject constructor(
|
||||
|
||||
fun switchMode(newMode: ReaderMode) {
|
||||
launchJob {
|
||||
val manga = checkNotNull(mangaData.value?.any)
|
||||
val manga = checkNotNull(mangaData.value?.toManga())
|
||||
dataRepository.saveReaderMode(
|
||||
manga = manga,
|
||||
mode = newMode,
|
||||
@@ -199,7 +198,7 @@ class ReaderViewModel @Inject constructor(
|
||||
}
|
||||
val readerState = state ?: currentState.value ?: return
|
||||
historyUpdateUseCase.invokeAsync(
|
||||
manga = mangaData.value?.any ?: return,
|
||||
manga = mangaData.value?.toManga() ?: return,
|
||||
readerState = readerState,
|
||||
percent = computePercent(readerState.chapterId, readerState.page),
|
||||
)
|
||||
@@ -295,7 +294,7 @@ class ReaderViewModel @Inject constructor(
|
||||
val state = checkNotNull(currentState.value)
|
||||
val page = checkNotNull(getCurrentPage()) { "Page not found" }
|
||||
val bookmark = Bookmark(
|
||||
manga = checkNotNull(mangaData.value?.any),
|
||||
manga = mangaData.requireValue().toManga(),
|
||||
pageId = page.id,
|
||||
chapterId = state.chapterId,
|
||||
page = state.page,
|
||||
@@ -315,7 +314,7 @@ class ReaderViewModel @Inject constructor(
|
||||
}
|
||||
bookmarkJob = launchJob {
|
||||
loadingJob?.join()
|
||||
val manga = checkNotNull(mangaData.value?.any)
|
||||
val manga = mangaData.requireValue().toManga()
|
||||
val state = checkNotNull(getCurrentState())
|
||||
bookmarksRepository.removeBookmark(manga.id, state.chapterId, state.page)
|
||||
onShowToast.call(R.string.bookmark_removed)
|
||||
@@ -324,25 +323,19 @@ class ReaderViewModel @Inject constructor(
|
||||
|
||||
private fun loadImpl() {
|
||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||
var manga = DoubleManga(
|
||||
dataRepository.resolveIntent(intent)
|
||||
?: throw NotFoundException("Cannot find manga", ""),
|
||||
)
|
||||
mangaData.value = manga
|
||||
val mangaFlow = doubleMangaLoadUseCase(intent)
|
||||
manga = mangaFlow.first { x -> x.any != null }
|
||||
chaptersLoader.init(viewModelScope, mangaFlow.withErrorHandling())
|
||||
// determine mode
|
||||
val singleManga = manga.requireAny()
|
||||
val details = detailsLoadUseCase.invoke(intent).first { x -> x.isLoaded }
|
||||
mangaData.value = details
|
||||
chaptersLoader.init(details)
|
||||
val manga = details.toManga()
|
||||
// obtain state
|
||||
if (currentState.value == null) {
|
||||
currentState.value = historyRepository.getOne(singleManga)?.let {
|
||||
currentState.value = historyRepository.getOne(manga)?.let {
|
||||
ReaderState(it)
|
||||
} ?: ReaderState(singleManga, preselectedBranch)
|
||||
} ?: ReaderState(manga, preselectedBranch)
|
||||
}
|
||||
val mode = detectReaderModeUseCase.invoke(singleManga, currentState.value)
|
||||
val branch = chaptersLoader.awaitChapter(currentState.value?.chapterId ?: 0L)?.branch
|
||||
mangaData.value = manga.filterChapters(branch)
|
||||
val mode = detectReaderModeUseCase.invoke(manga, currentState.value)
|
||||
val branch = chaptersLoader.peekChapter(currentState.value?.chapterId ?: 0L)?.branch
|
||||
mangaData.value = details.filterChapters(branch)
|
||||
readerMode.value = mode
|
||||
|
||||
chaptersLoader.loadSingleChapter(requireNotNull(currentState.value).chapterId)
|
||||
@@ -350,7 +343,7 @@ class ReaderViewModel @Inject constructor(
|
||||
if (!isIncognito) {
|
||||
currentState.value?.let {
|
||||
val percent = computePercent(it.chapterId, it.page)
|
||||
historyUpdateUseCase.invoke(singleManga, it, percent)
|
||||
historyUpdateUseCase.invoke(manga, it, percent)
|
||||
}
|
||||
}
|
||||
notifyStateChanged()
|
||||
@@ -383,11 +376,11 @@ class ReaderViewModel @Inject constructor(
|
||||
val state = getCurrentState()
|
||||
val chapter = state?.chapterId?.let { chaptersLoader.peekChapter(it) }
|
||||
val newState = ReaderUiState(
|
||||
mangaName = manga?.any?.title,
|
||||
mangaName = manga?.toManga()?.title,
|
||||
branch = chapter?.branch,
|
||||
chapterName = chapter?.name,
|
||||
chapterNumber = chapter?.number ?: 0,
|
||||
chaptersTotal = manga?.any?.getChapters(chapter?.branch)?.size ?: 0,
|
||||
chaptersTotal = manga?.chapters?.get(chapter?.branch)?.size ?: 0,
|
||||
totalPages = if (chapter != null) chaptersLoader.getPagesCount(chapter.id) else 0,
|
||||
currentPage = state?.page ?: 0,
|
||||
isSliderEnabled = settings.isReaderSliderEnabled,
|
||||
@@ -398,7 +391,7 @@ class ReaderViewModel @Inject constructor(
|
||||
|
||||
private fun computePercent(chapterId: Long, pageIndex: Int): Float {
|
||||
val branch = chaptersLoader.peekChapter(chapterId)?.branch
|
||||
val chapters = manga?.any?.getChapters(branch) ?: return PROGRESS_NONE
|
||||
val chapters = manga?.chapters?.get(branch) ?: return PROGRESS_NONE
|
||||
val chaptersCount = chapters.size
|
||||
val chapterIndex = chapters.indexOfFirst { x -> x.id == chapterId }
|
||||
val pagesCount = chaptersLoader.getPagesCount(chapterId)
|
||||
|
||||
@@ -118,7 +118,7 @@ class ReaderConfigSheet :
|
||||
|
||||
R.id.button_color_filter -> {
|
||||
val page = viewModel.getCurrentPage() ?: return
|
||||
val manga = viewModel.manga?.any ?: return
|
||||
val manga = viewModel.manga?.toManga() ?: return
|
||||
startActivity(ColorFilterConfigActivity.newIntent(v.context, manga, page))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,17 +7,17 @@ import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.filterNotNull
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.last
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import org.koitharu.kotatsu.core.model.findById
|
||||
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||
import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.ext.firstNotNull
|
||||
import org.koitharu.kotatsu.core.util.ext.require
|
||||
import org.koitharu.kotatsu.details.domain.DoubleMangaLoadUseCase
|
||||
import org.koitharu.kotatsu.details.domain.DetailsLoadUseCase
|
||||
import org.koitharu.kotatsu.list.ui.model.ListHeader
|
||||
import org.koitharu.kotatsu.list.ui.model.ListModel
|
||||
import org.koitharu.kotatsu.reader.domain.ChaptersLoader
|
||||
@@ -28,7 +28,7 @@ class PagesThumbnailsViewModel @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
mangaRepositoryFactory: MangaRepository.Factory,
|
||||
private val chaptersLoader: ChaptersLoader,
|
||||
doubleMangaLoadUseCase: DoubleMangaLoadUseCase,
|
||||
detailsLoadUseCase: DetailsLoadUseCase,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val currentPageIndex: Int =
|
||||
@@ -37,7 +37,7 @@ class PagesThumbnailsViewModel @Inject constructor(
|
||||
val manga = savedStateHandle.require<ParcelableManga>(PagesThumbnailsSheet.ARG_MANGA).manga
|
||||
|
||||
private val repository = mangaRepositoryFactory.create(manga.source)
|
||||
private val mangaDetails = doubleMangaLoadUseCase(manga).map {
|
||||
private val mangaDetails = detailsLoadUseCase(MangaIntent.of(manga)).map {
|
||||
val b = manga.chapters?.findById(initialChapterId)?.branch
|
||||
branch.value = b
|
||||
it.filterChapters(b)
|
||||
@@ -52,8 +52,7 @@ class PagesThumbnailsViewModel @Inject constructor(
|
||||
|
||||
init {
|
||||
loadingJob = launchLoadingJob(Dispatchers.Default) {
|
||||
chaptersLoader.init(viewModelScope, mangaDetails.filterNotNull())
|
||||
mangaDetails.first { x -> x?.hasChapter(initialChapterId) == true }
|
||||
chaptersLoader.init(checkNotNull(mangaDetails.last()))
|
||||
chaptersLoader.loadSingleChapter(initialChapterId)
|
||||
updateList()
|
||||
}
|
||||
@@ -79,13 +78,13 @@ class PagesThumbnailsViewModel @Inject constructor(
|
||||
updateList()
|
||||
}
|
||||
|
||||
private suspend fun updateList() {
|
||||
private fun updateList() {
|
||||
val snapshot = chaptersLoader.snapshot()
|
||||
val pages = buildList(snapshot.size + chaptersLoader.size + 2) {
|
||||
var previousChapterId = 0L
|
||||
for (page in snapshot) {
|
||||
if (page.chapterId != previousChapterId) {
|
||||
chaptersLoader.awaitChapter(page.chapterId)?.let {
|
||||
chaptersLoader.peekChapter(page.chapterId)?.let {
|
||||
add(ListHeader(it.name))
|
||||
}
|
||||
previousChapterId = page.chapterId
|
||||
|
||||
Reference in New Issue
Block a user