removed context (no user change) + small bug fixes

This commit is contained in:
LagradOst 2021-12-17 00:45:20 +01:00
parent 2580455cd5
commit a96b396307
28 changed files with 525 additions and 468 deletions

View file

@ -33,7 +33,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.lagradost.cloudstream3" applicationId "com.lagradost.cloudstream3"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 31 targetSdkVersion 30
versionCode 37 versionCode 37
versionName "2.4.5" versionName "2.4.5"

View file

@ -5,7 +5,13 @@ import android.content.Context
import android.widget.Toast import android.widget.Toast
import com.google.auto.service.AutoService import com.google.auto.service.AutoService
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall 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.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.ReportField
import org.acra.config.CoreConfiguration import org.acra.config.CoreConfiguration
import org.acra.data.CrashReportData import org.acra.data.CrashReportData
@ -84,5 +90,49 @@ class AcraApplication : Application() {
private set(value) { private set(value) {
_context = WeakReference(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)
}
} }
} }

View file

@ -164,6 +164,25 @@ object APIHolder {
return realSet 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*/ /**Every provider will **not** have try catch built in, so handle exceptions when calling these functions*/
@ -525,25 +544,27 @@ fun MainAPI.newMovieLoadResponse(
name: String, name: String,
url: String, url: String,
type: TvType, type: TvType,
dataUrl : String, dataUrl: String,
initializer: MovieLoadResponse.() -> Unit = { } initializer: MovieLoadResponse.() -> Unit = { }
): MovieLoadResponse { ): MovieLoadResponse {
val builder = MovieLoadResponse(name = name, url = url, apiName = this.name, type = type,dataUrl = dataUrl) val builder = MovieLoadResponse(name = name, url = url, apiName = this.name, type = type, dataUrl = dataUrl)
builder.initializer() builder.initializer()
return builder return builder
} }
fun LoadResponse.setDuration(input : String?) { fun LoadResponse.setDuration(input: String?) {
if (input == null) return if (input == null) return
Regex("([0-9]*)h.*?([0-9]*)m").matchEntire(input)?.groupValues?.let { values -> Regex("([0-9]*)h.*?([0-9]*)m").matchEntire(input)?.groupValues?.let { values ->
if(values.size == 3) { if (values.size == 3) {
val hours = values[1].toIntOrNull() val hours = values[1].toIntOrNull()
val minutes = values[2].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 -> Regex("([0-9]*)m").matchEntire(input)?.groupValues?.let { values ->
if(values.size == 2) { if (values.size == 2) {
this.duration = values[1].toIntOrNull() this.duration = values[1].toIntOrNull()
} }
} }
@ -584,7 +605,7 @@ fun MainAPI.newTvSeriesLoadResponse(
name: String, name: String,
url: String, url: String,
type: TvType, type: TvType,
episodes : List<TvSeriesEpisode>, episodes: List<TvSeriesEpisode>,
initializer: TvSeriesLoadResponse.() -> Unit = { } initializer: TvSeriesLoadResponse.() -> Unit = { }
): TvSeriesLoadResponse { ): TvSeriesLoadResponse {
val builder = TvSeriesLoadResponse(name = name, url = url, apiName = this.name, type = type, episodes = episodes) val builder = TvSeriesLoadResponse(name = name, url = url, apiName = this.name, type = type, episodes = episodes)

View file

@ -490,7 +490,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
if (str.contains(appString)) { if (str.contains(appString)) {
for (api in OAuth2Apis) { for (api in OAuth2Apis) {
if (str.contains("/${api.redirectUrl}")) { if (str.contains("/${api.redirectUrl}")) {
api.handleRedirect(this, str) api.handleRedirect(str)
} }
} }
} else { } else {
@ -511,7 +511,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
// init accounts // init accounts
for (api in OAuth2accountApis) { for (api in OAuth2accountApis) {
api.init(this) api.init()
} }
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)

View file

@ -1,9 +1,8 @@
package com.lagradost.cloudstream3.syncproviders package com.lagradost.cloudstream3.syncproviders
import android.content.Context import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys
import com.lagradost.cloudstream3.utils.DataStore.removeKeys import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
abstract class AccountManager(private val defIndex: Int) : OAuth2API { abstract class AccountManager(private val defIndex: Int) : OAuth2API {
var accountIndex = defIndex var accountIndex = defIndex
@ -13,44 +12,44 @@ abstract class AccountManager(private val defIndex: Int) : OAuth2API {
// int array of all accounts indexes // int array of all accounts indexes
private val accountsKey get() = "${idPrefix}_accounts" private val accountsKey get() = "${idPrefix}_accounts"
protected fun Context.removeAccountKeys() { protected fun removeAccountKeys() {
this.removeKeys(accountId) removeKeys(accountId)
val accounts = getAccounts(this).toMutableList() val accounts = getAccounts()?.toMutableList() ?: mutableListOf()
accounts.remove(accountIndex) accounts.remove(accountIndex)
this.setKey(accountsKey, accounts.toIntArray()) setKey(accountsKey, accounts.toIntArray())
init(this) init()
} }
fun getAccounts(context: Context): IntArray { fun getAccounts(): IntArray? {
return context.getKey(accountsKey, intArrayOf())!! return getKey(accountsKey, intArrayOf())
} }
fun init(context: Context) { fun init() {
accountIndex = context.getKey(accountActiveKey, defIndex)!! accountIndex = getKey(accountActiveKey, defIndex)!!
val accounts = getAccounts(context) val accounts = getAccounts()
if (accounts.isNotEmpty() && this.loginInfo(context) == null) { if (accounts?.isNotEmpty() == true && this.loginInfo() == null) {
accountIndex = accounts.first() accountIndex = accounts.first()
} }
} }
protected fun Context.switchToNewAccount() { protected fun switchToNewAccount() {
val accounts = getAccounts(this) val accounts = getAccounts()
accountIndex = (accounts.maxOrNull() ?: 0) + 1 accountIndex = (accounts?.maxOrNull() ?: 0) + 1
} }
protected fun Context.registerAccount() { protected fun registerAccount() {
this.setKey(accountActiveKey, accountIndex) setKey(accountActiveKey, accountIndex)
val accounts = getAccounts(this).toMutableList() val accounts = getAccounts()?.toMutableList() ?: mutableListOf()
if (!accounts.contains(accountIndex)) { if (!accounts.contains(accountIndex)) {
accounts.add(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 accountIndex = index
context.setKey(accountActiveKey, index) setKey(accountActiveKey, index)
} }
} }

View file

@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.syncproviders package com.lagradost.cloudstream3.syncproviders
import android.content.Context
import com.lagradost.cloudstream3.syncproviders.providers.AniListApi import com.lagradost.cloudstream3.syncproviders.providers.AniListApi
import com.lagradost.cloudstream3.syncproviders.providers.MALApi import com.lagradost.cloudstream3.syncproviders.providers.MALApi
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
@ -13,11 +12,11 @@ interface OAuth2API {
// don't change this as all keys depend on it // don't change this as all keys depend on it
val idPrefix : String val idPrefix : String
fun handleRedirect(context: Context, url: String) fun handleRedirect(url: String)
fun authenticate(context: Context) fun authenticate()
fun loginInfo(context: Context): LoginInfo? fun loginInfo(): LoginInfo?
fun logOut(context: Context) fun logOut()
class LoginInfo( class LoginInfo(
val profilePicture: String?, val profilePicture: String?,

View file

@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.syncproviders package com.lagradost.cloudstream3.syncproviders
import android.content.Context
import com.lagradost.cloudstream3.ShowStatus import com.lagradost.cloudstream3.ShowStatus
interface SyncAPI : OAuth2API { interface SyncAPI : OAuth2API {
@ -67,7 +66,7 @@ interface SyncAPI : OAuth2API {
val icon: Int val icon: Int
val mainUrl: String val mainUrl: String
fun search(context: Context, name: String): List<SyncSearchResult>? fun search(name: String): List<SyncSearchResult>?
/** /**
-1 -> None -1 -> None
@ -78,9 +77,9 @@ interface SyncAPI : OAuth2API {
4 -> PlanToWatch 4 -> PlanToWatch
5 -> ReWatching 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?
} }

View file

@ -1,11 +1,14 @@
package com.lagradost.cloudstream3.syncproviders.providers package com.lagradost.cloudstream3.syncproviders.providers
import android.content.Context
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue 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.R
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError 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.maxStale
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.unixTime import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.unixTime
import com.lagradost.cloudstream3.syncproviders.SyncAPI 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.AppUtils.splitQuery
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe 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 com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
import java.net.URL import java.net.URL
import java.util.* import java.util.*
@ -33,9 +32,9 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
override val mainUrl = "https://anilist.co" override val mainUrl = "https://anilist.co"
override val icon = R.drawable.ic_anilist_icon override val icon = R.drawable.ic_anilist_icon
override fun loginInfo(context: Context): OAuth2API.LoginInfo? { override fun loginInfo(): OAuth2API.LoginInfo? {
// context.getUser(true)?. // context.getUser(true)?.
context.getKey<AniListUser>(accountId, ANILIST_USER_KEY)?.let { user -> getKey<AniListUser>(accountId, ANILIST_USER_KEY)?.let { user ->
return OAuth2API.LoginInfo( return OAuth2API.LoginInfo(
profilePicture = user.picture, profilePicture = user.picture,
name = user.name, name = user.name,
@ -45,16 +44,16 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
return null return null
} }
override fun logOut(context: Context) { override fun logOut() {
context.removeAccountKeys() removeAccountKeys()
} }
override fun authenticate(context: Context) { override fun authenticate() {
val request = "https://anilist.co/api/v2/oauth/authorize?client_id=$key&response_type=token" 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 { try {
val sanitizer = val sanitizer =
splitQuery(URL(url.replace(appString, "https").replace("/#", "?"))) // FIX ERROR 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() val endTime = unixTime + expiresIn.toLong()
context.switchToNewAccount() switchToNewAccount()
context.setKey(accountId, ANILIST_UNIXTIME_KEY, endTime) setKey(accountId, ANILIST_UNIXTIME_KEY, endTime)
context.setKey(accountId, ANILIST_TOKEN_KEY, token) setKey(accountId, ANILIST_TOKEN_KEY, token)
context.setKey(ANILIST_SHOULD_UPDATE_LIST, true) setKey(ANILIST_SHOULD_UPDATE_LIST, true)
ioSafe { ioSafe {
context.getUser() getUser()
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() 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 val data = searchShows(name) ?: return null
return data.data.Page.media.map { return data.data.Page.media.map {
SyncAPI.SyncSearchResult( 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 internalId = id.toIntOrNull() ?: return null
val season = getSeason(internalId)?.data?.Media ?: 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 internalId = id.toIntOrNull() ?: return null
val data = context.getDataAboutId(internalId) ?: return null val data = getDataAboutId(internalId) ?: return null
return SyncAPI.SyncStatus( return SyncAPI.SyncStatus(
score = data.score, 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 { override fun score(id: String, status: SyncAPI.SyncStatus): Boolean {
return context.postDataAboutId( return postDataAboutId(
id.toIntOrNull() ?: return false, id.toIntOrNull() ?: return false,
fromIntToAnimeStatus(status.status), fromIntToAnimeStatus(status.status),
status.score, status.score,
status.watchedEpisodes status.watchedEpisodes
) ) ?: return false
} }
companion object { 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 if (getKey<String>(accountId, ANILIST_TOKEN_KEY, null) == null) return
ioSafe { ioSafe {
getUser() getUser()
} }
} }
private fun Context.checkToken(): Boolean { private fun checkToken(): Boolean {
return unixTime > getKey( return unixTime > getKey(
accountId, accountId,
ANILIST_UNIXTIME_KEY, 0L ANILIST_UNIXTIME_KEY, 0L
)!! )!!
} }
fun Context.getDataAboutId(id: Int): AniListTitleHolder? { fun getDataAboutId(id: Int): AniListTitleHolder? {
val q = val q =
"""query (${'$'}id: Int = $id) { # Define which variables will be used in the query (id) """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) 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 { return try {
if (!checkToken()) { if (!checkToken()) {
// println("VARS_ " + vars) // println("VARS_ " + vars)
@ -504,11 +503,11 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
@JsonProperty("MediaListCollection") val MediaListCollection: MediaListCollection @JsonProperty("MediaListCollection") val MediaListCollection: MediaListCollection
) )
fun Context.getAnilistListCached(): Array<Lists>? { fun getAnilistListCached(): Array<Lists>? {
return getKey(ANILIST_CACHED_LIST) as? Array<Lists> return getKey(ANILIST_CACHED_LIST) as? Array<Lists>
} }
fun Context.getAnilistAnimeListSmart(): Array<Lists>? { fun getAnilistAnimeListSmart(): Array<Lists>? {
if (getKey<String>( if (getKey<String>(
accountId, accountId,
ANILIST_TOKEN_KEY, ANILIST_TOKEN_KEY,
@ -529,11 +528,11 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
} }
} }
private fun Context.getFullAnilistList(): FullAnilistList? { private fun getFullAnilistList(): FullAnilistList? {
try { try {
var userID: Int? = null var userID: Int? = null
/** WARNING ASSUMES ONE USER! **/ /** WARNING ASSUMES ONE USER! **/
getKeys(ANILIST_USER_KEY).forEach { key -> getKeys(ANILIST_USER_KEY)?.forEach { key ->
getKey<AniListUser>(key, null)?.let { getKey<AniListUser>(key, null)?.let {
userID = it.id 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) { val q = """mutation (${'$'}animeId: Int = $id) {
ToggleFavourite (animeId: ${'$'}animeId) { ToggleFavourite (animeId: ${'$'}animeId) {
anime { anime {
@ -608,7 +607,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
return data != "" 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 { try {
val q = val q =
"""mutation (${'$'}id: Int = $id, ${'$'}status: MediaListStatus = ${ """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 = """ val q = """
{ {
Viewer { Viewer {

View file

@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.syncproviders.providers package com.lagradost.cloudstream3.syncproviders.providers
import android.content.Context
import com.lagradost.cloudstream3.syncproviders.OAuth2API import com.lagradost.cloudstream3.syncproviders.OAuth2API
//TODO dropbox sync //TODO dropbox sync
@ -10,19 +9,19 @@ class Dropbox : OAuth2API {
override val key = "zlqsamadlwydvb2" override val key = "zlqsamadlwydvb2"
override val redirectUrl = "dropboxlogin" override val redirectUrl = "dropboxlogin"
override fun authenticate(context: Context) { override fun authenticate() {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun handleRedirect(context: Context,url: String) { override fun handleRedirect(url: String) {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun logOut(context: Context) { override fun logOut() {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override fun loginInfo(context: Context): OAuth2API.LoginInfo? { override fun loginInfo(): OAuth2API.LoginInfo? {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
} }

View file

@ -1,12 +1,14 @@
package com.lagradost.cloudstream3.syncproviders.providers package com.lagradost.cloudstream3.syncproviders.providers
import android.content.Context
import android.util.Base64 import android.util.Base64
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.fasterxml.jackson.module.kotlin.readValue 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.R
import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.mvvm.logError 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.secondsToReadable
import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.unixTime import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.unixTime
import com.lagradost.cloudstream3.syncproviders.SyncAPI 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.AppUtils.splitQuery
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe 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 com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
import java.net.URL import java.net.URL
import java.security.SecureRandom import java.security.SecureRandom
@ -40,26 +39,27 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
override val icon: Int override val icon: Int
get() = R.drawable.mal_logo get() = R.drawable.mal_logo
override fun logOut(context: Context) { override fun logOut() {
context.removeAccountKeys() removeAccountKeys()
} }
override fun loginInfo(context: Context): OAuth2API.LoginInfo? { override fun loginInfo(): OAuth2API.LoginInfo? {
//context.getMalUser(true)? //getMalUser(true)?
context.getKey<MalUser>(accountId, MAL_USER_KEY)?.let { user -> getKey<MalUser>(accountId, MAL_USER_KEY)?.let { user ->
return OAuth2API.LoginInfo(profilePicture = user.picture, name = user.name, accountIndex = accountIndex) return OAuth2API.LoginInfo(profilePicture = user.picture, name = user.name, accountIndex = accountIndex)
} }
return null 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" val url = "https://api.myanimelist.net/v2/anime?q=$name&limit=$MAL_MAX_SEARCH_LIMIT"
var res = app.get( val auth = getKey<String>(
url, headers = mapOf(
"Authorization" to "Bearer " + context.getKey<String>(
accountId, accountId,
MAL_TOKEN_KEY MAL_TOKEN_KEY
)!!, ) ?: return emptyList()
val res = app.get(
url, headers = mapOf(
"Authorization" to "Bearer " + auth,
), cacheTime = 0 ), cacheTime = 0
).text ).text
return mapper.readValue<MalSearch>(res).data.map { 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 { override fun score(id: String, status : SyncAPI.SyncStatus): Boolean {
return context.setScoreRequest( return setScoreRequest(
id.toIntOrNull() ?: return false, id.toIntOrNull() ?: return false,
fromIntToAnimeStatus(status.status), fromIntToAnimeStatus(status.status),
status.score, 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 val internalId = id.toIntOrNull() ?: return null
TODO("Not yet implemented") 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 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( return SyncAPI.SyncStatus(
score = data.score, score = data.score,
status = malStatusAsString.indexOf(data.status), 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 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 { try {
val sanitizer = val sanitizer =
splitQuery(URL(url.replace(appString, "https").replace("/#", "?"))) // FIX ERROR splitQuery(URL(url.replace(appString, "https").replace("/#", "?"))) // FIX ERROR
@ -136,10 +136,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
} }
if (res != "") { if (res != "") {
context.switchToNewAccount() switchToNewAccount()
context.storeToken(res) storeToken(res)
context.getMalUser() getMalUser()
context.setKey(MAL_SHOULD_UPDATE_LIST, true) 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. // It is recommended to use a URL-safe string as code_verifier.
// See section 4 of RFC 7636 for more details. // See section 4 of RFC 7636 for more details.
@ -161,7 +161,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
val codeChallenge = codeVerifier val codeChallenge = codeVerifier
val request = val request =
"https://myanimelist.net/v1/oauth2/authorize?response_type=code&client_id=$key&code_challenge=$codeChallenge&state=RequestID$requestId" "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()) private val mapper = JsonMapper.builder().addModule(KotlinModule())
@ -170,7 +170,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
private var requestId = 0 private var requestId = 0
private var codeVerifier = "" private var codeVerifier = ""
private fun Context.storeToken(response: String) { private fun storeToken(response: String) {
try { try {
if (response != "") { if (response != "") {
val token = mapper.readValue<ResponseToken>(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 { try {
val res = app.post( val res = app.post(
"https://myanimelist.net/v1/oauth2/token", "https://myanimelist.net/v1/oauth2/token",
@ -278,11 +278,11 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
@JsonProperty("start_time") val start_time: String? @JsonProperty("start_time") val start_time: String?
) )
fun Context.getMalAnimeListCached(): Array<Data>? { fun getMalAnimeListCached(): Array<Data>? {
return getKey(MAL_CACHED_LIST) as? Array<Data> return getKey(MAL_CACHED_LIST) as? Array<Data>
} }
fun Context.getMalAnimeListSmart(): Array<Data>? { fun getMalAnimeListSmart(): Array<Data>? {
if (getKey<String>( if (getKey<String>(
accountId, accountId,
MAL_TOKEN_KEY 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 { return try {
checkMalToken() checkMalToken()
var offset = 0 var offset = 0
@ -322,8 +322,12 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
return fromIntToAnimeStatus(malStatusAsString.indexOf(string)) return fromIntToAnimeStatus(malStatusAsString.indexOf(string))
} }
private fun Context.getMalAnimeListSlice(offset: Int = 0): MalList? { private fun getMalAnimeListSlice(offset: Int = 0): MalList? {
val user = "@me" val user = "@me"
val auth = getKey<String>(
accountId,
MAL_TOKEN_KEY
) ?: return null
return try { return try {
// Very lackluster docs // Very lackluster docs
// https://myanimelist.net/apiconfig/references/api/v2#operation/users_user_id_animelist_get // 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" "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( val res = app.get(
url, headers = mapOf( url, headers = mapOf(
"Authorization" to "Bearer " + getKey<String>( "Authorization" to "Bearer $auth",
accountId,
MAL_TOKEN_KEY
)!!,
), cacheTime = 0 ), cacheTime = 0
).text ).text
res.toKotlinObject() 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 { return try {
// https://myanimelist.net/apiconfig/references/api/v2#operation/anime_anime_id_get // 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" 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" val user = "@me"
var isDone = false var isDone = false
var index = 0 var index = 0
@ -426,7 +427,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
return null return null
} }
private fun Context.checkMalToken() { private fun checkMalToken() {
if (unixTime > getKey( if (unixTime > getKey(
accountId, accountId,
MAL_UNIXTIME_KEY 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() checkMalToken()
return try { return try {
val res = app.get( val res = app.get(
@ -483,7 +484,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
} }
} }
fun Context.setScoreRequest( fun setScoreRequest(
id: Int, id: Int,
status: MalStatusType? = null, status: MalStatusType? = null,
score: Int? = null, score: Int? = null,
@ -514,7 +515,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
} }
} }
private fun Context.setScoreRequest( private fun setScoreRequest(
id: Int, id: Int,
status: String? = null, status: String? = null,
score: Int? = null, score: Int? = null,

View file

@ -113,7 +113,7 @@ object DownloadButtonSetup {
if (click.data.episode <= 0) null else click.data.episode, if (click.data.episode <= 0) null else click.data.episode,
click.data.season click.data.season
), ),
act.getViewPos(click.data.id)?.position ?: 0 getViewPos(click.data.id)?.position ?: 0
) )
) )
} }

View file

@ -106,7 +106,7 @@ class DownloadChildAdapter(
localCard = card localCard = card
val d = card.data val d = card.data
val posDur = itemView.context.getViewPos(d.id) val posDur = getViewPos(d.id)
if (posDur != null) { if (posDur != null) {
val visualPos = posDur.fixVisual() val visualPos = posDur.fixVisual()
progressBar.max = (visualPos.duration / 1000).toInt() progressBar.max = (visualPos.duration / 1000).toInt()

View file

@ -14,7 +14,6 @@ import android.widget.TextView
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSnapHelper import androidx.recyclerview.widget.LinearSnapHelper
@ -22,7 +21,10 @@ import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull 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.Resource
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.observe 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.SearchFragment.Companion.filterSearchResponse
import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings 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.AppUtils.loadSearchResult
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
@ -130,14 +131,12 @@ class HomeFragment : Fragment() {
} }
private val apiChangeClickListener = View.OnClickListener { view -> private val apiChangeClickListener = View.OnClickListener { view ->
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val validAPIs = view.context?.filterProviderByPreferredMedia()?.toMutableList() ?: mutableListOf()
val currentPrefMedia = settingsManager.getInt(getString(R.string.preferred_media_settings), 0)
val validAPIs = AppUtils.filterProviderByPreferredMedia(apis, currentPrefMedia).toMutableList()
validAPIs.add(0, randomApi) validAPIs.add(0, randomApi)
validAPIs.add(0, noneApi) validAPIs.add(0, noneApi)
view.popupMenuNoIconsAndNoStringRes(validAPIs.mapIndexed { index, api -> Pair(index, api.name) }) { 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() { private fun reloadStored() {
context?.let { ctx -> homeViewModel.loadResumeWatching()
homeViewModel.loadResumeWatching(ctx)
val list = EnumSet.noneOf(WatchType::class.java) 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) list.addAll(it)
} }
homeViewModel.loadStoredData(ctx, list) homeViewModel.loadStoredData(list)
}
} }
/*private fun handleBack(poppedFragment: Boolean) { /*private fun handleBack(poppedFragment: Boolean) {
@ -182,7 +179,7 @@ class HomeFragment : Fragment() {
home_change_api_loading.setOnClickListener(apiChangeClickListener) home_change_api_loading.setOnClickListener(apiChangeClickListener)
observe(homeViewModel.apiName) { apiName -> observe(homeViewModel.apiName) { apiName ->
context?.setKey(HOMEPAGE_API, apiName) setKey(HOMEPAGE_API, apiName)
home_provider_name?.text = apiName home_provider_name?.text = apiName
home_provider_meta_info?.isVisible = false home_provider_meta_info?.isVisible = false
@ -326,11 +323,11 @@ class HomeFragment : Fragment() {
} else { } else {
list.add(watch) list.add(watch)
} }
homeViewModel.loadStoredData(itemView.context, list) homeViewModel.loadStoredData(list)
} }
item.first?.setOnLongClickListener { itemView -> item.first?.setOnLongClickListener {
homeViewModel.loadStoredData(itemView.context, EnumSet.of(watch)) homeViewModel.loadStoredData(EnumSet.of(watch))
return@setOnLongClickListener true return@setOnLongClickListener true
} }
} }
@ -404,7 +401,7 @@ class HomeFragment : Fragment() {
if (id != null) { if (id != null) {
callback.view.popupMenuNoIcons(listOf(Pair(0, R.string.action_remove_from_bookmarks))) { callback.view.popupMenuNoIcons(listOf(Pair(0, R.string.action_remove_from_bookmarks))) {
if (itemId == 0) { if (itemId == 0) {
activity?.setResultWatchState(id, WatchType.NONE.internalId) setResultWatchState(id, WatchType.NONE.internalId)
reloadStored() reloadStored()
} }
} }
@ -438,7 +435,7 @@ class HomeFragment : Fragment() {
if (itemId == 0) { if (itemId == 0) {
val card = callback.card val card = callback.card
if (card is DataStoreHelper.ResumeWatchingResult) { if (card is DataStoreHelper.ResumeWatchingResult) {
context?.removeLastWatched(card.parentId) removeLastWatched(card.parentId)
reloadStored() reloadStored()
} }
} }
@ -476,11 +473,9 @@ class HomeFragment : Fragment() {
reloadStored() reloadStored()
val apiName = context?.getKey<String>(HOMEPAGE_API) 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) { if (homeViewModel.apiName.value != apiName || apiName == null) {
//println("Caught home: " + homeViewModel.apiName.value + " at " + apiName) //println("Caught home: " + homeViewModel.apiName.value + " at " + apiName)
homeViewModel.loadAndCancel(apiName, currentPrefMedia) homeViewModel.loadAndCancel(apiName)
} }
// nice profile pic on homepage // nice profile pic on homepage
@ -497,7 +492,7 @@ class HomeFragment : Fragment() {
} }
for (syncApi in OAuth2API.OAuth2Apis) { for (syncApi in OAuth2API.OAuth2Apis) {
val login = syncApi.loginInfo(ctx) val login = syncApi.loginInfo()
val pic = login?.profilePicture val pic = login?.profilePicture
if (pic != null) { if (pic != null) {
home_profile_picture.setImage(pic) home_profile_picture.setImage(pic)

View file

@ -1,13 +1,15 @@
package com.lagradost.cloudstream3.ui.home package com.lagradost.cloudstream3.ui.home
import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.AcraApplication.Companion.context 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.HomePageResponse
import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.SearchResponse 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.noneApi
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos 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.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -55,22 +58,22 @@ class HomeViewModel : ViewModel() {
private val _resumeWatching = MutableLiveData<List<SearchResponse>>() private val _resumeWatching = MutableLiveData<List<SearchResponse>>()
val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching val resumeWatching: LiveData<List<SearchResponse>> = _resumeWatching
fun loadResumeWatching(context: Context) = viewModelScope.launch { fun loadResumeWatching() = viewModelScope.launch {
val resumeWatching = withContext(Dispatchers.IO) { val resumeWatching = withContext(Dispatchers.IO) {
context.getAllResumeStateIds().mapNotNull { id -> getAllResumeStateIds()?.mapNotNull { id ->
context.getLastWatched(id) getLastWatched(id)
}.sortedBy { -it.updateTime } }?.sortedBy { -it.updateTime }
} }
// val resumeWatchingResult = ArrayList<DataStoreHelper.ResumeWatchingResult>() // val resumeWatchingResult = ArrayList<DataStoreHelper.ResumeWatchingResult>()
val resumeWatchingResult = withContext(Dispatchers.IO) { val resumeWatchingResult = withContext(Dispatchers.IO) {
resumeWatching.map { resume -> resumeWatching?.map { resume ->
val data = context.getKey<VideoDownloadHelper.DownloadHeaderCached>( val data = getKey<VideoDownloadHelper.DownloadHeaderCached>(
DOWNLOAD_HEADER_CACHE, DOWNLOAD_HEADER_CACHE,
resume.parentId.toString() resume.parentId.toString()
) ?: return@map null ) ?: return@map null
val watchPos = context.getViewPos(resume.episodeId) val watchPos = getViewPos(resume.episodeId)
DataStoreHelper.ResumeWatchingResult( DataStoreHelper.ResumeWatchingResult(
data.name, data.name,
data.url, data.url,
@ -84,18 +87,19 @@ class HomeViewModel : ViewModel() {
resume.season, resume.season,
resume.isFromDownload resume.isFromDownload
) )
}.filterNotNull() }?.filterNotNull()
} }
_resumeWatching.postValue(resumeWatchingResult) _resumeWatching.postValue(resumeWatchingResult)
} }
fun loadStoredData(context: Context, preferredWatchStatus: EnumSet<WatchType>?) = viewModelScope.launch { fun loadStoredData(preferredWatchStatus: EnumSet<WatchType>?) = viewModelScope.launch {
val watchStatusIds = withContext(Dispatchers.IO) { val watchStatusIds = withContext(Dispatchers.IO) {
context.getAllWatchStateIds().map { id -> getAllWatchStateIds()?.map { id ->
Pair(id, context.getResultWatchState(id)) Pair(id, getResultWatchState(id))
} }
}.distinctBy { it.first } }?.distinctBy { it.first } ?: return@launch
val length = WatchType.values().size val length = WatchType.values().size
val currentWatchTypes = EnumSet.noneOf(WatchType::class.java) val currentWatchTypes = EnumSet.noneOf(WatchType::class.java)
@ -125,10 +129,10 @@ class HomeViewModel : ViewModel() {
val list = withContext(Dispatchers.IO) { val list = withContext(Dispatchers.IO) {
watchStatusIds.filter { watchPrefNotNull.contains(it.second) } watchStatusIds.filter { watchPrefNotNull.contains(it.second) }
.mapNotNull { context.getBookmarkedData(it.first) } .mapNotNull { getBookmarkedData(it.first) }
.sortedBy { -it.latestUpdatedTime } .sortedBy { -it.latestUpdatedTime }
} }
_bookmarks.postValue(Pair(true,list)) _bookmarks.postValue(Pair(true, list))
} }
private var onGoingLoad: Job? = null private var onGoingLoad: Job? = null
@ -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) val api = getApiFromNameNull(preferredApiName)
if (preferredApiName == noneApi.name) if (preferredApiName == noneApi.name)
loadAndCancel(noneApi) loadAndCancel(noneApi)
else if (preferredApiName == randomApi.name || api == null) { 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() val apiRandom = validAPIs.random()
loadAndCancel(apiRandom) loadAndCancel(apiRandom)
context?.setKey(HOMEPAGE_API, apiRandom.name) setKey(HOMEPAGE_API, apiRandom.name)
}
} else { } else {
loadAndCancel(api) loadAndCancel(api)
} }

View file

@ -818,7 +818,6 @@ class PlayerFragment : Fragment() {
context?.let { ctx -> context?.let { ctx ->
//if (this::viewModel.isInitialized) { //if (this::viewModel.isInitialized) {
viewModel.setViewPos( viewModel.setViewPos(
ctx,
if (isDownloadedFile) uriData.id else getEpisode()?.id, if (isDownloadedFile) uriData.id else getEpisode()?.id,
exoPlayer.currentPosition, exoPlayer.currentPosition,
exoPlayer.duration exoPlayer.duration
@ -832,7 +831,7 @@ class PlayerFragment : Fragment() {
}*/ }*/
if (isDownloadedFile) { if (isDownloadedFile) {
ctx.setLastWatched( setLastWatched(
uriData.parentId, uriData.parentId,
uriData.id, uriData.id,
uriData.episode, uriData.episode,
@ -840,7 +839,7 @@ class PlayerFragment : Fragment() {
true true
) )
} else } else
viewModel.reloadEpisodes(ctx) viewModel.reloadEpisodes()
} }
} }
} }
@ -1390,9 +1389,7 @@ class PlayerFragment : Fragment() {
handlePlayerEvent(PlayerEventType.Play) handlePlayerEvent(PlayerEventType.Play)
} }
context?.let { ctx -> setPreferredSubLanguage(getAutoSelectLanguageISO639_1())
setPreferredSubLanguage(ctx.getAutoSelectLanguageISO639_1())
}
subView = player_view?.findViewById(R.id.exo_subtitles) subView = player_view?.findViewById(R.id.exo_subtitles)
subView?.let { sView -> subView?.let { sView ->
@ -1400,7 +1397,7 @@ class PlayerFragment : Fragment() {
subtitle_holder.addView(sView) subtitle_holder.addView(sView)
} }
subStyle = context?.getCurrentSavedStyle()!! subStyle = getCurrentSavedStyle()
onSubStyleChanged(subStyle) onSubStyleChanged(subStyle)
SubtitlesFragment.applyStyleEvent += ::onSubStyleChanged SubtitlesFragment.applyStyleEvent += ::onSubStyleChanged

View file

@ -38,7 +38,7 @@ class QuickSearchFragment(var isMainApis: Boolean = false) : Fragment() {
}) })
} }
fun pushSync(activity: Activity?, autoSearch: String? = null, callback : (SearchClickCallback) -> Unit) { fun pushSync(activity: Activity?, autoSearch: String? = null, callback: (SearchClickCallback) -> Unit) {
clickCallback = callback clickCallback = callback
activity.navigate(R.id.global_to_navigation_quick_search, Bundle().apply { activity.navigate(R.id.global_to_navigation_quick_search, Bundle().apply {
putBoolean("mainapi", false) putBoolean("mainapi", false)
@ -46,7 +46,7 @@ class QuickSearchFragment(var isMainApis: Boolean = false) : Fragment() {
}) })
} }
var clickCallback : ((SearchClickCallback) -> Unit)? = null var clickCallback: ((SearchClickCallback) -> Unit)? = null
} }
private val searchViewModel: SearchViewModel by activityViewModels() private val searchViewModel: SearchViewModel by activityViewModels()
@ -124,10 +124,7 @@ class QuickSearchFragment(var isMainApis: Boolean = false) : Fragment() {
searchMagIcon.scaleY = 0.65f searchMagIcon.scaleY = 0.65f
quick_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener { quick_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
context?.let { ctx -> searchViewModel.searchAndCancel(query = query, isMainApis = isMainApis, ignoreSettings = true)
searchViewModel.searchAndCancel(query = query, context = ctx, isMainApis = isMainApis, ignoreSettings = true)
}
quick_search?.let { quick_search?.let {
UIHelper.hideKeyboard(it) UIHelper.hideKeyboard(it)
} }

View file

@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.ui.result
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.ClipData import android.content.ClipData
import android.content.ClipboardManager import android.content.ClipboardManager
import android.content.Context
import android.content.Context.CLIPBOARD_SERVICE import android.content.Context.CLIPBOARD_SERVICE
import android.content.Intent import android.content.Intent
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.isAppInstalled
import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable import com.lagradost.cloudstream3.utils.AppUtils.isCastApiAvailable
import com.lagradost.cloudstream3.utils.AppUtils.isConnectedToChromecast 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.CastHelper.startCast
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getFolderName import com.lagradost.cloudstream3.utils.DataStore.getFolderName
@ -128,7 +128,7 @@ fun ResultEpisode.getDisplayPosition(): Long {
return position return position
} }
fun Context.buildResultEpisode( fun buildResultEpisode(
name: String?, name: String?,
poster: String?, poster: String?,
episode: Int, episode: Int,
@ -269,7 +269,7 @@ class ResultFragment : Fragment() {
private var startValue: Int? = null private var startValue: Int? = null
private fun updateSync(id: Int) { 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>>() val list = ArrayList<Pair<SyncAPI, String>>()
for (i in 0 until SyncApis.count()) { for (i in 0 until SyncApis.count()) {
val res = syncList[i] ?: continue val res = syncList[i] ?: continue
@ -596,7 +596,7 @@ class ResultFragment : Fragment() {
// 1. Checks if the lang should be downloaded // 1. Checks if the lang should be downloaded
// 2. Makes it into the download format // 2. Makes it into the download format
// 3. Downloads it as a .vtt file // 3. Downloads it as a .vtt file
val downloadList = ctx.getDownloadSubsLanguageISO639_1() val downloadList = getDownloadSubsLanguageISO639_1()
main { main {
subs?.let { subsList -> subs?.let { subsList ->
subsList.filter { subsList.filter {
@ -857,7 +857,7 @@ class ResultFragment : Fragment() {
//.map { watchType -> Triple(watchType.internalId, watchType.iconRes, watchType.stringRes) }, //.map { watchType -> Triple(watchType.internalId, watchType.iconRes, watchType.stringRes) },
) { ) {
context?.let { localContext -> 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), fab.context.getString(R.string.action_add_to_bookmarks),
showApply = false, 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)) }, .map { Pair(it ?: -2, fromIndexToSeasonText(it)) },
) { ) {
val id = this.itemId 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) { if (ranges != null) {
it.popupMenuNoIconsAndNoStringRes(ranges.map { status -> Pair(status.ordinal, status.toString()) } it.popupMenuNoIconsAndNoStringRes(ranges.map { status -> Pair(status.ordinal, status.toString()) }
.toList()) { .toList()) {
viewModel.changeDubStatus(requireContext(), DubStatus.values()[itemId]) viewModel.changeDubStatus(DubStatus.values()[itemId])
} }
} }
} }
@ -999,7 +998,7 @@ class ResultFragment : Fragment() {
val ranges = episodeRanges val ranges = episodeRanges
if (ranges != null) { if (ranges != null) {
it.popupMenuNoIconsAndNoStringRes(ranges.mapIndexed { index, s -> Pair(index, s) }.toList()) { 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()) updateSync(d.getId())
result_add_sync?.setOnClickListener { result_add_sync?.setOnClickListener {
QuickSearchFragment.pushSync(activity, d.name) { click -> 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( showToast(
activity, activity,
context?.getString(R.string.added_sync_format)?.format(click.card.name), context?.getString(R.string.added_sync_format)?.format(click.card.name),
Toast.LENGTH_SHORT Toast.LENGTH_SHORT
) )
}
updateSync(d.getId()) updateSync(d.getId())
} }
} }
@ -1290,7 +1290,7 @@ class ResultFragment : Fragment() {
val tempUrl = url val tempUrl = url
if (tempUrl != null) { if (tempUrl != null) {
result_reload_connectionerror.setOnClickListener { result_reload_connectionerror.setOnClickListener {
viewModel.load(it.context, tempUrl, apiName, showFillers) viewModel.load(tempUrl, apiName, showFillers)
} }
result_reload_connection_open_in_browser?.setOnClickListener { result_reload_connection_open_in_browser?.setOnClickListener {
@ -1304,18 +1304,12 @@ class ResultFragment : Fragment() {
} }
result_meta_site?.setOnClickListener { result_meta_site?.setOnClickListener {
val i = Intent(ACTION_VIEW) it.context?.openBrowser(tempUrl)
i.data = Uri.parse(tempUrl)
try {
startActivity(i)
} catch (e: Exception) {
e.printStackTrace()
}
} }
if (restart || viewModel.resultResponse.value == null) { if (restart || viewModel.resultResponse.value == null) {
viewModel.clear() viewModel.clear()
viewModel.load(ctx, tempUrl, apiName, showFillers) viewModel.load(tempUrl, apiName, showFillers)
} }
} }
} }

View file

@ -8,23 +8,24 @@ import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
import com.lagradost.cloudstream3.APIHolder.getId import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.mvvm.safeApiCall
import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.utils.* 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.getBookmarkedData
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDub
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultSeason
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.DataStoreHelper.removeLastWatched import com.lagradost.cloudstream3.utils.DataStoreHelper.removeLastWatched
import com.lagradost.cloudstream3.utils.DataStoreHelper.setBookmarkedData 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.setLastWatched
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultSeason
import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.setViewPos
import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.getFillerEpisodes import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.getFillerEpisodes
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -49,6 +50,7 @@ class ResultViewModel : ViewModel() {
id.value = null id.value = null
selectedSeason.value = -2 selectedSeason.value = -2
_dubSubEpisodes.value = null _dubSubEpisodes.value = null
_sync.value = null
} }
private var repo: APIRepository? = null private var repo: APIRepository? = null
@ -89,17 +91,17 @@ class ResultViewModel : ViewModel() {
private val _sync: MutableLiveData<List<Resource<SyncAPI.SyncResult?>>> = MutableLiveData() private val _sync: MutableLiveData<List<Resource<SyncAPI.SyncResult?>>> = MutableLiveData()
val sync: LiveData<List<Resource<SyncAPI.SyncResult?>>> get() = _sync 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 val currentId = id.value ?: return@launch
_watchStatus.postValue(status) _watchStatus.postValue(status)
val resultPage = page.value val resultPage = page.value
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
context.setResultWatchState(currentId, status.internalId) setResultWatchState(currentId, status.internalId)
if (resultPage != null) { if (resultPage != null) {
val current = context.getBookmarkedData(currentId) val current = getBookmarkedData(currentId)
val currentTime = System.currentTimeMillis() val currentTime = System.currentTimeMillis()
context.setBookmarkedData( setBookmarkedData(
currentId, currentId,
DataStoreHelper.BookmarkedData( DataStoreHelper.BookmarkedData(
currentId, 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 currentId = localId ?: id.value ?: return
val currentWatch = context.getResultWatchState(currentId) val currentWatch = getResultWatchState(currentId)
_watchStatus.postValue(currentWatch) _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 if (list == null) return
val seasonTypes = HashMap<Int?, Boolean>() val seasonTypes = HashMap<Int?, Boolean>()
for (i in list) { for (i in list) {
@ -141,7 +143,7 @@ class ResultViewModel : ViewModel() {
val realSelection = if (!seasonTypes.containsKey(selection)) seasons.first() else selection val realSelection = if (!seasonTypes.containsKey(selection)) seasons.first() else selection
val internalId = id.value val internalId = id.value
if (internalId != null) context.setResultSeason(internalId, realSelection) if (internalId != null) setResultSeason(internalId, realSelection)
selectedSeason.postValue(realSelection ?: -2) selectedSeason.postValue(realSelection ?: -2)
@ -187,33 +189,38 @@ class ResultViewModel : ViewModel() {
_publicEpisodes.postValue(Resource.Success(currentList)) _publicEpisodes.postValue(Resource.Success(currentList))
} }
fun changeSeason(context: Context, selection: Int?) { fun changeSeason(selection: Int?) {
filterEpisodes(context, _episodes.value, selection, null) filterEpisodes(_episodes.value, selection, null)
} }
fun changeRange(context: Context, range: Int?) { fun changeRange(range: Int?) {
filterEpisodes(context, _episodes.value, null, range) filterEpisodes(_episodes.value, null, range)
} }
fun changeDubStatus(context: Context, status: DubStatus?) { fun changeDubStatus(status: DubStatus?) {
dubSubEpisodes.value?.get(status)?.let { episodes -> dubSubEpisodes.value?.get(status)?.let { episodes ->
id.value?.let {
if (status != null) {
setDub(it, status)
}
}
_dubStatus.postValue(status) _dubStatus.postValue(status)
updateEpisodes(context, null, episodes, null) updateEpisodes(null, episodes, null)
} }
} }
fun updateSync(context: Context?, sync: List<Pair<SyncAPI, String>>) = viewModelScope.launch { fun updateSync(context: Context?, sync: List<Pair<SyncAPI, String>>) = viewModelScope.launch {
if(context == null) return@launch if (context == null) return@launch
val list = ArrayList<Resource<SyncAPI.SyncResult?>>() val list = ArrayList<Resource<SyncAPI.SyncResult?>>()
for (s in sync) { for (s in sync) {
val result = safeApiCall { s.first.getResult(context, s.second) } val result = safeApiCall { s.first.getResult(s.second) }
list.add(result) list.add(result)
_sync.postValue(list) _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) _episodes.postValue(list)
val set = HashMap<Int, Int>() val set = HashMap<Int, Int>()
@ -221,25 +228,23 @@ class ResultViewModel : ViewModel() {
episodeById.postValue(set) episodeById.postValue(set)
filterEpisodes( filterEpisodes(
context,
list, 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 current = _episodes.value ?: return
val copy = current.map { 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) 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 { try {
if (context == null || episodeId == null) return DataStoreHelper.setViewPos(episodeId, pos, dur)
context.setViewPos(episodeId, pos, dur)
var index = episodeById.value?.get(episodeId) ?: return var index = episodeById.value?.get(episodeId) ?: return
var startPos = pos var startPos = pos
@ -251,7 +256,7 @@ class ResultViewModel : ViewModel() {
if (startDur > 0L && (startPos * 100 / startDur) > 95) { if (startDur > 0L && (startPos * 100 / startDur) > 95) {
index++ index++
if (episodeList.size <= index) { // last episode if (episodeList.size <= index) { // last episode
context.removeLastWatched(parentId) removeLastWatched(parentId)
return return
} }
episode = episodeList[index] episode = episodeList[index]
@ -261,7 +266,7 @@ class ResultViewModel : ViewModel() {
continue continue
} else { } else {
context.setLastWatched(parentId, episode.id, episode.episode, episode.season) setLastWatched(parentId, episode.id, episode.episode, episode.season)
return return
} }
} }
@ -279,7 +284,7 @@ class ResultViewModel : ViewModel() {
return name 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)) _resultResponse.postValue(Resource.Loading(url))
_publicEpisodes.postValue(Resource.Loading()) _publicEpisodes.postValue(Resource.Loading())
@ -301,9 +306,9 @@ class ResultViewModel : ViewModel() {
page.postValue(d) page.postValue(d)
val mainId = d.getId() val mainId = d.getId()
id.postValue(mainId) id.postValue(mainId)
loadWatchStatus(context, mainId) loadWatchStatus(mainId)
context.setKey( setKey(
DOWNLOAD_HEADER_CACHE, DOWNLOAD_HEADER_CACHE,
mainId.toString(), mainId.toString(),
VideoDownloadHelper.DownloadHeaderCached( VideoDownloadHelper.DownloadHeaderCached(
@ -319,11 +324,14 @@ class ResultViewModel : ViewModel() {
when (d) { when (d) {
is AnimeLoadResponse -> { is AnimeLoadResponse -> {
//TODO context.getKey<>() isdub if (d.episodes.isEmpty()) {
_dubSubEpisodes.postValue(emptyMap())
return@launch
}
val isDub = val status = getDub(mainId)
d.episodes.containsKey(DubStatus.Dubbed) && !d.episodes[DubStatus.Dubbed].isNullOrEmpty() val statuses = d.episodes.map { it.key }
val dubStatus = if (isDub) DubStatus.Dubbed else DubStatus.Subbed val dubStatus = if (statuses.contains(status)) status else statuses.first()
_dubStatus.postValue(dubStatus) _dubStatus.postValue(dubStatus)
_dubSubSelections.postValue(d.episodes.keys) _dubSubSelections.postValue(d.episodes.keys)
@ -335,8 +343,7 @@ class ResultViewModel : ViewModel() {
for ((index, i) in ep.value.withIndex()) { for ((index, i) in ep.value.withIndex()) {
val episode = i.episode ?: (index + 1) val episode = i.episode ?: (index + 1)
episodes.add( episodes.add(buildResultEpisode(
context.buildResultEpisode(
filterName(i.name), filterName(i.name),
i.posterUrl, i.posterUrl,
episode, episode,
@ -350,8 +357,7 @@ class ResultViewModel : ViewModel() {
if (fillerEpisodes is Resource.Success) fillerEpisodes.value?.let { if (fillerEpisodes is Resource.Success) fillerEpisodes.value?.let {
it.contains(episode) && it[episode] == true it.contains(episode) && it[episode] == true
} ?: false else false, } ?: false else false,
) ))
)
} }
idIndex++ idIndex++
@ -360,7 +366,7 @@ class ResultViewModel : ViewModel() {
_dubSubEpisodes.postValue(res) _dubSubEpisodes.postValue(res)
res[dubStatus]?.let { episodes -> res[dubStatus]?.let { episodes ->
updateEpisodes(context, mainId, episodes, -1) updateEpisodes(mainId, episodes, -1)
} }
} }
@ -368,7 +374,7 @@ class ResultViewModel : ViewModel() {
val episodes = ArrayList<ResultEpisode>() val episodes = ArrayList<ResultEpisode>()
for ((index, i) in d.episodes.withIndex()) { for ((index, i) in d.episodes.withIndex()) {
episodes.add( episodes.add(
context.buildResultEpisode( buildResultEpisode(
filterName(i.name), filterName(i.name),
i.posterUrl, i.posterUrl,
i.episode ?: (index + 1), i.episode ?: (index + 1),
@ -382,13 +388,12 @@ class ResultViewModel : ViewModel() {
null, null,
) )
) )
} }
updateEpisodes(context, mainId, episodes, -1) updateEpisodes(mainId, episodes, -1)
} }
is MovieLoadResponse -> { is MovieLoadResponse -> {
updateEpisodes( buildResultEpisode(
context, mainId, arrayListOf(
context.buildResultEpisode(
d.name, d.name,
null, null,
0, 0,
@ -400,14 +405,14 @@ class ResultViewModel : ViewModel() {
null, null,
null, null,
null, null,
) ).let {
), -1 updateEpisodes(mainId, listOf(it), -1)
) }
} }
is TorrentLoadResponse -> { is TorrentLoadResponse -> {
updateEpisodes( updateEpisodes(
context, mainId, arrayListOf( mainId, listOf(
context.buildResultEpisode( buildResultEpisode(
d.name, d.name,
null, null,
0, 0,

View file

@ -48,7 +48,9 @@ class SearchFragment : Fragment() {
fun List<SearchResponse>.filterSearchResponse(): List<SearchResponse> { fun List<SearchResponse>.filterSearchResponse(): List<SearchResponse> {
return this.filter { response -> return this.filter { response ->
if (response is AnimeSearchResponse) { 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 { } else {
true true
} }
@ -291,16 +293,14 @@ class SearchFragment : Fragment() {
} }
} }
if(context?.isTvSettings() == true) { if (context?.isTvSettings() == true) {
search_filter.isFocusable = true search_filter.isFocusable = true
search_filter.isFocusableInTouchMode = true search_filter.isFocusableInTouchMode = true
} }
main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener { main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean { override fun onQueryTextSubmit(query: String): Boolean {
context?.let { ctx -> searchViewModel.searchAndCancel(query = query)
searchViewModel.searchAndCancel(query = query, context = ctx)
}
main_search?.let { main_search?.let {
hideKeyboard(it) hideKeyboard(it)

View file

@ -1,6 +1,5 @@
package com.lagradost.cloudstream3.ui.search package com.lagradost.cloudstream3.ui.search
import android.content.Context
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
@ -41,9 +40,9 @@ class SearchViewModel : ViewModel() {
} }
var onGoingSearch: Job? = null 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?.cancel()
onGoingSearch = search(query, isMainApis, ignoreSettings, context) onGoingSearch = search(query, isMainApis, ignoreSettings)
} }
data class SyncSearchResultSearchResponse( 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) { if (query.length <= 1) {
clearSearch() clearSearch()
return@launch return@launch
@ -90,7 +90,8 @@ class SearchViewModel : ViewModel() {
} else { } else {
syncApis.apmap { a -> syncApis.apmap { a ->
val search = safeApiCall { 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)) currentList.add(OnGoingSearch(a.name, search))

View file

@ -6,6 +6,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Environment import android.os.Environment
import android.widget.ImageView 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.getApiSettings
import com.lagradost.cloudstream3.APIHolder.restrictedApis import com.lagradost.cloudstream3.APIHolder.restrictedApis
import com.lagradost.cloudstream3.AcraApplication import com.lagradost.cloudstream3.AcraApplication
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.MainActivity.Companion.setLocale import com.lagradost.cloudstream3.MainActivity.Companion.setLocale
import com.lagradost.cloudstream3.MainActivity.Companion.showToast 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.syncproviders.OAuth2API.Companion.malApi
import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment 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.HOMEPAGE_API
import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate
import com.lagradost.cloudstream3.utils.Qualities 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.UIHelper.setImage
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath
import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadDir import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadDir
import com.lagradost.cloudstream3.utils.VideoDownloadManager.isScopedStorage
import java.io.File import java.io.File
import kotlin.concurrent.thread 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 ).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top
private fun showAccountSwitch(context: Context, api: AccountManager) { private fun showAccountSwitch(context: Context, api: AccountManager) {
val accounts = api.getAccounts() ?: return
val builder = val builder =
AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_switch) AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_switch)
val dialog = builder.show() val dialog = builder.show()
val accounts = api.getAccounts(context)
dialog.findViewById<TextView>(R.id.account_add)?.setOnClickListener { dialog.findViewById<TextView>(R.id.account_add)?.setOnClickListener {
api.authenticate(it.context) api.authenticate()
} }
val ogIndex = api.accountIndex val ogIndex = api.accountIndex
@ -141,7 +141,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
for (index in accounts) { for (index in accounts) {
api.accountIndex = index api.accountIndex = index
val accountInfo = api.loginInfo(context) val accountInfo = api.loginInfo()
if (accountInfo != null) { if (accountInfo != null) {
items.add(accountInfo) items.add(accountInfo)
} }
@ -149,26 +149,26 @@ class SettingsFragment : PreferenceFragmentCompat() {
api.accountIndex = ogIndex api.accountIndex = ogIndex
val adapter = AccountAdapter(items, R.layout.account_single) { val adapter = AccountAdapter(items, R.layout.account_single) {
dialog?.dismissSafe(activity) 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) val list = dialog.findViewById<RecyclerView>(R.id.account_list)
list?.adapter = adapter list?.adapter = adapter
} }
private fun showLoginInfo(context: Context, api: AccountManager, info: OAuth2API.LoginInfo) { private fun showLoginInfo(api: AccountManager, info: OAuth2API.LoginInfo) {
val builder = 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() val dialog = builder.show()
dialog.findViewById<ImageView>(R.id.account_profile_picture)?.setImage(info.profilePicture) dialog.findViewById<ImageView>(R.id.account_profile_picture)?.setImage(info.profilePicture)
dialog.findViewById<TextView>(R.id.account_logout)?.setOnClickListener { dialog.findViewById<TextView>(R.id.account_logout)?.setOnClickListener {
it.context?.let { ctx -> api.logOut()
api.logOut(ctx)
dialog.dismissSafe(activity) 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_site)?.text = api.name
dialog.findViewById<TextView>(R.id.account_switch_account)?.setOnClickListener { dialog.findViewById<TextView>(R.id.account_switch_account)?.setOnClickListener {
dialog.dismissSafe(activity) dialog.dismissSafe(activity)
@ -208,11 +208,11 @@ class SettingsFragment : PreferenceFragmentCompat() {
title = getString(R.string.login_format).format(api.name, getString(R.string.account)) title = getString(R.string.login_format).format(api.name, getString(R.string.account))
setOnPreferenceClickListener { pref -> setOnPreferenceClickListener { pref ->
pref.context?.let { ctx -> pref.context?.let { ctx ->
val info = api.loginInfo(ctx) val info = api.loginInfo()
if (info != null) { if (info != null) {
showLoginInfo(ctx, api, info) showLoginInfo(api, info)
} else { } 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. // app_name_download_path = Cloudstream and does not change depending on release.
// DOES NOT WORK ON SCOPED STORAGE. // 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) File.separator + resources.getString(R.string.app_name_download_path)
val first = listOf(defaultDir, secondaryDir) val first = listOf(defaultDir, secondaryDir)
return (try { return (try {
@ -352,7 +352,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
val currentPrefMedia = val currentPrefMedia =
settingsManager.getInt(getString(R.string.preferred_media_settings), 0) settingsManager.getInt(getString(R.string.prefer_media_type_key), 0)
activity?.showBottomDialog( activity?.showBottomDialog(
prefNames.toList(), prefNames.toList(),
@ -361,15 +361,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
true, true,
{}) { {}) {
settingsManager.edit() settingsManager.edit()
.putInt(getString(R.string.preferred_media_settings), prefValues[it]) .putInt(getString(R.string.prefer_media_type_key), prefValues[it])
.apply() .apply()
val apilist = AppUtils.filterProviderByPreferredMedia(apis, prefValues[it])
val apiRandom = if (apilist.size > 0) { removeKey(HOMEPAGE_API)
apilist.random().name
} else {
""
}
context?.setKey(HOMEPAGE_API, apiRandom)
(context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) } (context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) }
} }
return@setOnPreferenceClickListener true return@setOnPreferenceClickListener true

View file

@ -19,10 +19,11 @@ import androidx.fragment.app.Fragment
import com.google.android.exoplayer2.text.Cue import com.google.android.exoplayer2.text.Cue
import com.google.android.exoplayer2.ui.CaptionStyleCompat import com.google.android.exoplayer2.ui.CaptionStyleCompat
import com.jaredrummler.android.colorpicker.ColorPickerDialog 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
import com.lagradost.cloudstream3.MainActivity.Companion.showToast import com.lagradost.cloudstream3.MainActivity.Companion.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
@ -87,8 +88,8 @@ class SubtitlesFragment : Fragment() {
this.setKey(SUBTITLE_KEY, style) this.setKey(SUBTITLE_KEY, style)
} }
fun Context.getCurrentSavedStyle(): SaveCaptionStyle { fun getCurrentSavedStyle(): SaveCaptionStyle {
return this.getKey(SUBTITLE_KEY) ?: SaveCaptionStyle( return getKey(SUBTITLE_KEY) ?: SaveCaptionStyle(
getDefColor(0), getDefColor(0),
getDefColor(2), getDefColor(2),
getDefColor(3), getDefColor(3),
@ -109,11 +110,11 @@ class SubtitlesFragment : Fragment() {
return TypedValue.applyDimension(unit, size, metrics).toInt() 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") return getKey(SUBTITLE_DOWNLOAD_KEY) ?: listOf("en")
} }
fun Context.getAutoSelectLanguageISO639_1(): String { fun getAutoSelectLanguageISO639_1(): String {
return getKey(SUBTITLE_AUTO_SELECT_KEY) ?: "en" return getKey(SUBTITLE_AUTO_SELECT_KEY) ?: "en"
} }
} }
@ -184,7 +185,7 @@ class SubtitlesFragment : Fragment() {
context?.fixPaddingStatusbar(subs_root) context?.fixPaddingStatusbar(subs_root)
state = requireContext().getCurrentSavedStyle() state = getCurrentSavedStyle()
context?.updateState() context?.updateState()
fun View.setup(id: Int) { fun View.setup(id: Int) {
@ -381,17 +382,17 @@ class SubtitlesFragment : Fragment() {
val lang639_1 = langMap.map { it.ISO_639_1 } val lang639_1 = langMap.map { it.ISO_639_1 }
activity?.showDialog( activity?.showDialog(
langMap.map { it.languageName }, langMap.map { it.languageName },
lang639_1.indexOf(textView.context.getAutoSelectLanguageISO639_1()), lang639_1.indexOf(getAutoSelectLanguageISO639_1()),
(textView as TextView).text.toString(), (textView as TextView).text.toString(),
true, true,
dismissCallback dismissCallback
) { index -> ) { 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 -> subs_auto_select_language.setOnLongClickListener {
textView.context.setKey(SUBTITLE_AUTO_SELECT_KEY, "en") setKey(SUBTITLE_AUTO_SELECT_KEY, "en")
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true return@setOnLongClickListener true
} }
@ -399,7 +400,7 @@ class SubtitlesFragment : Fragment() {
subs_download_languages.setOnClickListener { textView -> subs_download_languages.setOnClickListener { textView ->
val langMap = SubtitleHelper.languages val langMap = SubtitleHelper.languages
val lang639_1 = langMap.map { it.ISO_639_1 } 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 } val keyMap = keys.map { lang639_1.indexOf(it) }.filter { it >= 0 }
activity?.showMultiDialog( activity?.showMultiDialog(
@ -408,12 +409,12 @@ class SubtitlesFragment : Fragment() {
(textView as TextView).text.toString(), (textView as TextView).text.toString(),
dismissCallback dismissCallback
) { indexList -> ) { 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 -> subs_download_languages.setOnLongClickListener {
textView.context.setKey(SUBTITLE_DOWNLOAD_KEY, listOf("en")) setKey(SUBTITLE_DOWNLOAD_KEY, listOf("en"))
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT) showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
return@setOnLongClickListener true return@setOnLongClickListener true

View file

@ -21,6 +21,7 @@ import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.GoogleApiAvailability
import com.google.android.gms.common.wrappers.Wrappers import com.google.android.gms.common.wrappers.Wrappers
import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.navigate
import java.net.URL import java.net.URL
@ -46,6 +47,7 @@ object AppUtils {
} }
fun Context.openBrowser(url: String) { fun Context.openBrowser(url: String) {
try {
val components = arrayOf(ComponentName(applicationContext, MainActivity::class.java)) val components = arrayOf(ComponentName(applicationContext, MainActivity::class.java))
val intent = Intent(Intent.ACTION_VIEW) val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(url) intent.data = Uri.parse(url)
@ -57,6 +59,9 @@ object AppUtils {
) )
else else
startActivity(intent) startActivity(intent)
} catch (e : Exception) {
logError(e)
}
} }
fun splitQuery(url: URL): Map<String, String> { fun splitQuery(url: URL): Map<String, String> {
@ -233,23 +238,4 @@ object AppUtils {
} }
return currentAudioFocusRequest 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
}
}
} }

View file

@ -40,7 +40,9 @@ object CastHelper {
(epData.name ?: "Episode ${epData.episode}") + " - ${link.name}" (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 val srcPoster = epData.poster ?: holder.poster
if (srcPoster != null) { if (srcPoster != null) {
@ -58,7 +60,7 @@ object CastHelper {
val builder = MediaInfo.Builder(link.url) val builder = MediaInfo.Builder(link.url)
.setStreamType(MediaInfo.STREAM_TYPE_BUFFERED) .setStreamType(MediaInfo.STREAM_TYPE_BUFFERED)
.setContentType(MimeTypes.VIDEO_UNKNOWN) .setContentType(if (link.isM3u8) MimeTypes.APPLICATION_M3U8 else MimeTypes.VIDEO_MP4)
.setMetadata(movieMetadata) .setMetadata(movieMetadata)
.setMediaTracks(tracks) .setMediaTracks(tracks)
data?.let { data?.let {
@ -95,8 +97,8 @@ object CastHelper {
subtitles: List<SubtitleFile>, subtitles: List<SubtitleFile>,
startIndex: Int? = null, startIndex: Int? = null,
startTime: Long? = null, startTime: Long? = null,
) : Boolean { ): Boolean {
if(this == null) return false if (this == null) return false
if (episodes.isEmpty()) return false if (episodes.isEmpty()) return false
if (currentLinks.size <= currentEpisodeIndex) return false if (currentLinks.size <= currentEpisodeIndex) return false
@ -105,13 +107,15 @@ object CastHelper {
val holder = val holder =
MetadataHolder(apiName, isMovie, title, poster, currentEpisodeIndex, episodes, currentLinks, subtitles) MetadataHolder(apiName, isMovie, title, poster, currentEpisodeIndex, episodes, currentLinks, subtitles)
val index = if(startIndex == null || startIndex < 0) 0 else startIndex val index = if (startIndex == null || startIndex < 0) 0 else startIndex
val mediaItem = val mediaItem =
getMediaInfo(epData, holder, index, JSONObject(mapper.writeValueAsString(holder)), subtitles) getMediaInfo(epData, holder, index, JSONObject(mapper.writeValueAsString(holder)), subtitles)
awaitLinks( 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) if (currentLinks.size > index + 1)
startCast( startCast(

View file

@ -1,19 +1,20 @@
package com.lagradost.cloudstream3.utils 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.SearchResponse
import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.ui.WatchType 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 VIDEO_POS_DUR = "video_pos_dur"
const val RESULT_WATCH_STATE = "result_watch_state" const val RESULT_WATCH_STATE = "result_watch_state"
const val RESULT_WATCH_STATE_DATA = "result_watch_state_data" const val RESULT_WATCH_STATE_DATA = "result_watch_state_data"
const val RESULT_RESUME_WATCHING = "result_resume_watching" const val RESULT_RESUME_WATCHING = "result_resume_watching"
const val RESULT_SEASON = "result_season" const val RESULT_SEASON = "result_season"
const val RESULT_DUB = "result_dub"
object DataStoreHelper { object DataStoreHelper {
data class PosDur(val position: Long, val duration: Long) data class PosDur(val position: Long, val duration: Long)
@ -57,21 +58,21 @@ object DataStoreHelper {
var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION var currentAccount: String = "0" //TODO ACCOUNT IMPLEMENTATION
fun Context.getAllWatchStateIds(): List<Int> { fun getAllWatchStateIds(): List<Int>? {
val folder = "$currentAccount/$RESULT_WATCH_STATE" val folder = "$currentAccount/$RESULT_WATCH_STATE"
return getKeys(folder).mapNotNull { return getKeys(folder)?.mapNotNull {
it.removePrefix("$folder/").toIntOrNull() it.removePrefix("$folder/").toIntOrNull()
} }
} }
fun Context.getAllResumeStateIds(): List<Int> { fun getAllResumeStateIds(): List<Int>? {
val folder = "$currentAccount/$RESULT_RESUME_WATCHING" val folder = "$currentAccount/$RESULT_RESUME_WATCHING"
return getKeys(folder).mapNotNull { return getKeys(folder)?.mapNotNull {
it.removePrefix("$folder/").toIntOrNull() it.removePrefix("$folder/").toIntOrNull()
} }
} }
fun Context.setLastWatched( fun setLastWatched(
parentId: Int?, parentId: Int?,
episodeId: Int?, episodeId: Int?,
episode: Int?, episode: Int?,
@ -93,12 +94,12 @@ object DataStoreHelper {
) )
} }
fun Context.removeLastWatched(parentId: Int?) { fun removeLastWatched(parentId: Int?) {
if (parentId == null) return if (parentId == null) return
removeKey("$currentAccount/$RESULT_RESUME_WATCHING", parentId.toString()) 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 if (id == null) return null
return getKey( return getKey(
"$currentAccount/$RESULT_RESUME_WATCHING", "$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 if (id == null) return
setKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString(), data) 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 if (id == null) return null
return getKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) 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 (id == null) return
if (dur < 10_000) return // too short if (dur < 10_000) return // too short
setKey("$currentAccount/$VIDEO_POS_DUR", id.toString(), PosDur(pos, dur)) 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) 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 if (id == null) return
val folder = "$currentAccount/$RESULT_WATCH_STATE" val folder = "$currentAccount/$RESULT_WATCH_STATE"
if (status == WatchType.NONE.internalId) { 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)) return WatchType.fromInternalId(getKey<Int>("$currentAccount/$RESULT_WATCH_STATE", id.toString(), null))
} }
fun Context.getResultSeason(id: Int): Int { fun getResultSeason(id: Int): Int {
return getKey("$currentAccount/$RESULT_SEASON", id.toString(), -1)!! 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) 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) 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 -> return idPrefixes.map { idPrefix ->
getKey("${idPrefix}_sync", id.toString()) getKey("${idPrefix}_sync", id.toString())
} }

View file

@ -5,10 +5,10 @@ import android.content.Context
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.ForegroundInfo import androidx.work.ForegroundInfo
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getKey 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_INFO
import com.lagradost.cloudstream3.utils.VideoDownloadManager.WORK_KEY_PACKAGE import com.lagradost.cloudstream3.utils.VideoDownloadManager.WORK_KEY_PACKAGE
import com.lagradost.cloudstream3.utils.VideoDownloadManager.downloadCheck 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) { private fun removeKeys(key: String) {
applicationContext.removeKey(WORK_KEY_INFO, key) removeKey(WORK_KEY_INFO, key)
applicationContext.removeKey(WORK_KEY_PACKAGE, key) removeKey(WORK_KEY_PACKAGE, key)
} }
private suspend fun awaitDownload(id: Int) { private suspend fun awaitDownload(id: Int) {

View file

@ -22,6 +22,8 @@ import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager import androidx.work.WorkManager
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.hippo.unifile.UniFile 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.MainActivity
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.logError 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.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.removeKey import com.lagradost.cloudstream3.utils.DataStore.removeKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -150,16 +151,16 @@ object VideoDownloadManager {
private const val SUCCESS_DOWNLOAD_DONE = 1 private const val SUCCESS_DOWNLOAD_DONE = 1
private const val SUCCESS_STREAM = 3 private const val SUCCESS_STREAM = 3
private const val SUCCESS_STOPPED = 2 private const val SUCCESS_STOPPED = 2
private const val ERROR_DELETING_FILE = // will not download the next one, but is still classified as an error
3 // 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_CREATE_FILE = -2
private const val ERROR_UNKNOWN = -10 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_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_CONNECTION_ERROR = -6
private const val ERROR_MEDIA_STORE_URI_CANT_BE_CREATED = -7 //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_CANT_OPEN_STREAM = -8
private const val ERROR_CONTENT_RESOLVER_NOT_FOUND = -9 private const val ERROR_CONTENT_RESOLVER_NOT_FOUND = -9
private const val KEY_RESUME_PACKAGES = "download_resume" private const val KEY_RESUME_PACKAGES = "download_resume"
@ -460,7 +461,7 @@ object VideoDownloadManager {
val base = basePathToFile(context, basePath) val base = basePathToFile(context, basePath)
val folder = base?.gotoDir(relativePath, false) 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) return context.contentResolver?.getExistingFolderStartName(relativePath)
} else { } else {
// val normalPath = // val normalPath =
@ -529,8 +530,6 @@ object VideoDownloadManager {
} }
} }
val isScopedStorage = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
data class CreateNotificationMetadata( data class CreateNotificationMetadata(
val type: DownloadType, val type: DownloadType,
val bytesDownloaded: Long, val bytesDownloaded: Long,
@ -561,7 +560,7 @@ object VideoDownloadManager {
var resume = tryResume var resume = tryResume
val baseFile = context.getBasePath() 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 cr = context.contentResolver ?: return StreamData(ERROR_CONTENT_RESOLVER_NOT_FOUND)
val currentExistingFile = val currentExistingFile =
@ -658,7 +657,7 @@ object VideoDownloadManager {
val displayName = getDisplayName(name, extension) val displayName = getDisplayName(name, extension)
val relativePath = 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 { fun deleteFile(): Int {
return delete(context, name, relativePath, extension, parentId, basePath.first) 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 if (extension == "mp4" && bytesTotal < 5000000) return ERROR_TOO_SMALL_CONNECTION // DATA IS LESS THAN 5MB, SOMETHING IS WRONG
parentId?.let { parentId?.let {
context.setKey( setKey(
KEY_DOWNLOAD_INFO, KEY_DOWNLOAD_INFO,
it.toString(), it.toString(),
DownloadedFileInfo( DownloadedFileInfo(
@ -798,8 +797,8 @@ object VideoDownloadManager {
} }
DownloadActionType.Stop -> { DownloadActionType.Stop -> {
isStopped = true; updateNotification() isStopped = true; updateNotification()
context.removeKey(KEY_RESUME_PACKAGES, event.first.toString()) removeKey(KEY_RESUME_PACKAGES, event.first.toString())
saveQueue(context) saveQueue()
} }
DownloadActionType.Resume -> { DownloadActionType.Resume -> {
isPaused = false; updateNotification() isPaused = false; updateNotification()
@ -1025,7 +1024,7 @@ object VideoDownloadManager {
val displayName = getDisplayName(name, extension) val displayName = getDisplayName(name, extension)
// If scoped storage and using download dir (not accessible with UniFile) // 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 relativePath = getRelativePath(folder)
val lastContent = val lastContent =
context.contentResolver.getExistingDownloadUriOrNullQ(relativePath, displayName) context.contentResolver.getExistingDownloadUriOrNullQ(relativePath, displayName)
@ -1081,7 +1080,7 @@ object VideoDownloadManager {
val basePath = context.getBasePath() val basePath = context.getBasePath()
val relativePath = 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) val stream = setupStream(context, name, relativePath, extension, realIndex > 0)
if (stream.errorCode != SUCCESS_STREAM) return stream.errorCode if (stream.errorCode != SUCCESS_STREAM) return stream.errorCode
@ -1119,7 +1118,7 @@ object VideoDownloadManager {
fun updateInfo() { fun updateInfo() {
parentId?.let { parentId?.let {
context.setKey( setKey(
KEY_DOWNLOAD_INFO, KEY_DOWNLOAD_INFO,
it.toString(), it.toString(),
DownloadedFileInfo( DownloadedFileInfo(
@ -1364,7 +1363,7 @@ object VideoDownloadManager {
val link = item.links[index] val link = item.links[index]
val resume = pkg.linkIndex == index val resume = pkg.linkIndex == index
context.setKey( setKey(
KEY_RESUME_PACKAGES, KEY_RESUME_PACKAGES,
id.toString(), id.toString(),
DownloadResumePackage(item, index) DownloadResumePackage(item, index)
@ -1383,7 +1382,7 @@ object VideoDownloadManager {
} }
} }
if (connectionResult != null && connectionResult > 0) { // SUCCESS if (connectionResult != null && connectionResult > 0) { // SUCCESS
context.removeKey(KEY_RESUME_PACKAGES, id.toString()) removeKey(KEY_RESUME_PACKAGES, id.toString())
break break
} }
} }
@ -1410,7 +1409,7 @@ object VideoDownloadManager {
context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, id.toString()) ?: return null context.getKey<DownloadedFileInfo>(KEY_DOWNLOAD_INFO, id.toString()) ?: return null
val base = basePathToFile(context, info.basePath) 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 cr = context.contentResolver ?: return null
val fileUri = val fileUri =
cr.getExistingDownloadUriOrNullQ(info.relativePath, info.displayName) ?: return null cr.getExistingDownloadUriOrNullQ(info.relativePath, info.displayName) ?: return null
@ -1457,7 +1456,7 @@ object VideoDownloadManager {
downloadStatusEvent.invoke(Pair(id, DownloadType.IsStopped)) downloadStatusEvent.invoke(Pair(id, DownloadType.IsStopped))
downloadDeleteEvent.invoke(id) downloadDeleteEvent.invoke(id)
val base = basePathToFile(context, info.basePath) 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 cr = context.contentResolver ?: return false
val fileUri = val fileUri =
cr.getExistingDownloadUriOrNullQ(info.relativePath, info.displayName) cr.getExistingDownloadUriOrNullQ(info.relativePath, info.displayName)
@ -1505,7 +1504,7 @@ object VideoDownloadManager {
// } // }
downloadQueue.addLast(pkg) downloadQueue.addLast(pkg)
downloadCheck(context, notificationCallback) downloadCheck(context, notificationCallback)
if (setKey) saveQueue(context) if (setKey) saveQueue()
} else { } else {
downloadEvent.invoke( downloadEvent.invoke(
Pair(pkg.item.ep.id, DownloadActionType.Resume) Pair(pkg.item.ep.id, DownloadActionType.Resume)
@ -1513,12 +1512,12 @@ object VideoDownloadManager {
} }
} }
private fun saveQueue(context: Context) { private fun saveQueue() {
val dQueue = val dQueue =
downloadQueue.toList() downloadQueue.toList()
.mapIndexed { index, any -> DownloadQueueResumePackage(index, any) } .mapIndexed { index, any -> DownloadQueueResumePackage(index, any) }
.toTypedArray() .toTypedArray()
context.setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue) setKey(KEY_RESUME_QUEUE_PACKAGES, dQueue)
} }
/*fun isMyServiceRunning(context: Context, serviceClass: Class<*>): Boolean { /*fun isMyServiceRunning(context: Context, serviceClass: Class<*>): Boolean {
@ -1576,7 +1575,7 @@ object VideoDownloadManager {
pkg: DownloadResumePackage, pkg: DownloadResumePackage,
) { ) {
val key = pkg.item.ep.id.toString() val key = pkg.item.ep.id.toString()
context.setKey(WORK_KEY_PACKAGE, key, pkg) setKey(WORK_KEY_PACKAGE, key, pkg)
startWork(context, key) startWork(context, key)
} }
@ -1596,7 +1595,7 @@ object VideoDownloadManager {
) )
val key = info.ep.id.toString() val key = info.ep.id.toString()
context.setKey(WORK_KEY_INFO, key, info) setKey(WORK_KEY_INFO, key, info)
startWork(context, key) startWork(context, key)
} }

View file

@ -1,6 +1,5 @@
package com.lagradost.cloudstream3 package com.lagradost.cloudstream3
import com.lagradost.cloudstream3.movieproviders.ThenosProvider
import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.Qualities
import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.SubtitleHelper