package ani.dantotsu.media import android.app.Activity import android.content.SharedPreferences import android.os.Handler import android.os.Looper import androidx.fragment.app.FragmentManager import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import ani.dantotsu.R import ani.dantotsu.connections.anilist.Anilist import ani.dantotsu.currContext import ani.dantotsu.loadData import ani.dantotsu.logger import ani.dantotsu.media.anime.Episode import ani.dantotsu.media.anime.SelectorDialogFragment import ani.dantotsu.media.manga.MangaChapter import ani.dantotsu.others.AniSkip import ani.dantotsu.others.Jikan import ani.dantotsu.others.Kitsu import ani.dantotsu.parsers.AnimeSources import ani.dantotsu.parsers.Book import ani.dantotsu.parsers.MangaImage import ani.dantotsu.parsers.MangaReadSources import ani.dantotsu.parsers.MangaSources import ani.dantotsu.parsers.NovelSources import ani.dantotsu.parsers.ShowResponse import ani.dantotsu.parsers.VideoExtractor import ani.dantotsu.parsers.WatchSources import ani.dantotsu.saveData import ani.dantotsu.snackString import ani.dantotsu.tryWithSuspend import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.MainScope import kotlinx.coroutines.launch import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get class MediaDetailsViewModel : ViewModel() { val scrolledToTop = MutableLiveData(true) fun saveSelected(id: Int, data: Selected, activity: Activity? = null) { saveData("$id-select", data, activity) } fun loadSelected(media: Media, isDownload: Boolean = false): Selected { val sharedPreferences = Injekt.get() val data = loadData("${media.id}-select") ?: Selected().let { it.sourceIndex = if (media.isAdult) 0 else when (media.anime != null) { true -> sharedPreferences.getInt("settings_def_anime_source_s_r", 0) else -> sharedPreferences.getInt(("settings_def_manga_source_s_r"), 0) } it.preferDub = loadData("settings_prefer_dub") ?: false saveSelected(media.id, it) it } if (isDownload) { data.sourceIndex = if (media.anime != null) { AnimeSources.list.size - 1 } else if (media.format == "MANGA" || media.format == "ONE_SHOT") { MangaSources.list.size - 1 } else { NovelSources.list.size - 1 } } return data } fun loadSelectedStringLocation(sourceName: String): Int { //find the location of the source in the list var location = watchSources?.list?.indexOfFirst { it.name == sourceName } ?: 0 if (location == -1) { location = 0 } return location } var continueMedia: Boolean? = null private var loading = false private val media: MutableLiveData = MutableLiveData(null) fun getMedia(): LiveData = media fun loadMedia(m: Media) { if (!loading) { loading = true media.postValue(Anilist.query.mediaDetails(m)) } loading = false } fun setMedia(m: Media) { media.postValue(m) } val responses = MutableLiveData?>(null) //Anime private val kitsuEpisodes: MutableLiveData> = MutableLiveData>(null) fun getKitsuEpisodes(): LiveData> = kitsuEpisodes suspend fun loadKitsuEpisodes(s: Media) { tryWithSuspend { if (kitsuEpisodes.value == null) kitsuEpisodes.postValue(Kitsu.getKitsuEpisodesDetails(s)) } } private val fillerEpisodes: MutableLiveData> = MutableLiveData>(null) fun getFillerEpisodes(): LiveData> = fillerEpisodes suspend fun loadFillerEpisodes(s: Media) { tryWithSuspend { if (fillerEpisodes.value == null) fillerEpisodes.postValue( Jikan.getEpisodes( s.idMAL ?: return@tryWithSuspend ) ) } } var watchSources: WatchSources? = null private val episodes = MutableLiveData>>(null) private val epsLoaded = mutableMapOf>() fun getEpisodes(): LiveData>> = episodes suspend fun loadEpisodes(media: Media, i: Int, invalidate: Boolean = false) { if (!epsLoaded.containsKey(i) || invalidate) { epsLoaded[i] = watchSources?.loadEpisodesFromMedia(i, media) ?: return } episodes.postValue(epsLoaded) } suspend fun forceLoadEpisode(media: Media, i: Int) { epsLoaded[i] = watchSources?.loadEpisodesFromMedia(i, media) ?: return episodes.postValue(epsLoaded) } suspend fun overrideEpisodes(i: Int, source: ShowResponse, id: Int) { watchSources?.saveResponse(i, id, source) epsLoaded[i] = watchSources?.loadEpisodes(i, source.link, source.extra, source.sAnime) ?: return episodes.postValue(epsLoaded) } private var episode = MutableLiveData(null) fun getEpisode(): LiveData = episode suspend fun loadEpisodeVideos(ep: Episode, i: Int, post: Boolean = true) { val link = ep.link ?: return if (!ep.allStreams || ep.extractors.isNullOrEmpty()) { val list = mutableListOf() ep.extractors = list watchSources?.get(i)?.apply { if (!post && !allowsPreloading) return@apply ep.sEpisode?.let { loadByVideoServers(link, ep.extra, it) { if (it.videos.isNotEmpty()) { list.add(it) ep.extractorCallback?.invoke(it) } } } ep.extractorCallback = null if (list.isNotEmpty()) ep.allStreams = true } } if (post) { episode.postValue(ep) MainScope().launch(Dispatchers.Main) { episode.value = null } } } val timeStamps = MutableLiveData?>() private val timeStampsMap: MutableMap?> = mutableMapOf() suspend fun loadTimeStamps( malId: Int?, episodeNum: Int?, duration: Long, useProxyForTimeStamps: Boolean ) { malId ?: return episodeNum ?: return if (timeStampsMap.containsKey(episodeNum)) return timeStamps.postValue(timeStampsMap[episodeNum]) val result = AniSkip.getResult(malId, episodeNum, duration, useProxyForTimeStamps) timeStampsMap[episodeNum] = result timeStamps.postValue(result) } suspend fun loadEpisodeSingleVideo( ep: Episode, selected: Selected, post: Boolean = true ): Boolean { if (ep.extractors.isNullOrEmpty()) { val server = selected.server ?: return false val link = ep.link ?: return false ep.extractors = mutableListOf(watchSources?.get(selected.sourceIndex)?.let { selected.sourceIndex = selected.sourceIndex if (!post && !it.allowsPreloading) null else ep.sEpisode?.let { it1 -> it.loadSingleVideoServer( server, link, ep.extra, it1, post ) } } ?: return false) ep.allStreams = false } if (post) { episode.postValue(ep) MainScope().launch(Dispatchers.Main) { episode.value = null } } return true } fun setEpisode(ep: Episode?, who: String) { logger("set episode ${ep?.number} - $who", false) episode.postValue(ep) MainScope().launch(Dispatchers.Main) { episode.value = null } } val epChanged = MutableLiveData(true) fun onEpisodeClick( media: Media, i: String, manager: FragmentManager, launch: Boolean = true, prevEp: String? = null, isDownload: Boolean = false ) { Handler(Looper.getMainLooper()).post { if (manager.findFragmentByTag("dialog") == null && !manager.isDestroyed) { if (media.anime?.episodes?.get(i) != null) { media.anime.selectedEpisode = i } else { snackString(currContext()?.getString(R.string.episode_not_found, i)) return@post } media.selected = this.loadSelected(media) val selector = SelectorDialogFragment.newInstance( media.selected!!.server, launch, prevEp, isDownload ) selector.show(manager, "dialog") } } } //Manga var mangaReadSources: MangaReadSources? = null private val mangaChapters = MutableLiveData>>(null) private val mangaLoaded = mutableMapOf>() fun getMangaChapters(): LiveData>> = mangaChapters suspend fun loadMangaChapters(media: Media, i: Int, invalidate: Boolean = false) { logger("Loading Manga Chapters : $mangaLoaded") if (!mangaLoaded.containsKey(i) || invalidate) tryWithSuspend { mangaLoaded[i] = mangaReadSources?.loadChaptersFromMedia(i, media) ?: return@tryWithSuspend } mangaChapters.postValue(mangaLoaded) } suspend fun overrideMangaChapters(i: Int, source: ShowResponse, id: Int) { mangaReadSources?.saveResponse(i, id, source) tryWithSuspend { mangaLoaded[i] = mangaReadSources?.loadChapters(i, source) ?: return@tryWithSuspend } mangaChapters.postValue(mangaLoaded) } private val mangaChapter = MutableLiveData(null) fun getMangaChapter(): LiveData = mangaChapter suspend fun loadMangaChapterImages( chapter: MangaChapter, selected: Selected, series: String, post: Boolean = true ): Boolean { return tryWithSuspend(true) { chapter.addImages( mangaReadSources?.get(selected.sourceIndex) ?.loadImages(chapter.link, chapter.sChapter) ?: return@tryWithSuspend false ) if (post) mangaChapter.postValue(chapter) true } ?: false } fun loadTransformation(mangaImage: MangaImage, source: Int): BitmapTransformation? { return if (mangaImage.useTransformation) mangaReadSources?.get(source) ?.getTransformation() else null } val novelSources = NovelSources val novelResponses = MutableLiveData>(null) suspend fun searchNovels(query: String, i: Int) { val position = if (i >= novelSources.list.size) 0 else i val source = novelSources[position] tryWithSuspend(post = true) { if (source != null) { novelResponses.postValue(source.search(query)) } } } suspend fun autoSearchNovels(media: Media) { val source = novelSources[media.selected?.sourceIndex ?: 0] tryWithSuspend(post = true) { if (source != null) { novelResponses.postValue(source.sortedSearch(media)) } } } val book: MutableLiveData = MutableLiveData(null) suspend fun loadBook(novel: ShowResponse, i: Int) { tryWithSuspend { book.postValue( novelSources[i]?.loadBook(novel.link, novel.extra) ?: return@tryWithSuspend ) } } }