349 lines
12 KiB
Kotlin
349 lines
12 KiB
Kotlin
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<SharedPreferences>()
|
|
val data = loadData<Selected>("${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<Media> = MutableLiveData<Media>(null)
|
|
fun getMedia(): LiveData<Media> = 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<List<ShowResponse>?>(null)
|
|
|
|
|
|
//Anime
|
|
private val kitsuEpisodes: MutableLiveData<Map<String, Episode>> =
|
|
MutableLiveData<Map<String, Episode>>(null)
|
|
|
|
fun getKitsuEpisodes(): LiveData<Map<String, Episode>> = kitsuEpisodes
|
|
suspend fun loadKitsuEpisodes(s: Media) {
|
|
tryWithSuspend {
|
|
if (kitsuEpisodes.value == null) kitsuEpisodes.postValue(Kitsu.getKitsuEpisodesDetails(s))
|
|
}
|
|
}
|
|
|
|
private val fillerEpisodes: MutableLiveData<Map<String, Episode>> =
|
|
MutableLiveData<Map<String, Episode>>(null)
|
|
|
|
fun getFillerEpisodes(): LiveData<Map<String, Episode>> = 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<MutableMap<Int, MutableMap<String, Episode>>>(null)
|
|
private val epsLoaded = mutableMapOf<Int, MutableMap<String, Episode>>()
|
|
fun getEpisodes(): LiveData<MutableMap<Int, MutableMap<String, Episode>>> = 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<Episode?>(null)
|
|
fun getEpisode(): LiveData<Episode?> = 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<VideoExtractor>()
|
|
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<List<AniSkip.Stamp>?>()
|
|
private val timeStampsMap: MutableMap<Int, List<AniSkip.Stamp>?> = 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<MutableMap<Int, MutableMap<String, MangaChapter>>>(null)
|
|
private val mangaLoaded = mutableMapOf<Int, MutableMap<String, MangaChapter>>()
|
|
fun getMangaChapters(): LiveData<MutableMap<Int, MutableMap<String, MangaChapter>>> =
|
|
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<MangaChapter?>(null)
|
|
fun getMangaChapter(): LiveData<MangaChapter?> = 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<List<ShowResponse>>(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<Book> = MutableLiveData(null)
|
|
suspend fun loadBook(novel: ShowResponse, i: Int) {
|
|
tryWithSuspend {
|
|
book.postValue(
|
|
novelSources[i]?.loadBook(novel.link, novel.extra) ?: return@tryWithSuspend
|
|
)
|
|
}
|
|
}
|
|
|
|
}
|