mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
removed context (no user change) + small bug fixes
This commit is contained in:
parent
2580455cd5
commit
a96b396307
28 changed files with 525 additions and 468 deletions
|
@ -33,7 +33,7 @@ android {
|
|||
defaultConfig {
|
||||
applicationId "com.lagradost.cloudstream3"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 31
|
||||
targetSdkVersion 30
|
||||
|
||||
versionCode 37
|
||||
versionName "2.4.5"
|
||||
|
|
|
@ -5,7 +5,13 @@ import android.content.Context
|
|||
import android.widget.Toast
|
||||
import com.google.auto.service.AutoService
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKeys
|
||||
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.removeKeys
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import org.acra.ReportField
|
||||
import org.acra.config.CoreConfiguration
|
||||
import org.acra.data.CrashReportData
|
||||
|
@ -84,5 +90,49 @@ class AcraApplication : Application() {
|
|||
private set(value) {
|
||||
_context = WeakReference(value)
|
||||
}
|
||||
|
||||
fun removeKeys(folder: String): Int? {
|
||||
return context?.removeKeys(folder)
|
||||
}
|
||||
|
||||
fun <T> setKey(path: String, value: T) {
|
||||
context?.setKey(path, value)
|
||||
}
|
||||
|
||||
fun <T> setKey(folder: String, path: String, value: T) {
|
||||
context?.setKey(folder, path, value)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> getKey(path: String, defVal: T?): T? {
|
||||
return context?.getKey(path, defVal)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> getKey(path: String): T? {
|
||||
return context?.getKey(path)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> getKey(folder: String, path: String): T? {
|
||||
return context?.getKey(folder, path)
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> getKey(folder: String, path: String, defVal: T?): T? {
|
||||
return context?.getKey(folder, path, defVal)
|
||||
}
|
||||
|
||||
fun getKeys(folder: String): List<String>? {
|
||||
return context?.getKeys(folder)
|
||||
}
|
||||
|
||||
fun removeKey(folder: String, path: String) {
|
||||
context?.removeKey(folder, path)
|
||||
}
|
||||
|
||||
fun removeKey(path: String) {
|
||||
context?.removeKey(path)
|
||||
}
|
||||
|
||||
fun openBrowser(url: String) {
|
||||
context?.openBrowser(url)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -164,6 +164,25 @@ object APIHolder {
|
|||
|
||||
return realSet
|
||||
}
|
||||
|
||||
fun Context.filterProviderByPreferredMedia(): List<MainAPI> {
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
val currentPrefMedia = settingsManager.getInt(this.getString(R.string.prefer_media_type_key), 0)
|
||||
val langs = this.getApiProviderLangSettings()
|
||||
val allApis = apis.filter { langs.contains(it.lang) }.filter { api -> api.hasMainPage }
|
||||
return if (currentPrefMedia < 1) {
|
||||
allApis
|
||||
} else {
|
||||
// Filter API depending on preferred media type
|
||||
val listEnumAnime = listOf(TvType.Anime, TvType.AnimeMovie, TvType.ONA)
|
||||
val listEnumMovieTv = listOf(TvType.Movie, TvType.TvSeries, TvType.Cartoon)
|
||||
val mediaTypeList = if (currentPrefMedia == 1) listEnumMovieTv else listEnumAnime
|
||||
|
||||
val filteredAPI =
|
||||
allApis.filter { api -> api.supportedTypes.any { it in mediaTypeList } }
|
||||
filteredAPI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**Every provider will **not** have try catch built in, so handle exceptions when calling these functions*/
|
||||
|
@ -539,7 +558,9 @@ fun LoadResponse.setDuration(input : String?) {
|
|||
if (values.size == 3) {
|
||||
val hours = values[1].toIntOrNull()
|
||||
val minutes = values[2].toIntOrNull()
|
||||
this.duration = if(minutes != null && hours != null) { hours * 60 + minutes } else null
|
||||
this.duration = if (minutes != null && hours != null) {
|
||||
hours * 60 + minutes
|
||||
} else null
|
||||
}
|
||||
}
|
||||
Regex("([0-9]*)m").matchEntire(input)?.groupValues?.let { values ->
|
||||
|
|
|
@ -490,7 +490,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
if (str.contains(appString)) {
|
||||
for (api in OAuth2Apis) {
|
||||
if (str.contains("/${api.redirectUrl}")) {
|
||||
api.handleRedirect(this, str)
|
||||
api.handleRedirect(str)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -511,7 +511,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
// init accounts
|
||||
for (api in OAuth2accountApis) {
|
||||
api.init(this)
|
||||
api.init()
|
||||
}
|
||||
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
package com.lagradost.cloudstream3.syncproviders
|
||||
|
||||
import android.content.Context
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.removeKeys
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
|
||||
abstract class AccountManager(private val defIndex: Int) : OAuth2API {
|
||||
var accountIndex = defIndex
|
||||
|
@ -13,44 +12,44 @@ abstract class AccountManager(private val defIndex: Int) : OAuth2API {
|
|||
// int array of all accounts indexes
|
||||
private val accountsKey get() = "${idPrefix}_accounts"
|
||||
|
||||
protected fun Context.removeAccountKeys() {
|
||||
this.removeKeys(accountId)
|
||||
val accounts = getAccounts(this).toMutableList()
|
||||
protected fun removeAccountKeys() {
|
||||
removeKeys(accountId)
|
||||
val accounts = getAccounts()?.toMutableList() ?: mutableListOf()
|
||||
accounts.remove(accountIndex)
|
||||
this.setKey(accountsKey, accounts.toIntArray())
|
||||
setKey(accountsKey, accounts.toIntArray())
|
||||
|
||||
init(this)
|
||||
init()
|
||||
}
|
||||
|
||||
fun getAccounts(context: Context): IntArray {
|
||||
return context.getKey(accountsKey, intArrayOf())!!
|
||||
fun getAccounts(): IntArray? {
|
||||
return getKey(accountsKey, intArrayOf())
|
||||
}
|
||||
|
||||
fun init(context: Context) {
|
||||
accountIndex = context.getKey(accountActiveKey, defIndex)!!
|
||||
val accounts = getAccounts(context)
|
||||
if (accounts.isNotEmpty() && this.loginInfo(context) == null) {
|
||||
fun init() {
|
||||
accountIndex = getKey(accountActiveKey, defIndex)!!
|
||||
val accounts = getAccounts()
|
||||
if (accounts?.isNotEmpty() == true && this.loginInfo() == null) {
|
||||
accountIndex = accounts.first()
|
||||
}
|
||||
}
|
||||
|
||||
protected fun Context.switchToNewAccount() {
|
||||
val accounts = getAccounts(this)
|
||||
accountIndex = (accounts.maxOrNull() ?: 0) + 1
|
||||
protected fun switchToNewAccount() {
|
||||
val accounts = getAccounts()
|
||||
accountIndex = (accounts?.maxOrNull() ?: 0) + 1
|
||||
}
|
||||
|
||||
protected fun Context.registerAccount() {
|
||||
this.setKey(accountActiveKey, accountIndex)
|
||||
val accounts = getAccounts(this).toMutableList()
|
||||
protected fun registerAccount() {
|
||||
setKey(accountActiveKey, accountIndex)
|
||||
val accounts = getAccounts()?.toMutableList() ?: mutableListOf()
|
||||
if (!accounts.contains(accountIndex)) {
|
||||
accounts.add(accountIndex)
|
||||
}
|
||||
|
||||
this.setKey(accountsKey, accounts.toIntArray())
|
||||
setKey(accountsKey, accounts.toIntArray())
|
||||
}
|
||||
|
||||
fun changeAccount(context: Context, index: Int) {
|
||||
fun changeAccount(index: Int) {
|
||||
accountIndex = index
|
||||
context.setKey(accountActiveKey, index)
|
||||
setKey(accountActiveKey, index)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.lagradost.cloudstream3.syncproviders
|
||||
|
||||
import android.content.Context
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.AniListApi
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.MALApi
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
@ -13,11 +12,11 @@ interface OAuth2API {
|
|||
// don't change this as all keys depend on it
|
||||
val idPrefix : String
|
||||
|
||||
fun handleRedirect(context: Context, url: String)
|
||||
fun authenticate(context: Context)
|
||||
fun handleRedirect(url: String)
|
||||
fun authenticate()
|
||||
|
||||
fun loginInfo(context: Context): LoginInfo?
|
||||
fun logOut(context: Context)
|
||||
fun loginInfo(): LoginInfo?
|
||||
fun logOut()
|
||||
|
||||
class LoginInfo(
|
||||
val profilePicture: String?,
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.lagradost.cloudstream3.syncproviders
|
||||
|
||||
import android.content.Context
|
||||
import com.lagradost.cloudstream3.ShowStatus
|
||||
|
||||
interface SyncAPI : OAuth2API {
|
||||
|
@ -67,7 +66,7 @@ interface SyncAPI : OAuth2API {
|
|||
val icon: Int
|
||||
|
||||
val mainUrl: String
|
||||
fun search(context: Context, name: String): List<SyncSearchResult>?
|
||||
fun search(name: String): List<SyncSearchResult>?
|
||||
|
||||
/**
|
||||
-1 -> None
|
||||
|
@ -78,9 +77,9 @@ interface SyncAPI : OAuth2API {
|
|||
4 -> PlanToWatch
|
||||
5 -> ReWatching
|
||||
*/
|
||||
fun score(context: Context, id: String, status: SyncStatus): Boolean
|
||||
fun score(id: String, status: SyncStatus): Boolean
|
||||
|
||||
fun getStatus(context: Context, id: String): SyncStatus?
|
||||
fun getStatus(id: String): SyncStatus?
|
||||
|
||||
fun getResult(context: Context, id: String): SyncResult?
|
||||
fun getResult(id: String): SyncResult?
|
||||
}
|
|
@ -1,11 +1,14 @@
|
|||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import android.content.Context
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
|
@ -15,12 +18,8 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.appString
|
|||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.maxStale
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.unixTime
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKeys
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
||||
import java.net.URL
|
||||
import java.util.*
|
||||
|
@ -33,9 +32,9 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
override val mainUrl = "https://anilist.co"
|
||||
override val icon = R.drawable.ic_anilist_icon
|
||||
|
||||
override fun loginInfo(context: Context): OAuth2API.LoginInfo? {
|
||||
override fun loginInfo(): OAuth2API.LoginInfo? {
|
||||
// context.getUser(true)?.
|
||||
context.getKey<AniListUser>(accountId, ANILIST_USER_KEY)?.let { user ->
|
||||
getKey<AniListUser>(accountId, ANILIST_USER_KEY)?.let { user ->
|
||||
return OAuth2API.LoginInfo(
|
||||
profilePicture = user.picture,
|
||||
name = user.name,
|
||||
|
@ -45,16 +44,16 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
return null
|
||||
}
|
||||
|
||||
override fun logOut(context: Context) {
|
||||
context.removeAccountKeys()
|
||||
override fun logOut() {
|
||||
removeAccountKeys()
|
||||
}
|
||||
|
||||
override fun authenticate(context: Context) {
|
||||
override fun authenticate() {
|
||||
val request = "https://anilist.co/api/v2/oauth/authorize?client_id=$key&response_type=token"
|
||||
context.openBrowser(request)
|
||||
openBrowser(request)
|
||||
}
|
||||
|
||||
override fun handleRedirect(context: Context, url: String) {
|
||||
override fun handleRedirect(url: String) {
|
||||
try {
|
||||
val sanitizer =
|
||||
splitQuery(URL(url.replace(appString, "https").replace("/#", "?"))) // FIX ERROR
|
||||
|
@ -63,19 +62,19 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
val endTime = unixTime + expiresIn.toLong()
|
||||
|
||||
context.switchToNewAccount()
|
||||
context.setKey(accountId, ANILIST_UNIXTIME_KEY, endTime)
|
||||
context.setKey(accountId, ANILIST_TOKEN_KEY, token)
|
||||
context.setKey(ANILIST_SHOULD_UPDATE_LIST, true)
|
||||
switchToNewAccount()
|
||||
setKey(accountId, ANILIST_UNIXTIME_KEY, endTime)
|
||||
setKey(accountId, ANILIST_TOKEN_KEY, token)
|
||||
setKey(ANILIST_SHOULD_UPDATE_LIST, true)
|
||||
ioSafe {
|
||||
context.getUser()
|
||||
getUser()
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
override fun search(context: Context, name: String): List<SyncAPI.SyncSearchResult>? {
|
||||
override fun search(name: String): List<SyncAPI.SyncSearchResult>? {
|
||||
val data = searchShows(name) ?: return null
|
||||
return data.data.Page.media.map {
|
||||
SyncAPI.SyncSearchResult(
|
||||
|
@ -88,7 +87,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
override fun getResult(context: Context, id: String): SyncAPI.SyncResult? {
|
||||
override fun getResult(id: String): SyncAPI.SyncResult? {
|
||||
val internalId = id.toIntOrNull() ?: return null
|
||||
val season = getSeason(internalId)?.data?.Media ?: return null
|
||||
|
||||
|
@ -104,9 +103,9 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
}
|
||||
|
||||
override fun getStatus(context: Context, id: String): SyncAPI.SyncStatus? {
|
||||
override fun getStatus(id: String): SyncAPI.SyncStatus? {
|
||||
val internalId = id.toIntOrNull() ?: return null
|
||||
val data = context.getDataAboutId(internalId) ?: return null
|
||||
val data = getDataAboutId(internalId) ?: return null
|
||||
|
||||
return SyncAPI.SyncStatus(
|
||||
score = data.score,
|
||||
|
@ -116,13 +115,13 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
}
|
||||
|
||||
override fun score(context: Context, id: String, status: SyncAPI.SyncStatus): Boolean {
|
||||
return context.postDataAboutId(
|
||||
override fun score(id: String, status: SyncAPI.SyncStatus): Boolean {
|
||||
return postDataAboutId(
|
||||
id.toIntOrNull() ?: return false,
|
||||
fromIntToAnimeStatus(status.status),
|
||||
status.score,
|
||||
status.watchedEpisodes
|
||||
)
|
||||
) ?: return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -331,21 +330,21 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
fun Context.initGetUser() {
|
||||
fun initGetUser() {
|
||||
if (getKey<String>(accountId, ANILIST_TOKEN_KEY, null) == null) return
|
||||
ioSafe {
|
||||
getUser()
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.checkToken(): Boolean {
|
||||
private fun checkToken(): Boolean {
|
||||
return unixTime > getKey(
|
||||
accountId,
|
||||
ANILIST_UNIXTIME_KEY, 0L
|
||||
)!!
|
||||
}
|
||||
|
||||
fun Context.getDataAboutId(id: Int): AniListTitleHolder? {
|
||||
fun getDataAboutId(id: Int): AniListTitleHolder? {
|
||||
val q =
|
||||
"""query (${'$'}id: Int = $id) { # Define which variables will be used in the query (id)
|
||||
Media (id: ${'$'}id, type: ANIME) { # Insert our variables into the query arguments (id) (type: ANIME is hard-coded in the query)
|
||||
|
@ -404,7 +403,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Context.postApi(url: String, q: String, cache: Boolean = false): String {
|
||||
private fun postApi(url: String, q: String, cache: Boolean = false): String {
|
||||
return try {
|
||||
if (!checkToken()) {
|
||||
// println("VARS_ " + vars)
|
||||
|
@ -504,11 +503,11 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
@JsonProperty("MediaListCollection") val MediaListCollection: MediaListCollection
|
||||
)
|
||||
|
||||
fun Context.getAnilistListCached(): Array<Lists>? {
|
||||
fun getAnilistListCached(): Array<Lists>? {
|
||||
return getKey(ANILIST_CACHED_LIST) as? Array<Lists>
|
||||
}
|
||||
|
||||
fun Context.getAnilistAnimeListSmart(): Array<Lists>? {
|
||||
fun getAnilistAnimeListSmart(): Array<Lists>? {
|
||||
if (getKey<String>(
|
||||
accountId,
|
||||
ANILIST_TOKEN_KEY,
|
||||
|
@ -529,11 +528,11 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Context.getFullAnilistList(): FullAnilistList? {
|
||||
private fun getFullAnilistList(): FullAnilistList? {
|
||||
try {
|
||||
var userID: Int? = null
|
||||
/** WARNING ASSUMES ONE USER! **/
|
||||
getKeys(ANILIST_USER_KEY).forEach { key ->
|
||||
getKeys(ANILIST_USER_KEY)?.forEach { key ->
|
||||
getKey<AniListUser>(key, null)?.let {
|
||||
userID = it.id
|
||||
}
|
||||
|
@ -591,7 +590,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
fun Context.toggleLike(id: Int): Boolean {
|
||||
fun toggleLike(id: Int): Boolean {
|
||||
val q = """mutation (${'$'}animeId: Int = $id) {
|
||||
ToggleFavourite (animeId: ${'$'}animeId) {
|
||||
anime {
|
||||
|
@ -608,7 +607,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
return data != ""
|
||||
}
|
||||
|
||||
private fun Context.postDataAboutId(id: Int, type: AniListStatusType, score: Int?, progress: Int?): Boolean {
|
||||
private fun postDataAboutId(id: Int, type: AniListStatusType, score: Int?, progress: Int?): Boolean {
|
||||
try {
|
||||
val q =
|
||||
"""mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${
|
||||
|
@ -632,7 +631,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Context.getUser(setSettings: Boolean = true): AniListUser? {
|
||||
private fun getUser(setSettings: Boolean = true): AniListUser? {
|
||||
val q = """
|
||||
{
|
||||
Viewer {
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import android.content.Context
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API
|
||||
|
||||
//TODO dropbox sync
|
||||
|
@ -10,19 +9,19 @@ class Dropbox : OAuth2API {
|
|||
override val key = "zlqsamadlwydvb2"
|
||||
override val redirectUrl = "dropboxlogin"
|
||||
|
||||
override fun authenticate(context: Context) {
|
||||
override fun authenticate() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun handleRedirect(context: Context,url: String) {
|
||||
override fun handleRedirect(url: String) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun logOut(context: Context) {
|
||||
override fun logOut() {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun loginInfo(context: Context): OAuth2API.LoginInfo? {
|
||||
override fun loginInfo(): OAuth2API.LoginInfo? {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
package com.lagradost.cloudstream3.syncproviders.providers
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Base64
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.app
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
|
@ -16,11 +18,8 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.appString
|
|||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.secondsToReadable
|
||||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.unixTime
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.splitQuery
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
||||
import java.net.URL
|
||||
import java.security.SecureRandom
|
||||
|
@ -40,26 +39,27 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
override val icon: Int
|
||||
get() = R.drawable.mal_logo
|
||||
|
||||
override fun logOut(context: Context) {
|
||||
context.removeAccountKeys()
|
||||
override fun logOut() {
|
||||
removeAccountKeys()
|
||||
}
|
||||
|
||||
override fun loginInfo(context: Context): OAuth2API.LoginInfo? {
|
||||
//context.getMalUser(true)?
|
||||
context.getKey<MalUser>(accountId, MAL_USER_KEY)?.let { user ->
|
||||
override fun loginInfo(): OAuth2API.LoginInfo? {
|
||||
//getMalUser(true)?
|
||||
getKey<MalUser>(accountId, MAL_USER_KEY)?.let { user ->
|
||||
return OAuth2API.LoginInfo(profilePicture = user.picture, name = user.name, accountIndex = accountIndex)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun search(context: Context, name: String): List<SyncAPI.SyncSearchResult> {
|
||||
override fun search(name: String): List<SyncAPI.SyncSearchResult> {
|
||||
val url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT"
|
||||
var res = app.get(
|
||||
url, headers = mapOf(
|
||||
"Authorization" to "Bearer " + context.getKey<String>(
|
||||
val auth = getKey<String>(
|
||||
accountId,
|
||||
MAL_TOKEN_KEY
|
||||
)!!,
|
||||
) ?: return emptyList()
|
||||
val res = app.get(
|
||||
url, headers = mapOf(
|
||||
"Authorization" to "Bearer " + auth,
|
||||
), cacheTime = 0
|
||||
).text
|
||||
return mapper.readValue<MalSearch>(res).data.map {
|
||||
|
@ -74,8 +74,8 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
override fun score(context: Context, id: String, status : SyncAPI.SyncStatus): Boolean {
|
||||
return context.setScoreRequest(
|
||||
override fun score(id: String, status : SyncAPI.SyncStatus): Boolean {
|
||||
return setScoreRequest(
|
||||
id.toIntOrNull() ?: return false,
|
||||
fromIntToAnimeStatus(status.status),
|
||||
status.score,
|
||||
|
@ -83,15 +83,15 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
}
|
||||
|
||||
override fun getResult(context: Context, id: String): SyncAPI.SyncResult? {
|
||||
override fun getResult(id: String): SyncAPI.SyncResult? {
|
||||
val internalId = id.toIntOrNull() ?: return null
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun getStatus(context: Context, id: String): SyncAPI.SyncStatus? {
|
||||
override fun getStatus(id: String): SyncAPI.SyncStatus? {
|
||||
val internalId = id.toIntOrNull() ?: return null
|
||||
|
||||
val data = context.getDataAboutMalId(internalId)?.my_list_status ?: return null
|
||||
val data = getDataAboutMalId(internalId)?.my_list_status ?: return null
|
||||
return SyncAPI.SyncStatus(
|
||||
score = data.score,
|
||||
status = malStatusAsString.indexOf(data.status),
|
||||
|
@ -111,7 +111,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
const val MAL_TOKEN_KEY: String = "mal_token" // anilist token for api
|
||||
}
|
||||
|
||||
override fun handleRedirect(context: Context, url: String) {
|
||||
override fun handleRedirect(url: String) {
|
||||
try {
|
||||
val sanitizer =
|
||||
splitQuery(URL(url.replace(appString, "https").replace("/#", "?"))) // FIX ERROR
|
||||
|
@ -136,10 +136,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
|
||||
if (res != "") {
|
||||
context.switchToNewAccount()
|
||||
context.storeToken(res)
|
||||
context.getMalUser()
|
||||
context.setKey(MAL_SHOULD_UPDATE_LIST, true)
|
||||
switchToNewAccount()
|
||||
storeToken(res)
|
||||
getMalUser()
|
||||
setKey(MAL_SHOULD_UPDATE_LIST, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
override fun authenticate(context: Context) {
|
||||
override fun authenticate() {
|
||||
// It is recommended to use a URL-safe string as code_verifier.
|
||||
// See section 4 of RFC 7636 for more details.
|
||||
|
||||
|
@ -161,7 +161,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
val codeChallenge = codeVerifier
|
||||
val request =
|
||||
"https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=$key&code_challenge=$codeChallenge&state=RequestID$requestId"
|
||||
context.openBrowser(request)
|
||||
openBrowser(request)
|
||||
}
|
||||
|
||||
private val mapper = JsonMapper.builder().addModule(KotlinModule())
|
||||
|
@ -170,7 +170,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
private var requestId = 0
|
||||
private var codeVerifier = ""
|
||||
|
||||
private fun Context.storeToken(response: String) {
|
||||
private fun storeToken(response: String) {
|
||||
try {
|
||||
if (response != "") {
|
||||
val token = mapper.readValue<ResponseToken>(response)
|
||||
|
@ -183,7 +183,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Context.refreshToken() {
|
||||
private fun refreshToken() {
|
||||
try {
|
||||
val res = app.post(
|
||||
"https://myanimelist.net/v1/oauth2/token",
|
||||
|
@ -278,11 +278,11 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
@JsonProperty("start_time") val start_time: String?
|
||||
)
|
||||
|
||||
fun Context.getMalAnimeListCached(): Array<Data>? {
|
||||
fun getMalAnimeListCached(): Array<Data>? {
|
||||
return getKey(MAL_CACHED_LIST) as? Array<Data>
|
||||
}
|
||||
|
||||
fun Context.getMalAnimeListSmart(): Array<Data>? {
|
||||
fun getMalAnimeListSmart(): Array<Data>? {
|
||||
if (getKey<String>(
|
||||
accountId,
|
||||
MAL_TOKEN_KEY
|
||||
|
@ -300,7 +300,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Context.getMalAnimeList(): Array<Data>? {
|
||||
private fun getMalAnimeList(): Array<Data>? {
|
||||
return try {
|
||||
checkMalToken()
|
||||
var offset = 0
|
||||
|
@ -322,8 +322,12 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
return fromIntToAnimeStatus(malStatusAsString.indexOf(string))
|
||||
}
|
||||
|
||||
private fun Context.getMalAnimeListSlice(offset: Int = 0): MalList? {
|
||||
private fun getMalAnimeListSlice(offset: Int = 0): MalList? {
|
||||
val user = "@me"
|
||||
val auth = getKey<String>(
|
||||
accountId,
|
||||
MAL_TOKEN_KEY
|
||||
) ?: return null
|
||||
return try {
|
||||
// Very lackluster docs
|
||||
// https://myanimelist.net/apiconfig/references/api/v2#operation/users_user_id_animelist_get
|
||||
|
@ -331,10 +335,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
"https://api.myanimelist.net/v2/users/$user/animelist?fields=list_status,num_episodes,media_type,status,start_date,end_date,synopsis,alternative_titles,mean,genres,rank,num_list_users,nsfw,average_episode_duration,num_favorites,popularity,num_scoring_users,start_season,favorites_info,broadcast,created_at,updated_at&nsfw=1&limit=100&offset=$offset"
|
||||
val res = app.get(
|
||||
url, headers = mapOf(
|
||||
"Authorization" to "Bearer " + getKey<String>(
|
||||
accountId,
|
||||
MAL_TOKEN_KEY
|
||||
)!!,
|
||||
"Authorization" to "Bearer $auth",
|
||||
), cacheTime = 0
|
||||
).text
|
||||
res.toKotlinObject()
|
||||
|
@ -344,7 +345,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Context.getDataAboutMalId(id: Int): MalAnime? {
|
||||
private fun getDataAboutMalId(id: Int): MalAnime? {
|
||||
return try {
|
||||
// https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get
|
||||
val url = "https://api.myanimelist.net/v2/anime/$id?fields=id,title,num_episodes,my_list_status"
|
||||
|
@ -362,7 +363,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
fun Context.setAllMalData() {
|
||||
fun setAllMalData() {
|
||||
val user = "@me"
|
||||
var isDone = false
|
||||
var index = 0
|
||||
|
@ -426,7 +427,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
return null
|
||||
}
|
||||
|
||||
private fun Context.checkMalToken() {
|
||||
private fun checkMalToken() {
|
||||
if (unixTime > getKey(
|
||||
accountId,
|
||||
MAL_UNIXTIME_KEY
|
||||
|
@ -436,7 +437,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Context.getMalUser(setSettings: Boolean = true): MalUser? {
|
||||
private fun getMalUser(setSettings: Boolean = true): MalUser? {
|
||||
checkMalToken()
|
||||
return try {
|
||||
val res = app.get(
|
||||
|
@ -483,7 +484,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
fun Context.setScoreRequest(
|
||||
fun setScoreRequest(
|
||||
id: Int,
|
||||
status: MalStatusType? = null,
|
||||
score: Int? = null,
|
||||
|
@ -514,7 +515,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
private fun Context.setScoreRequest(
|
||||
private fun setScoreRequest(
|
||||
id: Int,
|
||||
status: String? = null,
|
||||
score: Int? = null,
|
||||
|
|
|
@ -113,7 +113,7 @@ object DownloadButtonSetup {
|
|||
if (click.data.episode <= 0) null else click.data.episode,
|
||||
click.data.season
|
||||
),
|
||||
act.getViewPos(click.data.id)?.position ?: 0
|
||||
getViewPos(click.data.id)?.position ?: 0
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -106,7 +106,7 @@ class DownloadChildAdapter(
|
|||
localCard = card
|
||||
val d = card.data
|
||||
|
||||
val posDur = itemView.context.getViewPos(d.id)
|
||||
val posDur = getViewPos(d.id)
|
||||
if (posDur != null) {
|
||||
val visualPos = posDur.fixVisual()
|
||||
progressBar.max = (visualPos.duration / 1000).toInt()
|
||||
|
|
|
@ -14,7 +14,6 @@ import android.widget.TextView
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.LinearSnapHelper
|
||||
|
@ -22,7 +21,10 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import com.google.android.material.bottomsheet.BottomSheetDialog
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.apis
|
||||
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
|
@ -36,7 +38,6 @@ import com.lagradost.cloudstream3.ui.search.*
|
|||
import com.lagradost.cloudstream3.ui.search.SearchFragment.Companion.filterSearchResponse
|
||||
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.loadSearchResult
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
|
@ -130,14 +131,12 @@ class HomeFragment : Fragment() {
|
|||
}
|
||||
|
||||
private val apiChangeClickListener = View.OnClickListener { view ->
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val currentPrefMedia = settingsManager.getInt(getString(R.string.preferred_media_settings), 0)
|
||||
val validAPIs = AppUtils.filterProviderByPreferredMedia(apis, currentPrefMedia).toMutableList()
|
||||
val validAPIs = view.context?.filterProviderByPreferredMedia()?.toMutableList() ?: mutableListOf()
|
||||
|
||||
validAPIs.add(0, randomApi)
|
||||
validAPIs.add(0, noneApi)
|
||||
view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api -> Pair(index, api.name) }) {
|
||||
homeViewModel.loadAndCancel(validAPIs[itemId].name, currentPrefMedia)
|
||||
homeViewModel.loadAndCancel(validAPIs[itemId].name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -157,14 +156,12 @@ class HomeFragment : Fragment() {
|
|||
}*/
|
||||
|
||||
private fun reloadStored() {
|
||||
context?.let { ctx ->
|
||||
homeViewModel.loadResumeWatching(ctx)
|
||||
homeViewModel.loadResumeWatching()
|
||||
val list = EnumSet.noneOf(WatchType::class.java)
|
||||
ctx.getKey<IntArray>(HOME_BOOKMARK_VALUE_LIST)?.map { WatchType.fromInternalId(it) }?.let {
|
||||
getKey<IntArray>(HOME_BOOKMARK_VALUE_LIST)?.map { WatchType.fromInternalId(it) }?.let {
|
||||
list.addAll(it)
|
||||
}
|
||||
homeViewModel.loadStoredData(ctx, list)
|
||||
}
|
||||
homeViewModel.loadStoredData(list)
|
||||
}
|
||||
|
||||
/*private fun handleBack(poppedFragment: Boolean) {
|
||||
|
@ -182,7 +179,7 @@ class HomeFragment : Fragment() {
|
|||
home_change_api_loading.setOnClickListener(apiChangeClickListener)
|
||||
|
||||
observe(homeViewModel.apiName) { apiName ->
|
||||
context?.setKey(HOMEPAGE_API, apiName)
|
||||
setKey(HOMEPAGE_API, apiName)
|
||||
home_provider_name?.text = apiName
|
||||
home_provider_meta_info?.isVisible = false
|
||||
|
||||
|
@ -326,11 +323,11 @@ class HomeFragment : Fragment() {
|
|||
} else {
|
||||
list.add(watch)
|
||||
}
|
||||
homeViewModel.loadStoredData(itemView.context, list)
|
||||
homeViewModel.loadStoredData(list)
|
||||
}
|
||||
|
||||
item.first?.setOnLongClickListener { itemView ->
|
||||
homeViewModel.loadStoredData(itemView.context, EnumSet.of(watch))
|
||||
item.first?.setOnLongClickListener {
|
||||
homeViewModel.loadStoredData(EnumSet.of(watch))
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
}
|
||||
|
@ -404,7 +401,7 @@ class HomeFragment : Fragment() {
|
|||
if (id != null) {
|
||||
callback.view.popupMenuNoIcons(listOf(Pair(0, R.string.action_remove_from_bookmarks))) {
|
||||
if (itemId == 0) {
|
||||
activity?.setResultWatchState(id, WatchType.NONE.internalId)
|
||||
setResultWatchState(id, WatchType.NONE.internalId)
|
||||
reloadStored()
|
||||
}
|
||||
}
|
||||
|
@ -438,7 +435,7 @@ class HomeFragment : Fragment() {
|
|||
if (itemId == 0) {
|
||||
val card = callback.card
|
||||
if (card is DataStoreHelper.ResumeWatchingResult) {
|
||||
context?.removeLastWatched(card.parentId)
|
||||
removeLastWatched(card.parentId)
|
||||
reloadStored()
|
||||
}
|
||||
}
|
||||
|
@ -476,11 +473,9 @@ class HomeFragment : Fragment() {
|
|||
|
||||
reloadStored()
|
||||
val apiName = context?.getKey<String>(HOMEPAGE_API)
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
val currentPrefMedia = settingsManager.getInt(getString(R.string.preferred_media_settings), 0)
|
||||
if (homeViewModel.apiName.value != apiName || apiName == null) {
|
||||
//println("Caught home: " + homeViewModel.apiName.value + " at " + apiName)
|
||||
homeViewModel.loadAndCancel(apiName, currentPrefMedia)
|
||||
homeViewModel.loadAndCancel(apiName)
|
||||
}
|
||||
|
||||
// nice profile pic on homepage
|
||||
|
@ -497,7 +492,7 @@ class HomeFragment : Fragment() {
|
|||
}
|
||||
|
||||
for (syncApi in OAuth2API.OAuth2Apis) {
|
||||
val login = syncApi.loginInfo(ctx)
|
||||
val login = syncApi.loginInfo()
|
||||
val pic = login?.profilePicture
|
||||
if (pic != null) {
|
||||
home_profile_picture.setImage(pic)
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
package com.lagradost.cloudstream3.ui.home
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.lagradost.cloudstream3.APIHolder.apis
|
||||
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
||||
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.HomePageResponse
|
||||
import com.lagradost.cloudstream3.MainAPI
|
||||
import com.lagradost.cloudstream3.SearchResponse
|
||||
|
@ -16,15 +18,16 @@ import com.lagradost.cloudstream3.ui.APIRepository
|
|||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||
import com.lagradost.cloudstream3.utils.HOMEPAGE_API
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -55,22 +58,22 @@ class HomeViewModel : ViewModel() {
|
|||
private val _resumeWatching = MutableLiveData<List<SearchResponse>>()
|
||||
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
|
||||
|
||||
fun loadResumeWatching(context: Context) = viewModelScope.launch {
|
||||
fun loadResumeWatching() = viewModelScope.launch {
|
||||
val resumeWatching = withContext(Dispatchers.IO) {
|
||||
context.getAllResumeStateIds().mapNotNull { id ->
|
||||
context.getLastWatched(id)
|
||||
}.sortedBy { -it.updateTime }
|
||||
getAllResumeStateIds()?.mapNotNull { id ->
|
||||
getLastWatched(id)
|
||||
}?.sortedBy { -it.updateTime }
|
||||
}
|
||||
|
||||
// val resumeWatchingResult = ArrayList<DataStoreHelper.ResumeWatchingResult>()
|
||||
|
||||
val resumeWatchingResult = withContext(Dispatchers.IO) {
|
||||
resumeWatching.map { resume ->
|
||||
val data = context.getKey<VideoDownloadHelper.DownloadHeaderCached>(
|
||||
resumeWatching?.map { resume ->
|
||||
val data = getKey<VideoDownloadHelper.DownloadHeaderCached>(
|
||||
DOWNLOAD_HEADER_CACHE,
|
||||
resume.parentId.toString()
|
||||
) ?: return@map null
|
||||
val watchPos = context.getViewPos(resume.episodeId)
|
||||
val watchPos = getViewPos(resume.episodeId)
|
||||
DataStoreHelper.ResumeWatchingResult(
|
||||
data.name,
|
||||
data.url,
|
||||
|
@ -84,18 +87,19 @@ class HomeViewModel : ViewModel() {
|
|||
resume.season,
|
||||
resume.isFromDownload
|
||||
)
|
||||
}.filterNotNull()
|
||||
}?.filterNotNull()
|
||||
}
|
||||
|
||||
_resumeWatching.postValue(resumeWatchingResult)
|
||||
}
|
||||
|
||||
fun loadStoredData(context: Context, preferredWatchStatus: EnumSet<WatchType>?) = viewModelScope.launch {
|
||||
fun loadStoredData(preferredWatchStatus: EnumSet<WatchType>?) = viewModelScope.launch {
|
||||
val watchStatusIds = withContext(Dispatchers.IO) {
|
||||
context.getAllWatchStateIds().map { id ->
|
||||
Pair(id, context.getResultWatchState(id))
|
||||
getAllWatchStateIds()?.map { id ->
|
||||
Pair(id, getResultWatchState(id))
|
||||
}
|
||||
}.distinctBy { it.first }
|
||||
}?.distinctBy { it.first } ?: return@launch
|
||||
|
||||
val length = WatchType.values().size
|
||||
val currentWatchTypes = EnumSet.noneOf(WatchType::class.java)
|
||||
|
||||
|
@ -125,7 +129,7 @@ class HomeViewModel : ViewModel() {
|
|||
|
||||
val list = withContext(Dispatchers.IO) {
|
||||
watchStatusIds.filter { watchPrefNotNull.contains(it.second) }
|
||||
.mapNotNull { context.getBookmarkedData(it.first) }
|
||||
.mapNotNull { getBookmarkedData(it.first) }
|
||||
.sortedBy { -it.latestUpdatedTime }
|
||||
}
|
||||
_bookmarks.postValue(Pair(true, list))
|
||||
|
@ -176,15 +180,19 @@ class HomeViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
fun loadAndCancel(preferredApiName: String?, currentPrefMedia: Int) = viewModelScope.launch {
|
||||
fun loadAndCancel(preferredApiName: String?) = viewModelScope.launch {
|
||||
val api = getApiFromNameNull(preferredApiName)
|
||||
if (preferredApiName == noneApi.name)
|
||||
loadAndCancel(noneApi)
|
||||
else if (preferredApiName == randomApi.name || api == null) {
|
||||
val validAPIs = AppUtils.filterProviderByPreferredMedia(apis, currentPrefMedia)
|
||||
val validAPIs = context?.filterProviderByPreferredMedia()
|
||||
if(validAPIs.isNullOrEmpty()) {
|
||||
loadAndCancel(noneApi)
|
||||
} else {
|
||||
val apiRandom = validAPIs.random()
|
||||
loadAndCancel(apiRandom)
|
||||
context?.setKey(HOMEPAGE_API, apiRandom.name)
|
||||
setKey(HOMEPAGE_API, apiRandom.name)
|
||||
}
|
||||
} else {
|
||||
loadAndCancel(api)
|
||||
}
|
||||
|
|
|
@ -818,7 +818,6 @@ class PlayerFragment : Fragment() {
|
|||
context?.let { ctx ->
|
||||
//if (this::viewModel.isInitialized) {
|
||||
viewModel.setViewPos(
|
||||
ctx,
|
||||
if (isDownloadedFile) uriData.id else getEpisode()?.id,
|
||||
exoPlayer.currentPosition,
|
||||
exoPlayer.duration
|
||||
|
@ -832,7 +831,7 @@ class PlayerFragment : Fragment() {
|
|||
}*/
|
||||
|
||||
if (isDownloadedFile) {
|
||||
ctx.setLastWatched(
|
||||
setLastWatched(
|
||||
uriData.parentId,
|
||||
uriData.id,
|
||||
uriData.episode,
|
||||
|
@ -840,7 +839,7 @@ class PlayerFragment : Fragment() {
|
|||
true
|
||||
)
|
||||
} else
|
||||
viewModel.reloadEpisodes(ctx)
|
||||
viewModel.reloadEpisodes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1390,9 +1389,7 @@ class PlayerFragment : Fragment() {
|
|||
handlePlayerEvent(PlayerEventType.Play)
|
||||
}
|
||||
|
||||
context?.let { ctx ->
|
||||
setPreferredSubLanguage(ctx.getAutoSelectLanguageISO639_1())
|
||||
}
|
||||
setPreferredSubLanguage(getAutoSelectLanguageISO639_1())
|
||||
|
||||
subView = player_view?.findViewById(R.id.exo_subtitles)
|
||||
subView?.let { sView ->
|
||||
|
@ -1400,7 +1397,7 @@ class PlayerFragment : Fragment() {
|
|||
subtitle_holder.addView(sView)
|
||||
}
|
||||
|
||||
subStyle = context?.getCurrentSavedStyle()!!
|
||||
subStyle = getCurrentSavedStyle()
|
||||
onSubStyleChanged(subStyle)
|
||||
SubtitlesFragment.applyStyleEvent += ::onSubStyleChanged
|
||||
|
||||
|
|
|
@ -124,10 +124,7 @@ class QuickSearchFragment(var isMainApis: Boolean = false) : Fragment() {
|
|||
searchMagIcon.scaleY = 0.65f
|
||||
quick_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
context?.let { ctx ->
|
||||
searchViewModel.searchAndCancel(query = query, context = ctx, isMainApis = isMainApis, ignoreSettings = true)
|
||||
}
|
||||
|
||||
searchViewModel.searchAndCancel(query = query, isMainApis = isMainApis, ignoreSettings = true)
|
||||
quick_search?.let {
|
||||
UIHelper.hideKeyboard(it)
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.result
|
|||
import android.annotation.SuppressLint
|
||||
import android.content.ClipData
|
||||
import android.content.ClipboardManager
|
||||
import android.content.Context
|
||||
import android.content.Context.CLIPBOARD_SERVICE
|
||||
import android.content.Intent
|
||||
import android.content.Intent.*
|
||||
|
@ -60,6 +59,7 @@ import com.lagradost.cloudstream3.utils.*
|
|||
import com.lagradost.cloudstream3.utils.AppUtils.isAppInstalled
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
|
||||
import com.lagradost.cloudstream3.utils.CastHelper.startCast
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getFolderName
|
||||
|
@ -128,7 +128,7 @@ fun ResultEpisode.getDisplayPosition(): Long {
|
|||
return position
|
||||
}
|
||||
|
||||
fun Context.buildResultEpisode(
|
||||
fun buildResultEpisode(
|
||||
name: String?,
|
||||
poster: String?,
|
||||
episode: Int,
|
||||
|
@ -269,7 +269,7 @@ class ResultFragment : Fragment() {
|
|||
private var startValue: Int? = null
|
||||
|
||||
private fun updateSync(id: Int) {
|
||||
val syncList = context?.getSync(id, SyncApis.map { it.idPrefix }) ?: return
|
||||
val syncList = getSync(id, SyncApis.map { it.idPrefix }) ?: return
|
||||
val list = ArrayList<Pair<SyncAPI, String>>()
|
||||
for (i in 0 until SyncApis.count()) {
|
||||
val res = syncList[i] ?: continue
|
||||
|
@ -596,7 +596,7 @@ class ResultFragment : Fragment() {
|
|||
// 1. Checks if the lang should be downloaded
|
||||
// 2. Makes it into the download format
|
||||
// 3. Downloads it as a .vtt file
|
||||
val downloadList = ctx.getDownloadSubsLanguageISO639_1()
|
||||
val downloadList = getDownloadSubsLanguageISO639_1()
|
||||
main {
|
||||
subs?.let { subsList ->
|
||||
subsList.filter {
|
||||
|
@ -857,7 +857,7 @@ class ResultFragment : Fragment() {
|
|||
//.map { watchType -> Triple(watchType.internalId, watchType.iconRes, watchType.stringRes) },
|
||||
) {
|
||||
context?.let { localContext ->
|
||||
viewModel.updateWatchStatus(localContext, WatchType.fromInternalId(this.itemId))
|
||||
viewModel.updateWatchStatus(WatchType.fromInternalId(this.itemId))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -883,7 +883,7 @@ class ResultFragment : Fragment() {
|
|||
fab.context.getString(R.string.action_add_to_bookmarks),
|
||||
showApply = false,
|
||||
{}) {
|
||||
viewModel.updateWatchStatus(fab.context, WatchType.values()[it])
|
||||
viewModel.updateWatchStatus(WatchType.values()[it])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -938,9 +938,8 @@ class ResultFragment : Fragment() {
|
|||
.map { Pair(it ?: -2, fromIndexToSeasonText(it)) },
|
||||
) {
|
||||
val id = this.itemId
|
||||
context?.let {
|
||||
viewModel.changeSeason(it, if (id == -2) null else id)
|
||||
}
|
||||
|
||||
viewModel.changeSeason(if (id == -2) null else id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -981,7 +980,7 @@ class ResultFragment : Fragment() {
|
|||
if (ranges != null) {
|
||||
it.popupMenuNoIconsAndNoStringRes(ranges.map { status -> Pair(status.ordinal, status.toString()) }
|
||||
.toList()) {
|
||||
viewModel.changeDubStatus(requireContext(), DubStatus.values()[itemId])
|
||||
viewModel.changeDubStatus(DubStatus.values()[itemId])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -999,7 +998,7 @@ class ResultFragment : Fragment() {
|
|||
val ranges = episodeRanges
|
||||
if (ranges != null) {
|
||||
it.popupMenuNoIconsAndNoStringRes(ranges.mapIndexed { index, s -> Pair(index, s) }.toList()) {
|
||||
viewModel.changeRange(requireContext(), itemId)
|
||||
viewModel.changeRange(itemId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1087,13 +1086,14 @@ class ResultFragment : Fragment() {
|
|||
updateSync(d.getId())
|
||||
result_add_sync?.setOnClickListener {
|
||||
QuickSearchFragment.pushSync(activity, d.name) { click ->
|
||||
context?.addSync(d.getId(), click.card.apiName, click.card.url)?.let {
|
||||
addSync(d.getId(), click.card.apiName, click.card.url)
|
||||
|
||||
showToast(
|
||||
activity,
|
||||
context?.getString(R.string.added_sync_format)?.format(click.card.name),
|
||||
Toast.LENGTH_SHORT
|
||||
)
|
||||
}
|
||||
|
||||
updateSync(d.getId())
|
||||
}
|
||||
}
|
||||
|
@ -1290,7 +1290,7 @@ class ResultFragment : Fragment() {
|
|||
val tempUrl = url
|
||||
if (tempUrl != null) {
|
||||
result_reload_connectionerror.setOnClickListener {
|
||||
viewModel.load(it.context, tempUrl, apiName, showFillers)
|
||||
viewModel.load(tempUrl, apiName, showFillers)
|
||||
}
|
||||
|
||||
result_reload_connection_open_in_browser?.setOnClickListener {
|
||||
|
@ -1304,18 +1304,12 @@ class ResultFragment : Fragment() {
|
|||
}
|
||||
|
||||
result_meta_site?.setOnClickListener {
|
||||
val i = Intent(ACTION_VIEW)
|
||||
i.data = Uri.parse(tempUrl)
|
||||
try {
|
||||
startActivity(i)
|
||||
} catch (e: Exception) {
|
||||
e.printStackTrace()
|
||||
}
|
||||
it.context?.openBrowser(tempUrl)
|
||||
}
|
||||
|
||||
if (restart || viewModel.resultResponse.value == null) {
|
||||
viewModel.clear()
|
||||
viewModel.load(ctx, tempUrl, apiName, showFillers)
|
||||
viewModel.load(tempUrl, apiName, showFillers)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,23 +8,24 @@ import androidx.lifecycle.viewModelScope
|
|||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.APIHolder.getId
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.mvvm.Resource
|
||||
import com.lagradost.cloudstream3.mvvm.safeApiCall
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
import com.lagradost.cloudstream3.ui.APIRepository
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.utils.*
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.removeLastWatched
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setBookmarkedData
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setDub
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setLastWatched
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
|
||||
import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.getFillerEpisodes
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -49,6 +50,7 @@ class ResultViewModel : ViewModel() {
|
|||
id.value = null
|
||||
selectedSeason.value = -2
|
||||
_dubSubEpisodes.value = null
|
||||
_sync.value = null
|
||||
}
|
||||
|
||||
private var repo: APIRepository? = null
|
||||
|
@ -89,17 +91,17 @@ class ResultViewModel : ViewModel() {
|
|||
private val _sync: MutableLiveData<List<Resource<SyncAPI.SyncResult?>>> = MutableLiveData()
|
||||
val sync: LiveData<List<Resource<SyncAPI.SyncResult?>>> get() = _sync
|
||||
|
||||
fun updateWatchStatus(context: Context, status: WatchType) = viewModelScope.launch {
|
||||
fun updateWatchStatus(status: WatchType) = viewModelScope.launch {
|
||||
val currentId = id.value ?: return@launch
|
||||
_watchStatus.postValue(status)
|
||||
val resultPage = page.value
|
||||
|
||||
withContext(Dispatchers.IO) {
|
||||
context.setResultWatchState(currentId, status.internalId)
|
||||
setResultWatchState(currentId, status.internalId)
|
||||
if (resultPage != null) {
|
||||
val current = context.getBookmarkedData(currentId)
|
||||
val current = getBookmarkedData(currentId)
|
||||
val currentTime = System.currentTimeMillis()
|
||||
context.setBookmarkedData(
|
||||
setBookmarkedData(
|
||||
currentId,
|
||||
DataStoreHelper.BookmarkedData(
|
||||
currentId,
|
||||
|
@ -117,13 +119,13 @@ class ResultViewModel : ViewModel() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadWatchStatus(context: Context, localId: Int? = null) {
|
||||
private fun loadWatchStatus(localId: Int? = null) {
|
||||
val currentId = localId ?: id.value ?: return
|
||||
val currentWatch = context.getResultWatchState(currentId)
|
||||
val currentWatch = getResultWatchState(currentId)
|
||||
_watchStatus.postValue(currentWatch)
|
||||
}
|
||||
|
||||
private fun filterEpisodes(context: Context, list: List<ResultEpisode>?, selection: Int?, range: Int?) {
|
||||
private fun filterEpisodes(list: List<ResultEpisode>?, selection: Int?, range: Int?) {
|
||||
if (list == null) return
|
||||
val seasonTypes = HashMap<Int?, Boolean>()
|
||||
for (i in list) {
|
||||
|
@ -141,7 +143,7 @@ class ResultViewModel : ViewModel() {
|
|||
val realSelection = if (!seasonTypes.containsKey(selection)) seasons.first() else selection
|
||||
val internalId = id.value
|
||||
|
||||
if (internalId != null) context.setResultSeason(internalId, realSelection)
|
||||
if (internalId != null) setResultSeason(internalId, realSelection)
|
||||
|
||||
selectedSeason.postValue(realSelection ?: -2)
|
||||
|
||||
|
@ -187,18 +189,23 @@ class ResultViewModel : ViewModel() {
|
|||
_publicEpisodes.postValue(Resource.Success(currentList))
|
||||
}
|
||||
|
||||
fun changeSeason(context: Context, selection: Int?) {
|
||||
filterEpisodes(context, _episodes.value, selection, null)
|
||||
fun changeSeason(selection: Int?) {
|
||||
filterEpisodes(_episodes.value, selection, null)
|
||||
}
|
||||
|
||||
fun changeRange(context: Context, range: Int?) {
|
||||
filterEpisodes(context, _episodes.value, null, range)
|
||||
fun changeRange(range: Int?) {
|
||||
filterEpisodes(_episodes.value, null, range)
|
||||
}
|
||||
|
||||
fun changeDubStatus(context: Context, status: DubStatus?) {
|
||||
fun changeDubStatus(status: DubStatus?) {
|
||||
dubSubEpisodes.value?.get(status)?.let { episodes ->
|
||||
id.value?.let {
|
||||
if (status != null) {
|
||||
setDub(it, status)
|
||||
}
|
||||
}
|
||||
_dubStatus.postValue(status)
|
||||
updateEpisodes(context, null, episodes, null)
|
||||
updateEpisodes(null, episodes, null)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -207,13 +214,13 @@ class ResultViewModel : ViewModel() {
|
|||
|
||||
val list = ArrayList<Resource<SyncAPI.SyncResult?>>()
|
||||
for (s in sync) {
|
||||
val result = safeApiCall { s.first.getResult(context, s.second) }
|
||||
val result = safeApiCall { s.first.getResult(s.second) }
|
||||
list.add(result)
|
||||
_sync.postValue(list)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateEpisodes(context: Context, localId: Int?, list: List<ResultEpisode>, selection: Int?) {
|
||||
private fun updateEpisodes(localId: Int?, list: List<ResultEpisode>, selection: Int?) {
|
||||
_episodes.postValue(list)
|
||||
val set = HashMap<Int, Int>()
|
||||
|
||||
|
@ -221,25 +228,23 @@ class ResultViewModel : ViewModel() {
|
|||
episodeById.postValue(set)
|
||||
|
||||
filterEpisodes(
|
||||
context,
|
||||
list,
|
||||
if (selection == -1) context.getResultSeason(localId ?: id.value ?: return) else selection, null
|
||||
if (selection == -1) getResultSeason(localId ?: id.value ?: return) else selection, null
|
||||
)
|
||||
}
|
||||
|
||||
fun reloadEpisodes(context: Context) {
|
||||
fun reloadEpisodes() {
|
||||
val current = _episodes.value ?: return
|
||||
val copy = current.map {
|
||||
val posDur = context.getViewPos(it.id)
|
||||
val posDur = getViewPos(it.id)
|
||||
it.copy(position = posDur?.position ?: 0, duration = posDur?.duration ?: 0)
|
||||
}
|
||||
updateEpisodes(context, null, copy, selectedSeason.value)
|
||||
updateEpisodes(null, copy, selectedSeason.value)
|
||||
}
|
||||
|
||||
fun setViewPos(context: Context?, episodeId: Int?, pos: Long, dur: Long) {
|
||||
fun setViewPos(episodeId: Int?, pos: Long, dur: Long) {
|
||||
try {
|
||||
if (context == null || episodeId == null) return
|
||||
context.setViewPos(episodeId, pos, dur)
|
||||
DataStoreHelper.setViewPos(episodeId, pos, dur)
|
||||
var index = episodeById.value?.get(episodeId) ?: return
|
||||
|
||||
var startPos = pos
|
||||
|
@ -251,7 +256,7 @@ class ResultViewModel : ViewModel() {
|
|||
if (startDur > 0L && (startPos * 100 / startDur) > 95) {
|
||||
index++
|
||||
if (episodeList.size <= index) { // last episode
|
||||
context.removeLastWatched(parentId)
|
||||
removeLastWatched(parentId)
|
||||
return
|
||||
}
|
||||
episode = episodeList[index]
|
||||
|
@ -261,7 +266,7 @@ class ResultViewModel : ViewModel() {
|
|||
|
||||
continue
|
||||
} else {
|
||||
context.setLastWatched(parentId, episode.id, episode.episode, episode.season)
|
||||
setLastWatched(parentId, episode.id, episode.episode, episode.season)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -279,7 +284,7 @@ class ResultViewModel : ViewModel() {
|
|||
return name
|
||||
}
|
||||
|
||||
fun load(context: Context, url: String, apiName: String, showFillers: Boolean) = viewModelScope.launch {
|
||||
fun load(url: String, apiName: String, showFillers: Boolean) = viewModelScope.launch {
|
||||
_resultResponse.postValue(Resource.Loading(url))
|
||||
_publicEpisodes.postValue(Resource.Loading())
|
||||
|
||||
|
@ -301,9 +306,9 @@ class ResultViewModel : ViewModel() {
|
|||
page.postValue(d)
|
||||
val mainId = d.getId()
|
||||
id.postValue(mainId)
|
||||
loadWatchStatus(context, mainId)
|
||||
loadWatchStatus(mainId)
|
||||
|
||||
context.setKey(
|
||||
setKey(
|
||||
DOWNLOAD_HEADER_CACHE,
|
||||
mainId.toString(),
|
||||
VideoDownloadHelper.DownloadHeaderCached(
|
||||
|
@ -319,11 +324,14 @@ class ResultViewModel : ViewModel() {
|
|||
|
||||
when (d) {
|
||||
is AnimeLoadResponse -> {
|
||||
//TODO context.getKey<>() isdub
|
||||
if (d.episodes.isEmpty()) {
|
||||
_dubSubEpisodes.postValue(emptyMap())
|
||||
return@launch
|
||||
}
|
||||
|
||||
val isDub =
|
||||
d.episodes.containsKey(DubStatus.Dubbed) && !d.episodes[DubStatus.Dubbed].isNullOrEmpty()
|
||||
val dubStatus = if (isDub) DubStatus.Dubbed else DubStatus.Subbed
|
||||
val status = getDub(mainId)
|
||||
val statuses = d.episodes.map { it.key }
|
||||
val dubStatus = if (statuses.contains(status)) status else statuses.first()
|
||||
_dubStatus.postValue(dubStatus)
|
||||
|
||||
_dubSubSelections.postValue(d.episodes.keys)
|
||||
|
@ -335,8 +343,7 @@ class ResultViewModel : ViewModel() {
|
|||
for ((index, i) in ep.value.withIndex()) {
|
||||
|
||||
val episode = i.episode ?: (index + 1)
|
||||
episodes.add(
|
||||
context.buildResultEpisode(
|
||||
episodes.add(buildResultEpisode(
|
||||
filterName(i.name),
|
||||
i.posterUrl,
|
||||
episode,
|
||||
|
@ -350,8 +357,7 @@ class ResultViewModel : ViewModel() {
|
|||
if (fillerEpisodes is Resource.Success) fillerEpisodes.value?.let {
|
||||
it.contains(episode) && it[episode] == true
|
||||
} ?: false else false,
|
||||
)
|
||||
)
|
||||
))
|
||||
}
|
||||
idIndex++
|
||||
|
||||
|
@ -360,7 +366,7 @@ class ResultViewModel : ViewModel() {
|
|||
|
||||
_dubSubEpisodes.postValue(res)
|
||||
res[dubStatus]?.let { episodes ->
|
||||
updateEpisodes(context, mainId, episodes, -1)
|
||||
updateEpisodes(mainId, episodes, -1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -368,7 +374,7 @@ class ResultViewModel : ViewModel() {
|
|||
val episodes = ArrayList<ResultEpisode>()
|
||||
for ((index, i) in d.episodes.withIndex()) {
|
||||
episodes.add(
|
||||
context.buildResultEpisode(
|
||||
buildResultEpisode(
|
||||
filterName(i.name),
|
||||
i.posterUrl,
|
||||
i.episode ?: (index + 1),
|
||||
|
@ -382,13 +388,12 @@ class ResultViewModel : ViewModel() {
|
|||
null,
|
||||
)
|
||||
)
|
||||
|
||||
}
|
||||
updateEpisodes(context, mainId, episodes, -1)
|
||||
updateEpisodes(mainId, episodes, -1)
|
||||
}
|
||||
is MovieLoadResponse -> {
|
||||
updateEpisodes(
|
||||
context, mainId, arrayListOf(
|
||||
context.buildResultEpisode(
|
||||
buildResultEpisode(
|
||||
d.name,
|
||||
null,
|
||||
0,
|
||||
|
@ -400,14 +405,14 @@ class ResultViewModel : ViewModel() {
|
|||
null,
|
||||
null,
|
||||
null,
|
||||
)
|
||||
), -1
|
||||
)
|
||||
).let {
|
||||
updateEpisodes(mainId, listOf(it), -1)
|
||||
}
|
||||
}
|
||||
is TorrentLoadResponse -> {
|
||||
updateEpisodes(
|
||||
context, mainId, arrayListOf(
|
||||
context.buildResultEpisode(
|
||||
mainId, listOf(
|
||||
buildResultEpisode(
|
||||
d.name,
|
||||
null,
|
||||
0,
|
||||
|
|
|
@ -48,7 +48,9 @@ class SearchFragment : Fragment() {
|
|||
fun List<SearchResponse>.filterSearchResponse(): List<SearchResponse> {
|
||||
return this.filter { response ->
|
||||
if (response is AnimeSearchResponse) {
|
||||
(response.dubStatus.isNullOrEmpty()) || (response.dubStatus.any { APIRepository.dubStatusActive.contains(it) })
|
||||
(response.dubStatus.isNullOrEmpty()) || (response.dubStatus.any {
|
||||
APIRepository.dubStatusActive.contains(it)
|
||||
})
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
@ -298,9 +300,7 @@ class SearchFragment : Fragment() {
|
|||
|
||||
main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
|
||||
override fun onQueryTextSubmit(query: String): Boolean {
|
||||
context?.let { ctx ->
|
||||
searchViewModel.searchAndCancel(query = query, context = ctx)
|
||||
}
|
||||
searchViewModel.searchAndCancel(query = query)
|
||||
|
||||
main_search?.let {
|
||||
hideKeyboard(it)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.lagradost.cloudstream3.ui.search
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
@ -41,9 +40,9 @@ class SearchViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
var onGoingSearch: Job? = null
|
||||
fun searchAndCancel(query: String, isMainApis : Boolean = true, ignoreSettings : Boolean = false, context: Context) {
|
||||
fun searchAndCancel(query: String, isMainApis: Boolean = true, ignoreSettings: Boolean = false) {
|
||||
onGoingSearch?.cancel()
|
||||
onGoingSearch = search(query, isMainApis, ignoreSettings, context)
|
||||
onGoingSearch = search(query, isMainApis, ignoreSettings)
|
||||
}
|
||||
|
||||
data class SyncSearchResultSearchResponse(
|
||||
|
@ -66,7 +65,8 @@ class SearchViewModel : ViewModel() {
|
|||
)
|
||||
}
|
||||
|
||||
private fun search(query: String, isMainApis : Boolean = true, ignoreSettings : Boolean = false, context: Context) = viewModelScope.launch {
|
||||
private fun search(query: String, isMainApis: Boolean = true, ignoreSettings: Boolean = false) =
|
||||
viewModelScope.launch {
|
||||
if (query.length <= 1) {
|
||||
clearSearch()
|
||||
return@launch
|
||||
|
@ -90,7 +90,8 @@ class SearchViewModel : ViewModel() {
|
|||
} else {
|
||||
syncApis.apmap { a ->
|
||||
val search = safeApiCall {
|
||||
a.search(context, query)?.map { it.toSearchResponse() } ?: throw ErrorLoadingException()
|
||||
a.search(query)?.map { it.toSearchResponse() }
|
||||
?: throw ErrorLoadingException()
|
||||
}
|
||||
|
||||
currentList.add(OnGoingSearch(a.name, search))
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.widget.ImageView
|
||||
|
@ -24,6 +25,7 @@ import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings
|
|||
import com.lagradost.cloudstream3.APIHolder.getApiSettings
|
||||
import com.lagradost.cloudstream3.APIHolder.restrictedApis
|
||||
import com.lagradost.cloudstream3.AcraApplication
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.DubStatus
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.setLocale
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.showToast
|
||||
|
@ -36,8 +38,6 @@ import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.aniListApi
|
|||
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.malApi
|
||||
import com.lagradost.cloudstream3.ui.APIRepository
|
||||
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.HOMEPAGE_API
|
||||
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
|
@ -50,7 +50,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
|
|||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadDir
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.isScopedStorage
|
||||
import java.io.File
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
|
@ -126,13 +125,14 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top
|
||||
|
||||
private fun showAccountSwitch(context: Context, api: AccountManager) {
|
||||
val accounts = api.getAccounts() ?: return
|
||||
|
||||
val builder =
|
||||
AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_switch)
|
||||
val dialog = builder.show()
|
||||
|
||||
val accounts = api.getAccounts(context)
|
||||
dialog.findViewById<TextView>(R.id.account_add)?.setOnClickListener {
|
||||
api.authenticate(it.context)
|
||||
api.authenticate()
|
||||
}
|
||||
|
||||
val ogIndex = api.accountIndex
|
||||
|
@ -141,7 +141,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
|
||||
for (index in accounts) {
|
||||
api.accountIndex = index
|
||||
val accountInfo = api.loginInfo(context)
|
||||
val accountInfo = api.loginInfo()
|
||||
if (accountInfo != null) {
|
||||
items.add(accountInfo)
|
||||
}
|
||||
|
@ -149,26 +149,26 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
api.accountIndex = ogIndex
|
||||
val adapter = AccountAdapter(items, R.layout.account_single) {
|
||||
dialog?.dismissSafe(activity)
|
||||
api.changeAccount(it.view.context, it.card.accountIndex)
|
||||
api.changeAccount(it.card.accountIndex)
|
||||
}
|
||||
val list = dialog.findViewById<RecyclerView>(R.id.account_list)
|
||||
list?.adapter = adapter
|
||||
}
|
||||
|
||||
private fun showLoginInfo(context: Context, api: AccountManager, info: OAuth2API.LoginInfo) {
|
||||
private fun showLoginInfo(api: AccountManager, info: OAuth2API.LoginInfo) {
|
||||
val builder =
|
||||
AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_managment)
|
||||
AlertDialog.Builder(context ?: return, R.style.AlertDialogCustom).setView(R.layout.account_managment)
|
||||
val dialog = builder.show()
|
||||
|
||||
dialog.findViewById<ImageView>(R.id.account_profile_picture)?.setImage(info.profilePicture)
|
||||
dialog.findViewById<TextView>(R.id.account_logout)?.setOnClickListener {
|
||||
it.context?.let { ctx ->
|
||||
api.logOut(ctx)
|
||||
api.logOut()
|
||||
dialog.dismissSafe(activity)
|
||||
}
|
||||
}
|
||||
|
||||
dialog.findViewById<TextView>(R.id.account_name)?.text = info.name ?: context.getString(R.string.no_data)
|
||||
(info.name ?: context?.getString(R.string.no_data))?.let {
|
||||
dialog.findViewById<TextView>(R.id.account_name)?.text = it
|
||||
}
|
||||
dialog.findViewById<TextView>(R.id.account_site)?.text = api.name
|
||||
dialog.findViewById<TextView>(R.id.account_switch_account)?.setOnClickListener {
|
||||
dialog.dismissSafe(activity)
|
||||
|
@ -208,11 +208,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
title = getString(R.string.login_format).format(api.name, getString(R.string.account))
|
||||
setOnPreferenceClickListener { pref ->
|
||||
pref.context?.let { ctx ->
|
||||
val info = api.loginInfo(ctx)
|
||||
val info = api.loginInfo()
|
||||
if (info != null) {
|
||||
showLoginInfo(ctx, api, info)
|
||||
showLoginInfo(api, info)
|
||||
} else {
|
||||
api.authenticate(ctx)
|
||||
api.authenticate()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -305,7 +305,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
|
||||
// app_name_download_path = Cloudstream and does not change depending on release.
|
||||
// DOES NOT WORK ON SCOPED STORAGE.
|
||||
val secondaryDir = if (isScopedStorage) null else Environment.getExternalStorageDirectory().absolutePath +
|
||||
val secondaryDir = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) null else Environment.getExternalStorageDirectory().absolutePath +
|
||||
File.separator + resources.getString(R.string.app_name_download_path)
|
||||
val first = listOf(defaultDir, secondaryDir)
|
||||
return (try {
|
||||
|
@ -352,7 +352,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
|
||||
|
||||
val currentPrefMedia =
|
||||
settingsManager.getInt(getString(R.string.preferred_media_settings), 0)
|
||||
settingsManager.getInt(getString(R.string.prefer_media_type_key), 0)
|
||||
|
||||
activity?.showBottomDialog(
|
||||
prefNames.toList(),
|
||||
|
@ -361,15 +361,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
|||
true,
|
||||
{}) {
|
||||
settingsManager.edit()
|
||||
.putInt(getString(R.string.preferred_media_settings), prefValues[it])
|
||||
.putInt(getString(R.string.prefer_media_type_key), prefValues[it])
|
||||
.apply()
|
||||
val apilist = AppUtils.filterProviderByPreferredMedia(apis, prefValues[it])
|
||||
val apiRandom = if (apilist.size > 0) {
|
||||
apilist.random().name
|
||||
} else {
|
||||
""
|
||||
}
|
||||
context?.setKey(HOMEPAGE_API, apiRandom)
|
||||
|
||||
removeKey(HOMEPAGE_API)
|
||||
(context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) }
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
|
|
|
@ -19,10 +19,11 @@ import androidx.fragment.app.Fragment
|
|||
import com.google.android.exoplayer2.text.Cue
|
||||
import com.google.android.exoplayer2.ui.CaptionStyleCompat
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.MainActivity
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.showToast
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.Event
|
||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
|
||||
|
@ -87,8 +88,8 @@ class SubtitlesFragment : Fragment() {
|
|||
this.setKey(SUBTITLE_KEY, style)
|
||||
}
|
||||
|
||||
fun Context.getCurrentSavedStyle(): SaveCaptionStyle {
|
||||
return this.getKey(SUBTITLE_KEY) ?: SaveCaptionStyle(
|
||||
fun getCurrentSavedStyle(): SaveCaptionStyle {
|
||||
return getKey(SUBTITLE_KEY) ?: SaveCaptionStyle(
|
||||
getDefColor(0),
|
||||
getDefColor(2),
|
||||
getDefColor(3),
|
||||
|
@ -109,11 +110,11 @@ class SubtitlesFragment : Fragment() {
|
|||
return TypedValue.applyDimension(unit, size, metrics).toInt()
|
||||
}
|
||||
|
||||
fun Context.getDownloadSubsLanguageISO639_1(): List<String> {
|
||||
fun getDownloadSubsLanguageISO639_1(): List<String> {
|
||||
return getKey(SUBTITLE_DOWNLOAD_KEY) ?: listOf("en")
|
||||
}
|
||||
|
||||
fun Context.getAutoSelectLanguageISO639_1(): String {
|
||||
fun getAutoSelectLanguageISO639_1(): String {
|
||||
return getKey(SUBTITLE_AUTO_SELECT_KEY) ?: "en"
|
||||
}
|
||||
}
|
||||
|
@ -184,7 +185,7 @@ class SubtitlesFragment : Fragment() {
|
|||
|
||||
context?.fixPaddingStatusbar(subs_root)
|
||||
|
||||
state = requireContext().getCurrentSavedStyle()
|
||||
state = getCurrentSavedStyle()
|
||||
context?.updateState()
|
||||
|
||||
fun View.setup(id: Int) {
|
||||
|
@ -381,17 +382,17 @@ class SubtitlesFragment : Fragment() {
|
|||
val lang639_1 = langMap.map { it.ISO_639_1 }
|
||||
activity?.showDialog(
|
||||
langMap.map { it.languageName },
|
||||
lang639_1.indexOf(textView.context.getAutoSelectLanguageISO639_1()),
|
||||
lang639_1.indexOf(getAutoSelectLanguageISO639_1()),
|
||||
(textView as TextView).text.toString(),
|
||||
true,
|
||||
dismissCallback
|
||||
) { index ->
|
||||
textView.context.setKey(SUBTITLE_AUTO_SELECT_KEY, lang639_1[index])
|
||||
setKey(SUBTITLE_AUTO_SELECT_KEY, lang639_1[index])
|
||||
}
|
||||
}
|
||||
|
||||
subs_auto_select_language.setOnLongClickListener { textView ->
|
||||
textView.context.setKey(SUBTITLE_AUTO_SELECT_KEY, "en")
|
||||
subs_auto_select_language.setOnLongClickListener {
|
||||
setKey(SUBTITLE_AUTO_SELECT_KEY, "en")
|
||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
|
@ -399,7 +400,7 @@ class SubtitlesFragment : Fragment() {
|
|||
subs_download_languages.setOnClickListener { textView ->
|
||||
val langMap = SubtitleHelper.languages
|
||||
val lang639_1 = langMap.map { it.ISO_639_1 }
|
||||
val keys = textView.context.getDownloadSubsLanguageISO639_1()
|
||||
val keys = getDownloadSubsLanguageISO639_1()
|
||||
val keyMap = keys.map { lang639_1.indexOf(it) }.filter { it >= 0 }
|
||||
|
||||
activity?.showMultiDialog(
|
||||
|
@ -408,12 +409,12 @@ class SubtitlesFragment : Fragment() {
|
|||
(textView as TextView).text.toString(),
|
||||
dismissCallback
|
||||
) { indexList ->
|
||||
textView.context.setKey(SUBTITLE_DOWNLOAD_KEY, indexList.map { lang639_1[it] }.toList())
|
||||
setKey(SUBTITLE_DOWNLOAD_KEY, indexList.map { lang639_1[it] }.toList())
|
||||
}
|
||||
}
|
||||
|
||||
subs_download_languages.setOnLongClickListener { textView ->
|
||||
textView.context.setKey(SUBTITLE_DOWNLOAD_KEY, listOf("en"))
|
||||
subs_download_languages.setOnLongClickListener {
|
||||
setKey(SUBTITLE_DOWNLOAD_KEY, listOf("en"))
|
||||
|
||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||
return@setOnLongClickListener true
|
||||
|
|
|
@ -21,6 +21,7 @@ import com.google.android.gms.common.ConnectionResult
|
|||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.gms.common.wrappers.Wrappers
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.ui.result.ResultFragment
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||
import java.net.URL
|
||||
|
@ -46,6 +47,7 @@ object AppUtils {
|
|||
}
|
||||
|
||||
fun Context.openBrowser(url: String) {
|
||||
try {
|
||||
val components = arrayOf(ComponentName(applicationContext, MainActivity::class.java))
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(url)
|
||||
|
@ -57,6 +59,9 @@ object AppUtils {
|
|||
)
|
||||
else
|
||||
startActivity(intent)
|
||||
} catch (e : Exception) {
|
||||
logError(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun splitQuery(url: URL): Map<String, String> {
|
||||
|
@ -233,23 +238,4 @@ object AppUtils {
|
|||
}
|
||||
return currentAudioFocusRequest
|
||||
}
|
||||
|
||||
fun filterProviderByPreferredMedia(
|
||||
apis: ArrayList<MainAPI>,
|
||||
currentPrefMedia: Int
|
||||
): List<MainAPI> {
|
||||
val allApis = apis.filter { api -> api.hasMainPage }
|
||||
return if (currentPrefMedia < 1) {
|
||||
allApis
|
||||
} else {
|
||||
// Filter API depending on preferred media type
|
||||
val listEnumAnime = listOf(TvType.Anime, TvType.AnimeMovie, TvType.ONA)
|
||||
val listEnumMovieTv = listOf(TvType.Movie, TvType.TvSeries, TvType.Cartoon)
|
||||
val mediaTypeList = if (currentPrefMedia == 1) listEnumMovieTv else listEnumAnime
|
||||
|
||||
val filteredAPI =
|
||||
allApis.filter { api -> api.supportedTypes.any { it in mediaTypeList } }
|
||||
filteredAPI
|
||||
}
|
||||
}
|
||||
}
|
|
@ -40,7 +40,9 @@ object CastHelper {
|
|||
(epData.name ?: "Episode ${epData.episode}") + " - ${link.name}"
|
||||
)
|
||||
|
||||
movieMetadata.putString(MediaMetadata.KEY_TITLE, holder.title)
|
||||
holder.title?.let {
|
||||
movieMetadata.putString(MediaMetadata.KEY_TITLE, it)
|
||||
}
|
||||
|
||||
val srcPoster = epData.poster ?: holder.poster
|
||||
if (srcPoster != null) {
|
||||
|
@ -58,7 +60,7 @@ object CastHelper {
|
|||
|
||||
val builder = MediaInfo.Builder(link.url)
|
||||
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
|
||||
.setContentType(MimeTypes.VIDEO_UNKNOWN)
|
||||
.setContentType(if (link.isM3u8) MimeTypes.APPLICATION_M3U8 else MimeTypes.VIDEO_MP4)
|
||||
.setMetadata(movieMetadata)
|
||||
.setMediaTracks(tracks)
|
||||
data?.let {
|
||||
|
@ -111,7 +113,9 @@ object CastHelper {
|
|||
getMediaInfo(epData, holder, index, JSONObject(mapper.writeValueAsString(holder)), subtitles)
|
||||
|
||||
awaitLinks(
|
||||
this.remoteMediaClient?.load(MediaLoadRequestData.Builder().setMediaInfo(mediaItem).setCurrentTime(startTime ?: 0L).build() )
|
||||
this.remoteMediaClient?.load(
|
||||
MediaLoadRequestData.Builder().setMediaInfo(mediaItem).setCurrentTime(startTime ?: 0L).build()
|
||||
)
|
||||
) {
|
||||
if (currentLinks.size > index + 1)
|
||||
startCast(
|
||||
|
|
|
@ -1,19 +1,20 @@
|
|||
package com.lagradost.cloudstream3.utils
|
||||
|
||||
import android.content.Context
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.DubStatus
|
||||
import com.lagradost.cloudstream3.SearchResponse
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.ui.WatchType
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKeys
|
||||
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
|
||||
const val VIDEO_POS_DUR = "video_pos_dur"
|
||||
const val RESULT_WATCH_STATE = "result_watch_state"
|
||||
const val RESULT_WATCH_STATE_DATA = "result_watch_state_data"
|
||||
const val RESULT_RESUME_WATCHING = "result_resume_watching"
|
||||
const val RESULT_SEASON = "result_season"
|
||||
const val RESULT_DUB = "result_dub"
|
||||
|
||||
object DataStoreHelper {
|
||||
data class PosDur(val position: Long, val duration: Long)
|
||||
|
@ -57,21 +58,21 @@ object DataStoreHelper {
|
|||
|
||||
var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION
|
||||
|
||||
fun Context.getAllWatchStateIds(): List<Int> {
|
||||
fun getAllWatchStateIds(): List<Int>? {
|
||||
val folder = "$currentAccount/$RESULT_WATCH_STATE"
|
||||
return getKeys(folder).mapNotNull {
|
||||
return getKeys(folder)?.mapNotNull {
|
||||
it.removePrefix("$folder/").toIntOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.getAllResumeStateIds(): List<Int> {
|
||||
fun getAllResumeStateIds(): List<Int>? {
|
||||
val folder = "$currentAccount/$RESULT_RESUME_WATCHING"
|
||||
return getKeys(folder).mapNotNull {
|
||||
return getKeys(folder)?.mapNotNull {
|
||||
it.removePrefix("$folder/").toIntOrNull()
|
||||
}
|
||||
}
|
||||
|
||||
fun Context.setLastWatched(
|
||||
fun setLastWatched(
|
||||
parentId: Int?,
|
||||
episodeId: Int?,
|
||||
episode: Int?,
|
||||
|
@ -93,12 +94,12 @@ object DataStoreHelper {
|
|||
)
|
||||
}
|
||||
|
||||
fun Context.removeLastWatched(parentId: Int?) {
|
||||
fun removeLastWatched(parentId: Int?) {
|
||||
if (parentId == null) return
|
||||
removeKey("$currentAccount/$RESULT_RESUME_WATCHING", parentId.toString())
|
||||
}
|
||||
|
||||
fun Context.getLastWatched(id: Int?): VideoDownloadHelper.ResumeWatching? {
|
||||
fun getLastWatched(id: Int?): VideoDownloadHelper.ResumeWatching? {
|
||||
if (id == null) return null
|
||||
return getKey(
|
||||
"$currentAccount/$RESULT_RESUME_WATCHING",
|
||||
|
@ -106,27 +107,35 @@ object DataStoreHelper {
|
|||
)
|
||||
}
|
||||
|
||||
fun Context.setBookmarkedData(id: Int?, data: BookmarkedData) {
|
||||
fun setBookmarkedData(id: Int?, data: BookmarkedData) {
|
||||
if (id == null) return
|
||||
setKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString(), data)
|
||||
}
|
||||
|
||||
fun Context.getBookmarkedData(id: Int?): BookmarkedData? {
|
||||
fun getBookmarkedData(id: Int?): BookmarkedData? {
|
||||
if (id == null) return null
|
||||
return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString())
|
||||
}
|
||||
|
||||
fun Context.setViewPos(id: Int?, pos: Long, dur: Long) {
|
||||
fun setViewPos(id: Int?, pos: Long, dur: Long) {
|
||||
if (id == null) return
|
||||
if (dur < 10_000) return // too short
|
||||
setKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), PosDur(pos, dur))
|
||||
}
|
||||
|
||||
fun Context.getViewPos(id: Int): PosDur? {
|
||||
fun getViewPos(id: Int): PosDur? {
|
||||
return getKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), null)
|
||||
}
|
||||
|
||||
fun Context.setResultWatchState(id: Int?, status: Int) {
|
||||
fun getDub(id: Int): DubStatus {
|
||||
return DubStatus.values()[getKey("$currentAccount/$RESULT_DUB", id.toString()) ?: 0]
|
||||
}
|
||||
|
||||
fun setDub(id: Int, status: DubStatus) {
|
||||
setKey("$currentAccount/$RESULT_DUB", id.toString(), status.ordinal)
|
||||
}
|
||||
|
||||
fun setResultWatchState(id: Int?, status: Int) {
|
||||
if (id == null) return
|
||||
val folder = "$currentAccount/$RESULT_WATCH_STATE"
|
||||
if (status == WatchType.NONE.internalId) {
|
||||
|
@ -137,23 +146,23 @@ object DataStoreHelper {
|
|||
}
|
||||
}
|
||||
|
||||
fun Context.getResultWatchState(id: Int): WatchType {
|
||||
fun getResultWatchState(id: Int): WatchType {
|
||||
return WatchType.fromInternalId(getKey<Int>("$currentAccount/$RESULT_WATCH_STATE", id.toString(), null))
|
||||
}
|
||||
|
||||
fun Context.getResultSeason(id: Int): Int {
|
||||
return getKey("$currentAccount/$RESULT_SEASON", id.toString(), -1)!!
|
||||
fun getResultSeason(id: Int): Int {
|
||||
return getKey("$currentAccount/$RESULT_SEASON", id.toString()) ?: -1
|
||||
}
|
||||
|
||||
fun Context.setResultSeason(id: Int, value: Int?) {
|
||||
fun setResultSeason(id: Int, value: Int?) {
|
||||
setKey("$currentAccount/$RESULT_SEASON", id.toString(), value)
|
||||
}
|
||||
|
||||
fun Context.addSync(id: Int, idPrefix: String, url: String) {
|
||||
fun addSync(id: Int, idPrefix: String, url: String) {
|
||||
setKey("${idPrefix}_sync", id.toString(), url)
|
||||
}
|
||||
|
||||
fun Context.getSync(id : Int, idPrefixes : List<String>) : List<String?> {
|
||||
fun getSync(id: Int, idPrefixes: List<String>): List<String?> {
|
||||
return idPrefixes.map { idPrefix ->
|
||||
getKey("${idPrefix}_sync", id.toString())
|
||||
}
|
||||
|
|
|
@ -5,10 +5,10 @@ import android.content.Context
|
|||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ForegroundInfo
|
||||
import androidx.work.WorkerParameters
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.WORK_KEY_INFO
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.WORK_KEY_PACKAGE
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadCheck
|
||||
|
@ -62,8 +62,8 @@ class DownloadFileWorkManager(val context: Context, private val workerParams: Wo
|
|||
}
|
||||
|
||||
private fun removeKeys(key: String) {
|
||||
applicationContext.removeKey(WORK_KEY_INFO, key)
|
||||
applicationContext.removeKey(WORK_KEY_PACKAGE, key)
|
||||
removeKey(WORK_KEY_INFO, key)
|
||||
removeKey(WORK_KEY_PACKAGE, key)
|
||||
}
|
||||
|
||||
private suspend fun awaitDownload(id: Int) {
|
||||
|
|
|
@ -22,6 +22,8 @@ import androidx.work.OneTimeWorkRequest
|
|||
import androidx.work.WorkManager
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.hippo.unifile.UniFile
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
import com.lagradost.cloudstream3.MainActivity
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
|
@ -30,7 +32,6 @@ import com.lagradost.cloudstream3.services.VideoDownloadService
|
|||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.removeKey
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
|
@ -150,16 +151,16 @@ object VideoDownloadManager {
|
|||
private const val SUCCESS_DOWNLOAD_DONE = 1
|
||||
private const val SUCCESS_STREAM = 3
|
||||
private const val SUCCESS_STOPPED = 2
|
||||
private const val ERROR_DELETING_FILE =
|
||||
3 // will not download the next one, but is still classified as an error
|
||||
// will not download the next one, but is still classified as an error
|
||||
private const val ERROR_DELETING_FILE = 3
|
||||
private const val ERROR_CREATE_FILE = -2
|
||||
private const val ERROR_UNKNOWN = -10
|
||||
private const val ERROR_OPEN_FILE = -3
|
||||
//private const val ERROR_OPEN_FILE = -3
|
||||
private const val ERROR_TOO_SMALL_CONNECTION = -4
|
||||
private const val ERROR_WRONG_CONTENT = -5
|
||||
//private const val ERROR_WRONG_CONTENT = -5
|
||||
private const val ERROR_CONNECTION_ERROR = -6
|
||||
private const val ERROR_MEDIA_STORE_URI_CANT_BE_CREATED = -7
|
||||
private const val ERROR_CONTENT_RESOLVER_CANT_OPEN_STREAM = -8
|
||||
//private const val ERROR_MEDIA_STORE_URI_CANT_BE_CREATED = -7
|
||||
//private const val ERROR_CONTENT_RESOLVER_CANT_OPEN_STREAM = -8
|
||||
private const val ERROR_CONTENT_RESOLVER_NOT_FOUND = -9
|
||||
|
||||
private const val KEY_RESUME_PACKAGES = "download_resume"
|
||||
|
@ -460,7 +461,7 @@ object VideoDownloadManager {
|
|||
val base = basePathToFile(context, basePath)
|
||||
val folder = base?.gotoDir(relativePath, false)
|
||||
|
||||
if (isScopedStorage && base.isDownloadDir()) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && base.isDownloadDir()) {
|
||||
return context.contentResolver?.getExistingFolderStartName(relativePath)
|
||||
} else {
|
||||
// val normalPath =
|
||||
|
@ -529,8 +530,6 @@ object VideoDownloadManager {
|
|||
}
|
||||
}
|
||||
|
||||
val isScopedStorage = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
|
||||
|
||||
data class CreateNotificationMetadata(
|
||||
val type: DownloadType,
|
||||
val bytesDownloaded: Long,
|
||||
|
@ -561,7 +560,7 @@ object VideoDownloadManager {
|
|||
var resume = tryResume
|
||||
val baseFile = context.getBasePath()
|
||||
|
||||
if (isScopedStorage && baseFile.first?.isDownloadDir() == true) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && baseFile.first?.isDownloadDir() == true) {
|
||||
val cr = context.contentResolver ?: return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND)
|
||||
|
||||
val currentExistingFile =
|
||||
|
@ -658,7 +657,7 @@ object VideoDownloadManager {
|
|||
|
||||
val displayName = getDisplayName(name, extension)
|
||||
val relativePath =
|
||||
if (isScopedStorage && basePath.first.isDownloadDir()) getRelativePath(folder) else folder
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && basePath.first.isDownloadDir()) getRelativePath(folder) else folder
|
||||
|
||||
fun deleteFile(): Int {
|
||||
return delete(context, name, relativePath, extension, parentId, basePath.first)
|
||||
|
@ -720,7 +719,7 @@ object VideoDownloadManager {
|
|||
if (extension == "mp4" && bytesTotal < 5000000) return ERROR_TOO_SMALL_CONNECTION // DATA IS LESS THAN 5MB, SOMETHING IS WRONG
|
||||
|
||||
parentId?.let {
|
||||
context.setKey(
|
||||
setKey(
|
||||
KEY_DOWNLOAD_INFO,
|
||||
it.toString(),
|
||||
DownloadedFileInfo(
|
||||
|
@ -798,8 +797,8 @@ object VideoDownloadManager {
|
|||
}
|
||||
DownloadActionType.Stop -> {
|
||||
isStopped = true; updateNotification()
|
||||
context.removeKey(KEY_RESUME_PACKAGES, event.first.toString())
|
||||
saveQueue(context)
|
||||
removeKey(KEY_RESUME_PACKAGES, event.first.toString())
|
||||
saveQueue()
|
||||
}
|
||||
DownloadActionType.Resume -> {
|
||||
isPaused = false; updateNotification()
|
||||
|
@ -1025,7 +1024,7 @@ object VideoDownloadManager {
|
|||
val displayName = getDisplayName(name, extension)
|
||||
|
||||
// If scoped storage and using download dir (not accessible with UniFile)
|
||||
if (isScopedStorage && basePath.isDownloadDir()) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && basePath.isDownloadDir()) {
|
||||
val relativePath = getRelativePath(folder)
|
||||
val lastContent =
|
||||
context.contentResolver.getExistingDownloadUriOrNullQ(relativePath, displayName)
|
||||
|
@ -1081,7 +1080,7 @@ object VideoDownloadManager {
|
|||
val basePath = context.getBasePath()
|
||||
|
||||
val relativePath =
|
||||
if (isScopedStorage && basePath.first.isDownloadDir()) getRelativePath(folder) else folder
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && basePath.first.isDownloadDir()) getRelativePath(folder) else folder
|
||||
|
||||
val stream = setupStream(context, name, relativePath, extension, realIndex > 0)
|
||||
if (stream.errorCode != SUCCESS_STREAM) return stream.errorCode
|
||||
|
@ -1119,7 +1118,7 @@ object VideoDownloadManager {
|
|||
|
||||
fun updateInfo() {
|
||||
parentId?.let {
|
||||
context.setKey(
|
||||
setKey(
|
||||
KEY_DOWNLOAD_INFO,
|
||||
it.toString(),
|
||||
DownloadedFileInfo(
|
||||
|
@ -1364,7 +1363,7 @@ object VideoDownloadManager {
|
|||
val link = item.links[index]
|
||||
val resume = pkg.linkIndex == index
|
||||
|
||||
context.setKey(
|
||||
setKey(
|
||||
KEY_RESUME_PACKAGES,
|
||||
id.toString(),
|
||||
DownloadResumePackage(item, index)
|
||||
|
@ -1383,7 +1382,7 @@ object VideoDownloadManager {
|
|||
}
|
||||
}
|
||||
if (connectionResult != null && connectionResult > 0) { // SUCCESS
|
||||
context.removeKey(KEY_RESUME_PACKAGES, id.toString())
|
||||
removeKey(KEY_RESUME_PACKAGES, id.toString())
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -1410,7 +1409,7 @@ object VideoDownloadManager {
|
|||
context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, id.toString()) ?: return null
|
||||
val base = basePathToFile(context, info.basePath)
|
||||
|
||||
if (isScopedStorage && base.isDownloadDir()) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && base.isDownloadDir()) {
|
||||
val cr = context.contentResolver ?: return null
|
||||
val fileUri =
|
||||
cr.getExistingDownloadUriOrNullQ(info.relativePath, info.displayName) ?: return null
|
||||
|
@ -1457,7 +1456,7 @@ object VideoDownloadManager {
|
|||
downloadStatusEvent.invoke(Pair(id, DownloadType.IsStopped))
|
||||
downloadDeleteEvent.invoke(id)
|
||||
val base = basePathToFile(context, info.basePath)
|
||||
if (isScopedStorage && base.isDownloadDir()) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && base.isDownloadDir()) {
|
||||
val cr = context.contentResolver ?: return false
|
||||
val fileUri =
|
||||
cr.getExistingDownloadUriOrNullQ(info.relativePath, info.displayName)
|
||||
|
@ -1505,7 +1504,7 @@ object VideoDownloadManager {
|
|||
// }
|
||||
downloadQueue.addLast(pkg)
|
||||
downloadCheck(context, notificationCallback)
|
||||
if (setKey) saveQueue(context)
|
||||
if (setKey) saveQueue()
|
||||
} else {
|
||||
downloadEvent.invoke(
|
||||
Pair(pkg.item.ep.id, DownloadActionType.Resume)
|
||||
|
@ -1513,12 +1512,12 @@ object VideoDownloadManager {
|
|||
}
|
||||
}
|
||||
|
||||
private fun saveQueue(context: Context) {
|
||||
private fun saveQueue() {
|
||||
val dQueue =
|
||||
downloadQueue.toList()
|
||||
.mapIndexed { index, any -> DownloadQueueResumePackage(index, any) }
|
||||
.toTypedArray()
|
||||
context.setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue)
|
||||
setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue)
|
||||
}
|
||||
|
||||
/*fun isMyServiceRunning(context: Context, serviceClass: Class<*>): Boolean {
|
||||
|
@ -1576,7 +1575,7 @@ object VideoDownloadManager {
|
|||
pkg: DownloadResumePackage,
|
||||
) {
|
||||
val key = pkg.item.ep.id.toString()
|
||||
context.setKey(WORK_KEY_PACKAGE, key, pkg)
|
||||
setKey(WORK_KEY_PACKAGE, key, pkg)
|
||||
startWork(context, key)
|
||||
}
|
||||
|
||||
|
@ -1596,7 +1595,7 @@ object VideoDownloadManager {
|
|||
)
|
||||
|
||||
val key = info.ep.id.toString()
|
||||
context.setKey(WORK_KEY_INFO, key, info)
|
||||
setKey(WORK_KEY_INFO, key, info)
|
||||
startWork(context, key)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.lagradost.cloudstream3
|
||||
|
||||
import com.lagradost.cloudstream3.movieproviders.ThenosProvider
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper
|
||||
|
|
Loading…
Reference in a new issue