forked from recloudstream/cloudstream
		
	removed context (no user change) + small bug fixes
This commit is contained in:
		
							parent
							
								
									2580455cd5
								
							
						
					
					
						commit
						a96b396307
					
				
					 28 changed files with 525 additions and 468 deletions
				
			
		|  | @ -33,7 +33,7 @@ android { | ||||||
|     defaultConfig { |     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" | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -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*/ | ||||||
|  | @ -539,7 +558,9 @@ fun LoadResponse.setDuration(input : String?) { | ||||||
|         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 -> | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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?, | ||||||
|  |  | ||||||
|  | @ -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? | ||||||
| } | } | ||||||
|  | @ -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 { | ||||||
|  |  | ||||||
|  | @ -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") | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | @ -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, | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|                         ) |                         ) | ||||||
|                     ) |                     ) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -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() | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
|  | @ -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,7 +129,7 @@ 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)) | ||||||
|  | @ -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) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|                 } |                 } | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -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,18 +189,23 @@ 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) | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  | @ -207,13 +214,13 @@ class ResultViewModel : ViewModel() { | ||||||
| 
 | 
 | ||||||
|         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, | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|                 } |                 } | ||||||
|  | @ -298,9 +300,7 @@ class SearchFragment : Fragment() { | ||||||
| 
 | 
 | ||||||
|         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) | ||||||
|  |  | ||||||
|  | @ -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)) | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  | @ -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 { | ||||||
|  | @ -111,7 +113,9 @@ object CastHelper { | ||||||
|             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( | ||||||
|  |  | ||||||
|  | @ -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()) | ||||||
|         } |         } | ||||||
|  |  | ||||||
|  | @ -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) { | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue