mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Clean up and mark questionable code issues (#1209)
This commit is contained in:
parent
0aa48f335a
commit
04dda008c4
97 changed files with 563 additions and 721 deletions
|
@ -35,6 +35,7 @@ import java.io.File
|
|||
import java.io.FileNotFoundException
|
||||
import java.io.PrintStream
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.Locale
|
||||
import kotlin.concurrent.thread
|
||||
import kotlin.system.exitProcess
|
||||
|
||||
|
@ -81,14 +82,8 @@ class ExceptionHandler(val errorFile: File, val onError: (() -> Unit)) :
|
|||
ACRA.errorReporter.handleException(error)
|
||||
try {
|
||||
PrintStream(errorFile).use { ps ->
|
||||
ps.println(String.format("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}"))
|
||||
ps.println(
|
||||
String.format(
|
||||
"Fatal exception on thread %s (%d)",
|
||||
thread.name,
|
||||
thread.id
|
||||
)
|
||||
)
|
||||
ps.println("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}")
|
||||
ps.println("Fatal exception on thread ${thread.name} (${thread.id})")
|
||||
error.printStackTrace(ps)
|
||||
}
|
||||
} catch (ignored: FileNotFoundException) {
|
||||
|
@ -106,7 +101,6 @@ class AcraApplication : Application() {
|
|||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
//NativeCrashHandler.initCrashHandler()
|
||||
ExceptionHandler(filesDir.resolve("last_error")) {
|
||||
val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName)
|
||||
startActivity(Intent.makeRestartActivityTask(intent!!.component))
|
||||
|
|
|
@ -164,7 +164,7 @@ object CommonActivity {
|
|||
val toast = Toast(act)
|
||||
toast.duration = duration ?: Toast.LENGTH_SHORT
|
||||
toast.setGravity(Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM, 0, 5.toPx)
|
||||
toast.view = binding.root
|
||||
toast.view = binding.root //fixme Find an alternative using default Toasts since custom toasts are deprecated and won't appear with api30 set as minSDK version.
|
||||
currentToast = toast
|
||||
toast.show()
|
||||
|
||||
|
@ -464,20 +464,6 @@ object CommonActivity {
|
|||
|
||||
|
||||
fun onKeyDown(act: Activity?, keyCode: Int, event: KeyEvent?) {
|
||||
//println("Keycode: $keyCode")
|
||||
//showToast(
|
||||
// this,
|
||||
// "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}",
|
||||
// Toast.LENGTH_LONG
|
||||
//)
|
||||
|
||||
// Tested keycodes on remote:
|
||||
// KeyEvent.KEYCODE_MEDIA_FAST_FORWARD
|
||||
// KeyEvent.KEYCODE_MEDIA_REWIND
|
||||
// KeyEvent.KEYCODE_MENU
|
||||
// KeyEvent.KEYCODE_MEDIA_NEXT
|
||||
// KeyEvent.KEYCODE_MEDIA_PREVIOUS
|
||||
// KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE
|
||||
|
||||
// 149 keycode_numpad 5
|
||||
when (keyCode) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import java.util.concurrent.TimeUnit
|
|||
|
||||
|
||||
class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Downloader() {
|
||||
private val client: OkHttpClient
|
||||
private val client: OkHttpClient = builder.readTimeout(30, TimeUnit.SECONDS).build()
|
||||
override fun execute(request: Request): Response {
|
||||
val httpMethod: String = request.httpMethod()
|
||||
val url: String = request.url()
|
||||
|
@ -74,8 +74,4 @@ class DownloaderTestImpl private constructor(builder: OkHttpClient.Builder) : Do
|
|||
return instance
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
client = builder.readTimeout(30, TimeUnit.SECONDS).build()
|
||||
}
|
||||
}
|
|
@ -82,13 +82,13 @@ import com.lagradost.cloudstream3.plugins.PluginManager.loadAllOnlinePlugins
|
|||
import com.lagradost.cloudstream3.plugins.PluginManager.loadSinglePlugin
|
||||
import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver
|
||||
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_PLAYER
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_REPO
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_RESUME_WATCHING
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_SEARCH
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.OAuth2Apis
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appString
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringPlayer
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringRepo
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringSearch
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.inAppAuths
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.localListApi
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
||||
|
@ -347,7 +347,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
println("Repository url: $realUrl")
|
||||
loadRepository(realUrl)
|
||||
return true
|
||||
} else if (str.contains(appString)) {
|
||||
} else if (str.contains(APP_STRING)) {
|
||||
for (api in OAuth2Apis) {
|
||||
if (str.contains("/${api.redirectUrl}")) {
|
||||
ioSafe {
|
||||
|
@ -377,15 +377,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
}
|
||||
// This specific intent is used for the gradle deployWithAdb
|
||||
// https://github.com/recloudstream/gradle/blob/master/src/main/kotlin/com/lagradost/cloudstream3/gradle/tasks/DeployWithAdbTask.kt#L46
|
||||
if (str == "$appString:") {
|
||||
if (str == "$APP_STRING:") {
|
||||
PluginManager.hotReloadAllLocalPlugins(activity)
|
||||
}
|
||||
} else if (safeURI(str)?.scheme == appStringRepo) {
|
||||
val url = str.replaceFirst(appStringRepo, "https")
|
||||
} else if (safeURI(str)?.scheme == APP_STRING_REPO) {
|
||||
val url = str.replaceFirst(APP_STRING_REPO, "https")
|
||||
loadRepository(url)
|
||||
return true
|
||||
} else if (safeURI(str)?.scheme == appStringSearch) {
|
||||
val query = str.substringAfter("$appStringSearch://")
|
||||
} else if (safeURI(str)?.scheme == APP_STRING_SEARCH) {
|
||||
val query = str.substringAfter("$APP_STRING_SEARCH://")
|
||||
nextSearchQuery =
|
||||
try {
|
||||
URLDecoder.decode(query, "UTF-8")
|
||||
|
@ -399,7 +399,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
R.id.navigation_search
|
||||
activity?.findViewById<NavigationRailView>(R.id.nav_rail_view)?.selectedItemId =
|
||||
R.id.navigation_search
|
||||
} else if (safeURI(str)?.scheme == appStringPlayer) {
|
||||
} else if (safeURI(str)?.scheme == APP_STRING_PLAYER) {
|
||||
val uri = Uri.parse(str)
|
||||
val name = uri.getQueryParameter("name")
|
||||
val url = URLDecoder.decode(uri.authority, "UTF-8")
|
||||
|
@ -413,9 +413,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
)
|
||||
)
|
||||
)
|
||||
} else if (safeURI(str)?.scheme == appStringResumeWatching) {
|
||||
} else if (safeURI(str)?.scheme == APP_STRING_RESUME_WATCHING) {
|
||||
val id =
|
||||
str.substringAfter("$appStringResumeWatching://").toIntOrNull()
|
||||
str.substringAfter("$APP_STRING_RESUME_WATCHING://").toIntOrNull()
|
||||
?: return false
|
||||
ioSafe {
|
||||
val resumeWatchingCard =
|
||||
|
@ -469,7 +469,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
) DubStatus.Dubbed else DubStatus.Subbed, null
|
||||
)
|
||||
} else {
|
||||
viewModel.loadSmall(this, result)
|
||||
viewModel.loadSmall(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -605,7 +605,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
}
|
||||
|
||||
//private var mCastSession: CastSession? = null
|
||||
lateinit var mSessionManager: SessionManager
|
||||
var mSessionManager: SessionManager? = null
|
||||
private val mSessionManagerListener: SessionManagerListener<Session> by lazy { SessionManagerListenerImpl() }
|
||||
|
||||
private inner class SessionManagerListenerImpl : SessionManagerListener<Session> {
|
||||
|
@ -645,8 +645,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
setActivityInstance(this)
|
||||
try {
|
||||
if (isCastApiAvailable()) {
|
||||
//mCastSession = mSessionManager.currentCastSession
|
||||
mSessionManager.addSessionManagerListener(mSessionManagerListener)
|
||||
mSessionManager?.addSessionManagerListener(mSessionManagerListener)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
|
@ -662,7 +661,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
}
|
||||
try {
|
||||
if (isCastApiAvailable()) {
|
||||
mSessionManager.removeSessionManagerListener(mSessionManagerListener)
|
||||
mSessionManager?.removeSessionManagerListener(mSessionManagerListener)
|
||||
//mCastSession = null
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
@ -766,7 +765,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
list.forEach { custom ->
|
||||
allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
|
||||
?.let {
|
||||
allProviders.add(it.javaClass.newInstance().apply {
|
||||
allProviders.add(it.javaClass.getDeclaredConstructor().newInstance().apply {
|
||||
name = custom.name
|
||||
lang = custom.lang
|
||||
mainUrl = custom.url.trimEnd('/')
|
||||
|
@ -1147,7 +1146,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
super.onCreate(savedInstanceState)
|
||||
try {
|
||||
if (isCastApiAvailable()) {
|
||||
mSessionManager = CastContext.getSharedInstance(this).sessionManager
|
||||
CastContext.getSharedInstance(this) {it.run()}.addOnSuccessListener { mSessionManager = it.sessionManager }
|
||||
}
|
||||
} catch (t: Throwable) {
|
||||
logError(t)
|
||||
|
@ -1449,13 +1448,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
val value = viewModel.watchStatus.value ?: WatchType.NONE
|
||||
|
||||
this@MainActivity.showBottomDialog(
|
||||
WatchType.values().map { getString(it.stringRes) }.toList(),
|
||||
WatchType.entries.map { getString(it.stringRes) }.toList(),
|
||||
value.ordinal,
|
||||
this@MainActivity.getString(R.string.action_add_to_bookmarks),
|
||||
showApply = false,
|
||||
{}) {
|
||||
viewModel.updateWatchStatus(
|
||||
WatchType.values()[it],
|
||||
WatchType.entries[it],
|
||||
this@MainActivity
|
||||
)
|
||||
}
|
||||
|
@ -1465,12 +1464,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa
|
|||
?: SyncWatchType.NONE
|
||||
|
||||
this@MainActivity.showBottomDialog(
|
||||
SyncWatchType.values().map { getString(it.stringRes) }.toList(),
|
||||
SyncWatchType.entries.map { getString(it.stringRes) }.toList(),
|
||||
value.ordinal,
|
||||
this@MainActivity.getString(R.string.action_add_to_bookmarks),
|
||||
showApply = false,
|
||||
{}) {
|
||||
syncViewModel.setStatus(SyncWatchType.values()[it].internalId)
|
||||
syncViewModel.setStatus(SyncWatchType.entries[it].internalId)
|
||||
syncViewModel.publishUserData()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,53 +0,0 @@
|
|||
package com.lagradost.cloudstream3
|
||||
|
||||
import com.lagradost.cloudstream3.MainActivity.Companion.lastError
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager.checkSafeModeFile
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
object NativeCrashHandler {
|
||||
// external fun triggerNativeCrash()
|
||||
/*private external fun initNativeCrashHandler()
|
||||
private external fun getSignalStatus(): Int
|
||||
|
||||
private fun initSignalPolling() = CoroutineScope(Dispatchers.IO).launch {
|
||||
|
||||
//launch {
|
||||
// delay(10000)
|
||||
// triggerNativeCrash()
|
||||
//}
|
||||
|
||||
while (true) {
|
||||
delay(10_000)
|
||||
val signal = getSignalStatus()
|
||||
// Signal is initialized to zero
|
||||
if (signal == 0) continue
|
||||
|
||||
// Do not crash in safe mode!
|
||||
if (lastError != null) continue
|
||||
if (checkSafeModeFile()) continue
|
||||
|
||||
AcraApplication.exceptionHandler?.uncaughtException(
|
||||
Thread.currentThread(),
|
||||
RuntimeException("Native crash with code: $signal. Try uninstalling extensions.\n")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun initCrashHandler() {
|
||||
try {
|
||||
System.loadLibrary("native-lib")
|
||||
initNativeCrashHandler()
|
||||
} catch (t: Throwable) {
|
||||
// Make debug crash.
|
||||
if (BuildConfig.DEBUG) throw t
|
||||
logError(t)
|
||||
return
|
||||
}
|
||||
|
||||
initSignalPolling()
|
||||
}*/
|
||||
}
|
|
@ -2,15 +2,13 @@ package com.lagradost.cloudstream3.metaproviders
|
|||
|
||||
import com.lagradost.cloudstream3.MainAPI
|
||||
import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
|
||||
import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
||||
|
||||
object SyncRedirector {
|
||||
val syncApis = SyncApis
|
||||
private val syncIds =
|
||||
listOf(
|
||||
SyncIdName.MyAnimeList to Regex("""myanimelist\.net\/anime\/(\d+)"""),
|
||||
SyncIdName.Anilist to Regex("""anilist\.co\/anime\/(\d+)""")
|
||||
SyncIdName.MyAnimeList to Regex("""myanimelist\.net/anime/(\d+)"""),
|
||||
SyncIdName.Anilist to Regex("""anilist\.co/anime/(\d+)""")
|
||||
)
|
||||
|
||||
suspend fun redirect(
|
||||
|
|
|
@ -296,7 +296,7 @@ open class TraktProvider : MainAPI() {
|
|||
return try {
|
||||
val format = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault())
|
||||
val dateTime = dateString?.let { format.parse(it)?.time } ?: return false
|
||||
APIHolder.unixTimeMS < dateTime
|
||||
unixTimeMS < dateTime
|
||||
} catch (t: Throwable) {
|
||||
logError(t)
|
||||
false
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.lagradost.cloudstream3.network
|
||||
|
||||
import android.util.Base64
|
||||
import android.util.Log
|
||||
import android.webkit.CookieManager
|
||||
import androidx.annotation.AnyThread
|
||||
|
@ -10,7 +9,10 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
|||
import com.lagradost.nicehttp.Requests.Companion.await
|
||||
import com.lagradost.nicehttp.cookies
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import okhttp3.*
|
||||
import okhttp3.Headers
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Request
|
||||
import okhttp3.Response
|
||||
import java.net.URI
|
||||
|
||||
|
||||
|
|
|
@ -2,5 +2,4 @@ package com.lagradost.cloudstream3.plugins
|
|||
|
||||
@Suppress("unused")
|
||||
@Target(AnnotationTarget.CLASS)
|
||||
annotation class CloudstreamPlugin(
|
||||
)
|
||||
annotation class CloudstreamPlugin
|
|
@ -34,7 +34,7 @@ abstract class Plugin {
|
|||
*/
|
||||
fun registerMainAPI(element: MainAPI) {
|
||||
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) MainAPI")
|
||||
element.sourcePlugin = this.__filename
|
||||
element.sourcePlugin = this.filename
|
||||
// Race condition causing which would case duplicates if not for distinctBy
|
||||
synchronized(APIHolder.allProviders) {
|
||||
APIHolder.allProviders.add(element)
|
||||
|
@ -48,7 +48,7 @@ abstract class Plugin {
|
|||
*/
|
||||
fun registerExtractorAPI(element: ExtractorApi) {
|
||||
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) ExtractorApi")
|
||||
element.sourcePlugin = this.__filename
|
||||
element.sourcePlugin = this.filename
|
||||
extractorApis.add(element)
|
||||
}
|
||||
|
||||
|
@ -68,7 +68,11 @@ abstract class Plugin {
|
|||
*/
|
||||
var resources: Resources? = null
|
||||
/** Full file path to the plugin. */
|
||||
var __filename: String? = null
|
||||
@Deprecated("Renamed to `filename` to follow conventions", replaceWith = ReplaceWith("filename"))
|
||||
var __filename: String?
|
||||
get() = filename
|
||||
set(value) {filename = value}
|
||||
var filename: String? = null
|
||||
|
||||
/**
|
||||
* This will add a button in the settings allowing you to add custom settings
|
||||
|
|
|
@ -1,13 +1,16 @@
|
|||
package com.lagradost.cloudstream3.plugins
|
||||
|
||||
import android.Manifest
|
||||
import android.app.*
|
||||
import android.content.Context
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.AssetManager
|
||||
import android.content.res.Resources
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
|
@ -163,7 +166,7 @@ object PluginManager {
|
|||
|
||||
private val LOCAL_PLUGINS_PATH = CLOUD_STREAM_FOLDER + "plugins"
|
||||
|
||||
public var currentlyLoading: String? = null
|
||||
var currentlyLoading: String? = null
|
||||
|
||||
// Maps filepath to plugin
|
||||
val plugins: MutableMap<String, Plugin> =
|
||||
|
@ -339,7 +342,7 @@ object PluginManager {
|
|||
|
||||
//Omit non-NSFW if mode is set to NSFW only
|
||||
if (mode == AutoDownloadMode.NsfwOnly) {
|
||||
if (tvtypes.contains(TvType.NSFW.name) == false) {
|
||||
if (!tvtypes.contains(TvType.NSFW.name)) {
|
||||
return@mapNotNull null
|
||||
}
|
||||
}
|
||||
|
@ -504,10 +507,12 @@ object PluginManager {
|
|||
val version: Int = manifest.version ?: PLUGIN_VERSION_NOT_SET.also {
|
||||
Log.d(TAG, "No manifest version for ${data.internalName}")
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val pluginClass: Class<*> =
|
||||
loader.loadClass(manifest.pluginClassName) as Class<out Plugin?>
|
||||
val pluginInstance: Plugin =
|
||||
pluginClass.newInstance() as Plugin
|
||||
pluginClass.getDeclaredConstructor().newInstance() as Plugin
|
||||
|
||||
// Sets with the proper version
|
||||
setPluginData(data.copy(version = version))
|
||||
|
@ -517,14 +522,16 @@ object PluginManager {
|
|||
return true
|
||||
}
|
||||
|
||||
pluginInstance.__filename = file.absolutePath
|
||||
pluginInstance.filename = file.absolutePath
|
||||
if (manifest.requiresResources) {
|
||||
Log.d(TAG, "Loading resources for ${data.internalName}")
|
||||
// based on https://stackoverflow.com/questions/7483568/dynamic-resource-loading-from-other-apk
|
||||
val assets = AssetManager::class.java.newInstance()
|
||||
val assets = AssetManager::class.java.getDeclaredConstructor().newInstance()
|
||||
val addAssetPath =
|
||||
AssetManager::class.java.getMethod("addAssetPath", String::class.java)
|
||||
addAssetPath.invoke(assets, file.absolutePath)
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
pluginInstance.resources = Resources(
|
||||
assets,
|
||||
context.resources.displayMetrics,
|
||||
|
@ -566,14 +573,14 @@ object PluginManager {
|
|||
|
||||
// remove all registered apis
|
||||
synchronized(APIHolder.apis) {
|
||||
APIHolder.apis.filter { api -> api.sourcePlugin == plugin.__filename }.forEach {
|
||||
APIHolder.apis.filter { api -> api.sourcePlugin == plugin.filename }.forEach {
|
||||
removePluginMapping(it)
|
||||
}
|
||||
}
|
||||
synchronized(APIHolder.allProviders) {
|
||||
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename }
|
||||
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.filename }
|
||||
}
|
||||
extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.__filename }
|
||||
extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.filename }
|
||||
|
||||
classLoaders.values.removeIf { v -> v == plugin }
|
||||
|
||||
|
@ -720,9 +727,14 @@ object PluginManager {
|
|||
}
|
||||
|
||||
val notification = builder.build()
|
||||
with(NotificationManagerCompat.from(context)) {
|
||||
// notificationId is a unique int for each notification that you must define
|
||||
notify((System.currentTimeMillis() / 1000).toInt(), notification)
|
||||
// notificationId is a unique int for each notification that you must define
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
context,
|
||||
Manifest.permission.POST_NOTIFICATIONS
|
||||
) == PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
NotificationManagerCompat.from(context)
|
||||
.notify((System.currentTimeMillis() / 1000).toInt(), notification)
|
||||
}
|
||||
return notification
|
||||
} catch (e: Exception) {
|
||||
|
|
|
@ -73,7 +73,7 @@ object RepositoryManager {
|
|||
val PREBUILT_REPOSITORIES: Array<RepositoryData> by lazy {
|
||||
getKey("PREBUILT_REPOSITORIES") ?: emptyArray()
|
||||
}
|
||||
val GH_REGEX = Regex("^https://raw.githubusercontent.com/([A-Za-z0-9-]+)/([A-Za-z0-9_.-]+)/(.*)$")
|
||||
private val GH_REGEX = Regex("^https://raw.githubusercontent.com/([A-Za-z0-9-]+)/([A-Za-z0-9_.-]+)/(.*)$")
|
||||
|
||||
/* Convert raw.githubusercontent.com urls to cdn.jsdelivr.net if enabled in settings */
|
||||
fun convertRawGitUrl(url: String): String {
|
||||
|
|
|
@ -15,7 +15,7 @@ import kotlinx.coroutines.sync.withLock
|
|||
object VotingApi { // please do not cheat the votes lol
|
||||
private const val LOGKEY = "VotingApi"
|
||||
|
||||
private const val apiDomain = "https://counterapi.com/api"
|
||||
private const val API_DOMAIN = "https://counterapi.com/api"
|
||||
|
||||
private fun transformUrl(url: String): String = // dont touch or all votes get reset
|
||||
MessageDigest
|
||||
|
@ -49,13 +49,13 @@ object VotingApi { // please do not cheat the votes lol
|
|||
.joinToString("-")
|
||||
|
||||
private suspend fun readVote(pluginUrl: String): Int {
|
||||
var url = "${apiDomain}/cs-${getRepository(pluginUrl)}/vote/${transformUrl(pluginUrl)}?readOnly=true"
|
||||
val url = "${API_DOMAIN}/cs-${getRepository(pluginUrl)}/vote/${transformUrl(pluginUrl)}?readOnly=true"
|
||||
Log.d(LOGKEY, "Requesting: $url")
|
||||
return app.get(url).parsedSafe<Result>()?.value ?: 0
|
||||
}
|
||||
|
||||
private suspend fun writeVote(pluginUrl: String): Boolean {
|
||||
var url = "${apiDomain}/cs-${getRepository(pluginUrl)}/vote/${transformUrl(pluginUrl)}"
|
||||
val url = "${API_DOMAIN}/cs-${getRepository(pluginUrl)}/vote/${transformUrl(pluginUrl)}"
|
||||
Log.d(LOGKEY, "Requesting: $url")
|
||||
return app.get(url).parsedSafe<Result>()?.value != null
|
||||
}
|
||||
|
@ -69,8 +69,7 @@ object VotingApi { // please do not cheat the votes lol
|
|||
getKey("cs3-votes/${transformUrl(pluginUrl)}") ?: false
|
||||
|
||||
fun canVote(pluginUrl: String): Boolean {
|
||||
if (!PluginManager.urlPlugins.contains(pluginUrl)) return false
|
||||
return true
|
||||
return PluginManager.urlPlugins.contains(pluginUrl)
|
||||
}
|
||||
|
||||
private val voteLock = Mutex()
|
||||
|
|
|
@ -59,7 +59,7 @@ class SubtitleResource {
|
|||
return file
|
||||
}
|
||||
|
||||
fun unzip(file: File): List<Pair<String, File>> {
|
||||
private fun unzip(file: File): List<Pair<String, File>> {
|
||||
val entries = mutableListOf<Pair<String, File>>()
|
||||
|
||||
ZipInputStream(file.inputStream()).use { zipInputStream ->
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.lagradost.cloudstream3.subtitles
|
||||
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
|
||||
class AbstractSubtitleEntities {
|
||||
|
|
|
@ -56,22 +56,22 @@ abstract class AccountManager(private val defIndex: Int) : AuthAPI {
|
|||
subSourceApi
|
||||
)
|
||||
|
||||
const val appString = "cloudstreamapp"
|
||||
const val appStringRepo = "cloudstreamrepo"
|
||||
const val appStringPlayer = "cloudstreamplayer"
|
||||
const val APP_STRING = "cloudstreamapp"
|
||||
const val APP_STRING_REPO = "cloudstreamrepo"
|
||||
const val APP_STRING_PLAYER = "cloudstreamplayer"
|
||||
|
||||
// Instantly start the search given a query
|
||||
const val appStringSearch = "cloudstreamsearch"
|
||||
const val APP_STRING_SEARCH = "cloudstreamsearch"
|
||||
|
||||
// Instantly resume watching a show
|
||||
const val appStringResumeWatching = "cloudstreamcontinuewatching"
|
||||
const val APP_STRING_RESUME_WATCHING = "cloudstreamcontinuewatching"
|
||||
|
||||
val unixTime: Long
|
||||
get() = System.currentTimeMillis() / 1000L
|
||||
val unixTimeMs: Long
|
||||
get() = System.currentTimeMillis()
|
||||
|
||||
const val maxStale = 60 * 10
|
||||
const val MAX_STALE = 60 * 10
|
||||
|
||||
fun secondsToReadable(seconds: Int, completedValue: String): String {
|
||||
var secondsLong = seconds.toLong()
|
||||
|
|
|
@ -18,13 +18,13 @@ class Addic7ed : AbstractSubApi {
|
|||
override fun logOut() {}
|
||||
|
||||
companion object {
|
||||
const val host = "https://www.addic7ed.com"
|
||||
const val HOST = "https://www.addic7ed.com"
|
||||
const val TAG = "ADDIC7ED"
|
||||
}
|
||||
|
||||
private fun fixUrl(url: String): String {
|
||||
return if (url.startsWith("/")) host + url
|
||||
else if (!url.startsWith("http")) "$host/$url"
|
||||
return if (url.startsWith("/")) HOST + url
|
||||
else if (!url.startsWith("http")) "$HOST/$url"
|
||||
else url
|
||||
|
||||
}
|
||||
|
@ -62,7 +62,7 @@ class Addic7ed : AbstractSubApi {
|
|||
}
|
||||
|
||||
val title = queryText.substringBefore("(").trim()
|
||||
val url = "$host/search.php?search=${title}&Submit=Search"
|
||||
val url = "$HOST/search.php?search=${title}&Submit=Search"
|
||||
val hostDocument = app.get(url).document
|
||||
var searchResult = ""
|
||||
if (!hostDocument.select("span:contains($title)").isNullOrEmpty()) searchResult = url
|
||||
|
@ -74,8 +74,8 @@ class Addic7ed : AbstractSubApi {
|
|||
hostDocument.selectFirst("#sl button")?.attr("onmouseup")?.substringAfter("(")
|
||||
?.substringBefore(",")
|
||||
val doc = app.get(
|
||||
"$host/ajax_loadShow.php?show=$show&season=$seasonNum&langs=&hd=undefined&hi=undefined",
|
||||
referer = "$host/"
|
||||
"$HOST/ajax_loadShow.php?show=$show&season=$seasonNum&langs=&hd=undefined&hi=undefined",
|
||||
referer = "$HOST/"
|
||||
).document
|
||||
doc.select("#season tr:contains($queryLang)").mapNotNull { node ->
|
||||
if (node.selectFirst("td")?.text()
|
||||
|
@ -97,7 +97,7 @@ class Addic7ed : AbstractSubApi {
|
|||
val link = fixUrl(node.select("a.buttonDownload").attr("href"))
|
||||
val isHearingImpaired =
|
||||
!node.parent()!!.select("tr:last-child [title=\"Hearing Impaired\"]").isNullOrEmpty()
|
||||
cleanResources(results, name, link, mapOf("referer" to "$host/"), isHearingImpaired)
|
||||
cleanResources(results, name, link, mapOf("referer" to "$HOST/"), isHearingImpaired)
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
override suspend fun handleRedirect(url: String): Boolean {
|
||||
val sanitizer =
|
||||
splitQuery(URL(url.replace(appString, "https").replace("/#", "?"))) // FIX ERROR
|
||||
splitQuery(URL(url.replace(APP_STRING, "https").replace("/#", "?"))) // FIX ERROR
|
||||
val token = sanitizer["access_token"]!!
|
||||
val expiresIn = sanitizer["expires_in"]!!
|
||||
|
||||
|
@ -87,7 +87,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
override suspend fun search(name: String): List<SyncAPI.SyncSearchResult>? {
|
||||
val data = searchShows(name) ?: return null
|
||||
return data.data?.Page?.media?.map {
|
||||
return data.data?.page?.media?.map {
|
||||
SyncAPI.SyncSearchResult(
|
||||
it.title.romaji ?: return null,
|
||||
this.name,
|
||||
|
@ -101,7 +101,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
override suspend fun getResult(id: String): SyncAPI.SyncResult {
|
||||
val internalId = (Regex("anilist\\.co/anime/(\\d*)").find(id)?.groupValues?.getOrNull(1)
|
||||
?: id).toIntOrNull() ?: throw ErrorLoadingException("Invalid internalId")
|
||||
val season = getSeason(internalId).data.Media
|
||||
val season = getSeason(internalId).data.media
|
||||
|
||||
return SyncAPI.SyncResult(
|
||||
season.id.toString(),
|
||||
|
@ -301,12 +301,12 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
//println("NAME $name NEW NAME ${name.replace(blackListRegex, "")}")
|
||||
val shows = searchShows(name.replace(blackListRegex, ""))
|
||||
|
||||
shows?.data?.Page?.media?.find {
|
||||
shows?.data?.page?.media?.find {
|
||||
(malId ?: "NONE") == it.idMal.toString()
|
||||
}?.let { return it }
|
||||
|
||||
val filtered =
|
||||
shows?.data?.Page?.media?.filter {
|
||||
shows?.data?.page?.media?.filter {
|
||||
(((it.startDate.year ?: year.toString()) == year.toString()
|
||||
|| year == null))
|
||||
}
|
||||
|
@ -496,7 +496,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
val data = postApi(q, true)
|
||||
val d = parseJson<GetDataRoot>(data ?: return null)
|
||||
|
||||
val main = d.data?.Media
|
||||
val main = d.data?.media
|
||||
if (main?.mediaListEntry != null) {
|
||||
return AniListTitleHolder(
|
||||
title = main.title,
|
||||
|
@ -536,7 +536,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
headers = mapOf(
|
||||
"Authorization" to "Bearer " + (getAuth()
|
||||
?: return@suspendSafeApiCall null),
|
||||
if (cache) "Cache-Control" to "max-stale=$maxStale" else "Cache-Control" to "no-cache"
|
||||
if (cache) "Cache-Control" to "max-stale=$MAX_STALE" else "Cache-Control" to "no-cache"
|
||||
),
|
||||
cacheTime = 0,
|
||||
data = mapOf(
|
||||
|
@ -647,7 +647,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
|
||||
data class Data(
|
||||
@JsonProperty("MediaListCollection") val MediaListCollection: MediaListCollection
|
||||
@JsonProperty("MediaListCollection") val mediaListCollection: MediaListCollection
|
||||
)
|
||||
|
||||
private fun getAniListListCached(): Array<Lists>? {
|
||||
|
@ -659,7 +659,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
if (checkToken()) return null
|
||||
return if (requireLibraryRefresh) {
|
||||
val list = getFullAniListList()?.data?.MediaListCollection?.lists?.toTypedArray()
|
||||
val list = getFullAniListList()?.data?.mediaListCollection?.lists?.toTypedArray()
|
||||
if (list != null) {
|
||||
setKey(ANILIST_CACHED_LIST, list)
|
||||
}
|
||||
|
@ -678,7 +678,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
// To fill empty lists when AniList does not return them
|
||||
val baseMap =
|
||||
AniListStatusType.values().filter { it.value >= 0 }.associate {
|
||||
AniListStatusType.entries.filter { it.value >= 0 }.associate {
|
||||
it.stringRes to emptyList<SyncAPI.LibraryItem>()
|
||||
}
|
||||
|
||||
|
@ -764,7 +764,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
/** Used to query a saved MediaItem on the list to get the id for removal */
|
||||
data class MediaListItemRoot(@JsonProperty("data") val data: MediaListItem? = null)
|
||||
data class MediaListItem(@JsonProperty("MediaList") val MediaList: MediaListId? = null)
|
||||
data class MediaListItem(@JsonProperty("MediaList") val mediaList: MediaListId? = null)
|
||||
data class MediaListId(@JsonProperty("id") val id: Long? = null)
|
||||
|
||||
private suspend fun postDataAboutId(
|
||||
|
@ -787,7 +787,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
"""
|
||||
val response = postApi(idQuery)
|
||||
val listId =
|
||||
tryParseJson<MediaListItemRoot>(response)?.data?.MediaList?.id ?: return false
|
||||
tryParseJson<MediaListItemRoot>(response)?.data?.mediaList?.id ?: return false
|
||||
"""
|
||||
mutation(${'$'}id: Int = $listId) {
|
||||
DeleteMediaListEntry(id: ${'$'}id) {
|
||||
|
@ -836,7 +836,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
val data = postApi(q)
|
||||
if (data.isNullOrBlank()) return null
|
||||
val userData = parseJson<AniListRoot>(data)
|
||||
val u = userData.data?.Viewer
|
||||
val u = userData.data?.viewer
|
||||
val user = AniListUser(
|
||||
u?.id,
|
||||
u?.name,
|
||||
|
@ -858,8 +858,8 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
suspend fun getSeasonRecursive(id: Int) {
|
||||
val season = getSeason(id)
|
||||
seasons.add(season)
|
||||
if (season.data.Media.format?.startsWith("TV") == true) {
|
||||
season.data.Media.relations?.edges?.forEach {
|
||||
if (season.data.media.format?.startsWith("TV") == true) {
|
||||
season.data.media.relations?.edges?.forEach {
|
||||
if (it.node?.format != null) {
|
||||
if (it.relationType == "SEQUEL" && it.node.format.startsWith("TV")) {
|
||||
getSeasonRecursive(it.node.id)
|
||||
|
@ -878,7 +878,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
|
||||
data class SeasonData(
|
||||
@JsonProperty("Media") val Media: SeasonMedia,
|
||||
@JsonProperty("Media") val media: SeasonMedia,
|
||||
)
|
||||
|
||||
data class SeasonMedia(
|
||||
|
@ -1050,7 +1050,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
|
||||
data class AniListData(
|
||||
@JsonProperty("Viewer") val Viewer: AniListViewer?,
|
||||
@JsonProperty("Viewer") val viewer: AniListViewer?,
|
||||
)
|
||||
|
||||
data class AniListRoot(
|
||||
|
@ -1090,7 +1090,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
|
||||
data class LikeData(
|
||||
@JsonProperty("Viewer") val Viewer: LikeViewer?,
|
||||
@JsonProperty("Viewer") val viewer: LikeViewer?,
|
||||
)
|
||||
|
||||
data class LikeRoot(
|
||||
|
@ -1130,7 +1130,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
|
||||
data class GetDataData(
|
||||
@JsonProperty("Media") val Media: GetDataMedia?,
|
||||
@JsonProperty("Media") val media: GetDataMedia?,
|
||||
)
|
||||
|
||||
data class GetDataRoot(
|
||||
|
@ -1163,7 +1163,7 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
|
||||
data class GetSearchPage(
|
||||
@JsonProperty("Page") val Page: GetSearchData?,
|
||||
@JsonProperty("Page") val page: GetSearchData?,
|
||||
)
|
||||
|
||||
data class GetSearchData(
|
||||
|
|
|
@ -119,8 +119,6 @@ class LocalList : SyncAPI {
|
|||
ListSorting.AlphabeticalZ,
|
||||
ListSorting.UpdatedNew,
|
||||
ListSorting.UpdatedOld,
|
||||
// ListSorting.RatingHigh,
|
||||
// ListSorting.RatingLow,
|
||||
)
|
||||
)
|
||||
}
|
||||
|
|
|
@ -19,14 +19,18 @@ import com.lagradost.cloudstream3.syncproviders.SyncIdName
|
|||
import com.lagradost.cloudstream3.ui.SyncWatchType
|
||||
import com.lagradost.cloudstream3.ui.library.ListSorting
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.AppContextUtils.splitQuery
|
||||
import com.lagradost.cloudstream3.utils.AppUtils.parseJson
|
||||
import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject
|
||||
import java.net.URL
|
||||
import java.security.SecureRandom
|
||||
import java.text.ParseException
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.time.Instant
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
|
||||
/** max 100 via https://myanimelist.net/apiconfig/references/api/v2#tag/anime */
|
||||
const val MAL_MAX_SEARCH_LIMIT = 25
|
||||
|
@ -51,7 +55,6 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
|
||||
override fun loginInfo(): AuthAPI.LoginInfo? {
|
||||
//getMalUser(true)?
|
||||
getKey<MalUser>(accountId, MAL_USER_KEY)?.let { user ->
|
||||
return AuthAPI.LoginInfo(
|
||||
profilePicture = user.picture,
|
||||
|
@ -84,7 +87,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
this.name,
|
||||
node.id.toString(),
|
||||
"$mainUrl/anime/${node.id}/",
|
||||
node.main_picture?.large ?: node.main_picture?.medium
|
||||
node.mainPicture?.large ?: node.mainPicture?.medium
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -178,7 +181,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
private fun parseDate(string: String?): Long? {
|
||||
return try {
|
||||
SimpleDateFormat("yyyy-MM-dd")?.parse(string ?: return null)?.time
|
||||
SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(string ?: return null)?.time
|
||||
} catch (e: Exception) {
|
||||
null
|
||||
}
|
||||
|
@ -190,7 +193,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
apiName = this.name,
|
||||
syncId = node.id.toString(),
|
||||
url = "$mainUrl/anime/${node.id}",
|
||||
posterUrl = node.main_picture?.large
|
||||
posterUrl = node.mainPicture?.large
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -244,12 +247,12 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
val internalId = id.toIntOrNull() ?: return null
|
||||
|
||||
val data =
|
||||
getDataAboutMalId(internalId)?.my_list_status //?: throw ErrorLoadingException("No my_list_status")
|
||||
getDataAboutMalId(internalId)?.myListStatus //?: throw ErrorLoadingException("No my_list_status")
|
||||
return SyncAPI.SyncStatus(
|
||||
score = data?.score,
|
||||
status = SyncWatchType.fromInternalId(malStatusAsString.indexOf(data?.status)) ,
|
||||
status = SyncWatchType.fromInternalId(malStatusAsString.indexOf(data?.status)),
|
||||
isFavorite = null,
|
||||
watchedEpisodes = data?.num_episodes_watched,
|
||||
watchedEpisodes = data?.numEpisodesWatched,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -291,7 +294,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
private fun parseDateLong(string: String?): Long? {
|
||||
return try {
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").parse(
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssZ", Locale.getDefault()).parse(
|
||||
string ?: return null
|
||||
)?.time?.div(1000)
|
||||
} catch (e: Exception) {
|
||||
|
@ -302,7 +305,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
override suspend fun handleRedirect(url: String): Boolean {
|
||||
val sanitizer =
|
||||
splitQuery(URL(url.replace(appString, "https").replace("/#", "?"))) // FIX ERROR
|
||||
splitQuery(URL(url.replace(APP_STRING, "https").replace("/#", "?"))) // FIX ERROR
|
||||
val state = sanitizer["state"]!!
|
||||
if (state == "RequestID$requestId") {
|
||||
val currentCode = sanitizer["code"]!!
|
||||
|
@ -351,9 +354,9 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
try {
|
||||
if (response != "") {
|
||||
val token = parseJson<ResponseToken>(response)
|
||||
setKey(accountId, MAL_UNIXTIME_KEY, (token.expires_in + unixTime))
|
||||
setKey(accountId, MAL_REFRESH_TOKEN_KEY, token.refresh_token)
|
||||
setKey(accountId, MAL_TOKEN_KEY, token.access_token)
|
||||
setKey(accountId, MAL_UNIXTIME_KEY, (token.expiresIn + unixTime))
|
||||
setKey(accountId, MAL_REFRESH_TOKEN_KEY, token.refreshToken)
|
||||
setKey(accountId, MAL_TOKEN_KEY, token.accessToken)
|
||||
requireLibraryRefresh = true
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
@ -395,53 +398,53 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
data class Node(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("title") val title: String,
|
||||
@JsonProperty("main_picture") val main_picture: MainPicture?,
|
||||
@JsonProperty("alternative_titles") val alternative_titles: AlternativeTitles?,
|
||||
@JsonProperty("media_type") val media_type: String?,
|
||||
@JsonProperty("num_episodes") val num_episodes: Int?,
|
||||
@JsonProperty("main_picture") val mainPicture: MainPicture?,
|
||||
@JsonProperty("alternative_titles") val alternativeTitles: AlternativeTitles?,
|
||||
@JsonProperty("media_type") val mediaType: String?,
|
||||
@JsonProperty("num_episodes") val numEpisodes: Int?,
|
||||
@JsonProperty("status") val status: String?,
|
||||
@JsonProperty("start_date") val start_date: String?,
|
||||
@JsonProperty("end_date") val end_date: String?,
|
||||
@JsonProperty("average_episode_duration") val average_episode_duration: Int?,
|
||||
@JsonProperty("start_date") val startDate: String?,
|
||||
@JsonProperty("end_date") val endDate: String?,
|
||||
@JsonProperty("average_episode_duration") val averageEpisodeDuration: Int?,
|
||||
@JsonProperty("synopsis") val synopsis: String?,
|
||||
@JsonProperty("mean") val mean: Double?,
|
||||
@JsonProperty("genres") val genres: List<Genres>?,
|
||||
@JsonProperty("rank") val rank: Int?,
|
||||
@JsonProperty("popularity") val popularity: Int?,
|
||||
@JsonProperty("num_list_users") val num_list_users: Int?,
|
||||
@JsonProperty("num_favorites") val num_favorites: Int?,
|
||||
@JsonProperty("num_scoring_users") val num_scoring_users: Int?,
|
||||
@JsonProperty("start_season") val start_season: StartSeason?,
|
||||
@JsonProperty("num_list_users") val numListUsers: Int?,
|
||||
@JsonProperty("num_favorites") val numFavorites: Int?,
|
||||
@JsonProperty("num_scoring_users") val numScoringUsers: Int?,
|
||||
@JsonProperty("start_season") val startSeason: StartSeason?,
|
||||
@JsonProperty("broadcast") val broadcast: Broadcast?,
|
||||
@JsonProperty("nsfw") val nsfw: String?,
|
||||
@JsonProperty("created_at") val created_at: String?,
|
||||
@JsonProperty("updated_at") val updated_at: String?
|
||||
@JsonProperty("created_at") val createdAt: String?,
|
||||
@JsonProperty("updated_at") val updatedAt: String?
|
||||
)
|
||||
|
||||
data class ListStatus(
|
||||
@JsonProperty("status") val status: String?,
|
||||
@JsonProperty("score") val score: Int,
|
||||
@JsonProperty("num_episodes_watched") val num_episodes_watched: Int,
|
||||
@JsonProperty("is_rewatching") val is_rewatching: Boolean,
|
||||
@JsonProperty("updated_at") val updated_at: String,
|
||||
@JsonProperty("num_episodes_watched") val numEpisodesWatched: Int,
|
||||
@JsonProperty("is_rewatching") val isRewatching: Boolean,
|
||||
@JsonProperty("updated_at") val updatedAt: String,
|
||||
)
|
||||
|
||||
data class Data(
|
||||
@JsonProperty("node") val node: Node,
|
||||
@JsonProperty("list_status") val list_status: ListStatus?,
|
||||
@JsonProperty("list_status") val listStatus: ListStatus?,
|
||||
) {
|
||||
fun toLibraryItem(): SyncAPI.LibraryItem {
|
||||
return SyncAPI.LibraryItem(
|
||||
this.node.title,
|
||||
"https://myanimelist.net/anime/${this.node.id}/",
|
||||
this.node.id.toString(),
|
||||
this.list_status?.num_episodes_watched,
|
||||
this.node.num_episodes,
|
||||
this.list_status?.score?.times(10),
|
||||
parseDateLong(this.list_status?.updated_at),
|
||||
this.listStatus?.numEpisodesWatched,
|
||||
this.node.numEpisodes,
|
||||
this.listStatus?.score?.times(10),
|
||||
parseDateLong(this.listStatus?.updatedAt),
|
||||
"MAL",
|
||||
TvType.Anime,
|
||||
this.node.main_picture?.large ?: this.node.main_picture?.medium,
|
||||
this.node.mainPicture?.large ?: this.node.mainPicture?.medium,
|
||||
null,
|
||||
null,
|
||||
plot = this.node.synopsis,
|
||||
|
@ -470,8 +473,8 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
|
||||
data class Broadcast(
|
||||
@JsonProperty("day_of_the_week") val day_of_the_week: String?,
|
||||
@JsonProperty("start_time") val start_time: String?
|
||||
@JsonProperty("day_of_the_week") val dayOfTheWeek: String?,
|
||||
@JsonProperty("start_time") val startTime: String?
|
||||
)
|
||||
|
||||
private fun getMalAnimeListCached(): Array<Data>? {
|
||||
|
@ -491,14 +494,14 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
override suspend fun getPersonalLibrary(): SyncAPI.LibraryMetadata {
|
||||
val list = getMalAnimeListSmart()?.groupBy {
|
||||
convertToStatus(it.list_status?.status ?: "").stringRes
|
||||
convertToStatus(it.listStatus?.status ?: "").stringRes
|
||||
}?.mapValues { group ->
|
||||
group.value.map { it.toLibraryItem() }
|
||||
} ?: emptyMap()
|
||||
|
||||
// To fill empty lists when MAL does not return them
|
||||
val baseMap =
|
||||
MalStatusType.values().filter { it.value >= 0 }.associate {
|
||||
MalStatusType.entries.filter { it.value >= 0 }.associate {
|
||||
it.stringRes to emptyList<SyncAPI.LibraryItem>()
|
||||
}
|
||||
|
||||
|
@ -573,7 +576,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
).text
|
||||
val values = parseJson<MalRoot>(res)
|
||||
val titles =
|
||||
values.data.map { MalTitleHolder(it.list_status, it.node.id, it.node.title) }
|
||||
values.data.map { MalTitleHolder(it.listStatus, it.node.id, it.node.title) }
|
||||
for (t in titles) {
|
||||
allTitles[t.id] = t
|
||||
}
|
||||
|
@ -582,11 +585,13 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
fun convertJapanTimeToTimeRemaining(date: String, endDate: String? = null): String? {
|
||||
private fun convertJapanTimeToTimeRemaining(date: String, endDate: String? = null): String? {
|
||||
// No time remaining if the show has already ended
|
||||
try {
|
||||
endDate?.let {
|
||||
if (SimpleDateFormat("yyyy-MM-dd").parse(it).time < System.currentTimeMillis()) return@convertJapanTimeToTimeRemaining null
|
||||
if (SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).parse(it)
|
||||
?.before(Date.from(Instant.now())) != false
|
||||
) return@convertJapanTimeToTimeRemaining null
|
||||
}
|
||||
} catch (e: ParseException) {
|
||||
logError(e)
|
||||
|
@ -603,7 +608,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
val currentWeek = currentDate.get(Calendar.WEEK_OF_MONTH)
|
||||
val currentYear = currentDate.get(Calendar.YEAR)
|
||||
|
||||
val dateFormat = SimpleDateFormat("yyyy MM W EEEE HH:mm")
|
||||
val dateFormat = SimpleDateFormat("yyyy MM W EEEE HH:mm", Locale.getDefault())
|
||||
dateFormat.timeZone = TimeZone.getTimeZone("Japan")
|
||||
val parsedDate =
|
||||
dateFormat.parse("$currentYear $currentMonth $currentWeek $date") ?: return null
|
||||
|
@ -647,13 +652,13 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
id: Int,
|
||||
status: MalStatusType? = null,
|
||||
score: Int? = null,
|
||||
num_watched_episodes: Int? = null,
|
||||
numWatchedEpisodes: Int? = null,
|
||||
): Boolean {
|
||||
val res = setScoreRequest(
|
||||
id,
|
||||
if (status == null) null else malStatusAsString[maxOf(0, status.value)],
|
||||
score,
|
||||
num_watched_episodes
|
||||
numWatchedEpisodes
|
||||
)
|
||||
|
||||
return if (res.isNullOrBlank()) {
|
||||
|
@ -670,17 +675,18 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private suspend fun setScoreRequest(
|
||||
id: Int,
|
||||
status: String? = null,
|
||||
score: Int? = null,
|
||||
num_watched_episodes: Int? = null,
|
||||
numWatchedEpisodes: Int? = null,
|
||||
): String? {
|
||||
val data = mapOf(
|
||||
"status" to status,
|
||||
"score" to score?.toString(),
|
||||
"num_watched_episodes" to num_watched_episodes?.toString()
|
||||
).filter { it.value != null } as Map<String, String>
|
||||
"num_watched_episodes" to numWatchedEpisodes?.toString()
|
||||
).filterValues { it != null } as Map<String, String>
|
||||
|
||||
return app.put(
|
||||
"$apiUrl/v2/anime/$id/my_list_status",
|
||||
|
@ -693,10 +699,10 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
|
||||
data class ResponseToken(
|
||||
@JsonProperty("token_type") val token_type: String,
|
||||
@JsonProperty("expires_in") val expires_in: Int,
|
||||
@JsonProperty("access_token") val access_token: String,
|
||||
@JsonProperty("refresh_token") val refresh_token: String,
|
||||
@JsonProperty("token_type") val tokenType: String,
|
||||
@JsonProperty("expires_in") val expiresIn: Int,
|
||||
@JsonProperty("access_token") val accessToken: String,
|
||||
@JsonProperty("refresh_token") val refreshToken: String,
|
||||
)
|
||||
|
||||
data class MalRoot(
|
||||
|
@ -705,7 +711,7 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
data class MalDatum(
|
||||
@JsonProperty("node") val node: MalNode,
|
||||
@JsonProperty("list_status") val list_status: MalStatus,
|
||||
@JsonProperty("list_status") val listStatus: MalStatus,
|
||||
)
|
||||
|
||||
data class MalNode(
|
||||
|
@ -722,16 +728,16 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
data class MalStatus(
|
||||
@JsonProperty("status") val status: String,
|
||||
@JsonProperty("score") val score: Int,
|
||||
@JsonProperty("num_episodes_watched") val num_episodes_watched: Int,
|
||||
@JsonProperty("is_rewatching") val is_rewatching: Boolean,
|
||||
@JsonProperty("updated_at") val updated_at: String,
|
||||
@JsonProperty("num_episodes_watched") val numEpisodesWatched: Int,
|
||||
@JsonProperty("is_rewatching") val isRewatching: Boolean,
|
||||
@JsonProperty("updated_at") val updatedAt: String,
|
||||
)
|
||||
|
||||
data class MalUser(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("name") val name: String,
|
||||
@JsonProperty("location") val location: String,
|
||||
@JsonProperty("joined_at") val joined_at: String,
|
||||
@JsonProperty("joined_at") val joinedAt: String,
|
||||
@JsonProperty("picture") val picture: String?,
|
||||
)
|
||||
|
||||
|
@ -744,9 +750,9 @@ class MALApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
data class SmallMalAnime(
|
||||
@JsonProperty("id") val id: Int,
|
||||
@JsonProperty("title") val title: String?,
|
||||
@JsonProperty("num_episodes") val num_episodes: Int,
|
||||
@JsonProperty("my_list_status") val my_list_status: MalStatus?,
|
||||
@JsonProperty("main_picture") val main_picture: MalMainPicture?,
|
||||
@JsonProperty("num_episodes") val numEpisodes: Int,
|
||||
@JsonProperty("my_list_status") val myListStatus: MalStatus?,
|
||||
@JsonProperty("main_picture") val mainPicture: MalMainPicture?,
|
||||
)
|
||||
|
||||
data class MalSearchNode(
|
||||
|
|
|
@ -15,7 +15,6 @@ import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities
|
|||
import com.lagradost.cloudstream3.syncproviders.AuthAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI
|
||||
import com.lagradost.cloudstream3.syncproviders.InAppAuthAPIManager
|
||||
import com.lagradost.cloudstream3.utils.AppContextUtils
|
||||
import com.lagradost.cloudstream3.utils.AppUtils
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
@ -30,10 +29,10 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
|
|||
|
||||
companion object {
|
||||
const val OPEN_SUBTITLES_USER_KEY: String = "open_subtitles_user" // user data like profile
|
||||
const val apiKey = "uyBLgFD17MgrYmA0gSXoKllMJBelOYj2"
|
||||
const val host = "https://api.opensubtitles.com/api/v1"
|
||||
const val API_KEY = "uyBLgFD17MgrYmA0gSXoKllMJBelOYj2"
|
||||
const val HOST = "https://api.opensubtitles.com/api/v1"
|
||||
const val TAG = "OPENSUBS"
|
||||
const val coolDownDuration: Long = 1000L * 30L // CoolDown if 429 error code in ms
|
||||
const val COOLDOWN_DURATION: Long = 1000L * 30L // CoolDown if 429 error code in ms
|
||||
var currentCoolDown: Long = 0L
|
||||
var currentSession: SubtitleOAuthEntity? = null
|
||||
}
|
||||
|
@ -49,7 +48,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
|
|||
chain.request().newBuilder()
|
||||
.removeHeader("user-agent")
|
||||
.addHeader("user-agent", userAgent)
|
||||
.addHeader("Api-Key", apiKey)
|
||||
.addHeader("Api-Key", API_KEY)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
@ -66,7 +65,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
|
|||
}
|
||||
|
||||
private fun throwGotTooManyRequests() {
|
||||
currentCoolDown = unixTimeMs + coolDownDuration
|
||||
currentCoolDown = unixTimeMs + COOLDOWN_DURATION
|
||||
throw ErrorLoadingException("Too many requests")
|
||||
}
|
||||
|
||||
|
@ -115,7 +114,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
|
|||
private suspend fun initLogin(username: String, password: String): Boolean {
|
||||
//Log.i(TAG, "DATA = [$username] [$password]")
|
||||
val response = app.post(
|
||||
url = "$host/login",
|
||||
url = "$HOST/login",
|
||||
headers = mapOf(
|
||||
"Content-Type" to "application/json",
|
||||
),
|
||||
|
@ -134,7 +133,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
|
|||
SubtitleOAuthEntity(
|
||||
user = username,
|
||||
pass = password,
|
||||
access_token = token.token ?: run {
|
||||
accessToken = token.token ?: run {
|
||||
return false
|
||||
})
|
||||
)
|
||||
|
@ -197,8 +196,8 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
|
|||
|
||||
val searchQueryUrl = when (imdbId > 0) {
|
||||
//Use imdb_id to search if its valid
|
||||
true -> "$host/subtitles?imdb_id=$imdbId&languages=${fixedLang}$yearQuery$epQuery$seasonQuery"
|
||||
false -> "$host/subtitles?query=${queryText}&languages=${fixedLang}$yearQuery$epQuery$seasonQuery"
|
||||
true -> "$HOST/subtitles?imdb_id=$imdbId&languages=${fixedLang}$yearQuery$epQuery$seasonQuery"
|
||||
false -> "$HOST/subtitles?query=${queryText}&languages=${fixedLang}$yearQuery$epQuery$seasonQuery"
|
||||
}
|
||||
|
||||
val req = app.get(
|
||||
|
@ -233,7 +232,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
|
|||
val resSeasonNum = featureDetails?.seasonNumber ?: query.seasonNumber
|
||||
val year = featureDetails?.year ?: query.year
|
||||
val type = if ((resSeasonNum ?: 0) > 0) TvType.TvSeries else TvType.Movie
|
||||
val isHearingImpaired = attr.hearing_impaired ?: false
|
||||
val isHearingImpaired = attr.hearingImpaired ?: false
|
||||
//Log.i(TAG, "Result id/name => ${item.id} / $name")
|
||||
item.attributes?.files?.forEach { file ->
|
||||
val resultData = file.fileId?.toString() ?: ""
|
||||
|
@ -266,11 +265,11 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
|
|||
throwIfCantDoRequest()
|
||||
|
||||
val req = app.post(
|
||||
url = "$host/download",
|
||||
url = "$HOST/download",
|
||||
headers = mapOf(
|
||||
Pair(
|
||||
"Authorization",
|
||||
"Bearer ${currentSession?.access_token ?: throw ErrorLoadingException("No access token active in current session")}"
|
||||
"Bearer ${currentSession?.accessToken ?: throw ErrorLoadingException("No access token active in current session")}"
|
||||
),
|
||||
Pair("Content-Type", "application/json"),
|
||||
Pair("Accept", "*/*")
|
||||
|
@ -299,7 +298,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
|
|||
data class SubtitleOAuthEntity(
|
||||
var user: String,
|
||||
var pass: String,
|
||||
var access_token: String,
|
||||
var accessToken: String,
|
||||
)
|
||||
|
||||
data class OAuthToken(
|
||||
|
@ -324,7 +323,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi
|
|||
@JsonProperty("url") var url: String? = null,
|
||||
@JsonProperty("files") var files: List<ResultFiles>? = listOf(),
|
||||
@JsonProperty("feature_details") var featDetails: ResultFeatureDetails? = ResultFeatureDetails(),
|
||||
@JsonProperty("hearing_impaired") var hearing_impaired: Boolean? = null,
|
||||
@JsonProperty("hearing_impaired") var hearingImpaired: Boolean? = null,
|
||||
)
|
||||
|
||||
data class ResultFiles(
|
||||
|
|
|
@ -38,6 +38,7 @@ import java.security.SecureRandom
|
|||
import java.text.SimpleDateFormat
|
||||
import java.time.Instant
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
import java.util.TimeZone
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.DurationUnit
|
||||
|
@ -144,8 +145,8 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
|
||||
companion object {
|
||||
private const val clientId: String = BuildConfig.SIMKL_CLIENT_ID
|
||||
private const val clientSecret: String = BuildConfig.SIMKL_CLIENT_SECRET
|
||||
private const val CLIENT_ID: String = BuildConfig.SIMKL_CLIENT_ID
|
||||
private const val CLIENT_SECRET: String = BuildConfig.SIMKL_CLIENT_SECRET
|
||||
private var lastLoginState = ""
|
||||
|
||||
const val SIMKL_TOKEN_KEY: String = "simkl_token"
|
||||
|
@ -154,10 +155,10 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
const val SIMKL_CACHED_LIST_TIME: String = "simkl_cached_time"
|
||||
|
||||
/** 2014-09-01T09:10:11Z -> 1409562611 */
|
||||
private const val simklDateFormat = "yyyy-MM-dd'T'HH:mm:ss'Z'"
|
||||
private const val SIMKL_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss'Z'"
|
||||
fun getUnixTime(string: String?): Long? {
|
||||
return try {
|
||||
SimpleDateFormat(simklDateFormat).apply {
|
||||
SimpleDateFormat(SIMKL_DATE_FORMAT, Locale.getDefault()).apply {
|
||||
this.timeZone = TimeZone.getTimeZone("UTC")
|
||||
}.parse(
|
||||
string ?: return null
|
||||
|
@ -171,7 +172,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
/** 1409562611 -> 2014-09-01T09:10:11Z */
|
||||
fun getDateTime(unixTime: Long?): String? {
|
||||
return try {
|
||||
SimpleDateFormat(simklDateFormat).apply {
|
||||
SimpleDateFormat(SIMKL_DATE_FORMAT, Locale.getDefault()).apply {
|
||||
this.timeZone = TimeZone.getTimeZone("UTC")
|
||||
}.format(
|
||||
Date.from(
|
||||
|
@ -208,7 +209,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
companion object {
|
||||
fun fromString(string: String): SimklListStatusType? {
|
||||
return SimklListStatusType.values().firstOrNull {
|
||||
return SimklListStatusType.entries.firstOrNull {
|
||||
it.originalName == string
|
||||
}
|
||||
}
|
||||
|
@ -219,17 +220,17 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
data class TokenRequest(
|
||||
@JsonProperty("code") val code: String,
|
||||
@JsonProperty("client_id") val client_id: String = clientId,
|
||||
@JsonProperty("client_secret") val client_secret: String = clientSecret,
|
||||
@JsonProperty("redirect_uri") val redirect_uri: String = "$appString://simkl",
|
||||
@JsonProperty("grant_type") val grant_type: String = "authorization_code"
|
||||
@JsonProperty("client_id") val clientId: String = CLIENT_ID,
|
||||
@JsonProperty("client_secret") val clientSecret: String = CLIENT_SECRET,
|
||||
@JsonProperty("redirect_uri") val redirectUri: String = "$APP_STRING://simkl",
|
||||
@JsonProperty("grant_type") val grantType: String = "authorization_code"
|
||||
)
|
||||
|
||||
data class TokenResponse(
|
||||
/** No expiration date */
|
||||
val access_token: String,
|
||||
val token_type: String,
|
||||
val scope: String
|
||||
@JsonProperty("access_token") val accessToken: String,
|
||||
@JsonProperty("token_type") val tokenType: String,
|
||||
@JsonProperty("scope") val scope: String
|
||||
)
|
||||
// -------------------
|
||||
|
||||
|
@ -261,15 +262,15 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
// -------------------
|
||||
data class ActivitiesResponse(
|
||||
val all: String?,
|
||||
val tv_shows: UpdatedAt,
|
||||
val anime: UpdatedAt,
|
||||
val movies: UpdatedAt,
|
||||
@JsonProperty("all") val all: String?,
|
||||
@JsonProperty("tv_shows") val tvShows: UpdatedAt,
|
||||
@JsonProperty("anime") val anime: UpdatedAt,
|
||||
@JsonProperty("movies") val movies: UpdatedAt,
|
||||
) {
|
||||
data class UpdatedAt(
|
||||
val all: String?,
|
||||
val removed_from_list: String?,
|
||||
val rated_at: String?,
|
||||
@JsonProperty("all") val all: String?,
|
||||
@JsonProperty("removed_from_list") val removedFromList: String?,
|
||||
@JsonProperty("rated_at") val ratedAt: String?,
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -308,7 +309,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
@JsonProperty("title") val title: String?,
|
||||
@JsonProperty("year") val year: Int?,
|
||||
@JsonProperty("ids") val ids: Ids?,
|
||||
@JsonProperty("total_episodes") val total_episodes: Int? = null,
|
||||
@JsonProperty("total_episodes") val totalEpisodes: Int? = null,
|
||||
@JsonProperty("status") val status: String? = null,
|
||||
@JsonProperty("poster") val poster: String? = null,
|
||||
@JsonProperty("type") val type: String? = null,
|
||||
|
@ -540,7 +541,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
|
||||
debugPrint { "Requesting episodes from $url" }
|
||||
return app.get(url, params = mapOf("client_id" to clientId))
|
||||
return app.get(url, params = mapOf("client_id" to CLIENT_ID))
|
||||
.parsedSafe<Array<EpisodeMetadata>>()?.also {
|
||||
val cacheTime =
|
||||
if (hasEnded == true) SimklCache.CacheTimes.OneMonth.value else SimklCache.CacheTimes.ThirtyMinutes.value
|
||||
|
@ -558,7 +559,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
@JsonProperty("seasons") seasons: List<Season>? = null,
|
||||
@JsonProperty("episodes") episodes: List<Season.Episode>? = null,
|
||||
@JsonProperty("rating") val rating: Int? = null,
|
||||
@JsonProperty("rated_at") val rated_at: String? = null,
|
||||
@JsonProperty("rated_at") val ratedAt: String? = null,
|
||||
) : MediaObject(title, year, ids, seasons = seasons, episodes = episodes)
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
|
@ -567,7 +568,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
@JsonProperty("year") year: Int?,
|
||||
@JsonProperty("ids") ids: Ids?,
|
||||
@JsonProperty("rating") val rating: Int,
|
||||
@JsonProperty("rated_at") val rated_at: String? = getDateTime(unixTime)
|
||||
@JsonProperty("rated_at") val ratedAt: String? = getDateTime(unixTime)
|
||||
) : MediaObject(title, year, ids)
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
|
@ -576,7 +577,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
@JsonProperty("year") year: Int?,
|
||||
@JsonProperty("ids") ids: Ids?,
|
||||
@JsonProperty("to") val to: String,
|
||||
@JsonProperty("watched_at") val watched_at: String? = getDateTime(unixTime)
|
||||
@JsonProperty("watched_at") val watchedAt: String? = getDateTime(unixTime)
|
||||
) : MediaObject(title, year, ids)
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_EMPTY)
|
||||
|
@ -631,24 +632,24 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
|
||||
interface Metadata {
|
||||
val last_watched_at: String?
|
||||
val lastWatchedAt: String?
|
||||
val status: String?
|
||||
val user_rating: Int?
|
||||
val last_watched: String?
|
||||
val watched_episodes_count: Int?
|
||||
val total_episodes_count: Int?
|
||||
val userRating: Int?
|
||||
val lastWatched: String?
|
||||
val watchedEpisodesCount: Int?
|
||||
val totalEpisodesCount: Int?
|
||||
|
||||
fun getIds(): ShowMetadata.Show.Ids
|
||||
fun toLibraryItem(): SyncAPI.LibraryItem
|
||||
}
|
||||
|
||||
data class MovieMetadata(
|
||||
override val last_watched_at: String?,
|
||||
override val status: String,
|
||||
override val user_rating: Int?,
|
||||
override val last_watched: String?,
|
||||
override val watched_episodes_count: Int?,
|
||||
override val total_episodes_count: Int?,
|
||||
@JsonProperty("last_watched_at") override val lastWatchedAt: String?,
|
||||
@JsonProperty("status") override val status: String,
|
||||
@JsonProperty("user_rating") override val userRating: Int?,
|
||||
@JsonProperty("last_watched") override val lastWatched: String?,
|
||||
@JsonProperty("watched_episodes_count") override val watchedEpisodesCount: Int?,
|
||||
@JsonProperty("total_episodes_count") override val totalEpisodesCount: Int?,
|
||||
val movie: ShowMetadata.Show
|
||||
) : Metadata {
|
||||
override fun getIds(): ShowMetadata.Show.Ids {
|
||||
|
@ -660,10 +661,10 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
this.movie.title,
|
||||
"https://simkl.com/tv/${movie.ids.simkl}",
|
||||
movie.ids.simkl.toString(),
|
||||
this.watched_episodes_count,
|
||||
this.total_episodes_count,
|
||||
this.user_rating?.times(10),
|
||||
getUnixTime(last_watched_at) ?: 0,
|
||||
this.watchedEpisodesCount,
|
||||
this.totalEpisodesCount,
|
||||
this.userRating?.times(10),
|
||||
getUnixTime(lastWatchedAt) ?: 0,
|
||||
"Simkl",
|
||||
TvType.Movie,
|
||||
this.movie.poster?.let { getPosterUrl(it) },
|
||||
|
@ -675,12 +676,12 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
}
|
||||
|
||||
data class ShowMetadata(
|
||||
@JsonProperty("last_watched_at") override val last_watched_at: String?,
|
||||
@JsonProperty("last_watched_at") override val lastWatchedAt: String?,
|
||||
@JsonProperty("status") override val status: String,
|
||||
@JsonProperty("user_rating") override val user_rating: Int?,
|
||||
@JsonProperty("last_watched") override val last_watched: String?,
|
||||
@JsonProperty("watched_episodes_count") override val watched_episodes_count: Int?,
|
||||
@JsonProperty("total_episodes_count") override val total_episodes_count: Int?,
|
||||
@JsonProperty("user_rating") override val userRating: Int?,
|
||||
@JsonProperty("last_watched") override val lastWatched: String?,
|
||||
@JsonProperty("watched_episodes_count") override val watchedEpisodesCount: Int?,
|
||||
@JsonProperty("total_episodes_count") override val totalEpisodesCount: Int?,
|
||||
@JsonProperty("show") val show: Show
|
||||
) : Metadata {
|
||||
override fun getIds(): Show.Ids {
|
||||
|
@ -692,10 +693,10 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
this.show.title,
|
||||
"https://simkl.com/tv/${show.ids.simkl}",
|
||||
show.ids.simkl.toString(),
|
||||
this.watched_episodes_count,
|
||||
this.total_episodes_count,
|
||||
this.user_rating?.times(10),
|
||||
getUnixTime(last_watched_at) ?: 0,
|
||||
this.watchedEpisodesCount,
|
||||
this.totalEpisodesCount,
|
||||
this.userRating?.times(10),
|
||||
getUnixTime(lastWatchedAt) ?: 0,
|
||||
"Simkl",
|
||||
TvType.Anime,
|
||||
this.show.poster?.let { getPosterUrl(it) },
|
||||
|
@ -749,7 +750,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
chain.request()
|
||||
.newBuilder()
|
||||
.addHeader("Authorization", "Bearer $token")
|
||||
.addHeader("simkl-api-key", clientId)
|
||||
.addHeader("simkl-api-key", CLIENT_ID)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
@ -810,7 +811,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
val episodeConstructor = SimklEpisodeConstructor(
|
||||
searchResult.ids?.simkl,
|
||||
searchResult.type,
|
||||
searchResult.total_episodes,
|
||||
searchResult.totalEpisodes,
|
||||
searchResult.hasEnded()
|
||||
)
|
||||
|
||||
|
@ -832,12 +833,12 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
)
|
||||
}
|
||||
?: return null,
|
||||
score = foundItem.user_rating,
|
||||
watchedEpisodes = foundItem.watched_episodes_count,
|
||||
maxEpisodes = searchResult.total_episodes,
|
||||
score = foundItem.userRating,
|
||||
watchedEpisodes = foundItem.watchedEpisodesCount,
|
||||
maxEpisodes = searchResult.totalEpisodes,
|
||||
episodeConstructor = episodeConstructor,
|
||||
oldEpisodes = foundItem.watched_episodes_count ?: 0,
|
||||
oldScore = foundItem.user_rating,
|
||||
oldEpisodes = foundItem.watchedEpisodesCount ?: 0,
|
||||
oldScore = foundItem.userRating,
|
||||
oldStatus = foundItem.status
|
||||
)
|
||||
} else {
|
||||
|
@ -845,7 +846,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
status = SyncWatchType.fromInternalId(SimklListStatusType.None.value),
|
||||
score = 0,
|
||||
watchedEpisodes = 0,
|
||||
maxEpisodes = if (searchResult.type == "movie") 0 else searchResult.total_episodes,
|
||||
maxEpisodes = if (searchResult.type == "movie") 0 else searchResult.totalEpisodes,
|
||||
episodeConstructor = episodeConstructor,
|
||||
oldEpisodes = 0,
|
||||
oldStatus = null,
|
||||
|
@ -891,12 +892,12 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
|
||||
/** See https://simkl.docs.apiary.io/#reference/search/id-lookup/get-items-by-id */
|
||||
suspend fun searchByIds(serviceMap: Map<SimklSyncServices, String>): Array<MediaObject>? {
|
||||
private suspend fun searchByIds(serviceMap: Map<SimklSyncServices, String>): Array<MediaObject>? {
|
||||
if (serviceMap.isEmpty()) return emptyArray()
|
||||
|
||||
return app.get(
|
||||
"$mainUrl/search/id",
|
||||
params = mapOf("client_id" to clientId) + serviceMap.map { (service, id) ->
|
||||
params = mapOf("client_id" to CLIENT_ID) + serviceMap.map { (service, id) ->
|
||||
service.originalName to id
|
||||
}
|
||||
).parsedSafe()
|
||||
|
@ -904,14 +905,14 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
override suspend fun search(name: String): List<SyncAPI.SyncSearchResult>? {
|
||||
return app.get(
|
||||
"$mainUrl/search/", params = mapOf("client_id" to clientId, "q" to name)
|
||||
"$mainUrl/search/", params = mapOf("client_id" to CLIENT_ID, "q" to name)
|
||||
).parsedSafe<Array<MediaObject>>()?.mapNotNull { it.toSyncSearchResult() }
|
||||
}
|
||||
|
||||
override fun authenticate(activity: FragmentActivity?) {
|
||||
lastLoginState = BigInteger(130, SecureRandom()).toString(32)
|
||||
val url =
|
||||
"https://simkl.com/oauth/authorize?response_type=code&client_id=$clientId&redirect_uri=$appString://${redirectUrl}&state=$lastLoginState"
|
||||
"https://simkl.com/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$APP_STRING://${redirectUrl}&state=$lastLoginState"
|
||||
openBrowser(url, activity)
|
||||
}
|
||||
|
||||
|
@ -961,15 +962,15 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
val activities = getActivities()
|
||||
val lastCacheUpdate = getKey<Long>(accountId, SIMKL_CACHED_LIST_TIME)
|
||||
val lastRemoval = listOf(
|
||||
activities?.tv_shows?.removed_from_list,
|
||||
activities?.anime?.removed_from_list,
|
||||
activities?.movies?.removed_from_list
|
||||
activities?.tvShows?.removedFromList,
|
||||
activities?.anime?.removedFromList,
|
||||
activities?.movies?.removedFromList
|
||||
).maxOf {
|
||||
getUnixTime(it) ?: -1
|
||||
}
|
||||
val lastRealUpdate =
|
||||
listOf(
|
||||
activities?.tv_shows?.all,
|
||||
activities?.tvShows?.all,
|
||||
activities?.anime?.all,
|
||||
activities?.movies?.all,
|
||||
).maxOf {
|
||||
|
@ -1039,7 +1040,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
override suspend fun getDevicePin(): OAuth2API.PinAuthData? {
|
||||
val pinAuthResp = app.get(
|
||||
"$mainUrl/oauth/pin?client_id=$clientId&redirect_uri=$appString://${redirectUrl}"
|
||||
"$mainUrl/oauth/pin?client_id=$CLIENT_ID&redirect_uri=$APP_STRING://${redirectUrl}"
|
||||
).parsedSafe<PinAuthResponse>() ?: return null
|
||||
|
||||
return OAuth2API.PinAuthData(
|
||||
|
@ -1053,7 +1054,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
|
||||
override suspend fun handleDeviceAuth(pinAuthData: OAuth2API.PinAuthData): Boolean {
|
||||
val pinAuthResp = app.get(
|
||||
"$mainUrl/oauth/pin/${pinAuthData.userCode}?client_id=$clientId"
|
||||
"$mainUrl/oauth/pin/${pinAuthData.userCode}?client_id=$CLIENT_ID"
|
||||
).parsedSafe<PinExchangeResponse>() ?: return false
|
||||
|
||||
if (pinAuthResp.accessToken != null) {
|
||||
|
@ -1088,7 +1089,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI {
|
|||
).parsedSafe<TokenResponse>() ?: return false
|
||||
|
||||
switchToNewAccount()
|
||||
setKey(accountId, SIMKL_TOKEN_KEY, token.access_token)
|
||||
setKey(accountId, SIMKL_TOKEN_KEY, token.accessToken)
|
||||
|
||||
val user = getUser()
|
||||
if (user == null) {
|
||||
|
|
|
@ -59,6 +59,7 @@ class SubSourceApi : AbstractSubProvider {
|
|||
it?.subs?.filter { sub ->
|
||||
sub.releaseName!!.contains(
|
||||
String.format(
|
||||
null,
|
||||
"E%02d",
|
||||
query.epNumber
|
||||
)
|
||||
|
|
|
@ -50,7 +50,7 @@ class APIRepository(val api: MainAPI) {
|
|||
|
||||
private val cache = threadSafeListOf<SavedLoadResponse>()
|
||||
private var cacheIndex: Int = 0
|
||||
const val cacheSize = 20
|
||||
const val CACHE_SIZE = 20
|
||||
}
|
||||
|
||||
private fun afterPluginsLoaded(forceReload: Boolean) {
|
||||
|
@ -94,9 +94,9 @@ class APIRepository(val api: MainAPI) {
|
|||
val add = SavedLoadResponse(unixTime, response, lookingForHash)
|
||||
|
||||
synchronized(cache) {
|
||||
if (cache.size > cacheSize) {
|
||||
if (cache.size > CACHE_SIZE) {
|
||||
cache[cacheIndex] = add // rolling cache
|
||||
cacheIndex = (cacheIndex + 1) % cacheSize
|
||||
cacheIndex = (cacheIndex + 1) % CACHE_SIZE
|
||||
} else {
|
||||
cache.add(add)
|
||||
}
|
||||
|
|
|
@ -112,6 +112,7 @@ abstract class BaseAdapter<
|
|||
holder.onViewDetachedFromWindow()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun save(recyclerView: RecyclerView) {
|
||||
for (child in recyclerView.children) {
|
||||
val holder =
|
||||
|
@ -124,6 +125,7 @@ abstract class BaseAdapter<
|
|||
stateViewModel.layoutManagerStates[id]?.clear()
|
||||
}
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
private fun getState(holder: ViewHolderState<S>): S? =
|
||||
stateViewModel.layoutManagerStates[id]?.get(holder.absoluteAdapterPosition) as? S
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.view.Menu
|
|||
import android.view.View.*
|
||||
import android.widget.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.json.JsonMapper
|
||||
import com.fasterxml.jackson.module.kotlin.kotlinModule
|
||||
|
@ -263,6 +264,7 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi
|
|||
|
||||
var isLoadingMore = false
|
||||
|
||||
|
||||
override fun onMediaStatusUpdated() {
|
||||
super.onMediaStatusUpdated()
|
||||
val meta = getCurrentMetaData()
|
||||
|
|
|
@ -8,8 +8,8 @@ import androidx.recyclerview.widget.GridLayoutManager
|
|||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlin.math.abs
|
||||
|
||||
class GrdLayoutManager(val context: Context, _spanCount: Int) :
|
||||
GridLayoutManager(context, _spanCount) {
|
||||
class GrdLayoutManager(val context: Context, spanCount: Int) :
|
||||
GridLayoutManager(context, spanCount) {
|
||||
override fun onFocusSearchFailed(
|
||||
focused: View,
|
||||
focusDirection: Int,
|
||||
|
|
|
@ -51,7 +51,7 @@ class EasterEggMonke : AppCompatActivity() {
|
|||
FrameLayout.LayoutParams.WRAP_CONTENT)
|
||||
binding.frame.addView(newStar)
|
||||
|
||||
newStar.scaleX = Math.random().toFloat() * 1.5f + newStar.scaleX
|
||||
newStar.scaleX += Math.random().toFloat() * 1.5f
|
||||
newStar.scaleY = newStar.scaleX
|
||||
starW *= newStar.scaleX
|
||||
starH *= newStar.scaleY
|
||||
|
|
|
@ -15,7 +15,7 @@ open class NonFinalAdapterListUpdateCallback
|
|||
/**
|
||||
* Creates an AdapterListUpdateCallback that will dispatch update events to the given adapter.
|
||||
*
|
||||
* @param adapter The Adapter to send updates to.
|
||||
* @param mAdapter The Adapter to send updates to.
|
||||
*/(private var mAdapter: RecyclerView.Adapter<*>) :
|
||||
ListUpdateCallback {
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ enum class WatchType(val internalId: Int, @StringRes val stringRes: Int, @Drawab
|
|||
NONE(5, R.string.type_none, R.drawable.ic_baseline_add_24);
|
||||
|
||||
companion object {
|
||||
fun fromInternalId(id: Int?) = values().find { value -> value.internalId == id } ?: NONE
|
||||
fun fromInternalId(id: Int?) = entries.find { value -> value.internalId == id } ?: NONE
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,6 @@ enum class SyncWatchType(val internalId: Int, @StringRes val stringRes: Int, @Dr
|
|||
REWATCHING(5, R.string.type_re_watching, R.drawable.ic_baseline_bookmark_24);
|
||||
|
||||
companion object {
|
||||
fun fromInternalId(id: Int?) = values().find { value -> value.internalId == id } ?: NONE
|
||||
fun fromInternalId(id: Int?) = entries.find { value -> value.internalId == id } ?: NONE
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,10 @@ import android.webkit.JavascriptInterface
|
|||
import android.webkit.WebResourceRequest
|
||||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.lagradost.cloudstream3.MainActivity
|
||||
import com.lagradost.cloudstream3.USER_AGENT
|
||||
|
@ -29,6 +31,7 @@ class WebviewFragment : Fragment() {
|
|||
}
|
||||
|
||||
binding?.webView?.webViewClient = object : WebViewClient() {
|
||||
@OptIn(UnstableApi::class)
|
||||
override fun shouldOverrideUrlLoading(
|
||||
view: WebView?,
|
||||
request: WebResourceRequest?
|
||||
|
|
|
@ -54,6 +54,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) :
|
|||
}
|
||||
|
||||
init {
|
||||
@Suppress("LeakingThis")
|
||||
resetViewData()
|
||||
}
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
|||
class DownloadButton(context: Context, attributeSet: AttributeSet) :
|
||||
PieFetchButton(context, attributeSet) {
|
||||
|
||||
var mainText: TextView? = null
|
||||
private var mainText: TextView? = null
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
progressText = findViewById(R.id.result_movie_download_text_precentage)
|
||||
|
|
|
@ -15,7 +15,7 @@ import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
|||
import com.lagradost.cloudstream3.ui.search.SearchResultBuilder
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.isBottomLayout
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||
|
||||
class HomeScrollViewHolderState(view: ViewBinding) : ViewHolderState<Boolean>(view) {
|
||||
|
@ -54,7 +54,7 @@ class HomeChildItemAdapter(
|
|||
var hasNext: Boolean = false
|
||||
|
||||
override fun onCreateContent(parent: ViewGroup): ViewHolderState<Boolean> {
|
||||
val expanded = parent.context.IsBottomLayout()
|
||||
val expanded = parent.context.isBottomLayout()
|
||||
/* val layout = if (bottom) R.layout.home_result_grid_expanded else R.layout.home_result_grid
|
||||
|
||||
val root = LayoutInflater.from(parent.context).inflate(layout, parent, false)
|
||||
|
@ -133,7 +133,6 @@ class HomeChildItemAdapter(
|
|||
item,
|
||||
position,
|
||||
holder.itemView,
|
||||
null, // nextFocusBehavior,
|
||||
nextFocusUp,
|
||||
nextFocusDown
|
||||
)
|
||||
|
|
|
@ -17,7 +17,6 @@ import androidx.core.view.isGone
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.lifecycle.*
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.google.android.material.bottomsheet.BottomSheetBehavior
|
||||
|
@ -234,7 +233,7 @@ class HomeFragment : Fragment() {
|
|||
return bottomSheetDialogBuilder
|
||||
}
|
||||
|
||||
fun getPairList(
|
||||
private fun getPairList(
|
||||
anime: Chip?,
|
||||
cartoons: Chip?,
|
||||
tvs: Chip?,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package com.lagradost.cloudstream3.ui.home
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -53,12 +55,12 @@ open class ParentItemAdapter(
|
|||
"value",
|
||||
recyclerView?.layoutManager?.onSaveInstanceState()
|
||||
)
|
||||
(recyclerView?.adapter as? BaseAdapter<*,*>)?.save(recyclerView)
|
||||
(recyclerView?.adapter as? BaseAdapter<*, *>)?.save(recyclerView)
|
||||
}
|
||||
|
||||
override fun restore(state: Bundle) {
|
||||
(binding as? HomepageParentBinding)?.homeChildRecyclerview?.layoutManager?.onRestoreInstanceState(
|
||||
state.getParcelable("value")
|
||||
state.getSafeParcelable<Parcelable>("value")
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -170,3 +172,8 @@ open class ParentItemAdapter(
|
|||
.toMutableList())
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
inline fun <reified T> Bundle.getSafeParcelable(key: String): T? =
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) getParcelable(key)
|
||||
else getParcelable(key, T::class.java)
|
|
@ -117,15 +117,12 @@ class HomeParentItemAdapterPreview(
|
|||
}
|
||||
|
||||
override fun restore(state: Bundle) {
|
||||
state.getParcelable<Parcelable>("resumeRecyclerView")?.let { recycle ->
|
||||
state.getSafeParcelable<Parcelable>("resumeRecyclerView")?.let { recycle ->
|
||||
resumeRecyclerView.layoutManager?.onRestoreInstanceState(recycle)
|
||||
}
|
||||
state.getParcelable<Parcelable>("bookmarkRecyclerView")?.let { recycle ->
|
||||
state.getSafeParcelable<Parcelable>("bookmarkRecyclerView")?.let { recycle ->
|
||||
bookmarkRecyclerView.layoutManager?.onRestoreInstanceState(recycle)
|
||||
}
|
||||
//state.getInt("previewViewpager").let { recycle ->
|
||||
// previewViewpager.setCurrentItem(recycle,true)
|
||||
//}
|
||||
}
|
||||
|
||||
val previewAdapter = HomeScrollAdapter(fragment = fragment)
|
||||
|
|
|
@ -152,7 +152,7 @@ class HomeViewModel : ViewModel() {
|
|||
}
|
||||
}?.distinctBy { it.first } ?: return@launchSafe
|
||||
|
||||
val length = WatchType.values().size
|
||||
val length = WatchType.entries.size
|
||||
val currentWatchTypes = mutableSetOf<WatchType>()
|
||||
|
||||
for (watch in watchStatusIds) {
|
||||
|
@ -387,7 +387,9 @@ class HomeViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
is Resource.Failure -> {
|
||||
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
|
||||
_page.postValue(data!!)
|
||||
@Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
|
||||
_preview.postValue(data!!)
|
||||
}
|
||||
|
||||
|
@ -397,9 +399,7 @@ class HomeViewModel : ViewModel() {
|
|||
}
|
||||
|
||||
fun click(callback: SearchClickCallback) {
|
||||
if (callback.action == SEARCH_ACTION_FOCUSED) {
|
||||
//focusCallback(callback.card)
|
||||
} else {
|
||||
if (callback.action != SEARCH_ACTION_FOCUSED) {
|
||||
SearchHelper.handleSearchClickCallback(callback)
|
||||
}
|
||||
}
|
||||
|
@ -516,7 +516,7 @@ class HomeViewModel : ViewModel() {
|
|||
} else {
|
||||
_page.postValue(Resource.Loading())
|
||||
if (preferredApiName != null)
|
||||
_apiName.postValue(preferredApiName)
|
||||
_apiName.postValue(preferredApiName!!)
|
||||
}
|
||||
} else {
|
||||
// if the api is found, then set it to it and save key
|
||||
|
|
|
@ -600,8 +600,4 @@ class LibraryFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
class MenuSearchView(context: Context) : SearchView(context) {
|
||||
override fun onActionViewCollapsed() {
|
||||
super.onActionViewCollapsed()
|
||||
}
|
||||
}
|
||||
class MenuSearchView(context: Context) : SearchView(context)
|
|
@ -15,6 +15,7 @@ import com.lagradost.cloudstream3.syncproviders.SyncAPI
|
|||
import com.lagradost.cloudstream3.ui.BaseAdapter
|
||||
import com.lagradost.cloudstream3.ui.BaseDiffCallback
|
||||
import com.lagradost.cloudstream3.ui.ViewHolderState
|
||||
import com.lagradost.cloudstream3.ui.home.getSafeParcelable
|
||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
|
@ -32,7 +33,7 @@ class ViewpagerAdapterViewHolderState(val binding: LibraryViewpagerPageBinding)
|
|||
}
|
||||
|
||||
override fun restore(state: Bundle) {
|
||||
state.getParcelable<Parcelable>("pageRecyclerview")?.let { recycle ->
|
||||
state.getSafeParcelable<Parcelable>("pageRecyclerview")?.let { recycle ->
|
||||
binding.pageRecyclerview.layoutManager?.onRestoreInstanceState(recycle)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ import androidx.core.view.isGone
|
|||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.media3.common.PlaybackException
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.session.MediaSession
|
||||
import androidx.media3.ui.*
|
||||
|
@ -216,7 +217,7 @@ abstract class AbstractPlayerFragment(
|
|||
return
|
||||
}
|
||||
player.handleEvent(
|
||||
CSPlayerEvent.values()[intent.getIntExtra(
|
||||
CSPlayerEvent.entries[intent.getIntExtra(
|
||||
EXTRA_CONTROL_TYPE,
|
||||
0
|
||||
)], source = PlayerEventSource.UI
|
||||
|
@ -603,12 +604,12 @@ abstract class AbstractPlayerFragment(
|
|||
}
|
||||
|
||||
fun nextResize() {
|
||||
resizeMode = (resizeMode + 1) % PlayerResize.values().size
|
||||
resizeMode = (resizeMode + 1) % PlayerResize.entries.size
|
||||
resize(resizeMode, true)
|
||||
}
|
||||
|
||||
fun resize(resize: Int, showToast: Boolean) {
|
||||
resize(PlayerResize.values()[resize], showToast)
|
||||
resize(PlayerResize.entries[resize], showToast)
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
|
|
|
@ -9,7 +9,11 @@ import android.os.Looper
|
|||
import android.util.Log
|
||||
import android.util.Rational
|
||||
import android.widget.FrameLayout
|
||||
import androidx.media3.common.C.*
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.C.TIME_UNSET
|
||||
import androidx.media3.common.C.TRACK_TYPE_AUDIO
|
||||
import androidx.media3.common.C.TRACK_TYPE_TEXT
|
||||
import androidx.media3.common.C.TRACK_TYPE_VIDEO
|
||||
import androidx.media3.common.Format
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.MimeTypes
|
||||
|
@ -19,9 +23,10 @@ import androidx.media3.common.TrackGroup
|
|||
import androidx.media3.common.TrackSelectionOverride
|
||||
import androidx.media3.common.Tracks
|
||||
import androidx.media3.common.VideoSize
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.database.StandaloneDatabaseProvider
|
||||
import androidx.media3.datasource.DataSource
|
||||
import androidx.media3.datasource.DefaultDataSourceFactory
|
||||
import androidx.media3.datasource.DefaultDataSource
|
||||
import androidx.media3.datasource.DefaultHttpDataSource
|
||||
import androidx.media3.datasource.HttpDataSource
|
||||
import androidx.media3.datasource.cache.CacheDataSource
|
||||
|
@ -66,7 +71,6 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkPlayList
|
|||
import com.lagradost.cloudstream3.utils.ExtractorLinkType
|
||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
||||
import java.io.File
|
||||
import java.lang.IllegalArgumentException
|
||||
import java.util.UUID
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import javax.net.ssl.SSLContext
|
||||
|
@ -84,7 +88,7 @@ const val toleranceBeforeUs = 300_000L
|
|||
* seek position, in microseconds. Must be non-negative.
|
||||
*/
|
||||
const val toleranceAfterUs = 300_000L
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class CS3IPlayer : IPlayer {
|
||||
private var isPlaying = false
|
||||
private var exoPlayer: ExoPlayer? = null
|
||||
|
@ -257,7 +261,6 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
private var currentSubtitles: SubtitleData? = null
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun List<Tracks.Group>.getTrack(id: String?): Pair<TrackGroup, Int>? {
|
||||
if (id == null) return null
|
||||
// This beast of an expression does:
|
||||
|
@ -342,7 +345,6 @@ class CS3IPlayer : IPlayer {
|
|||
}.flatten()
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun Tracks.Group.getFormats(): List<Pair<Format, Int>> {
|
||||
return (0 until this.mediaTrackGroup.length).mapNotNull { i ->
|
||||
if (this.isSupported)
|
||||
|
@ -371,7 +373,6 @@ class CS3IPlayer : IPlayer {
|
|||
)
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
override fun getVideoTracks(): CurrentTracks {
|
||||
val allTracks = exoPlayer?.currentTracks?.groups ?: emptyList()
|
||||
val videoTracks = allTracks.filter { it.type == TRACK_TYPE_VIDEO }
|
||||
|
@ -391,7 +392,6 @@ class CS3IPlayer : IPlayer {
|
|||
/**
|
||||
* @return True if the player should be reloaded
|
||||
* */
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
override fun setPreferredSubtitles(subtitle: SubtitleData?): Boolean {
|
||||
Log.i(TAG, "setPreferredSubtitles init $subtitle")
|
||||
currentSubtitles = subtitle
|
||||
|
@ -451,7 +451,7 @@ class CS3IPlayer : IPlayer {
|
|||
} ?: false
|
||||
}
|
||||
|
||||
var currentSubtitleOffset: Long = 0
|
||||
private var currentSubtitleOffset: Long = 0
|
||||
|
||||
override fun setSubtitleOffset(offset: Long) {
|
||||
currentSubtitleOffset = offset
|
||||
|
@ -459,7 +459,7 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
|
||||
override fun getSubtitleOffset(): Long {
|
||||
return currentSubtitleOffset //currentTextRenderer?.getRenderOffsetMs() ?: currentSubtitleOffset
|
||||
return currentSubtitleOffset
|
||||
}
|
||||
|
||||
override fun getCurrentPreferredSubtitle(): SubtitleData? {
|
||||
|
@ -470,7 +470,6 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
override fun getAspectRatio(): Rational? {
|
||||
return exoPlayer?.videoFormat?.let { format ->
|
||||
Rational(format.width, format.height)
|
||||
|
@ -481,14 +480,13 @@ class CS3IPlayer : IPlayer {
|
|||
subtitleHelper.setSubStyle(style)
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
override fun saveData() {
|
||||
Log.i(TAG, "saveData")
|
||||
updatedTime()
|
||||
|
||||
exoPlayer?.let { exo ->
|
||||
playbackPosition = exo.currentPosition
|
||||
currentWindow = exo.currentWindowIndex
|
||||
currentWindow = exo.currentMediaItemIndex
|
||||
isPlaying = exo.isPlaying
|
||||
}
|
||||
}
|
||||
|
@ -500,7 +498,7 @@ class CS3IPlayer : IPlayer {
|
|||
updatedTime()
|
||||
|
||||
exoPlayer?.apply {
|
||||
setPlayWhenReady(false)
|
||||
playWhenReady = false
|
||||
stop()
|
||||
release()
|
||||
}
|
||||
|
@ -563,7 +561,6 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
var requestSubtitleUpdate: (() -> Unit)? = null
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun createOnlineSource(headers: Map<String, String>): HttpDataSource.Factory {
|
||||
val source = OkHttpDataSource.Factory(app.baseClient).setUserAgent(USER_AGENT)
|
||||
return source.apply {
|
||||
|
@ -571,7 +568,6 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun createOnlineSource(link: ExtractorLink): HttpDataSource.Factory {
|
||||
val provider = getApiFromNameNull(link.source)
|
||||
val interceptor = provider?.getVideoInterceptor(link)
|
||||
|
@ -604,53 +600,10 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun Context.createOfflineSource(): DataSource.Factory {
|
||||
return DefaultDataSourceFactory(this, USER_AGENT)
|
||||
return DefaultDataSource.Factory(this, DefaultHttpDataSource.Factory().setUserAgent(USER_AGENT))
|
||||
}
|
||||
|
||||
/*private fun getSubSources(
|
||||
onlineSourceFactory: DataSource.Factory?,
|
||||
offlineSourceFactory: DataSource.Factory?,
|
||||
subHelper: PlayerSubtitleHelper,
|
||||
): Pair<List<SingleSampleMediaSource>, List<SubtitleData>> {
|
||||
val activeSubtitles = ArrayList<SubtitleData>()
|
||||
val subSources = subHelper.getAllSubtitles().mapNotNull { sub ->
|
||||
val subConfig = MediaItem.SubtitleConfiguration.Builder(Uri.parse(sub.url))
|
||||
.setMimeType(sub.mimeType)
|
||||
.setLanguage("_${sub.name}")
|
||||
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
||||
.build()
|
||||
when (sub.origin) {
|
||||
SubtitleOrigin.DOWNLOADED_FILE -> {
|
||||
if (offlineSourceFactory != null) {
|
||||
activeSubtitles.add(sub)
|
||||
SingleSampleMediaSource.Factory(offlineSourceFactory)
|
||||
.createMediaSource(subConfig, C.TIME_UNSET)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
SubtitleOrigin.URL -> {
|
||||
if (onlineSourceFactory != null) {
|
||||
activeSubtitles.add(sub)
|
||||
SingleSampleMediaSource.Factory(onlineSourceFactory)
|
||||
.createMediaSource(subConfig, C.TIME_UNSET)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
SubtitleOrigin.OPEN_SUBTITLES -> {
|
||||
// TODO
|
||||
throw NotImplementedError()
|
||||
}
|
||||
}
|
||||
}
|
||||
println("SUBSRC: ${subSources.size} activeSubtitles : ${activeSubtitles.size} of ${subHelper.getAllSubtitles().size} ")
|
||||
return Pair(subSources, activeSubtitles)
|
||||
}*/
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun getCache(context: Context, cacheSize: Long): SimpleCache? {
|
||||
return try {
|
||||
val databaseProvider = StandaloneDatabaseProvider(context)
|
||||
|
@ -682,7 +635,6 @@ class CS3IPlayer : IPlayer {
|
|||
return getMediaItemBuilder(mimeType).setUri(url).build()
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun getTrackSelector(context: Context, maxVideoHeight: Int?): TrackSelector {
|
||||
val trackSelector = DefaultTrackSelector(context)
|
||||
trackSelector.parameters = trackSelector.buildUponParameters()
|
||||
|
@ -696,7 +648,6 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
var currentTextRenderer: CustomTextRenderer? = null
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun buildExoPlayer(
|
||||
context: Context,
|
||||
mediaItemSlices: List<MediaItemSlice>,
|
||||
|
@ -736,7 +687,7 @@ class CS3IPlayer : IPlayer {
|
|||
textRendererOutput,
|
||||
eventHandler.looper,
|
||||
CustomSubtitleDecoderFactory()
|
||||
).also { this.currentTextRenderer = it }
|
||||
).also { renderer -> this.currentTextRenderer = renderer }
|
||||
currentTextRenderer
|
||||
} else it
|
||||
}.toTypedArray()
|
||||
|
@ -1033,7 +984,7 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
//fixme: Use onPlaybackStateChanged(int) and onPlayWhenReadyChanged(boolean, int) instead.
|
||||
override fun onPlayerStateChanged(playWhenReady: Boolean, playbackState: Int) {
|
||||
exoPlayer?.let { exo ->
|
||||
event(
|
||||
|
@ -1169,7 +1120,6 @@ class CS3IPlayer : IPlayer {
|
|||
|
||||
private var lastTimeStamps: List<EpisodeSkip.SkipStamp> = emptyList()
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
override fun addTimeStamps(timeStamps: List<EpisodeSkip.SkipStamp>) {
|
||||
lastTimeStamps = timeStamps
|
||||
timeStamps.forEach { timestamp ->
|
||||
|
@ -1187,7 +1137,6 @@ class CS3IPlayer : IPlayer {
|
|||
updatedTime(source = PlayerEventSource.Player)
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
fun onRenderFirst() {
|
||||
if (hasUsedFirstRender) { // this insures that we only call this once per player load
|
||||
return
|
||||
|
@ -1254,7 +1203,6 @@ class CS3IPlayer : IPlayer {
|
|||
}
|
||||
}
|
||||
|
||||
@SuppressLint("UnsafeOptInUsageError")
|
||||
private fun getSubSources(
|
||||
onlineSourceFactory: HttpDataSource.Factory?,
|
||||
offlineSourceFactory: DataSource.Factory?,
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.player
|
|||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.media3.common.Format
|
||||
import androidx.media3.common.MimeTypes
|
||||
|
@ -31,7 +32,7 @@ import java.nio.charset.Charset
|
|||
* @param fallbackFormat used to create a decoder based on mimetype if the subtitle string is not
|
||||
* enough to identify the subtitle format.
|
||||
**/
|
||||
@UnstableApi
|
||||
@OptIn(UnstableApi::class)
|
||||
class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder {
|
||||
companion object {
|
||||
fun updateForcedEncoding(context: Context) {
|
||||
|
@ -72,7 +73,7 @@ class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder {
|
|||
RegexOption.IGNORE_CASE
|
||||
),
|
||||
)
|
||||
val captionRegex = listOf(Regex("""(-\s?|)[\[({][\w\d\s]*?[])}]\s*"""))
|
||||
val captionRegex = listOf(Regex("""(-\s?|)[\[({][\w\s]*?[])}]\s*"""))
|
||||
|
||||
//https://emptycharacter.com/
|
||||
//https://www.fileformat.info/info/unicode/char/200b/index.htm
|
||||
|
@ -262,7 +263,7 @@ class CustomDecoder(private val fallbackFormat: Format?) : SubtitleDecoder {
|
|||
}
|
||||
|
||||
/** See https://github.com/google/ExoPlayer/blob/release-v2/library/core/src/main/java/com/google/android/exoplayer2/text/SubtitleDecoderFactory.java */
|
||||
@UnstableApi
|
||||
@OptIn(UnstableApi::class)
|
||||
class CustomSubtitleDecoderFactory : SubtitleDecoderFactory {
|
||||
override fun supportsFormat(format: Format): Boolean {
|
||||
// return SubtitleDecoderFactory.DEFAULT.supportsFormat(format)
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
package com.lagradost.cloudstream3.ui.player
|
||||
|
||||
import android.os.Looper
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.exoplayer.text.SubtitleDecoderFactory
|
||||
import androidx.media3.exoplayer.text.TextOutput
|
||||
|
||||
@UnstableApi
|
||||
@OptIn(UnstableApi::class)
|
||||
class CustomTextRenderer(
|
||||
offset: Long,
|
||||
output: TextOutput?,
|
||||
|
|
|
@ -49,7 +49,7 @@ class DownloadFileGenerator(
|
|||
return null
|
||||
}
|
||||
|
||||
fun cleanDisplayName(name: String): String {
|
||||
private fun cleanDisplayName(name: String): String {
|
||||
return name.substringBeforeLast('.').trim()
|
||||
}
|
||||
|
||||
|
|
|
@ -8,14 +8,10 @@ import androidx.activity.OnBackPressedCallback
|
|||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.lagradost.cloudstream3.CommonActivity
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||
import com.lagradost.safefile.SafeFile
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.ui.player.OfflinePlaybackHelper.playLink
|
||||
import com.lagradost.cloudstream3.ui.player.OfflinePlaybackHelper.playUri
|
||||
|
||||
const val DTAG = "PlayerActivity"
|
||||
|
||||
class DownloadedPlayerActivity : AppCompatActivity() {
|
||||
private val dTAG = "DownloadedPlayerAct"
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHO
|
|||
import android.view.animation.AlphaAnimation
|
||||
import android.view.animation.Animation
|
||||
import android.view.animation.AnimationUtils
|
||||
import androidx.annotation.OptIn
|
||||
import android.widget.LinearLayout
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.graphics.blue
|
||||
|
@ -35,6 +36,7 @@ import androidx.core.view.isGone
|
|||
import androidx.core.view.isInvisible
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.core.widget.doOnTextChanged
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.lagradost.cloudstream3.CommonActivity.keyEventListener
|
||||
|
@ -50,7 +52,6 @@ import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvid
|
|||
import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper
|
||||
import com.lagradost.cloudstream3.ui.result.setText
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
|
@ -245,6 +246,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
fadeAnimation.duration = 100
|
||||
fadeAnimation.fillAfter = true
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
val sView = subView
|
||||
val sStyle = subStyle
|
||||
if (sView != null && sStyle != null) {
|
||||
|
@ -300,42 +302,40 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
|
||||
private fun restoreOrientationWithSensor(activity: Activity) {
|
||||
val currentOrientation = activity.resources.configuration.orientation
|
||||
var orientation = 0
|
||||
when (currentOrientation) {
|
||||
val orientation = when (currentOrientation) {
|
||||
Configuration.ORIENTATION_LANDSCAPE ->
|
||||
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
|
||||
Configuration.ORIENTATION_SQUARE, Configuration.ORIENTATION_UNDEFINED ->
|
||||
orientation = dynamicOrientation()
|
||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
|
||||
Configuration.ORIENTATION_PORTRAIT ->
|
||||
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
|
||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
|
||||
|
||||
else -> dynamicOrientation()
|
||||
}
|
||||
activity.requestedOrientation = orientation
|
||||
}
|
||||
|
||||
private fun toggleOrientationWithSensor(activity: Activity) {
|
||||
val currentOrientation = activity.resources.configuration.orientation
|
||||
var orientation = 0
|
||||
when (currentOrientation) {
|
||||
val orientation: Int = when (currentOrientation) {
|
||||
Configuration.ORIENTATION_LANDSCAPE ->
|
||||
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
|
||||
|
||||
Configuration.ORIENTATION_SQUARE, Configuration.ORIENTATION_UNDEFINED ->
|
||||
orientation = dynamicOrientation()
|
||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
|
||||
|
||||
Configuration.ORIENTATION_PORTRAIT ->
|
||||
orientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE
|
||||
|
||||
else -> dynamicOrientation()
|
||||
}
|
||||
activity.requestedOrientation = orientation
|
||||
}
|
||||
|
||||
open fun lockOrientation(activity: Activity) {
|
||||
val display =
|
||||
@Suppress("DEPRECATION")
|
||||
val display = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R)
|
||||
(activity.getSystemService(Context.WINDOW_SERVICE) as WindowManager).defaultDisplay
|
||||
else activity.display!!
|
||||
val rotation = display.rotation
|
||||
val currentOrientation = activity.resources.configuration.orientation
|
||||
var orientation = 0
|
||||
val orientation: Int
|
||||
when (currentOrientation) {
|
||||
Configuration.ORIENTATION_LANDSCAPE ->
|
||||
orientation =
|
||||
|
@ -344,15 +344,14 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
else
|
||||
ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
|
||||
|
||||
Configuration.ORIENTATION_SQUARE, Configuration.ORIENTATION_UNDEFINED ->
|
||||
orientation = dynamicOrientation()
|
||||
|
||||
Configuration.ORIENTATION_PORTRAIT ->
|
||||
orientation =
|
||||
if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_270)
|
||||
ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
else
|
||||
ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
|
||||
|
||||
else -> orientation = dynamicOrientation()
|
||||
}
|
||||
activity.requestedOrientation = orientation
|
||||
}
|
||||
|
@ -1167,6 +1166,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
return true
|
||||
}
|
||||
|
||||
@SuppressLint("GestureBackNavigation")
|
||||
private fun handleKeyEvent(event: KeyEvent, hasNavigated: Boolean): Boolean {
|
||||
if (hasNavigated) {
|
||||
autoHide()
|
||||
|
@ -1581,7 +1581,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
|
|||
}
|
||||
}
|
||||
// cs3 is peak media center
|
||||
setRemainingTimeCounter(durationMode || Globals.isLayout(Globals.TV))
|
||||
setRemainingTimeCounter(durationMode || isLayout(TV))
|
||||
playerBinding?.exoPosition?.doOnTextChanged { _, _, _, _ ->
|
||||
updateRemainingTime()
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.app.Dialog
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.ColorStateList
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
|
@ -13,6 +14,7 @@ import android.view.View
|
|||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.animation.addListener
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.isGone
|
||||
|
@ -21,6 +23,7 @@ import androidx.lifecycle.ViewModelProvider
|
|||
import androidx.preference.PreferenceManager
|
||||
import androidx.media3.common.Format.NO_VALUE
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.lagradost.cloudstream3.*
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||
|
@ -63,6 +66,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
|
|||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||
import com.lagradost.safefile.SafeFile
|
||||
import kotlinx.coroutines.Job
|
||||
import java.io.Serializable
|
||||
import java.util.*
|
||||
import kotlin.math.abs
|
||||
|
||||
|
@ -234,7 +238,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
private fun closestQuality(target: Int?): Qualities {
|
||||
if (target == null) return Qualities.Unknown
|
||||
return Qualities.values().minBy { abs(it.value - target) }
|
||||
return Qualities.entries.minBy { abs(it.value - target) }
|
||||
}
|
||||
|
||||
private fun getLinkPriority(
|
||||
|
@ -367,8 +371,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
binding.subtitleAdapter.choiceMode = AbsListView.CHOICE_MODE_SINGLE
|
||||
binding.subtitleAdapter.adapter = arrayAdapter
|
||||
val adapter =
|
||||
binding.subtitleAdapter.adapter as? ArrayAdapter<AbstractSubtitleEntities.SubtitleEntity>
|
||||
|
||||
binding.subtitleAdapter.setOnItemClickListener { _, _, position, _ ->
|
||||
currentSubtitle = currentSubtitles.getOrNull(position) ?: return@setOnItemClickListener
|
||||
|
@ -379,8 +381,8 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
|
||||
fun setSubtitlesList(list: List<AbstractSubtitleEntities.SubtitleEntity>) {
|
||||
currentSubtitles = list
|
||||
adapter?.clear()
|
||||
adapter?.addAll(currentSubtitles)
|
||||
arrayAdapter.clear()
|
||||
arrayAdapter.addAll(currentSubtitles)
|
||||
}
|
||||
|
||||
val currentTempMeta = getMetaData()
|
||||
|
@ -522,7 +524,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
//TODO: Set year text from currently loaded movie on Player
|
||||
//dialog.subtitles_search_year?.setText(currentTempMeta.year)
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
private fun openSubPicker() {
|
||||
try {
|
||||
subsPathPicker.launch(
|
||||
|
@ -795,7 +797,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
settingsManager.edit().putString(
|
||||
ctx.getString(R.string.subtitles_encoding_key), prefValues[it]
|
||||
).apply()
|
||||
|
||||
updateForcedEncoding(ctx)
|
||||
dismiss()
|
||||
player.seekTime(-1) // to update subtitles, a dirty trick
|
||||
|
@ -1290,7 +1291,7 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
private fun unwrapBundle(savedInstanceState: Bundle?) {
|
||||
Log.i(TAG, "unwrapBundle = $savedInstanceState")
|
||||
savedInstanceState?.let { bundle ->
|
||||
sync.addSyncs(bundle.getSerializable("syncData") as? HashMap<String, String>?)
|
||||
sync.addSyncs(bundle.getSafeSerializable<HashMap<String, String>>("syncData"))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1507,3 +1508,6 @@ class GeneratorPlayer : FullScreenPlayer() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
inline fun <reified T : Serializable> Bundle.getSafeSerializable(key: String) : T? = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) getSerializable(key) as? T else getSerializable(key, T::class.java)
|
|
@ -8,7 +8,6 @@ import com.lagradost.cloudstream3.utils.EpisodeSkip
|
|||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
|
||||
enum class PlayerEventType(val value: Int) {
|
||||
//Stop(-1),
|
||||
Pause(0),
|
||||
Play(1),
|
||||
SeekForward(2),
|
||||
|
|
|
@ -4,7 +4,6 @@ import android.net.Uri
|
|||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.amap
|
||||
import com.lagradost.cloudstream3.utils.ExtractorLink
|
||||
import com.lagradost.cloudstream3.ui.player.ExtractorUri
|
||||
import com.lagradost.cloudstream3.utils.INFER_TYPE
|
||||
import com.lagradost.cloudstream3.utils.Qualities
|
||||
import com.lagradost.cloudstream3.utils.loadExtractor
|
||||
|
|
|
@ -29,6 +29,7 @@ import android.os.Message;
|
|||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.Format;
|
||||
import androidx.media3.common.text.Cue;
|
||||
|
@ -66,7 +67,7 @@ import java.util.stream.Collectors;
|
|||
* obtained from a {@link SubtitleDecoderFactory}. The actual rendering of the subtitle {@link Cue}s
|
||||
* is delegated to a {@link TextOutput}.
|
||||
*/
|
||||
@UnstableApi
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class NonFinalTextRenderer extends BaseRenderer implements Callback {
|
||||
|
||||
private static final String TAG = "TextRenderer";
|
||||
|
@ -74,7 +75,7 @@ public class NonFinalTextRenderer extends BaseRenderer implements Callback {
|
|||
/**
|
||||
* @param trackType The track type that the renderer handles. One of the {@link C} {@code
|
||||
* TRACK_TYPE_*} constants.
|
||||
* @param outputHandler
|
||||
* @param outputHandler todo description
|
||||
*/
|
||||
public NonFinalTextRenderer(int trackType, @Nullable Handler outputHandler) {
|
||||
super(trackType);
|
||||
|
@ -416,13 +417,11 @@ public class NonFinalTextRenderer extends BaseRenderer implements Callback {
|
|||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public boolean handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MSG_UPDATE_OUTPUT:
|
||||
invokeUpdateOutputInternal((List<Cue>) msg.obj);
|
||||
return true;
|
||||
default:
|
||||
throw new IllegalStateException();
|
||||
if (msg.what == MSG_UPDATE_OUTPUT) {
|
||||
invokeUpdateOutputInternal((List<Cue>) msg.obj);
|
||||
return true;
|
||||
}
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
|
||||
private void invokeUpdateOutputInternal(List<Cue> cues) {
|
||||
|
@ -441,7 +440,6 @@ public class NonFinalTextRenderer extends BaseRenderer implements Callback {
|
|||
}
|
||||
).collect(Collectors.toList());
|
||||
|
||||
output.onCues(fixedCues);
|
||||
output.onCues(new CueGroup(fixedCues, 0L));
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@ import android.app.Activity
|
|||
import android.content.ContentUris
|
||||
import android.net.Uri
|
||||
import androidx.core.content.ContextCompat.getString
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.ui.player.ExtractorUri
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
||||
import com.lagradost.safefile.SafeFile
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import kotlinx.coroutines.launch
|
|||
|
||||
class PlayerGeneratorViewModel : ViewModel() {
|
||||
companion object {
|
||||
val TAG = "PlayViewGen"
|
||||
const val TAG = "PlayViewGen"
|
||||
}
|
||||
|
||||
private var generator: IGenerator? = null
|
||||
|
|
|
@ -4,7 +4,9 @@ import android.util.Log
|
|||
import android.util.TypedValue
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.media3.common.MimeTypes
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.SubtitleView
|
||||
import com.lagradost.cloudstream3.SubtitleFile
|
||||
import com.lagradost.cloudstream3.ui.player.CustomDecoder.Companion.regexSubtitlesToRemoveBloat
|
||||
|
@ -47,6 +49,7 @@ data class SubtitleData(
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
class PlayerSubtitleHelper {
|
||||
private var activeSubtitles: Set<SubtitleData> = emptySet()
|
||||
private var allSubtitles: Set<SubtitleData> = emptySet()
|
||||
|
|
|
@ -239,7 +239,11 @@ private class M3u8PreviewGenerator(override var params: ImageParams) : IPreviewG
|
|||
// generated images 1:1 to idx of hsl
|
||||
private var images: Array<Bitmap?> = arrayOf()
|
||||
|
||||
private val TAG = "PreviewImgM3u8"
|
||||
companion object {
|
||||
private const val TAG = "PreviewImgM3u8"
|
||||
}
|
||||
|
||||
|
||||
|
||||
// prefixSum[i] = sum(hsl.ts[0..i].time)
|
||||
// where [0] = 0, [1] = hsl.ts[0].time aka time at start of segment, do [b] - [a] for range a,b
|
||||
|
@ -388,13 +392,6 @@ private class M3u8PreviewGenerator(override var params: ImageParams) : IPreviewG
|
|||
logError(t)
|
||||
continue
|
||||
}
|
||||
|
||||
/*
|
||||
val buffer = hsl.resolveLinkSafe(index) ?: continue
|
||||
tmpFile?.writeBytes(buffer)
|
||||
val buff = FileOutputStream(tmpFile)
|
||||
retriever.setDataSource(buff.fd)
|
||||
val frame = retriever.getFrameAtTime(0L)*/
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,14 +409,16 @@ private class Mp4PreviewGenerator(override var params: ImageParams) : IPreviewGe
|
|||
null
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "PreviewImgMp4"
|
||||
}
|
||||
|
||||
override fun hasPreview(): Boolean {
|
||||
synchronized(images) {
|
||||
return loadedLod >= MIN_LOD
|
||||
}
|
||||
}
|
||||
|
||||
val TAG = "PreviewImgMp4"
|
||||
|
||||
override fun getPreviewImage(fraction: Float): Bitmap? {
|
||||
synchronized(images) {
|
||||
if (loadedLod < MIN_LOD) {
|
||||
|
@ -524,7 +523,7 @@ private class Mp4PreviewGenerator(override var params: ImageParams) : IPreviewGe
|
|||
val fraction = (1.0f.div((1 shl l).toFloat()) + i * 1.0f.div(items.toFloat()))
|
||||
Log.i(TAG, "Generating preview for ${fraction * 100}%")
|
||||
val frame = durationUs * fraction
|
||||
val img = retriever.image(frame.toLong(), params);
|
||||
val img = retriever.image(frame.toLong(), params)
|
||||
if (!scope.isActive) return
|
||||
if (img == null || img.width <= 1 || img.height <= 1) continue
|
||||
synchronized(images) {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package com.lagradost.cloudstream3.ui.player
|
||||
|
||||
import android.util.Log
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull
|
||||
import com.lagradost.cloudstream3.APIHolder.unixTime
|
||||
import com.lagradost.cloudstream3.LoadResponse
|
||||
|
|
|
@ -17,7 +17,6 @@ class PriorityAdapter<T>(override val items: MutableList<SourcePriority<T>>) :
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return PriorityViewHolder(
|
||||
PlayerPrioritizeItemBinding.inflate(LayoutInflater.from(parent.context),parent,false),
|
||||
//LayoutInflater.from(parent.context).inflate(R.layout.player_prioritize_item, parent, false)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -31,10 +30,6 @@ class PriorityAdapter<T>(override val items: MutableList<SourcePriority<T>>) :
|
|||
val binding: PlayerPrioritizeItemBinding,
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
fun <T> bind(item: SourcePriority<T>) {
|
||||
/* val plusButton: ImageView = itemView.add_button
|
||||
val subtractButton: ImageView = itemView.subtract_button
|
||||
val priorityText: TextView = itemView.priority_text
|
||||
val priorityNumber: TextView = itemView.priority_number*/
|
||||
binding.priorityText.text = item.name
|
||||
|
||||
fun updatePriority() {
|
||||
|
|
|
@ -29,8 +29,6 @@ class ProfilesAdapter(
|
|||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return ProfilesViewHolder(
|
||||
PlayerQualityProfileItemBinding.inflate(LayoutInflater.from(parent.context),parent,false)
|
||||
//LayoutInflater.from(parent.context)
|
||||
// .inflate(R.layout.player_quality_profile_item, parent, false)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package com.lagradost.cloudstream3.ui.player.source_priority
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.StringRes
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
|
@ -104,7 +103,7 @@ object QualityDataHelper {
|
|||
* Must under all circumstances at least return one profile
|
||||
**/
|
||||
fun getProfiles(): List<QualityProfile> {
|
||||
val availableTypes = QualityProfileType.values().toMutableList()
|
||||
val availableTypes = QualityProfileType.entries.toMutableList()
|
||||
val profiles = (1..PROFILE_COUNT).map { profileNumber ->
|
||||
// Get the real type
|
||||
val type = getQualityProfileType(profileNumber)
|
||||
|
@ -140,12 +139,12 @@ object QualityDataHelper {
|
|||
}
|
||||
}
|
||||
|
||||
QualityProfileType.values().forEach {
|
||||
QualityProfileType.entries.forEach {
|
||||
if (it.unique) insertType(profiles, it)
|
||||
}
|
||||
|
||||
debugAssert({
|
||||
!QualityProfileType.values().all { type ->
|
||||
!QualityProfileType.entries.all { type ->
|
||||
!type.unique || profiles.any { it.type == type }
|
||||
}
|
||||
}, { "All unique quality types do not exist" })
|
||||
|
|
|
@ -65,7 +65,7 @@ class QualityProfileDialog(
|
|||
|
||||
setDefaultBtt.setOnClickListener {
|
||||
val currentProfile = getCurrentProfile() ?: return@setOnClickListener
|
||||
val choices = QualityDataHelper.QualityProfileType.values()
|
||||
val choices = QualityDataHelper.QualityProfileType.entries
|
||||
.filter { it != QualityDataHelper.QualityProfileType.None }
|
||||
val choiceNames = choices.map { txt(it.stringRes).asString(context) }
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ class SourcePriorityDialog(
|
|||
)
|
||||
|
||||
qualitiesRecyclerView.adapter = PriorityAdapter(
|
||||
Qualities.values().mapNotNull {
|
||||
Qualities.entries.mapNotNull {
|
||||
SourcePriority(
|
||||
it,
|
||||
Qualities.getStringByIntFull(it.value).ifBlank { return@mapNotNull null },
|
||||
|
|
|
@ -3,8 +3,6 @@ package com.lagradost.cloudstream3.ui.result
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.IdRes
|
||||
import androidx.annotation.LayoutRes
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
@ -70,8 +68,7 @@ class ActorAdaptor(
|
|||
}
|
||||
}
|
||||
|
||||
private inner class CardViewHolder
|
||||
constructor(
|
||||
private inner class CardViewHolder(
|
||||
val binding: CastItemBinding,
|
||||
private val focusCallback: (View?) -> Unit = {}
|
||||
) :
|
||||
|
|
|
@ -169,8 +169,7 @@ class EpisodeAdapter(
|
|||
return cardList.size
|
||||
}
|
||||
|
||||
class EpisodeCardViewHolderLarge
|
||||
constructor(
|
||||
class EpisodeCardViewHolderLarge(
|
||||
val binding: ResultEpisodeLargeBinding,
|
||||
private val hasDownloadSupport: Boolean,
|
||||
private val clickCallback: (EpisodeClickEvent) -> Unit,
|
||||
|
@ -335,8 +334,7 @@ class EpisodeAdapter(
|
|||
}
|
||||
}
|
||||
|
||||
class EpisodeCardViewHolderSmall
|
||||
constructor(
|
||||
class EpisodeCardViewHolderSmall(
|
||||
val binding: ResultEpisodeBinding,
|
||||
private val hasDownloadSupport: Boolean,
|
||||
private val clickCallback: (EpisodeClickEvent) -> Unit,
|
||||
|
|
|
@ -8,18 +8,6 @@ import com.lagradost.cloudstream3.databinding.ResultMiniImageBinding
|
|||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
|
||||
/*
|
||||
class ImageAdapter(context: Context, val resource: Int) : ArrayAdapter<Int>(context, resource) {
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val newConvertView = convertView ?: run {
|
||||
val mInflater = context
|
||||
.getSystemService(Activity.LAYOUT_INFLATER_SERVICE) as LayoutInflater
|
||||
mInflater.inflate(resource, null)
|
||||
}
|
||||
getItem(position)?.let { (newConvertView as? ImageView?)?.setImageResource(it) }
|
||||
return newConvertView
|
||||
}
|
||||
}*/
|
||||
const val IMAGE_CLICK = 0
|
||||
const val IMAGE_LONG_CLICK = 1
|
||||
|
||||
|
@ -66,8 +54,7 @@ class ImageAdapter(
|
|||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
class ImageViewHolder
|
||||
constructor(val binding: ResultMiniImageBinding) :
|
||||
class ImageViewHolder(val binding: ResultMiniImageBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
fun bind(
|
||||
img: Int,
|
||||
|
|
|
@ -78,11 +78,12 @@ import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
|||
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||
|
||||
open class ResultFragmentPhone : FullScreenPlayer() {
|
||||
private val gestureRegionsListener = object : PanelsChildGestureRegionObserver.GestureRegionsListener {
|
||||
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {
|
||||
binding?.resultOverlappingPanels?.setChildGestureRegions(gestureRegions)
|
||||
private val gestureRegionsListener =
|
||||
object : PanelsChildGestureRegionObserver.GestureRegionsListener {
|
||||
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {
|
||||
binding?.resultOverlappingPanels?.setChildGestureRegions(gestureRegions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected lateinit var viewModel: ResultViewModel2
|
||||
protected lateinit var syncModel: SyncViewModel
|
||||
|
@ -336,7 +337,6 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
}
|
||||
|
||||
|
||||
|
||||
// ===== ===== =====
|
||||
|
||||
resultBinding?.apply {
|
||||
|
@ -430,16 +430,16 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
if (newStatus == null) return@toggleSubscriptionStatus
|
||||
|
||||
val message = if (newStatus) {
|
||||
// Kinda icky to have this here, but it works.
|
||||
SubscriptionWorkManager.enqueuePeriodicWork(context)
|
||||
R.string.subscription_new
|
||||
} else {
|
||||
R.string.subscription_deleted
|
||||
}
|
||||
// Kinda icky to have this here, but it works.
|
||||
SubscriptionWorkManager.enqueuePeriodicWork(context)
|
||||
R.string.subscription_new
|
||||
} else {
|
||||
R.string.subscription_deleted
|
||||
}
|
||||
|
||||
val name = (viewModel.page.value as? Resource.Success)?.value?.title
|
||||
?: txt(R.string.no_data).asStringNull(context) ?: ""
|
||||
showToast(txt(message, name), Toast.LENGTH_SHORT)
|
||||
val name = (viewModel.page.value as? Resource.Success)?.value?.title
|
||||
?: txt(R.string.no_data).asStringNull(context) ?: ""
|
||||
showToast(txt(message, name), Toast.LENGTH_SHORT)
|
||||
}
|
||||
context?.let { openBatteryOptimizationSettings(it) }
|
||||
}
|
||||
|
@ -473,8 +473,16 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
if (act.isCastApiAvailable()) {
|
||||
try {
|
||||
CastButtonFactory.setUpMediaRouteButton(act, this)
|
||||
val castContext = CastContext.getSharedInstance(act.applicationContext)
|
||||
isGone = castContext.castState == CastState.NO_DEVICES_AVAILABLE
|
||||
CastContext.getSharedInstance(act.applicationContext) {
|
||||
it.run()
|
||||
}.addOnCompleteListener {
|
||||
isGone = if (it.isSuccessful) {
|
||||
it.result.castState == CastState.NO_DEVICES_AVAILABLE
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
}
|
||||
// this shit leaks for some reason
|
||||
//castContext.addCastStateListener { state ->
|
||||
// media_route_button?.isGone = state == CastState.NO_DEVICES_AVAILABLE
|
||||
|
@ -961,12 +969,12 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
|
||||
setOnClickListener { fab ->
|
||||
activity?.showBottomDialog(
|
||||
WatchType.values().map { fab.context.getString(it.stringRes) }.toList(),
|
||||
WatchType.entries.map { fab.context.getString(it.stringRes) }.toList(),
|
||||
watchType.ordinal,
|
||||
fab.context.getString(R.string.action_add_to_bookmarks),
|
||||
showApply = false,
|
||||
{}) {
|
||||
viewModel.updateWatchStatus(WatchType.values()[it], context)
|
||||
viewModel.updateWatchStatus(WatchType.entries[it], context)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1046,7 +1054,7 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
text?.asStringNull(ctx) ?: return@mapNotNull null
|
||||
)
|
||||
}) {
|
||||
viewModel.changeDubStatus(DubStatus.values()[itemId])
|
||||
viewModel.changeDubStatus(DubStatus.entries[itemId])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1103,7 +1111,8 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
|||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(gestureRegionsListener)
|
||||
PanelsChildGestureRegionObserver.Provider.get()
|
||||
.addGestureRegionsUpdateListener(gestureRegionsListener)
|
||||
}
|
||||
|
||||
private fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
|
||||
|
|
|
@ -56,7 +56,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.navigate
|
|||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
|
||||
class ResultFragmentTv : Fragment() {
|
||||
protected lateinit var viewModel: ResultViewModel2
|
||||
private lateinit var viewModel: ResultViewModel2
|
||||
private var binding: FragmentResultTvBinding? = null
|
||||
|
||||
override fun onDestroyView() {
|
||||
|
@ -418,10 +418,6 @@ class ResultFragmentTv : Fragment() {
|
|||
|
||||
resultCastItems.layoutManager = object : LinearListLayout(view.context) {
|
||||
|
||||
override fun onInterceptFocusSearch(focused: View, direction: Int): View? {
|
||||
return super.onInterceptFocusSearch(focused, direction)
|
||||
}
|
||||
|
||||
override fun onRequestChildFocus(
|
||||
parent: RecyclerView,
|
||||
state: RecyclerView.State,
|
||||
|
@ -649,7 +645,7 @@ class ResultFragmentTv : Fragment() {
|
|||
|
||||
binding?.apply {
|
||||
|
||||
(data as? Resource.Success)?.value?.let { (text, ep) ->
|
||||
(data as? Resource.Success)?.value?.let { (_, ep) ->
|
||||
|
||||
resultPlayMovieButton.setOnClickListener {
|
||||
viewModel.handleAction(
|
||||
|
@ -817,45 +813,8 @@ class ResultFragmentTv : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Okay so what is this fuckery?
|
||||
* Basically Android TV will crash if you request a new focus while
|
||||
* the adapter gets updated.
|
||||
*
|
||||
* This means that if you load thumbnails and request a next focus at the same time
|
||||
* the app will crash without any way to catch it!
|
||||
*
|
||||
* How to bypass this?
|
||||
* This code basically steals the focus for 500ms and puts it in an inescapable view
|
||||
* then lets out the focus by requesting focus to result_episodes
|
||||
*/
|
||||
|
||||
val hasEpisodes =
|
||||
!(resultEpisodes.adapter as? EpisodeAdapter?)?.cardList.isNullOrEmpty()
|
||||
/*val focus = activity?.currentFocus
|
||||
|
||||
if (hasEpisodes) {
|
||||
// Make it impossible to focus anywhere else!
|
||||
temporaryNoFocus.isFocusable = true
|
||||
temporaryNoFocus.requestFocus()
|
||||
}*/
|
||||
|
||||
(resultEpisodes.adapter as? EpisodeAdapter)?.updateList(episodes.value)
|
||||
|
||||
/* if (hasEpisodes) main {
|
||||
|
||||
delay(500)
|
||||
// This might make some people sad as it changes the focus when leaving an episode :(
|
||||
if(focus?.requestFocus() == true) {
|
||||
temporaryNoFocus.isFocusable = false
|
||||
return@main
|
||||
}
|
||||
temporaryNoFocus.isFocusable = false
|
||||
temporaryNoFocus.requestFocus()
|
||||
}
|
||||
|
||||
if (hasNoFocus())
|
||||
binding?.resultEpisodes?.requestFocus()*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2723,7 +2723,7 @@ class ResultViewModel2 : ViewModel() {
|
|||
val id: Int?,
|
||||
) : LoadResponse
|
||||
|
||||
fun loadSmall(activity: Activity?, searchResponse: SearchResponse) = ioSafe {
|
||||
fun loadSmall(searchResponse: SearchResponse) = ioSafe {
|
||||
val url = searchResponse.url
|
||||
_page.postValue(Resource.Loading(url))
|
||||
_episodes.postValue(Resource.Loading())
|
||||
|
|
|
@ -63,8 +63,7 @@ class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter<Recycler
|
|||
}
|
||||
|
||||
|
||||
private class SelectViewHolder
|
||||
constructor(
|
||||
private class SelectViewHolder(
|
||||
binding: ResultSelectionBinding,
|
||||
) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
|
|
@ -11,7 +11,7 @@ import com.lagradost.cloudstream3.SearchResponse
|
|||
import com.lagradost.cloudstream3.databinding.SearchResultGridBinding
|
||||
import com.lagradost.cloudstream3.databinding.SearchResultGridExpandedBinding
|
||||
import com.lagradost.cloudstream3.ui.AutofitRecyclerView
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.IsBottomLayout
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.isBottomLayout
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
|
@ -41,7 +41,7 @@ class SearchAdapter(
|
|||
val inflater = LayoutInflater.from(parent.context)
|
||||
|
||||
val layout =
|
||||
if (parent.context.IsBottomLayout()) SearchResultGridExpandedBinding.inflate(
|
||||
if (parent.context.isBottomLayout()) SearchResultGridExpandedBinding.inflate(
|
||||
inflater,
|
||||
parent,
|
||||
false
|
||||
|
@ -83,8 +83,7 @@ class SearchAdapter(
|
|||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
class CardViewHolder
|
||||
constructor(
|
||||
class CardViewHolder(
|
||||
val binding: ViewBinding,
|
||||
private val clickCallback: (SearchClickCallback) -> Unit,
|
||||
resView: AutofitRecyclerView
|
||||
|
|
|
@ -1,16 +1,11 @@
|
|||
package com.lagradost.cloudstream3.ui.search
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.databinding.AccountSingleBinding
|
||||
import com.lagradost.cloudstream3.databinding.SearchHistoryItemBinding
|
||||
|
||||
data class SearchHistoryItem(
|
||||
|
@ -63,8 +58,7 @@ class SearchHistoryAdaptor(
|
|||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
class CardViewHolder
|
||||
constructor(
|
||||
class CardViewHolder(
|
||||
val binding: SearchHistoryItemBinding,
|
||||
private val clickCallback: (SearchHistoryCallback) -> Unit,
|
||||
) :
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.lagradost.cloudstream3.ui.search
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
|
@ -37,16 +38,12 @@ object SearchResultBuilder {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nextFocusBehavior True if first, False if last, Null if between.
|
||||
* Used to prevent escaping the adapter horizontally (focus wise).
|
||||
*/
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
fun bind(
|
||||
clickCallback: (SearchClickCallback) -> Unit,
|
||||
card: SearchResponse,
|
||||
position: Int,
|
||||
itemView: View,
|
||||
nextFocusBehavior: Boolean? = null,
|
||||
nextFocusUp: Int? = null,
|
||||
nextFocusDown: Int? = null,
|
||||
colorCallback : ((Palette) -> Unit)? = null
|
||||
|
|
|
@ -3,11 +3,9 @@ package com.lagradost.cloudstream3.ui.search
|
|||
import com.lagradost.cloudstream3.SearchQuality
|
||||
import com.lagradost.cloudstream3.SearchResponse
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.SyncApis
|
||||
|
||||
//TODO Relevance of this class since it's not used
|
||||
class SyncSearchViewModel {
|
||||
private val repos = SyncApis
|
||||
|
||||
data class SyncSearchResultSearchResponse(
|
||||
override val name: String,
|
||||
override val url: String,
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.lagradost.cloudstream3.ui.settings
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -13,7 +14,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
|||
class AccountClickCallback(val action: Int, val view: View, val card: AuthAPI.LoginInfo)
|
||||
|
||||
class AccountAdapter(
|
||||
val cardList: List<AuthAPI.LoginInfo>,
|
||||
private val cardList: List<AuthAPI.LoginInfo>,
|
||||
private val clickCallback: (AccountClickCallback) -> Unit
|
||||
) :
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
|
@ -42,12 +43,12 @@ class AccountAdapter(
|
|||
return cardList[position].accountIndex.toLong()
|
||||
}
|
||||
|
||||
class CardViewHolder
|
||||
constructor(val binding: AccountSingleBinding, private val clickCallback: (AccountClickCallback) -> Unit) :
|
||||
class CardViewHolder(val binding: AccountSingleBinding, private val clickCallback: (AccountClickCallback) -> Unit) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
// private val pfp: ImageView = itemView.findViewById(R.id.account_profile_picture)!!
|
||||
// private val accountName: TextView = itemView.findViewById(R.id.account_name)!!
|
||||
|
||||
@SuppressLint("StringFormatInvalid")
|
||||
fun bind(card: AuthAPI.LoginInfo) {
|
||||
// just in case name is null account index will show, should never happened
|
||||
binding.accountName.text = card.name ?: "%s %d".format(
|
||||
|
|
|
@ -26,7 +26,6 @@ import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
|||
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
||||
import com.lagradost.cloudstream3.utils.DataStoreHelper
|
||||
import com.lagradost.cloudstream3.utils.UIHelper
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper
|
||||
|
|
|
@ -28,10 +28,8 @@ import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
|||
import com.lagradost.cloudstream3.network.initClient
|
||||
import com.lagradost.cloudstream3.ui.EasterEggMonke
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.beneneCount
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
|
||||
|
|
|
@ -10,7 +10,6 @@ import com.lagradost.cloudstream3.mvvm.logError
|
|||
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getFolderSize
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn
|
||||
|
@ -108,7 +107,7 @@ class SettingsPlayer : PreferenceFragmentCompat() {
|
|||
getPref(R.string.hide_player_control_names_key)?.hideOn(TV)
|
||||
|
||||
getPref(R.string.quality_pref_key)?.setOnPreferenceClickListener {
|
||||
val prefValues = Qualities.values().map { it.value }.reversed().toMutableList()
|
||||
val prefValues = Qualities.entries.map { it.value }.reversed().toMutableList()
|
||||
prefValues.remove(Qualities.Unknown.value)
|
||||
|
||||
val prefNames = prefValues.map { Qualities.getStringByInt(it) }
|
||||
|
@ -116,7 +115,7 @@ class SettingsPlayer : PreferenceFragmentCompat() {
|
|||
val currentQuality =
|
||||
settingsManager.getInt(
|
||||
getString(R.string.quality_pref_key),
|
||||
Qualities.values().last().value
|
||||
Qualities.entries.last().value
|
||||
)
|
||||
|
||||
activity?.showBottomDialog(
|
||||
|
@ -132,7 +131,7 @@ class SettingsPlayer : PreferenceFragmentCompat() {
|
|||
}
|
||||
|
||||
getPref(R.string.quality_pref_mobile_data_key)?.setOnPreferenceClickListener {
|
||||
val prefValues = Qualities.values().map { it.value }.reversed().toMutableList()
|
||||
val prefValues = Qualities.entries.map { it.value }.reversed().toMutableList()
|
||||
prefValues.remove(Qualities.Unknown.value)
|
||||
|
||||
val prefNames = prefValues.map { Qualities.getStringByInt(it) }
|
||||
|
@ -140,7 +139,7 @@ class SettingsPlayer : PreferenceFragmentCompat() {
|
|||
val currentQuality =
|
||||
settingsManager.getInt(
|
||||
getString(R.string.quality_pref_mobile_data_key),
|
||||
Qualities.values().last().value
|
||||
Qualities.entries.last().value
|
||||
)
|
||||
|
||||
activity?.showBottomDialog(
|
||||
|
|
|
@ -34,7 +34,7 @@ class SettingsProviders : PreferenceFragmentCompat() {
|
|||
|
||||
getPref(R.string.display_sub_key)?.setOnPreferenceClickListener {
|
||||
activity?.getApiDubstatusSettings()?.let { current ->
|
||||
val dublist = DubStatus.values()
|
||||
val dublist = DubStatus.entries
|
||||
val names = dublist.map { it.name }
|
||||
|
||||
val currentList = ArrayList<Int>()
|
||||
|
|
|
@ -128,7 +128,7 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
|||
}
|
||||
|
||||
binding.saveBtt.setOnClickListener {
|
||||
val date = SimpleDateFormat("yyyy_MM_dd_HH_mm").format(Date(currentTimeMillis()))
|
||||
val date = SimpleDateFormat("yyyy_MM_dd_HH_mm", Locale.getDefault()).format(Date(currentTimeMillis()))
|
||||
var fileStream: OutputStream? = null
|
||||
try {
|
||||
fileStream = VideoDownloadManager.setupStream(
|
||||
|
@ -169,10 +169,10 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
|||
prefValues.indexOf(currentInstaller),
|
||||
getString(R.string.apk_installer_settings),
|
||||
true,
|
||||
{}) {
|
||||
{}) { num ->
|
||||
try {
|
||||
settingsManager.edit()
|
||||
.putInt(getString(R.string.apk_installer_key), prefValues[it])
|
||||
.putInt(getString(R.string.apk_installer_key), prefValues[num])
|
||||
.apply()
|
||||
} catch (e: Exception) {
|
||||
logError(e)
|
||||
|
@ -209,9 +209,9 @@ class SettingsUpdates : PreferenceFragmentCompat() {
|
|||
prefValues.indexOf(current),
|
||||
getString(R.string.automatic_plugin_download_mode_title),
|
||||
true,
|
||||
{}) {
|
||||
{}) { num ->
|
||||
settingsManager.edit()
|
||||
.putInt(getString(R.string.auto_download_plugins_key), prefValues[it]).apply()
|
||||
.putInt(getString(R.string.auto_download_plugins_key), prefValues[num]).apply()
|
||||
(context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) }
|
||||
}
|
||||
return@setOnPreferenceClickListener true
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
package com.lagradost.cloudstream3.ui.settings.extensions
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.text.format.Formatter.formatShortFileSize
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.annotation.VisibleForTesting
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.isGone
|
||||
import androidx.core.view.isVisible
|
||||
|
@ -27,11 +29,10 @@ import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage
|
|||
import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.setImage
|
||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
import java.text.DecimalFormat
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.log10
|
||||
import kotlin.math.pow
|
||||
|
||||
|
||||
data class PluginViewData(
|
||||
|
@ -95,21 +96,13 @@ class PluginAdapter(
|
|||
}
|
||||
|
||||
companion object {
|
||||
private tailrec fun findClosestBase2(target: Int, current: Int = 16, max: Int = 512): Int {
|
||||
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
|
||||
tailrec fun findClosestBase2(target: Int, current: Int = 16, max: Int = 512): Int {
|
||||
if (current >= max) return max
|
||||
if (current >= target) return current
|
||||
return findClosestBase2(target, current * 2, max)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testFindClosestBase2() {
|
||||
Assert.assertEquals(16, findClosestBase2(0))
|
||||
Assert.assertEquals(256, findClosestBase2(170))
|
||||
Assert.assertEquals(256, findClosestBase2(256))
|
||||
Assert.assertEquals(512, findClosestBase2(257))
|
||||
Assert.assertEquals(512, findClosestBase2(700))
|
||||
}
|
||||
|
||||
private val iconSizeExact = 32.toPx
|
||||
private val iconSize by lazy {
|
||||
findClosestBase2(iconSizeExact, 16, 512)
|
||||
|
@ -122,10 +115,7 @@ class PluginAdapter(
|
|||
val base = value / 3
|
||||
return if (value >= 3 && base < suffix.size) {
|
||||
DecimalFormat("#0.00").format(
|
||||
numValue / Math.pow(
|
||||
10.0,
|
||||
(base * 3).toDouble()
|
||||
)
|
||||
numValue / 10.0.pow((base * 3).toDouble())
|
||||
) + suffix[base]
|
||||
} else {
|
||||
DecimalFormat().format(numValue)
|
||||
|
@ -136,6 +126,7 @@ class PluginAdapter(
|
|||
inner class PluginViewHolder(val binding: RepositoryItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
|
||||
@SuppressLint("SetTextI18n")
|
||||
fun bind(
|
||||
data: PluginViewData,
|
||||
) {
|
||||
|
|
|
@ -190,7 +190,7 @@ class PluginsFragment : Fragment() {
|
|||
bindChips(
|
||||
binding?.tvtypesChipsScroll?.tvtypesChips,
|
||||
emptyList(),
|
||||
TvType.values().toList(),
|
||||
TvType.entries.toList(),
|
||||
callback = { list ->
|
||||
pluginViewModel.tvTypes.clear()
|
||||
pluginViewModel.tvTypes.addAll(list.map { it.name })
|
||||
|
|
|
@ -10,7 +10,6 @@ import androidx.lifecycle.ViewModel
|
|||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider
|
||||
import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
|
|
|
@ -10,7 +10,6 @@ import androidx.core.util.forEach
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.TvType
|
||||
import com.lagradost.cloudstream3.databinding.FragmentSetupMediaBinding
|
||||
|
|
|
@ -15,8 +15,11 @@ import android.widget.Toast
|
|||
import androidx.fragment.app.Fragment
|
||||
import androidx.media3.common.text.Cue
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.google.android.gms.cast.TextTrackStyle
|
||||
import com.google.android.gms.cast.TextTrackStyle.*
|
||||
import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_DEPRESSED
|
||||
import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_DROP_SHADOW
|
||||
import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_NONE
|
||||
import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_OUTLINE
|
||||
import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_RAISED
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent
|
||||
|
@ -42,7 +45,7 @@ data class SaveChromeCaptionStyle(
|
|||
@JsonProperty("fontGenericFamily") var fontGenericFamily: Int? = null,
|
||||
@JsonProperty("backgroundColor") var backgroundColor: Int = 0x00FFFFFF, // transparent
|
||||
@JsonProperty("edgeColor") var edgeColor: Int = Color.BLACK, // BLACK
|
||||
@JsonProperty("edgeType") var edgeType: Int = TextTrackStyle.EDGE_TYPE_OUTLINE,
|
||||
@JsonProperty("edgeType") var edgeType: Int = EDGE_TYPE_OUTLINE,
|
||||
@JsonProperty("foregroundColor") var foregroundColor: Int = Color.WHITE,
|
||||
@JsonProperty("fontScale") var fontScale: Float = 1.05f,
|
||||
@JsonProperty("windowColor") var windowColor: Int = Color.TRANSPARENT,
|
||||
|
@ -99,7 +102,7 @@ class ChromecastSubtitlesFragment : Fragment() {
|
|||
}
|
||||
|
||||
private fun onColorSelected(stuff: Pair<Int, Int>) {
|
||||
context?.setColor(stuff.first, stuff.second)
|
||||
setColor(stuff.first, stuff.second)
|
||||
if (hide)
|
||||
activity?.hideSystemUI()
|
||||
}
|
||||
|
@ -122,7 +125,7 @@ class ChromecastSubtitlesFragment : Fragment() {
|
|||
return if (color == Color.TRANSPARENT) Color.BLACK else color
|
||||
}
|
||||
|
||||
private fun Context.setColor(id: Int, color: Int?) {
|
||||
private fun setColor(id: Int, color: Int?) {
|
||||
val realColor = color ?: getDefColor(id)
|
||||
when (id) {
|
||||
0 -> state.foregroundColor = realColor
|
||||
|
@ -135,7 +138,7 @@ class ChromecastSubtitlesFragment : Fragment() {
|
|||
updateState()
|
||||
}
|
||||
|
||||
private fun Context.updateState() {
|
||||
private fun updateState() {
|
||||
//subtitle_text?.setStyle(fromSaveToStyle(state))
|
||||
}
|
||||
|
||||
|
@ -173,7 +176,7 @@ class ChromecastSubtitlesFragment : Fragment() {
|
|||
fixPaddingStatusbar(binding?.subsRoot)
|
||||
|
||||
state = getCurrentSavedStyle()
|
||||
context?.updateState()
|
||||
updateState()
|
||||
|
||||
val isTvSettings = isLayout(TV or EMULATOR)
|
||||
|
||||
|
@ -195,7 +198,7 @@ class ChromecastSubtitlesFragment : Fragment() {
|
|||
}
|
||||
|
||||
this.setOnLongClickListener {
|
||||
it.context.setColor(id, null)
|
||||
setColor(id, null)
|
||||
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
|
@ -247,13 +250,13 @@ class ChromecastSubtitlesFragment : Fragment() {
|
|||
dismissCallback
|
||||
) { index ->
|
||||
state.edgeType = edgeTypes.map { it.first }[index]
|
||||
textView.context.updateState()
|
||||
updateState()
|
||||
}
|
||||
}
|
||||
|
||||
binding?.subsEdgeType?.setOnLongClickListener {
|
||||
state.edgeType = defaultState.edgeType
|
||||
it.context.updateState()
|
||||
updateState()
|
||||
showToast(R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
|
@ -323,12 +326,12 @@ class ChromecastSubtitlesFragment : Fragment() {
|
|||
dismissCallback
|
||||
) { index ->
|
||||
state.fontFamily = fontTypes.map { it.first }[index]
|
||||
textView.context.updateState()
|
||||
updateState()
|
||||
}
|
||||
}
|
||||
binding?.subsFont?.setOnLongClickListener { textView ->
|
||||
binding?.subsFont?.setOnLongClickListener { _ ->
|
||||
state.fontFamily = defaultState.fontFamily
|
||||
textView.context.updateState()
|
||||
updateState()
|
||||
showToast(activity, R.string.subs_default_reset_toast, Toast.LENGTH_SHORT)
|
||||
return@setOnLongClickListener true
|
||||
}
|
||||
|
|
|
@ -14,11 +14,13 @@ import android.view.ViewGroup
|
|||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.FontRes
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import androidx.media3.common.text.Cue
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.CaptionStyleCompat
|
||||
import com.jaredrummler.android.colorpicker.ColorPickerDialog
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
|
@ -28,7 +30,6 @@ import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent
|
|||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.databinding.SubtitleSettingsBinding
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.TV
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
import com.lagradost.cloudstream3.utils.DataStore.setKey
|
||||
|
@ -46,7 +47,7 @@ const val SUBTITLE_KEY = "subtitle_settings"
|
|||
const val SUBTITLE_AUTO_SELECT_KEY = "subs_auto_select"
|
||||
const val SUBTITLE_DOWNLOAD_KEY = "subs_auto_download"
|
||||
|
||||
data class SaveCaptionStyle(
|
||||
data class SaveCaptionStyle @OptIn(UnstableApi::class) constructor(
|
||||
@JsonProperty("foregroundColor") var foregroundColor: Int,
|
||||
@JsonProperty("backgroundColor") var backgroundColor: Int,
|
||||
@JsonProperty("windowColor") var windowColor: Int,
|
||||
|
@ -67,7 +68,7 @@ data class SaveCaptionStyle(
|
|||
|
||||
const val DEF_SUBS_ELEVATION = 20
|
||||
|
||||
@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
@OptIn(androidx.media3.common.util.UnstableApi::class)
|
||||
class SubtitlesFragment : Fragment() {
|
||||
companion object {
|
||||
val applyStyleEvent = Event<SaveCaptionStyle>()
|
||||
|
@ -167,7 +168,7 @@ class SubtitlesFragment : Fragment() {
|
|||
activity?.hideSystemUI()
|
||||
}
|
||||
|
||||
private fun onDialogDismissed(id: Int) {
|
||||
private fun onDialogDismissed(@Suppress("UNUSED_PARAMETER") id: Int) {
|
||||
if (hide)
|
||||
activity?.hideSystemUI()
|
||||
}
|
||||
|
|
|
@ -83,7 +83,7 @@ object EpisodeSkip {
|
|||
startMs = start,
|
||||
endMs = end
|
||||
)
|
||||
}?.let { list ->
|
||||
}.let { list ->
|
||||
out.addAll(list)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,7 +43,6 @@ import androidx.recyclerview.widget.RecyclerView
|
|||
import androidx.tvprovider.media.tv.*
|
||||
import androidx.tvprovider.media.tv.WatchNextProgram.fromCursor
|
||||
import androidx.viewpager2.widget.ViewPager2
|
||||
import com.fasterxml.jackson.module.kotlin.readValue
|
||||
import com.google.android.gms.cast.framework.CastContext
|
||||
import com.google.android.gms.cast.framework.CastState
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
|
@ -58,7 +57,7 @@ import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEv
|
|||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.appStringResumeWatching
|
||||
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_RESUME_WATCHING
|
||||
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
|
||||
import com.lagradost.cloudstream3.ui.WebviewFragment
|
||||
import com.lagradost.cloudstream3.ui.player.SubtitleData
|
||||
|
@ -161,7 +160,7 @@ object AppContextUtils {
|
|||
.setTitle(title)
|
||||
.setPosterArtUri(Uri.parse(card.posterUrl))
|
||||
.setIntentUri(Uri.parse(card.id?.let {
|
||||
"$appStringResumeWatching://$it"
|
||||
"$APP_STRING_RESUME_WATCHING://$it"
|
||||
} ?: card.url))
|
||||
.setInternalProviderId(card.url)
|
||||
.setLastEngagementTimeUtcMillis(
|
||||
|
|
|
@ -81,12 +81,12 @@ object BackupUtils {
|
|||
|
||||
// Kinda hack, but I couldn't think of a better way
|
||||
data class BackupVars(
|
||||
@JsonProperty("_Bool") val _Bool: Map<String, Boolean>?,
|
||||
@JsonProperty("_Int") val _Int: Map<String, Int>?,
|
||||
@JsonProperty("_String") val _String: Map<String, String>?,
|
||||
@JsonProperty("_Float") val _Float: Map<String, Float>?,
|
||||
@JsonProperty("_Long") val _Long: Map<String, Long>?,
|
||||
@JsonProperty("_StringSet") val _StringSet: Map<String, Set<String>?>?,
|
||||
@JsonProperty("_Bool") val bool: Map<String, Boolean>?,
|
||||
@JsonProperty("_Int") val int: Map<String, Int>?,
|
||||
@JsonProperty("_String") val string: Map<String, String>?,
|
||||
@JsonProperty("_Float") val float: Map<String, Float>?,
|
||||
@JsonProperty("_Long") val long: Map<String, Long>?,
|
||||
@JsonProperty("_StringSet") val stringSet: Map<String, Set<String>?>?,
|
||||
)
|
||||
|
||||
data class BackupFile(
|
||||
|
@ -134,21 +134,21 @@ object BackupUtils {
|
|||
) {
|
||||
if (context == null) return
|
||||
if (restoreSettings) {
|
||||
context.restoreMap(backupFile.settings._Bool, true)
|
||||
context.restoreMap(backupFile.settings._Int, true)
|
||||
context.restoreMap(backupFile.settings._String, true)
|
||||
context.restoreMap(backupFile.settings._Float, true)
|
||||
context.restoreMap(backupFile.settings._Long, true)
|
||||
context.restoreMap(backupFile.settings._StringSet, true)
|
||||
context.restoreMap(backupFile.settings.bool, true)
|
||||
context.restoreMap(backupFile.settings.int, true)
|
||||
context.restoreMap(backupFile.settings.string, true)
|
||||
context.restoreMap(backupFile.settings.float, true)
|
||||
context.restoreMap(backupFile.settings.long, true)
|
||||
context.restoreMap(backupFile.settings.stringSet, true)
|
||||
}
|
||||
|
||||
if (restoreDataStore) {
|
||||
context.restoreMap(backupFile.datastore._Bool)
|
||||
context.restoreMap(backupFile.datastore._Int)
|
||||
context.restoreMap(backupFile.datastore._String)
|
||||
context.restoreMap(backupFile.datastore._Float)
|
||||
context.restoreMap(backupFile.datastore._Long)
|
||||
context.restoreMap(backupFile.datastore._StringSet)
|
||||
context.restoreMap(backupFile.datastore.bool)
|
||||
context.restoreMap(backupFile.datastore.int)
|
||||
context.restoreMap(backupFile.datastore.string)
|
||||
context.restoreMap(backupFile.datastore.float)
|
||||
context.restoreMap(backupFile.datastore.long)
|
||||
context.restoreMap(backupFile.datastore.stringSet)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -56,16 +56,27 @@ data class Editor(
|
|||
) {
|
||||
/** Always remember to call apply after */
|
||||
fun<T> setKeyRaw(path: String, value: T) {
|
||||
when (value) {
|
||||
is Boolean -> editor.putBoolean(path, value)
|
||||
is Int -> editor.putInt(path, value)
|
||||
is String -> editor.putString(path, value)
|
||||
is Float -> editor.putFloat(path, value)
|
||||
is Long -> editor.putLong(path, value)
|
||||
(value as? Set<String> != null) -> editor.putStringSet(path, value as Set<String>)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
if (isStringSet(value)) {
|
||||
editor.putStringSet(path, value as Set<String>)
|
||||
} else {
|
||||
when (value) {
|
||||
is Boolean -> editor.putBoolean(path, value)
|
||||
is Int -> editor.putInt(path, value)
|
||||
is String -> editor.putString(path, value)
|
||||
is Float -> editor.putFloat(path, value)
|
||||
is Long -> editor.putLong(path, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun isStringSet(value: Any?) : Boolean {
|
||||
if (value is Set<*>) {
|
||||
return value.filterIsInstance<String>().size == value.size
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
fun apply() {
|
||||
editor.apply()
|
||||
System.gc()
|
||||
|
|
|
@ -7,7 +7,6 @@ import androidx.work.ForegroundInfo
|
|||
import androidx.work.WorkerParameters
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import com.lagradost.cloudstream3.utils.DataStore.getKey
|
||||
import com.lagradost.cloudstream3.utils.VideoDownloadManager.WORK_KEY_INFO
|
||||
|
|
|
@ -32,26 +32,26 @@ import java.io.InputStreamReader
|
|||
|
||||
class InAppUpdater {
|
||||
companion object {
|
||||
const val GITHUB_USER_NAME = "recloudstream"
|
||||
const val GITHUB_REPO = "cloudstream"
|
||||
private const val GITHUB_USER_NAME = "recloudstream"
|
||||
private const val GITHUB_REPO = "cloudstream"
|
||||
|
||||
const val LOG_TAG = "InAppUpdater"
|
||||
private const val LOG_TAG = "InAppUpdater"
|
||||
|
||||
// === IN APP UPDATER ===
|
||||
data class GithubAsset(
|
||||
@JsonProperty("name") val name: String,
|
||||
@JsonProperty("size") val size: Int, // Size bytes
|
||||
@JsonProperty("browser_download_url") val browser_download_url: String, // download link
|
||||
@JsonProperty("content_type") val content_type: String, // application/vnd.android.package-archive
|
||||
@JsonProperty("browser_download_url") val browserDownloadUrl: String, // download link
|
||||
@JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive
|
||||
)
|
||||
|
||||
data class GithubRelease(
|
||||
@JsonProperty("tag_name") val tag_name: String, // Version code
|
||||
@JsonProperty("tag_name") val tagName: String, // Version code
|
||||
@JsonProperty("body") val body: String, // Desc
|
||||
@JsonProperty("assets") val assets: List<GithubAsset>,
|
||||
@JsonProperty("target_commitish") val target_commitish: String, // branch
|
||||
@JsonProperty("target_commitish") val targetCommitish: String, // branch
|
||||
@JsonProperty("prerelease") val prerelease: Boolean,
|
||||
@JsonProperty("node_id") val node_id: String //Node Id
|
||||
@JsonProperty("node_id") val nodeId: String //Node Id
|
||||
)
|
||||
|
||||
data class GithubObject(
|
||||
|
@ -61,7 +61,7 @@ class InAppUpdater {
|
|||
)
|
||||
|
||||
data class GithubTag(
|
||||
@JsonProperty("object") val github_object: GithubObject,
|
||||
@JsonProperty("object") val githubObject: GithubObject,
|
||||
)
|
||||
|
||||
data class Update(
|
||||
|
@ -114,7 +114,7 @@ class InAppUpdater {
|
|||
response.filter { rel ->
|
||||
!rel.prerelease
|
||||
}.sortedWith(compareBy { release ->
|
||||
release.assets.firstOrNull { it.content_type == "application/vnd.android.package-archive" }?.name?.let { it1 ->
|
||||
release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 ->
|
||||
versionRegex.find(
|
||||
it1
|
||||
)?.groupValues?.let {
|
||||
|
@ -134,7 +134,7 @@ class InAppUpdater {
|
|||
foundAsset?.name?.let { assetName ->
|
||||
val foundVersion = versionRegex.find(assetName)
|
||||
val shouldUpdate =
|
||||
if (foundAsset.browser_download_url != "" && foundVersion != null) currentVersion?.versionName?.let { versionName ->
|
||||
if (foundAsset.browserDownloadUrl != "" && foundVersion != null) currentVersion?.versionName?.let { versionName ->
|
||||
versionRegexLocal.find(versionName)?.groupValues?.let {
|
||||
it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt()
|
||||
}
|
||||
|
@ -146,10 +146,10 @@ class InAppUpdater {
|
|||
return if (foundVersion != null) {
|
||||
Update(
|
||||
shouldUpdate,
|
||||
foundAsset.browser_download_url,
|
||||
foundAsset.browserDownloadUrl,
|
||||
foundVersion.groupValues[2],
|
||||
found.body,
|
||||
found.node_id
|
||||
found.nodeId
|
||||
)
|
||||
} else {
|
||||
Update(false, null, null, null, null)
|
||||
|
@ -168,33 +168,33 @@ class InAppUpdater {
|
|||
|
||||
val found =
|
||||
response.lastOrNull { rel ->
|
||||
rel.prerelease || rel.tag_name == "pre-release"
|
||||
rel.prerelease || rel.tagName == "pre-release"
|
||||
}
|
||||
val foundAsset = found?.assets?.filter { it ->
|
||||
it.content_type == "application/vnd.android.package-archive"
|
||||
it.contentType == "application/vnd.android.package-archive"
|
||||
}?.getOrNull(0)
|
||||
|
||||
val tagResponse =
|
||||
parseJson<GithubTag>(app.get(tagUrl, headers = headers).text)
|
||||
|
||||
Log.d(LOG_TAG, "Fetched GitHub tag: ${tagResponse.github_object.sha.take(7)}")
|
||||
Log.d(LOG_TAG, "Fetched GitHub tag: ${tagResponse.githubObject.sha.take(7)}")
|
||||
|
||||
val shouldUpdate =
|
||||
(getString(R.string.commit_hash)
|
||||
.trim { c -> c.isWhitespace() }
|
||||
.take(7)
|
||||
!=
|
||||
tagResponse.github_object.sha
|
||||
tagResponse.githubObject.sha
|
||||
.trim { c -> c.isWhitespace() }
|
||||
.take(7))
|
||||
|
||||
return if (foundAsset != null) {
|
||||
Update(
|
||||
shouldUpdate,
|
||||
foundAsset.browser_download_url,
|
||||
tagResponse.github_object.sha.take(10),
|
||||
foundAsset.browserDownloadUrl,
|
||||
tagResponse.githubObject.sha.take(10),
|
||||
found.body,
|
||||
found.node_id
|
||||
found.nodeId
|
||||
)
|
||||
} else {
|
||||
Update(false, null, null, null, null)
|
||||
|
|
|
@ -11,7 +11,6 @@ import android.os.Build
|
|||
import android.widget.Toast
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.logError
|
||||
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
import java.io.InputStream
|
||||
|
||||
|
@ -57,7 +56,7 @@ class ApkInstaller(private val service: PackageInstallerService) {
|
|||
PackageInstaller.STATUS_FAILURE
|
||||
)) {
|
||||
PackageInstaller.STATUS_PENDING_USER_ACTION -> {
|
||||
val userAction = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
||||
val userAction = intent.getSafeParcelableExtra<Intent>(Intent.EXTRA_INTENT)
|
||||
userAction?.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
context.startActivity(userAction)
|
||||
}
|
||||
|
@ -146,3 +145,5 @@ class ApkInstaller(private val service: PackageInstallerService) {
|
|||
}
|
||||
}
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
inline fun <reified T> Intent.getSafeParcelableExtra(key: String): T? = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) getParcelableExtra(key) else getParcelableExtra(key, T::class.java)
|
||||
|
|
|
@ -17,8 +17,8 @@ import com.lagradost.cloudstream3.R
|
|||
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
|
||||
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
|
||||
|
||||
const val packageName = BuildConfig.APPLICATION_ID
|
||||
const val TAG = "PowerManagerAPI"
|
||||
private const val PACKAGE_NAME = BuildConfig.APPLICATION_ID
|
||||
private const val TAG = "PowerManagerAPI"
|
||||
|
||||
object BatteryOptimizationChecker {
|
||||
|
||||
|
@ -72,7 +72,7 @@ object BatteryOptimizationChecker {
|
|||
val intent = Intent()
|
||||
try {
|
||||
intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
|
||||
.setData(Uri.fromParts("package", packageName, null))
|
||||
.setData(Uri.fromParts("package", PACKAGE_NAME, null))
|
||||
context.startActivity(intent, Bundle())
|
||||
} catch (t: Throwable) {
|
||||
Log.e(TAG, "Unable to invoke any intent", t)
|
||||
|
|
|
@ -73,8 +73,8 @@ object SyncUtil {
|
|||
val response = app.get(url, cacheTime = 1, cacheUnit = TimeUnit.DAYS).text
|
||||
val mapped = parseJson<MalSyncPage?>(response)
|
||||
|
||||
val overrideMal = mapped?.malId ?: mapped?.Mal?.id ?: mapped?.Anilist?.malId
|
||||
val overrideAnilist = mapped?.aniId ?: mapped?.Anilist?.id
|
||||
val overrideMal = mapped?.malId ?: mapped?.mal?.id ?: mapped?.anilist?.malId
|
||||
val overrideAnilist = mapped?.aniId ?: mapped?.anilist?.id
|
||||
|
||||
if (overrideMal != null) {
|
||||
return overrideMal.toString() to overrideAnilist?.toString()
|
||||
|
@ -135,8 +135,8 @@ object SyncUtil {
|
|||
@JsonProperty("createdAt") val createdAt: String?,
|
||||
@JsonProperty("updatedAt") val updatedAt: String?,
|
||||
@JsonProperty("deletedAt") val deletedAt: String?,
|
||||
@JsonProperty("Mal") val Mal: Mal?,
|
||||
@JsonProperty("Anilist") val Anilist: Anilist?,
|
||||
@JsonProperty("Mal") val mal: Mal?,
|
||||
@JsonProperty("Anilist") val anilist: Anilist?,
|
||||
@JsonProperty("malUrl") val malUrl: String?
|
||||
)
|
||||
|
||||
|
|
|
@ -553,7 +553,7 @@ object UIHelper {
|
|||
return result
|
||||
}
|
||||
|
||||
fun Context?.IsBottomLayout(): Boolean {
|
||||
fun Context?.isBottomLayout(): Boolean {
|
||||
if (this == null) return true
|
||||
val settingsManager = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
return settingsManager.getBoolean(getString(R.string.bottom_title_key), true)
|
||||
|
|
|
@ -293,6 +293,7 @@ object VideoDownloadManager {
|
|||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_IMMUTABLE)
|
||||
} else {
|
||||
//fixme Specify a better flag
|
||||
PendingIntent.getActivity(context, 0, intent, 0)
|
||||
}
|
||||
builder.setContentIntent(pendingIntent)
|
||||
|
@ -475,10 +476,10 @@ object VideoDownloadManager {
|
|||
}
|
||||
}
|
||||
|
||||
private const val reservedChars = "|\\?*<\":>+[]/\'"
|
||||
private const val RESERVED_CHARS = "|\\?*<\":>+[]/\'"
|
||||
fun sanitizeFilename(name: String, removeSpaces: Boolean = false): String {
|
||||
var tempName = name
|
||||
for (c in reservedChars) {
|
||||
for (c in RESERVED_CHARS) {
|
||||
tempName = tempName.replace(c, ' ')
|
||||
}
|
||||
if (removeSpaces) tempName = tempName.replace(" ", "")
|
||||
|
@ -1699,7 +1700,7 @@ object VideoDownloadManager {
|
|||
}
|
||||
*/
|
||||
fun getDownloadFileInfoAndUpdateSettings(context: Context, id: Int): DownloadedFileInfoResult? =
|
||||
getDownloadFileInfo(context, id, removeKeys = true)
|
||||
getDownloadFileInfo(context, id)
|
||||
|
||||
private fun DownloadedFileInfo.toFile(context: Context): SafeFile? {
|
||||
return basePathToFile(context, this.basePath)?.gotoDirectory(relativePath)
|
||||
|
@ -1709,7 +1710,6 @@ object VideoDownloadManager {
|
|||
private fun getDownloadFileInfo(
|
||||
context: Context,
|
||||
id: Int,
|
||||
removeKeys: Boolean = false
|
||||
): DownloadedFileInfoResult? {
|
||||
try {
|
||||
val info =
|
||||
|
|
|
@ -19,7 +19,7 @@ class FlowLayout : ViewGroup {
|
|||
@SuppressLint("CustomViewStyleable")
|
||||
internal constructor(c: Context, attrs: AttributeSet?) : super(c, attrs) {
|
||||
val t = c.obtainStyledAttributes(attrs, R.styleable.FlowLayout_Layout)
|
||||
itemSpacing = t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_itemSpacing, 0);
|
||||
itemSpacing = t.getDimensionPixelSize(R.styleable.FlowLayout_Layout_itemSpacing, 0)
|
||||
t.recycle()
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
package com.lagradost.cloudstream3
|
||||
|
||||
import com.lagradost.cloudstream3.ui.settings.extensions.PluginAdapter.Companion.findClosestBase2
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class PluginAdapterTest {
|
||||
@Test
|
||||
fun testFindClosestBase2() {
|
||||
Assert.assertEquals(16, findClosestBase2(0))
|
||||
Assert.assertEquals(256, findClosestBase2(170))
|
||||
Assert.assertEquals(256, findClosestBase2(256))
|
||||
Assert.assertEquals(512, findClosestBase2(257))
|
||||
Assert.assertEquals(512, findClosestBase2(700))
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue