Refactor manga loading
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
package org.koitharu.kotatsu.details.domain
|
||||
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||
import kotlinx.coroutines.flow.flatMapLatest
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koitharu.kotatsu.core.model.DoubleManga
|
||||
import org.koitharu.kotatsu.core.model.isLocal
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.data.LocalManga
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
class DetailsInteractor @Inject constructor(
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val favouritesRepository: FavouritesRepository,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val trackingRepository: TrackingRepository,
|
||||
private val settings: AppSettings,
|
||||
private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
|
||||
) {
|
||||
|
||||
fun observeIsFavourite(mangaId: Long): Flow<Boolean> {
|
||||
return favouritesRepository.observeCategoriesIds(mangaId)
|
||||
.map { it.isNotEmpty() }
|
||||
}
|
||||
|
||||
fun observeNewChapters(mangaId: Long): Flow<Int> {
|
||||
return settings.observeAsFlow(AppSettings.KEY_TRACKER_ENABLED) { isTrackerEnabled }
|
||||
.flatMapLatest { isEnabled ->
|
||||
if (isEnabled) {
|
||||
trackingRepository.observeNewChaptersCount(mangaId)
|
||||
} else {
|
||||
flowOf(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun observeScrobblingInfo(mangaId: Long): Flow<List<ScrobblingInfo>> {
|
||||
return combine(
|
||||
scrobblers.map { it.observeScrobblingInfo(mangaId) },
|
||||
) { scrobblingInfo ->
|
||||
scrobblingInfo.filterNotNull()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun deleteLocalManga(manga: Manga) {
|
||||
val victim = if (manga.isLocal) manga else localMangaRepository.findSavedManga(manga)?.manga
|
||||
checkNotNull(victim) { "Cannot find saved manga for ${manga.title}" }
|
||||
val original = if (manga.isLocal) localMangaRepository.getRemoteManga(manga) else manga
|
||||
localMangaRepository.delete(victim) || throw IOException("Unable to delete file")
|
||||
runCatchingCancellable {
|
||||
historyRepository.deleteOrSwap(victim, original)
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
}
|
||||
}
|
||||
|
||||
fun observeIncognitoMode(mangaFlow: Flow<Manga?>): Flow<Boolean> {
|
||||
return mangaFlow
|
||||
.distinctUntilChangedBy { it?.isNsfw }
|
||||
.flatMapLatest { manga ->
|
||||
if (manga != null) {
|
||||
historyRepository.observeShouldSkip(manga)
|
||||
} else {
|
||||
settings.observeAsFlow(AppSettings.KEY_INCOGNITO_MODE) { isIncognitoModeEnabled }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun updateLocal(subject: DoubleManga?, localManga: LocalManga): DoubleManga? {
|
||||
return if (subject?.any?.id == localManga.manga.id) {
|
||||
subject.copy(
|
||||
localManga = runCatchingCancellable {
|
||||
localMangaRepository.getDetails(localManga.manga)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
subject
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -160,12 +160,14 @@ class ChaptersFragment :
|
||||
val selectedIds = selectionController?.peekCheckedIds() ?: return false
|
||||
val allItems = chaptersAdapter?.items.orEmpty()
|
||||
val items = allItems.withIndex().filter { (_, x) -> x.chapter.id in selectedIds }
|
||||
menu.findItem(R.id.action_save).isVisible = items.none { (_, x) ->
|
||||
x.chapter.source == MangaSource.LOCAL
|
||||
}
|
||||
menu.findItem(R.id.action_delete).isVisible = items.all { (_, x) ->
|
||||
x.chapter.source == MangaSource.LOCAL
|
||||
var canSave = true
|
||||
var canDelete = true
|
||||
items.forEach { (_, x) ->
|
||||
val isLocal = x.isDownloaded || x.chapter.source == MangaSource.LOCAL
|
||||
if (isLocal) canSave = false else canDelete = false
|
||||
}
|
||||
menu.findItem(R.id.action_save).isVisible = canSave
|
||||
menu.findItem(R.id.action_delete).isVisible = canDelete
|
||||
menu.findItem(R.id.action_select_all).isVisible = items.size < allItems.size
|
||||
menu.findItem(R.id.action_mark_current).isVisible = items.size == 1
|
||||
mode.title = items.size.toString()
|
||||
|
||||
@@ -52,7 +52,7 @@ fun mapChapters(
|
||||
isCurrent = chapter.id == currentId,
|
||||
isUnread = isUnread,
|
||||
isNew = false,
|
||||
isDownloaded = false,
|
||||
isDownloaded = remoteManga != null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import androidx.core.net.toUri
|
||||
import androidx.core.text.getSpans
|
||||
import androidx.core.text.parseAsHtml
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.asFlow
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
@@ -25,82 +26,74 @@ import kotlinx.coroutines.flow.flowOf
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.transformLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.plus
|
||||
import org.koitharu.kotatsu.R
|
||||
import org.koitharu.kotatsu.bookmarks.domain.Bookmark
|
||||
import org.koitharu.kotatsu.bookmarks.domain.BookmarksRepository
|
||||
import org.koitharu.kotatsu.core.model.DoubleManga
|
||||
import org.koitharu.kotatsu.core.model.getPreferredBranch
|
||||
import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||
import org.koitharu.kotatsu.core.prefs.observeAsFlow
|
||||
import org.koitharu.kotatsu.core.ui.BaseViewModel
|
||||
import org.koitharu.kotatsu.core.util.SingleLiveEvent
|
||||
import org.koitharu.kotatsu.core.util.asFlowLiveData
|
||||
import org.koitharu.kotatsu.core.util.ext.computeSize
|
||||
import org.koitharu.kotatsu.core.util.ext.requireValue
|
||||
import org.koitharu.kotatsu.core.util.ext.toFileOrNull
|
||||
import org.koitharu.kotatsu.details.domain.BranchComparator
|
||||
import org.koitharu.kotatsu.details.domain.DetailsInteractor
|
||||
import org.koitharu.kotatsu.details.ui.model.ChapterListItem
|
||||
import org.koitharu.kotatsu.details.ui.model.HistoryInfo
|
||||
import org.koitharu.kotatsu.details.ui.model.MangaBranch
|
||||
import org.koitharu.kotatsu.download.ui.worker.DownloadWorker
|
||||
import org.koitharu.kotatsu.favourites.domain.FavouritesRepository
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.data.LocalManga
|
||||
import org.koitharu.kotatsu.local.data.LocalStorageChanges
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.local.domain.DoubleMangaLoader
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.Scrobbler
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingInfo
|
||||
import org.koitharu.kotatsu.scrobbling.common.domain.model.ScrobblingStatus
|
||||
import org.koitharu.kotatsu.tracker.domain.TrackingRepository
|
||||
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class DetailsViewModel @Inject constructor(
|
||||
private val historyRepository: HistoryRepository,
|
||||
favouritesRepository: FavouritesRepository,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
trackingRepository: TrackingRepository,
|
||||
private val bookmarksRepository: BookmarksRepository,
|
||||
private val settings: AppSettings,
|
||||
private val scrobblers: Set<@JvmSuppressWildcards Scrobbler>,
|
||||
private val imageGetter: Html.ImageGetter,
|
||||
private val delegate: MangaDetailsDelegate,
|
||||
@LocalStorageChanges private val localStorageChanges: SharedFlow<LocalManga?>,
|
||||
private val downloadScheduler: DownloadWorker.Scheduler,
|
||||
private val interactor: DetailsInteractor,
|
||||
savedStateHandle: SavedStateHandle,
|
||||
private val mangaLoader: DoubleMangaLoader,
|
||||
) : BaseViewModel() {
|
||||
|
||||
private val intent = MangaIntent(savedStateHandle)
|
||||
private val mangaId = intent.mangaId
|
||||
private val doubleManga: MutableStateFlow<DoubleManga?> = MutableStateFlow(intent.manga?.let { DoubleManga(it) })
|
||||
private var loadingJob: Job
|
||||
|
||||
val onShowToast = SingleLiveEvent<Int>()
|
||||
val onDownloadStarted = SingleLiveEvent<Unit>()
|
||||
|
||||
private val mangaData = combine(
|
||||
delegate.onlineManga,
|
||||
delegate.localManga,
|
||||
) { o, l ->
|
||||
o ?: l
|
||||
}.stateIn(viewModelScope, SharingStarted.Lazily, null)
|
||||
private val mangaData = doubleManga.map { it?.any }
|
||||
.stateIn(viewModelScope, SharingStarted.Eagerly, doubleManga.value?.any)
|
||||
|
||||
private val history = historyRepository.observeOne(delegate.mangaId)
|
||||
private val history = historyRepository.observeOne(mangaId)
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, null)
|
||||
|
||||
private val favourite = favouritesRepository.observeCategoriesIds(delegate.mangaId).map { it.isNotEmpty() }
|
||||
private val favourite = interactor.observeIsFavourite(mangaId)
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
|
||||
|
||||
private val newChapters = settings.observeAsFlow(AppSettings.KEY_TRACKER_ENABLED) { isTrackerEnabled }
|
||||
.flatMapLatest { isEnabled ->
|
||||
if (isEnabled) {
|
||||
trackingRepository.observeNewChaptersCount(delegate.mangaId)
|
||||
} else {
|
||||
flowOf(0)
|
||||
}
|
||||
}.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0)
|
||||
private val newChapters = interactor.observeNewChapters(mangaId)
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, 0)
|
||||
|
||||
private val chaptersQuery = MutableStateFlow("")
|
||||
private val selectedBranch = MutableStateFlow<String?>(null)
|
||||
|
||||
private val chaptersReversed = settings.observeAsFlow(AppSettings.KEY_REVERSE_CHAPTERS) { chaptersReverse }
|
||||
.stateIn(viewModelScope + Dispatchers.Default, SharingStarted.Eagerly, false)
|
||||
@@ -112,9 +105,9 @@ class DetailsViewModel @Inject constructor(
|
||||
|
||||
val historyInfo: LiveData<HistoryInfo> = combine(
|
||||
mangaData,
|
||||
delegate.selectedBranch,
|
||||
selectedBranch,
|
||||
history,
|
||||
historyRepository.observeShouldSkip(mangaData),
|
||||
interactor.observeIncognitoMode(mangaData),
|
||||
) { m, b, h, im ->
|
||||
HistoryInfo(m, b, h, im)
|
||||
}.asFlowLiveData(
|
||||
@@ -126,10 +119,11 @@ class DetailsViewModel @Inject constructor(
|
||||
if (it != null) bookmarksRepository.observeBookmarks(it) else flowOf(emptyList())
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
|
||||
|
||||
val localSize = delegate.localManga
|
||||
val localSize = doubleManga
|
||||
.map {
|
||||
if (it != null) {
|
||||
val file = it.url.toUri().toFileOrNull()
|
||||
val local = it?.local
|
||||
if (local != null) {
|
||||
val file = local.url.toUri().toFileOrNull()
|
||||
file?.computeSize() ?: 0L
|
||||
} else {
|
||||
0L
|
||||
@@ -152,46 +146,38 @@ class DetailsViewModel @Inject constructor(
|
||||
val isScrobblingAvailable: Boolean
|
||||
get() = scrobblers.any { it.isAvailable }
|
||||
|
||||
val scrobblingInfo: LiveData<List<ScrobblingInfo>> = combine(
|
||||
scrobblers.map { it.observeScrobblingInfo(delegate.mangaId) },
|
||||
) { scrobblingInfo ->
|
||||
scrobblingInfo.filterNotNull()
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
|
||||
val scrobblingInfo: LiveData<List<ScrobblingInfo>> = interactor.observeScrobblingInfo(mangaId)
|
||||
.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
|
||||
|
||||
val branches: LiveData<List<MangaBranch>> = combine(
|
||||
delegate.onlineManga,
|
||||
delegate.localManga,
|
||||
delegate.selectedBranch,
|
||||
) { m, l, b ->
|
||||
val chapters = concat(m?.chapters, l?.chapters)
|
||||
if (chapters.isEmpty()) return@combine emptyList()
|
||||
doubleManga,
|
||||
selectedBranch,
|
||||
) { m, b ->
|
||||
val chapters = m?.chapters
|
||||
if (chapters.isNullOrEmpty()) return@combine emptyList()
|
||||
chapters.groupBy { x -> x.branch }
|
||||
.map { x -> MangaBranch(x.key, x.value.size, x.key == b) }
|
||||
.sortedWith(BranchComparator())
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext + Dispatchers.Default, emptyList())
|
||||
|
||||
val selectedBranchName = delegate.selectedBranch
|
||||
val selectedBranchName = selectedBranch
|
||||
.asFlowLiveData(viewModelScope.coroutineContext, null)
|
||||
|
||||
val isChaptersEmpty: LiveData<Boolean> = combine(
|
||||
delegate.onlineManga,
|
||||
delegate.localManga,
|
||||
doubleManga,
|
||||
isLoading.asFlow(),
|
||||
) { manga, local, loading ->
|
||||
(manga != null && manga.chapters.isNullOrEmpty()) &&
|
||||
(local != null && local.chapters.isNullOrEmpty()) &&
|
||||
!loading
|
||||
) { manga, loading ->
|
||||
manga?.any != null && manga.chapters.isNullOrEmpty() && !loading
|
||||
}.asFlowLiveData(viewModelScope.coroutineContext, false)
|
||||
|
||||
val chapters = combine(
|
||||
combine(
|
||||
delegate.onlineManga,
|
||||
delegate.localManga,
|
||||
doubleManga,
|
||||
history,
|
||||
delegate.selectedBranch,
|
||||
selectedBranch,
|
||||
newChapters,
|
||||
) { manga, local, history, branch, news ->
|
||||
mapChapters(manga, local, history, news, branch)
|
||||
) { manga, history, branch, news ->
|
||||
mapChapters(manga?.remote, manga?.local, history, news, branch)
|
||||
},
|
||||
chaptersReversed,
|
||||
chaptersQuery,
|
||||
@@ -200,7 +186,7 @@ class DetailsViewModel @Inject constructor(
|
||||
}.asLiveData(viewModelScope.coroutineContext + Dispatchers.Default)
|
||||
|
||||
val selectedBranchValue: String?
|
||||
get() = delegate.selectedBranch.value
|
||||
get() = selectedBranch.value
|
||||
|
||||
init {
|
||||
loadingJob = doLoad()
|
||||
@@ -216,20 +202,14 @@ class DetailsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun deleteLocal() {
|
||||
val m = delegate.localManga.value
|
||||
val m = doubleManga.value?.local
|
||||
if (m == null) {
|
||||
onShowToast.call(R.string.file_not_found)
|
||||
return
|
||||
}
|
||||
launchLoadingJob(Dispatchers.Default) {
|
||||
val manga = if (m.source == MangaSource.LOCAL) m else localMangaRepository.findSavedManga(m)?.manga
|
||||
checkNotNull(manga) { "Cannot find saved manga for ${m.title}" }
|
||||
val original = localMangaRepository.getRemoteManga(manga)
|
||||
localMangaRepository.delete(manga) || throw IOException("Unable to delete file")
|
||||
runCatchingCancellable {
|
||||
historyRepository.deleteOrSwap(manga, original)
|
||||
}
|
||||
onMangaRemoved.emitCall(manga)
|
||||
interactor.deleteLocalManga(m)
|
||||
onMangaRemoved.emitCall(m)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,11 +225,7 @@ class DetailsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun setSelectedBranch(branch: String?) {
|
||||
delegate.selectedBranch.value = branch
|
||||
}
|
||||
|
||||
fun getRemoteManga(): Manga? {
|
||||
return delegate.onlineManga.value
|
||||
selectedBranch.value = branch
|
||||
}
|
||||
|
||||
fun performChapterSearch(query: String?) {
|
||||
@@ -260,7 +236,7 @@ class DetailsViewModel @Inject constructor(
|
||||
val scrobbler = getScrobbler(index) ?: return
|
||||
launchJob(Dispatchers.Default) {
|
||||
scrobbler.updateScrobblingInfo(
|
||||
mangaId = delegate.mangaId,
|
||||
mangaId = mangaId,
|
||||
rating = rating,
|
||||
status = status,
|
||||
comment = null,
|
||||
@@ -272,26 +248,32 @@ class DetailsViewModel @Inject constructor(
|
||||
val scrobbler = getScrobbler(index) ?: return
|
||||
launchJob(Dispatchers.Default) {
|
||||
scrobbler.unregisterScrobbling(
|
||||
mangaId = delegate.mangaId,
|
||||
mangaId = mangaId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun markChapterAsCurrent(chapterId: Long) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
val manga = checkNotNull(mangaData.value)
|
||||
val chapters = checkNotNull(manga.getChapters(selectedBranchValue))
|
||||
val manga = checkNotNull(doubleManga.value)
|
||||
val chapters = checkNotNull(manga.filterChapters(selectedBranchValue).chapters)
|
||||
val chapterIndex = chapters.indexOfFirst { it.id == chapterId }
|
||||
check(chapterIndex in chapters.indices) { "Chapter not found" }
|
||||
val percent = chapterIndex / chapters.size.toFloat()
|
||||
historyRepository.addOrUpdate(manga = manga, chapterId = chapterId, page = 0, scroll = 0, percent = percent)
|
||||
historyRepository.addOrUpdate(
|
||||
manga = manga.requireAny(),
|
||||
chapterId = chapterId,
|
||||
page = 0,
|
||||
scroll = 0,
|
||||
percent = percent,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun download(chaptersIds: Set<Long>?) {
|
||||
launchJob(Dispatchers.Default) {
|
||||
downloadScheduler.schedule(
|
||||
delegate.onlineManga.value ?: checkNotNull(manga.value),
|
||||
doubleManga.requireValue().requireAny(),
|
||||
chaptersIds,
|
||||
)
|
||||
onDownloadStarted.emitCall(Unit)
|
||||
@@ -299,7 +281,12 @@ class DetailsViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
private fun doLoad() = launchLoadingJob(Dispatchers.Default) {
|
||||
delegate.doLoad()
|
||||
val result = mangaLoader.load(intent)
|
||||
val manga = result.requireAny()
|
||||
// find default branch
|
||||
val hist = historyRepository.getOne(manga)
|
||||
selectedBranch.value = manga.getPreferredBranch(hist)
|
||||
doubleManga.value = result
|
||||
}
|
||||
|
||||
private fun List<ChapterListItem>.filterSearch(query: String): List<ChapterListItem> {
|
||||
@@ -313,21 +300,9 @@ class DetailsViewModel @Inject constructor(
|
||||
|
||||
private suspend fun onDownloadComplete(downloadedManga: LocalManga?) {
|
||||
downloadedManga ?: return
|
||||
val currentManga = mangaData.value ?: return
|
||||
if (currentManga.id != downloadedManga.manga.id) {
|
||||
return
|
||||
}
|
||||
if (currentManga.source == MangaSource.LOCAL) {
|
||||
reload()
|
||||
} else {
|
||||
viewModelScope.launch(Dispatchers.Default) {
|
||||
runCatchingCancellable {
|
||||
localMangaRepository.getDetails(downloadedManga.manga)
|
||||
}.onSuccess {
|
||||
delegate.publishManga(it)
|
||||
}.onFailure {
|
||||
it.printStackTraceDebug()
|
||||
}
|
||||
launchJob {
|
||||
doubleManga.update {
|
||||
interactor.updateLocal(it, downloadedManga)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -353,18 +328,4 @@ class DetailsViewModel @Inject constructor(
|
||||
}
|
||||
return scrobbler
|
||||
}
|
||||
|
||||
private fun <T> concat(a: List<T>?, b: List<T>?): List<T> {
|
||||
return when {
|
||||
a == null && b == null -> emptyList<T>()
|
||||
a == null && b != null -> b
|
||||
a != null && b == null -> a
|
||||
a != null && b != null -> buildList<T>(a.size + b.size) {
|
||||
addAll(a)
|
||||
addAll(b)
|
||||
}
|
||||
|
||||
else -> error("This shouldn't have happened")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
package org.koitharu.kotatsu.details.ui
|
||||
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import dagger.hilt.android.ViewModelLifecycle
|
||||
import dagger.hilt.android.scopes.ViewModelScoped
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import org.koitharu.kotatsu.core.model.getPreferredBranch
|
||||
import org.koitharu.kotatsu.core.os.NetworkState
|
||||
import org.koitharu.kotatsu.core.parser.MangaDataRepository
|
||||
import org.koitharu.kotatsu.core.parser.MangaIntent
|
||||
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||
import org.koitharu.kotatsu.core.util.RetainedLifecycleCoroutineScope
|
||||
import org.koitharu.kotatsu.history.domain.HistoryRepository
|
||||
import org.koitharu.kotatsu.local.domain.LocalMangaRepository
|
||||
import org.koitharu.kotatsu.parsers.exception.NotFoundException
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||
import org.koitharu.kotatsu.parsers.util.runCatchingCancellable
|
||||
import org.koitharu.kotatsu.util.ext.printStackTraceDebug
|
||||
import javax.inject.Inject
|
||||
|
||||
@ViewModelScoped
|
||||
class MangaDetailsDelegate @Inject constructor(
|
||||
savedStateHandle: SavedStateHandle,
|
||||
lifecycle: ViewModelLifecycle,
|
||||
private val mangaDataRepository: MangaDataRepository,
|
||||
private val historyRepository: HistoryRepository,
|
||||
private val localMangaRepository: LocalMangaRepository,
|
||||
private val mangaRepositoryFactory: MangaRepository.Factory,
|
||||
networkState: NetworkState,
|
||||
) {
|
||||
private val viewModelScope = RetainedLifecycleCoroutineScope(lifecycle)
|
||||
|
||||
private val intent = MangaIntent(savedStateHandle)
|
||||
private val onlineMangaStateFlow = MutableStateFlow<Manga?>(null)
|
||||
private val localMangaStateFlow = MutableStateFlow<Manga?>(null)
|
||||
|
||||
val onlineManga = combine(
|
||||
onlineMangaStateFlow,
|
||||
networkState,
|
||||
) { m, s -> m.takeIf { s } }
|
||||
.stateIn(viewModelScope, SharingStarted.Lazily, null)
|
||||
val localManga = localMangaStateFlow.asStateFlow()
|
||||
|
||||
val selectedBranch = MutableStateFlow<String?>(null)
|
||||
val mangaId = intent.manga?.id ?: intent.mangaId
|
||||
|
||||
init {
|
||||
intent.manga?.let {
|
||||
publishManga(it)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun doLoad() {
|
||||
var manga = mangaDataRepository.resolveIntent(intent) ?: throw NotFoundException("Cannot find manga", "")
|
||||
publishManga(manga)
|
||||
manga = mangaRepositoryFactory.create(manga.source).getDetails(manga)
|
||||
// find default branch
|
||||
val hist = historyRepository.getOne(manga)
|
||||
selectedBranch.value = manga.getPreferredBranch(hist)
|
||||
publishManga(manga)
|
||||
runCatchingCancellable {
|
||||
if (manga.source == MangaSource.LOCAL) {
|
||||
val m = localMangaRepository.getRemoteManga(manga) ?: return@runCatchingCancellable null
|
||||
mangaRepositoryFactory.create(m.source).getDetails(m)
|
||||
} else {
|
||||
localMangaRepository.findSavedManga(manga)?.manga
|
||||
}
|
||||
}.onFailure { error ->
|
||||
error.printStackTraceDebug()
|
||||
}.onSuccess {
|
||||
if (it != null) {
|
||||
publishManga(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun publishManga(manga: Manga) {
|
||||
if (manga.source == MangaSource.LOCAL) {
|
||||
localMangaStateFlow
|
||||
} else {
|
||||
onlineMangaStateFlow
|
||||
}.value = manga
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user