AquaStream/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt

334 lines
14 KiB
Kotlin
Raw Normal View History

2021-04-30 17:20:15 +00:00
package com.lagradost.cloudstream3.ui.home
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
2021-07-29 00:19:42 +00:00
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.filterHomePageListByFilmQuality
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
import com.lagradost.cloudstream3.APIHolder.filterSearchResultByFilmQuality
2021-07-29 00:19:42 +00:00
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.HomePageList
2022-10-28 01:51:27 +00:00
import com.lagradost.cloudstream3.LoadResponse
2021-07-29 15:16:08 +00:00
import com.lagradost.cloudstream3.MainAPI
2021-07-30 21:03:46 +00:00
import com.lagradost.cloudstream3.SearchResponse
2022-08-18 00:54:05 +00:00
import com.lagradost.cloudstream3.mvvm.*
2021-07-29 00:19:42 +00:00
import com.lagradost.cloudstream3.ui.APIRepository
2021-09-05 11:56:25 +00:00
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
2021-07-30 21:03:46 +00:00
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
import com.lagradost.cloudstream3.utils.DataStoreHelper
2021-08-25 15:28:25 +00:00
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds
2021-07-30 21:03:46 +00:00
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
2021-08-25 15:28:25 +00:00
import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched
2021-07-30 21:03:46 +00:00
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
2021-08-25 15:28:25 +00:00
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
2022-08-16 19:07:49 +00:00
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
2021-07-30 21:03:46 +00:00
import kotlinx.coroutines.Dispatchers
2021-08-25 15:28:25 +00:00
import kotlinx.coroutines.Job
2021-07-30 21:03:46 +00:00
import kotlinx.coroutines.withContext
2021-12-09 21:20:27 +00:00
import java.util.*
import kotlin.collections.set
2021-04-30 17:20:15 +00:00
class HomeViewModel : ViewModel() {
2021-10-19 20:17:06 +00:00
private var repo: APIRepository? = null
2021-04-30 17:20:15 +00:00
2021-07-29 00:19:42 +00:00
private val _apiName = MutableLiveData<String>()
val apiName: LiveData<String> = _apiName
private val _randomItems = MutableLiveData<List<SearchResponse>?>(null)
val randomItems: LiveData<List<SearchResponse>?> = _randomItems
2021-07-29 00:19:42 +00:00
private fun autoloadRepo(): APIRepository {
return APIRepository(apis.first { it.hasMainPage })
}
private val _availableWatchStatusTypes =
MutableLiveData<Pair<EnumSet<WatchType>, EnumSet<WatchType>>>()
val availableWatchStatusTypes: LiveData<Pair<EnumSet<WatchType>, EnumSet<WatchType>>> =
_availableWatchStatusTypes
2021-12-09 21:20:27 +00:00
private val _bookmarks = MutableLiveData<Pair<Boolean, List<SearchResponse>>>()
val bookmarks: LiveData<Pair<Boolean, List<SearchResponse>>> = _bookmarks
2021-07-30 21:03:46 +00:00
2021-08-25 15:28:25 +00:00
private val _resumeWatching = MutableLiveData<List<SearchResponse>>()
2022-10-28 01:51:27 +00:00
private val _preview = MutableLiveData<Resource<LoadResponse>>()
2021-08-25 15:28:25 +00:00
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
2022-10-28 01:51:27 +00:00
val preview: LiveData<Resource<LoadResponse>> = _preview
2021-08-25 15:28:25 +00:00
2022-08-18 00:54:05 +00:00
fun loadResumeWatching() = viewModelScope.launchSafe {
2021-08-25 15:28:25 +00:00
val resumeWatching = withContext(Dispatchers.IO) {
getAllResumeStateIds()?.mapNotNull { id ->
getLastWatched(id)
}?.sortedBy { -it.updateTime }
2021-08-25 15:28:25 +00:00
}
// val resumeWatchingResult = ArrayList<DataStoreHelper.ResumeWatchingResult>()
val resumeWatchingResult = withContext(Dispatchers.IO) {
resumeWatching?.map { resume ->
val data = getKey<VideoDownloadHelper.DownloadHeaderCached>(
2021-08-25 15:28:25 +00:00
DOWNLOAD_HEADER_CACHE,
resume.parentId.toString()
) ?: return@map null
val watchPos = getViewPos(resume.episodeId)
2021-08-25 15:28:25 +00:00
DataStoreHelper.ResumeWatchingResult(
data.name,
data.url,
data.apiName,
data.type,
data.poster,
watchPos,
resume.episodeId,
resume.parentId,
resume.episode,
resume.season,
resume.isFromDownload
)
}?.filterNotNull()
2021-08-25 15:28:25 +00:00
}
2022-02-06 15:43:29 +00:00
resumeWatchingResult?.let {
_resumeWatching.postValue(it)
}
2021-08-25 15:28:25 +00:00
}
2022-08-18 00:54:05 +00:00
fun loadStoredData(preferredWatchStatus: EnumSet<WatchType>?) = viewModelScope.launchSafe {
2021-07-30 21:03:46 +00:00
val watchStatusIds = withContext(Dispatchers.IO) {
getAllWatchStateIds()?.map { id ->
Pair(id, getResultWatchState(id))
2021-07-30 21:03:46 +00:00
}
2022-08-18 00:54:05 +00:00
}?.distinctBy { it.first } ?: return@launchSafe
2021-07-30 21:03:46 +00:00
val length = WatchType.values().size
2021-12-09 21:20:27 +00:00
val currentWatchTypes = EnumSet.noneOf(WatchType::class.java)
2021-07-30 21:03:46 +00:00
for (watch in watchStatusIds) {
currentWatchTypes.add(watch.second)
if (currentWatchTypes.size >= length) {
break
}
}
2021-07-30 23:41:54 +00:00
currentWatchTypes.remove(WatchType.NONE)
2021-07-30 21:03:46 +00:00
if (currentWatchTypes.size <= 0) {
2021-12-09 21:20:27 +00:00
_bookmarks.postValue(Pair(false, ArrayList()))
2022-08-18 00:54:05 +00:00
return@launchSafe
2021-07-30 21:03:46 +00:00
}
2021-12-09 21:20:27 +00:00
val watchPrefNotNull = preferredWatchStatus ?: EnumSet.of(currentWatchTypes.first())
//if (currentWatchTypes.any { watchPrefNotNull.contains(it) }) watchPrefNotNull else listOf(currentWatchTypes.first())
2021-07-30 23:41:54 +00:00
_availableWatchStatusTypes.postValue(
2021-07-30 21:03:46 +00:00
Pair(
2021-12-09 21:20:27 +00:00
watchPrefNotNull,
currentWatchTypes,
2021-07-30 21:03:46 +00:00
)
)
2021-12-09 21:20:27 +00:00
2021-07-30 21:03:46 +00:00
val list = withContext(Dispatchers.IO) {
2021-12-09 21:20:27 +00:00
watchStatusIds.filter { watchPrefNotNull.contains(it.second) }
.mapNotNull { getBookmarkedData(it.first) }
2021-07-30 23:41:54 +00:00
.sortedBy { -it.latestUpdatedTime }
2021-07-30 21:03:46 +00:00
}
_bookmarks.postValue(Pair(true, list))
2021-07-30 21:03:46 +00:00
}
2021-10-19 20:17:06 +00:00
private var onGoingLoad: Job? = null
2021-10-09 21:59:37 +00:00
private fun loadAndCancel(api: MainAPI?) {
2021-08-25 15:28:25 +00:00
onGoingLoad?.cancel()
onGoingLoad = load(api)
}
data class ExpandableHomepageList(
var list: HomePageList,
var currentPage: Int,
var hasNext: Boolean,
)
private val expandable: MutableMap<String, ExpandableHomepageList> = mutableMapOf()
private val _page =
MutableLiveData<Resource<Map<String, ExpandableHomepageList>>>(Resource.Loading())
val page: LiveData<Resource<Map<String, ExpandableHomepageList>>> = _page
val lock: MutableSet<String> = mutableSetOf()
2022-07-31 14:20:48 +00:00
suspend fun expandAndReturn(name: String): ExpandableHomepageList? {
if (lock.contains(name)) return null
lock += name
repo?.apply {
2022-10-08 23:36:06 +00:00
waitForHomeDelay()
expandable[name]?.let { current ->
debugAssert({ !current.hasNext }) {
"Expand called when not needed"
}
val nextPage = current.currentPage + 1
val next = getMainPage(nextPage, mainPage.indexOfFirst { it.name == name })
if (next is Resource.Success) {
next.value.filterNotNull().forEach { main ->
main.items.forEach { newList ->
val key = newList.name
expandable[key]?.apply {
hasNext = main.hasNext
currentPage = nextPage
debugWarning({ newList.list.any { outer -> this.list.list.any { it.url == outer.url } } }) {
"Expanded contained an item that was previously already in the list\n${list.name} = ${this.list.list}\n${newList.name} = ${newList.list}"
}
this.list.list += newList.list
this.list.list.distinctBy { it.url } // just to be sure we are not adding the same shit for some reason
} ?: debugWarning {
"Expanded an item not in main load named $key, current list is ${expandable.keys}"
}
}
}
} else {
current.hasNext = false
}
}
_page.postValue(Resource.Success(expandable))
}
lock -= name
return expandable[name]
}
// this is soo over engineered, but idk how I can make it clean without making the main api harder to use :pensive:
2022-08-18 00:54:05 +00:00
fun expand(name: String) = viewModelScope.launchSafe {
expandAndReturn(name)
}
2022-10-28 01:51:27 +00:00
2022-08-18 00:54:05 +00:00
private fun load(api: MainAPI?) = viewModelScope.launchSafe {
2021-09-05 11:56:25 +00:00
repo = if (api != null) {
2021-07-29 00:19:42 +00:00
APIRepository(api)
} else {
autoloadRepo()
}
2021-10-09 21:59:37 +00:00
2021-07-29 15:44:16 +00:00
_apiName.postValue(repo?.name)
2021-11-27 18:49:51 +00:00
_randomItems.postValue(listOf())
2021-09-05 11:56:25 +00:00
if (repo?.hasMainPage == true) {
_page.postValue(Resource.Loading())
2022-10-28 01:51:27 +00:00
_preview.postValue(Resource.Loading())
when (val data = repo?.getMainPage(1, null)) {
is Resource.Success -> {
2021-12-24 16:09:01 +00:00
try {
expandable.clear()
data.value.forEach { home ->
home?.items?.forEach { list ->
2022-08-16 19:07:49 +00:00
val filteredList =
context?.filterHomePageListByFilmQuality(list) ?: list
expandable[list.name] =
ExpandableHomepageList(filteredList, 1, home.hasNext)
}
}
val items = data.value.mapNotNull { it?.items }.flatten()
2022-10-28 01:51:27 +00:00
items.randomOrNull()?.list?.randomOrNull()?.url?.let { url ->
// backup request in case first fails
var first = repo?.load(url)
if(first == null ||first is Resource.Failure) {
first = repo?.load(items.random().list.random().url)
}
first?.let {
_preview.postValue(it)
} ?: run {
_preview.postValue(
Resource.Failure(
false,
null,
null,
"No repo found, this should never happen"
)
)
}
} ?: run {
_preview.postValue(
Resource.Failure(
false,
null,
null,
"No homepage items"
)
)
}
_page.postValue(Resource.Success(expandable))
//val home = data.value
if (items.isNotEmpty()) {
2021-12-24 16:09:01 +00:00
val currentList =
items.shuffled().filter { it.list.isNotEmpty() }
.flatMap { it.list }
2021-12-24 16:09:01 +00:00
.distinctBy { it.url }
.toList()
if (currentList.isNotEmpty()) {
2022-08-16 19:07:49 +00:00
val randomItems =
context?.filterSearchResultByFilmQuality(currentList.shuffled())
?: currentList.shuffled()
2021-12-24 16:09:01 +00:00
_randomItems.postValue(randomItems)
}
}
} catch (e: Exception) {
2021-12-24 16:09:01 +00:00
_randomItems.postValue(emptyList())
logError(e)
}
}
is Resource.Failure -> {
_page.postValue(data!!)
}
2022-01-07 19:27:25 +00:00
else -> Unit
}
2022-07-31 14:20:48 +00:00
} else {
_page.postValue(Resource.Success(emptyMap()))
2022-10-28 01:51:27 +00:00
_preview.postValue(Resource.Failure(false, null, null, "No homepage"))
2021-09-05 11:56:25 +00:00
}
2021-04-30 17:20:15 +00:00
}
2021-07-29 15:16:08 +00:00
2022-08-23 19:28:42 +00:00
fun loadAndCancel(preferredApiName: String?, forceReload: Boolean = true) =
viewModelScope.launchSafe {
// Since plugins are loaded in stages this function can get called multiple times.
2022-08-24 02:21:46 +00:00
// The issue with this is that the homepage may be fetched multiple times while the first request is loading
val api = getApiFromNameNull(preferredApiName)
if (!forceReload && api?.let { expandable[it.name]?.list?.list?.isNotEmpty() } == true) {
2022-08-23 19:28:42 +00:00
return@launchSafe
}
2022-09-11 11:43:05 +00:00
if (preferredApiName == noneApi.name) {
2022-08-23 19:28:42 +00:00
setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)
loadAndCancel(noneApi)
2022-08-24 02:21:46 +00:00
} else if (preferredApiName == randomApi.name) {
2022-08-23 19:28:42 +00:00
val validAPIs = context?.filterProviderByPreferredMedia()
if (validAPIs.isNullOrEmpty()) {
// Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded
loadAndCancel(noneApi)
} else {
val apiRandom = validAPIs.random()
loadAndCancel(apiRandom)
setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name)
}
2022-10-06 22:06:14 +00:00
// If the plugin isn't loaded yet. (Does not set the key)
} else if (api == null) {
loadAndCancel(noneApi)
} else {
2022-08-23 19:28:42 +00:00
setKey(USER_SELECTED_HOMEPAGE_API, api.name)
loadAndCancel(api)
}
2021-09-05 11:56:25 +00:00
}
2021-04-30 17:20:15 +00:00
}