From 1d5561068574b5c783e052751ee16fcc8ca11c93 Mon Sep 17 00:00:00 2001 From: Cloudburst <18114966+C10udburst@users.noreply.github.com> Date: Sun, 8 Sep 2024 22:51:19 +0200 Subject: [PATCH 001/962] initial start on the VideoClickAction api --- app/src/main/AndroidManifest.xml | 9 +--- .../lagradost/cloudstream3/MainActivity.kt | 2 + .../cloudstream3/actions/OpenInAppAction.kt | 48 +++++++++++++++++++ .../cloudstream3/actions/VideoClickAction.kt | 44 +++++++++++++++++ .../lagradost/cloudstream3/plugins/Plugin.kt | 14 ++++++ .../cloudstream3/plugins/PluginManager.kt | 7 +++ .../cloudstream3/ui/result/EpisodeAdapter.kt | 5 ++ .../ui/result/ResultViewModel2.kt | 37 ++++++++++++++ 8 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1aeef5550..a04504acd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,7 +17,7 @@ - + @@ -30,13 +30,6 @@ android:name="android.software.leanback" android:required="false" /> - - - - - - - ? = null + //TODO REFACTOR AF open class ResultResume( val packageString: String, diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt new file mode 100644 index 000000000..4673af3f3 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt @@ -0,0 +1,48 @@ +package com.lagradost.cloudstream3.actions + +import android.app.Activity +import android.content.ComponentName +import android.content.Intent +import com.lagradost.cloudstream3.MainActivity.Companion.activityResultLauncher +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.UiText +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.AppContextUtils.isAppInstalled + +abstract class OpenInAppAction( + open val appName: UiText, + open val packageName: String, + private val intentClass: String?, + private val action: String = Intent.ACTION_VIEW +): VideoClickAction() { + override val name: UiText + get() = txt(R.string.episode_action_play_in_format, appName) + + override fun shouldShow(activity: Activity?, video: ResultEpisode) = activity?.isAppInstalled(packageName) == true + + override fun runAction( + activity: Activity?, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + if (activity == null) return + val intent = Intent(action) + intent.setPackage(packageName) + if (intentClass != null) { + intent.component = ComponentName(packageName, intentClass) + } + putExtra(activity, intent, video, result, index) + + // TODO: understand the spaghetti that is ResultResume + activityResultLauncher?.launch(intent) + } + + /** + * Before intent is sent, this function is called to put extra data into the intent. + * @see VideoClickAction.runAction + * */ + abstract fun putExtra(activity: Activity, intent: Intent, video: ResultEpisode, result: LinkLoadingResult, index: Int?) +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt new file mode 100644 index 000000000..ab188a702 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -0,0 +1,44 @@ +package com.lagradost.cloudstream3.actions + +import android.app.Activity +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.UiText +import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf +import com.lagradost.cloudstream3.utils.ExtractorLinkType + +object VideoClickActionHolder { + val allVideoClickActions = threadSafeListOf() + + private const val ACTION_ID_OFFSET = 1000 + + fun makeOptionMap(activity: Activity?, video: ResultEpisode) = allVideoClickActions + .filter { it.shouldShow(activity, video) } + .mapIndexed { index, it -> it.name to index + ACTION_ID_OFFSET } + + fun getActionById(id: Int): VideoClickAction? = allVideoClickActions.getOrNull(id - ACTION_ID_OFFSET) +} + +abstract class VideoClickAction { + abstract val name: UiText + + /** if true, the app will show dialog to select source - result.links[index] */ + val oneSource : Boolean = false + + /** Which type of sources this action can handle. */ + val sourceTypes: Set = ExtractorLinkType.entries.toSet() + + /** Determines which plugin a given provider is from. This is the full path to the plugin. */ + var sourcePlugin: String? = null + + abstract fun shouldShow(activity: Activity?, video: ResultEpisode): Boolean + + /** + * This function is called when the action is clicked. + * @param activity The current activity + * @param video The episode/movie that was clicked + * @param result The result of the link loading, contains video & subtitle links + * @param index if oneSource is true, this is the index of the selected source + */ + abstract fun runAction(activity: Activity?, video: ResultEpisode, result: LinkLoadingResult, index: Int?) +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt index fc8365876..e35ae24b9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt @@ -9,6 +9,8 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.extractorApis import android.util.Log import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.actions.VideoClickActionHolder const val PLUGIN_TAG = "PluginInstance" @@ -52,6 +54,18 @@ abstract class Plugin { extractorApis.add(element) } + /** + * Used to register VideoClickAction instances + * @param element VideoClickAction you want to register + */ + fun registerVideoClickAction(element: VideoClickAction) { + Log.i(PLUGIN_TAG, "Adding ${element.name} VideoClickAction") + element.sourcePlugin = this.filename + synchronized(VideoClickActionHolder.allVideoClickActions) { + VideoClickActionHolder.allVideoClickActions.add(element) + } + } + class Manifest { @JsonProperty("name") var name: String? = null diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index c7f416883..8535592d4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -24,6 +24,8 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.mvvm.debugPrint import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall @@ -583,8 +585,13 @@ object PluginManager { synchronized(APIHolder.allProviders) { APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.filename } } + extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.filename } + synchronized(VideoClickActionHolder.allVideoClickActions) { + VideoClickActionHolder.allVideoClickActions.removeIf { action: VideoClickAction -> action.sourcePlugin == plugin.filename } + } + classLoaders.values.removeIf { v -> v == plugin } plugins.remove(absolutePath) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 4cd9cc9ea..a19adc4e8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -30,6 +30,11 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +/** + * Ids >= 1000 are reserved for VideoClickActions + * @see VideoClickActionHolder + */ + const val ACTION_PLAY_EPISODE_IN_PLAYER = 1 const val ACTION_PLAY_EPISODE_IN_VLC_PLAYER = 2 const val ACTION_PLAY_EPISODE_IN_BROWSER = 3 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index a29941d11..99fc65f81 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -41,6 +41,7 @@ import com.lagradost.cloudstream3.MainActivity.Companion.VLC_COMPONENT import com.lagradost.cloudstream3.MainActivity.Companion.VLC_PACKAGE import com.lagradost.cloudstream3.MainActivity.Companion.WEB_VIDEO import com.lagradost.cloudstream3.MainActivity.Companion.WEB_VIDEO_CAST_PACKAGE +import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.metaproviders.SyncRedirector import com.lagradost.cloudstream3.mvvm.* import com.lagradost.cloudstream3.syncproviders.AccountManager @@ -1576,6 +1577,9 @@ class ResultViewModel2 : ViewModel() { when (click.action) { ACTION_SHOW_OPTIONS -> { val options = mutableListOf>() + + VideoClickActionHolder.makeOptionMap(activity, click.data) + if (activity?.isConnectedToChromecast() == true) { options.addAll( listOf( @@ -1615,6 +1619,10 @@ class ResultViewModel2 : ViewModel() { ) ) + options.addAll( + VideoClickActionHolder.makeOptionMap(activity, click.data) + ) + // Do not add mark as watched on movies if (!listOf(TvType.Movie, TvType.AnimeMovie).contains(click.data.tvType)) { val isWatched = @@ -1933,6 +1941,35 @@ class ResultViewModel2 : ViewModel() { // Kinda dirty to reload all episodes :( reloadEpisodes() } + + else -> { + val action = VideoClickActionHolder.getActionById(click.action) ?: return + + // TODO: use action.sourceTypes + if (action.oneSource) { + loadLinks(click.data, isVisible = true, LoadType.ExternalApp) { links -> + action.runAction( + activity, + click.data, + links, + null + ) + } + } else { + acquireSingleLink( + click.data, + LoadType.ExternalApp, + action.name + ) { (result, index) -> + action.runAction( + activity, + click.data, + result, + index + ) + } + } + } } } From 9505ca259255c545471278a071339d8dabf47041 Mon Sep 17 00:00:00 2001 From: Cloudburst <18114966+C10udburst@users.noreply.github.com> Date: Mon, 9 Sep 2024 17:21:45 +0200 Subject: [PATCH 002/962] move all app actions to new api, handle some todos --- .../lagradost/cloudstream3/CommonActivity.kt | 28 +- .../lagradost/cloudstream3/MainActivity.kt | 105 +----- .../cloudstream3/actions/OpenInAppAction.kt | 93 ++++- .../cloudstream3/actions/VideoClickAction.kt | 32 +- .../cloudstream3/actions/temp/MpvKtPackage.kt | 67 ++++ .../cloudstream3/actions/temp/MpvPackage.kt | 61 ++++ .../actions/temp/PlayInBrowserAction.kt | 41 +++ .../cloudstream3/actions/temp/TestAction.kt | 47 +++ .../cloudstream3/actions/temp/VlcPackage.kt | 55 +++ .../actions/temp/WebVideoCastPackage.kt | 61 ++++ .../cloudstream3/ui/ControllerActivity.kt | 16 +- .../ui/player/DownloadFileGenerator.kt | 6 +- .../ui/player/ExtractorLinkGenerator.kt | 9 +- .../cloudstream3/ui/player/IGenerator.kt | 65 ++-- .../cloudstream3/ui/player/LinkGenerator.kt | 6 +- .../ui/player/PlayerGeneratorViewModel.kt | 7 +- .../ui/player/RepoLinkGenerator.kt | 10 +- .../ui/result/ResultViewModel2.kt | 345 ++---------------- 18 files changed, 552 insertions(+), 502 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/TestAction.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index ee3a5d122..50e6d8c98 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -30,15 +30,14 @@ import com.google.android.material.chip.ChipGroup import com.google.android.material.navigationrail.NavigationRailView import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.MainActivity.Companion.resumeApps +import com.lagradost.cloudstream3.actions.OpenInAppAction +import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.databinding.ToastBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.player.PlayerEventType -import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.ui.settings.Globals.updateTv import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl -import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission @@ -218,20 +217,15 @@ object CommonActivity { componentActivity.updateTv() NewPipe.init(DownloaderTestImpl.getInstance()) - for (resumeApp in resumeApps) { - resumeApp.launcher = - componentActivity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - val resultCode = result.resultCode - val data = result.data - if (resultCode == AppCompatActivity.RESULT_OK && data != null && resumeApp.position != null && resumeApp.duration != null) { - val pos = resumeApp.getPosition(data) - val dur = resumeApp.getDuration(data) - if (dur > 0L && pos > 0L) - DataStoreHelper.setViewPos(getKey(resumeApp.lastId), pos, dur) - removeKey(resumeApp.lastId) - ResultFragment.updateUI() - } - } + MainActivity.activityResultLauncher = componentActivity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == AppCompatActivity.RESULT_OK) { + val actionUid = getKey("last_click_action") ?: return@registerForActivityResult + Log.d(TAG, "Loading action $actionUid result handler") + val action = VideoClickActionHolder.getByUniqueId(actionUid) as? OpenInAppAction ?: return@registerForActivityResult + action.onResult(act, result.data) + removeKey("last_click_action") + removeKey("last_opened_id") + } } // Ask for notification permissions on Android 13 diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index d99283593..0625f572e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -198,110 +198,7 @@ import kotlin.system.exitProcess class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCallback { companion object { - const val VLC_PACKAGE = "org.videolan.vlc" - const val MPV_PACKAGE = "is.xyz.mpv" - const val MPV_YTDL_PACKAGE = "is.xyz.mpv.ytdl" - const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo" - - val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity") - val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity") - val MPV_YTDL_COMPONENT = ComponentName(MPV_YTDL_PACKAGE, "$MPV_PACKAGE.MPVActivity") - - val activityResultLauncher: ActivityResultLauncher? = null - - //TODO REFACTOR AF - open class ResultResume( - val packageString: String, - val action: String = Intent.ACTION_VIEW, - val position: String? = null, - val duration: String? = null, - var launcher: ActivityResultLauncher? = null, - ) { - val defaultTime = -1L - - val lastId get() = "${packageString}_last_open_id" - suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) { - val intent = Intent(action) - - if (id != null) - setKey(lastId, id) - else - removeKey(lastId) - - intent.setPackage(packageString) - callback.invoke(intent) - launcher?.launch(intent) - } - - open fun getPosition(intent: Intent?): Long { - return defaultTime - } - - open fun getDuration(intent: Intent?): Long { - return defaultTime - } - } - - val VLC = object : ResultResume( - VLC_PACKAGE, - // Android 13 intent restrictions fucks up specifically launching the VLC player - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - "org.videolan.vlc.player.result" - } else { - Intent.ACTION_VIEW - }, - "extra_position", - "extra_duration", - ) { - override fun getPosition(intent: Intent?): Long { - return intent?.getLongExtra(this.position, defaultTime) ?: defaultTime - } - - override fun getDuration(intent: Intent?): Long { - return intent?.getLongExtra(this.duration, defaultTime) ?: defaultTime - } - } - - val MPV = object : ResultResume( - MPV_PACKAGE, - //"is.xyz.mpv.MPVActivity.result", // resume not working :pensive: - position = "position", - duration = "duration", - ) { - override fun getPosition(intent: Intent?): Long { - return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() - ?: defaultTime - } - - override fun getDuration(intent: Intent?): Long { - return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() - ?: defaultTime - } - } - - val MPV_YTDL = object : ResultResume( - MPV_YTDL_PACKAGE, - //"is.xyz.mpv.ytdl/is.xyz.mpv.MPVActivity.result", // resume not working :pensive: - position = "position", - duration = "duration", - ) { - override fun getPosition(intent: Intent?): Long { - return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() - ?: defaultTime - } - - override fun getDuration(intent: Intent?): Long { - return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() - ?: defaultTime - } - } - - val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE) - - val resumeApps = arrayOf( - VLC, MPV, MPV_YTDL, WEB_VIDEO - ) - + var activityResultLauncher: ActivityResultLauncher? = null const val TAG = "MAINACT" const val ANIMATED_OUTLINE: Boolean = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt index 4673af3f3..534102fea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt @@ -1,20 +1,86 @@ package com.lagradost.cloudstream3.actions import android.app.Activity +import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Intent +import android.widget.Toast +import androidx.core.content.FileProvider +import androidx.core.net.toUri +import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity.Companion.activityResultLauncher import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.AppContextUtils.isAppInstalled +import com.lagradost.cloudstream3.utils.Coroutines.main +import com.lagradost.cloudstream3.utils.DataStoreHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.io.File + +fun updateDurationAndPosition(position: Long, duration: Long) { + if (position <= 0 || duration <= 0) return + DataStoreHelper.setViewPos(getKey("last_opened_id"), position, duration) + ResultFragment.updateUI() +} + +/** + * Util method that may be helpful for creating intents for apps that support m3u8 files. + * All sources are written to a temporary m3u8 file, which is then sent to the app. + */ +fun makeTempM3U8Intent(activity: Activity, + intent: Intent, + result: LinkLoadingResult) { + intent.apply { + addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } + + val outputDir = activity.cacheDir + + if (result.links.size == 1) { + intent.setDataAndType(result.links.first().url.toUri(), "video/*") + } else { + val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir) + + var text = "#EXTM3U" + + //With subtitles it doesn't work for no reason :( + /*for (sub in result.subs) { + val normalizedName = sub.name.replace("[^a-zA-Z0-9 ]".toRegex(), "") + val language = fromLanguageToTwoLetters(sub.name, true) ?: "und" + text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${normalizedName}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${language}\",URI=\"${sub.url}\"" + }*/ + + for (link in result.links) { + text += "\n#EXTINF:, ${link.name}\n${link.url}" + } + outputFile.writeText(text) + + intent.setDataAndType( + FileProvider.getUriForFile( + activity, + activity.applicationContext.packageName + ".provider", + outputFile + ), "video/*" + ) + } +} abstract class OpenInAppAction( open val appName: UiText, open val packageName: String, - private val intentClass: String?, + private val intentClass: String? = null, private val action: String = Intent.ACTION_VIEW ): VideoClickAction() { override val name: UiText @@ -35,9 +101,21 @@ abstract class OpenInAppAction( intent.component = ComponentName(packageName, intentClass) } putExtra(activity, intent, video, result, index) - - // TODO: understand the spaghetti that is ResultResume - activityResultLauncher?.launch(intent) + setKey("last_opened_id", video.id) + try { + CoroutineScope(Dispatchers.IO).launch { + activityResultLauncher?.launch(intent) + } + } catch (t: Throwable) { + logError(t) + main { + if (t is ActivityNotFoundException) { + showToast(txt(R.string.app_not_found_error), Toast.LENGTH_LONG) + } else { + showToast(t.toString(), Toast.LENGTH_LONG) + } + } + } } /** @@ -45,4 +123,11 @@ abstract class OpenInAppAction( * @see VideoClickAction.runAction * */ abstract fun putExtra(activity: Activity, intent: Intent, video: ResultEpisode, result: LinkLoadingResult, index: Int?) + + /** + * This function is called when the app is opened again after the intent was sent. + * You can use it to for example read duration and position. + * @see updateDurationAndPosition + */ + abstract fun onResult(activity: Activity, intent: Intent?) } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt index ab188a702..75d3747ae 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -1,6 +1,14 @@ package com.lagradost.cloudstream3.actions import android.app.Activity +import com.lagradost.cloudstream3.actions.temp.MpvKtPackage +import com.lagradost.cloudstream3.actions.temp.MpvKtPreviewPackage +import com.lagradost.cloudstream3.actions.temp.MpvPackage +import com.lagradost.cloudstream3.actions.temp.MpvYTDLPackage +import com.lagradost.cloudstream3.actions.temp.PlayInBrowserAction +import com.lagradost.cloudstream3.actions.temp.TestAction +import com.lagradost.cloudstream3.actions.temp.VlcPackage +import com.lagradost.cloudstream3.actions.temp.WebVideoCastPackage import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.UiText @@ -8,29 +16,43 @@ import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.ExtractorLinkType object VideoClickActionHolder { - val allVideoClickActions = threadSafeListOf() + val allVideoClickActions = threadSafeListOf( + PlayInBrowserAction(), VlcPackage(), TestAction(), + MpvPackage(), MpvYTDLPackage(), + WebVideoCastPackage(), MpvKtPackage(), MpvKtPreviewPackage() + ) private const val ACTION_ID_OFFSET = 1000 fun makeOptionMap(activity: Activity?, video: ResultEpisode) = allVideoClickActions - .filter { it.shouldShow(activity, video) } - .mapIndexed { index, it -> it.name to index + ACTION_ID_OFFSET } + // We need to have index before filtering + .mapIndexed { index, it -> it to index + ACTION_ID_OFFSET } + .filter { it.first.shouldShow(activity, video) } + .map { it.first.name to it.second } + fun getActionById(id: Int): VideoClickAction? = allVideoClickActions.getOrNull(id - ACTION_ID_OFFSET) + + fun getByUniqueId(uniqueId: String): VideoClickAction? = allVideoClickActions.firstOrNull { it.uniqueId() == uniqueId } } abstract class VideoClickAction { abstract val name: UiText /** if true, the app will show dialog to select source - result.links[index] */ - val oneSource : Boolean = false + open val oneSource : Boolean = false + + /** if true, this action could be selected as default one press action in settings */ + open val canBeDefault: Boolean = false /** Which type of sources this action can handle. */ - val sourceTypes: Set = ExtractorLinkType.entries.toSet() + open val sourceTypes: Set = ExtractorLinkType.entries.toSet() /** Determines which plugin a given provider is from. This is the full path to the plugin. */ var sourcePlugin: String? = null + fun uniqueId() = "$sourcePlugin:${this::class.simpleName}" + abstract fun shouldShow(activity: Activity?, video: ResultEpisode): Boolean /** diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt new file mode 100644 index 000000000..57f564d24 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt @@ -0,0 +1,67 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import androidx.core.net.toUri +import com.lagradost.cloudstream3.actions.OpenInAppAction +import com.lagradost.cloudstream3.actions.updateDurationAndPosition +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.ExtractorLinkType + +class MpvKtPreviewPackage: MpvKtPackage( + appName = "mpvKt Preview", + packageName = "live.mehiz.mpvkt.preview", +) + +open class MpvKtPackage( + appName: String = "mpvKt", + packageName: String = "live.mehiz.mpvkt", +): OpenInAppAction( + appName = txt(appName), + packageName = packageName, + intentClass = "live.mehiz.mpvkt.ui.player.PlayerActivity" +) { + override val oneSource = true + + override val sourceTypes = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + + override fun putExtra( + activity: Activity, + intent: Intent, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + val link = result.links[index ?: 0] + + intent.apply { + putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) + putExtra("subs.name", result.subs.map { it.name }.toTypedArray()) + putExtra("subs.filename", result.subs.map { it.name }.toTypedArray()) + setDataAndType(Uri.parse(link.url), "video/*") + // m3u8 plays, but changing sources feature is not available + // makeTempM3U8Intent(activity, this, result) + putExtra("secure_uri", true) + putExtra("return_result", true) + //putExtra("headers", link.headers.flatMap { listOf(it.key, it.value) }.toTypedArray()) + val position = getViewPos(video.id)?.position + if (position != null) + putExtra("position", position.toInt()) + } + } + + override fun onResult(activity: Activity, intent: Intent?) { + val position = intent?.getIntExtra("position", -1)?.toLong() ?: -1 + val duration = intent?.getIntExtra("duration", -1)?.toLong() ?: -1 + updateDurationAndPosition(position, duration) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt new file mode 100644 index 000000000..7ff05a3f1 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -0,0 +1,61 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import android.content.Intent +import androidx.core.net.toUri +import com.lagradost.api.Log +import com.lagradost.cloudstream3.actions.OpenInAppAction +import com.lagradost.cloudstream3.actions.makeTempM3U8Intent +import com.lagradost.cloudstream3.actions.updateDurationAndPosition +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.ExtractorLinkType + +class MpvYTDLPackage : MpvPackage("MPV YTDL", "is.xyz.mpv.ytdl") { + override val sourceTypes = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) +} + +open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv"): OpenInAppAction( + txt(appName), + packageName, + "is.xyz.mpv.MPVActivity" +) { + override val sourceTypes = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + + override fun putExtra( + activity: Activity, + intent: Intent, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + intent.apply { + putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) + putExtra("subs.name", result.subs.map { it.name }.toTypedArray()) + putExtra("subs.filename", result.subs.map { it.name }.toTypedArray()) + makeTempM3U8Intent(activity, this, result) + putExtra("secure_uri", true) + putExtra("return_result", true) + val position = getViewPos(video.id)?.position + if (position != null) + putExtra("position", position.toInt()) + } + } + + override fun onResult(activity: Activity, intent: Intent?) { + val position = intent?.getIntExtra("position", -1) ?: -1 + val duration = intent?.getIntExtra("duration", -1) ?: -1 + Log.d("MPV", "Position: $position, Duration: $duration") + updateDurationAndPosition(position.toLong(), duration.toLong()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt new file mode 100644 index 000000000..6d6f78f51 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt @@ -0,0 +1,41 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.ExtractorLinkType + +class PlayInBrowserAction: VideoClickAction() { + override val name = txt("Play in browser") + + override val oneSource = true + + override val sourceTypes: Set = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + + override fun shouldShow(activity: Activity?, video: ResultEpisode) = true + + override fun runAction( + activity: Activity?, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + if (index == null) return + try { + val i = Intent(Intent.ACTION_VIEW) + i.data = Uri.parse(result.links[index].url) + activity?.startActivity(i) + } catch (e: Exception) { + logError(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/TestAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/TestAction.kt new file mode 100644 index 000000000..3c7464a05 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/TestAction.kt @@ -0,0 +1,47 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.AppUtils.toJson + +class TestAction: VideoClickAction() { + override val name = txt("Test action") + + override fun shouldShow(activity: Activity?, video: ResultEpisode): Boolean { + return true + } + + override fun runAction( + activity: Activity?, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + // Show dialog box + val text = """ +Result links: +${result.links.joinToString("\n") { if (it.url.length > 50) it.url.take(25) + "..." + it.url.takeLast(25) else it.url }} +Result subs: +${result.subs.joinToString("\n") { if (it.url.length > 50) it.url.take(25) + "..." + it.url.takeLast(25) else it.url }} +Video: +${video.toJson()} + """.trim() + + + activity?.runOnUiThread { + val dialog = android.app.AlertDialog.Builder(activity) + dialog.apply { + setTitle("Action: ${video.name}") + setMessage(text) + setPositiveButton("OK") { dialog, _ -> + dialog.dismiss() + } + } + dialog.show() + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt new file mode 100644 index 000000000..7f3b34d01 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt @@ -0,0 +1,55 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import android.content.Intent +import android.os.Build +import com.lagradost.api.Log +import com.lagradost.cloudstream3.actions.OpenInAppAction +import com.lagradost.cloudstream3.actions.makeTempM3U8Intent +import com.lagradost.cloudstream3.actions.updateDurationAndPosition +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos + +class VlcPackage: OpenInAppAction( + appName = txt("VLC"), + packageName = "org.videolan.vlc", + intentClass = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + "org.videolan.vlc.gui.video.VideoPlayerActivity" + } else { + null + }, + action = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + "org.videolan.vlc.player.result" + } else { + Intent.ACTION_VIEW + } +) { + override val oneSource = false + + // https://wiki.videolan.org/Android_Player_Intents/ + override fun putExtra( + activity: Activity, + intent: Intent, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + + makeTempM3U8Intent(activity, intent, result) + + val position = getViewPos(video.id)?.position ?: 0L + + intent.putExtra("from_start", false) + intent.putExtra("position", position) + } + + override fun onResult(activity: Activity, intent: Intent?) { + val position = intent?.getLongExtra("extra_position", -1) ?: -1 + val duration = intent?.getLongExtra("extra_duration", -1) ?: -1 + Log.d("VLC", "Position: $position, Duration: $duration") + updateDurationAndPosition(position, duration) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt new file mode 100644 index 000000000..468b2e4e4 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt @@ -0,0 +1,61 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.core.net.toUri +import com.lagradost.cloudstream3.USER_AGENT +import com.lagradost.cloudstream3.actions.OpenInAppAction +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.ExtractorLinkType + +class WebVideoCastPackage: OpenInAppAction( + txt("Web Video Cast"), + "com.instantbits.cast.webvideo" +) { + + override val oneSource = true + + override val sourceTypes = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + + override fun putExtra( + activity: Activity, + intent: Intent, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + val link = result.links[index ?: 0] + + intent.apply { + setDataAndType(Uri.parse(link.url), "video/*") + + val title = video.name ?: video.headerName + + putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) + putExtra("title", title) + video.poster?.let { putExtra("poster", it) } + val headers = Bundle().apply { + if (link.referer.isNotBlank()) + putString("Referer", link.referer) + putString("User-Agent", USER_AGENT) + for ((key, value) in link.headers) { + putString(key, value) + } + } + putExtra("android.media.intent.extra.HTTP_HEADERS", headers) + putExtra("secure_uri", true) + } + } + + override fun onResult(activity: Activity, intent: Intent?) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt index 1eaac5056..b6556cbfe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt @@ -3,10 +3,15 @@ package com.lagradost.cloudstream3.ui import android.os.Bundle import android.util.Log import android.view.Menu -import android.view.View.* -import android.widget.* +import android.view.View.GONE +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import android.widget.AbsListView +import android.widget.ArrayAdapter +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.ListView 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 @@ -25,7 +30,7 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.sortUrls -import com.lagradost.cloudstream3.ui.player.LoadType +import com.lagradost.cloudstream3.ui.player.LOADTYPE_CHROMECAST import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.result.ResultEpisode @@ -298,7 +303,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi val isSuccessful = safeApiCall { generator.generateLinks( - clearCache = false, type = LoadType.Chromecast, + clearCache = false, + allowedTypes = LOADTYPE_CHROMECAST, callback = { it.first?.let { link -> currentLinks.add(link) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt index c7db7d045..7d3d18ca9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt @@ -6,6 +6,7 @@ import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.SubtitleUtils.cleanDisplayName import com.lagradost.cloudstream3.utils.SubtitleUtils.isMatchingSubtitle import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadFileInfoAndUpdateSettings @@ -57,10 +58,11 @@ class DownloadFileGenerator( override suspend fun generateLinks( clearCache: Boolean, - type: LoadType, + sourceTypes: Set, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int + offset: Int, + isCasting: Boolean ): Boolean { val meta = episodes[currentIndex + offset] diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt index ec485f1c8..794dd762d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.player import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType class ExtractorLinkGenerator( private val links: List, @@ -37,15 +38,15 @@ class ExtractorLinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - type: LoadType, + sourceTypes: Set, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int + offset: Int, + isCasting: Boolean ): Boolean { subtitles.forEach(subtitleCallback) - val allowedTypes = type.toSet() links.forEach { - if(allowedTypes.contains(it.type)) { + if(sourceTypes.contains(it.type)) { callback.invoke(it to null) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt index 6b8e6ea88..89ce7974f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt @@ -3,45 +3,31 @@ package com.lagradost.cloudstream3.ui.player import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType -enum class LoadType { - Unknown, - InApp, - InAppDownload, - ExternalApp, - Browser, - Chromecast, - Fcast -} +val LOADTYPE_INAPP = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 +) + +val LOADTYPE_INAPP_DOWNLOAD = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.M3U8 +) + +val LOADTYPE_CHROMECAST = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 +) + +val LOADTYPE_FCAST = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 +) + +val LOADTYPE_ALL = ExtractorLinkType.entries.toSet() -fun LoadType.toSet() : Set { - return when(this) { - LoadType.InApp -> setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.DASH, - ExtractorLinkType.M3U8 - ) - LoadType.Browser -> setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.DASH, - ExtractorLinkType.M3U8 - ) - LoadType.InAppDownload -> setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.M3U8 - ) - LoadType.ExternalApp, LoadType.Unknown -> ExtractorLinkType.entries.toSet() - LoadType.Chromecast -> setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.DASH, - ExtractorLinkType.M3U8 - ) - LoadType.Fcast -> setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.DASH, - ExtractorLinkType.M3U8 - ) - } -} interface IGenerator { val hasCache: Boolean @@ -60,9 +46,10 @@ interface IGenerator { /* not safe, must use try catch */ suspend fun generateLinks( clearCache: Boolean, - type: LoadType, + sourceTypes: Set, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, offset: Int = 0, + isCasting: Boolean = false ): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt index 20feae413..109e3137b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt @@ -4,6 +4,7 @@ import android.net.Uri import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.loadExtractor @@ -69,10 +70,11 @@ class LinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - type: LoadType, + sourceTypes: Set, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int + offset: Int, + isCasting: Boolean ): Boolean { links.amap { link -> if (!extract || !loadExtractor(link.url, referer, { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt index 122eaa975..67cd9de6d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt @@ -15,6 +15,7 @@ import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.EpisodeSkip import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -94,7 +95,7 @@ class PlayerGeneratorViewModel : ViewModel() { if (generator?.hasCache == true && generator?.hasNext() == true) { safeApiCall { generator?.generateLinks( - type = LoadType.InApp, + sourceTypes = LOADTYPE_INAPP, clearCache = false, callback = {}, subtitleCallback = {}, @@ -173,7 +174,7 @@ class PlayerGeneratorViewModel : ViewModel() { } } - fun loadLinks(type: LoadType = LoadType.InApp) { + fun loadLinks(sourceTypes: Set = LOADTYPE_INAPP) { Log.i(TAG, "loadLinks") currentJob?.cancel() @@ -188,7 +189,7 @@ class PlayerGeneratorViewModel : ViewModel() { // load more data _loadingLinks.postValue(Resource.Loading()) val loadingState = safeApiCall { - generator?.generateLinks(type = type, clearCache = forceClearCache, callback = { + generator?.generateLinks(sourceTypes = sourceTypes, clearCache = forceClearCache, callback = { currentLinks.add(it) // Clone to prevent ConcurrentModificationException normalSafeApiCall { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt index 588afbb50..b97ca155b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt @@ -1,13 +1,13 @@ 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 import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType import kotlin.math.max import kotlin.math.min @@ -75,12 +75,12 @@ class RepoLinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - type: LoadType, + allowedTypes: Set, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int + offset: Int, + isCasting: Boolean, ): Boolean { - val allowedTypes = type.toSet() val index = currentIndex val current = episodes.getOrNull(index + offset) ?: return false @@ -123,7 +123,7 @@ class RepoLinkGenerator( val result = APIRepository( getApiFromNameNull(current.apiName) ?: throw Exception("This provider does not exist") ).loadLinks(current.data, - isCasting = LoadType.Chromecast == type, + isCasting = isCasting, subtitleCallback = { file -> val correctFile = PlayerSubtitleHelper.getSubtitleData(file) if (correctFile.url.isNotEmpty() && !currentSubsUrls.contains(correctFile.url)) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 99fc65f81..bbb835e1c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -2,16 +2,11 @@ package com.lagradost.cloudstream3.ui.result import android.app.Activity import android.content.* -import android.net.Uri -import android.os.Build -import android.os.Bundle import android.text.format.Formatter.formatFileSize import android.util.Log import android.widget.Toast import androidx.annotation.MainThread import androidx.appcompat.app.AlertDialog -import androidx.core.content.FileProvider -import androidx.core.net.toUri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -30,17 +25,6 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie import com.lagradost.cloudstream3.LoadResponse.Companion.readIdFromString -import com.lagradost.cloudstream3.MainActivity.Companion.MPV -import com.lagradost.cloudstream3.MainActivity.Companion.MPV_COMPONENT -import com.lagradost.cloudstream3.MainActivity.Companion.MPV_PACKAGE -import com.lagradost.cloudstream3.MainActivity.Companion.MPV_YTDL -import com.lagradost.cloudstream3.MainActivity.Companion.MPV_YTDL_COMPONENT -import com.lagradost.cloudstream3.MainActivity.Companion.MPV_YTDL_PACKAGE -import com.lagradost.cloudstream3.MainActivity.Companion.VLC -import com.lagradost.cloudstream3.MainActivity.Companion.VLC_COMPONENT -import com.lagradost.cloudstream3.MainActivity.Companion.VLC_PACKAGE -import com.lagradost.cloudstream3.MainActivity.Companion.WEB_VIDEO -import com.lagradost.cloudstream3.MainActivity.Companion.WEB_VIDEO_CAST_PACKAGE import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.metaproviders.SyncRedirector import com.lagradost.cloudstream3.mvvm.* @@ -48,20 +32,22 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.providers.Kitsu -import com.lagradost.cloudstream3.syncproviders.providers.SimklApi import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.IGenerator -import com.lagradost.cloudstream3.ui.player.LoadType +import com.lagradost.cloudstream3.ui.player.LOADTYPE_ALL +import com.lagradost.cloudstream3.ui.player.LOADTYPE_CHROMECAST +import com.lagradost.cloudstream3.ui.player.LOADTYPE_FCAST +import com.lagradost.cloudstream3.ui.player.LOADTYPE_INAPP +import com.lagradost.cloudstream3.ui.player.LOADTYPE_INAPP_DOWNLOAD import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull -import com.lagradost.cloudstream3.utils.AppContextUtils.isAppInstalled import com.lagradost.cloudstream3.utils.AppContextUtils.isConnectedToChromecast import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.AppContextUtils.sortSubs @@ -70,6 +56,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe import com.lagradost.cloudstream3.utils.Coroutines.main +import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites @@ -102,7 +89,6 @@ import com.lagradost.cloudstream3.utils.fcast.FcastSession import com.lagradost.cloudstream3.utils.fcast.Opcode import com.lagradost.cloudstream3.utils.fcast.PlayMessage import kotlinx.coroutines.* -import java.io.File import java.util.concurrent.TimeUnit /** This starts at 1 */ @@ -804,7 +790,7 @@ class ResultViewModel2 : ViewModel() { val generator = RepoLinkGenerator(listOf(episode)) val currentLinks = mutableSetOf() val currentSubs = mutableSetOf() - generator.generateLinks(clearCache = false, LoadType.Chromecast, callback = { + generator.generateLinks(clearCache = false, allowedTypes = LOADTYPE_CHROMECAST, callback = { it.first?.let { link -> currentLinks.add(link) } @@ -955,7 +941,7 @@ class ResultViewModel2 : ViewModel() { isVisible: Boolean = true ) { if (activity == null) return - loadLinks(result, isVisible = isVisible, LoadType.Chromecast) { data -> + loadLinks(result, isVisible = isVisible, sourceTypes = LOADTYPE_CHROMECAST) { data -> startChromecast(activity, result, data.links, data.subs, 0) } } @@ -1301,7 +1287,7 @@ class ResultViewModel2 : ViewModel() { private fun loadLinks( result: ResultEpisode, isVisible: Boolean, - type: LoadType, + sourceTypes: Set = LOADTYPE_ALL, clearCache: Boolean = false, work: suspend (CoroutineScope.(LinkLoadingResult) -> Unit) ) { @@ -1310,7 +1296,7 @@ class ResultViewModel2 : ViewModel() { val links = loadLinks( result, isVisible = isVisible, - type = type, + sourceTypes = sourceTypes, clearCache = clearCache ) if (!this.isActive) return@ioSafe @@ -1321,11 +1307,11 @@ class ResultViewModel2 : ViewModel() { private var currentLoadLinkJob: Job? = null private fun acquireSingleLink( result: ResultEpisode, - type: LoadType, + sourceTypes: Set, text: UiText, callback: (Pair) -> Unit, ) { - loadLinks(result, isVisible = true, type) { links -> + loadLinks(result, isVisible = true, sourceTypes) { links -> // Could not find a better way to do this val context = AcraApplication.context postPopup( @@ -1345,7 +1331,7 @@ class ResultViewModel2 : ViewModel() { text: UiText, callback: (Pair) -> Unit, ) { - loadLinks(result, isVisible = true, type = LoadType.Unknown) { links -> + loadLinks(result, isVisible = true) { links -> postPopup( text, links.subs.map { txt(it.name) }) @@ -1358,7 +1344,7 @@ class ResultViewModel2 : ViewModel() { private suspend fun CoroutineScope.loadLinks( result: ResultEpisode, isVisible: Boolean, - type: LoadType, + sourceTypes: Set = LOADTYPE_ALL, clearCache: Boolean = false, ): LinkLoadingResult { val tempGenerator = RepoLinkGenerator(listOf(result)) @@ -1372,7 +1358,7 @@ class ResultViewModel2 : ViewModel() { } try { updatePage() - tempGenerator.generateLinks(clearCache, type, { (link, _) -> + tempGenerator.generateLinks(clearCache, sourceTypes, { (link, _) -> if (link != null) { links += link updatePage() @@ -1390,185 +1376,11 @@ class ResultViewModel2 : ViewModel() { return LinkLoadingResult(sortUrls(links), sortSubs(subs)) } - private fun launchActivity( - activity: Activity?, - resumeApp: MainActivity.Companion.ResultResume, - id: Int? = null, - work: suspend (Intent.(Activity) -> Unit) - ): Job? { - val act = activity ?: return null - return CoroutineScope(Dispatchers.IO).launch { - try { - resumeApp.launch(id) { - work(act) - } - } catch (t: Throwable) { - logError(t) - main { - if (t is ActivityNotFoundException) { - showToast(txt(R.string.app_not_found_error), Toast.LENGTH_LONG) - } else { - showToast(t.toString(), Toast.LENGTH_LONG) - } - } - } - } - } - - private fun playInWebVideo( - activity: Activity?, - link: ExtractorLink, - title: String?, - posterUrl: String?, - subtitles: List - ) = launchActivity(activity, WEB_VIDEO) { - setDataAndType(Uri.parse(link.url), "video/*") - - putExtra("subs", subtitles.map { it.url.toUri() }.toTypedArray()) - title?.let { putExtra("title", title) } - posterUrl?.let { putExtra("poster", posterUrl) } - val headers = Bundle().apply { - if (link.referer.isNotBlank()) - putString("Referer", link.referer) - putString("User-Agent", USER_AGENT) - for ((key, value) in link.headers) { - putString(key, value) - } - } - putExtra("android.media.intent.extra.HTTP_HEADERS", headers) - putExtra("secure_uri", true) - } - - private fun playWithMpv( - activity: Activity?, - id: Int, - link: ExtractorLink, - subtitles: List, - resume: Boolean = true, - ) = launchActivity(activity, MPV, id) { - putExtra("subs", subtitles.map { it.url.toUri() }.toTypedArray()) - putExtra("subs.name", subtitles.map { it.name }.toTypedArray()) - putExtra("subs.filename", subtitles.map { it.name }.toTypedArray()) - setDataAndType(Uri.parse(link.url), "video/*") - component = MPV_COMPONENT - putExtra("secure_uri", true) - putExtra("return_result", true) - val position = getViewPos(id)?.position - if (resume && position != null) - putExtra("position", position.toInt()) - } - - private fun playWithMpvYtdl( - activity: Activity?, - id: Int, - link: ExtractorLink, - subtitles: List, - resume: Boolean = true, - ) = launchActivity(activity, MPV_YTDL, id) { - putExtra("subs", subtitles.map { it.url.toUri() }.toTypedArray()) - putExtra("subs.name", subtitles.map { it.name }.toTypedArray()) - putExtra("subs.filename", subtitles.map { it.name }.toTypedArray()) - setDataAndType(Uri.parse(link.url), "video/*") - component = MPV_YTDL_COMPONENT - putExtra("secure_uri", true) - putExtra("return_result", true) - val position = getViewPos(id)?.position - if (resume && position != null) - putExtra("position", position.toInt()) - } - - // https://wiki.videolan.org/Android_Player_Intents/ - private fun playWithVlc( - activity: Activity?, - data: LinkLoadingResult, - id: Int, - resume: Boolean = true, - // if it is only a single link then resume works correctly - singleFile: Boolean? = null - ) = launchActivity(activity, VLC, id) { act -> - addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - - val outputDir = act.cacheDir - - if (singleFile ?: (data.links.size == 1)) { - setDataAndType(data.links.first().url.toUri(), "video/*") - } else { - val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir) - - var text = "#EXTM3U" - - // With subtitles it doesn't work for no reason :( -// for (sub in data.subs) { -// text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.name}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.name}\",URI=\"${sub.url}\"" -// } - for (link in data.links) { - text += "\n#EXTINF:, ${link.name}\n${link.url}" - } - outputFile.writeText(text) - - setDataAndType( - FileProvider.getUriForFile( - act, - act.applicationContext.packageName + ".provider", - outputFile - ), "video/*" - ) - } - - val position = if (resume) { - getViewPos(id)?.position ?: 0L - } else { - 1L - } - - // Component no longer safe to use in A13 for VLC - // https://code.videolan.org/videolan/vlc-android/-/issues/2776 - // This will likely need to be updated once VLC fixes their documentation. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - component = VLC_COMPONENT - } - - putExtra("from_start", !resume) - putExtra("position", position) - } - - fun handleAction(click: EpisodeClickEvent) = viewModelScope.launchSafe { handleEpisodeClickEvent(click) } - data class ExternalApp( - val packageString: String, - val name: Int, - val action: Int, - ) - - private val apps = listOf( - ExternalApp( - VLC_PACKAGE, - R.string.player_settings_play_in_vlc, - ACTION_PLAY_EPISODE_IN_VLC_PLAYER - ), ExternalApp( - WEB_VIDEO_CAST_PACKAGE, - R.string.player_settings_play_in_web, - ACTION_PLAY_EPISODE_IN_WEB_VIDEO - ), - ExternalApp( - MPV_PACKAGE, - R.string.player_settings_play_in_mpv, - ACTION_PLAY_EPISODE_IN_MPV - ), - ExternalApp( - MPV_YTDL_PACKAGE, - R.string.player_settings_play_in_mpvytdl, - ACTION_PLAY_EPISODE_IN_MPV_YTDL - ) - ) - fun releaseEpisodeSynopsis() { _episodeSynopsis.postValue(null) } @@ -1578,8 +1390,6 @@ class ResultViewModel2 : ViewModel() { ACTION_SHOW_OPTIONS -> { val options = mutableListOf>() - VideoClickActionHolder.makeOptionMap(activity, click.data) - if (activity?.isConnectedToChromecast() == true) { options.addAll( listOf( @@ -1597,20 +1407,8 @@ class ResultViewModel2 : ViewModel() { options.add(txt(R.string.episode_action_play_in_app) to ACTION_PLAY_EPISODE_IN_PLAYER) - for (app in apps) { - if (activity?.isAppInstalled(app.packageString) == true) { - options.add( - txt( - R.string.episode_action_play_in_format, - txt(app.name) - ) to app.action - ) - } - } - options.addAll( listOf( - txt(R.string.episode_action_play_in_browser) to ACTION_PLAY_EPISODE_IN_BROWSER, txt(R.string.episode_action_copy_link) to ACTION_COPY_LINK, txt(R.string.episode_action_auto_download) to ACTION_DOWNLOAD_EPISODE, txt(R.string.episode_action_download_mirror) to ACTION_DOWNLOAD_MIRROR, @@ -1724,7 +1522,7 @@ class ResultViewModel2 : ViewModel() { val response = currentResponse ?: return acquireSingleLink( click.data, - LoadType.InAppDownload, + LOADTYPE_INAPP_DOWNLOAD, txt(R.string.episode_action_download_mirror) ) { (result, index) -> ioSafe { @@ -1754,7 +1552,7 @@ class ResultViewModel2 : ViewModel() { loadLinks( click.data, isVisible = false, - type = LoadType.InApp, + LOADTYPE_INAPP, clearCache = true ) } @@ -1767,7 +1565,7 @@ class ResultViewModel2 : ViewModel() { ACTION_CHROME_CAST_MIRROR -> { acquireSingleLink( click.data, - LoadType.Chromecast, + LOADTYPE_CHROMECAST, txt(R.string.episode_action_chromecast_mirror) ) { (result, index) -> startChromecast(activity, click.data, result.links, result.subs, index) @@ -1784,7 +1582,7 @@ class ResultViewModel2 : ViewModel() { acquireSingleLink( click.data, - LoadType.Fcast, + LOADTYPE_FCAST, txt(R.string.episode_action_cast_mirror) ) { (result, index) -> val host = device?.host ?: return@acquireSingleLink @@ -1807,24 +1605,10 @@ class ResultViewModel2 : ViewModel() { } } - ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink( - click.data, - LoadType.Browser, - txt(R.string.episode_action_play_in_browser) - ) { (result, index) -> - try { - val i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse(result.links[index].url) - activity?.startActivity(i) - } catch (e: Exception) { - logError(e) - } - } - ACTION_COPY_LINK -> { acquireSingleLink( click.data, - LoadType.ExternalApp, + LOADTYPE_ALL, txt(R.string.episode_action_copy_link) ) { (result, index) -> val link = result.links[index] @@ -1836,70 +1620,6 @@ class ResultViewModel2 : ViewModel() { startChromecast(activity, click.data) } - ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> { - loadLinks(click.data, isVisible = true, LoadType.ExternalApp) { links -> - if (links.links.isEmpty()) { - showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT) - return@loadLinks - } - - playWithVlc( - activity, - links, - click.data.id - ) - } - } - - ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink( - click.data, - LoadType.Chromecast, - txt( - R.string.episode_action_play_in_format, - txt(R.string.player_settings_play_in_web) - ) - ) { (result, index) -> - playInWebVideo( - activity, - result.links[index], - click.data.name ?: click.data.headerName, - click.data.poster, - result.subs - ) - } - - ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink( - click.data, - LoadType.Chromecast, - txt( - R.string.episode_action_play_in_format, - txt(R.string.player_settings_play_in_mpv) - ) - ) { (result, index) -> - playWithMpv( - activity, - click.data.id, - result.links[index], - result.subs - ) - } - - ACTION_PLAY_EPISODE_IN_MPV_YTDL -> acquireSingleLink( - click.data, - LoadType.Chromecast, - txt( - R.string.episode_action_play_in_format, - txt(R.string.player_settings_play_in_mpvytdl) - ) - ) { (result, index) -> - playWithMpvYtdl( - activity, - click.data.id, - result.links[index], - result.subs - ) - } - ACTION_PLAY_EPISODE_IN_PLAYER -> { val data = currentResponse?.syncData?.toList() ?: emptyList() val list = @@ -1915,7 +1635,7 @@ class ResultViewModel2 : ViewModel() { if (currentResponse?.type == TvType.CustomMedia) { generator?.generateLinks( clearCache = true, - LoadType.Unknown, + LOADTYPE_ALL, callback = {}, subtitleCallback = {}) } else { @@ -1945,20 +1665,12 @@ class ResultViewModel2 : ViewModel() { else -> { val action = VideoClickActionHolder.getActionById(click.action) ?: return + activity?.setKey("last_click_action", action.uniqueId()) // TODO: use action.sourceTypes if (action.oneSource) { - loadLinks(click.data, isVisible = true, LoadType.ExternalApp) { links -> - action.runAction( - activity, - click.data, - links, - null - ) - } - } else { acquireSingleLink( click.data, - LoadType.ExternalApp, + action.sourceTypes, action.name ) { (result, index) -> action.runAction( @@ -1968,6 +1680,15 @@ class ResultViewModel2 : ViewModel() { index ) } + } else { + loadLinks(click.data, isVisible = true, action.sourceTypes) { links -> + action.runAction( + activity, + click.data, + links, + null + ) + } } } } @@ -2077,7 +1798,7 @@ class ResultViewModel2 : ViewModel() { isResponseRequired = false ) if (map.isNullOrEmpty()) return@argamap - updateEpisodes = DubStatus.values().map { dubStatus -> + updateEpisodes = DubStatus.entries.map { dubStatus -> val current = this.episodes[dubStatus]?.mapIndexed { index, episode -> episode.apply { From 6f76352cbea21312c02f0306e996ef470f7a2ebd Mon Sep 17 00:00:00 2001 From: Cloudburst <18114966+C10udburst@users.noreply.github.com> Date: Tue, 10 Sep 2024 21:17:36 +0200 Subject: [PATCH 003/962] move all possible actions to new api, handle some todos --- .../lagradost/cloudstream3/MainActivity.kt | 12 +-- .../cloudstream3/actions/OpenInAppAction.kt | 46 ++++++----- .../cloudstream3/actions/VideoClickAction.kt | 35 ++++++-- .../cloudstream3/actions/temp/MpvKtPackage.kt | 14 ++-- .../cloudstream3/actions/temp/MpvPackage.kt | 22 ++--- .../actions/temp/PlayInBrowserAction.kt | 14 ++-- .../cloudstream3/actions/temp/TestAction.kt | 38 ++------- .../cloudstream3/actions/temp/VlcPackage.kt | 10 ++- .../actions/temp/WebVideoCastPackage.kt | 9 +- .../actions/temp/fcast/FcastAction.kt | 66 +++++++++++++++ .../temp}/fcast/FcastManager.kt | 2 +- .../temp}/fcast/FcastSession.kt | 2 +- .../{utils => actions/temp}/fcast/Packets.kt | 2 +- .../cloudstream3/ui/ControllerActivity.kt | 3 +- .../cloudstream3/ui/player/IGenerator.kt | 6 -- .../cloudstream3/ui/result/EpisodeAdapter.kt | 25 +----- .../ui/result/ResultViewModel2.kt | 82 ++++++------------- .../ui/settings/SettingsPlayer.kt | 18 ++-- app/src/main/res/values/strings.xml | 2 +- app/src/main/res/xml/settings_player.xml | 2 +- 20 files changed, 214 insertions(+), 196 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt rename app/src/main/java/com/lagradost/cloudstream3/{utils => actions/temp}/fcast/FcastManager.kt (98%) rename app/src/main/java/com/lagradost/cloudstream3/{utils => actions/temp}/fcast/FcastSession.kt (96%) rename app/src/main/java/com/lagradost/cloudstream3/{utils => actions/temp}/fcast/Packets.kt (95%) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 0625f572e..fa54545cf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -173,7 +173,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API -import com.lagradost.cloudstream3.utils.fcast.FcastManager +import com.lagradost.cloudstream3.actions.temp.fcast.FcastManager import com.lagradost.safefile.SafeFile import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -186,16 +186,6 @@ import kotlin.math.abs import kotlin.math.absoluteValue import kotlin.system.exitProcess -//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898 -//https://wiki.videolan.org/Android_Player_Intents/ - -//https://github.com/mpv-android/mpv-android/blob/0eb3cdc6f1632636b9c30d52ec50e4b017661980/app/src/main/java/is/xyz/mpv/MPVActivity.kt#L904 -//https://mpv-android.github.io/mpv-android/intent.html - -// https://www.webvideocaster.com/integrations - -//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225 - class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCallback { companion object { var activityResultLauncher: ActivityResultLauncher? = null diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt index 534102fea..45e61f2a8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.actions import android.app.Activity import android.content.ActivityNotFoundException import android.content.ComponentName +import android.content.Context import android.content.Intent import android.widget.Toast import androidx.core.content.FileProvider @@ -36,9 +37,10 @@ fun updateDurationAndPosition(position: Long, duration: Long) { * Util method that may be helpful for creating intents for apps that support m3u8 files. * All sources are written to a temporary m3u8 file, which is then sent to the app. */ -fun makeTempM3U8Intent(activity: Activity, - intent: Intent, - result: LinkLoadingResult) { +fun makeTempM3U8Intent( + context: Context, + intent: Intent, + result: LinkLoadingResult) { intent.apply { addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) @@ -46,33 +48,35 @@ fun makeTempM3U8Intent(activity: Activity, addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } - val outputDir = activity.cacheDir + val outputDir = context.cacheDir if (result.links.size == 1) { intent.setDataAndType(result.links.first().url.toUri(), "video/*") } else { val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir) - var text = "#EXTM3U" + var text = "#EXTM3U\n#EXT-X-VERSION:3" - //With subtitles it doesn't work for no reason :( + result.links.forEachIndexed { index, link -> + text += "\n#EXTINF:$index,${link.name}\n${link.url}" + } + + //With subtitles it doesn't work for no reason :( /*for (sub in result.subs) { val normalizedName = sub.name.replace("[^a-zA-Z0-9 ]".toRegex(), "") - val language = fromLanguageToTwoLetters(sub.name, true) ?: "und" - text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${normalizedName}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${language}\",URI=\"${sub.url}\"" + text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${normalizedName}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.languageCode}\",URI=\"${sub.url}\"" }*/ - for (link in result.links) { - text += "\n#EXTINF:, ${link.name}\n${link.url}" - } + text += "\n#EXT-X-ENDLIST" + outputFile.writeText(text) intent.setDataAndType( FileProvider.getUriForFile( - activity, - activity.applicationContext.packageName + ".provider", + context, + context.applicationContext.packageName + ".provider", outputFile - ), "video/*" + ), "application/x-mpegURL" ) } } @@ -86,21 +90,23 @@ abstract class OpenInAppAction( override val name: UiText get() = txt(R.string.episode_action_play_in_format, appName) - override fun shouldShow(activity: Activity?, video: ResultEpisode) = activity?.isAppInstalled(packageName) == true + override val isPlayer = true + + override fun shouldShow(context: Context?, video: ResultEpisode?) = context?.isAppInstalled(packageName) == true override fun runAction( - activity: Activity?, + context: Context?, video: ResultEpisode, result: LinkLoadingResult, index: Int? ) { - if (activity == null) return + if (context == null) return val intent = Intent(action) intent.setPackage(packageName) if (intentClass != null) { intent.component = ComponentName(packageName, intentClass) } - putExtra(activity, intent, video, result, index) + putExtra(context, intent, video, result, index) setKey("last_opened_id", video.id) try { CoroutineScope(Dispatchers.IO).launch { @@ -122,11 +128,11 @@ abstract class OpenInAppAction( * Before intent is sent, this function is called to put extra data into the intent. * @see VideoClickAction.runAction * */ - abstract fun putExtra(activity: Activity, intent: Intent, video: ResultEpisode, result: LinkLoadingResult, index: Int?) + abstract fun putExtra(context: Context, intent: Intent, video: ResultEpisode, result: LinkLoadingResult, index: Int?) /** * This function is called when the app is opened again after the intent was sent. - * You can use it to for example read duration and position. + * You can use it to for example update duration and position. * @see updateDurationAndPosition */ abstract fun onResult(activity: Activity, intent: Intent?) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt index 75d3747ae..a19553901 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -1,6 +1,8 @@ package com.lagradost.cloudstream3.actions import android.app.Activity +import android.content.Context +import com.lagradost.api.Log import com.lagradost.cloudstream3.actions.temp.MpvKtPackage import com.lagradost.cloudstream3.actions.temp.MpvKtPreviewPackage import com.lagradost.cloudstream3.actions.temp.MpvPackage @@ -9,24 +11,31 @@ import com.lagradost.cloudstream3.actions.temp.PlayInBrowserAction import com.lagradost.cloudstream3.actions.temp.TestAction import com.lagradost.cloudstream3.actions.temp.VlcPackage import com.lagradost.cloudstream3.actions.temp.WebVideoCastPackage +import com.lagradost.cloudstream3.actions.temp.fcast.FcastAction import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.ExtractorLinkType +import kotlin.reflect.jvm.jvmName object VideoClickActionHolder { val allVideoClickActions = threadSafeListOf( PlayInBrowserAction(), VlcPackage(), TestAction(), MpvPackage(), MpvYTDLPackage(), - WebVideoCastPackage(), MpvKtPackage(), MpvKtPreviewPackage() + WebVideoCastPackage(), MpvKtPackage(), MpvKtPreviewPackage(), + FcastAction() ) + init { + Log.d("VideoClickActionHolder", "allVideoClickActions: ${allVideoClickActions.map { it.uniqueId() }}") + } + private const val ACTION_ID_OFFSET = 1000 fun makeOptionMap(activity: Activity?, video: ResultEpisode) = allVideoClickActions // We need to have index before filtering - .mapIndexed { index, it -> it to index + ACTION_ID_OFFSET } + .mapIndexed { id, it -> it to id + ACTION_ID_OFFSET } .filter { it.first.shouldShow(activity, video) } .map { it.first.name to it.second } @@ -34,6 +43,16 @@ object VideoClickActionHolder { fun getActionById(id: Int): VideoClickAction? = allVideoClickActions.getOrNull(id - ACTION_ID_OFFSET) fun getByUniqueId(uniqueId: String): VideoClickAction? = allVideoClickActions.firstOrNull { it.uniqueId() == uniqueId } + + fun uniqueIdToId(uniqueId: String?): Int? { + if (uniqueId == null) return null + return allVideoClickActions + .mapIndexed { id, it -> it to id + ACTION_ID_OFFSET } + .firstOrNull { it.first.uniqueId() == uniqueId } + ?.second + } + + fun getPlayers(activity: Activity? = null) = allVideoClickActions.filter { it.isPlayer && it.shouldShow(activity, null) } } abstract class VideoClickAction { @@ -42,8 +61,8 @@ abstract class VideoClickAction { /** if true, the app will show dialog to select source - result.links[index] */ open val oneSource : Boolean = false - /** if true, this action could be selected as default one press action in settings */ - open val canBeDefault: Boolean = false + /** if true, this action could be selected as default player (one press action) in settings */ + open val isPlayer: Boolean = false /** Which type of sources this action can handle. */ open val sourceTypes: Set = ExtractorLinkType.entries.toSet() @@ -51,16 +70,16 @@ abstract class VideoClickAction { /** Determines which plugin a given provider is from. This is the full path to the plugin. */ var sourcePlugin: String? = null - fun uniqueId() = "$sourcePlugin:${this::class.simpleName}" + fun uniqueId() = "$sourcePlugin:${this::class.jvmName}" - abstract fun shouldShow(activity: Activity?, video: ResultEpisode): Boolean + abstract fun shouldShow(context: Context?, video: ResultEpisode?): Boolean /** * This function is called when the action is clicked. - * @param activity The current activity + * @param context The current activity * @param video The episode/movie that was clicked * @param result The result of the link loading, contains video & subtitle links * @param index if oneSource is true, this is the index of the selected source */ - abstract fun runAction(activity: Activity?, video: ResultEpisode, result: LinkLoadingResult, index: Int?) + abstract fun runAction(context: Context?, video: ResultEpisode, result: LinkLoadingResult, index: Int?) } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt index 57f564d24..f5ded49b8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity +import android.content.Context import android.content.Intent import android.net.Uri import androidx.core.net.toUri @@ -34,27 +35,28 @@ open class MpvKtPackage( ) override fun putExtra( - activity: Activity, + context: Context, intent: Intent, video: ResultEpisode, result: LinkLoadingResult, index: Int? ) { - val link = result.links[index ?: 0] + val link = result.links.getOrNull(index ?: 0) ?: return intent.apply { putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) - putExtra("subs.name", result.subs.map { it.name }.toTypedArray()) - putExtra("subs.filename", result.subs.map { it.name }.toTypedArray()) setDataAndType(Uri.parse(link.url), "video/*") + // m3u8 plays, but changing sources feature is not available // makeTempM3U8Intent(activity, this, result) - putExtra("secure_uri", true) - putExtra("return_result", true) + //putExtra("headers", link.headers.flatMap { listOf(it.key, it.value) }.toTypedArray()) + val position = getViewPos(video.id)?.position if (position != null) putExtra("position", position.toInt()) + + putExtra("secure_uri", true) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt index 7ff05a3f1..4c66d0450 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity +import android.content.Context import android.content.Intent import androidx.core.net.toUri import com.lagradost.api.Log @@ -13,6 +14,9 @@ import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.ExtractorLinkType +// https://github.com/mpv-android/mpv-android/blob/0eb3cdc6f1632636b9c30d52ec50e4b017661980/app/src/main/java/is/xyz/mpv/MPVActivity.kt#L904 +// https://mpv-android.github.io/mpv-android/intent.html + class MpvYTDLPackage : MpvPackage("MPV YTDL", "is.xyz.mpv.ytdl") { override val sourceTypes = setOf( ExtractorLinkType.VIDEO, @@ -26,14 +30,9 @@ open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv packageName, "is.xyz.mpv.MPVActivity" ) { - override val sourceTypes = setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.DASH, - ExtractorLinkType.M3U8 - ) override fun putExtra( - activity: Activity, + context: Context, intent: Intent, video: ResultEpisode, result: LinkLoadingResult, @@ -41,14 +40,15 @@ open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv ) { intent.apply { putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) - putExtra("subs.name", result.subs.map { it.name }.toTypedArray()) - putExtra("subs.filename", result.subs.map { it.name }.toTypedArray()) - makeTempM3U8Intent(activity, this, result) - putExtra("secure_uri", true) - putExtra("return_result", true) + putExtra("title", video.name) + + makeTempM3U8Intent(context, this, result) + val position = getViewPos(video.id)?.position if (position != null) putExtra("position", position.toInt()) + + putExtra("secure_uri", true) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt index 6d6f78f51..39b689398 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt @@ -1,6 +1,6 @@ package com.lagradost.cloudstream3.actions.temp -import android.app.Activity +import android.content.Context import android.content.Intent import android.net.Uri import com.lagradost.cloudstream3.actions.VideoClickAction @@ -15,25 +15,27 @@ class PlayInBrowserAction: VideoClickAction() { override val oneSource = true + override val isPlayer = true + override val sourceTypes: Set = setOf( ExtractorLinkType.VIDEO, ExtractorLinkType.DASH, ExtractorLinkType.M3U8 ) - override fun shouldShow(activity: Activity?, video: ResultEpisode) = true + override fun shouldShow(context: Context?, video: ResultEpisode?) = true override fun runAction( - activity: Activity?, + context: Context?, video: ResultEpisode, result: LinkLoadingResult, index: Int? ) { - if (index == null) return + val link = result.links.getOrNull(index ?: 0) ?: return try { val i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse(result.links[index].url) - activity?.startActivity(i) + i.data = Uri.parse(link.url) + context?.startActivity(i) } catch (e: Exception) { logError(e) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/TestAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/TestAction.kt index 3c7464a05..c825202a1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/TestAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/TestAction.kt @@ -1,47 +1,27 @@ package com.lagradost.cloudstream3.actions.temp -import android.app.Activity +import android.content.Context +import android.content.Intent import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.actions.makeTempM3U8Intent import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.txt -import com.lagradost.cloudstream3.utils.AppUtils.toJson class TestAction: VideoClickAction() { override val name = txt("Test action") - override fun shouldShow(activity: Activity?, video: ResultEpisode): Boolean { - return true - } + override fun shouldShow(context: Context?, video: ResultEpisode?) = true override fun runAction( - activity: Activity?, + context: Context?, video: ResultEpisode, result: LinkLoadingResult, index: Int? ) { - // Show dialog box - val text = """ -Result links: -${result.links.joinToString("\n") { if (it.url.length > 50) it.url.take(25) + "..." + it.url.takeLast(25) else it.url }} -Result subs: -${result.subs.joinToString("\n") { if (it.url.length > 50) it.url.take(25) + "..." + it.url.takeLast(25) else it.url }} -Video: -${video.toJson()} - """.trim() - - - activity?.runOnUiThread { - val dialog = android.app.AlertDialog.Builder(activity) - dialog.apply { - setTitle("Action: ${video.name}") - setMessage(text) - setPositiveButton("OK") { dialog, _ -> - dialog.dismiss() - } - } - dialog.show() - } - + if (context == null) return + val i = Intent(Intent.ACTION_VIEW) + makeTempM3U8Intent(context, i, result) + context.startActivity(i) } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt index 7f3b34d01..ec824d4eb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity +import android.content.Context import android.content.Intent import android.os.Build import com.lagradost.api.Log @@ -12,6 +13,9 @@ import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +// https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898 +// https://wiki.videolan.org/Android_Player_Intents/ + class VlcPackage: OpenInAppAction( appName = txt("VLC"), packageName = "org.videolan.vlc", @@ -28,21 +32,21 @@ class VlcPackage: OpenInAppAction( ) { override val oneSource = false - // https://wiki.videolan.org/Android_Player_Intents/ override fun putExtra( - activity: Activity, + context: Context, intent: Intent, video: ResultEpisode, result: LinkLoadingResult, index: Int? ) { - makeTempM3U8Intent(activity, intent, result) + makeTempM3U8Intent(context, intent, result) val position = getViewPos(video.id)?.position ?: 0L intent.putExtra("from_start", false) intent.putExtra("position", position) + intent.putExtra("secure_uri", true) } override fun onResult(activity: Activity, intent: Intent?) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt index 468b2e4e4..f8419f63c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity +import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle @@ -12,6 +13,8 @@ import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.ExtractorLinkType +// https://www.webvideocaster.com/integrations + class WebVideoCastPackage: OpenInAppAction( txt("Web Video Cast"), "com.instantbits.cast.webvideo" @@ -26,7 +29,7 @@ class WebVideoCastPackage: OpenInAppAction( ) override fun putExtra( - activity: Activity, + context: Context, intent: Intent, video: ResultEpisode, result: LinkLoadingResult, @@ -55,7 +58,5 @@ class WebVideoCastPackage: OpenInAppAction( } } - override fun onResult(activity: Activity, intent: Intent?) { - - } + override fun onResult(activity: Activity, intent: Intent?) = Unit } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt new file mode 100644 index 000000000..f2f7621e8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt @@ -0,0 +1,66 @@ +package com.lagradost.cloudstream3.actions.temp.fcast + +import android.content.Context +import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.USER_AGENT +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog + +class FcastAction: VideoClickAction() { + override val name = txt("Fcast") + + override val oneSource = true + + override val sourceTypes = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + + override fun shouldShow(context: Context?, video: ResultEpisode?) = FcastManager.currentDevices.isNotEmpty() + + override fun runAction( + context: Context?, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + val link = result.links.getOrNull(index ?: 0) ?: return + val devices = FcastManager.currentDevices.toList() + context?.getActivity()?.showBottomDialog( + devices.map { it.name }, + -1, + "Select device", + false, + {}) { + val position = getViewPos(video.id)?.position + castTo(devices.getOrNull(it), link, position) + } + } + + + private fun castTo(device: PublicDeviceInfo?, link: ExtractorLink, position: Long?) { + val host = device?.host ?: return + + FcastSession(host).use { session -> + session.sendMessage( + Opcode.Play, + PlayMessage( + "application/vnd.apple.mpegurl", + link.url, + time = position?.let { it / 1000.0 }, + headers = mapOf( + "referer" to link.referer, + "user-agent" to USER_AGENT + ) + link.headers + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastManager.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastManager.kt similarity index 98% rename from app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastManager.kt rename to app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastManager.kt index e7c36a872..78682ca1c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastManager.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.utils.fcast +package com.lagradost.cloudstream3.actions.temp.fcast import android.content.Context import android.net.nsd.NsdManager diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastSession.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastSession.kt similarity index 96% rename from app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastSession.kt rename to app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastSession.kt index 1f33bca43..326d11191 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastSession.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastSession.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.utils.fcast +package com.lagradost.cloudstream3.actions.temp.fcast import android.util.Log import androidx.annotation.WorkerThread diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/Packets.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/Packets.kt similarity index 95% rename from app/src/main/java/com/lagradost/cloudstream3/utils/fcast/Packets.kt rename to app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/Packets.kt index 61c00d6ed..26f5cec53 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/Packets.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/Packets.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.utils.fcast +package com.lagradost.cloudstream3.actions.temp.fcast // See https://gitlab.com/futo-org/fcast/-/wikis/Protocol-version-1 enum class Opcode(val value: Byte) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt index b6556cbfe..4b5d680c3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt @@ -311,7 +311,8 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi } }, subtitleCallback = { currentSubs.add(it) - }) + }, + isCasting = true) } val sortedLinks = sortUrls(currentLinks) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt index 89ce7974f..31cf0c70f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt @@ -20,12 +20,6 @@ val LOADTYPE_CHROMECAST = setOf( ExtractorLinkType.M3U8 ) -val LOADTYPE_FCAST = setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.DASH, - ExtractorLinkType.M3U8 -) - val LOADTYPE_ALL = ExtractorLinkType.entries.toSet() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index a19adc4e8..0c754f513 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -11,6 +11,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.databinding.ResultEpisodeBinding import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable @@ -34,10 +35,7 @@ import java.util.Locale * Ids >= 1000 are reserved for VideoClickActions * @see VideoClickActionHolder */ - const val ACTION_PLAY_EPISODE_IN_PLAYER = 1 -const val ACTION_PLAY_EPISODE_IN_VLC_PLAYER = 2 -const val ACTION_PLAY_EPISODE_IN_BROWSER = 3 const val ACTION_CHROME_CAST_EPISODE = 4 const val ACTION_CHROME_CAST_MIRROR = 5 @@ -57,12 +55,7 @@ const val ACTION_SHOW_DESCRIPTION = 15 const val ACTION_DOWNLOAD_EPISODE_SUBTITLE = 13 const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14 -const val ACTION_PLAY_EPISODE_IN_WEB_VIDEO = 16 -const val ACTION_PLAY_EPISODE_IN_MPV = 17 -const val ACTION_PLAY_EPISODE_IN_MPV_YTDL = 20 - const val ACTION_MARK_AS_WATCHED = 18 -const val ACTION_FCAST = 19 const val TV_EP_SIZE = 400 @@ -74,22 +67,10 @@ class EpisodeAdapter( private val downloadClickCallback: (DownloadClickEvent) -> Unit, ) : RecyclerView.Adapter() { companion object { - /** - * @return ACTION_PLAY_EPISODE_IN_PLAYER, ACTION_PLAY_EPISODE_IN_BROWSER or ACTION_PLAY_EPISODE_IN_VLC_PLAYER depending on player settings. - * See array.xml/player_pref_values - **/ fun getPlayerAction(context: Context): Int { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) - return when (settingsManager.getInt(context.getString(R.string.player_pref_key), 1)) { - 1 -> ACTION_PLAY_EPISODE_IN_PLAYER - 2 -> ACTION_PLAY_EPISODE_IN_VLC_PLAYER - 3 -> ACTION_PLAY_EPISODE_IN_BROWSER - 4 -> ACTION_PLAY_EPISODE_IN_WEB_VIDEO - 5 -> ACTION_PLAY_EPISODE_IN_MPV - 6 -> ACTION_PLAY_EPISODE_IN_MPV_YTDL - else -> ACTION_PLAY_EPISODE_IN_PLAYER - } + val playerPref = settingsManager.getString(context.getString(R.string.player_default_key), "") + return VideoClickActionHolder.uniqueIdToId(playerPref) ?: ACTION_PLAY_EPISODE_IN_PLAYER } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index bbb835e1c..a330d5638 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -39,7 +39,6 @@ import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.IGenerator import com.lagradost.cloudstream3.ui.player.LOADTYPE_ALL import com.lagradost.cloudstream3.ui.player.LOADTYPE_CHROMECAST -import com.lagradost.cloudstream3.ui.player.LOADTYPE_FCAST import com.lagradost.cloudstream3.ui.player.LOADTYPE_INAPP import com.lagradost.cloudstream3.ui.player.LOADTYPE_INAPP_DOWNLOAD import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator @@ -84,10 +83,6 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.setVideoWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.updateSubscribedData import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper import com.lagradost.cloudstream3.utils.UIHelper.navigate -import com.lagradost.cloudstream3.utils.fcast.FcastManager -import com.lagradost.cloudstream3.utils.fcast.FcastSession -import com.lagradost.cloudstream3.utils.fcast.Opcode -import com.lagradost.cloudstream3.utils.fcast.PlayMessage import kotlinx.coroutines.* import java.util.concurrent.TimeUnit @@ -790,7 +785,7 @@ class ResultViewModel2 : ViewModel() { val generator = RepoLinkGenerator(listOf(episode)) val currentLinks = mutableSetOf() val currentSubs = mutableSetOf() - generator.generateLinks(clearCache = false, allowedTypes = LOADTYPE_CHROMECAST, callback = { + generator.generateLinks(clearCache = false, allowedTypes = LOADTYPE_INAPP_DOWNLOAD, callback = { it.first?.let { link -> currentLinks.add(link) } @@ -941,7 +936,7 @@ class ResultViewModel2 : ViewModel() { isVisible: Boolean = true ) { if (activity == null) return - loadLinks(result, isVisible = isVisible, sourceTypes = LOADTYPE_CHROMECAST) { data -> + loadLinks(result, isVisible = isVisible, sourceTypes = LOADTYPE_CHROMECAST, isCasting = true) { data -> startChromecast(activity, result, data.links, data.subs, 0) } } @@ -1251,7 +1246,7 @@ class ResultViewModel2 : ViewModel() { _loadedLinks.postValue(null) } - private fun postPopup(text: UiText, options: List, callback: suspend (Int?) -> Unit) { + fun postPopup(text: UiText, options: List, callback: suspend (Int?) -> Unit) { _selectPopup.postValue( SelectPopup.SelectText( text, @@ -1289,6 +1284,7 @@ class ResultViewModel2 : ViewModel() { isVisible: Boolean, sourceTypes: Set = LOADTYPE_ALL, clearCache: Boolean = false, + isCasting: Boolean = false, work: suspend (CoroutineScope.(LinkLoadingResult) -> Unit) ) { currentLoadLinkJob?.cancel() @@ -1297,7 +1293,8 @@ class ResultViewModel2 : ViewModel() { result, isVisible = isVisible, sourceTypes = sourceTypes, - clearCache = clearCache + clearCache = clearCache, + isCasting = isCasting ) if (!this.isActive) return@ioSafe work(links) @@ -1309,9 +1306,10 @@ class ResultViewModel2 : ViewModel() { result: ResultEpisode, sourceTypes: Set, text: UiText, - callback: (Pair) -> Unit, + isCasting: Boolean = false, + callback: (Pair) -> Unit ) { - loadLinks(result, isVisible = true, sourceTypes) { links -> + loadLinks(result, isVisible = true, sourceTypes, isCasting = isCasting) { links -> // Could not find a better way to do this val context = AcraApplication.context postPopup( @@ -1346,6 +1344,7 @@ class ResultViewModel2 : ViewModel() { isVisible: Boolean, sourceTypes: Set = LOADTYPE_ALL, clearCache: Boolean = false, + isCasting: Boolean = false ): LinkLoadingResult { val tempGenerator = RepoLinkGenerator(listOf(result)) @@ -1358,15 +1357,19 @@ class ResultViewModel2 : ViewModel() { } try { updatePage() - tempGenerator.generateLinks(clearCache, sourceTypes, { (link, _) -> - if (link != null) { - links += link - updatePage() - } - }, { sub -> + tempGenerator.generateLinks(clearCache, + allowedTypes = sourceTypes, + callback = { (link, _) -> + if (link != null) { + links += link + updatePage() + } + }, + subtitleCallback = { sub -> subs += sub updatePage() - }) + }, + isCasting = isCasting) } catch (e: Exception) { logError(e) } finally { @@ -1399,12 +1402,6 @@ class ResultViewModel2 : ViewModel() { ) } - if (FcastManager.currentDevices.isNotEmpty()) { - options.add( - txt(R.string.player_settings_play_in_fcast) to ACTION_FCAST - ) - } - options.add(txt(R.string.episode_action_play_in_app) to ACTION_PLAY_EPISODE_IN_PLAYER) options.addAll( @@ -1566,45 +1563,13 @@ class ResultViewModel2 : ViewModel() { acquireSingleLink( click.data, LOADTYPE_CHROMECAST, - txt(R.string.episode_action_chromecast_mirror) + txt(R.string.episode_action_chromecast_mirror), + isCasting = true ) { (result, index) -> startChromecast(activity, click.data, result.links, result.subs, index) } } - ACTION_FCAST -> { - val devices = FcastManager.currentDevices.toList() - postPopup( - txt(R.string.player_settings_select_cast_device), - devices.map { txt(it.name) }) { index -> - if (index == null) return@postPopup - val device = devices.getOrNull(index) - - acquireSingleLink( - click.data, - LOADTYPE_FCAST, - txt(R.string.episode_action_cast_mirror) - ) { (result, index) -> - val host = device?.host ?: return@acquireSingleLink - val link = result.links.getOrNull(index) ?: return@acquireSingleLink - - FcastSession(host).use { session -> - session.sendMessage( - Opcode.Play, - PlayMessage( - link.type.getMimeType(), - link.url, - headers = mapOf( - "referer" to link.referer, - "user-agent" to USER_AGENT - ) + link.headers - ) - ) - } - } - } - } - ACTION_COPY_LINK -> { acquireSingleLink( click.data, @@ -1666,7 +1631,6 @@ class ResultViewModel2 : ViewModel() { val action = VideoClickActionHolder.getActionById(click.action) ?: return activity?.setKey("last_click_action", action.uniqueId()) - // TODO: use action.sourceTypes if (action.oneSource) { acquireSingleLink( click.data, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt index 1753032ac..17580236f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt @@ -6,6 +6,7 @@ import android.view.View import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.PHONE @@ -155,10 +156,17 @@ class SettingsPlayer : PreferenceFragmentCompat() { return@setOnPreferenceClickListener true } - getPref(R.string.player_pref_key)?.setOnPreferenceClickListener { - val prefNames = resources.getStringArray(R.array.player_pref_names) - val prefValues = resources.getIntArray(R.array.player_pref_values) - val current = settingsManager.getInt(getString(R.string.player_pref_key), 1) + getPref(R.string.player_default_key)?.setOnPreferenceClickListener { + val players = VideoClickActionHolder.getPlayers(activity) + val prefNames = buildList { + add(getString(R.string.player_settings_play_in_app)) + addAll(players.map { it.name.asStringNull(activity) ?: it.javaClass.simpleName }) + } + val prefValues = buildList { + add("") + addAll(players.map { it.uniqueId() }) + } + val current = settingsManager.getString(getString(R.string.player_default_key), "") ?: "" activity?.showBottomDialog( prefNames.toList(), @@ -166,7 +174,7 @@ class SettingsPlayer : PreferenceFragmentCompat() { getString(R.string.player_pref), true, {}) { - settingsManager.edit().putInt(getString(R.string.player_pref_key), prefValues[it]).apply() + settingsManager.edit().putString(getString(R.string.player_default_key), prefValues[it]).apply() } return@setOnPreferenceClickListener true } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b2ec8a760..7c7553208 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,7 +17,7 @@ subtitle_settings_chromecast_key quality_pref_key quality_pref_mobile_data_key - player_pref_key + player_default_key prefer_limit_title_key prefer_limit_title_rez_key apk_installer_key diff --git a/app/src/main/res/xml/settings_player.xml b/app/src/main/res/xml/settings_player.xml index 73f9bb5bf..a2575e315 100644 --- a/app/src/main/res/xml/settings_player.xml +++ b/app/src/main/res/xml/settings_player.xml @@ -22,7 +22,7 @@ From e288c83c3d748bfc1b09c5337fffeebd90d28f54 Mon Sep 17 00:00:00 2001 From: Cloudburst <18114966+C10udburst@users.noreply.github.com> Date: Tue, 17 Sep 2024 23:37:59 +0200 Subject: [PATCH 004/962] remove unused strings, address comment --- .../cloudstream3/actions/OpenInAppAction.kt | 11 ++--- .../cloudstream3/actions/VideoClickAction.kt | 6 ++- .../actions/temp/CopyClipboardAction.kt | 27 ++++++++++++ .../actions/temp/PlayInBrowserAction.kt | 3 +- .../temp/{TestAction.kt => ViewM3U8Action.kt} | 7 ++- .../cloudstream3/actions/temp/VlcPackage.kt | 11 ++++- .../actions/temp/fcast/FcastAction.kt | 7 +-- .../cloudstream3/ui/result/EpisodeAdapter.kt | 1 - .../ui/result/ResultViewModel2.kt | 12 ----- app/src/main/res/values-ajp/strings.xml | 8 ---- app/src/main/res/values-ar/strings.xml | 8 ---- app/src/main/res/values-ars/strings.xml | 2 - app/src/main/res/values-as/strings.xml | 8 ---- app/src/main/res/values-bg/strings.xml | 6 --- app/src/main/res/values-bn/strings.xml | 2 - app/src/main/res/values-bp/strings.xml | 8 ---- app/src/main/res/values-cs/strings.xml | 8 ---- app/src/main/res/values-de/strings.xml | 7 --- app/src/main/res/values-el/strings.xml | 7 --- app/src/main/res/values-es/array.xml | 26 ----------- app/src/main/res/values-es/strings.xml | 8 ---- app/src/main/res/values-fr/strings.xml | 8 +--- app/src/main/res/values-hi/strings.xml | 2 - app/src/main/res/values-hr/strings.xml | 8 ---- app/src/main/res/values-hu/strings.xml | 6 --- app/src/main/res/values-in/strings.xml | 7 --- app/src/main/res/values-it/strings.xml | 8 ---- app/src/main/res/values-iw/strings.xml | 6 --- app/src/main/res/values-ja/strings.xml | 3 -- app/src/main/res/values-ko/strings.xml | 7 --- app/src/main/res/values-lt/strings.xml | 5 --- app/src/main/res/values-lv/strings.xml | 6 --- app/src/main/res/values-mk/strings.xml | 7 --- app/src/main/res/values-ml/strings.xml | 2 - app/src/main/res/values-ms/strings.xml | 7 --- app/src/main/res/values-my/strings.xml | 6 --- app/src/main/res/values-nl/strings.xml | 6 --- app/src/main/res/values-nn/strings.xml | 2 - app/src/main/res/values-no/strings.xml | 6 --- app/src/main/res/values-or/strings.xml | 4 -- app/src/main/res/values-pl/array.xml | 26 ----------- app/src/main/res/values-pl/strings.xml | 8 ---- app/src/main/res/values-pt/strings.xml | 7 --- app/src/main/res/values-qt/strings.xml | 7 --- app/src/main/res/values-ro/strings.xml | 7 --- app/src/main/res/values-ru/strings.xml | 7 --- app/src/main/res/values-sk/strings.xml | 2 - app/src/main/res/values-so/strings.xml | 6 --- app/src/main/res/values-sv/strings.xml | 7 --- app/src/main/res/values-ta/strings.xml | 7 --- app/src/main/res/values-tl/strings.xml | 2 - app/src/main/res/values-tr/array.xml | 42 ------------------ app/src/main/res/values-tr/strings.xml | 7 --- app/src/main/res/values-uk/strings.xml | 8 ---- app/src/main/res/values-ur/strings.xml | 6 --- app/src/main/res/values-vi/array.xml | 26 ----------- app/src/main/res/values-vi/strings.xml | 8 ---- app/src/main/res/values-zh-rTW/strings.xml | 7 --- app/src/main/res/values-zh/strings.xml | 8 ---- app/src/main/res/values/array.xml | 44 ------------------- app/src/main/res/values/strings.xml | 8 ---- 61 files changed, 56 insertions(+), 483 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt rename app/src/main/java/com/lagradost/cloudstream3/actions/temp/{TestAction.kt => ViewM3U8Action.kt} (80%) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt index 45e61f2a8..99c1ac38b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt @@ -20,7 +20,6 @@ import com.lagradost.cloudstream3.ui.result.ResultFragment import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.AppContextUtils.isAppInstalled -import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStoreHelper import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -112,15 +111,11 @@ abstract class OpenInAppAction( CoroutineScope(Dispatchers.IO).launch { activityResultLauncher?.launch(intent) } + } catch (_: ActivityNotFoundException) { + showToast(R.string.app_not_found_error, Toast.LENGTH_LONG) } catch (t: Throwable) { logError(t) - main { - if (t is ActivityNotFoundException) { - showToast(txt(R.string.app_not_found_error), Toast.LENGTH_LONG) - } else { - showToast(t.toString(), Toast.LENGTH_LONG) - } - } + showToast(t.toString(), Toast.LENGTH_LONG) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt index a19553901..f66ed74d9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -3,12 +3,13 @@ package com.lagradost.cloudstream3.actions import android.app.Activity import android.content.Context import com.lagradost.api.Log +import com.lagradost.cloudstream3.actions.temp.CopyClipboardAction import com.lagradost.cloudstream3.actions.temp.MpvKtPackage import com.lagradost.cloudstream3.actions.temp.MpvKtPreviewPackage import com.lagradost.cloudstream3.actions.temp.MpvPackage import com.lagradost.cloudstream3.actions.temp.MpvYTDLPackage import com.lagradost.cloudstream3.actions.temp.PlayInBrowserAction -import com.lagradost.cloudstream3.actions.temp.TestAction +import com.lagradost.cloudstream3.actions.temp.ViewM3U8Action import com.lagradost.cloudstream3.actions.temp.VlcPackage import com.lagradost.cloudstream3.actions.temp.WebVideoCastPackage import com.lagradost.cloudstream3.actions.temp.fcast.FcastAction @@ -21,7 +22,8 @@ import kotlin.reflect.jvm.jvmName object VideoClickActionHolder { val allVideoClickActions = threadSafeListOf( - PlayInBrowserAction(), VlcPackage(), TestAction(), + PlayInBrowserAction(), CopyClipboardAction(), + VlcPackage(), ViewM3U8Action(), MpvPackage(), MpvYTDLPackage(), WebVideoCastPackage(), MpvKtPackage(), MpvKtPreviewPackage(), FcastAction() diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt new file mode 100644 index 000000000..e054b5ce2 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt @@ -0,0 +1,27 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.content.Context +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper + +class CopyClipboardAction: VideoClickAction() { + override val name = txt("Copy to clipboard") + + override val oneSource = true + + override fun shouldShow(context: Context?, video: ResultEpisode?) = true + + override fun runAction( + context: Context?, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + if (index == null) return + val link = result.links.getOrNull(index) ?: return + clipboardHelper(txt(link.name), link.url) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt index 39b689398..de32bb4b3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.actions.temp import android.content.Context import android.content.Intent import android.net.Uri +import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.result.LinkLoadingResult @@ -11,7 +12,7 @@ import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.ExtractorLinkType class PlayInBrowserAction: VideoClickAction() { - override val name = txt("Play in browser") + override val name = txt(R.string.episode_action_play_in_format, "Browser") override val oneSource = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/TestAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt similarity index 80% rename from app/src/main/java/com/lagradost/cloudstream3/actions/temp/TestAction.kt rename to app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt index c825202a1..c14168e96 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/TestAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt @@ -2,14 +2,17 @@ package com.lagradost.cloudstream3.actions.temp import android.content.Context import android.content.Intent +import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.actions.makeTempM3U8Intent import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.txt -class TestAction: VideoClickAction() { - override val name = txt("Test action") +class ViewM3U8Action: VideoClickAction() { + override val name = txt(R.string.episode_action_play_in_format, "m3u8 player") + + override val isPlayer = true override fun shouldShow(context: Context?, video: ResultEpisode?) = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt index ec824d4eb..ecb37fdc6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt @@ -5,12 +5,14 @@ import android.content.Context import android.content.Intent import android.os.Build import com.lagradost.api.Log +import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.actions.OpenInAppAction import com.lagradost.cloudstream3.actions.makeTempM3U8Intent import com.lagradost.cloudstream3.actions.updateDurationAndPosition import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos // https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898 @@ -47,6 +49,14 @@ class VlcPackage: OpenInAppAction( intent.putExtra("from_start", false) intent.putExtra("position", position) intent.putExtra("secure_uri", true) + intent.putExtra("title", video.name) + + val subsLang = getKey(SUBTITLE_AUTO_SELECT_KEY) ?: "en" + result.subs.firstOrNull { + subsLang == it.languageCode + }?.let { + intent.putExtra("subtitles_location", it.url) + } } override fun onResult(activity: Activity, intent: Intent?) { @@ -55,5 +65,4 @@ class VlcPackage: OpenInAppAction( Log.d("VLC", "Position: $position, Duration: $duration") updateDurationAndPosition(position, duration) } - } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt index f2f7621e8..c0f92e4df 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.actions.temp.fcast import android.content.Context import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.ui.result.LinkLoadingResult @@ -13,7 +14,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog class FcastAction: VideoClickAction() { - override val name = txt("Fcast") + override val name = txt("Fcast to device") override val oneSource = true @@ -36,7 +37,7 @@ class FcastAction: VideoClickAction() { context?.getActivity()?.showBottomDialog( devices.map { it.name }, -1, - "Select device", + txt(R.string.player_settings_select_cast_device).asString(context), false, {}) { val position = getViewPos(video.id)?.position @@ -52,7 +53,7 @@ class FcastAction: VideoClickAction() { session.sendMessage( Opcode.Play, PlayMessage( - "application/vnd.apple.mpegurl", + "video/*", link.url, time = position?.let { it / 1000.0 }, headers = mapOf( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 0c754f513..2dd8e2ab4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -44,7 +44,6 @@ const val ACTION_DOWNLOAD_EPISODE = 6 const val ACTION_DOWNLOAD_MIRROR = 7 const val ACTION_RELOAD_EPISODE = 8 -const val ACTION_COPY_LINK = 9 const val ACTION_SHOW_OPTIONS = 10 diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index a330d5638..b5f83201e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -1406,7 +1406,6 @@ class ResultViewModel2 : ViewModel() { options.addAll( listOf( - txt(R.string.episode_action_copy_link) to ACTION_COPY_LINK, txt(R.string.episode_action_auto_download) to ACTION_DOWNLOAD_EPISODE, txt(R.string.episode_action_download_mirror) to ACTION_DOWNLOAD_MIRROR, txt(R.string.episode_action_download_subtitle) to ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR, @@ -1570,17 +1569,6 @@ class ResultViewModel2 : ViewModel() { } } - ACTION_COPY_LINK -> { - acquireSingleLink( - click.data, - LOADTYPE_ALL, - txt(R.string.episode_action_copy_link) - ) { (result, index) -> - val link = result.links[index] - clipboardHelper(txt(link.name), link.url) - } - } - ACTION_CHROME_CAST_EPISODE -> { startChromecast(activity, click.data) } diff --git a/app/src/main/res/values-ajp/strings.xml b/app/src/main/res/values-ajp/strings.xml index 7a70ccc6e..aa861bd47 100644 --- a/app/src/main/res/values-ajp/strings.xml +++ b/app/src/main/res/values-ajp/strings.xml @@ -221,9 +221,7 @@ بسبِب أعطال إزا نحط على مستوى عالي كتير على الأجهزة يللي م بتساع كتير، متل تلفزيون \"أندرويد\". شي غير أفي هيدا التجديد - نسوخ الرابط مَشي بال آپ - مشي بمتصفح الويب مفيد لتجاوز المنع من مزود خدمة الإنترنت مسلسل غير الحجم @@ -390,10 +388,8 @@ م قدرنا ننزل الإصدار الجديد تبع الآپ المؤلفين إضافة - كاست ڤيديو ع الوَب معقول يكون موجود أصلًا مشتركينلو - متصفح الوَب كل اللغات دايمًا كتوب ب أحرف كاپيتال، A بدل a مشغل الڤيديو المفضل @@ -422,7 +418,6 @@ خلصت محي السجل تجَدَد (من قديم للجديد) - \"ڤي أل سي ميديا پلاير\" كاميرا وَب أبجديًا (من الياء للألف) @@ -504,7 +499,6 @@ مزامنة شوفو معلومات عن المشكلة مدعوم - \"أم پي ڤي\" ظبّط وقت الترجمة افتتاح مختلط مقطع دعائي @@ -627,7 +621,6 @@ رح ينزل ب %s الحلقة ال %2$d من الجزء ال%1$d رح تنزل ب كاست مراية - إف كاست نقي جهاز الكاست ويكي \"كلود ستريم\" أكونتات @@ -669,6 +662,5 @@ \n%s صورة زغيرة مع التقريب وال تبعيد بت حط صورة زغير من الڤيديو إنت و عم بت قرب أو ترجع بال ڤيديو - MPV YTDL بعد مش معمول لود لولا ترجمة \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 33a1172ca..d4958b6d3 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -233,8 +233,6 @@ مرآة كروم كاست تشغيل في التطبيق %s تشغيل في - تشغيل في الويب - نسخ الرابط التحميل التلقائي تحميل بجودات مختلفة إعادة تحميل الروابط @@ -464,10 +462,6 @@ فتح(تشغيل) %1$s %2$d%3$s المكونات الإضافية المحدثة %d - VLC - MPV - اسقاط فيديو الويب - متصفح الإنترنت تخطي %s الافتتاح النهاية @@ -653,7 +647,6 @@ قادم خلال %s سيتم إصدار الحلقة %1$d من الموسم %2$d في مرآة البث - بث ف حدد جهاز البث CloudStream ويكي إعدادات الأمان @@ -695,6 +688,5 @@ \n%s معاينة شريط البحث تمكين معاينة الصورة المصغرة على شريط البحث - MPV YTDL لم يتم تحميل أي ترجمات بعد \ No newline at end of file diff --git a/app/src/main/res/values-ars/strings.xml b/app/src/main/res/values-ars/strings.xml index 873c55bf3..d1efdbbc5 100644 --- a/app/src/main/res/values-ars/strings.xml +++ b/app/src/main/res/values-ars/strings.xml @@ -311,11 +311,9 @@ اخرون تخطي هذا التحديث .قد يتسبب في تأخير التحديثات لبضعة أيام .jsDelivr باستخدام GitHubيتجاوز حظر - انسخ الرابط الدرامات الآسيوية في قائمة الانتظار افتح في التطبيق - افتح في المتصفح مفيد لتجاوز حجب مزودي خدمة الإنترنت مسلسل تقييم diff --git a/app/src/main/res/values-as/strings.xml b/app/src/main/res/values-as/strings.xml index 0ed41795c..b001ab768 100644 --- a/app/src/main/res/values-as/strings.xml +++ b/app/src/main/res/values-as/strings.xml @@ -89,7 +89,6 @@ সম্পূৰ্ণ সৰ্বজনীন তালিকা বন্ধ কৰক - VLC বেটাৰী অপ্টিমাইজেচন নিষ্ক্ৰিয় কৰক সদস্যতা গ্ৰহণ কৰা গুণসমূহ @@ -302,8 +301,6 @@ কাষ্ট মিৰৰ ডাব ছপা প্লে %s ত - ব্ৰাউজাৰত প্লে কৰক - লিংক কপি কৰক স্বয়ংক্ৰিয় ডাউনলোড ডাউনলোড মিৰৰ সাব ছপা @@ -498,10 +495,6 @@ HLS প্লেলিস্ট পছন্দৰ ভিডিঅ\' প্লেয়াৰ আভ্যন্তৰীণ প্লেয়াৰ - MPV - ৱেব ভিডিঅ\' কাষ্ট - Fcast - ৱেব ব্ৰাউজাৰ কাষ্ট ডিভাইচ চয়ন কৰক মিশ্ৰিত সমাপ্তি মিশ্ৰিত উদ্‌ঘাটনী @@ -650,7 +643,6 @@ ডিভাইচ পিন ক\'ড পোৱা নাই, স্থানীয় প্ৰমাণীকৰণ চেষ্টা কৰক পিন ক\'ডৰ মেয়াদ শেষ হৈছে! ক\'ড মেয়াদ শেষ হব %1$dm %2$ds - MPV YTDL সীকবৰ প্ৰিভিউ সীকবৰত প্ৰিভিউ থাম্বনেইল সক্ৰিয় কৰক আৰম্ভৰ পৰা প্লে কৰক diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 3579c8353..218fa5e57 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -238,8 +238,6 @@ Chromecast огледало Пусни в приложението Пусни в %s - Пусни в браузър - Копирай връзка Автоматично изтегляне Изтегляне на огледало Презареждане на връзки @@ -443,10 +441,6 @@ HLS плейлист Предпочитан видео плеър Вътрешен плеър - VLC - MPV - Уеб видео предаване - Уеб браузър Приложението не е намерено Автоматично изтегли добавки Всички езици diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index c5ed86514..f1bc8f82a 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -230,7 +230,6 @@ প্লাগইন ডাউনলোড ফিল্টার করতে মোড নির্বাচন করুন লিঙ্ক পুনরায় লোড হয়েছে সুইচ অ্যাকাউন্ট - ব্রাউজারে প্লে করুন দাবিত্যাগ এশিয়ান ড্রামা সোর্স @@ -274,7 +273,6 @@ টরেন্ট এপিসোড ক্রোমকাস্ট করুন প্লে হচ্ছে %s সময়ের মধ্যে - লিঙ্ক কপি করুন স্বয়ংক্রিয় ডাউনলোড টাইটেল প্লেয়ার দেখা যাচ্ছে - সিকের পরিমাণ diff --git a/app/src/main/res/values-bp/strings.xml b/app/src/main/res/values-bp/strings.xml index 0ad12bdf4..a39c4e208 100644 --- a/app/src/main/res/values-bp/strings.xml +++ b/app/src/main/res/values-bp/strings.xml @@ -235,8 +235,6 @@ Alternativa pelo Chromecast Assistir no App Assistir no %s - Assistir no navegador - Copiar link Auto download Baixar por servidor alternativo Recarregar links @@ -538,16 +536,13 @@ Começar Suportado Status - MPV Abrindo mistura - VLC Reinicie o aplicativo para ver as alterações. Visualização info de crash Faixas de áudio Adicionado em (novo para antigo) Faixas de video Legendas - Navegador 18+ Links Funcionalidades do Player @@ -563,7 +558,6 @@ Vídeo Android TV Wi-Fi - Lista de videos da web A interface de usuário não foi gerada corretamente. Isto se trata de um bug importante e deve ser reportado imediatamente %s Características da interface de usuário Provedor de teste @@ -642,7 +636,6 @@ Redefinir Próximos em %s Temporada %1$d Episódio %2$d será lançado em - Fcast Selecione o dispositivo de transmissão Espelhar transmissão CloudStream Wiki @@ -685,6 +678,5 @@ Desmarcar todos Ativar visualização de miniatura na barra de busca Visualização da barra de busca - MPV + YTDL Ainda não há legendas carregadas \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 414f26440..6e6d3b2a1 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -223,8 +223,6 @@ Chromecast jako zrcadlo Přehrát v aplikace Přehrát ve %s - Přehrát v prohlížeči - Zkopírovat odkaz Automaticky stáhnout Zrcadlo stahování Obnovit odkazy @@ -400,8 +398,6 @@ Veřejný seznam Velká písmena u všech titulků Playlist HLS - MPV - Webové vysílání videa Aplikace nenalezena Přeskočit %s Úvod @@ -451,13 +447,11 @@ Popis Stav Nejprve nainstalujte rozšíření - VLC Smíšený konec Jazyk Interní přehrávač Rekapitulace Vymazat historii - Webový prohlížeč Všechny jazyky Smíšený úvod Poděkování @@ -645,7 +639,6 @@ Vychází %s Epizoda %2$d ze série %1$d bude vydána za Vysílat zrcadlení - Fcast Vyberte zařízení k vysílání CloudStream Wiki Zabezpečení @@ -687,6 +680,5 @@ Odstranit (%1$d | %2$s) Náhled v liště přehrávače Povolit náhled miniatur na liště přehrávače - MPV YTDL Zatím nenačteny žádné titulky \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c9656d9de..6cf8ff3bd 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -241,8 +241,6 @@ Downloadfehler, bitte überprüfen sie die Speicherberechtigungen Chromecast-Episode In %s wiedergeben - In Browser wiedergeben - Link kopieren Auto-Download Alternativer Download Links neu laden @@ -437,10 +435,6 @@ HLS-Playlist Bevorzugter Videoplayer Interner Player - VLC - MPV - Web Video Cast - Browser App nicht gefunden Alle Sprachen Überspringen %s @@ -618,7 +612,6 @@ hide_player_control_names_key Staffel %1$d Episode %2$d wird veröffentlicht in Wird veröffentlicht in %s - Fcast Sicherheit Konten Repository öffnen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index e22182d23..268ea7bd1 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -194,8 +194,6 @@ Chromecast επεισόδιο Αναπαραγωγή εντός της εφαρμογής Αναπαραγωγή σε %s - Αναπαραγωγή στον περιηγητή - Αντιγραφή συνδέσμου Αυτόματη λήψη Λήψη mirror Επαναφόρτωση συνδέσμων @@ -359,10 +357,6 @@ HLS Playlist Προτεινόμενο πρόγραμμα αναπαραγωγής Ενσωματωμένο πρόγραμμα αναπαραγωγής - VLC - MPV - Web Video Cast - Περιηγητής Η εφαρμογή δεν βρέθηκε %1$s Επ %2$d Το επεισόδιο %d θα κυκλοφορήσει σε @@ -580,7 +574,6 @@ Δευτερόλεπτα Σκιπ όταν φαίνεται ο αναπαραγωγέας (πλειερ) Δοκιμή όλων των παροχών Αυτό το τεστ προορίζεται μόνο για τους προγραμματιστές και δε επαληθείει ούτε απορρίπτει την λειτουργία οποιουδήποτε παρόχου. - Fcast Επιλογή συσκευής για αναμετάδοση Πρόβλημα στην πρόσβαση στο Clipboard, Παρακαλώ προσπαθήστε ξανά. Πρόβλημα στην αντιγραφή , Παρακαλούμε αντιγράψτε το logcat και επικοινωνήστε με την υποστήριξη. diff --git a/app/src/main/res/values-es/array.xml b/app/src/main/res/values-es/array.xml index eb197f43e..fddd832a5 100644 --- a/app/src/main/res/values-es/array.xml +++ b/app/src/main/res/values-es/array.xml @@ -152,32 +152,6 @@ @string/show_title_key - - @string/episode_action_chromecast_episode - @string/episode_action_chromecast_mirror - @string/episode_action_play_in_app - @string/episode_action_play_in_format - @string/episode_action_play_in_browser - @string/episode_action_copy_link - @string/episode_action_auto_download - @string/episode_action_download_mirror - @string/episode_action_download_subtitle - @string/episode_action_reload_links - - - - 4 - 5 - 1 - 2 - 3 - 9 - 6 - 7 - 13 - 8 - - @string/automatic @string/phone_layout diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ccca67b36..2ec81cf28 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -57,11 +57,7 @@ Cantidad de búsquedas del reproductor (segundos) Use el brillo del sistema en el reproductor de la app en lugar de una superposición oscura Resolución del reproductor de video - MPV Reproductor - VLC - Web Video Cast - Navegador Web Iniciar el siguiente episodio cuando el actual termine Omitir Intro Apertura @@ -78,9 +74,7 @@ Actualizar progreso de lo visto Duplicar en Chromecast No se encontraron Episodios - Reproducir en Navegador Reproducir en %s - Copiar enlace Descarga automática Descargar desde servidor alternativo Recargar enlaces @@ -621,7 +615,6 @@ Próximamente en %s La temporada %1$d y el episodio %2$d se estrenarán en Seleccionar el dispositivo para transmitir - Fcast Espejo de transmisión Wiki de CloudStream Seguridad @@ -663,6 +656,5 @@ \n%s Activar la previsualización para las miniaturas en la barra de búsqueda Previsualización de Seekbar - MPV YTDL Aún no hay subtítulos cargados \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 09746f1f8..0d40ce6ae 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -111,8 +111,6 @@ Miroir Chromecast Lecture dans l\'application Lecture dans %s - Lecture dans le navigateur - Copier le lien Téléchargement automatique Télécharger depuis le miroir Recharger les liens @@ -140,7 +138,7 @@ DNS avec HTTPS Afficher les animés en Anglais (Dub) / sous-titrés Disposition en mode téléphone - %1$s Episode %2$d + episode_action_copy_link Note : %.1f Zoom Adapter à l\'écran @@ -430,12 +428,10 @@ Installer l\'extension d\'abord Playlist HLS Lecteur vidéo préféré - VLC Fin mitigée Introduction mitigée Installation de la mise a jour de l\'application… Impossible d\'installer la nouvelle version de l\'application - Navigateur Web Certains téléphones ne supporte pas le nouvel installateur d\'application. Essayez l\'option de l\'ancien installateur si les mises-à-jour ne s\'installe pas. Précédent Ignorer la configuration @@ -456,7 +452,6 @@ Lecteur interne Application introuvable Trop de texte. Impossible de sauvegarder dans le presse papier. - MPV Installateur de paquet plugins Cela supprimera également tous les plugins du repository @@ -466,7 +461,6 @@ Langage Afficher les popups skip pour les intro / fins Ancienne méthode d\'installation - Web Video Cast Liens Gestes Fonctionnalités du lecteur diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index eed5e44aa..4457b0ec4 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -114,8 +114,6 @@ क्रोमकास्ट मिरर एप्प में चलाएं %s में चलाएं - Browser में चलाएं - लिंक कॉपी करें डाउनलोड करें मिरर डाउनलोड लिंक दोबारा लोड करें diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 44f1bd5b9..dcacbeee3 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -253,8 +253,6 @@ Chromecast mirror Pokreni u aplikaciji Pokreni u %s - Pokreni u pregledniku - Kopiraj poveznicu Automatsko preuzimanje Preuzmi zrcalo Ponovo učitaj poveznice @@ -461,9 +459,6 @@ Preferirani video player Interni player Najprije instaliraj proširenje - VLC - MPV - Emitiranje na webu Aplikacija nije pronađena Svi jezici Previše teksta. Nije moguće spremiti u međuspremnik. @@ -495,7 +490,6 @@ Zadane postavke Izgledi Značajke - Web preglednik Preskoči %s Kraj Sažetak @@ -646,7 +640,6 @@ Vaši CloudStream podaci su sada spremljeni u sigurnosnu kopiju. Iako je vjerojatnost mala, neki se uređaji mogu ponašati drugačije. Ako izgubite pristup aplikaciji, potpuno izbrišite podatke aplikacije i obnovite ih pomoću sigurnosne kopije. Ispričavamo se zbog mogućih neugodnosti. Sezona %1$d epizoda %2$d izlazi Cast mirror - Fcast Odaberi uređaj za emitiranje CloudStream Wiki Računi @@ -686,6 +679,5 @@ Stvarno želite trajno izbrisati sve epizode u sljedećoj seriji? \n \n%s - MPV YTDL Još nije učitan nijedan titl \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 1426e8a38..f124ad062 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -182,7 +182,6 @@ Dokumentumfilm Ázsiai dráma Linkek újratöltése - Link másolás Letöltés mirror Automatikus letöltés Adatok eltárolva @@ -227,7 +226,6 @@ Chromecast mirror Lejátszás az alkalmazásban Lejátszás %s - Lejátszás böngészőben Feliratok letöltése Újracsatlakozás… Húzd balra vagy jobbra a videólejátszóban az idő vezérléséhez @@ -352,7 +350,6 @@ Mit szeretnél látni Minden %s már letöltött Először telepítse a bővítményt - Webböngésző Kinézet Alkalmazás elrendezés Szinkronizálás @@ -368,7 +365,6 @@ Töltse le az összes bővítményt ebből a tárolóból? Biztonságos mód bekapcsolva Méret - MPV Alkalmazás nem található PackageInstaller Rendezés e szerint @@ -403,7 +399,6 @@ Emelt HD HLS lejátszási lista - VLC Nem sikerült telepíteni az alkalmazás új verzióját %s hitelesítve Körvonal @@ -528,7 +523,6 @@ Hivatkozó (opcionális) Nem találhatóak pluginek a repóban Repó nem található, ellenőrizze a címet vagy próbálja VPN-el - Web Videó Cast %s kihagyása A kihagyási felugró ablakok mutatása nyitás/zárás esetén Alapbeállítás diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 22203b398..9255c459e 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -221,8 +221,6 @@ Mirror Chromecast Putar di aplikasi Putar di %s - Putar di browser - Salin tautan Download otomatis Download mirror Muat ulang tautan @@ -442,8 +440,6 @@ Bahasa Pemutar video utama Pemutar Bawaan - VLC - MPV Terunduh %1$d %2$s Memulai mengunduh %1$d %2$s… Semua fitur tambahkan dimatikan karena crash, untuk memudahkanmu mencari penyebab crash. @@ -495,9 +491,7 @@ Tidak Memasang pembaruan… Tidak dapat memasang versi terbaru - Web browser Aplikasi tidak ditemukan - Web Video Cast Hapus Riwayat Tampilkan popup untuk skip sesi pembuka/akhir Mengunduh pembaruan… @@ -643,7 +637,6 @@ Akan datang di %s Cermin Cast Pilih perangkat cast - Fcast CloudStream Wiki Keamanan Akun diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 3e7636ce5..5902cd69e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -243,8 +243,6 @@ Mirror Chromecast Riproduci in app Riproduci in %s - Riproduci nel browser - Copia link Download Mirror download Aggiorna link @@ -448,10 +446,6 @@ Playlist HLS Video player preferito Player interno - VLC - MPV - Cast Web Video - Web browser App non trovata Tutte le lingue Salta %s @@ -642,7 +636,6 @@ L\'episodio %2$d della stagione %1$d uscirà tra Mirror cast Seleziona dispositivo per cast - Fcast Wiki di CloudStream Conti Sicurezza @@ -683,6 +676,5 @@ \n%s Anteprima barra di ricerca Abilita miniatura di anteprima sulla barra di ricerca - MPV YTDL Nessun sottotitolo caricato \ No newline at end of file diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 6316d7f79..6ebe405bc 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -168,7 +168,6 @@ דרמה אסייתית כרומקאסט את הפרק כרומקאסט את המראה - נגן בדפדפן תווית כתוביות החלף רכיבי ממשק משתמש בפוסטר דלג על הפתיח @@ -326,7 +325,6 @@ שגיאת הורדה, בדוק הרשאות אחסון נגן באפליקציה נגן ב %s - העתק קישור הורדה אוטומטית טען מחדש קישורים תווית איכות @@ -431,7 +429,6 @@ כל %s כבר הורד מחברים שפה - MPV קרדיטים מיין בחר ספרייה @@ -445,8 +442,6 @@ הורד את כל התוספים ממאגר זה? רצועות שמע מסלולים - Web Video Cast - דפדפן אינטרנט כל התוספים נכבו עקב התרסקות כדי לעזור לך למצוא את האחד הגורם לצרות. מוריד החבילות עודכן %d תוספים @@ -479,7 +474,6 @@ פלייליסט HLS נגן וידאו מועדף נגן פנימי - VLC האפליקציה לא נמצאה כל השפות דלג %s diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index ed9850e8e..3ca79b3d5 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -144,9 +144,6 @@ 完成 進行中 デフォルト - ウェブブラウザ - VLC - MPV 言語 作成者 サイズ diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 6993ec1c9..d62ecb69b 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -186,8 +186,6 @@ Chromecast 미러링 앱에서 재생 %s에서 재생 - 브라우저에서 재생 - 링크 복사 자동 다운로드 다운로드 미러 링크 새로고침 @@ -445,7 +443,6 @@ 소개 유형 먼저 확장 프로그램을 설치하세요 - 웹 브라우저 앱을 찾을 수 없음 모든 언어 건너뛰기 %s @@ -482,11 +479,8 @@ \n파일이 제거될 때까지 시작 시 확장 프로그램을 로드하지 않습니다. HLS 재생목록 내부 플레이어 - MPV 선호하는 동영상 플레이어 - VLC 라이브러리 선택 - 웹 동영상 캐스트 이 목록이 비어 있습니다. 다른 목록으로 전환해 보세요. 필러 라이브 스트리밍 재생 @@ -579,7 +573,6 @@ 자동 회전 모바일 데이터 사용 불가능 - fcast 캐스트 장치 선택 복사하는 중 오류가 발생했습니다. 로그캣을 복사하고 문의하십시오. 구독 취소 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index fe205dab7..cc68d77eb 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -163,10 +163,8 @@ Įvertinta Praleisti šį atnaujinimą Veiksmai - Kopijuoti nuorodą Paleisti programoje Sinchronizuoti - Paleisti naršyklėje Pašalinti puslapį Perkrauti nuorodos Išjungti @@ -192,7 +190,6 @@ Mobilūs duomenys šaunusPrisijungimoVardas Autoriai - Naršyklė Visos kalbos 4K Pradėta siųsti %1$d %2$s… @@ -206,7 +203,6 @@ Kalbos kodas (lt) Baigta Išvalyti istoriją - VLC Redaguoti Wi-Fi Greitai būs… @@ -227,7 +223,6 @@ UHD Dydis Palaikoma - MPV ManoŠaunusPuslapis Anonsas Istorija diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 8f13b93c0..9469aa8e8 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -241,8 +241,6 @@ Chromecast morror Palaist aplikācijā Atskaņot uekšā %s - Atskaņot internetā - Kopēt linku Automātiski ielādēt Ielādēt spoguli Pārlādēt saites @@ -437,8 +435,6 @@ HLS atskaņošanas saraksts Vēlamais video atskaņotājs Iekšējais atskaņotājs - MPV - Web video apraide Aplikācijs nav atrasta Visas valodas Beigas @@ -513,8 +509,6 @@ Lejupielādējiet to vietņu sarakstu, kuras vēlaties izmantot Vispirms instalējiet paplašinājumu Atvēršana - VLC - Interneta mekletājs Sākums Izlaist %s Noņemt no skatītajiem diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 671257492..79dc1ee7e 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -164,8 +164,6 @@ Огледало на Chromecastr Пушти во апликацијата Пушти на %s - Пушти на прелистувач - Копирај линк Авто превземање Превземи Mirror Вчитај повторно врски @@ -297,7 +295,6 @@ Износот на барањето што се користи кога плеерот е скриен Преземи преводи Јавна листа - MPV Инсталатор на пакети ОВА Ажурирање и резервна копија @@ -425,7 +422,6 @@ /?? hello@world.com +30 - VLC Рестартирај Цртан филм Почна да презема %1$d %2$s… @@ -448,7 +444,6 @@ Камера Камера SDR - Веб-прелистувач Апликацијата не е пронајдена Корисничко име Отвори со @@ -488,7 +483,6 @@ Сите екстензии беа исклучени поради пад за да ви помогнат да ја пронајдете онаа што предизвикува проблеми. Оцена: %s Големина - Веб-видео Cast Сите јазици Исчисти историја Обележи како гледано @@ -611,7 +605,6 @@ Сега е направена резервна копија на вашите податоци на CloudStream. Иако можноста за ова е многу мала, сите уреди можат да се однесуваат поинаку. Во ретки случаи, кога ќе се заклучите од пристап до апликацијата, целосно исчистете ги податоците на апликацијата и вратете ги од резервна копија. Многу ни е жал за какви било непријатности што произлегуваат од ова. Ресетирај Сезона %1$d Епизода %2$d ќе биде објавена за - Fcast Одбери уред да кастираш Оневозможи оптимизација на батерија Отклучи CloudStream diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 1c2d855e5..bf0fede72 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -151,8 +151,6 @@ Chromecast Mirror --> ആപ്പിൽ പ്ലേയ് ചെയ്യുക %sയിൽ പ്ലേയ് ചെയ്യുക - ബ്രൗസറിൽ പ്ലേയ് ചെയ്യുക - ലിങ്ക് പകർത്തുക ഡൌൺലോഡ് ചെയ്യൂ മിറർ ഡൗണ്ലോഡ് ലിങ്ക്സ് വീണ്ടും ലോഡുചെയ്യുക diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 07154776d..e865d58db 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -2,7 +2,6 @@ Semua bahasa Langkau %s - Pelayar web Sejarah Kosongkan sejarah Pengenalan @@ -232,7 +231,6 @@ Dinaikkan Lihat video dalam bahasa-bahasa ini Normal - Main dalam pelayar Tambah Diguna Anime @@ -323,7 +321,6 @@ Chromecast episod Main dalam %s Muat turun gagal, cek keizinan storan - Salin pautan Muat turun cermin Muat turun sari kata Label sub @@ -449,8 +446,6 @@ Pengesahan Password/PIN Sari kata belum tetapkan lagi Disokong - MPV - Fcast Ralat tidak dapat akses Clipboard, Sila cuba sekali lagi. Ralat menyalin, Sila salin logcat dan hubungi penyokong aplikasi. Amaran @@ -462,8 +457,6 @@ Maks Amaran: CloudStream 3 tidak bertanggungjawab atas penggunaan tambahan pihak ketiga dan tidak memberi sumbang kepada mereka! Mula semula aplikasi untuk lihat perubahan. - VLC - MPV YTDL Senarai ini kosong. Sila tukar yang lain. Data mudah alih Tambah ke kegemaran diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index 31e6ef276..9d82dd47d 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -157,8 +157,6 @@ Chromecast ဖန်သားပြင် အက်ပ်တွင်းဖွင့် ဖွင့်ရန် %s - ဘရောက်ဇာထဲမှာ ဖွင့်ရန် - လင့်ကူးယူရန် အလိုအလျောက်ဒေါင်းလုဒ် လင့်များကို ပြန်စစ်ရန် အရည်အသွေး အမှတ်အသား @@ -338,7 +336,6 @@ နောက်သို့ အပ်ဒိတ်လုပ်ပြီး %d ဖြည့်စွက်များ ဒေါင်းလုဒ်မလုပ်ရသေး: %d - ဝဘ်ဘရောက်ဇာ အက်ပ်မတွေ့ပါ ဘာသာစကားအားလုံး ကျော်ရန် %s @@ -422,9 +419,6 @@ ထောက်ပံ့ထားသော ဘာသာစကား အဆက်များကိုအရင်သွင်းပါ - VLC - MPV - ဝဘ်ထဲတွင်ဖွင့်ရန် အစပိုင်း အဆုံးပိုင်း ကြည့်ရှုခဲ့သည်များကိုရှင်းရန် diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 4bef20cc2..6518eb0e7 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -235,8 +235,6 @@ Chromecast mirror Speel in app Speel in %s - Speel in browser - Kopieer link Automatisch downloaden Download mirror Herlaad Linkss @@ -409,7 +407,6 @@ Kan %s niet laden Alle %s reeds gedownload plugin - VLC Sla %s over Links App updates @@ -468,10 +465,7 @@ Herhaal installatieproces Automatisch bijwerken plugin Sommige telefoons ondersteunen het nieuwe installatieprogramma niet. Probeer de oude optie als de updates niet worden geïnstalleerd. - Web Video Cast Interne speler - MPV - Web browser Gemengd einde Herhaling Start diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index 33ebe1b51..5b5577c2e 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -149,8 +149,6 @@ Kjeldefeil Spel av i programmet Spel av i %s - Spel av i nettlesaren - Kopier lenke Automatisk nedlasting Last inn lenker på nytt Last ned undertekstar diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 41117f104..ac13f57f9 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -171,8 +171,6 @@ Støpt Speil Spill i appen Spill i %s - Spill i nettleseren - Kopier link Automatisk nedlasting Last ned speil Last inn lenker på nytt @@ -250,7 +248,6 @@ HLS-spilleliste Foretrukket videospiller Intern spiller - VLC Alle språk Hopp over %s Tøm historikk @@ -283,7 +280,6 @@ Beskrivelse Legg til konto Normal - MPV Dobbelttrykk for å sette på pause +30 Skygge @@ -416,7 +412,6 @@ Kunne ikke logge inn på %s Store bokstaver i undertekster Utviklere - Nettleser Sensurerbart Vev Lenke til strøm @@ -450,7 +445,6 @@ Fjern unødvendig informasjon fra undertekster Ekstra Filtrer etter foretrukket mediaspråk - Vev-videosending Tilbakeblikk SD Forfilm diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index a9cff7edf..1a7d9c72f 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -28,14 +28,12 @@ ଟି ଅଧ୍ୟାୟ ଅଧ୍ୟାୟ %s‌ରେ ଚଲାଅ - ବ୍ରାଉଜର୍‌ରେ ଚଲାଅ ଉପଶୀର୍ଷକ ଡାଉନଲୋଡ୍ କରିବା /%d /?? ଅଧ୍ୟାୟ %d ମୁକ୍ତିଲାଭ କଲା! ସ୍ୱତଃ ଡାଉନଲୋଡ୍ ଲିଙ୍କ୍‌ଗୁଡ଼ିକୁ ପୁନଃଲୋଡ୍ କରିବା - ଲିଙ୍କ୍ କପି କରିନେବା ଆପ୍‌ରେ ଚଲାଅ Chromecast ଅଧ୍ୟାୟ @@ -61,8 +59,6 @@ ପ୍ରାନ୍ତ ଆପ୍ ମିଳିଲା ନାହିଁ ସବୁ ଭାଷା - VLC - MPV ମିଶ୍ରିତ ପ୍ରାନ୍ତ ମିଶ୍ରିତ ଆଦ୍ୟ ଶ୍ରେୟ diff --git a/app/src/main/res/values-pl/array.xml b/app/src/main/res/values-pl/array.xml index a43d7bcfe..45a8e56e1 100644 --- a/app/src/main/res/values-pl/array.xml +++ b/app/src/main/res/values-pl/array.xml @@ -161,32 +161,6 @@ @string/show_title_key - - @string/episode_action_chromecast_episode - @string/episode_action_chromecast_mirror - @string/episode_action_play_in_app - @string/episode_action_play_in_format - @string/episode_action_play_in_browser - @string/episode_action_copy_link - @string/episode_action_auto_download - @string/episode_action_download_mirror - @string/episode_action_download_subtitle - @string/episode_action_reload_links - - - - 4 - 5 - 1 - 2 - 3 - 9 - 6 - 7 - 13 - 8 - - @string/automatic @string/phone_layout diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 0974257b0..f905c5f7b 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -232,8 +232,6 @@ Mirror dla Chromecast Odtwórz w aplikacji Odtwórz w %s - Odtwórz w przeglądarce - Kopiuj link Automatyczne pobieranie Pobierz mirror Odśwież linki @@ -421,10 +419,6 @@ Playlista HLS Preferowany odtwarzacz wideo Odtwarzacz wewnętrzny - VLC - MPV - Web Video Cast - Przeglądarka Aplikacja nie została znaleziona Wszystkie języki Wyczyść historię @@ -621,7 +615,6 @@ Resetuj Nadchodzące w %s Odcinek %2$d sezonu %1$d wyjdzie za - Fcast Wybierz urządzenie do transmisji Mirror transmisji Wiki CloudStream @@ -664,6 +657,5 @@ Usuń (%1$d | %2$s) Podgląd paska przewijania Włącz podgląd miniatury na pasku wyszukiwania - MPV YTDL Nie wczytano jeszcze napisów \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 421746742..fd3fa52d9 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -228,8 +228,6 @@ Alternativa pelo Chromecast Reproduzir na app Reproduzir no %s - Reproduzir no navegador - Copiar link Transferência Automática Transferir por servidor alternativo Recarregar links @@ -441,18 +439,14 @@ Abertura Selecionar Biblioteca Contorna o bloqueio de URLs raw do GitHub usando jsDelivr. Pode atrasar as atualizações por uns dias. - VLC Todas as linguagens Atualizado (Novo para Antigo) Inscrito HDR Reiniciar - Navegador Web Atualizado (Antigo para Novo) - Web Video Cast DVD Instalador de pacotes - MPV Remover dos assistidos Não foi possível instalar a nova versão do aplicativo Inscrição cancelada em %s @@ -617,7 +611,6 @@ Para garantir descarregamentos ininterruptos e notificações de programas de TV subscritos, o CloudStream precisa de permissão para ser executado em segundo plano. Ao premir OK, será direcionado para informações da aplicação. Aí, desloque-se para utilização da bateria da aplicação e defina a utilização da bateria para sem restrições. Tenha em atenção que esta permissão não significa que o CS3 irá esgotar a sua bateria. Este só funcionará em segundo plano quando necessário, como ao receber notificações ou baixar vídeos de extensões oficiais. Se optar por cancelar, pode ajustar esta definição mais tarde em definições gerais. Reiniciar Episódio %1$d Episódio %2$d vai ser lançado em - Fcast Escolha o dispositivo Transmitir hide_player_control_names_key diff --git a/app/src/main/res/values-qt/strings.xml b/app/src/main/res/values-qt/strings.xml index 378e3aaec..8f0e14cbc 100644 --- a/app/src/main/res/values-qt/strings.xml +++ b/app/src/main/res/values-qt/strings.xml @@ -144,8 +144,6 @@ aoohaaahhu ahouuhhh ooo-ahahaauuh aaahhu ooo-ahah ohaauuh %s - ahoha ooo-ahahohoohah oooohh - aauugghhahhaauugghh aaaghhoooohh aaahhu ahooo ohooo-ahahaohaohahhhoouuh ahoooaaahhuahaaahhuoha @@ -318,7 +316,6 @@ aaahh uuuugggh oooohh oooogggoog uuuuhhhaagg - uuh uuh aahh uugg oooogg ag aagg ug ooooggguh ooooggg %d aahh @@ -472,8 +469,6 @@ ooh oooohhhooh oogg oooogg ooh uuh uh g ooogg oh uuhh uug uuhh ooh aah uuuuggg ooooggg ooooggg aaaagggh uuuugg - ooh uuuhh aahh - uuh uuuuhhh aah ooh uuugg uuuuhhh uuuuhh aagg oooohhh @@ -617,8 +612,6 @@ %s (Disabled) Rating: %s aaaagg - oog - uuuhh aaaagg aahh oooohh uuuuuk aaagg aaaahhh diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 8105aa3e8..ec1115112 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -228,8 +228,6 @@ Chromecast alternativ Redă în Aplicație Redă în %s - Redă în Browser - Copiază link-ul Auto-descărcare Descărcă prin Alernativă Reîncărcare link-uri @@ -467,7 +465,6 @@ Nu Abonat la %s Aplicația va fi actualizată la ieșire - Web Video Cast Ocoliri ISP Anterior Sortează @@ -475,7 +472,6 @@ Filtrați în funcție de limba media preferată Episodul %d a fost lansat! Android TV - VLC Urmăriți videoclipuri în aceste limbi Revenire Acțiuni @@ -483,7 +479,6 @@ URL invalid Toate extensiile au fost dezactivate din cauza unei defecțiuni pentru a vă ajuta să o găsiți pe cea care cauzează probleme. Se descarcă actualizarea aplicației… - Browser web CloudStream nu are niciun site instalat din start. Trebuie să instalați site-urile din depozite. \n \nAlăturați-vă Discord-ului nostru sau căutați online. @@ -522,7 +517,6 @@ Se actualizează emisiunile abonate Abonat Lista publică - MPV Moştenit Test de furnizor Furnizori @@ -640,6 +634,5 @@ Sezonul %1$d Episod %2$d va fi lansat în Selectați divece-ul pe care doriți să faceți cast Cast mirror - Fcast hide_player_control_names_key \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 809f12ce4..2b0402566 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -190,7 +190,6 @@ Торренты Другое Ошибка загрузки, проверьте разрешения хранилища - Копировать ссылку Автоскачивание Загрузка. Зеркало Сезон @@ -270,8 +269,6 @@ Размер Авторы Поддерживается - VLC - MPV Пропустить %s Концовка Используйте яркость системы в проигрывателе приложения вместо темного наложения @@ -293,7 +290,6 @@ Неожиданная ошибка плеера Эпизод Chromecast Воспроизведение на %s - Воспроизвести в браузере Скачать субтитры Знак качества Переключение элементов интерфейса на плакате @@ -309,7 +305,6 @@ %d из 10 Посмотреть информацию о сбое Предпочитаемый видеоплеер - Веб-браузер Приложение не найдено Все языки Вступление @@ -492,7 +487,6 @@ Отображать рандомную кнопку в библиотеке и главной странице Рандомная кнопка Legacy (старый) - Web Video Cast Не отправляет данные Перезагрузить ссылки Предпочтительные медиа @@ -620,7 +614,6 @@ Сброс Сезон %1$d Эпизод %2$d выйдет Выйдет %s - Fcast Выберите девайс для трансляции hide_player_control_names_key В данный момент загрузок нет. diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 4e38be6b9..196ed1d60 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -188,7 +188,6 @@ Žiadne titulky Anime Kreslené - Skopírovať odkaz Automaticky stiahnuť Zrkadlo sťahovania Zamknúť @@ -320,7 +319,6 @@ Ázijské drámy Anime Chyba zdroja - Prehrať v prehliadači Štítok dabingu Štítok titulkov Názov diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index e4ee98f96..8b1e4cc15 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -119,7 +119,6 @@ Asalkiisa Isla appkan ku daawo Ku daawo %s - Ku daawo barawsarka Dejinta iskeed ah Deji toorentiga(mirror) Cusbooneysii lifaaqyada @@ -239,7 +238,6 @@ Eeg xogaha hoose 🐈 Badhinka xajmiga daaraha Fashil ka yimi dhiibaha - Koobu garee lifaaqa Sharraxaad ma leh Sharraxaadda Duluc ma leh @@ -472,10 +470,6 @@ Kordhiyeyaasha Liiska bulshada kale Muqaal daaraha appka - VLC - MPV - Web Video Cast - Barawsarka Kuuguma jiro appkaasi Dhammaan luuqadaha Is dhaafi %s diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index cb695c0f9..601500ff7 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -148,8 +148,6 @@ Chromecasta en Länk Spela upp i appen Spela upp i %s - Spela upp i webbläsaren - Kopiera länk Automatisk nerladdning Ladda ner en specifik länk Ladda om alla länkar @@ -379,13 +377,11 @@ Visa sub Kunde inte öppna appen GitHub Proxy - Webbläsarens videospelare Installerar uppdatering till appen… Kunde inte nå GitHub, sätter på jsDelivr proxy… Leverantörer Nytt webbplatsnamn Ta bort reklam från undertexter - VLC Alla språk Rensa historik PackageInstaller @@ -409,8 +405,6 @@ Videospelare Öppna med Synkronisera undertexter - MPV - Web Video Cast Misslyckades Ja Videospår @@ -619,7 +613,6 @@ Kommer ut om %s Fel vid kopiering, kopiera logcat och kontakta appsupport. Media - Fcast Cast mirror Säsong %1$d Avsnitt %2$d kommer att släppas om Välj cast-enhet diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index c8c3243cd..a2b4cadd2 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -266,8 +266,6 @@ மறுதொடக்கம் விவரம் ஆசிரியர்கள் - வலை வீடியோ நடிகர்கள் - இணைய உலாவி %s ஐத் தவிர்க்கவும் மறுபரிசீலனை செய்யுங்கள் அறிமுகம் @@ -365,7 +363,6 @@ முன்மாதிரி தளவமைப்பு பதிவிறக்கம் செய்யப்பட்ட கோப்பு பகுத்தல் - எம்.பி.வி. உங்கள் நூலகம் காலியாக உள்ளது :( \n நூலகக் கணக்கில் உள்நுழைக அல்லது உங்கள் உள்ளக நூலகத்தில் காட்சிகளைச் சேர்க்கவும். குழுவிலகவும் @@ -431,8 +428,6 @@ மூல பிழை தொலை பிழை ரெண்டரர் பிழை - உலாவியில் விளையாடுங்கள் - இணைப்பை நகலெடுக்கவும் ஆட்டோ பதிவிறக்கம் கண்ணாடியைப் பதிவிறக்கவும் இணைப்புகளை மீண்டும் ஏற்றவும் @@ -520,7 +515,6 @@ அளவு ஆதரிக்கப்பட்டது முதலில் நீட்டிப்பை நிறுவவும் - Fcast காச்ட் சாதனத்தைத் தேர்ந்தெடுக்கவும் அனைத்து மொழிகளும் ஆம் @@ -551,7 +545,6 @@ பதிவிறக்கம் செய்யப்படவில்லை: %d புதுப்பிக்கப்பட்டது %d செருகுநிரல்கள் உள் வீரர் - வி.எல்.சி. திறப்பு கலப்பு திறப்பு வரவு diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index d832144dd..5fd25031e 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -174,8 +174,6 @@ Chromecast Mirror I-play sa App I-play sa %s - I-play sa browser - Kopyahin ang Link Awtomatiking i-download Download mirror Subukan muli diff --git a/app/src/main/res/values-tr/array.xml b/app/src/main/res/values-tr/array.xml index 22a94ebf0..4ec45e6d1 100644 --- a/app/src/main/res/values-tr/array.xml +++ b/app/src/main/res/values-tr/array.xml @@ -33,22 +33,6 @@ 6 - - @string/player_settings_play_in_app - @string/player_settings_play_in_vlc - @string/player_settings_play_in_mpv - @string/player_settings_play_in_web - @string/player_settings_play_in_browser - - - - 1 - 2 - 5 - 4 - 3 - - @string/resolution_and_title @string/title @@ -187,32 +171,6 @@ @string/show_title_key - - @string/episode_action_chromecast_episode - @string/episode_action_chromecast_mirror - @string/episode_action_play_in_app - @string/episode_action_play_in_format - @string/episode_action_play_in_browser - @string/episode_action_copy_link - @string/episode_action_auto_download - @string/episode_action_download_mirror - @string/episode_action_download_subtitle - @string/episode_action_reload_links - - - - 4 - 5 - 1 - 2 - 3 - 9 - 6 - 7 - 13 - 8 - - @string/automatic @string/phone_layout diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 3e3c0e208..46123d7ab 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -258,8 +258,6 @@ Bağlantıyı Chromecast ile yayınla Burada oynat %s üzerinden oynat - Tarayıcıda oynat - Bağlantıyı kopyala Otomatik indir Şu kaynaktan indir Bağlantıları yenile @@ -482,10 +480,6 @@ HLS Oynatma Listesi Tercih edilen video oynatıcısı Dahili oynatıcı - VLC - MPV - Web Video Yayını - İnternet tarayıcısı Uygulama bulunamadı Geçmiş İzlendi olarak işaretle @@ -669,7 +663,6 @@ Sezon %1$d Bölüm %2$d tarihinde yayınlanacak Yansıtılacak cihaz seç Ekran yansıtma - Fcast CloudStream Viki Güvenlik Hesaplar diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 479bdc38b..7cf754f97 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -248,7 +248,6 @@ Змінити розмір Короткий зміст Фільми - Скопіювати посилання Перезавантажити посилання Документальні фільми NSFW @@ -257,7 +256,6 @@ Торент Мітка якості NSFW - Переглянути в браузері Несподівана помилка плеєра Помилка завантаження, перевірте дозволи на зберігання Епізод Chromecast @@ -445,10 +443,6 @@ Спочатку встановіть розширення Список відтворення HLS Вбудований плеєр - VLC - MPV - Web Video Cast - Веббраузер Ендінґ Коротке повторення Пропустити %s @@ -620,7 +614,6 @@ Скинути Наступний через %s %1$d сезон %2$d епізод вийде через - Fcast Оберіть пристрій для трансляції Трансляція через дзеркало CloudStream Wiki @@ -663,6 +656,5 @@ \n%s Попередній перегляд повзунка Ввімкнути мініатюру попереднього перегляду на повзунку - MPV YTDL Субтитри ще не завантажено \ No newline at end of file diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 5a32dfe8a..06508bc9e 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -243,7 +243,6 @@ Chromecast mirror ایپ میں چلائیں %s میں چلائیں - کاپی لنک ڈاؤنلوڈ mirror لنکس کو دوبارہ لوڈ کریں سب ٹائٹلز ڈاؤن لوڈ @@ -336,7 +335,6 @@ کوئی زیرنویس میں دیری نہیں این ایس ایف ڈبلیو آٹو ڈاؤن لوڈ - browser میں چلائیں بہت زیادہ سیٹ ہونے پر کم میموری والی ڈیوائس(جیسے کہ Android TV) پر کریشوں کا سبب بنتا ہے. Remove site Add a clone of an existing site, with a different URL @@ -361,7 +359,6 @@ قرارداد اور عنوان HDR %s سے ان سبسکرائب کیا گیا - ویب ویڈیو کاسٹ 18+ لاگ https://example.com/example.mp4 @@ -427,11 +424,8 @@ حالت سائز زبان - وی ایل سی ترجیحی ویڈیو پلیئر اندرونی پلیئر - ایم پی وی - ویب براؤزر ایپ نہیں ملی Recap مخلوط اختتام diff --git a/app/src/main/res/values-vi/array.xml b/app/src/main/res/values-vi/array.xml index f363befda..c6ff1c4e2 100644 --- a/app/src/main/res/values-vi/array.xml +++ b/app/src/main/res/values-vi/array.xml @@ -153,32 +153,6 @@ @string/show_title_key - - @string/episode_action_chromecast_episode - @string/episode_action_chromecast_mirror - @string/episode_action_play_in_app - @string/episode_action_play_in_format - @string/episode_action_play_in_browser - @string/episode_action_copy_link - @string/episode_action_auto_download - @string/episode_action_download_mirror - @string/episode_action_download_subtitle - @string/episode_action_reload_links - - - - 4 - 5 - 1 - 2 - 3 - 9 - 6 - 7 - 13 - 8 - - @string/automatic @string/phone_layout diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 53b19bd8b..5c4cd380e 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -243,8 +243,6 @@ Chiếu Chromecast Xem với trình phát mặc định Xem với trình phát %s - Xem tại trình duyệt - Sao chép liên kết Tự động tải xuống Nguồn tải xuống Lấy link mới nhất @@ -472,10 +470,6 @@ Hỗ trợ Ngôn ngữ Cài đặt tiện ích trước - VLC - MPV - Web Video Cast - Trình duyệt web Không thấy ứng dụng Tất cả ngôn ngữ Tua %s @@ -671,11 +665,9 @@ \n \n%s Xóa plugin - Fcast Ngày phát hành (Cũ đến mới) Ẩn tên các nút điều khiển Bật chế độ xem trước hình thu nhỏ trên seekbar Xem trước Seekbar - MPV YTDL Chưa tải phụ đề \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b8b8b4884..3c002c29a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -258,8 +258,6 @@ Chromecast 鏡像 在應用程式中播放 在 %s 中播放 - 在瀏覽器中播放 - 複製連結 自動下載 下載鏡像 重新載入連結 @@ -482,10 +480,6 @@ HLS 播放清單 偏好影片播放器 內部播放器 - VLC - MPV - 網路影片播放 - 網頁瀏覽器 未找到應用程式 所有語言 跳過 %s @@ -663,7 +657,6 @@ 使用指紋、面容 ID、PIN、圖案和密碼解除鎖定應用程式。 由於多次嘗試失敗,此畫面已關閉。請重新啟動應用程式。 剪貼簿存取失敗,請再試一次。 - Fcast 複製失敗,請複製 logcat 內容並聯繫應用程式支援者。 無法開啟 CloudStream 的應用程式資訊頁面。 您的 CloudStream 資料已完成備份。儘管可能性非常低,但因不同裝置的行為都有所不同,在極少數情況下,您可能會無法存取本應用程式。此時請完全清除本應用程式的資料,再使用已有的備份進行還原。若因此造成任何不便,我們深感抱歉。 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index ff85df32e..45e350439 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -259,8 +259,6 @@ Chromecast 镜像 在应用中播放 在 %s 中播放 - 在浏览器中播放 - 复制链接 自动下载 下载镜像 重新加载链接 @@ -483,10 +481,6 @@ HLS 播放列表 首选视频播放器 内部播放器 - VLC - MPV - 投屏 - 浏览器 未找到应用 所有语言 跳过 %s @@ -665,7 +659,6 @@ 在多次尝试失败后,提示将关闭。只需重启应用程序再试。 即将在 %s CloudStream Wiki - Fcast 选择投射设备 %1$d季%2$d集将在 投射镜像 @@ -674,7 +667,6 @@ 打开本地视频 安全 访问智能手机或电脑上的 %s 并输入上述代码 - MPV YTDL 进度条预览 启用进度条预览缩略图 删除插件 diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index b077997fe..a987420e9 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -50,24 +50,6 @@ @string/nsfw - - @string/player_settings_play_in_app - @string/player_settings_play_in_vlc - @string/player_settings_play_in_mpv - @string/player_settings_play_in_mpvytdl - @string/player_settings_play_in_web - @string/player_settings_play_in_browser - - - - 1 - 2 - 5 - 6 - 4 - 3 - - @string/resolution_and_title @string/title @@ -226,32 +208,6 @@ @string/show_title_key - - @string/episode_action_chromecast_episode - @string/episode_action_chromecast_mirror - @string/episode_action_play_in_app - @string/episode_action_play_in_format - @string/episode_action_play_in_browser - @string/episode_action_copy_link - @string/episode_action_auto_download - @string/episode_action_download_mirror - @string/episode_action_download_subtitle - @string/episode_action_reload_links - - - - 4 - 5 - 1 - 2 - 3 - 9 - 6 - 7 - 13 - 8 - - @string/automatic @string/phone_layout diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7c7553208..c35c2b9d0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -381,8 +381,6 @@ Cast mirror Play in app Play in %s - Play in browser - Copy link Auto download Download mirror Reload links @@ -662,12 +660,6 @@ HLS Playlist Preferred video player Internal player - VLC - MPV - MPV YTDL - Web Video Cast - Fcast - Web browser Select cast device App not found All Languages From f7594e523992da3b0805ae1c1e82664e20b552d2 Mon Sep 17 00:00:00 2001 From: Cloudburst <18114966+C10udburst@users.noreply.github.com> Date: Sun, 22 Sep 2024 20:56:34 +0200 Subject: [PATCH 005/962] VideoClickAction api (#1329) --- app/src/main/AndroidManifest.xml | 9 +- .../lagradost/cloudstream3/CommonActivity.kt | 28 +- .../lagradost/cloudstream3/MainActivity.kt | 115 +---- .../cloudstream3/actions/OpenInAppAction.kt | 134 ++++++ .../cloudstream3/actions/VideoClickAction.kt | 87 ++++ .../actions/temp/CopyClipboardAction.kt | 27 ++ .../cloudstream3/actions/temp/MpvKtPackage.kt | 69 +++ .../cloudstream3/actions/temp/MpvPackage.kt | 61 +++ .../actions/temp/PlayInBrowserAction.kt | 44 ++ .../actions/temp/ViewM3U8Action.kt | 30 ++ .../cloudstream3/actions/temp/VlcPackage.kt | 68 +++ .../actions/temp/WebVideoCastPackage.kt | 62 +++ .../actions/temp/fcast/FcastAction.kt | 67 +++ .../temp}/fcast/FcastManager.kt | 2 +- .../temp}/fcast/FcastSession.kt | 2 +- .../{utils => actions/temp}/fcast/Packets.kt | 2 +- .../lagradost/cloudstream3/plugins/Plugin.kt | 14 + .../cloudstream3/plugins/PluginManager.kt | 7 + .../cloudstream3/ui/ControllerActivity.kt | 19 +- .../ui/player/DownloadFileGenerator.kt | 6 +- .../ui/player/ExtractorLinkGenerator.kt | 9 +- .../cloudstream3/ui/player/IGenerator.kt | 59 +-- .../cloudstream3/ui/player/LinkGenerator.kt | 6 +- .../ui/player/PlayerGeneratorViewModel.kt | 7 +- .../ui/player/RepoLinkGenerator.kt | 10 +- .../cloudstream3/ui/result/EpisodeAdapter.kt | 29 +- .../ui/result/ResultViewModel2.kt | 436 +++--------------- .../ui/settings/SettingsPlayer.kt | 18 +- app/src/main/res/values-ajp/strings.xml | 8 - app/src/main/res/values-ar/strings.xml | 8 - app/src/main/res/values-ars/strings.xml | 2 - app/src/main/res/values-as/strings.xml | 8 - app/src/main/res/values-bg/strings.xml | 6 - app/src/main/res/values-bn/strings.xml | 2 - app/src/main/res/values-bp/strings.xml | 8 - app/src/main/res/values-cs/strings.xml | 8 - app/src/main/res/values-de/strings.xml | 7 - app/src/main/res/values-el/strings.xml | 7 - app/src/main/res/values-es/array.xml | 26 -- app/src/main/res/values-es/strings.xml | 8 - app/src/main/res/values-fr/strings.xml | 8 +- app/src/main/res/values-hi/strings.xml | 2 - app/src/main/res/values-hr/strings.xml | 8 - app/src/main/res/values-hu/strings.xml | 6 - app/src/main/res/values-in/strings.xml | 7 - app/src/main/res/values-it/strings.xml | 8 - app/src/main/res/values-iw/strings.xml | 6 - app/src/main/res/values-ja/strings.xml | 3 - app/src/main/res/values-ko/strings.xml | 7 - app/src/main/res/values-lt/strings.xml | 5 - app/src/main/res/values-lv/strings.xml | 6 - app/src/main/res/values-mk/strings.xml | 7 - app/src/main/res/values-ml/strings.xml | 2 - app/src/main/res/values-ms/strings.xml | 7 - app/src/main/res/values-my/strings.xml | 6 - app/src/main/res/values-nl/strings.xml | 6 - app/src/main/res/values-nn/strings.xml | 2 - app/src/main/res/values-no/strings.xml | 6 - app/src/main/res/values-or/strings.xml | 4 - app/src/main/res/values-pl/array.xml | 26 -- app/src/main/res/values-pl/strings.xml | 8 - app/src/main/res/values-pt/strings.xml | 9 +- app/src/main/res/values-qt/strings.xml | 7 - app/src/main/res/values-ro/strings.xml | 7 - app/src/main/res/values-ru/strings.xml | 7 - app/src/main/res/values-sk/strings.xml | 2 - app/src/main/res/values-so/strings.xml | 6 - app/src/main/res/values-sv/strings.xml | 7 - app/src/main/res/values-ta/strings.xml | 7 - app/src/main/res/values-tl/strings.xml | 2 - app/src/main/res/values-tr/array.xml | 42 -- app/src/main/res/values-tr/strings.xml | 7 - app/src/main/res/values-uk/strings.xml | 8 - app/src/main/res/values-ur/strings.xml | 6 - app/src/main/res/values-vi/array.xml | 26 -- app/src/main/res/values-vi/strings.xml | 8 - app/src/main/res/values-zh-rTW/strings.xml | 7 - app/src/main/res/values-zh/strings.xml | 8 - app/src/main/res/values/array.xml | 44 -- app/src/main/res/values/strings.xml | 10 +- app/src/main/res/xml/settings_player.xml | 2 +- 81 files changed, 839 insertions(+), 1048 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt rename app/src/main/java/com/lagradost/cloudstream3/{utils => actions/temp}/fcast/FcastManager.kt (98%) rename app/src/main/java/com/lagradost/cloudstream3/{utils => actions/temp}/fcast/FcastSession.kt (96%) rename app/src/main/java/com/lagradost/cloudstream3/{utils => actions/temp}/fcast/Packets.kt (95%) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 1aeef5550..a04504acd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,7 +17,7 @@ - + @@ -30,13 +30,6 @@ android:name="android.software.leanback" android:required="false" /> - - - - - - - - val resultCode = result.resultCode - val data = result.data - if (resultCode == AppCompatActivity.RESULT_OK && data != null && resumeApp.position != null && resumeApp.duration != null) { - val pos = resumeApp.getPosition(data) - val dur = resumeApp.getDuration(data) - if (dur > 0L && pos > 0L) - DataStoreHelper.setViewPos(getKey(resumeApp.lastId), pos, dur) - removeKey(resumeApp.lastId) - ResultFragment.updateUI() - } - } + MainActivity.activityResultLauncher = componentActivity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == AppCompatActivity.RESULT_OK) { + val actionUid = getKey("last_click_action") ?: return@registerForActivityResult + Log.d(TAG, "Loading action $actionUid result handler") + val action = VideoClickActionHolder.getByUniqueId(actionUid) as? OpenInAppAction ?: return@registerForActivityResult + action.onResult(act, result.data) + removeKey("last_click_action") + removeKey("last_opened_id") + } } // Ask for notification permissions on Android 13 diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 3581a3d29..fa54545cf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -173,7 +173,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API -import com.lagradost.cloudstream3.utils.fcast.FcastManager +import com.lagradost.cloudstream3.actions.temp.fcast.FcastManager import com.lagradost.safefile.SafeFile import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -186,120 +186,9 @@ import kotlin.math.abs import kotlin.math.absoluteValue import kotlin.system.exitProcess -//https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898 -//https://wiki.videolan.org/Android_Player_Intents/ - -//https://github.com/mpv-android/mpv-android/blob/0eb3cdc6f1632636b9c30d52ec50e4b017661980/app/src/main/java/is/xyz/mpv/MPVActivity.kt#L904 -//https://mpv-android.github.io/mpv-android/intent.html - -// https://www.webvideocaster.com/integrations - -//https://github.com/jellyfin/jellyfin-android/blob/6cbf0edf84a3da82347c8d59b5d5590749da81a9/app/src/main/java/org/jellyfin/mobile/bridge/ExternalPlayer.kt#L225 - class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCallback { companion object { - const val VLC_PACKAGE = "org.videolan.vlc" - const val MPV_PACKAGE = "is.xyz.mpv" - const val MPV_YTDL_PACKAGE = "is.xyz.mpv.ytdl" - const val WEB_VIDEO_CAST_PACKAGE = "com.instantbits.cast.webvideo" - - val VLC_COMPONENT = ComponentName(VLC_PACKAGE, "$VLC_PACKAGE.gui.video.VideoPlayerActivity") - val MPV_COMPONENT = ComponentName(MPV_PACKAGE, "$MPV_PACKAGE.MPVActivity") - val MPV_YTDL_COMPONENT = ComponentName(MPV_YTDL_PACKAGE, "$MPV_PACKAGE.MPVActivity") - - //TODO REFACTOR AF - open class ResultResume( - val packageString: String, - val action: String = Intent.ACTION_VIEW, - val position: String? = null, - val duration: String? = null, - var launcher: ActivityResultLauncher? = null, - ) { - val defaultTime = -1L - - val lastId get() = "${packageString}_last_open_id" - suspend fun launch(id: Int?, callback: suspend Intent.() -> Unit) { - val intent = Intent(action) - - if (id != null) - setKey(lastId, id) - else - removeKey(lastId) - - intent.setPackage(packageString) - callback.invoke(intent) - launcher?.launch(intent) - } - - open fun getPosition(intent: Intent?): Long { - return defaultTime - } - - open fun getDuration(intent: Intent?): Long { - return defaultTime - } - } - - val VLC = object : ResultResume( - VLC_PACKAGE, - // Android 13 intent restrictions fucks up specifically launching the VLC player - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - "org.videolan.vlc.player.result" - } else { - Intent.ACTION_VIEW - }, - "extra_position", - "extra_duration", - ) { - override fun getPosition(intent: Intent?): Long { - return intent?.getLongExtra(this.position, defaultTime) ?: defaultTime - } - - override fun getDuration(intent: Intent?): Long { - return intent?.getLongExtra(this.duration, defaultTime) ?: defaultTime - } - } - - val MPV = object : ResultResume( - MPV_PACKAGE, - //"is.xyz.mpv.MPVActivity.result", // resume not working :pensive: - position = "position", - duration = "duration", - ) { - override fun getPosition(intent: Intent?): Long { - return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() - ?: defaultTime - } - - override fun getDuration(intent: Intent?): Long { - return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() - ?: defaultTime - } - } - - val MPV_YTDL = object : ResultResume( - MPV_YTDL_PACKAGE, - //"is.xyz.mpv.ytdl/is.xyz.mpv.MPVActivity.result", // resume not working :pensive: - position = "position", - duration = "duration", - ) { - override fun getPosition(intent: Intent?): Long { - return intent?.getIntExtra(this.position, defaultTime.toInt())?.toLong() - ?: defaultTime - } - - override fun getDuration(intent: Intent?): Long { - return intent?.getIntExtra(this.duration, defaultTime.toInt())?.toLong() - ?: defaultTime - } - } - - val WEB_VIDEO = ResultResume(WEB_VIDEO_CAST_PACKAGE) - - val resumeApps = arrayOf( - VLC, MPV, MPV_YTDL, WEB_VIDEO - ) - + var activityResultLauncher: ActivityResultLauncher? = null const val TAG = "MAINACT" const val ANIMATED_OUTLINE: Boolean = false diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt new file mode 100644 index 000000000..99c1ac38b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt @@ -0,0 +1,134 @@ +package com.lagradost.cloudstream3.actions + +import android.app.Activity +import android.content.ActivityNotFoundException +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.widget.Toast +import androidx.core.content.FileProvider +import androidx.core.net.toUri +import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.MainActivity.Companion.activityResultLauncher +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.ResultFragment +import com.lagradost.cloudstream3.ui.result.UiText +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.AppContextUtils.isAppInstalled +import com.lagradost.cloudstream3.utils.DataStoreHelper +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import java.io.File + +fun updateDurationAndPosition(position: Long, duration: Long) { + if (position <= 0 || duration <= 0) return + DataStoreHelper.setViewPos(getKey("last_opened_id"), position, duration) + ResultFragment.updateUI() +} + +/** + * Util method that may be helpful for creating intents for apps that support m3u8 files. + * All sources are written to a temporary m3u8 file, which is then sent to the app. + */ +fun makeTempM3U8Intent( + context: Context, + intent: Intent, + result: LinkLoadingResult) { + intent.apply { + addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + } + + val outputDir = context.cacheDir + + if (result.links.size == 1) { + intent.setDataAndType(result.links.first().url.toUri(), "video/*") + } else { + val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir) + + var text = "#EXTM3U\n#EXT-X-VERSION:3" + + result.links.forEachIndexed { index, link -> + text += "\n#EXTINF:$index,${link.name}\n${link.url}" + } + + //With subtitles it doesn't work for no reason :( + /*for (sub in result.subs) { + val normalizedName = sub.name.replace("[^a-zA-Z0-9 ]".toRegex(), "") + text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${normalizedName}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.languageCode}\",URI=\"${sub.url}\"" + }*/ + + text += "\n#EXT-X-ENDLIST" + + outputFile.writeText(text) + + intent.setDataAndType( + FileProvider.getUriForFile( + context, + context.applicationContext.packageName + ".provider", + outputFile + ), "application/x-mpegURL" + ) + } +} + +abstract class OpenInAppAction( + open val appName: UiText, + open val packageName: String, + private val intentClass: String? = null, + private val action: String = Intent.ACTION_VIEW +): VideoClickAction() { + override val name: UiText + get() = txt(R.string.episode_action_play_in_format, appName) + + override val isPlayer = true + + override fun shouldShow(context: Context?, video: ResultEpisode?) = context?.isAppInstalled(packageName) == true + + override fun runAction( + context: Context?, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + if (context == null) return + val intent = Intent(action) + intent.setPackage(packageName) + if (intentClass != null) { + intent.component = ComponentName(packageName, intentClass) + } + putExtra(context, intent, video, result, index) + setKey("last_opened_id", video.id) + try { + CoroutineScope(Dispatchers.IO).launch { + activityResultLauncher?.launch(intent) + } + } catch (_: ActivityNotFoundException) { + showToast(R.string.app_not_found_error, Toast.LENGTH_LONG) + } catch (t: Throwable) { + logError(t) + showToast(t.toString(), Toast.LENGTH_LONG) + } + } + + /** + * Before intent is sent, this function is called to put extra data into the intent. + * @see VideoClickAction.runAction + * */ + abstract fun putExtra(context: Context, intent: Intent, video: ResultEpisode, result: LinkLoadingResult, index: Int?) + + /** + * This function is called when the app is opened again after the intent was sent. + * You can use it to for example update duration and position. + * @see updateDurationAndPosition + */ + abstract fun onResult(activity: Activity, intent: Intent?) +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt new file mode 100644 index 000000000..f66ed74d9 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -0,0 +1,87 @@ +package com.lagradost.cloudstream3.actions + +import android.app.Activity +import android.content.Context +import com.lagradost.api.Log +import com.lagradost.cloudstream3.actions.temp.CopyClipboardAction +import com.lagradost.cloudstream3.actions.temp.MpvKtPackage +import com.lagradost.cloudstream3.actions.temp.MpvKtPreviewPackage +import com.lagradost.cloudstream3.actions.temp.MpvPackage +import com.lagradost.cloudstream3.actions.temp.MpvYTDLPackage +import com.lagradost.cloudstream3.actions.temp.PlayInBrowserAction +import com.lagradost.cloudstream3.actions.temp.ViewM3U8Action +import com.lagradost.cloudstream3.actions.temp.VlcPackage +import com.lagradost.cloudstream3.actions.temp.WebVideoCastPackage +import com.lagradost.cloudstream3.actions.temp.fcast.FcastAction +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.UiText +import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf +import com.lagradost.cloudstream3.utils.ExtractorLinkType +import kotlin.reflect.jvm.jvmName + +object VideoClickActionHolder { + val allVideoClickActions = threadSafeListOf( + PlayInBrowserAction(), CopyClipboardAction(), + VlcPackage(), ViewM3U8Action(), + MpvPackage(), MpvYTDLPackage(), + WebVideoCastPackage(), MpvKtPackage(), MpvKtPreviewPackage(), + FcastAction() + ) + + init { + Log.d("VideoClickActionHolder", "allVideoClickActions: ${allVideoClickActions.map { it.uniqueId() }}") + } + + private const val ACTION_ID_OFFSET = 1000 + + fun makeOptionMap(activity: Activity?, video: ResultEpisode) = allVideoClickActions + // We need to have index before filtering + .mapIndexed { id, it -> it to id + ACTION_ID_OFFSET } + .filter { it.first.shouldShow(activity, video) } + .map { it.first.name to it.second } + + + fun getActionById(id: Int): VideoClickAction? = allVideoClickActions.getOrNull(id - ACTION_ID_OFFSET) + + fun getByUniqueId(uniqueId: String): VideoClickAction? = allVideoClickActions.firstOrNull { it.uniqueId() == uniqueId } + + fun uniqueIdToId(uniqueId: String?): Int? { + if (uniqueId == null) return null + return allVideoClickActions + .mapIndexed { id, it -> it to id + ACTION_ID_OFFSET } + .firstOrNull { it.first.uniqueId() == uniqueId } + ?.second + } + + fun getPlayers(activity: Activity? = null) = allVideoClickActions.filter { it.isPlayer && it.shouldShow(activity, null) } +} + +abstract class VideoClickAction { + abstract val name: UiText + + /** if true, the app will show dialog to select source - result.links[index] */ + open val oneSource : Boolean = false + + /** if true, this action could be selected as default player (one press action) in settings */ + open val isPlayer: Boolean = false + + /** Which type of sources this action can handle. */ + open val sourceTypes: Set = ExtractorLinkType.entries.toSet() + + /** Determines which plugin a given provider is from. This is the full path to the plugin. */ + var sourcePlugin: String? = null + + fun uniqueId() = "$sourcePlugin:${this::class.jvmName}" + + abstract fun shouldShow(context: Context?, video: ResultEpisode?): Boolean + + /** + * This function is called when the action is clicked. + * @param context The current activity + * @param video The episode/movie that was clicked + * @param result The result of the link loading, contains video & subtitle links + * @param index if oneSource is true, this is the index of the selected source + */ + abstract fun runAction(context: Context?, video: ResultEpisode, result: LinkLoadingResult, index: Int?) +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt new file mode 100644 index 000000000..e054b5ce2 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt @@ -0,0 +1,27 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.content.Context +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper + +class CopyClipboardAction: VideoClickAction() { + override val name = txt("Copy to clipboard") + + override val oneSource = true + + override fun shouldShow(context: Context?, video: ResultEpisode?) = true + + override fun runAction( + context: Context?, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + if (index == null) return + val link = result.links.getOrNull(index) ?: return + clipboardHelper(txt(link.name), link.url) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt new file mode 100644 index 000000000..f5ded49b8 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt @@ -0,0 +1,69 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import androidx.core.net.toUri +import com.lagradost.cloudstream3.actions.OpenInAppAction +import com.lagradost.cloudstream3.actions.updateDurationAndPosition +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.ExtractorLinkType + +class MpvKtPreviewPackage: MpvKtPackage( + appName = "mpvKt Preview", + packageName = "live.mehiz.mpvkt.preview", +) + +open class MpvKtPackage( + appName: String = "mpvKt", + packageName: String = "live.mehiz.mpvkt", +): OpenInAppAction( + appName = txt(appName), + packageName = packageName, + intentClass = "live.mehiz.mpvkt.ui.player.PlayerActivity" +) { + override val oneSource = true + + override val sourceTypes = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + + override fun putExtra( + context: Context, + intent: Intent, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + val link = result.links.getOrNull(index ?: 0) ?: return + + intent.apply { + putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) + setDataAndType(Uri.parse(link.url), "video/*") + + // m3u8 plays, but changing sources feature is not available + // makeTempM3U8Intent(activity, this, result) + + //putExtra("headers", link.headers.flatMap { listOf(it.key, it.value) }.toTypedArray()) + + val position = getViewPos(video.id)?.position + if (position != null) + putExtra("position", position.toInt()) + + putExtra("secure_uri", true) + } + } + + override fun onResult(activity: Activity, intent: Intent?) { + val position = intent?.getIntExtra("position", -1)?.toLong() ?: -1 + val duration = intent?.getIntExtra("duration", -1)?.toLong() ?: -1 + updateDurationAndPosition(position, duration) + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt new file mode 100644 index 000000000..4c66d0450 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -0,0 +1,61 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import android.content.Context +import android.content.Intent +import androidx.core.net.toUri +import com.lagradost.api.Log +import com.lagradost.cloudstream3.actions.OpenInAppAction +import com.lagradost.cloudstream3.actions.makeTempM3U8Intent +import com.lagradost.cloudstream3.actions.updateDurationAndPosition +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.ExtractorLinkType + +// https://github.com/mpv-android/mpv-android/blob/0eb3cdc6f1632636b9c30d52ec50e4b017661980/app/src/main/java/is/xyz/mpv/MPVActivity.kt#L904 +// https://mpv-android.github.io/mpv-android/intent.html + +class MpvYTDLPackage : MpvPackage("MPV YTDL", "is.xyz.mpv.ytdl") { + override val sourceTypes = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) +} + +open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv"): OpenInAppAction( + txt(appName), + packageName, + "is.xyz.mpv.MPVActivity" +) { + + override fun putExtra( + context: Context, + intent: Intent, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + intent.apply { + putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) + putExtra("title", video.name) + + makeTempM3U8Intent(context, this, result) + + val position = getViewPos(video.id)?.position + if (position != null) + putExtra("position", position.toInt()) + + putExtra("secure_uri", true) + } + } + + override fun onResult(activity: Activity, intent: Intent?) { + val position = intent?.getIntExtra("position", -1) ?: -1 + val duration = intent?.getIntExtra("duration", -1) ?: -1 + Log.d("MPV", "Position: $position, Duration: $duration") + updateDurationAndPosition(position.toLong(), duration.toLong()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt new file mode 100644 index 000000000..de32bb4b3 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt @@ -0,0 +1,44 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.content.Context +import android.content.Intent +import android.net.Uri +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.ExtractorLinkType + +class PlayInBrowserAction: VideoClickAction() { + override val name = txt(R.string.episode_action_play_in_format, "Browser") + + override val oneSource = true + + override val isPlayer = true + + override val sourceTypes: Set = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + + override fun shouldShow(context: Context?, video: ResultEpisode?) = true + + override fun runAction( + context: Context?, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + val link = result.links.getOrNull(index ?: 0) ?: return + try { + val i = Intent(Intent.ACTION_VIEW) + i.data = Uri.parse(link.url) + context?.startActivity(i) + } catch (e: Exception) { + logError(e) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt new file mode 100644 index 000000000..c14168e96 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt @@ -0,0 +1,30 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.content.Context +import android.content.Intent +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.actions.makeTempM3U8Intent +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt + +class ViewM3U8Action: VideoClickAction() { + override val name = txt(R.string.episode_action_play_in_format, "m3u8 player") + + override val isPlayer = true + + override fun shouldShow(context: Context?, video: ResultEpisode?) = true + + override fun runAction( + context: Context?, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + if (context == null) return + val i = Intent(Intent.ACTION_VIEW) + makeTempM3U8Intent(context, i, result) + context.startActivity(i) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt new file mode 100644 index 000000000..ecb37fdc6 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt @@ -0,0 +1,68 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Build +import com.lagradost.api.Log +import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.actions.OpenInAppAction +import com.lagradost.cloudstream3.actions.makeTempM3U8Intent +import com.lagradost.cloudstream3.actions.updateDurationAndPosition +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos + +// https://github.com/videolan/vlc-android/blob/3706c4be2da6800b3d26344fc04fab03ffa4b860/application/vlc-android/src/org/videolan/vlc/gui/video/VideoPlayerActivity.kt#L1898 +// https://wiki.videolan.org/Android_Player_Intents/ + +class VlcPackage: OpenInAppAction( + appName = txt("VLC"), + packageName = "org.videolan.vlc", + intentClass = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + "org.videolan.vlc.gui.video.VideoPlayerActivity" + } else { + null + }, + action = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { + "org.videolan.vlc.player.result" + } else { + Intent.ACTION_VIEW + } +) { + override val oneSource = false + + override fun putExtra( + context: Context, + intent: Intent, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + + makeTempM3U8Intent(context, intent, result) + + val position = getViewPos(video.id)?.position ?: 0L + + intent.putExtra("from_start", false) + intent.putExtra("position", position) + intent.putExtra("secure_uri", true) + intent.putExtra("title", video.name) + + val subsLang = getKey(SUBTITLE_AUTO_SELECT_KEY) ?: "en" + result.subs.firstOrNull { + subsLang == it.languageCode + }?.let { + intent.putExtra("subtitles_location", it.url) + } + } + + override fun onResult(activity: Activity, intent: Intent?) { + val position = intent?.getLongExtra("extra_position", -1) ?: -1 + val duration = intent?.getLongExtra("extra_duration", -1) ?: -1 + Log.d("VLC", "Position: $position, Duration: $duration") + updateDurationAndPosition(position, duration) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt new file mode 100644 index 000000000..f8419f63c --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt @@ -0,0 +1,62 @@ +package com.lagradost.cloudstream3.actions.temp + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Bundle +import androidx.core.net.toUri +import com.lagradost.cloudstream3.USER_AGENT +import com.lagradost.cloudstream3.actions.OpenInAppAction +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.ExtractorLinkType + +// https://www.webvideocaster.com/integrations + +class WebVideoCastPackage: OpenInAppAction( + txt("Web Video Cast"), + "com.instantbits.cast.webvideo" +) { + + override val oneSource = true + + override val sourceTypes = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + + override fun putExtra( + context: Context, + intent: Intent, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + val link = result.links[index ?: 0] + + intent.apply { + setDataAndType(Uri.parse(link.url), "video/*") + + val title = video.name ?: video.headerName + + putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) + putExtra("title", title) + video.poster?.let { putExtra("poster", it) } + val headers = Bundle().apply { + if (link.referer.isNotBlank()) + putString("Referer", link.referer) + putString("User-Agent", USER_AGENT) + for ((key, value) in link.headers) { + putString(key, value) + } + } + putExtra("android.media.intent.extra.HTTP_HEADERS", headers) + putExtra("secure_uri", true) + } + } + + override fun onResult(activity: Activity, intent: Intent?) = Unit +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt new file mode 100644 index 000000000..c0f92e4df --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt @@ -0,0 +1,67 @@ +package com.lagradost.cloudstream3.actions.temp.fcast + +import android.content.Context +import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.USER_AGENT +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.ui.result.LinkLoadingResult +import com.lagradost.cloudstream3.ui.result.ResultEpisode +import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType +import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog + +class FcastAction: VideoClickAction() { + override val name = txt("Fcast to device") + + override val oneSource = true + + override val sourceTypes = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 + ) + + override fun shouldShow(context: Context?, video: ResultEpisode?) = FcastManager.currentDevices.isNotEmpty() + + override fun runAction( + context: Context?, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) { + val link = result.links.getOrNull(index ?: 0) ?: return + val devices = FcastManager.currentDevices.toList() + context?.getActivity()?.showBottomDialog( + devices.map { it.name }, + -1, + txt(R.string.player_settings_select_cast_device).asString(context), + false, + {}) { + val position = getViewPos(video.id)?.position + castTo(devices.getOrNull(it), link, position) + } + } + + + private fun castTo(device: PublicDeviceInfo?, link: ExtractorLink, position: Long?) { + val host = device?.host ?: return + + FcastSession(host).use { session -> + session.sendMessage( + Opcode.Play, + PlayMessage( + "video/*", + link.url, + time = position?.let { it / 1000.0 }, + headers = mapOf( + "referer" to link.referer, + "user-agent" to USER_AGENT + ) + link.headers + ) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastManager.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastManager.kt similarity index 98% rename from app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastManager.kt rename to app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastManager.kt index e7c36a872..78682ca1c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastManager.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.utils.fcast +package com.lagradost.cloudstream3.actions.temp.fcast import android.content.Context import android.net.nsd.NsdManager diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastSession.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastSession.kt similarity index 96% rename from app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastSession.kt rename to app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastSession.kt index 1f33bca43..326d11191 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/FcastSession.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastSession.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.utils.fcast +package com.lagradost.cloudstream3.actions.temp.fcast import android.util.Log import androidx.annotation.WorkerThread diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/Packets.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/Packets.kt similarity index 95% rename from app/src/main/java/com/lagradost/cloudstream3/utils/fcast/Packets.kt rename to app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/Packets.kt index 61c00d6ed..26f5cec53 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/fcast/Packets.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/Packets.kt @@ -1,4 +1,4 @@ -package com.lagradost.cloudstream3.utils.fcast +package com.lagradost.cloudstream3.actions.temp.fcast // See https://gitlab.com/futo-org/fcast/-/wikis/Protocol-version-1 enum class Opcode(val value: Byte) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt index fc8365876..e35ae24b9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/Plugin.kt @@ -9,6 +9,8 @@ import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.extractorApis import android.util.Log import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.actions.VideoClickActionHolder const val PLUGIN_TAG = "PluginInstance" @@ -52,6 +54,18 @@ abstract class Plugin { extractorApis.add(element) } + /** + * Used to register VideoClickAction instances + * @param element VideoClickAction you want to register + */ + fun registerVideoClickAction(element: VideoClickAction) { + Log.i(PLUGIN_TAG, "Adding ${element.name} VideoClickAction") + element.sourcePlugin = this.filename + synchronized(VideoClickActionHolder.allVideoClickActions) { + VideoClickActionHolder.allVideoClickActions.add(element) + } + } + class Manifest { @JsonProperty("name") var name: String? = null diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index c7f416883..8535592d4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -24,6 +24,8 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent +import com.lagradost.cloudstream3.actions.VideoClickAction +import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.mvvm.debugPrint import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall @@ -583,8 +585,13 @@ object PluginManager { synchronized(APIHolder.allProviders) { APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.filename } } + extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.filename } + synchronized(VideoClickActionHolder.allVideoClickActions) { + VideoClickActionHolder.allVideoClickActions.removeIf { action: VideoClickAction -> action.sourcePlugin == plugin.filename } + } + classLoaders.values.removeIf { v -> v == plugin } plugins.remove(absolutePath) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt index 1eaac5056..4b5d680c3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/ControllerActivity.kt @@ -3,10 +3,15 @@ package com.lagradost.cloudstream3.ui import android.os.Bundle import android.util.Log import android.view.Menu -import android.view.View.* -import android.widget.* +import android.view.View.GONE +import android.view.View.INVISIBLE +import android.view.View.VISIBLE +import android.widget.AbsListView +import android.widget.ArrayAdapter +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.ListView 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 @@ -25,7 +30,7 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.sortUrls -import com.lagradost.cloudstream3.ui.player.LoadType +import com.lagradost.cloudstream3.ui.player.LOADTYPE_CHROMECAST import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.result.ResultEpisode @@ -298,14 +303,16 @@ class SelectSourceController(val view: ImageView, val activity: ControllerActivi val isSuccessful = safeApiCall { generator.generateLinks( - clearCache = false, type = LoadType.Chromecast, + clearCache = false, + allowedTypes = LOADTYPE_CHROMECAST, callback = { it.first?.let { link -> currentLinks.add(link) } }, subtitleCallback = { currentSubs.add(it) - }) + }, + isCasting = true) } val sortedLinks = sortUrls(currentLinks) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt index c7db7d045..7d3d18ca9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt @@ -6,6 +6,7 @@ import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.SubtitleUtils.cleanDisplayName import com.lagradost.cloudstream3.utils.SubtitleUtils.isMatchingSubtitle import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadFileInfoAndUpdateSettings @@ -57,10 +58,11 @@ class DownloadFileGenerator( override suspend fun generateLinks( clearCache: Boolean, - type: LoadType, + sourceTypes: Set, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int + offset: Int, + isCasting: Boolean ): Boolean { val meta = episodes[currentIndex + offset] diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt index ec485f1c8..794dd762d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/ExtractorLinkGenerator.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.ui.player import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType class ExtractorLinkGenerator( private val links: List, @@ -37,15 +38,15 @@ class ExtractorLinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - type: LoadType, + sourceTypes: Set, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int + offset: Int, + isCasting: Boolean ): Boolean { subtitles.forEach(subtitleCallback) - val allowedTypes = type.toSet() links.forEach { - if(allowedTypes.contains(it.type)) { + if(sourceTypes.contains(it.type)) { callback.invoke(it to null) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt index 6b8e6ea88..31cf0c70f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/IGenerator.kt @@ -3,45 +3,25 @@ package com.lagradost.cloudstream3.ui.player import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType -enum class LoadType { - Unknown, - InApp, - InAppDownload, - ExternalApp, - Browser, - Chromecast, - Fcast -} +val LOADTYPE_INAPP = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 +) + +val LOADTYPE_INAPP_DOWNLOAD = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.M3U8 +) + +val LOADTYPE_CHROMECAST = setOf( + ExtractorLinkType.VIDEO, + ExtractorLinkType.DASH, + ExtractorLinkType.M3U8 +) + +val LOADTYPE_ALL = ExtractorLinkType.entries.toSet() -fun LoadType.toSet() : Set { - return when(this) { - LoadType.InApp -> setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.DASH, - ExtractorLinkType.M3U8 - ) - LoadType.Browser -> setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.DASH, - ExtractorLinkType.M3U8 - ) - LoadType.InAppDownload -> setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.M3U8 - ) - LoadType.ExternalApp, LoadType.Unknown -> ExtractorLinkType.entries.toSet() - LoadType.Chromecast -> setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.DASH, - ExtractorLinkType.M3U8 - ) - LoadType.Fcast -> setOf( - ExtractorLinkType.VIDEO, - ExtractorLinkType.DASH, - ExtractorLinkType.M3U8 - ) - } -} interface IGenerator { val hasCache: Boolean @@ -60,9 +40,10 @@ interface IGenerator { /* not safe, must use try catch */ suspend fun generateLinks( clearCache: Boolean, - type: LoadType, + sourceTypes: Set, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, offset: Int = 0, + isCasting: Boolean = false ): Boolean } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt index 20feae413..109e3137b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/LinkGenerator.kt @@ -4,6 +4,7 @@ import android.net.Uri import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.loadExtractor @@ -69,10 +70,11 @@ class LinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - type: LoadType, + sourceTypes: Set, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int + offset: Int, + isCasting: Boolean ): Boolean { links.amap { link -> if (!extract || !loadExtractor(link.url, referer, { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt index 122eaa975..67cd9de6d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt @@ -15,6 +15,7 @@ import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.EpisodeSkip import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -94,7 +95,7 @@ class PlayerGeneratorViewModel : ViewModel() { if (generator?.hasCache == true && generator?.hasNext() == true) { safeApiCall { generator?.generateLinks( - type = LoadType.InApp, + sourceTypes = LOADTYPE_INAPP, clearCache = false, callback = {}, subtitleCallback = {}, @@ -173,7 +174,7 @@ class PlayerGeneratorViewModel : ViewModel() { } } - fun loadLinks(type: LoadType = LoadType.InApp) { + fun loadLinks(sourceTypes: Set = LOADTYPE_INAPP) { Log.i(TAG, "loadLinks") currentJob?.cancel() @@ -188,7 +189,7 @@ class PlayerGeneratorViewModel : ViewModel() { // load more data _loadingLinks.postValue(Resource.Loading()) val loadingState = safeApiCall { - generator?.generateLinks(type = type, clearCache = forceClearCache, callback = { + generator?.generateLinks(sourceTypes = sourceTypes, clearCache = forceClearCache, callback = { currentLinks.add(it) // Clone to prevent ConcurrentModificationException normalSafeApiCall { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt index 588afbb50..b97ca155b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/RepoLinkGenerator.kt @@ -1,13 +1,13 @@ 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 import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType import kotlin.math.max import kotlin.math.min @@ -75,12 +75,12 @@ class RepoLinkGenerator( override suspend fun generateLinks( clearCache: Boolean, - type: LoadType, + allowedTypes: Set, callback: (Pair) -> Unit, subtitleCallback: (SubtitleData) -> Unit, - offset: Int + offset: Int, + isCasting: Boolean, ): Boolean { - val allowedTypes = type.toSet() val index = currentIndex val current = episodes.getOrNull(index + offset) ?: return false @@ -123,7 +123,7 @@ class RepoLinkGenerator( val result = APIRepository( getApiFromNameNull(current.apiName) ?: throw Exception("This provider does not exist") ).loadLinks(current.data, - isCasting = LoadType.Chromecast == type, + isCasting = isCasting, subtitleCallback = { file -> val correctFile = PlayerSubtitleHelper.getSubtitleData(file) if (correctFile.url.isNotEmpty() && !currentSubsUrls.contains(correctFile.url)) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 4cd9cc9ea..2dd8e2ab4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -11,6 +11,7 @@ import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.databinding.ResultEpisodeBinding import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable @@ -30,9 +31,11 @@ import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +/** + * Ids >= 1000 are reserved for VideoClickActions + * @see VideoClickActionHolder + */ const val ACTION_PLAY_EPISODE_IN_PLAYER = 1 -const val ACTION_PLAY_EPISODE_IN_VLC_PLAYER = 2 -const val ACTION_PLAY_EPISODE_IN_BROWSER = 3 const val ACTION_CHROME_CAST_EPISODE = 4 const val ACTION_CHROME_CAST_MIRROR = 5 @@ -41,7 +44,6 @@ const val ACTION_DOWNLOAD_EPISODE = 6 const val ACTION_DOWNLOAD_MIRROR = 7 const val ACTION_RELOAD_EPISODE = 8 -const val ACTION_COPY_LINK = 9 const val ACTION_SHOW_OPTIONS = 10 @@ -52,12 +54,7 @@ const val ACTION_SHOW_DESCRIPTION = 15 const val ACTION_DOWNLOAD_EPISODE_SUBTITLE = 13 const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14 -const val ACTION_PLAY_EPISODE_IN_WEB_VIDEO = 16 -const val ACTION_PLAY_EPISODE_IN_MPV = 17 -const val ACTION_PLAY_EPISODE_IN_MPV_YTDL = 20 - const val ACTION_MARK_AS_WATCHED = 18 -const val ACTION_FCAST = 19 const val TV_EP_SIZE = 400 @@ -69,22 +66,10 @@ class EpisodeAdapter( private val downloadClickCallback: (DownloadClickEvent) -> Unit, ) : RecyclerView.Adapter() { companion object { - /** - * @return ACTION_PLAY_EPISODE_IN_PLAYER, ACTION_PLAY_EPISODE_IN_BROWSER or ACTION_PLAY_EPISODE_IN_VLC_PLAYER depending on player settings. - * See array.xml/player_pref_values - **/ fun getPlayerAction(context: Context): Int { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) - return when (settingsManager.getInt(context.getString(R.string.player_pref_key), 1)) { - 1 -> ACTION_PLAY_EPISODE_IN_PLAYER - 2 -> ACTION_PLAY_EPISODE_IN_VLC_PLAYER - 3 -> ACTION_PLAY_EPISODE_IN_BROWSER - 4 -> ACTION_PLAY_EPISODE_IN_WEB_VIDEO - 5 -> ACTION_PLAY_EPISODE_IN_MPV - 6 -> ACTION_PLAY_EPISODE_IN_MPV_YTDL - else -> ACTION_PLAY_EPISODE_IN_PLAYER - } + val playerPref = settingsManager.getString(context.getString(R.string.player_default_key), "") + return VideoClickActionHolder.uniqueIdToId(playerPref) ?: ACTION_PLAY_EPISODE_IN_PLAYER } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index a29941d11..b5f83201e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -2,16 +2,11 @@ package com.lagradost.cloudstream3.ui.result import android.app.Activity import android.content.* -import android.net.Uri -import android.os.Build -import android.os.Bundle import android.text.format.Formatter.formatFileSize import android.util.Log import android.widget.Toast import androidx.annotation.MainThread import androidx.appcompat.app.AlertDialog -import androidx.core.content.FileProvider -import androidx.core.net.toUri import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel @@ -30,37 +25,28 @@ import com.lagradost.cloudstream3.LoadResponse.Companion.getAniListId import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId import com.lagradost.cloudstream3.LoadResponse.Companion.isMovie import com.lagradost.cloudstream3.LoadResponse.Companion.readIdFromString -import com.lagradost.cloudstream3.MainActivity.Companion.MPV -import com.lagradost.cloudstream3.MainActivity.Companion.MPV_COMPONENT -import com.lagradost.cloudstream3.MainActivity.Companion.MPV_PACKAGE -import com.lagradost.cloudstream3.MainActivity.Companion.MPV_YTDL -import com.lagradost.cloudstream3.MainActivity.Companion.MPV_YTDL_COMPONENT -import com.lagradost.cloudstream3.MainActivity.Companion.MPV_YTDL_PACKAGE -import com.lagradost.cloudstream3.MainActivity.Companion.VLC -import com.lagradost.cloudstream3.MainActivity.Companion.VLC_COMPONENT -import com.lagradost.cloudstream3.MainActivity.Companion.VLC_PACKAGE -import com.lagradost.cloudstream3.MainActivity.Companion.WEB_VIDEO -import com.lagradost.cloudstream3.MainActivity.Companion.WEB_VIDEO_CAST_PACKAGE +import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.metaproviders.SyncRedirector import com.lagradost.cloudstream3.mvvm.* import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.providers.Kitsu -import com.lagradost.cloudstream3.syncproviders.providers.SimklApi import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.player.GeneratorPlayer import com.lagradost.cloudstream3.ui.player.IGenerator -import com.lagradost.cloudstream3.ui.player.LoadType +import com.lagradost.cloudstream3.ui.player.LOADTYPE_ALL +import com.lagradost.cloudstream3.ui.player.LOADTYPE_CHROMECAST +import com.lagradost.cloudstream3.ui.player.LOADTYPE_INAPP +import com.lagradost.cloudstream3.ui.player.LOADTYPE_INAPP_DOWNLOAD import com.lagradost.cloudstream3.ui.player.RepoLinkGenerator import com.lagradost.cloudstream3.ui.player.SubtitleData import com.lagradost.cloudstream3.ui.result.EpisodeAdapter.Companion.getPlayerAction import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull -import com.lagradost.cloudstream3.utils.AppContextUtils.isAppInstalled import com.lagradost.cloudstream3.utils.AppContextUtils.isConnectedToChromecast import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.AppContextUtils.sortSubs @@ -69,6 +55,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioWork import com.lagradost.cloudstream3.utils.Coroutines.ioWorkSafe import com.lagradost.cloudstream3.utils.Coroutines.main +import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites @@ -96,12 +83,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.setVideoWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.updateSubscribedData import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper import com.lagradost.cloudstream3.utils.UIHelper.navigate -import com.lagradost.cloudstream3.utils.fcast.FcastManager -import com.lagradost.cloudstream3.utils.fcast.FcastSession -import com.lagradost.cloudstream3.utils.fcast.Opcode -import com.lagradost.cloudstream3.utils.fcast.PlayMessage import kotlinx.coroutines.* -import java.io.File import java.util.concurrent.TimeUnit /** This starts at 1 */ @@ -803,7 +785,7 @@ class ResultViewModel2 : ViewModel() { val generator = RepoLinkGenerator(listOf(episode)) val currentLinks = mutableSetOf() val currentSubs = mutableSetOf() - generator.generateLinks(clearCache = false, LoadType.Chromecast, callback = { + generator.generateLinks(clearCache = false, allowedTypes = LOADTYPE_INAPP_DOWNLOAD, callback = { it.first?.let { link -> currentLinks.add(link) } @@ -954,7 +936,7 @@ class ResultViewModel2 : ViewModel() { isVisible: Boolean = true ) { if (activity == null) return - loadLinks(result, isVisible = isVisible, LoadType.Chromecast) { data -> + loadLinks(result, isVisible = isVisible, sourceTypes = LOADTYPE_CHROMECAST, isCasting = true) { data -> startChromecast(activity, result, data.links, data.subs, 0) } } @@ -1264,7 +1246,7 @@ class ResultViewModel2 : ViewModel() { _loadedLinks.postValue(null) } - private fun postPopup(text: UiText, options: List, callback: suspend (Int?) -> Unit) { + fun postPopup(text: UiText, options: List, callback: suspend (Int?) -> Unit) { _selectPopup.postValue( SelectPopup.SelectText( text, @@ -1300,8 +1282,9 @@ class ResultViewModel2 : ViewModel() { private fun loadLinks( result: ResultEpisode, isVisible: Boolean, - type: LoadType, + sourceTypes: Set = LOADTYPE_ALL, clearCache: Boolean = false, + isCasting: Boolean = false, work: suspend (CoroutineScope.(LinkLoadingResult) -> Unit) ) { currentLoadLinkJob?.cancel() @@ -1309,8 +1292,9 @@ class ResultViewModel2 : ViewModel() { val links = loadLinks( result, isVisible = isVisible, - type = type, - clearCache = clearCache + sourceTypes = sourceTypes, + clearCache = clearCache, + isCasting = isCasting ) if (!this.isActive) return@ioSafe work(links) @@ -1320,11 +1304,12 @@ class ResultViewModel2 : ViewModel() { private var currentLoadLinkJob: Job? = null private fun acquireSingleLink( result: ResultEpisode, - type: LoadType, + sourceTypes: Set, text: UiText, - callback: (Pair) -> Unit, + isCasting: Boolean = false, + callback: (Pair) -> Unit ) { - loadLinks(result, isVisible = true, type) { links -> + loadLinks(result, isVisible = true, sourceTypes, isCasting = isCasting) { links -> // Could not find a better way to do this val context = AcraApplication.context postPopup( @@ -1344,7 +1329,7 @@ class ResultViewModel2 : ViewModel() { text: UiText, callback: (Pair) -> Unit, ) { - loadLinks(result, isVisible = true, type = LoadType.Unknown) { links -> + loadLinks(result, isVisible = true) { links -> postPopup( text, links.subs.map { txt(it.name) }) @@ -1357,8 +1342,9 @@ class ResultViewModel2 : ViewModel() { private suspend fun CoroutineScope.loadLinks( result: ResultEpisode, isVisible: Boolean, - type: LoadType, + sourceTypes: Set = LOADTYPE_ALL, clearCache: Boolean = false, + isCasting: Boolean = false ): LinkLoadingResult { val tempGenerator = RepoLinkGenerator(listOf(result)) @@ -1371,15 +1357,19 @@ class ResultViewModel2 : ViewModel() { } try { updatePage() - tempGenerator.generateLinks(clearCache, type, { (link, _) -> - if (link != null) { - links += link - updatePage() - } - }, { sub -> + tempGenerator.generateLinks(clearCache, + allowedTypes = sourceTypes, + callback = { (link, _) -> + if (link != null) { + links += link + updatePage() + } + }, + subtitleCallback = { sub -> subs += sub updatePage() - }) + }, + isCasting = isCasting) } catch (e: Exception) { logError(e) } finally { @@ -1389,185 +1379,11 @@ class ResultViewModel2 : ViewModel() { return LinkLoadingResult(sortUrls(links), sortSubs(subs)) } - private fun launchActivity( - activity: Activity?, - resumeApp: MainActivity.Companion.ResultResume, - id: Int? = null, - work: suspend (Intent.(Activity) -> Unit) - ): Job? { - val act = activity ?: return null - return CoroutineScope(Dispatchers.IO).launch { - try { - resumeApp.launch(id) { - work(act) - } - } catch (t: Throwable) { - logError(t) - main { - if (t is ActivityNotFoundException) { - showToast(txt(R.string.app_not_found_error), Toast.LENGTH_LONG) - } else { - showToast(t.toString(), Toast.LENGTH_LONG) - } - } - } - } - } - - private fun playInWebVideo( - activity: Activity?, - link: ExtractorLink, - title: String?, - posterUrl: String?, - subtitles: List - ) = launchActivity(activity, WEB_VIDEO) { - setDataAndType(Uri.parse(link.url), "video/*") - - putExtra("subs", subtitles.map { it.url.toUri() }.toTypedArray()) - title?.let { putExtra("title", title) } - posterUrl?.let { putExtra("poster", posterUrl) } - val headers = Bundle().apply { - if (link.referer.isNotBlank()) - putString("Referer", link.referer) - putString("User-Agent", USER_AGENT) - for ((key, value) in link.headers) { - putString(key, value) - } - } - putExtra("android.media.intent.extra.HTTP_HEADERS", headers) - putExtra("secure_uri", true) - } - - private fun playWithMpv( - activity: Activity?, - id: Int, - link: ExtractorLink, - subtitles: List, - resume: Boolean = true, - ) = launchActivity(activity, MPV, id) { - putExtra("subs", subtitles.map { it.url.toUri() }.toTypedArray()) - putExtra("subs.name", subtitles.map { it.name }.toTypedArray()) - putExtra("subs.filename", subtitles.map { it.name }.toTypedArray()) - setDataAndType(Uri.parse(link.url), "video/*") - component = MPV_COMPONENT - putExtra("secure_uri", true) - putExtra("return_result", true) - val position = getViewPos(id)?.position - if (resume && position != null) - putExtra("position", position.toInt()) - } - - private fun playWithMpvYtdl( - activity: Activity?, - id: Int, - link: ExtractorLink, - subtitles: List, - resume: Boolean = true, - ) = launchActivity(activity, MPV_YTDL, id) { - putExtra("subs", subtitles.map { it.url.toUri() }.toTypedArray()) - putExtra("subs.name", subtitles.map { it.name }.toTypedArray()) - putExtra("subs.filename", subtitles.map { it.name }.toTypedArray()) - setDataAndType(Uri.parse(link.url), "video/*") - component = MPV_YTDL_COMPONENT - putExtra("secure_uri", true) - putExtra("return_result", true) - val position = getViewPos(id)?.position - if (resume && position != null) - putExtra("position", position.toInt()) - } - - // https://wiki.videolan.org/Android_Player_Intents/ - private fun playWithVlc( - activity: Activity?, - data: LinkLoadingResult, - id: Int, - resume: Boolean = true, - // if it is only a single link then resume works correctly - singleFile: Boolean? = null - ) = launchActivity(activity, VLC, id) { act -> - addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) - - val outputDir = act.cacheDir - - if (singleFile ?: (data.links.size == 1)) { - setDataAndType(data.links.first().url.toUri(), "video/*") - } else { - val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir) - - var text = "#EXTM3U" - - // With subtitles it doesn't work for no reason :( -// for (sub in data.subs) { -// text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${sub.name}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.name}\",URI=\"${sub.url}\"" -// } - for (link in data.links) { - text += "\n#EXTINF:, ${link.name}\n${link.url}" - } - outputFile.writeText(text) - - setDataAndType( - FileProvider.getUriForFile( - act, - act.applicationContext.packageName + ".provider", - outputFile - ), "video/*" - ) - } - - val position = if (resume) { - getViewPos(id)?.position ?: 0L - } else { - 1L - } - - // Component no longer safe to use in A13 for VLC - // https://code.videolan.org/videolan/vlc-android/-/issues/2776 - // This will likely need to be updated once VLC fixes their documentation. - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) { - component = VLC_COMPONENT - } - - putExtra("from_start", !resume) - putExtra("position", position) - } - - fun handleAction(click: EpisodeClickEvent) = viewModelScope.launchSafe { handleEpisodeClickEvent(click) } - data class ExternalApp( - val packageString: String, - val name: Int, - val action: Int, - ) - - private val apps = listOf( - ExternalApp( - VLC_PACKAGE, - R.string.player_settings_play_in_vlc, - ACTION_PLAY_EPISODE_IN_VLC_PLAYER - ), ExternalApp( - WEB_VIDEO_CAST_PACKAGE, - R.string.player_settings_play_in_web, - ACTION_PLAY_EPISODE_IN_WEB_VIDEO - ), - ExternalApp( - MPV_PACKAGE, - R.string.player_settings_play_in_mpv, - ACTION_PLAY_EPISODE_IN_MPV - ), - ExternalApp( - MPV_YTDL_PACKAGE, - R.string.player_settings_play_in_mpvytdl, - ACTION_PLAY_EPISODE_IN_MPV_YTDL - ) - ) - fun releaseEpisodeSynopsis() { _episodeSynopsis.postValue(null) } @@ -1576,6 +1392,7 @@ class ResultViewModel2 : ViewModel() { when (click.action) { ACTION_SHOW_OPTIONS -> { val options = mutableListOf>() + if (activity?.isConnectedToChromecast() == true) { options.addAll( listOf( @@ -1585,29 +1402,10 @@ class ResultViewModel2 : ViewModel() { ) } - if (FcastManager.currentDevices.isNotEmpty()) { - options.add( - txt(R.string.player_settings_play_in_fcast) to ACTION_FCAST - ) - } - options.add(txt(R.string.episode_action_play_in_app) to ACTION_PLAY_EPISODE_IN_PLAYER) - for (app in apps) { - if (activity?.isAppInstalled(app.packageString) == true) { - options.add( - txt( - R.string.episode_action_play_in_format, - txt(app.name) - ) to app.action - ) - } - } - options.addAll( listOf( - txt(R.string.episode_action_play_in_browser) to ACTION_PLAY_EPISODE_IN_BROWSER, - txt(R.string.episode_action_copy_link) to ACTION_COPY_LINK, txt(R.string.episode_action_auto_download) to ACTION_DOWNLOAD_EPISODE, txt(R.string.episode_action_download_mirror) to ACTION_DOWNLOAD_MIRROR, txt(R.string.episode_action_download_subtitle) to ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR, @@ -1615,6 +1413,10 @@ class ResultViewModel2 : ViewModel() { ) ) + options.addAll( + VideoClickActionHolder.makeOptionMap(activity, click.data) + ) + // Do not add mark as watched on movies if (!listOf(TvType.Movie, TvType.AnimeMovie).contains(click.data.tvType)) { val isWatched = @@ -1716,7 +1518,7 @@ class ResultViewModel2 : ViewModel() { val response = currentResponse ?: return acquireSingleLink( click.data, - LoadType.InAppDownload, + LOADTYPE_INAPP_DOWNLOAD, txt(R.string.episode_action_download_mirror) ) { (result, index) -> ioSafe { @@ -1746,7 +1548,7 @@ class ResultViewModel2 : ViewModel() { loadLinks( click.data, isVisible = false, - type = LoadType.InApp, + LOADTYPE_INAPP, clearCache = true ) } @@ -1759,139 +1561,18 @@ class ResultViewModel2 : ViewModel() { ACTION_CHROME_CAST_MIRROR -> { acquireSingleLink( click.data, - LoadType.Chromecast, - txt(R.string.episode_action_chromecast_mirror) + LOADTYPE_CHROMECAST, + txt(R.string.episode_action_chromecast_mirror), + isCasting = true ) { (result, index) -> startChromecast(activity, click.data, result.links, result.subs, index) } } - ACTION_FCAST -> { - val devices = FcastManager.currentDevices.toList() - postPopup( - txt(R.string.player_settings_select_cast_device), - devices.map { txt(it.name) }) { index -> - if (index == null) return@postPopup - val device = devices.getOrNull(index) - - acquireSingleLink( - click.data, - LoadType.Fcast, - txt(R.string.episode_action_cast_mirror) - ) { (result, index) -> - val host = device?.host ?: return@acquireSingleLink - val link = result.links.getOrNull(index) ?: return@acquireSingleLink - - FcastSession(host).use { session -> - session.sendMessage( - Opcode.Play, - PlayMessage( - link.type.getMimeType(), - link.url, - headers = mapOf( - "referer" to link.referer, - "user-agent" to USER_AGENT - ) + link.headers - ) - ) - } - } - } - } - - ACTION_PLAY_EPISODE_IN_BROWSER -> acquireSingleLink( - click.data, - LoadType.Browser, - txt(R.string.episode_action_play_in_browser) - ) { (result, index) -> - try { - val i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse(result.links[index].url) - activity?.startActivity(i) - } catch (e: Exception) { - logError(e) - } - } - - ACTION_COPY_LINK -> { - acquireSingleLink( - click.data, - LoadType.ExternalApp, - txt(R.string.episode_action_copy_link) - ) { (result, index) -> - val link = result.links[index] - clipboardHelper(txt(link.name), link.url) - } - } - ACTION_CHROME_CAST_EPISODE -> { startChromecast(activity, click.data) } - ACTION_PLAY_EPISODE_IN_VLC_PLAYER -> { - loadLinks(click.data, isVisible = true, LoadType.ExternalApp) { links -> - if (links.links.isEmpty()) { - showToast(R.string.no_links_found_toast, Toast.LENGTH_SHORT) - return@loadLinks - } - - playWithVlc( - activity, - links, - click.data.id - ) - } - } - - ACTION_PLAY_EPISODE_IN_WEB_VIDEO -> acquireSingleLink( - click.data, - LoadType.Chromecast, - txt( - R.string.episode_action_play_in_format, - txt(R.string.player_settings_play_in_web) - ) - ) { (result, index) -> - playInWebVideo( - activity, - result.links[index], - click.data.name ?: click.data.headerName, - click.data.poster, - result.subs - ) - } - - ACTION_PLAY_EPISODE_IN_MPV -> acquireSingleLink( - click.data, - LoadType.Chromecast, - txt( - R.string.episode_action_play_in_format, - txt(R.string.player_settings_play_in_mpv) - ) - ) { (result, index) -> - playWithMpv( - activity, - click.data.id, - result.links[index], - result.subs - ) - } - - ACTION_PLAY_EPISODE_IN_MPV_YTDL -> acquireSingleLink( - click.data, - LoadType.Chromecast, - txt( - R.string.episode_action_play_in_format, - txt(R.string.player_settings_play_in_mpvytdl) - ) - ) { (result, index) -> - playWithMpvYtdl( - activity, - click.data.id, - result.links[index], - result.subs - ) - } - ACTION_PLAY_EPISODE_IN_PLAYER -> { val data = currentResponse?.syncData?.toList() ?: emptyList() val list = @@ -1907,7 +1588,7 @@ class ResultViewModel2 : ViewModel() { if (currentResponse?.type == TvType.CustomMedia) { generator?.generateLinks( clearCache = true, - LoadType.Unknown, + LOADTYPE_ALL, callback = {}, subtitleCallback = {}) } else { @@ -1933,6 +1614,35 @@ class ResultViewModel2 : ViewModel() { // Kinda dirty to reload all episodes :( reloadEpisodes() } + + else -> { + val action = VideoClickActionHolder.getActionById(click.action) ?: return + + activity?.setKey("last_click_action", action.uniqueId()) + if (action.oneSource) { + acquireSingleLink( + click.data, + action.sourceTypes, + action.name + ) { (result, index) -> + action.runAction( + activity, + click.data, + result, + index + ) + } + } else { + loadLinks(click.data, isVisible = true, action.sourceTypes) { links -> + action.runAction( + activity, + click.data, + links, + null + ) + } + } + } } } @@ -2040,7 +1750,7 @@ class ResultViewModel2 : ViewModel() { isResponseRequired = false ) if (map.isNullOrEmpty()) return@argamap - updateEpisodes = DubStatus.values().map { dubStatus -> + updateEpisodes = DubStatus.entries.map { dubStatus -> val current = this.episodes[dubStatus]?.mapIndexed { index, episode -> episode.apply { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt index 1753032ac..17580236f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt @@ -6,6 +6,7 @@ import android.view.View import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.PHONE @@ -155,10 +156,17 @@ class SettingsPlayer : PreferenceFragmentCompat() { return@setOnPreferenceClickListener true } - getPref(R.string.player_pref_key)?.setOnPreferenceClickListener { - val prefNames = resources.getStringArray(R.array.player_pref_names) - val prefValues = resources.getIntArray(R.array.player_pref_values) - val current = settingsManager.getInt(getString(R.string.player_pref_key), 1) + getPref(R.string.player_default_key)?.setOnPreferenceClickListener { + val players = VideoClickActionHolder.getPlayers(activity) + val prefNames = buildList { + add(getString(R.string.player_settings_play_in_app)) + addAll(players.map { it.name.asStringNull(activity) ?: it.javaClass.simpleName }) + } + val prefValues = buildList { + add("") + addAll(players.map { it.uniqueId() }) + } + val current = settingsManager.getString(getString(R.string.player_default_key), "") ?: "" activity?.showBottomDialog( prefNames.toList(), @@ -166,7 +174,7 @@ class SettingsPlayer : PreferenceFragmentCompat() { getString(R.string.player_pref), true, {}) { - settingsManager.edit().putInt(getString(R.string.player_pref_key), prefValues[it]).apply() + settingsManager.edit().putString(getString(R.string.player_default_key), prefValues[it]).apply() } return@setOnPreferenceClickListener true } diff --git a/app/src/main/res/values-ajp/strings.xml b/app/src/main/res/values-ajp/strings.xml index 6896cf2af..0b30cebac 100644 --- a/app/src/main/res/values-ajp/strings.xml +++ b/app/src/main/res/values-ajp/strings.xml @@ -221,9 +221,7 @@ بسبِب أعطال إزا نحط على مستوى عالي كتير على الأجهزة يللي م بتساع كتير، متل تلفزيون \"أندرويد\". شي غير أفي هيدا التجديد - نسوخ الرابط مَشي بال آپ - مشي بمتصفح الويب مفيد لتجاوز المنع من مزود خدمة الإنترنت مسلسل غير الحجم @@ -390,10 +388,8 @@ م قدرنا ننزل الإصدار الجديد تبع الآپ المؤلفين إضافة - كاست ڤيديو ع الوَب معقول يكون موجود أصلًا مشتركينلو - متصفح الوَب كل اللغات دايمًا كتوب ب أحرف كاپيتال، A بدل a مشغل الڤيديو المفضل @@ -422,7 +418,6 @@ خلصت محي السجل تجَدَد (من قديم للجديد) - \"ڤي أل سي ميديا پلاير\" كاميرا وَب أبجديًا (من الياء للألف) @@ -504,7 +499,6 @@ مزامنة شوفو معلومات عن المشكلة مدعوم - \"أم پي ڤي\" ظبّط وقت الترجمة افتتاح مختلط مقطع دعائي @@ -627,7 +621,6 @@ رح ينزل ب %s الحلقة ال %2$d من الجزء ال%1$d رح تنزل ب كاست مراية - إف كاست نقي جهاز الكاست ويكي \"كلود ستريم\" أكونتات @@ -669,6 +662,5 @@ \n%s صورة زغيرة مع التقريب وال تبعيد بت حط صورة زغير من الڤيديو إنت و عم بت قرب أو ترجع بال ڤيديو - MPV YTDL بعد مش معمول لود لولا ترجمة \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 25118d963..56bb4b4a0 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -233,8 +233,6 @@ مرآة كروم كاست تشغيل في التطبيق %s تشغيل في - تشغيل في الويب - نسخ الرابط التحميل التلقائي تحميل بجودات مختلفة إعادة تحميل الروابط @@ -464,10 +462,6 @@ فتح(تشغيل) %1$s %2$d%3$s المكونات الإضافية المحدثة %d - VLC - MPV - اسقاط فيديو الويب - متصفح الإنترنت تخطي %s الافتتاح النهاية @@ -653,7 +647,6 @@ قادم خلال %s سيتم إصدار الحلقة %1$d من الموسم %2$d في مرآة البث - بث ف حدد جهاز البث CloudStream ويكي إعدادات الأمان @@ -695,6 +688,5 @@ \n%s معاينة شريط البحث تمكين معاينة الصورة المصغرة على شريط البحث - MPV YTDL لم يتم تحميل أي ترجمات بعد \ No newline at end of file diff --git a/app/src/main/res/values-ars/strings.xml b/app/src/main/res/values-ars/strings.xml index 873c55bf3..d1efdbbc5 100644 --- a/app/src/main/res/values-ars/strings.xml +++ b/app/src/main/res/values-ars/strings.xml @@ -311,11 +311,9 @@ اخرون تخطي هذا التحديث .قد يتسبب في تأخير التحديثات لبضعة أيام .jsDelivr باستخدام GitHubيتجاوز حظر - انسخ الرابط الدرامات الآسيوية في قائمة الانتظار افتح في التطبيق - افتح في المتصفح مفيد لتجاوز حجب مزودي خدمة الإنترنت مسلسل تقييم diff --git a/app/src/main/res/values-as/strings.xml b/app/src/main/res/values-as/strings.xml index fc50d2d02..2fdd469b4 100644 --- a/app/src/main/res/values-as/strings.xml +++ b/app/src/main/res/values-as/strings.xml @@ -89,7 +89,6 @@ সম্পূৰ্ণ সৰ্বজনীন তালিকা বন্ধ কৰক - VLC বেটাৰী অপ্টিমাইজেচন নিষ্ক্ৰিয় কৰক সদস্যতা গ্ৰহণ কৰা গুণসমূহ @@ -302,8 +301,6 @@ কাষ্ট মিৰৰ ডাব ছপা প্লে %s ত - ব্ৰাউজাৰত প্লে কৰক - লিংক কপি কৰক স্বয়ংক্ৰিয় ডাউনলোড ডাউনলোড মিৰৰ সাব ছপা @@ -498,10 +495,6 @@ HLS প্লেলিস্ট পছন্দৰ ভিডিঅ\' প্লেয়াৰ আভ্যন্তৰীণ প্লেয়াৰ - MPV - ৱেব ভিডিঅ\' কাষ্ট - Fcast - ৱেব ব্ৰাউজাৰ কাষ্ট ডিভাইচ চয়ন কৰক মিশ্ৰিত সমাপ্তি মিশ্ৰিত উদ্‌ঘাটনী @@ -650,7 +643,6 @@ ডিভাইচ পিন ক\'ড পোৱা নাই, স্থানীয় প্ৰমাণীকৰণ চেষ্টা কৰক পিন ক\'ডৰ মেয়াদ শেষ হৈছে! ক\'ড মেয়াদ শেষ হব %1$dm %2$ds - MPV YTDL সীকবৰ প্ৰিভিউ সীকবৰত প্ৰিভিউ থাম্বনেইল সক্ৰিয় কৰক আৰম্ভৰ পৰা প্লে কৰক diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 3579c8353..218fa5e57 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -238,8 +238,6 @@ Chromecast огледало Пусни в приложението Пусни в %s - Пусни в браузър - Копирай връзка Автоматично изтегляне Изтегляне на огледало Презареждане на връзки @@ -443,10 +441,6 @@ HLS плейлист Предпочитан видео плеър Вътрешен плеър - VLC - MPV - Уеб видео предаване - Уеб браузър Приложението не е намерено Автоматично изтегли добавки Всички езици diff --git a/app/src/main/res/values-bn/strings.xml b/app/src/main/res/values-bn/strings.xml index c5ed86514..f1bc8f82a 100644 --- a/app/src/main/res/values-bn/strings.xml +++ b/app/src/main/res/values-bn/strings.xml @@ -230,7 +230,6 @@ প্লাগইন ডাউনলোড ফিল্টার করতে মোড নির্বাচন করুন লিঙ্ক পুনরায় লোড হয়েছে সুইচ অ্যাকাউন্ট - ব্রাউজারে প্লে করুন দাবিত্যাগ এশিয়ান ড্রামা সোর্স @@ -274,7 +273,6 @@ টরেন্ট এপিসোড ক্রোমকাস্ট করুন প্লে হচ্ছে %s সময়ের মধ্যে - লিঙ্ক কপি করুন স্বয়ংক্রিয় ডাউনলোড টাইটেল প্লেয়ার দেখা যাচ্ছে - সিকের পরিমাণ diff --git a/app/src/main/res/values-bp/strings.xml b/app/src/main/res/values-bp/strings.xml index 4c29a99e7..55e1ce180 100644 --- a/app/src/main/res/values-bp/strings.xml +++ b/app/src/main/res/values-bp/strings.xml @@ -235,8 +235,6 @@ Alternativa pelo Chromecast Assistir no App Assistir no %s - Assistir no navegador - Copiar link Auto download Baixar por servidor alternativo Recarregar links @@ -538,16 +536,13 @@ Começar Suportado Status - MPV Abrindo mistura - VLC Reinicie o aplicativo para ver as alterações. Visualização info de crash Faixas de áudio Adicionado em (novo para antigo) Faixas de video Legendas - Navegador 18+ Links Funcionalidades do Player @@ -563,7 +558,6 @@ Vídeo Android TV Wi-Fi - Lista de videos da web A interface de usuário não foi gerada corretamente. Isto se trata de um bug importante e deve ser reportado imediatamente %s Características da interface de usuário Provedor de teste @@ -642,7 +636,6 @@ Redefinir Próximos em %s Temporada %1$d Episódio %2$d será lançado em - Fcast Selecione o dispositivo de transmissão Espelhar transmissão CloudStream Wiki @@ -685,6 +678,5 @@ Desmarcar todos Ativar visualização de miniatura na barra de busca Visualização da barra de busca - MPV + YTDL Ainda não há legendas carregadas \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 04b4b213b..ba03b05fb 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -223,8 +223,6 @@ Chromecast jako zrcadlo Přehrát v aplikace Přehrát ve %s - Přehrát v prohlížeči - Zkopírovat odkaz Automaticky stáhnout Zrcadlo stahování Obnovit odkazy @@ -400,8 +398,6 @@ Veřejný seznam Velká písmena u všech titulků Playlist HLS - MPV - Webové vysílání videa Aplikace nenalezena Přeskočit %s Úvod @@ -451,13 +447,11 @@ Popis Stav Nejprve nainstalujte rozšíření - VLC Smíšený konec Jazyk Interní přehrávač Rekapitulace Vymazat historii - Webový prohlížeč Všechny jazyky Smíšený úvod Poděkování @@ -645,7 +639,6 @@ Vychází %s Epizoda %2$d ze série %1$d bude vydána za Vysílat zrcadlení - Fcast Vyberte zařízení k vysílání CloudStream Wiki Zabezpečení @@ -687,6 +680,5 @@ Odstranit (%1$d | %2$s) Náhled v liště přehrávače Povolit náhled miniatur na liště přehrávače - MPV YTDL Zatím nenačteny žádné titulky \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c9656d9de..6cf8ff3bd 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -241,8 +241,6 @@ Downloadfehler, bitte überprüfen sie die Speicherberechtigungen Chromecast-Episode In %s wiedergeben - In Browser wiedergeben - Link kopieren Auto-Download Alternativer Download Links neu laden @@ -437,10 +435,6 @@ HLS-Playlist Bevorzugter Videoplayer Interner Player - VLC - MPV - Web Video Cast - Browser App nicht gefunden Alle Sprachen Überspringen %s @@ -618,7 +612,6 @@ hide_player_control_names_key Staffel %1$d Episode %2$d wird veröffentlicht in Wird veröffentlicht in %s - Fcast Sicherheit Konten Repository öffnen diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index e22182d23..268ea7bd1 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -194,8 +194,6 @@ Chromecast επεισόδιο Αναπαραγωγή εντός της εφαρμογής Αναπαραγωγή σε %s - Αναπαραγωγή στον περιηγητή - Αντιγραφή συνδέσμου Αυτόματη λήψη Λήψη mirror Επαναφόρτωση συνδέσμων @@ -359,10 +357,6 @@ HLS Playlist Προτεινόμενο πρόγραμμα αναπαραγωγής Ενσωματωμένο πρόγραμμα αναπαραγωγής - VLC - MPV - Web Video Cast - Περιηγητής Η εφαρμογή δεν βρέθηκε %1$s Επ %2$d Το επεισόδιο %d θα κυκλοφορήσει σε @@ -580,7 +574,6 @@ Δευτερόλεπτα Σκιπ όταν φαίνεται ο αναπαραγωγέας (πλειερ) Δοκιμή όλων των παροχών Αυτό το τεστ προορίζεται μόνο για τους προγραμματιστές και δε επαληθείει ούτε απορρίπτει την λειτουργία οποιουδήποτε παρόχου. - Fcast Επιλογή συσκευής για αναμετάδοση Πρόβλημα στην πρόσβαση στο Clipboard, Παρακαλώ προσπαθήστε ξανά. Πρόβλημα στην αντιγραφή , Παρακαλούμε αντιγράψτε το logcat και επικοινωνήστε με την υποστήριξη. diff --git a/app/src/main/res/values-es/array.xml b/app/src/main/res/values-es/array.xml index eb197f43e..fddd832a5 100644 --- a/app/src/main/res/values-es/array.xml +++ b/app/src/main/res/values-es/array.xml @@ -152,32 +152,6 @@ @string/show_title_key - - @string/episode_action_chromecast_episode - @string/episode_action_chromecast_mirror - @string/episode_action_play_in_app - @string/episode_action_play_in_format - @string/episode_action_play_in_browser - @string/episode_action_copy_link - @string/episode_action_auto_download - @string/episode_action_download_mirror - @string/episode_action_download_subtitle - @string/episode_action_reload_links - - - - 4 - 5 - 1 - 2 - 3 - 9 - 6 - 7 - 13 - 8 - - @string/automatic @string/phone_layout diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 155c5f746..a0c4587f5 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -57,11 +57,7 @@ Cantidad de búsquedas del reproductor (segundos) Use el brillo del sistema en el reproductor de la app en lugar de una superposición oscura Resolución del reproductor de video - MPV Reproductor - VLC - Web Video Cast - Navegador Web Iniciar el siguiente episodio cuando el actual termine Omitir Intro Apertura @@ -78,9 +74,7 @@ Actualizar progreso de lo visto Duplicar en Chromecast No se encontraron Episodios - Reproducir en Navegador Reproducir en %s - Copiar enlace Descarga automática Descargar desde servidor alternativo Recargar enlaces @@ -621,7 +615,6 @@ Próximamente en %s La temporada %1$d y el episodio %2$d se estrenarán en Seleccionar el dispositivo para transmitir - Fcast Espejo de transmisión Wiki de CloudStream Seguridad @@ -663,6 +656,5 @@ \n%s Activar la previsualización para las miniaturas en la barra de búsqueda Previsualización de Seekbar - MPV YTDL Aún no hay subtítulos cargados \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 0483cb2ea..5b3f09120 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -111,8 +111,6 @@ Miroir Chromecast Lecture dans l\'application Lecture dans %s - Lecture dans le navigateur - Copier le lien Téléchargement automatique Télécharger depuis le miroir Recharger les liens @@ -140,7 +138,7 @@ DNS avec HTTPS Afficher les animés en Anglais (Dub) / sous-titrés Disposition en mode téléphone - %1$s Episode %2$d + episode_action_copy_link Note : %.1f Zoom Adapter à l\'écran @@ -430,12 +428,10 @@ Installer l\'extension d\'abord Playlist HLS Lecteur vidéo préféré - VLC Fin mitigée Introduction mitigée Installation de la mise a jour de l\'application… Impossible d\'installer la nouvelle version de l\'application - Navigateur Web Certains téléphones ne supporte pas le nouvel installateur d\'application. Essayez l\'option de l\'ancien installateur si les mises-à-jour ne s\'installe pas. Précédent Ignorer la configuration @@ -456,7 +452,6 @@ Lecteur interne Application introuvable Trop de texte. Impossible de sauvegarder dans le presse papier. - MPV Installateur de paquet plugins Cela supprimera également tous les plugins du repository @@ -466,7 +461,6 @@ Langage Afficher les popups skip pour les intro / fins Ancienne méthode d\'installation - Web Video Cast Liens Gestes Fonctionnalités du lecteur diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index eed5e44aa..4457b0ec4 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -114,8 +114,6 @@ क्रोमकास्ट मिरर एप्प में चलाएं %s में चलाएं - Browser में चलाएं - लिंक कॉपी करें डाउनलोड करें मिरर डाउनलोड लिंक दोबारा लोड करें diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 44f1bd5b9..dcacbeee3 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -253,8 +253,6 @@ Chromecast mirror Pokreni u aplikaciji Pokreni u %s - Pokreni u pregledniku - Kopiraj poveznicu Automatsko preuzimanje Preuzmi zrcalo Ponovo učitaj poveznice @@ -461,9 +459,6 @@ Preferirani video player Interni player Najprije instaliraj proširenje - VLC - MPV - Emitiranje na webu Aplikacija nije pronađena Svi jezici Previše teksta. Nije moguće spremiti u međuspremnik. @@ -495,7 +490,6 @@ Zadane postavke Izgledi Značajke - Web preglednik Preskoči %s Kraj Sažetak @@ -646,7 +640,6 @@ Vaši CloudStream podaci su sada spremljeni u sigurnosnu kopiju. Iako je vjerojatnost mala, neki se uređaji mogu ponašati drugačije. Ako izgubite pristup aplikaciji, potpuno izbrišite podatke aplikacije i obnovite ih pomoću sigurnosne kopije. Ispričavamo se zbog mogućih neugodnosti. Sezona %1$d epizoda %2$d izlazi Cast mirror - Fcast Odaberi uređaj za emitiranje CloudStream Wiki Računi @@ -686,6 +679,5 @@ Stvarno želite trajno izbrisati sve epizode u sljedećoj seriji? \n \n%s - MPV YTDL Još nije učitan nijedan titl \ No newline at end of file diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 1426e8a38..f124ad062 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -182,7 +182,6 @@ Dokumentumfilm Ázsiai dráma Linkek újratöltése - Link másolás Letöltés mirror Automatikus letöltés Adatok eltárolva @@ -227,7 +226,6 @@ Chromecast mirror Lejátszás az alkalmazásban Lejátszás %s - Lejátszás böngészőben Feliratok letöltése Újracsatlakozás… Húzd balra vagy jobbra a videólejátszóban az idő vezérléséhez @@ -352,7 +350,6 @@ Mit szeretnél látni Minden %s már letöltött Először telepítse a bővítményt - Webböngésző Kinézet Alkalmazás elrendezés Szinkronizálás @@ -368,7 +365,6 @@ Töltse le az összes bővítményt ebből a tárolóból? Biztonságos mód bekapcsolva Méret - MPV Alkalmazás nem található PackageInstaller Rendezés e szerint @@ -403,7 +399,6 @@ Emelt HD HLS lejátszási lista - VLC Nem sikerült telepíteni az alkalmazás új verzióját %s hitelesítve Körvonal @@ -528,7 +523,6 @@ Hivatkozó (opcionális) Nem találhatóak pluginek a repóban Repó nem található, ellenőrizze a címet vagy próbálja VPN-el - Web Videó Cast %s kihagyása A kihagyási felugró ablakok mutatása nyitás/zárás esetén Alapbeállítás diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 09dfb5322..efd0ed0cf 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -221,8 +221,6 @@ Mirror Chromecast Putar di aplikasi Putar di %s - Putar di browser - Salin tautan Download otomatis Download mirror Muat ulang tautan @@ -442,8 +440,6 @@ Bahasa Pemutar video utama Pemutar Bawaan - VLC - MPV Terunduh %1$d %2$s Memulai mengunduh %1$d %2$s… Semua fitur tambahkan dimatikan karena crash, untuk memudahkanmu mencari penyebab crash. @@ -495,9 +491,7 @@ Tidak Memasang pembaruan… Tidak dapat memasang versi terbaru - Web browser Aplikasi tidak ditemukan - Web Video Cast Hapus Riwayat Tampilkan popup untuk skip sesi pembuka/akhir Mengunduh pembaruan… @@ -643,7 +637,6 @@ Akan datang di %s Cermin Cast Pilih perangkat cast - Fcast CloudStream Wiki Keamanan Akun diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index f498ccae1..590c167d9 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -243,8 +243,6 @@ Mirror Chromecast Riproduci in app Riproduci in %s - Riproduci nel browser - Copia link Download Mirror download Aggiorna link @@ -448,10 +446,6 @@ Playlist HLS Video player preferito Player interno - VLC - MPV - Cast Web Video - Web browser App non trovata Tutte le lingue Salta %s @@ -642,7 +636,6 @@ L\'episodio %2$d della stagione %1$d uscirà tra Mirror cast Seleziona dispositivo per cast - Fcast Wiki di CloudStream Conti Sicurezza @@ -683,6 +676,5 @@ \n%s Anteprima barra di ricerca Abilita miniatura di anteprima sulla barra di ricerca - MPV YTDL Nessun sottotitolo caricato \ No newline at end of file diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 6316d7f79..6ebe405bc 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -168,7 +168,6 @@ דרמה אסייתית כרומקאסט את הפרק כרומקאסט את המראה - נגן בדפדפן תווית כתוביות החלף רכיבי ממשק משתמש בפוסטר דלג על הפתיח @@ -326,7 +325,6 @@ שגיאת הורדה, בדוק הרשאות אחסון נגן באפליקציה נגן ב %s - העתק קישור הורדה אוטומטית טען מחדש קישורים תווית איכות @@ -431,7 +429,6 @@ כל %s כבר הורד מחברים שפה - MPV קרדיטים מיין בחר ספרייה @@ -445,8 +442,6 @@ הורד את כל התוספים ממאגר זה? רצועות שמע מסלולים - Web Video Cast - דפדפן אינטרנט כל התוספים נכבו עקב התרסקות כדי לעזור לך למצוא את האחד הגורם לצרות. מוריד החבילות עודכן %d תוספים @@ -479,7 +474,6 @@ פלייליסט HLS נגן וידאו מועדף נגן פנימי - VLC האפליקציה לא נמצאה כל השפות דלג %s diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index ed9850e8e..3ca79b3d5 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -144,9 +144,6 @@ 完成 進行中 デフォルト - ウェブブラウザ - VLC - MPV 言語 作成者 サイズ diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 6993ec1c9..d62ecb69b 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -186,8 +186,6 @@ Chromecast 미러링 앱에서 재생 %s에서 재생 - 브라우저에서 재생 - 링크 복사 자동 다운로드 다운로드 미러 링크 새로고침 @@ -445,7 +443,6 @@ 소개 유형 먼저 확장 프로그램을 설치하세요 - 웹 브라우저 앱을 찾을 수 없음 모든 언어 건너뛰기 %s @@ -482,11 +479,8 @@ \n파일이 제거될 때까지 시작 시 확장 프로그램을 로드하지 않습니다. HLS 재생목록 내부 플레이어 - MPV 선호하는 동영상 플레이어 - VLC 라이브러리 선택 - 웹 동영상 캐스트 이 목록이 비어 있습니다. 다른 목록으로 전환해 보세요. 필러 라이브 스트리밍 재생 @@ -579,7 +573,6 @@ 자동 회전 모바일 데이터 사용 불가능 - fcast 캐스트 장치 선택 복사하는 중 오류가 발생했습니다. 로그캣을 복사하고 문의하십시오. 구독 취소 diff --git a/app/src/main/res/values-lt/strings.xml b/app/src/main/res/values-lt/strings.xml index fe205dab7..cc68d77eb 100644 --- a/app/src/main/res/values-lt/strings.xml +++ b/app/src/main/res/values-lt/strings.xml @@ -163,10 +163,8 @@ Įvertinta Praleisti šį atnaujinimą Veiksmai - Kopijuoti nuorodą Paleisti programoje Sinchronizuoti - Paleisti naršyklėje Pašalinti puslapį Perkrauti nuorodos Išjungti @@ -192,7 +190,6 @@ Mobilūs duomenys šaunusPrisijungimoVardas Autoriai - Naršyklė Visos kalbos 4K Pradėta siųsti %1$d %2$s… @@ -206,7 +203,6 @@ Kalbos kodas (lt) Baigta Išvalyti istoriją - VLC Redaguoti Wi-Fi Greitai būs… @@ -227,7 +223,6 @@ UHD Dydis Palaikoma - MPV ManoŠaunusPuslapis Anonsas Istorija diff --git a/app/src/main/res/values-lv/strings.xml b/app/src/main/res/values-lv/strings.xml index 8f13b93c0..9469aa8e8 100644 --- a/app/src/main/res/values-lv/strings.xml +++ b/app/src/main/res/values-lv/strings.xml @@ -241,8 +241,6 @@ Chromecast morror Palaist aplikācijā Atskaņot uekšā %s - Atskaņot internetā - Kopēt linku Automātiski ielādēt Ielādēt spoguli Pārlādēt saites @@ -437,8 +435,6 @@ HLS atskaņošanas saraksts Vēlamais video atskaņotājs Iekšējais atskaņotājs - MPV - Web video apraide Aplikācijs nav atrasta Visas valodas Beigas @@ -513,8 +509,6 @@ Lejupielādējiet to vietņu sarakstu, kuras vēlaties izmantot Vispirms instalējiet paplašinājumu Atvēršana - VLC - Interneta mekletājs Sākums Izlaist %s Noņemt no skatītajiem diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 671257492..79dc1ee7e 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -164,8 +164,6 @@ Огледало на Chromecastr Пушти во апликацијата Пушти на %s - Пушти на прелистувач - Копирај линк Авто превземање Превземи Mirror Вчитај повторно врски @@ -297,7 +295,6 @@ Износот на барањето што се користи кога плеерот е скриен Преземи преводи Јавна листа - MPV Инсталатор на пакети ОВА Ажурирање и резервна копија @@ -425,7 +422,6 @@ /?? hello@world.com +30 - VLC Рестартирај Цртан филм Почна да презема %1$d %2$s… @@ -448,7 +444,6 @@ Камера Камера SDR - Веб-прелистувач Апликацијата не е пронајдена Корисничко име Отвори со @@ -488,7 +483,6 @@ Сите екстензии беа исклучени поради пад за да ви помогнат да ја пронајдете онаа што предизвикува проблеми. Оцена: %s Големина - Веб-видео Cast Сите јазици Исчисти историја Обележи како гледано @@ -611,7 +605,6 @@ Сега е направена резервна копија на вашите податоци на CloudStream. Иако можноста за ова е многу мала, сите уреди можат да се однесуваат поинаку. Во ретки случаи, кога ќе се заклучите од пристап до апликацијата, целосно исчистете ги податоците на апликацијата и вратете ги од резервна копија. Многу ни е жал за какви било непријатности што произлегуваат од ова. Ресетирај Сезона %1$d Епизода %2$d ќе биде објавена за - Fcast Одбери уред да кастираш Оневозможи оптимизација на батерија Отклучи CloudStream diff --git a/app/src/main/res/values-ml/strings.xml b/app/src/main/res/values-ml/strings.xml index 1c2d855e5..bf0fede72 100644 --- a/app/src/main/res/values-ml/strings.xml +++ b/app/src/main/res/values-ml/strings.xml @@ -151,8 +151,6 @@ Chromecast Mirror --> ആപ്പിൽ പ്ലേയ് ചെയ്യുക %sയിൽ പ്ലേയ് ചെയ്യുക - ബ്രൗസറിൽ പ്ലേയ് ചെയ്യുക - ലിങ്ക് പകർത്തുക ഡൌൺലോഡ് ചെയ്യൂ മിറർ ഡൗണ്ലോഡ് ലിങ്ക്സ് വീണ്ടും ലോഡുചെയ്യുക diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 07154776d..e865d58db 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -2,7 +2,6 @@ Semua bahasa Langkau %s - Pelayar web Sejarah Kosongkan sejarah Pengenalan @@ -232,7 +231,6 @@ Dinaikkan Lihat video dalam bahasa-bahasa ini Normal - Main dalam pelayar Tambah Diguna Anime @@ -323,7 +321,6 @@ Chromecast episod Main dalam %s Muat turun gagal, cek keizinan storan - Salin pautan Muat turun cermin Muat turun sari kata Label sub @@ -449,8 +446,6 @@ Pengesahan Password/PIN Sari kata belum tetapkan lagi Disokong - MPV - Fcast Ralat tidak dapat akses Clipboard, Sila cuba sekali lagi. Ralat menyalin, Sila salin logcat dan hubungi penyokong aplikasi. Amaran @@ -462,8 +457,6 @@ Maks Amaran: CloudStream 3 tidak bertanggungjawab atas penggunaan tambahan pihak ketiga dan tidak memberi sumbang kepada mereka! Mula semula aplikasi untuk lihat perubahan. - VLC - MPV YTDL Senarai ini kosong. Sila tukar yang lain. Data mudah alih Tambah ke kegemaran diff --git a/app/src/main/res/values-my/strings.xml b/app/src/main/res/values-my/strings.xml index 31e6ef276..9d82dd47d 100644 --- a/app/src/main/res/values-my/strings.xml +++ b/app/src/main/res/values-my/strings.xml @@ -157,8 +157,6 @@ Chromecast ဖန်သားပြင် အက်ပ်တွင်းဖွင့် ဖွင့်ရန် %s - ဘရောက်ဇာထဲမှာ ဖွင့်ရန် - လင့်ကူးယူရန် အလိုအလျောက်ဒေါင်းလုဒ် လင့်များကို ပြန်စစ်ရန် အရည်အသွေး အမှတ်အသား @@ -338,7 +336,6 @@ နောက်သို့ အပ်ဒိတ်လုပ်ပြီး %d ဖြည့်စွက်များ ဒေါင်းလုဒ်မလုပ်ရသေး: %d - ဝဘ်ဘရောက်ဇာ အက်ပ်မတွေ့ပါ ဘာသာစကားအားလုံး ကျော်ရန် %s @@ -422,9 +419,6 @@ ထောက်ပံ့ထားသော ဘာသာစကား အဆက်များကိုအရင်သွင်းပါ - VLC - MPV - ဝဘ်ထဲတွင်ဖွင့်ရန် အစပိုင်း အဆုံးပိုင်း ကြည့်ရှုခဲ့သည်များကိုရှင်းရန် diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 4bef20cc2..6518eb0e7 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -235,8 +235,6 @@ Chromecast mirror Speel in app Speel in %s - Speel in browser - Kopieer link Automatisch downloaden Download mirror Herlaad Linkss @@ -409,7 +407,6 @@ Kan %s niet laden Alle %s reeds gedownload plugin - VLC Sla %s over Links App updates @@ -468,10 +465,7 @@ Herhaal installatieproces Automatisch bijwerken plugin Sommige telefoons ondersteunen het nieuwe installatieprogramma niet. Probeer de oude optie als de updates niet worden geïnstalleerd. - Web Video Cast Interne speler - MPV - Web browser Gemengd einde Herhaling Start diff --git a/app/src/main/res/values-nn/strings.xml b/app/src/main/res/values-nn/strings.xml index 33ebe1b51..5b5577c2e 100644 --- a/app/src/main/res/values-nn/strings.xml +++ b/app/src/main/res/values-nn/strings.xml @@ -149,8 +149,6 @@ Kjeldefeil Spel av i programmet Spel av i %s - Spel av i nettlesaren - Kopier lenke Automatisk nedlasting Last inn lenker på nytt Last ned undertekstar diff --git a/app/src/main/res/values-no/strings.xml b/app/src/main/res/values-no/strings.xml index 41117f104..ac13f57f9 100644 --- a/app/src/main/res/values-no/strings.xml +++ b/app/src/main/res/values-no/strings.xml @@ -171,8 +171,6 @@ Støpt Speil Spill i appen Spill i %s - Spill i nettleseren - Kopier link Automatisk nedlasting Last ned speil Last inn lenker på nytt @@ -250,7 +248,6 @@ HLS-spilleliste Foretrukket videospiller Intern spiller - VLC Alle språk Hopp over %s Tøm historikk @@ -283,7 +280,6 @@ Beskrivelse Legg til konto Normal - MPV Dobbelttrykk for å sette på pause +30 Skygge @@ -416,7 +412,6 @@ Kunne ikke logge inn på %s Store bokstaver i undertekster Utviklere - Nettleser Sensurerbart Vev Lenke til strøm @@ -450,7 +445,6 @@ Fjern unødvendig informasjon fra undertekster Ekstra Filtrer etter foretrukket mediaspråk - Vev-videosending Tilbakeblikk SD Forfilm diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index a9cff7edf..1a7d9c72f 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -28,14 +28,12 @@ ଟି ଅଧ୍ୟାୟ ଅଧ୍ୟାୟ %s‌ରେ ଚଲାଅ - ବ୍ରାଉଜର୍‌ରେ ଚଲାଅ ଉପଶୀର୍ଷକ ଡାଉନଲୋଡ୍ କରିବା /%d /?? ଅଧ୍ୟାୟ %d ମୁକ୍ତିଲାଭ କଲା! ସ୍ୱତଃ ଡାଉନଲୋଡ୍ ଲିଙ୍କ୍‌ଗୁଡ଼ିକୁ ପୁନଃଲୋଡ୍ କରିବା - ଲିଙ୍କ୍ କପି କରିନେବା ଆପ୍‌ରେ ଚଲାଅ Chromecast ଅଧ୍ୟାୟ @@ -61,8 +59,6 @@ ପ୍ରାନ୍ତ ଆପ୍ ମିଳିଲା ନାହିଁ ସବୁ ଭାଷା - VLC - MPV ମିଶ୍ରିତ ପ୍ରାନ୍ତ ମିଶ୍ରିତ ଆଦ୍ୟ ଶ୍ରେୟ diff --git a/app/src/main/res/values-pl/array.xml b/app/src/main/res/values-pl/array.xml index a43d7bcfe..45a8e56e1 100644 --- a/app/src/main/res/values-pl/array.xml +++ b/app/src/main/res/values-pl/array.xml @@ -161,32 +161,6 @@ @string/show_title_key - - @string/episode_action_chromecast_episode - @string/episode_action_chromecast_mirror - @string/episode_action_play_in_app - @string/episode_action_play_in_format - @string/episode_action_play_in_browser - @string/episode_action_copy_link - @string/episode_action_auto_download - @string/episode_action_download_mirror - @string/episode_action_download_subtitle - @string/episode_action_reload_links - - - - 4 - 5 - 1 - 2 - 3 - 9 - 6 - 7 - 13 - 8 - - @string/automatic @string/phone_layout diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 8f0c0b125..2f26c3f5b 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -232,8 +232,6 @@ Mirror dla Chromecast Odtwórz w aplikacji Odtwórz w %s - Odtwórz w przeglądarce - Kopiuj link Automatyczne pobieranie Pobierz mirror Odśwież linki @@ -421,10 +419,6 @@ Playlista HLS Preferowany odtwarzacz wideo Odtwarzacz wewnętrzny - VLC - MPV - Web Video Cast - Przeglądarka Aplikacja nie została znaleziona Wszystkie języki Wyczyść historię @@ -621,7 +615,6 @@ Resetuj Nadchodzące w %s Odcinek %2$d sezonu %1$d wyjdzie za - Fcast Wybierz urządzenie do transmisji Mirror transmisji Wiki CloudStream @@ -664,6 +657,5 @@ Usuń (%1$d | %2$s) Podgląd paska przewijania Włącz podgląd miniatury na pasku wyszukiwania - MPV YTDL Nie wczytano jeszcze napisów \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 2afd50310..7fdbc6bee 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -228,8 +228,6 @@ Alternativa pelo Chromecast Reproduzir na app Reproduzir no %s - Reproduzir no navegador - Copiar link Transferência Automática Transferir por servidor alternativo Recarregar links @@ -441,18 +439,14 @@ Abertura Selecionar Biblioteca Contorna o bloqueio de URLs raw do GitHub usando jsDelivr. Pode atrasar as atualizações por uns dias. - VLC Todas as linguagens Atualizado (Novo para Antigo) Inscrito HDR Reiniciar - Navegador Web Atualizado (Antigo para Novo) - Web Video Cast DVD Instalador de pacotes - MPV Remover dos assistidos Não foi possível instalar a nova versão do aplicativo Inscrição cancelada em %s @@ -616,8 +610,7 @@ Desativar a otimização da bateria Para garantir descarregamentos ininterruptos e notificações de programas de TV subscritos, o CloudStream precisa de permissão para ser executado em segundo plano. Ao premir OK, será direcionado para informações da aplicação. Aí, desloque-se para utilização da bateria da aplicação e defina a utilização da bateria para sem restrições. Tenha em atenção que esta permissão não significa que o CS3 irá esgotar a sua bateria. Este só funcionará em segundo plano quando necessário, como ao receber notificações ou baixar vídeos de extensões oficiais. Se optar por cancelar, pode ajustar esta definição mais tarde em definições gerais. Reiniciar - Episódio %2$d da Temporada %1$d vai ser lançado em - Fcast + Episódio %1$d Episódio %2$d vai ser lançado em Escolha o dispositivo Transmitir hide_player_control_names_key diff --git a/app/src/main/res/values-qt/strings.xml b/app/src/main/res/values-qt/strings.xml index 378e3aaec..8f0e14cbc 100644 --- a/app/src/main/res/values-qt/strings.xml +++ b/app/src/main/res/values-qt/strings.xml @@ -144,8 +144,6 @@ aoohaaahhu ahouuhhh ooo-ahahaauuh aaahhu ooo-ahah ohaauuh %s - ahoha ooo-ahahohoohah oooohh - aauugghhahhaauugghh aaaghhoooohh aaahhu ahooo ohooo-ahahaohaohahhhoouuh ahoooaaahhuahaaahhuoha @@ -318,7 +316,6 @@ aaahh uuuugggh oooohh oooogggoog uuuuhhhaagg - uuh uuh aahh uugg oooogg ag aagg ug ooooggguh ooooggg %d aahh @@ -472,8 +469,6 @@ ooh oooohhhooh oogg oooogg ooh uuh uh g ooogg oh uuhh uug uuhh ooh aah uuuuggg ooooggg ooooggg aaaagggh uuuugg - ooh uuuhh aahh - uuh uuuuhhh aah ooh uuugg uuuuhhh uuuuhh aagg oooohhh @@ -617,8 +612,6 @@ %s (Disabled) Rating: %s aaaagg - oog - uuuhh aaaagg aahh oooohh uuuuuk aaagg aaaahhh diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 8105aa3e8..ec1115112 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -228,8 +228,6 @@ Chromecast alternativ Redă în Aplicație Redă în %s - Redă în Browser - Copiază link-ul Auto-descărcare Descărcă prin Alernativă Reîncărcare link-uri @@ -467,7 +465,6 @@ Nu Abonat la %s Aplicația va fi actualizată la ieșire - Web Video Cast Ocoliri ISP Anterior Sortează @@ -475,7 +472,6 @@ Filtrați în funcție de limba media preferată Episodul %d a fost lansat! Android TV - VLC Urmăriți videoclipuri în aceste limbi Revenire Acțiuni @@ -483,7 +479,6 @@ URL invalid Toate extensiile au fost dezactivate din cauza unei defecțiuni pentru a vă ajuta să o găsiți pe cea care cauzează probleme. Se descarcă actualizarea aplicației… - Browser web CloudStream nu are niciun site instalat din start. Trebuie să instalați site-urile din depozite. \n \nAlăturați-vă Discord-ului nostru sau căutați online. @@ -522,7 +517,6 @@ Se actualizează emisiunile abonate Abonat Lista publică - MPV Moştenit Test de furnizor Furnizori @@ -640,6 +634,5 @@ Sezonul %1$d Episod %2$d va fi lansat în Selectați divece-ul pe care doriți să faceți cast Cast mirror - Fcast hide_player_control_names_key \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e2f6a4b13..c251ca9bb 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -190,7 +190,6 @@ Торренты Другое Ошибка загрузки, проверьте разрешения хранилища - Копировать ссылку Автоскачивание Загрузка. Зеркало Сезон @@ -270,8 +269,6 @@ Размер Авторы Поддерживается - VLC - MPV Пропустить %s Концовка Используйте яркость системы в проигрывателе приложения вместо темного наложения @@ -293,7 +290,6 @@ Неожиданная ошибка плеера Эпизод Chromecast Воспроизведение на %s - Воспроизвести в браузере Скачать субтитры Знак качества Переключение элементов интерфейса на плакате @@ -309,7 +305,6 @@ %d из 10 Посмотреть информацию о сбое Предпочитаемый видеоплеер - Веб-браузер Приложение не найдено Все языки Вступление @@ -492,7 +487,6 @@ Отображать рандомную кнопку в библиотеке и главной странице Рандомная кнопка Legacy (старый) - Web Video Cast Не отправляет данные Перезагрузить ссылки Предпочтительные медиа @@ -620,7 +614,6 @@ Сброс Сезон %1$d Эпизод %2$d выйдет Выйдет %s - Fcast Выберите девайс для трансляции hide_player_control_names_key В данный момент загрузок нет. diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 4e38be6b9..196ed1d60 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -188,7 +188,6 @@ Žiadne titulky Anime Kreslené - Skopírovať odkaz Automaticky stiahnuť Zrkadlo sťahovania Zamknúť @@ -320,7 +319,6 @@ Ázijské drámy Anime Chyba zdroja - Prehrať v prehliadači Štítok dabingu Štítok titulkov Názov diff --git a/app/src/main/res/values-so/strings.xml b/app/src/main/res/values-so/strings.xml index e4ee98f96..8b1e4cc15 100644 --- a/app/src/main/res/values-so/strings.xml +++ b/app/src/main/res/values-so/strings.xml @@ -119,7 +119,6 @@ Asalkiisa Isla appkan ku daawo Ku daawo %s - Ku daawo barawsarka Dejinta iskeed ah Deji toorentiga(mirror) Cusbooneysii lifaaqyada @@ -239,7 +238,6 @@ Eeg xogaha hoose 🐈 Badhinka xajmiga daaraha Fashil ka yimi dhiibaha - Koobu garee lifaaqa Sharraxaad ma leh Sharraxaadda Duluc ma leh @@ -472,10 +470,6 @@ Kordhiyeyaasha Liiska bulshada kale Muqaal daaraha appka - VLC - MPV - Web Video Cast - Barawsarka Kuuguma jiro appkaasi Dhammaan luuqadaha Is dhaafi %s diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 9757fdf67..06f388848 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -148,8 +148,6 @@ Chromecasta en Länk Spela upp i appen Spela upp i %s - Spela upp i webbläsaren - Kopiera länk Automatisk nerladdning Ladda ner en specifik länk Ladda om alla länkar @@ -379,13 +377,11 @@ Visa sub Kunde inte öppna appen GitHub Proxy - Webbläsarens videospelare Installerar uppdatering till appen… Kunde inte nå GitHub, sätter på jsDelivr proxy… Leverantörer Nytt webbplatsnamn Ta bort reklam från undertexter - VLC Alla språk Rensa historik PackageInstaller @@ -409,8 +405,6 @@ Videospelare Öppna med Synkronisera undertexter - MPV - Web Video Cast Misslyckades Ja Videospår @@ -619,7 +613,6 @@ Kommer ut om %s Fel vid kopiering, kopiera logcat och kontakta appsupport. Media - Fcast Cast mirror Säsong %1$d Avsnitt %2$d kommer att släppas om Välj cast-enhet diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index c8c3243cd..a2b4cadd2 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -266,8 +266,6 @@ மறுதொடக்கம் விவரம் ஆசிரியர்கள் - வலை வீடியோ நடிகர்கள் - இணைய உலாவி %s ஐத் தவிர்க்கவும் மறுபரிசீலனை செய்யுங்கள் அறிமுகம் @@ -365,7 +363,6 @@ முன்மாதிரி தளவமைப்பு பதிவிறக்கம் செய்யப்பட்ட கோப்பு பகுத்தல் - எம்.பி.வி. உங்கள் நூலகம் காலியாக உள்ளது :( \n நூலகக் கணக்கில் உள்நுழைக அல்லது உங்கள் உள்ளக நூலகத்தில் காட்சிகளைச் சேர்க்கவும். குழுவிலகவும் @@ -431,8 +428,6 @@ மூல பிழை தொலை பிழை ரெண்டரர் பிழை - உலாவியில் விளையாடுங்கள் - இணைப்பை நகலெடுக்கவும் ஆட்டோ பதிவிறக்கம் கண்ணாடியைப் பதிவிறக்கவும் இணைப்புகளை மீண்டும் ஏற்றவும் @@ -520,7 +515,6 @@ அளவு ஆதரிக்கப்பட்டது முதலில் நீட்டிப்பை நிறுவவும் - Fcast காச்ட் சாதனத்தைத் தேர்ந்தெடுக்கவும் அனைத்து மொழிகளும் ஆம் @@ -551,7 +545,6 @@ பதிவிறக்கம் செய்யப்படவில்லை: %d புதுப்பிக்கப்பட்டது %d செருகுநிரல்கள் உள் வீரர் - வி.எல்.சி. திறப்பு கலப்பு திறப்பு வரவு diff --git a/app/src/main/res/values-tl/strings.xml b/app/src/main/res/values-tl/strings.xml index d832144dd..5fd25031e 100644 --- a/app/src/main/res/values-tl/strings.xml +++ b/app/src/main/res/values-tl/strings.xml @@ -174,8 +174,6 @@ Chromecast Mirror I-play sa App I-play sa %s - I-play sa browser - Kopyahin ang Link Awtomatiking i-download Download mirror Subukan muli diff --git a/app/src/main/res/values-tr/array.xml b/app/src/main/res/values-tr/array.xml index 22a94ebf0..4ec45e6d1 100644 --- a/app/src/main/res/values-tr/array.xml +++ b/app/src/main/res/values-tr/array.xml @@ -33,22 +33,6 @@ 6 - - @string/player_settings_play_in_app - @string/player_settings_play_in_vlc - @string/player_settings_play_in_mpv - @string/player_settings_play_in_web - @string/player_settings_play_in_browser - - - - 1 - 2 - 5 - 4 - 3 - - @string/resolution_and_title @string/title @@ -187,32 +171,6 @@ @string/show_title_key - - @string/episode_action_chromecast_episode - @string/episode_action_chromecast_mirror - @string/episode_action_play_in_app - @string/episode_action_play_in_format - @string/episode_action_play_in_browser - @string/episode_action_copy_link - @string/episode_action_auto_download - @string/episode_action_download_mirror - @string/episode_action_download_subtitle - @string/episode_action_reload_links - - - - 4 - 5 - 1 - 2 - 3 - 9 - 6 - 7 - 13 - 8 - - @string/automatic @string/phone_layout diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index d40e5d7aa..b516de738 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -258,8 +258,6 @@ Bağlantıyı Chromecast ile yayınla Burada oynat %s üzerinden oynat - Tarayıcıda oynat - Bağlantıyı kopyala Otomatik indir Şu kaynaktan indir Bağlantıları yenile @@ -482,10 +480,6 @@ HLS Oynatma Listesi Tercih edilen video oynatıcısı Dahili oynatıcı - VLC - MPV - Web Video Yayını - İnternet tarayıcısı Uygulama bulunamadı Geçmiş İzlendi olarak işaretle @@ -669,7 +663,6 @@ Sezon %1$d Bölüm %2$d tarihinde yayınlanacak Yansıtılacak cihaz seç Ekran yansıtma - Fcast CloudStream Viki Güvenlik Hesaplar diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 010af23c9..c5e234f0d 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -248,7 +248,6 @@ Змінити розмір Короткий зміст Фільми - Скопіювати посилання Перезавантажити посилання Документальні фільми NSFW @@ -257,7 +256,6 @@ Торент Мітка якості NSFW - Переглянути в браузері Несподівана помилка плеєра Помилка завантаження, перевірте дозволи на зберігання Епізод Chromecast @@ -445,10 +443,6 @@ Спочатку встановіть розширення Список відтворення HLS Вбудований плеєр - VLC - MPV - Web Video Cast - Веббраузер Ендінґ Коротке повторення Пропустити %s @@ -620,7 +614,6 @@ Скинути Наступний через %s %1$d сезон %2$d епізод вийде через - Fcast Оберіть пристрій для трансляції Трансляція через дзеркало CloudStream Wiki @@ -663,6 +656,5 @@ \n%s Попередній перегляд повзунка Ввімкнути мініатюру попереднього перегляду на повзунку - MPV YTDL Субтитри ще не завантажено \ No newline at end of file diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 5a32dfe8a..06508bc9e 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -243,7 +243,6 @@ Chromecast mirror ایپ میں چلائیں %s میں چلائیں - کاپی لنک ڈاؤنلوڈ mirror لنکس کو دوبارہ لوڈ کریں سب ٹائٹلز ڈاؤن لوڈ @@ -336,7 +335,6 @@ کوئی زیرنویس میں دیری نہیں این ایس ایف ڈبلیو آٹو ڈاؤن لوڈ - browser میں چلائیں بہت زیادہ سیٹ ہونے پر کم میموری والی ڈیوائس(جیسے کہ Android TV) پر کریشوں کا سبب بنتا ہے. Remove site Add a clone of an existing site, with a different URL @@ -361,7 +359,6 @@ قرارداد اور عنوان HDR %s سے ان سبسکرائب کیا گیا - ویب ویڈیو کاسٹ 18+ لاگ https://example.com/example.mp4 @@ -427,11 +424,8 @@ حالت سائز زبان - وی ایل سی ترجیحی ویڈیو پلیئر اندرونی پلیئر - ایم پی وی - ویب براؤزر ایپ نہیں ملی Recap مخلوط اختتام diff --git a/app/src/main/res/values-vi/array.xml b/app/src/main/res/values-vi/array.xml index f363befda..c6ff1c4e2 100644 --- a/app/src/main/res/values-vi/array.xml +++ b/app/src/main/res/values-vi/array.xml @@ -153,32 +153,6 @@ @string/show_title_key - - @string/episode_action_chromecast_episode - @string/episode_action_chromecast_mirror - @string/episode_action_play_in_app - @string/episode_action_play_in_format - @string/episode_action_play_in_browser - @string/episode_action_copy_link - @string/episode_action_auto_download - @string/episode_action_download_mirror - @string/episode_action_download_subtitle - @string/episode_action_reload_links - - - - 4 - 5 - 1 - 2 - 3 - 9 - 6 - 7 - 13 - 8 - - @string/automatic @string/phone_layout diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 53b19bd8b..5c4cd380e 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -243,8 +243,6 @@ Chiếu Chromecast Xem với trình phát mặc định Xem với trình phát %s - Xem tại trình duyệt - Sao chép liên kết Tự động tải xuống Nguồn tải xuống Lấy link mới nhất @@ -472,10 +470,6 @@ Hỗ trợ Ngôn ngữ Cài đặt tiện ích trước - VLC - MPV - Web Video Cast - Trình duyệt web Không thấy ứng dụng Tất cả ngôn ngữ Tua %s @@ -671,11 +665,9 @@ \n \n%s Xóa plugin - Fcast Ngày phát hành (Cũ đến mới) Ẩn tên các nút điều khiển Bật chế độ xem trước hình thu nhỏ trên seekbar Xem trước Seekbar - MPV YTDL Chưa tải phụ đề \ No newline at end of file diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index b8b8b4884..3c002c29a 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -258,8 +258,6 @@ Chromecast 鏡像 在應用程式中播放 在 %s 中播放 - 在瀏覽器中播放 - 複製連結 自動下載 下載鏡像 重新載入連結 @@ -482,10 +480,6 @@ HLS 播放清單 偏好影片播放器 內部播放器 - VLC - MPV - 網路影片播放 - 網頁瀏覽器 未找到應用程式 所有語言 跳過 %s @@ -663,7 +657,6 @@ 使用指紋、面容 ID、PIN、圖案和密碼解除鎖定應用程式。 由於多次嘗試失敗,此畫面已關閉。請重新啟動應用程式。 剪貼簿存取失敗,請再試一次。 - Fcast 複製失敗,請複製 logcat 內容並聯繫應用程式支援者。 無法開啟 CloudStream 的應用程式資訊頁面。 您的 CloudStream 資料已完成備份。儘管可能性非常低,但因不同裝置的行為都有所不同,在極少數情況下,您可能會無法存取本應用程式。此時請完全清除本應用程式的資料,再使用已有的備份進行還原。若因此造成任何不便,我們深感抱歉。 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index ff85df32e..45e350439 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -259,8 +259,6 @@ Chromecast 镜像 在应用中播放 在 %s 中播放 - 在浏览器中播放 - 复制链接 自动下载 下载镜像 重新加载链接 @@ -483,10 +481,6 @@ HLS 播放列表 首选视频播放器 内部播放器 - VLC - MPV - 投屏 - 浏览器 未找到应用 所有语言 跳过 %s @@ -665,7 +659,6 @@ 在多次尝试失败后,提示将关闭。只需重启应用程序再试。 即将在 %s CloudStream Wiki - Fcast 选择投射设备 %1$d季%2$d集将在 投射镜像 @@ -674,7 +667,6 @@ 打开本地视频 安全 访问智能手机或电脑上的 %s 并输入上述代码 - MPV YTDL 进度条预览 启用进度条预览缩略图 删除插件 diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index b077997fe..a987420e9 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -50,24 +50,6 @@ @string/nsfw - - @string/player_settings_play_in_app - @string/player_settings_play_in_vlc - @string/player_settings_play_in_mpv - @string/player_settings_play_in_mpvytdl - @string/player_settings_play_in_web - @string/player_settings_play_in_browser - - - - 1 - 2 - 5 - 6 - 4 - 3 - - @string/resolution_and_title @string/title @@ -226,32 +208,6 @@ @string/show_title_key - - @string/episode_action_chromecast_episode - @string/episode_action_chromecast_mirror - @string/episode_action_play_in_app - @string/episode_action_play_in_format - @string/episode_action_play_in_browser - @string/episode_action_copy_link - @string/episode_action_auto_download - @string/episode_action_download_mirror - @string/episode_action_download_subtitle - @string/episode_action_reload_links - - - - 4 - 5 - 1 - 2 - 3 - 9 - 6 - 7 - 13 - 8 - - @string/automatic @string/phone_layout diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9d9362bec..56f2465d2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -17,7 +17,7 @@ subtitle_settings_chromecast_key quality_pref_key quality_pref_mobile_data_key - player_pref_key + player_default_key prefer_limit_title_key prefer_limit_title_rez_key apk_installer_key @@ -381,8 +381,6 @@ Cast mirror Play in app Play in %s - Play in browser - Copy link Auto download Download mirror Reload links @@ -662,12 +660,6 @@ HLS Playlist Preferred video player Internal player - VLC - MPV - MPV YTDL - Web Video Cast - Fcast - Web browser Select cast device App not found All Languages diff --git a/app/src/main/res/xml/settings_player.xml b/app/src/main/res/xml/settings_player.xml index 73f9bb5bf..a2575e315 100644 --- a/app/src/main/res/xml/settings_player.xml +++ b/app/src/main/res/xml/settings_player.xml @@ -22,7 +22,7 @@ From cffc14a6df0e8ff1d8dbf46d745586a578158d72 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 24 Sep 2024 15:28:10 +0200 Subject: [PATCH 006/962] fixes critical issues with #1329 --- .../lagradost/cloudstream3/CommonActivity.kt | 2 +- .../cloudstream3/actions/OpenInAppAction.kt | 101 +++++++++--------- .../cloudstream3/actions/VideoClickAction.kt | 42 +++++++- .../actions/temp/CopyClipboardAction.kt | 2 +- .../cloudstream3/actions/temp/MpvKtPackage.kt | 2 +- .../cloudstream3/actions/temp/MpvPackage.kt | 2 +- .../actions/temp/PlayInBrowserAction.kt | 13 +-- .../actions/temp/ViewM3U8Action.kt | 2 +- .../cloudstream3/actions/temp/VlcPackage.kt | 16 +-- .../actions/temp/WebVideoCastPackage.kt | 2 +- .../actions/temp/fcast/FcastAction.kt | 2 +- .../ui/result/ResultViewModel2.kt | 4 +- 12 files changed, 113 insertions(+), 77 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 50e6d8c98..8498925c8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -222,7 +222,7 @@ object CommonActivity { val actionUid = getKey("last_click_action") ?: return@registerForActivityResult Log.d(TAG, "Loading action $actionUid result handler") val action = VideoClickActionHolder.getByUniqueId(actionUid) as? OpenInAppAction ?: return@registerForActivityResult - action.onResult(act, result.data) + action.onResultSafe(act, result.data) removeKey("last_click_action") removeKey("last_opened_id") } diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt index 99c1ac38b..4952bde6b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt @@ -1,16 +1,13 @@ package com.lagradost.cloudstream3.actions import android.app.Activity -import android.content.ActivityNotFoundException import android.content.ComponentName import android.content.Context import android.content.Intent -import android.widget.Toast import androidx.core.content.FileProvider import androidx.core.net.toUri import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity.Companion.activityResultLauncher import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError @@ -21,9 +18,6 @@ import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.AppContextUtils.isAppInstalled import com.lagradost.cloudstream3.utils.DataStoreHelper -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import java.io.File fun updateDurationAndPosition(position: Long, duration: Long) { @@ -39,7 +33,13 @@ fun updateDurationAndPosition(position: Long, duration: Long) { fun makeTempM3U8Intent( context: Context, intent: Intent, - result: LinkLoadingResult) { + result: LinkLoadingResult +) { + if (result.links.size == 1) { + intent.setDataAndType(result.links.first().url.toUri(), "video/*") + return + } + intent.apply { addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) @@ -47,37 +47,29 @@ fun makeTempM3U8Intent( addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) } - val outputDir = context.cacheDir + val outputFile = File.createTempFile("mirrorlist", ".m3u8", context.cacheDir) + var text = "#EXTM3U\n#EXT-X-VERSION:3" - if (result.links.size == 1) { - intent.setDataAndType(result.links.first().url.toUri(), "video/*") - } else { - val outputFile = File.createTempFile("mirrorlist", ".m3u8", outputDir) - - var text = "#EXTM3U\n#EXT-X-VERSION:3" - - result.links.forEachIndexed { index, link -> - text += "\n#EXTINF:$index,${link.name}\n${link.url}" - } - - //With subtitles it doesn't work for no reason :( - /*for (sub in result.subs) { - val normalizedName = sub.name.replace("[^a-zA-Z0-9 ]".toRegex(), "") - text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${normalizedName}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.languageCode}\",URI=\"${sub.url}\"" - }*/ - - text += "\n#EXT-X-ENDLIST" - - outputFile.writeText(text) - - intent.setDataAndType( - FileProvider.getUriForFile( - context, - context.applicationContext.packageName + ".provider", - outputFile - ), "application/x-mpegURL" - ) + result.links.forEach { link -> + text += "\n#EXTINF:0,${link.name}\n${link.url}" } + + //With subtitles it doesn't work for no reason :( + /*for (sub in result.subs) { + val normalizedName = sub.name.replace("[^a-zA-Z0-9 ]".toRegex(), "") + text += "\n#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID=\"subs\",NAME=\"${normalizedName}\",DEFAULT=NO,AUTOSELECT=NO,FORCED=NO,LANGUAGE=\"${sub.languageCode}\",URI=\"${sub.url}\"" + }*/ + + text += "\n#EXT-X-ENDLIST" + outputFile.writeText(text) + + intent.setDataAndType( + FileProvider.getUriForFile( + context, + context.applicationContext.packageName + ".provider", + outputFile + ), "application/x-mpegURL" + ) } abstract class OpenInAppAction( @@ -85,15 +77,16 @@ abstract class OpenInAppAction( open val packageName: String, private val intentClass: String? = null, private val action: String = Intent.ACTION_VIEW -): VideoClickAction() { +) : VideoClickAction() { override val name: UiText get() = txt(R.string.episode_action_play_in_format, appName) override val isPlayer = true - override fun shouldShow(context: Context?, video: ResultEpisode?) = context?.isAppInstalled(packageName) == true + override fun shouldShow(context: Context?, video: ResultEpisode?) = + context?.isAppInstalled(packageName) != false - override fun runAction( + override suspend fun runAction( context: Context?, video: ResultEpisode, result: LinkLoadingResult, @@ -107,28 +100,36 @@ abstract class OpenInAppAction( } putExtra(context, intent, video, result, index) setKey("last_opened_id", video.id) - try { - CoroutineScope(Dispatchers.IO).launch { - activityResultLauncher?.launch(intent) - } - } catch (_: ActivityNotFoundException) { - showToast(R.string.app_not_found_error, Toast.LENGTH_LONG) - } catch (t: Throwable) { - logError(t) - showToast(t.toString(), Toast.LENGTH_LONG) - } + activityResultLauncher?.launch(intent) } /** * Before intent is sent, this function is called to put extra data into the intent. * @see VideoClickAction.runAction * */ - abstract fun putExtra(context: Context, intent: Intent, video: ResultEpisode, result: LinkLoadingResult, index: Int?) + @Throws + abstract suspend fun putExtra( + context: Context, + intent: Intent, + video: ResultEpisode, + result: LinkLoadingResult, + index: Int? + ) /** * This function is called when the app is opened again after the intent was sent. * You can use it to for example update duration and position. * @see updateDurationAndPosition */ + @Throws abstract fun onResult(activity: Activity, intent: Intent?) + + /** Safe version of onResult, we don't trust extension devs to not crash the app */ + fun onResultSafe(activity: Activity, intent: Intent?) { + try { + onResult(activity, intent) + } catch (t: Throwable) { + logError(t) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt index f66ed74d9..eada9fea1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -1,8 +1,13 @@ package com.lagradost.cloudstream3.actions import android.app.Activity +import android.content.ActivityNotFoundException import android.content.Context +import android.widget.Toast import com.lagradost.api.Log +import com.lagradost.cloudstream3.CommonActivity +import com.lagradost.cloudstream3.ErrorLoadingException +import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.temp.CopyClipboardAction import com.lagradost.cloudstream3.actions.temp.MpvKtPackage import com.lagradost.cloudstream3.actions.temp.MpvKtPreviewPackage @@ -13,9 +18,11 @@ import com.lagradost.cloudstream3.actions.temp.ViewM3U8Action import com.lagradost.cloudstream3.actions.temp.VlcPackage import com.lagradost.cloudstream3.actions.temp.WebVideoCastPackage import com.lagradost.cloudstream3.actions.temp.fcast.FcastAction +import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.UiText +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.ExtractorLinkType import kotlin.reflect.jvm.jvmName @@ -38,7 +45,7 @@ object VideoClickActionHolder { fun makeOptionMap(activity: Activity?, video: ResultEpisode) = allVideoClickActions // We need to have index before filtering .mapIndexed { id, it -> it to id + ACTION_ID_OFFSET } - .filter { it.first.shouldShow(activity, video) } + .filter { it.first.shouldShowSafe(activity, video) } .map { it.first.name to it.second } @@ -54,7 +61,7 @@ object VideoClickActionHolder { ?.second } - fun getPlayers(activity: Activity? = null) = allVideoClickActions.filter { it.isPlayer && it.shouldShow(activity, null) } + fun getPlayers(activity: Activity? = null) = allVideoClickActions.filter { it.isPlayer && it.shouldShowSafe(activity, null) } } abstract class VideoClickAction { @@ -74,8 +81,20 @@ abstract class VideoClickAction { fun uniqueId() = "$sourcePlugin:${this::class.jvmName}" + @Throws abstract fun shouldShow(context: Context?, video: ResultEpisode?): Boolean + /** Safe version of shouldShow, as we don't trust extension devs to handle exceptions, + * however no dev *should* throw in shouldShow */ + fun shouldShowSafe(context: Context?, video: ResultEpisode?): Boolean { + return try { + shouldShow(context,video) + } catch (t : Throwable) { + logError(t) + false + } + } + /** * This function is called when the action is clicked. * @param context The current activity @@ -83,5 +102,22 @@ abstract class VideoClickAction { * @param result The result of the link loading, contains video & subtitle links * @param index if oneSource is true, this is the index of the selected source */ - abstract fun runAction(context: Context?, video: ResultEpisode, result: LinkLoadingResult, index: Int?) + @Throws + abstract suspend fun runAction(context: Context?, video: ResultEpisode, result: LinkLoadingResult, index: Int?) + + /** Safe version of runAction, as we don't trust extension devs to handle exceptions */ + fun runActionSafe(context: Context?, video: ResultEpisode, result: LinkLoadingResult, index: Int?) = ioSafe { + try { + runAction(context, video, result, index) + } catch (_ : NotImplementedError) { + CommonActivity.showToast("runAction has not been implemented for ${name.asStringNull(context)}, please contact the extension developer of $sourcePlugin", Toast.LENGTH_LONG) + } catch (error : ErrorLoadingException) { + CommonActivity.showToast(error.message, Toast.LENGTH_LONG) + } catch (_: ActivityNotFoundException) { + CommonActivity.showToast(R.string.app_not_found_error, Toast.LENGTH_LONG) + } catch (t : Throwable) { + logError(t) + CommonActivity.showToast(t.toString(), Toast.LENGTH_LONG) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt index e054b5ce2..481917e5e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt @@ -14,7 +14,7 @@ class CopyClipboardAction: VideoClickAction() { override fun shouldShow(context: Context?, video: ResultEpisode?) = true - override fun runAction( + override suspend fun runAction( context: Context?, video: ResultEpisode, result: LinkLoadingResult, diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt index f5ded49b8..6514de174 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt @@ -34,7 +34,7 @@ open class MpvKtPackage( ExtractorLinkType.M3U8 ) - override fun putExtra( + override suspend fun putExtra( context: Context, intent: Intent, video: ResultEpisode, diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt index 4c66d0450..fa45aed8f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -31,7 +31,7 @@ open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv "is.xyz.mpv.MPVActivity" ) { - override fun putExtra( + override suspend fun putExtra( context: Context, intent: Intent, video: ResultEpisode, diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt index de32bb4b3..1e11281a5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt @@ -5,7 +5,6 @@ import android.content.Intent import android.net.Uri import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.VideoClickAction -import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.txt @@ -26,19 +25,15 @@ class PlayInBrowserAction: VideoClickAction() { override fun shouldShow(context: Context?, video: ResultEpisode?) = true - override fun runAction( + override suspend fun runAction( context: Context?, video: ResultEpisode, result: LinkLoadingResult, index: Int? ) { val link = result.links.getOrNull(index ?: 0) ?: return - try { - val i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse(link.url) - context?.startActivity(i) - } catch (e: Exception) { - logError(e) - } + val i = Intent(Intent.ACTION_VIEW) + i.data = Uri.parse(link.url) + context?.startActivity(i) } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt index c14168e96..fa7fc1173 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt @@ -16,7 +16,7 @@ class ViewM3U8Action: VideoClickAction() { override fun shouldShow(context: Context?, video: ResultEpisode?) = true - override fun runAction( + override suspend fun runAction( context: Context?, video: ResultEpisode, result: LinkLoadingResult, diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt index ecb37fdc6..18143c001 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt @@ -4,6 +4,7 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.os.Build +import androidx.core.net.toUri import com.lagradost.api.Log import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.actions.OpenInAppAction @@ -32,18 +33,21 @@ class VlcPackage: OpenInAppAction( Intent.ACTION_VIEW } ) { - override val oneSource = false + // while VLC supports multi links, it has poor support, so we disable it for now + override val oneSource = true - override fun putExtra( + override suspend fun putExtra( context: Context, intent: Intent, video: ResultEpisode, result: LinkLoadingResult, index: Int? ) { - - makeTempM3U8Intent(context, intent, result) - + if (index != null) { + intent.setDataAndType(result.links[index].url.toUri(), "video/*") + } else { + makeTempM3U8Intent(context, intent, result) + } val position = getViewPos(video.id)?.position ?: 0L intent.putExtra("from_start", false) @@ -51,7 +55,7 @@ class VlcPackage: OpenInAppAction( intent.putExtra("secure_uri", true) intent.putExtra("title", video.name) - val subsLang = getKey(SUBTITLE_AUTO_SELECT_KEY) ?: "en" + val subsLang = getKey(SUBTITLE_AUTO_SELECT_KEY) ?: "en" result.subs.firstOrNull { subsLang == it.languageCode }?.let { diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt index f8419f63c..4f44b3969 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt @@ -28,7 +28,7 @@ class WebVideoCastPackage: OpenInAppAction( ExtractorLinkType.M3U8 ) - override fun putExtra( + override suspend fun putExtra( context: Context, intent: Intent, video: ResultEpisode, diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt index c0f92e4df..9de8e7d5d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt @@ -26,7 +26,7 @@ class FcastAction: VideoClickAction() { override fun shouldShow(context: Context?, video: ResultEpisode?) = FcastManager.currentDevices.isNotEmpty() - override fun runAction( + override suspend fun runAction( context: Context?, video: ResultEpisode, result: LinkLoadingResult, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index b5f83201e..bf3b82a91 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -1625,7 +1625,7 @@ class ResultViewModel2 : ViewModel() { action.sourceTypes, action.name ) { (result, index) -> - action.runAction( + action.runActionSafe( activity, click.data, result, @@ -1634,7 +1634,7 @@ class ResultViewModel2 : ViewModel() { } } else { loadLinks(click.data, isVisible = true, action.sourceTypes) { links -> - action.runAction( + action.runActionSafe( activity, click.data, links, From d899ecb82f67970bb32b4f2cd7a5cd0ae2c0248e Mon Sep 17 00:00:00 2001 From: CranberrySoup <142951702+CranberrySoup@users.noreply.github.com> Date: Tue, 24 Sep 2024 14:23:33 +0000 Subject: [PATCH 007/962] Fix navigation bar in light mode (#1350) --- app/src/main/res/color/item_select_color.xml | 4 ++-- app/src/main/res/values/colors.xml | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/color/item_select_color.xml b/app/src/main/res/color/item_select_color.xml index 5a9453b7f..208afb18b 100644 --- a/app/src/main/res/color/item_select_color.xml +++ b/app/src/main/res/color/item_select_color.xml @@ -3,5 +3,5 @@ - - \ No newline at end of file + + diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 5154b65ff..f63308b61 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -89,5 +89,4 @@ #48E484 #ea596e #FF9800 - #F5F5F5 - \ No newline at end of file + From 078de970d522f7ead336c7d93d3455867b78d5e0 Mon Sep 17 00:00:00 2001 From: CranberrySoup <142951702+CranberrySoup@users.noreply.github.com> Date: Thu, 26 Sep 2024 17:41:54 +0000 Subject: [PATCH 008/962] Update FcastAction.kt (#1352) --- .../lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt index 9de8e7d5d..f69da0e05 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt @@ -53,7 +53,7 @@ class FcastAction: VideoClickAction() { session.sendMessage( Opcode.Play, PlayMessage( - "video/*", + link.type.getMimeType(), link.url, time = position?.let { it / 1000.0 }, headers = mapOf( @@ -64,4 +64,4 @@ class FcastAction: VideoClickAction() { ) } } -} \ No newline at end of file +} From bd95ecf5ba0d30da76103597b40ebe77b80d2ad9 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 26 Sep 2024 20:32:22 +0200 Subject: [PATCH 009/962] Small changes to VideoClickAction API --- .../cloudstream3/actions/OpenInAppAction.kt | 2 +- .../cloudstream3/actions/VideoClickAction.kt | 74 +++++++++++++++++-- .../cloudstream3/actions/temp/MpvPackage.kt | 9 ++- .../actions/temp/PlayInBrowserAction.kt | 2 +- .../actions/temp/ViewM3U8Action.kt | 2 +- .../cloudstream3/utils/AppContextUtils.kt | 9 ++- 6 files changed, 84 insertions(+), 14 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt index 4952bde6b..8abc5e665 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt @@ -100,7 +100,7 @@ abstract class OpenInAppAction( } putExtra(context, intent, video, result, index) setKey("last_opened_id", video.id) - activityResultLauncher?.launch(intent) + launchResult(intent) } /** diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt index eada9fea1..0d1e87a74 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -3,10 +3,14 @@ package com.lagradost.cloudstream3.actions import android.app.Activity import android.content.ActivityNotFoundException import android.content.Context +import android.content.Intent +import android.os.Bundle import android.widget.Toast +import androidx.core.app.ActivityOptionsCompat import com.lagradost.api.Log import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.ErrorLoadingException +import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.temp.CopyClipboardAction import com.lagradost.cloudstream3.actions.temp.MpvKtPackage @@ -25,15 +29,29 @@ import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.ExtractorLinkType +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext +import java.util.concurrent.Callable +import java.util.concurrent.FutureTask import kotlin.reflect.jvm.jvmName object VideoClickActionHolder { - val allVideoClickActions = threadSafeListOf( - PlayInBrowserAction(), CopyClipboardAction(), - VlcPackage(), ViewM3U8Action(), - MpvPackage(), MpvYTDLPackage(), - WebVideoCastPackage(), MpvKtPackage(), MpvKtPreviewPackage(), - FcastAction() + val allVideoClickActions = threadSafeListOf( + // Default + PlayInBrowserAction(), + CopyClipboardAction(), + ViewM3U8Action(), + // main support external apps + VlcPackage(), + MpvPackage(), + FcastAction(), + // forks/backup apps + WebVideoCastPackage(), + MpvYTDLPackage(), + MpvKtPackage(), + MpvKtPreviewPackage(), + // added by plugins + // ... ) init { @@ -79,6 +97,50 @@ abstract class VideoClickAction { /** Determines which plugin a given provider is from. This is the full path to the plugin. */ var sourcePlugin: String? = null + /** Even if VideoClickAction should not run any UI code, startActivity requires it, + * this is a wrapper for runOnUiThread in a suspended safe context that bubble up exceptions */ + @Throws + suspend fun uiThread(callable : Callable) : T? { + val future = FutureTask{ + try { + Result.success(callable.call()) + } catch (t : Throwable) { + Result.failure(t) + } + } + CommonActivity.activity?.runOnUiThread(future) ?: throw ErrorLoadingException("No UI Activity, this should never happened") + val result = withContext(Dispatchers.IO) { + return@withContext future.get() + } + return result.getOrThrow() + } + + /** Internally uses activityResultLauncher, + * use this when the activity has a result like watched position */ + @Throws + suspend fun launchResult(intent : Intent?, options : ActivityOptionsCompat? = null) { + if (intent == null) { + return + } + + uiThread { + MainActivity.activityResultLauncher?.launch(intent,options) + } + } + + /** Internally uses startActivity, use this when you don't + * have any result that needs to be stored when exiting the activity */ + @Throws + suspend fun launch(intent : Intent?, bundle : Bundle? = null) { + if (intent == null) { + return + } + + uiThread { + CommonActivity.activity?.startActivity(intent, bundle) + } + } + fun uniqueId() = "$sourcePlugin:${this::class.jvmName}" @Throws diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt index fa45aed8f..382f7d89a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity import android.content.Context import android.content.Intent +import android.net.Uri import androidx.core.net.toUri import com.lagradost.api.Log import com.lagradost.cloudstream3.actions.OpenInAppAction @@ -30,7 +31,7 @@ open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv packageName, "is.xyz.mpv.MPVActivity" ) { - + override val oneSource = true // mpv has poor playlist support on TV override suspend fun putExtra( context: Context, intent: Intent, @@ -42,7 +43,11 @@ open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) putExtra("title", video.name) - makeTempM3U8Intent(context, this, result) + if (index != null) { + setDataAndType(Uri.parse(result.links.getOrNull(index)?.url ?: return), "video/*") + } else { + makeTempM3U8Intent(context, this, result) + } val position = getViewPos(video.id)?.position if (position != null) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt index 1e11281a5..1558bfd6a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt @@ -34,6 +34,6 @@ class PlayInBrowserAction: VideoClickAction() { val link = result.links.getOrNull(index ?: 0) ?: return val i = Intent(Intent.ACTION_VIEW) i.data = Uri.parse(link.url) - context?.startActivity(i) + launch(i) } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt index fa7fc1173..b69562dd1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt @@ -25,6 +25,6 @@ class ViewM3U8Action: VideoClickAction() { if (context == null) return val i = Intent(Intent.ACTION_VIEW) makeTempM3U8Intent(context, i, result) - context.startActivity(i) + launch(i) } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt index 8d65acf7e..7da76dbd7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt @@ -51,6 +51,7 @@ import com.google.android.gms.common.wrappers.Wrappers import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.apis +import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent @@ -645,7 +646,7 @@ object AppContextUtils { url: String, fallbackWebview: Boolean = false, fragment: Fragment? = null, - ) { + ) = (this.getActivity() ?: activity)?.runOnUiThread { try { val intent = Intent(Intent.ACTION_VIEW) intent.data = Uri.parse(url) @@ -677,10 +678,12 @@ object AppContextUtils { } fun Context.isNetworkAvailable(): Boolean { - val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + val connectivityManager = + getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { val network = connectivityManager.activeNetwork ?: return false - val networkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false + val networkCapabilities = + connectivityManager.getNetworkCapabilities(network) ?: return false networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) } else { @Suppress("DEPRECATION") From f4fd30330688d1043c7b353212712a2fd22f0685 Mon Sep 17 00:00:00 2001 From: CranberrySoup <142951702+CranberrySoup@users.noreply.github.com> Date: Sun, 29 Sep 2024 15:14:59 +0000 Subject: [PATCH 010/962] Fix fcast bottom dialog (#1357) --- .../actions/temp/fcast/FcastAction.kt | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt index f69da0e05..458d1dc99 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt @@ -34,14 +34,16 @@ class FcastAction: VideoClickAction() { ) { val link = result.links.getOrNull(index ?: 0) ?: return val devices = FcastManager.currentDevices.toList() - context?.getActivity()?.showBottomDialog( - devices.map { it.name }, - -1, - txt(R.string.player_settings_select_cast_device).asString(context), - false, - {}) { - val position = getViewPos(video.id)?.position - castTo(devices.getOrNull(it), link, position) + uiThread { + context?.getActivity()?.showBottomDialog( + devices.map { it.name }, + -1, + txt(R.string.player_settings_select_cast_device).asString(context), + false, + {}) { + val position = getViewPos(video.id)?.position + castTo(devices.getOrNull(it), link, position) + } } } From 3c385ea5e47b804706b66d08b49238dcfd181341 Mon Sep 17 00:00:00 2001 From: Jay Rathod <54902986+zzjjaayy@users.noreply.github.com> Date: Wed, 2 Oct 2024 01:38:40 +0530 Subject: [PATCH 011/962] fix accidental motion in player when accessing systemBars (#1358) --- .../ui/player/FullScreenPlayer.kt | 40 +++++++++++++++---- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 9325f067f..70f7e3686 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -21,13 +21,14 @@ import android.view.MotionEvent import android.view.Surface import android.view.View import android.view.ViewGroup +import android.view.WindowInsets import android.view.WindowManager import android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES 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.annotation.OptIn import androidx.core.graphics.blue import androidx.core.graphics.green import androidx.core.graphics.red @@ -76,6 +77,7 @@ import kotlin.math.max import kotlin.math.min import kotlin.math.round + const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking const val MINIMUM_VERTICAL_SWIPE = 2.0f // in percentage const val MINIMUM_HORIZONTAL_SWIPE = 2.0f // in percentage @@ -202,12 +204,34 @@ open class FullScreenPlayer : AbstractPlayerFragment() { throw NotImplementedError() } - /** Returns false if the touch is on the status bar or navigation bar*/ - private fun isValidTouch(rawX: Float, rawY: Float): Boolean { - val statusHeight = statusBarHeight ?: 0 - // val navHeight = navigationBarHeight ?: 0 - // nav height is removed because screenWidth already takes into account that - return rawY > statusHeight && rawX < screenWidth //- navHeight + /** + * [isValidTouch] should be called on a [View] spanning across the screen for reliable results. + * + * Android has supported gesture navigation properly since API-30. We get the absolute screen dimens using + * [WindowManager.getCurrentWindowMetrics] and remove the stable insets + * {[WindowInsets.getInsetsIgnoringVisibility]} to get a safe perimeter. + * This approach supports any and all types of necessary system insets. + * + * @return false if the touch is on the status bar or navigation bar + * */ + private fun View.isValidTouch(rawX: Float, rawY: Float): Boolean { + // NOTE: screenWidth is without the navbar width when 3button nav is turned on. + if(Build.VERSION.SDK_INT >= 30) { + // real = absolute dimen without any default deductions like navbar width + val windowMetrics = (context?.getSystemService(Context.WINDOW_SERVICE) as? WindowManager)?.currentWindowMetrics + val realScreenHeight = windowMetrics?.let { windowMetrics.bounds.bottom - windowMetrics.bounds.top } ?: screenHeight + val realScreenWidth = windowMetrics?.let { windowMetrics.bounds.right - windowMetrics.bounds.left } ?: screenWidth + + val insets = rootWindowInsets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()) + val isOutsideHeight = rawY < insets.top || rawY > (realScreenHeight - insets.bottom) + val isOutsideWidth = if(windowMetrics == null) rawX < screenWidth + else rawX < insets.left || rawX > (realScreenWidth - insets.right) + + return !(isOutsideWidth || isOutsideHeight) + } else { + val statusHeight = statusBarHeight ?: 0 + return rawY > statusHeight && rawX < screenWidth + } } override fun exitedPipMode() { @@ -937,7 +961,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { when (event.action) { MotionEvent.ACTION_DOWN -> { // validates if the touch is inside of the player area - isCurrentTouchValid = isValidTouch(currentTouch.x, currentTouch.y) + isCurrentTouchValid = view.isValidTouch(currentTouch.x, currentTouch.y) /*if (isCurrentTouchValid && player_episode_list?.isVisible == true) { player_episode_list?.isVisible = false } else*/ if (isCurrentTouchValid) { From 3a657d7bfcd75b4f454838d1a1eb665c1bea4be8 Mon Sep 17 00:00:00 2001 From: Jay Rathod <54902986+zzjjaayy@users.noreply.github.com> Date: Wed, 2 Oct 2024 01:42:04 +0530 Subject: [PATCH 012/962] #1302 Option to not show exit confirmation dialog (#1345) --- .../lagradost/cloudstream3/MainActivity.kt | 55 ++++++++++++------- .../ui/settings/SettingsFragment.kt | 8 ++- .../cloudstream3/ui/settings/SettingsUI.kt | 49 ++++++++++++----- .../main/res/drawable/ic_baseline_exit_24.xml | 5 ++ .../main/res/layout/confirm_exit_dialog.xml | 13 +++++ app/src/main/res/values/array.xml | 11 ++++ app/src/main/res/values/strings.xml | 7 +++ app/src/main/res/xml/settings_general.xml | 2 - app/src/main/res/xml/settings_ui.xml | 5 ++ 9 files changed, 117 insertions(+), 38 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_exit_24.xml create mode 100644 app/src/main/res/layout/confirm_exit_dialog.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index fa54545cf..7ea399dac 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -1,9 +1,11 @@ package com.lagradost.cloudstream3 import android.animation.ValueAnimator +import android.annotation.SuppressLint import android.content.ComponentName import android.content.Context import android.content.Intent +import android.content.SharedPreferences import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Rect @@ -18,6 +20,7 @@ import android.view.MenuItem import android.view.View import android.view.ViewGroup import android.view.WindowManager +import android.widget.CheckBox import android.widget.ImageView import android.widget.LinearLayout import android.widget.Toast @@ -29,7 +32,6 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout -import androidx.core.view.GravityCompat import androidx.core.view.children import androidx.core.view.isGone import androidx.core.view.isInvisible @@ -64,7 +66,6 @@ import com.lagradost.cloudstream3.APIHolder.initAll import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.loadThemes import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent @@ -169,7 +170,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.getResourceColor import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.requestRW -import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API @@ -613,14 +613,31 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa onUserLeaveHint(this) } - private fun showConfirmExitDialog() { - val builder: AlertDialog.Builder = AlertDialog.Builder(this) - builder.setTitle(R.string.confirm_exit_dialog) - builder.apply { - // Forceful exit since back button can actually go back to setup - setPositiveButton(R.string.yes) { _, _ -> exitProcess(0) } - setNegativeButton(R.string.no) { _, _ -> } + @SuppressLint("ApplySharedPref") // commit since the op needs to be synchronous + private fun showConfirmExitDialog(settingsManager: SharedPreferences) { + val confirmBeforeExit = settingsManager.getInt(getString(R.string.confirm_exit_key), -1) + when(confirmBeforeExit) { + // AUTO - Confirm exit is shown only on TV or EMULATOR by default + -1 -> if(isLayout(PHONE)) exitProcess(0) + // DON'T SHOW + 1 -> exitProcess(0) + // 0 -> SHOW + else -> { /*NO-OP : Continue*/ } } + + val dialogView = layoutInflater.inflate(R.layout.confirm_exit_dialog, null) + val dontShowAgainCheck: CheckBox = dialogView.findViewById(R.id.checkboxDontShowAgain) + val builder: AlertDialog.Builder = AlertDialog.Builder(this) + builder.setView(dialogView) + .setTitle(R.string.confirm_exit_dialog) + .setNegativeButton(R.string.no) { _, _ -> /*NO-OP*/} + .setPositiveButton(R.string.yes) { _, _ -> + if(dontShowAgainCheck.isChecked) { + settingsManager.edit().putInt(getString(R.string.confirm_exit_key), 1).commit() + } + exitProcess(0) + } + builder.show().setDefaultFocus() } @@ -1521,16 +1538,14 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa } } - if (isLayout(TV or EMULATOR)) { - if (navDestination.matchDestination(R.id.navigation_home)) { - attachBackPressedCallback { - showConfirmExitDialog() - window?.navigationBarColor = - colorFromAttribute(R.attr.primaryGrayBackground) - updateLocale() - } - } else detachBackPressedCallback() - } + if (navDestination.matchDestination(R.id.navigation_home)) { + attachBackPressedCallback { + showConfirmExitDialog(settingsManager) + window?.navigationBarColor = + colorFromAttribute(R.attr.primaryGrayBackground) + updateLocale() + } + } else detachBackPressedCallback() } //val navController = findNavController(R.id.nav_host_fragment) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 88335eeaf..339fe752d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -69,12 +69,16 @@ class SettingsFragment : Fragment() { } /** - * Hide the Preference on selected layouts. + * Hide the [Preference] on selected layouts. + * @return [Preference] if visible otherwise null. + * + * [hideOn] is usually followed by some actions on the preference which are mostly + * unnecessary when the preference is disabled for the said layout thus returning null. **/ fun Preference?.hideOn(layoutFlags: Int): Preference? { if (this == null) return null this.isVisible = !isLayout(layoutFlags) - return this + return if(this.isVisible) this else null } /** diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt index 8c3ad0ade..014bd93fa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt @@ -67,21 +67,23 @@ class SettingsUI : PreferenceFragmentCompat() { settingsManager.getInt(getString(R.string.app_layout_key), -1) activity?.showBottomDialog( - prefNames.toList(), - prefValues.indexOf(currentLayout), - getString(R.string.app_layout), - true, - {}) { - try { - settingsManager.edit() - .putInt(getString(R.string.app_layout_key), prefValues[it]) - .apply() - context?.updateTv() - activity?.recreate() - } catch (e: Exception) { - logError(e) + items = prefNames.toList(), + selectedIndex = prefValues.indexOf(currentLayout), + name = getString(R.string.app_layout), + showApply = true, + dismissCallback = {}, + callback = { + try { + settingsManager.edit() + .putInt(getString(R.string.app_layout_key), prefValues[it]) + .apply() + context?.updateTv() + activity?.recreate() + } catch (e: Exception) { + logError(e) + } } - } + ) return@setOnPreferenceClickListener true } @@ -187,5 +189,24 @@ class SettingsUI : PreferenceFragmentCompat() { return@setOnPreferenceClickListener true } + getPref(R.string.confirm_exit_key)?.setOnPreferenceClickListener { + val prefNames = resources.getStringArray(R.array.confirm_exit) + val prefValues = resources.getIntArray(R.array.confirm_exit_values) + val confirmExit = settingsManager.getInt(getString(R.string.confirm_exit_key), -1) + + activity?.showBottomDialog( + items = prefNames.toList(), + selectedIndex = prefValues.indexOf(confirmExit), + name = getString(R.string.confirm_before_exiting_title), + showApply = true, + dismissCallback = {}, + callback = { selectedOption -> + settingsManager.edit() + .putInt(getString(R.string.confirm_exit_key), prefValues[selectedOption]) + .apply() + } + ) + return@setOnPreferenceClickListener true + } } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_exit_24.xml b/app/src/main/res/drawable/ic_baseline_exit_24.xml new file mode 100644 index 000000000..bf421c227 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_exit_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/layout/confirm_exit_dialog.xml b/app/src/main/res/layout/confirm_exit_dialog.xml new file mode 100644 index 000000000..518aaa477 --- /dev/null +++ b/app/src/main/res/layout/confirm_exit_dialog.xml @@ -0,0 +1,13 @@ + + + + diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index a987420e9..9a2438d47 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -222,6 +222,17 @@ 2 + + @string/automatic + @string/show + @string/dont_show + + + -1 + 0 + 1 + + Normal Dandelion Yellow diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 56f2465d2..144529ae8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -808,4 +808,11 @@ Seekbar preview Enable preview thumbnail on seekbar No subtitles loaded yet + + Confirm before exiting + Show dialog before exiting the app + confirm_exit_key + Show + Don\'t Show + diff --git a/app/src/main/res/xml/settings_general.xml b/app/src/main/res/xml/settings_general.xml index 853bbda19..4ddc54f33 100644 --- a/app/src/main/res/xml/settings_general.xml +++ b/app/src/main/res/xml/settings_general.xml @@ -6,8 +6,6 @@ android:title="@string/app_language" android:icon="@drawable/ic_baseline_language_24" /> - - + Date: Fri, 4 Oct 2024 19:41:39 +0000 Subject: [PATCH 013/962] Update player_custom_layout.xml (#1366) --- app/src/main/res/layout/player_custom_layout.xml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index 3d78f0a60..412701c9f 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -170,24 +170,24 @@ Date: Sun, 13 Oct 2024 23:15:03 +0800 Subject: [PATCH 014/962] Added Filegram and Fix Vidguard Extractor (#1370) --- .../cloudstream3/extractors/Filegram.kt | 63 +++++++++++++++ .../cloudstream3/extractors/Vidguard.kt | 76 +++++++++++-------- .../cloudstream3/utils/ExtractorApi.kt | 2 + 3 files changed, 109 insertions(+), 32 deletions(-) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt new file mode 100644 index 000000000..df7e03373 --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt @@ -0,0 +1,63 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.USER_AGENT +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.M3u8Helper +import com.lagradost.cloudstream3.utils.fixUrl +import com.lagradost.cloudstream3.utils.getAndUnpack +import org.jsoup.nodes.Element + +open class Filegram : ExtractorApi() { + override val name = "Filegram" + override val mainUrl = "https://filegram.to" + override val requiresReferer = true + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val header = mapOf( + "Accept" to "*/*", + "Accept-language" to "en-US,en;q=0.9", + "Origin" to mainUrl, + "Accept-Encoding" to "gzip, deflate, br, zstd", + "Connection" to "keep-alive", + "Sec-Fetch-Dest" to "empty", + "Sec-Fetch-Mode" to "cors", + "Sec-Fetch-Site" to "same-site", + "user-agent" to USER_AGENT, + ) + + val doc = app.get(getEmbedUrl(url), referer = referer).document + val unpackedJs = unpackJs(doc).toString() + val videoUrl = Regex("""file:\s*"([^"]+\.m3u8[^"]*)"""").find(unpackedJs)?.groupValues?.get(1) + if (videoUrl != null) { + M3u8Helper.generateM3u8( + this.name, + fixUrl(videoUrl), + "$mainUrl/", + headers = header + ).forEach(callback) + } + } + + private fun unpackJs(script: Element): String? { + return script.select("script").find { it.data().contains("eval(function(p,a,c,k,e,d)") } + ?.data()?.let { getAndUnpack(it) } + } + + private fun getEmbedUrl(url: String): String { + return if (!url.contains("/embed-")) { + val videoId = url.substringAfter("$mainUrl/") + "$mainUrl/embed-$videoId" + } else { + url + } + } + +} diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidguard.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidguard.kt index eb4244a51..22f66936d 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidguard.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vidguard.kt @@ -12,7 +12,8 @@ import org.mozilla.javascript.Context import org.mozilla.javascript.NativeJSON import org.mozilla.javascript.NativeObject import org.mozilla.javascript.Scriptable -import java.util.Base64 +import kotlin.io.encoding.Base64 +import kotlin.io.encoding.ExperimentalEncodingApi class Vidguardto1 : Vidguardto() { override val mainUrl = "https://bembed.net" @@ -37,8 +38,7 @@ open class Vidguardto : ExtractorApi() { subtitleCallback: (SubtitleFile) -> Unit, callback: (ExtractorLink) -> Unit ) { - val embedUrl = url.replace("/d/", "/e/") - val res = app.get(embedUrl) + val res = app.get(getEmbedUrl(url)) val resc = res.document.select("script:containsData(eval)").firstOrNull()?.data() resc?.let { val jsonStr2 = AppUtils.parseJson(runJS2(it)) @@ -57,41 +57,43 @@ open class Vidguardto : ExtractorApi() { } } - private fun sigDecode(url: String): String { + @OptIn(ExperimentalEncodingApi::class) + private fun sigDecode(url: String): String { val sig = url.split("sig=")[1].split("&")[0] - var t = "" - for (v in sig.chunked(2)) { - val byteValue = Integer.parseInt(v, 16) xor 2 - t += byteValue.toChar() - } - val padding = when (t.length % 4) { - 2 -> "==" - 3 -> "=" - else -> "" - } - val decoded = Base64.getDecoder().decode((t + padding).toByteArray(Charsets.UTF_8)) - t = String(decoded).dropLast(5).reversed() - val charArray = t.toCharArray() - for (i in 0 until charArray.size - 1 step 2) { - val temp = charArray[i] - charArray[i] = charArray[i + 1] - charArray[i + 1] = temp - } - val modifiedSig = String(charArray).dropLast(5) - return url.replace(sig, modifiedSig) + val t = sig.chunked(2) + .joinToString("") { (Integer.parseInt(it, 16) xor 2).toChar().toString() } + .let { + val padding = when (it.length % 4) { + 2 -> "==" + 3 -> "=" + else -> "" + } + String(Base64.decode((it + padding).toByteArray(Charsets.UTF_8))) + } + .dropLast(5) + .reversed() + .toCharArray() + .apply { + for (i in indices step 2) { + if (i + 1 < size) { + this[i] = this[i + 1].also { this[i + 1] = this[i] } + } + } + } + .concatToString() + .dropLast(5) + return url.replace(sig, t) } private fun runJS2(hideMyHtmlContent: String): String { var result = "" val r = Runnable { - Log.d("runJS", "start") val rhino = Context.enter() rhino.initSafeStandardObjects() rhino.optimizationLevel = -1 val scope: Scriptable = rhino.initSafeStandardObjects() scope.put("window", scope, scope) try { - Log.d("runJS", "Executing JavaScript: $hideMyHtmlContent") rhino.evaluateString( scope, hideMyHtmlContent, @@ -101,27 +103,37 @@ open class Vidguardto : ExtractorApi() { ) val svgObject = scope.get("svg", scope) result = if (svgObject is NativeObject) { - NativeJSON.stringify(Context.getCurrentContext(), scope, svgObject, null, null) - .toString() + NativeJSON.stringify( + Context.getCurrentContext(), + scope, + svgObject, + null, + null + ).toString() } else { Context.toString(svgObject) } - Log.d("runJS", "Result: $result") } catch (e: Exception) { Log.e("runJS", "Error executing JavaScript: ${e.message}") } finally { Context.exit() } } - val t = Thread(ThreadGroup("A"), r, "thread_rhino", 2000000)// StackSize 2Mb: Run in a thread because rhino requires more stack size for large scripts. - t.start(); + val t = Thread(ThreadGroup("A"), r, "thread_rhino", 8 * 1024 * 1024) // Increase stack size to 8MB + t.start() t.join() t.interrupt() return result } + + private fun getEmbedUrl(url: String): String { + return url.takeIf { it.contains("/d/") } + ?.replace("/d/", "/e/") ?: url + } data class SvgObject( val stream: String, val hash: String - ) + ) + } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 3244f9942..83e3c4f49 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -52,6 +52,7 @@ import com.lagradost.cloudstream3.extractors.FileMoon import com.lagradost.cloudstream3.extractors.FileMoonIn import com.lagradost.cloudstream3.extractors.FileMoonSx import com.lagradost.cloudstream3.extractors.Filesim +import com.lagradost.cloudstream3.extractors.Filegram import com.lagradost.cloudstream3.extractors.Fplayer import com.lagradost.cloudstream3.extractors.Geodailymotion import com.lagradost.cloudstream3.extractors.GMPlayer @@ -1011,6 +1012,7 @@ val extractorApis: MutableList = arrayListOf( Boosterx(), Ds2play(), Ds2video(), + Filegram(), ) From 54f22139d864840863d93f1588b5801e7987c2c7 Mon Sep 17 00:00:00 2001 From: Jay Rathod <54902986+zzjjaayy@users.noreply.github.com> Date: Sun, 13 Oct 2024 21:34:07 +0530 Subject: [PATCH 015/962] feat: option to choose backup target directory (#1354) --- .../ui/settings/SettingsGeneral.kt | 50 ++++--------- .../ui/settings/SettingsUpdates.kt | 73 +++++++++++++++++-- .../ui/settings/utils/DirectoryPicker.kt | 27 +++++++ .../cloudstream3/utils/BackupUtils.kt | 53 +++++++++++++- .../drawable/ic_baseline_folder_open_24.xml | 5 ++ app/src/main/res/values/strings.xml | 5 ++ app/src/main/res/xml/settings_updates.xml | 5 ++ 7 files changed, 172 insertions(+), 46 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt create mode 100644 app/src/main/res/drawable/ic_baseline_folder_open_24.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index 7cb1a848f..da723b738 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -7,7 +7,6 @@ import android.os.Build import android.os.Bundle import android.view.View import android.widget.Toast -import androidx.activity.result.contract.ActivityResultContracts import androidx.appcompat.app.AlertDialog import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager @@ -35,6 +34,7 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar +import com.lagradost.cloudstream3.ui.settings.utils.getChooseFolderLauncher import com.lagradost.cloudstream3.utils.BatteryOptimizationChecker.isAppRestricted import com.lagradost.cloudstream3.utils.BatteryOptimizationChecker.showBatteryOptimizationDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog @@ -46,7 +46,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath -import com.lagradost.safefile.SafeFile // Change local language settings in the app. fun getCurrentLocale(context: Context): String { @@ -146,34 +145,15 @@ class SettingsGeneral : PreferenceFragmentCompat() { val lang: String, ) - // Open file picker - private val pathPicker = - registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri -> - // It lies, it can be null if file manager quits. - if (uri == null) return@registerForActivityResult - val context = context ?: AcraApplication.context ?: return@registerForActivityResult - // RW perms for the path - val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or - Intent.FLAG_GRANT_WRITE_URI_PERMISSION - - context.contentResolver.takePersistableUriPermission(uri, flags) - - val file = SafeFile.fromUri(context, uri) - val filePath = file?.filePath() - println("Selected URI path: $uri - Full path: $filePath") - - // Stores the real URI using download_path_key - // Important that the URI is stored instead of filepath due to permissions. - PreferenceManager.getDefaultSharedPreferences(context) - .edit().putString(getString(R.string.download_path_key), uri.toString()).apply() - - // From URI -> File path - // File path here is purely for cosmetic purposes in settings - (filePath ?: uri.toString()).let { - PreferenceManager.getDefaultSharedPreferences(context) - .edit().putString(getString(R.string.download_path_pref), it).apply() - } + private val pathPicker = getChooseFolderLauncher { uri, path -> + val context = context ?: AcraApplication.context ?: return@getChooseFolderLauncher + (path ?: uri.toString()).let { + PreferenceManager.getDefaultSharedPreferences(context).edit() + .putString(getString(R.string.download_path_key), uri.toString()) + .putString(getString(R.string.download_path_key_visual), it) + .apply() } + } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { hideKeyboard() @@ -367,11 +347,11 @@ class SettingsGeneral : PreferenceFragmentCompat() { val dirs = getDownloadDirs() val currentDir = - settingsManager.getString(getString(R.string.download_path_pref), null) + settingsManager.getString(getString(R.string.download_path_key_visual), null) ?: context?.let { ctx -> VideoDownloadManager.getDefaultDir(ctx)?.filePath() } activity?.showBottomDialog( - dirs + listOf("Custom"), + dirs + listOf(getString(R.string.custom)), dirs.indexOf(currentDir), getString(R.string.download_path_pref), true, @@ -386,11 +366,11 @@ class SettingsGeneral : PreferenceFragmentCompat() { } else { // Sets both visual and actual paths. // key = used path - // pref = visual path + // visual = visual path settingsManager.edit() - .putString(getString(R.string.download_path_key), dirs[it]).apply() - settingsManager.edit() - .putString(getString(R.string.download_path_pref), dirs[it]).apply() + .putString(getString(R.string.download_path_key), dirs[it]) + .putString(getString(R.string.download_path_key_visual), dirs[it]) + .apply() } } return@setOnPreferenceClickListener true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 260c66747..4c45480cd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.settings +import android.net.Uri import android.os.Bundle import android.view.View import android.widget.Toast @@ -14,13 +15,18 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.databinding.LogcatBinding import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.services.BackupWorkManager import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR +import com.lagradost.cloudstream3.ui.settings.Globals.TV 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 import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar +import com.lagradost.cloudstream3.ui.settings.utils.getChooseFolderLauncher import com.lagradost.cloudstream3.utils.BackupUtils import com.lagradost.cloudstream3.utils.BackupUtils.restorePrompt import com.lagradost.cloudstream3.utils.Coroutines.ioSafe @@ -37,7 +43,8 @@ import java.io.InputStreamReader import java.io.OutputStream import java.lang.System.currentTimeMillis import java.text.SimpleDateFormat -import java.util.* +import java.util.Date +import java.util.Locale class SettingsUpdates : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -47,10 +54,20 @@ class SettingsUpdates : PreferenceFragmentCompat() { setToolBarScrollFlags() } + private val pathPicker = getChooseFolderLauncher { uri, path -> + val context = context ?: AcraApplication.context ?: return@getChooseFolderLauncher + (path ?: uri.toString()).let { + PreferenceManager.getDefaultSharedPreferences(context).edit() + .putString(getString(R.string.backup_path_key), uri.toString()) + .putString(getString(R.string.backup_dir_key), it) + .apply() + } + } + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { hideKeyboard() setPreferencesFromResource(R.xml.settings_updates, rootKey) - //val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext()) + val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext()) getPref(R.string.backup_key)?.setOnPreferenceClickListener { BackupUtils.backup(activity) @@ -58,8 +75,6 @@ class SettingsUpdates : PreferenceFragmentCompat() { } getPref(R.string.automatic_backup_key)?.setOnPreferenceClickListener { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext()) - val prefNames = resources.getStringArray(R.array.periodic_work_names) val prefValues = resources.getIntArray(R.array.periodic_work_values) val current = settingsManager.getInt(getString(R.string.automatic_backup_key), 0) @@ -89,6 +104,38 @@ class SettingsUpdates : PreferenceFragmentCompat() { activity?.restorePrompt() return@setOnPreferenceClickListener true } + getPref(R.string.backup_path_key)?.hideOn(TV or EMULATOR)?.setOnPreferenceClickListener { + val dirs = getBackupDirsForDisplay() + val currentDir = + settingsManager.getString(getString(R.string.backup_dir_key), null) + ?: context?.let { ctx -> BackupUtils.getDefaultBackupDir(ctx)?.filePath() } + + activity?.showBottomDialog( + dirs + listOf(getString(R.string.custom)), + dirs.indexOf(currentDir), + getString(R.string.backup_path_title), + true, + {}) { + // Last = custom + if (it == dirs.size) { + try { + pathPicker.launch(Uri.EMPTY) + } catch (e: Exception) { + logError(e) + } + } else { + // Sets both visual and actual paths. + // path = used uri + // dir = dir path + settingsManager.edit() + .putString(getString(R.string.backup_path_key), dirs[it]) + .putString(getString(R.string.backup_dir_key), dirs[it]) + .apply() + } + } + return@setOnPreferenceClickListener true + } + getPref(R.string.show_logcat_key)?.setOnPreferenceClickListener { pref -> val builder = AlertDialog.Builder(pref.context, R.style.AlertDialogCustom) @@ -156,8 +203,6 @@ class SettingsUpdates : PreferenceFragmentCompat() { } getPref(R.string.apk_installer_key)?.setOnPreferenceClickListener { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(it.context) - val prefNames = resources.getStringArray(R.array.apk_installer_pref) val prefValues = resources.getIntArray(R.array.apk_installer_values) @@ -196,8 +241,6 @@ class SettingsUpdates : PreferenceFragmentCompat() { } getPref(R.string.auto_download_plugins_key)?.setOnPreferenceClickListener { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(it.context) - val prefNames = resources.getStringArray(R.array.auto_download_plugin) val prefValues = enumValues().sortedBy { x -> x.value }.map { x -> x.value } @@ -217,4 +260,18 @@ class SettingsUpdates : PreferenceFragmentCompat() { return@setOnPreferenceClickListener true } } + + private fun getBackupDirsForDisplay(): List { + return normalSafeApiCall { + context?.let { ctx -> + val defaultDir = BackupUtils.getDefaultBackupDir(ctx)?.filePath() + val first = listOf(defaultDir) + (runCatching { + first + BackupUtils.getCurrentBackupDir(ctx).let { + it.first?.filePath() ?: it.second + } + }.getOrNull() ?: first).filterNotNull().distinct() + } + } ?: emptyList() + } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt new file mode 100644 index 000000000..9e126b7a6 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt @@ -0,0 +1,27 @@ +package com.lagradost.cloudstream3.ui.settings.utils + +import android.content.Intent +import android.net.Uri +import androidx.activity.result.contract.ActivityResultContracts +import androidx.fragment.app.Fragment +import com.lagradost.cloudstream3.AcraApplication +import com.lagradost.safefile.SafeFile + +fun Fragment.getChooseFolderLauncher(dirSelected: (uri: Uri?, path: String?) -> Unit) = + registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri -> + // It lies, it can be null if file manager quits. + if (uri == null) return@registerForActivityResult + val context = context ?: AcraApplication.context ?: return@registerForActivityResult + // RW perms for the path + val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or + Intent.FLAG_GRANT_WRITE_URI_PERMISSION + + context.contentResolver.takePersistableUriPermission(uri, flags) + + val filePath = SafeFile.fromUri(context, uri)?.filePath() + println("Selected URI path: $uri - Full path: $filePath") + + // store the actual URI instead of the path due to permissions. + // filePath should only be used for cosmetic purposes. + dirSelected(uri, filePath) + } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt index b25be59f9..a44af8773 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt @@ -7,7 +7,9 @@ import android.widget.Toast import androidx.activity.result.ActivityResultLauncher import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.WorkerThread +import androidx.core.net.toUri import androidx.fragment.app.FragmentActivity +import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.module.kotlin.readValue import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity @@ -35,8 +37,14 @@ import com.lagradost.cloudstream3.utils.DataStore.getSharedPrefs import com.lagradost.cloudstream3.utils.DataStore.mapper import com.lagradost.cloudstream3.utils.UIHelper.checkWrite import com.lagradost.cloudstream3.utils.UIHelper.requestRW +import com.lagradost.cloudstream3.utils.VideoDownloadManager.StreamData +import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath import com.lagradost.cloudstream3.utils.VideoDownloadManager.setupStream +import com.lagradost.safefile.MediaFileContentType +import com.lagradost.safefile.SafeFile import okhttp3.internal.closeQuietly +import java.io.File +import java.io.IOException import java.io.OutputStream import java.io.PrintWriter import java.lang.System.currentTimeMillis @@ -69,7 +77,12 @@ object BackupUtils { "biometric_key", // can lock down users if backup is shared on a incompatible device "nginx_user", // Nginx user key - "download_path_key" // No access rights after restore data from backup + + // No access rights after restore data from backup + "download_path_key", + "download_path_key_visual", + "backup_path_key", + "backup_dir_path_key" ) /** false if key should not be contained in backup */ @@ -166,10 +179,9 @@ object BackupUtils { } val date = SimpleDateFormat("yyyy_MM_dd_HH_mm").format(Date(currentTimeMillis())) - val ext = "txt" val displayName = "CS3_Backup_${date}" val backupFile = getBackup(context) - val stream = setupStream(context, displayName, null, ext, false) + val stream = setupBackupStream(context, displayName) fileStream = stream.openNew() printStream = PrintWriter(fileStream) @@ -195,6 +207,18 @@ object BackupUtils { } } + @Throws(IOException::class) + private fun setupBackupStream(context: Context, name: String, ext: String = "txt"): StreamData { + return setupStream( + baseFile = getCurrentBackupDir(context).first ?: getDefaultBackupDir(context) + ?: throw IOException("Bad config"), + name, + folder = null, + extension = ext, + tryResume = false + ) + } + fun FragmentActivity.setUpBackup() { try { restoreFileSelector = @@ -264,4 +288,27 @@ object BackupUtils { } editor.apply() } + + /** + * Copy of [VideoDownloadManager.basePathToFile], [VideoDownloadManager.getDefaultDir] and [VideoDownloadManager.getBasePath] + * modded for backup specific paths + * */ + + fun getDefaultBackupDir(context: Context): SafeFile? { + return SafeFile.fromMedia(context, MediaFileContentType.Downloads) + } + + fun getCurrentBackupDir(context: Context): Pair { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) + val basePathSetting = settingsManager.getString(context.getString(R.string.backup_path_key), null) + return baseBackupPathToFile(context, basePathSetting) to basePathSetting + } + + private fun baseBackupPathToFile(context: Context, path: String?): SafeFile? { + return when { + path.isNullOrBlank() -> getDefaultBackupDir(context) + path.startsWith("content://") -> SafeFile.fromUri(context, path.toUri()) + else -> SafeFile.fromFile(context, File(path)) + } + } } diff --git a/app/src/main/res/drawable/ic_baseline_folder_open_24.xml b/app/src/main/res/drawable/ic_baseline_folder_open_24.xml new file mode 100644 index 000000000..6e130c3c9 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_folder_open_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 144529ae8..30c2a9498 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -46,6 +46,7 @@ dns_key jsdelivr_proxy_key download_path_key + download_path_key_visual Cloudstream app_layout_key primary_color_key @@ -808,6 +809,10 @@ Seekbar preview Enable preview thumbnail on seekbar No subtitles loaded yet + backup_path_key + Backup folder location + backup_dir_key + Custom Confirm before exiting Show dialog before exiting the app diff --git a/app/src/main/res/xml/settings_updates.xml b/app/src/main/res/xml/settings_updates.xml index 102f8ee4b..741a62a7a 100644 --- a/app/src/main/res/xml/settings_updates.xml +++ b/app/src/main/res/xml/settings_updates.xml @@ -46,6 +46,11 @@ android:icon="@drawable/baseline_restore_page_24" android:key="@string/restore_key" android:title="@string/restore_settings" /> + + Date: Sun, 13 Oct 2024 19:24:46 +0200 Subject: [PATCH 016/962] Translations update from Hosted Weblate (#1348) Co-authored-by: Anarchydr Co-authored-by: Cloudburst <18114966+C10udburst@users.noreply.github.com> Co-authored-by: Dan Co-authored-by: Fjuro Co-authored-by: James Sands Co-authored-by: Jose Delvani Co-authored-by: Kyivska Hryvnya Co-authored-by: Massimo Pissarello Co-authored-by: Matthaiks Co-authored-by: Pizza Party Co-authored-by: PumbaLP Galant Co-authored-by: Rex_sa Co-authored-by: Sanloz Co-authored-by: Tiago Lucas Co-authored-by: gallegonovato --- app/src/main/res/values-ajp/strings.xml | 12 ++-- app/src/main/res/values-ar/strings.xml | 4 ++ app/src/main/res/values-bp/strings.xml | 4 ++ app/src/main/res/values-cs/strings.xml | 4 ++ app/src/main/res/values-de/strings.xml | 7 +- app/src/main/res/values-es/strings.xml | 4 ++ app/src/main/res/values-fr/strings.xml | 3 +- app/src/main/res/values-hr/strings.xml | 10 ++- app/src/main/res/values-in/strings.xml | 1 - app/src/main/res/values-it/strings.xml | 92 +++++++++++++------------ app/src/main/res/values-pl/strings.xml | 4 ++ app/src/main/res/values-pt/strings.xml | 9 ++- app/src/main/res/values-qt/strings.xml | 13 +++- app/src/main/res/values-ru/strings.xml | 5 +- app/src/main/res/values-sv/strings.xml | 1 - app/src/main/res/values-tr/strings.xml | 1 - app/src/main/res/values-uk/strings.xml | 6 +- app/src/main/res/values-vi/strings.xml | 8 ++- 18 files changed, 121 insertions(+), 67 deletions(-) diff --git a/app/src/main/res/values-ajp/strings.xml b/app/src/main/res/values-ajp/strings.xml index 0b30cebac..8b442c831 100644 --- a/app/src/main/res/values-ajp/strings.xml +++ b/app/src/main/res/values-ajp/strings.xml @@ -6,7 +6,7 @@ التنزيلات %1$sالحلقة %2$d محي الملف - سوري، الآپ تعطل. رح ينبعت تقرير عن المشكلة للمطورين. + سوري، الآپ تعطل. رح ينبعت تقرير عن المشكلة للمطورين %1$d %2$s بس بعات البيانات وقت ما يتعطل الآپ شوف الخلفية @@ -443,7 +443,7 @@ التلخيص إِيه الرئيسي - سكر الآپ حتى تطبق التغيرات + سكر الآپ حتى تطبق التغيرات. ساعدوني عوز هيدا إذا عم بتبين الترجمة %d ميلي ثانية بعد ما لازم م نلاقا الآپ @@ -452,7 +452,7 @@ مقترح TS فتاح من الإنترنت - غَبر شكل الآپ حتى يتناسب مع جهازك + غَير شكل الآپ حتى يتناسب مع جهازك مطفي: %d وقف هيدي للايحة فاضية. جربو تبدلو ع غيرا. @@ -637,7 +637,7 @@ بلشه من الأول تحذير فتاح الڤيديو اللي ع جهازك - مافي تنزيلات هلّق + مافي تنزيلات هلّق. محي الإضافة محي اسامي كبسات صيطرة مشغل الڤيديو نهار اللي نزل (من جديد ل قديم) @@ -663,4 +663,8 @@ صورة زغيرة مع التقريب وال تبعيد بت حط صورة زغير من الڤيديو إنت و عم بت قرب أو ترجع بال ڤيديو بعد مش معمول لود لولا ترجمة + فرجي + م تفرجي + طلوب تأكيد قبلما تطلع من الآپ + أكّد إنو بدك تطلع \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 56bb4b4a0..f32335263 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -689,4 +689,8 @@ معاينة شريط البحث تمكين معاينة الصورة المصغرة على شريط البحث لم يتم تحميل أي ترجمات بعد + تأكيد قبل الخروج + عرض مربع الحوار قبل الخروج من التطبيق + أظهِر + لا تظهر \ No newline at end of file diff --git a/app/src/main/res/values-bp/strings.xml b/app/src/main/res/values-bp/strings.xml index 55e1ce180..0b50327ae 100644 --- a/app/src/main/res/values-bp/strings.xml +++ b/app/src/main/res/values-bp/strings.xml @@ -679,4 +679,8 @@ Ativar visualização de miniatura na barra de busca Visualização da barra de busca Ainda não há legendas carregadas + Mostrar + Confirmar antes de sair + Mostrar caixa de diálogo antes de sair do aplicativo + Não mostrar \ No newline at end of file diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index ba03b05fb..104017444 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -681,4 +681,8 @@ Náhled v liště přehrávače Povolit náhled miniatur na liště přehrávače Zatím nenačteny žádné titulky + Potvrdit před ukončením + Zobrazit dialog před ukončením aplikace + Zobrazit + Nezobrazovat \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 6cf8ff3bd..a0774097c 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -390,8 +390,8 @@ Fertig Erweiterungen Repository hinzufügen - Repository-Name - Repository-URL + Repository Name (Optional) + Repository-URL oder Kurz Code Plugin geladen Plugin gelöscht Fehler beim Laden von %s @@ -652,4 +652,7 @@ \n%s Veröffentlichungsdatum (von neu nach alt) Veröffentlichungsdatum (von alt nach neu) + Suchleisten Vorschau + Vorschau Bilder in der Suchleiste einschalten + Keine Untertitel geladen \ No newline at end of file diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index a0c4587f5..32453ec14 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -657,4 +657,8 @@ Activar la previsualización para las miniaturas en la barra de búsqueda Previsualización de Seekbar Aún no hay subtítulos cargados + Mostrar el diálogo antes de salir de la aplicación + Mostrar + No mostrar + Confirmar antes de salir \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 5b3f09120..e446b6c19 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -138,7 +138,7 @@ DNS avec HTTPS Afficher les animés en Anglais (Dub) / sous-titrés Disposition en mode téléphone - episode_action_copy_link + %1$s Ep %2$d Note : %.1f Zoom Adapter à l\'écran @@ -628,7 +628,6 @@ Ouvrir le dépôt Code expire dans %1$dm %2$ds Wiki de CloudStream - MPV YTDL Le code PIN est maintenant expiré ! Image du code QR Supprimer l\'extension diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index dcacbeee3..8fc2850c6 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -411,8 +411,8 @@ Gotovo Proširenja Dodaj repozitorij - Ime repozitorija - URL repozitorija + Ime repozitorija (Neobavezno) + URL repozitorija ili kratki kod Dodatak učitan Dodatak izbrisan Nije moguće učitati %s @@ -680,4 +680,10 @@ \n \n%s Još nije učitan nijedan titl + Pretpregled trake za traženje + Omogući minijaturu pregleda na traci za pretraživanje + Potvrdi prije izlaza + Prikaži dijaloški okvir prije izlaska iz aplikacije + Prikaži + Ne prikazuj \ No newline at end of file diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index efd0ed0cf..f7ab78f0f 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -647,7 +647,6 @@ Mengabaikan Kode kedaluwarsa dalam %1$dm %2$ds Otorisasi Lokal - MPV YTDL Kunjungi %s di ponsel pintar atau komputer Anda dan masukkan kode di atas Pratinjau bilah pencarian Aktifkan pratinjau gambar mini di bilah pencarian diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 590c167d9..7955b8ca3 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -134,12 +134,12 @@ Tocca due volte il lato destro o sinistro dello schermo per mandare avanti o indietro il video Tocca due volte al centro per mettere in pausa Intervallo di ricerca lettore (secondi) - Utilizzare la luminosità del sistema - Utilizzare la luminosità del sistema al posto di una sovrapposizione scura + Usa luminosità del sistema + Utilizza la luminosità del sistema nel lettore dell\'app anziché una sovrapposizione scura Sincronizza automaticamente gli episodi guardati - Ripristinare i dati da backup - Backup data + Ripristina dati da un backup + Backup dei dati File di backup caricato Impossibile ripristinare i dati dal file %s Dati salvati @@ -187,7 +187,7 @@ Nessun episodio trovato Elimina file Elimina - Cancella + Annulla Pausa Riprendi -30 @@ -218,7 +218,7 @@ OVA Torrent Documentari - Drama Asiatici + Drammi asiatici Livestreams NSFW Altri @@ -230,34 +230,34 @@ OVA Torrent Documentario - Drama Asiatico + Dramma asiatico Livestream NSFW Altro - Source error + Errore sorgente Errore remoto Errore del renderer - Errore inaspettato nel player video + Errore imprevisto del lettore Errore download, controlla i permessi di archiviazione Episodio Chromecast Mirror Chromecast - Riproduci in app + Riproduci nell\'app Riproduci in %s - Download + Download automatico Mirror download - Aggiorna link + Ricarica link Download sottotitoli - Etichetta Qualità - Etichetta Dub - Etichetta Sub + Etichetta qualità + Etichetta dub + Etichetta sub Titolo - Etichette sui Poster + Attiva/disattiva elementi dell\'UI sul poster Nessun aggiornamento trovato Controlla aggiornamenti Blocca Ridimensiona Sorgente - Salta intro + Salta OP Non mostrare di nuovo Salta questo aggiornamento Aggiorna @@ -270,12 +270,12 @@ Cancella cache immagini e video Causa arresti anomali se impostato troppo alto sui dispositivi con poca memoria, come Android TV. Se impostato troppo alto può causare problemi su sistemi con poca archiviazione interna, come i dispositivi Android TV. - DNS over HTTPS - Utile per bypassare i blocchi ISP + DNS su HTTPS + Utile per aggirare i blocchi ISP Clona un sito Rimuovi sito Aggiungi un clone di un sito esistente, con un URL differente - Posizione Download + Percorso di download URL server NGINX Mostra anime doppiati/sottotitolati Adatta allo schermo @@ -306,7 +306,7 @@ hello@world.com 127.0.0.1 NuovoNomeSito - https://example.com + https://esempio.com Codice lingua (it) %1$s %2$s account @@ -323,7 +323,7 @@ /?? /%d %s autenticato - Impossibile autenticarsi a %s + Impossibile effettuare l\'accesso a %s Nessuno Normale @@ -337,8 +337,8 @@ Sincronizza sottotitoli 1000 ms Ritardo sottotitoli - Usa se i sottotitoli sono mostrati %d ms troppo presto - Usa se i sottotitoli vengono mostrati %d ms troppo tardi + Usalo se i sottotitoli vengono mostrati %d ms in anticipo + Usalo se i sottotitoli vengono mostrati %d ms in ritardo Nessun ritardo dei sottotitoli Cancella cronologia Cronologia - Mostra popup per salta sigla iniziale/finale + Mostra popup di salto per apertura/fine Testo troppo lungo. Impossibile salvare negli appunti. CloudStream Scarica automaticamente i plugins Impossibile installare la nuova versione dell\'app - Aggiorna il progresso di avanzamento + Aggiorna progresso di avanzamento Opening misto - Installando l\'aggiornamento dell\'app… + Installazione aggiornamento dell\'app… Rimuovi le didascalie chiuse dai sottotitoli Rimuovi il bloat dai sottotitoli Opening @@ -476,7 +476,7 @@ Sei sicuro di voler uscire? No - Scaricando l\'aggiornamento dell\'app… + Download aggiornamento dell\'app… Installa automaticamente tutti i plugin non ancora installati dai repository aggiunti. ProgrammaInstallazionePacchetti Alcuni telefoni non supportano il nuovo programma di installazione dei pacchetti. Prova l\'opzione legacy se gli aggiornamenti non vengono installati. @@ -536,7 +536,7 @@ Iscritto a %s Impossibile raggiungere GitHub. Attivazione proxy jsDelivr… Evita il blocco degli URL github non elaborati utilizzando jsDelivr. Potrebbe causare un ritardo degli aggiornamenti di alcuni giorni. - Baypass ISP + Bypass ISP Ripristina Aggiornando shows a cui sei iscritto L\'episodio %d è stato rilasciato! @@ -575,27 +575,27 @@ Frequenza di backup Trovato Possibile Duplicato Aggiungi ai preferiti - Rimpiazza tutti + Sostituisci tutto PIN non corretto. Riprova. Disiscriviti Il PIN deve essere almeno di 4 caratteri - Rimpiazza + Sostituisci Aggiungi Iscriviti Rimuovi dai preferiti - Seleziona un Account + Seleziona un account Sembra che nella tua libreria esista già un elemento potenzialmente duplicato: \'%s.\' \n \nDesideri aggiungere comunque questo elemento, sostituire quello esistente o annullare l\'azione? Inserisci PIN PIN - Inserisci PIN Corrente - Entrato come %s + Inserisci PIN corrente + Connesso come %s Inserisci il PIN per %s Blocca profilo Usa account predefinito Salta la selezione dell\'account all\'avvio - Gestisci Accounts + Gestisci account Modifica account Collegamenti ricaricati Ruota @@ -609,7 +609,7 @@ Questo test è pensato solo per gli sviluppatori e non verifica o nega il funzionamento di alcuna estensione. Notifica nuovo episodio Sblocca CloudStream - Blocca con biometria + Blocco con dati biometrici Autenticazione con password/PIN L\'autenticazione biometrica non è supportata su questo dispositivo Sblocca app con impronta digitale, Face ID, PIN, sequenza e password. @@ -677,4 +677,8 @@ Anteprima barra di ricerca Abilita miniatura di anteprima sulla barra di ricerca Nessun sottotitolo caricato + Conferma prima di uscire + Mostra finestra di dialogo prima di uscire dall\'app + Mostra + Non mostrare \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 2f26c3f5b..6c1cb88bb 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -658,4 +658,8 @@ Podgląd paska przewijania Włącz podgląd miniatury na pasku wyszukiwania Nie wczytano jeszcze napisów + Pokaż okno dialogowe przed wyjściem z aplikacji + Pokaż + Potwierdź przed wyjściem + Nie pokazuj \ No newline at end of file diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 7fdbc6bee..da4b1fc14 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -354,8 +354,8 @@ Feito Extensões Adicionar repositório - Nome do repositório - URL do repositório + Nome do repositório (Opcional) + URL do repositório or Referência Plugin Carregado Plugin Apagado Falha ao carregar %s @@ -622,7 +622,6 @@ Irá também eliminar permanentemente todos os episódios da seguinte série: \n \n%s - MPV YTDL Eliminar (%1$d | %2$s) Visite %s no seu smartphone ou computador e introduza o código acima Recomeçar @@ -656,4 +655,8 @@ Ainda sem legendadas carregadas Abrir repositório Dispensar + Confirmar antes de sair + Mostrar diálogo antes de sair da aplicação + Mostrar + Não mostrar \ No newline at end of file diff --git a/app/src/main/res/values-qt/strings.xml b/app/src/main/res/values-qt/strings.xml index 8f0e14cbc..2ca5a5881 100644 --- a/app/src/main/res/values-qt/strings.xml +++ b/app/src/main/res/values-qt/strings.xml @@ -153,7 +153,7 @@ ohaooo-ahahouuhhh ahohooh aaaaaohaah ooh - ahh haa ah ooooo + Boooooo ooooo aaahhuooh ahh aaaaa oooohh aauugghh aaaghhahhooo-ahahahhha aaaghh @@ -311,7 +311,7 @@ 4K oohh ah uuk aagg ag aah uug uuuugggoog - uuuuggguug uugg + uuuuggguug uugg (uuooal) aaaahhhaah %1$d %2$s aaahh uuuugggh oooohh oooogggoog @@ -604,7 +604,7 @@ aaagg oooogg og uuugg aaaagggag aaak ooohh uuuuuk aag aaak uh ooh aah oh oogg oohh uuuugg - aaaaggguuh uug + aaaaggguuh uug o aaooolllo uuuugg uuuuhhh ooogg eek oogg %s aaaagg @@ -654,4 +654,11 @@ uuhh ug oooohhh ooh oogg uh 4 uuuuhhhooh uuuugg oh oooohhh + Oo hoo-hoo haaa-oo ar-ar + ee-ee-ar-ar pee-ee-oo-oo kik-kik oo kik-hoo + kik-kik-kik pee-ee-oo-oo + Boo kik-kik oo-chit ar-ar coo haa + Boo + Oo-ahh oo-chit ar-ar + Boooooo \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index c251ca9bb..5dd66d0f8 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -656,6 +656,9 @@ Войти локально Предосмотр поисковой строки Включить предосмотр миниатюр в поисковой строке - MPV YTDL Субтитры еще не загружены + Подтвердить перед выходом + Показать диалог перед выходом из апликации + Показать + Не показывать \ No newline at end of file diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 06f388848..4670e807d 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -632,7 +632,6 @@ \n%2$s Aktivera förhandsgranskningsminiatyr i sökfältet Förhandsgranskning av sökfältet - MPV YTDL Spela från början Releasedatum (Ny till äldre) Releasedatum (Äldre till nytt) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index b516de738..5ea5a7150 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -703,6 +703,5 @@ Çevrimdışı izlemek için kullanılabilir Seekbar önizleme Arama çubuğunda önizleme küçük resmini etkinleştir - MPV YTDL Henüz altyazı yüklenmedi \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index c5e234f0d..0b52780d2 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -406,7 +406,7 @@ Готово Розширення Додати репозиторій - Назва репозиторію (Необов\'язково) + Назва репозиторію (необов\'язково) URL-адреса репозиторію або короткий код Плагін завантажено Плагін завантажено @@ -657,4 +657,8 @@ Попередній перегляд повзунка Ввімкнути мініатюру попереднього перегляду на повзунку Субтитри ще не завантажено + Підтвердити перед виходом + Показувати + Показувати діалог перед виходом із застосунку + Не показувати \ No newline at end of file diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 5c4cd380e..4a63c3af4 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -400,8 +400,8 @@ Hoàn tất Tiện ích mở rộng Thêm kho lưu trữ - Tên kho lưu trữ - Đường dẫn kho lưu trữ + Tên kho lưu trữ (Tùy chọn) + Đường dẫn kho lưu trữ hoặc Mã ngắn Đã tải plugin Plugin đã xoá Không tải được %s @@ -670,4 +670,8 @@ Bật chế độ xem trước hình thu nhỏ trên seekbar Xem trước Seekbar Chưa tải phụ đề + Xác nhận trước khi thoát + Hiện hộp thoại trước khi thoát ứng dụng + Không hiển thị + Hiển thị \ No newline at end of file From d870978f3a70db4362b7179f0e9f909a865d2d57 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Mon, 14 Oct 2024 18:01:24 +0000 Subject: [PATCH 017/962] Removed crash reporting --- .../lagradost/cloudstream3/AcraApplication.kt | 8 +-- .../ui/setup/SetupFragmentLayout.kt | 4 +- .../main/res/layout/fragment_setup_layout.xml | 72 ++++++++++--------- app/src/main/res/xml/settings_updates.xml | 4 +- 4 files changed, 45 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index d6f978fe5..ba9401021 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -42,11 +42,11 @@ import kotlin.system.exitProcess class CustomReportSender : ReportSender { // Sends all your crashes to google forms override fun send(context: Context, errorContent: CrashReportData) { - println("Sending report") + /*println("Sending report") val url = - "https://docs.google.com/forms/d/e/1FAIpQLSfO4r353BJ79TTY_-t5KWSIJT2xfqcQWY81xjAA1-1N0U2eSg/formResponse" + "https://docs.google.com/forms/d/e/$id/formResponse" val data = mapOf( - "entry.1993829403" to errorContent.toJSON() + "entry.$entry" to errorContent.toJSON() ) thread { // to not run it on main thread @@ -62,7 +62,7 @@ class CustomReportSender : ReportSender { normalSafeApiCall { Toast.makeText(context, R.string.acra_report_toast, Toast.LENGTH_SHORT).show() } - } + }*/ } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt index d8fa46e63..9de6038e3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt @@ -68,7 +68,7 @@ class SetupFragmentLayout : Fragment() { .apply() activity?.recreate() } - acraSwitch.setOnCheckedChangeListener { _, enableCrashReporting -> + /*acraSwitch.setOnCheckedChangeListener { _, enableCrashReporting -> // Use same pref as in settings settingsManager.edit().putBoolean(ACRA.PREF_DISABLE_ACRA, !enableCrashReporting) .apply() @@ -83,7 +83,7 @@ class SetupFragmentLayout : Fragment() { crashReportingText.text = getText( if (enableCrashReporting) R.string.bug_report_settings_off else R.string.bug_report_settings_on - ) + )*/ nextBtt.setOnClickListener { diff --git a/app/src/main/res/layout/fragment_setup_layout.xml b/app/src/main/res/layout/fragment_setup_layout.xml index e14170e3f..a742c27b5 100644 --- a/app/src/main/res/layout/fragment_setup_layout.xml +++ b/app/src/main/res/layout/fragment_setup_layout.xml @@ -6,44 +6,46 @@ android:layout_height="match_parent" android:orientation="vertical"> - - - - - - - - - - - + android:layout_margin="20dp" + android:orientation="horizontal"> - - + + + + + + + + + + + + + --> - + android:title="@string/pref_disable_acra" />--> From f30319ff7cb3cdaa34207171c5f691f198e3430c Mon Sep 17 00:00:00 2001 From: CranberrySoup <142951702+CranberrySoup@users.noreply.github.com> Date: Wed, 16 Oct 2024 19:42:40 +0000 Subject: [PATCH 018/962] Update OpenSubtitlesApi.kt (#1382) --- .../cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt index 37b956146..2e28f1945 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/OpenSubtitlesApi.kt @@ -42,7 +42,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi /** Automatically adds required api headers */ private class OpenSubtitleInterceptor : Interceptor { /** Required user agent! */ - private val userAgent = "Cloudstream3 v0.1" + private val userAgent = "Cloudstream3 v0.2" override fun intercept(chain: Interceptor.Chain): Response { return chain.proceed( chain.request().newBuilder() @@ -118,7 +118,7 @@ class OpenSubtitlesApi(index: Int) : InAppAuthAPIManager(index), AbstractSubApi headers = mapOf( "Content-Type" to "application/json", ), - data = mapOf( + json = mapOf( "username" to username, "password" to password ), From 217cf416ab267cf1054f11dd744a3c679c27f240 Mon Sep 17 00:00:00 2001 From: Elvis Tony Date: Mon, 21 Oct 2024 15:58:16 +0100 Subject: [PATCH 019/962] Update CommonActivity.kt - Added Numpad 1,2 Channel Up,Down for Navigating Episodes (#1387) Added KeyEvent Support for Numpad 1 & Channel Down for Previous Episode and Numpad 2 & Channel Up for Next Episode. Source: [Documentation](https://developer.android.com/reference/android/view/KeyEvent#KEYCODE_CHANNEL_UP) --- .../main/java/com/lagradost/cloudstream3/CommonActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 8498925c8..c541dde5b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -493,11 +493,11 @@ object CommonActivity { PlayerEventType.SeekBack } - KeyEvent.KEYCODE_MEDIA_NEXT, KeyEvent.KEYCODE_BUTTON_R1, KeyEvent.KEYCODE_N -> { + KeyEvent.KEYCODE_MEDIA_NEXT, KeyEvent.KEYCODE_BUTTON_R1, KeyEvent.KEYCODE_N, KeyEvent.KEYCODE_NUMPAD_2, KeyEvent.KEYCODE_CHANNEL_UP -> { PlayerEventType.NextEpisode } - KeyEvent.KEYCODE_MEDIA_PREVIOUS, KeyEvent.KEYCODE_BUTTON_L1, KeyEvent.KEYCODE_B -> { + KeyEvent.KEYCODE_MEDIA_PREVIOUS, KeyEvent.KEYCODE_BUTTON_L1, KeyEvent.KEYCODE_B, KeyEvent.KEYCODE_NUMPAD_1, KeyEvent.KEYCODE_CHANNEL_DOWN -> { PlayerEventType.PrevEpisode } From 62b3c697d6f851693fa11d504068ae8f9da94bb3 Mon Sep 17 00:00:00 2001 From: RowdyRushya Date: Fri, 25 Oct 2024 08:30:04 -0700 Subject: [PATCH 020/962] fix vidstreaming extractor (#1393) --- .../lagradost/cloudstream3/extractors/helper/GogoHelper.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt index 1766af6cf..35c23b2f7 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt @@ -107,8 +107,7 @@ object GogoHelper { "$mainUrl/encrypt-ajax.php?$encryptRequestData", headers = mapOf("X-Requested-With" to "XMLHttpRequest") ) - val dataencrypted = - jsonResponse.text.substringAfter("{\"data\":\"").substringBefore("\"}") + val dataencrypted = jsonResponse.parsedSafe()?.data ?: return@safeApiCall val datadecrypted = cryptoHandler(dataencrypted, foundIv, foundDecryptKey, false) val sources = AppUtils.parseJson(datadecrypted) @@ -155,4 +154,8 @@ object GogoHelper { @JsonProperty("type") val type: String?, @JsonProperty("default") val default: String? = null ) + + data class GogoJsonData( + @JsonProperty("data") val data: String? = null + ) } \ No newline at end of file From 83318b0b0f82cc4736fb2a39160f748d232f4110 Mon Sep 17 00:00:00 2001 From: IndusAryan <125901294+IndusAryan@users.noreply.github.com> Date: Sat, 26 Oct 2024 21:49:39 +0530 Subject: [PATCH 021/962] feat(refactor): replace glide with coil image loading library (fast & small) (#1368) --- app/build.gradle.kts | 6 +- .../lagradost/cloudstream3/AcraApplication.kt | 3 + .../lagradost/cloudstream3/CommonActivity.kt | 2 +- .../lagradost/cloudstream3/MainActivity.kt | 14 +- .../cloudstream3/actions/OpenInAppAction.kt | 5 +- .../cloudstream3/actions/VideoClickAction.kt | 2 +- .../actions/temp/CopyClipboardAction.kt | 2 +- .../cloudstream3/actions/temp/MpvKtPackage.kt | 2 +- .../cloudstream3/actions/temp/MpvPackage.kt | 2 +- .../actions/temp/PlayInBrowserAction.kt | 2 +- .../actions/temp/ViewM3U8Action.kt | 2 +- .../cloudstream3/actions/temp/VlcPackage.kt | 2 +- .../actions/temp/WebVideoCastPackage.kt | 2 +- .../actions/temp/fcast/FcastAction.kt | 2 +- .../cloudstream3/plugins/PluginManager.kt | 4 +- .../services/SubscriptionWorkManager.kt | 2 +- .../cloudstream3/syncproviders/SyncApi.kt | 2 +- .../syncproviders/providers/AniListApi.kt | 2 +- .../syncproviders/providers/LocalList.kt | 2 +- .../syncproviders/providers/MALApi.kt | 2 +- .../syncproviders/providers/SimklApi.kt | 2 +- .../cloudstream3/ui/account/AccountAdapter.kt | 14 +- .../cloudstream3/ui/account/AccountHelper.kt | 7 +- .../ui/download/DownloadAdapter.kt | 4 +- .../cloudstream3/ui/home/HomeFragment.kt | 4 +- .../cloudstream3/ui/home/HomeScrollAdapter.kt | 6 +- .../ui/library/LibraryFragment.kt | 2 +- .../ui/player/FullScreenPlayer.kt | 4 +- .../player/source_priority/ProfilesAdapter.kt | 33 ++- .../source_priority/QualityDataHelper.kt | 4 +- .../source_priority/QualityProfileDialog.kt | 2 +- .../source_priority/SourcePriorityDialog.kt | 2 +- .../cloudstream3/ui/result/ActorAdaptor.kt | 8 +- .../cloudstream3/ui/result/EpisodeAdapter.kt | 8 +- .../ui/result/ResultFragmentPhone.kt | 22 +- .../ui/result/ResultFragmentTv.kt | 34 ++-- .../ui/result/ResultViewModel2.kt | 15 +- .../cloudstream3/ui/result/SelectAdaptor.kt | 2 + .../ui/search/SearchResultBuilder.kt | 7 +- .../ui/settings/AccountAdapter.kt | 25 +-- .../ui/settings/SettingsAccount.kt | 17 +- .../ui/settings/SettingsFragment.kt | 22 +- .../ui/settings/SettingsUpdates.kt | 2 +- .../settings/extensions/ExtensionsFragment.kt | 2 +- .../extensions/ExtensionsViewModel.kt | 4 +- .../ui/settings/extensions/PluginAdapter.kt | 26 +-- .../extensions/PluginDetailsFragment.kt | 18 +- .../settings/extensions/PluginsViewModel.kt | 2 +- .../ui/settings/extensions/RepoAdapter.kt | 2 +- .../lagradost/cloudstream3/utils/AniSkip.kt | 1 - .../cloudstream3/utils/BackupUtils.kt | 1 - .../cloudstream3/utils/DataStoreHelper.kt | 6 +- .../lagradost/cloudstream3/utils/GlideApp.kt | 56 ------ .../cloudstream3/utils/ImageModuleCoil.kt | 162 +++++++++++++++ .../lagradost/cloudstream3/utils/ImageUtil.kt | 14 ++ .../utils/SingleSelectionHelper.kt | 4 +- .../cloudstream3/utils/SnackbarHelper.kt | 1 - .../result/UiText.kt => utils/TextUtil.kt} | 71 +------ .../lagradost/cloudstream3/utils/UIHelper.kt | 189 +----------------- .../utils/VideoDownloadManager.kt | 34 +++- 60 files changed, 391 insertions(+), 510 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/GlideApp.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/ImageModuleCoil.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/ImageUtil.kt rename app/src/main/java/com/lagradost/cloudstream3/{ui/result/UiText.kt => utils/TextUtil.kt} (63%) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 48a28e89b..f8b84772f 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -173,11 +173,7 @@ dependencies { implementation("com.google.android.material:material:1.12.0") implementation("androidx.constraintlayout:constraintlayout:2.1.4") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") - - // Glide Module - ksp("com.github.bumptech.glide:ksp:4.16.0") - implementation("com.github.bumptech.glide:glide:4.16.0") - implementation("com.github.bumptech.glide:okhttp3-integration:4.16.0") + implementation("io.coil-kt:coil:2.7.0") // Coil Image Loading // For KSP -> Official Annotation Processors are Not Yet Supported for KSP ksp("dev.zacsweers.autoservice:auto-service-ksp:1.2.0") diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index ba9401021..ac8877ed3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -22,6 +22,7 @@ import com.lagradost.cloudstream3.utils.DataStore.getKeys import com.lagradost.cloudstream3.utils.DataStore.removeKey import com.lagradost.cloudstream3.utils.DataStore.removeKeys import com.lagradost.cloudstream3.utils.DataStore.setKey +import com.lagradost.cloudstream3.utils.ImageLoader import kotlinx.coroutines.runBlocking import org.acra.ACRA import org.acra.ReportField @@ -101,6 +102,8 @@ class AcraApplication : Application() { override fun onCreate() { super.onCreate() + ImageLoader.initializeCoilImageLoader(this) + ExceptionHandler(filesDir.resolve("last_error")) { val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName) startActivity(Intent.makeRestartActivityTask(intent!!.component)) diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index c541dde5b..aea23d77d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -35,7 +35,7 @@ import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.databinding.ToastBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.player.PlayerEventType -import com.lagradost.cloudstream3.ui.result.UiText +import com.lagradost.cloudstream3.utils.UiText import com.lagradost.cloudstream3.ui.settings.Globals.updateTv import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl import com.lagradost.cloudstream3.utils.Event diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 7ea399dac..60b743711 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -2,7 +2,6 @@ package com.lagradost.cloudstream3 import android.animation.ValueAnimator import android.annotation.SuppressLint -import android.content.ComponentName import android.content.Context import android.content.Intent import android.content.SharedPreferences @@ -10,7 +9,6 @@ import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Rect import android.net.Uri -import android.os.Build import android.os.Bundle import android.util.AttributeSet import android.util.Log @@ -114,10 +112,9 @@ import com.lagradost.cloudstream3.ui.result.LinearListLayout import com.lagradost.cloudstream3.ui.result.ResultViewModel2 import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST import com.lagradost.cloudstream3.ui.result.SyncViewModel -import com.lagradost.cloudstream3.ui.result.setImage -import com.lagradost.cloudstream3.ui.result.setText -import com.lagradost.cloudstream3.ui.result.setTextHtml -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.setText +import com.lagradost.cloudstream3.utils.setTextHtml +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.ui.search.SearchFragment import com.lagradost.cloudstream3.ui.search.SearchResultBuilder import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR @@ -174,6 +171,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API import com.lagradost.cloudstream3.actions.temp.fcast.FcastManager +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.safefile.SafeFile import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock @@ -1372,7 +1370,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa resultviewPreviewMetaRating.setText(d.ratingText) resultviewPreviewDescription.setTextHtml(d.plotText) - resultviewPreviewPoster.setImage( + resultviewPreviewPoster.loadImage( d.posterImage ?: d.posterBackgroundImage ) @@ -1615,7 +1613,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa observe(homeViewModel.currentAccount) { currentAccount -> if (currentAccount != null) { - navProfilePic?.setImage( + navProfilePic?.loadImage( currentAccount.image ) navProfileRoot.isVisible = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt index 8abc5e665..cc64a6d39 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt @@ -8,14 +8,13 @@ import androidx.core.content.FileProvider import androidx.core.net.toUri import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey -import com.lagradost.cloudstream3.MainActivity.Companion.activityResultLauncher import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.ui.result.ResultFragment -import com.lagradost.cloudstream3.ui.result.UiText -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.UiText +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.AppContextUtils.isAppInstalled import com.lagradost.cloudstream3.utils.DataStoreHelper import java.io.File diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt index 0d1e87a74..7e8b1c97b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -25,7 +25,7 @@ import com.lagradost.cloudstream3.actions.temp.fcast.FcastAction import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.UiText +import com.lagradost.cloudstream3.utils.UiText import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.ExtractorLinkType diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt index 481917e5e..7e89d7c8c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/CopyClipboardAction.kt @@ -4,7 +4,7 @@ import android.content.Context import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper class CopyClipboardAction: VideoClickAction() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt index 6514de174..102f0ac8b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt @@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.actions.OpenInAppAction import com.lagradost.cloudstream3.actions.updateDurationAndPosition import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.ExtractorLinkType diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt index 382f7d89a..68e619c92 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -11,7 +11,7 @@ import com.lagradost.cloudstream3.actions.makeTempM3U8Intent import com.lagradost.cloudstream3.actions.updateDurationAndPosition import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.ExtractorLinkType diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt index 1558bfd6a..7c1b68c05 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.ExtractorLinkType class PlayInBrowserAction: VideoClickAction() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt index b69562dd1..791566862 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/ViewM3U8Action.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.actions.makeTempM3U8Intent import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt class ViewM3U8Action: VideoClickAction() { override val name = txt(R.string.episode_action_play_in_format, "m3u8 player") diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt index 18143c001..df4fcca81 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt @@ -12,7 +12,7 @@ import com.lagradost.cloudstream3.actions.makeTempM3U8Intent import com.lagradost.cloudstream3.actions.updateDurationAndPosition import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.ui.subtitles.SUBTITLE_AUTO_SELECT_KEY import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt index 4f44b3969..9f7eee7b8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt @@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.actions.OpenInAppAction import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.ExtractorLinkType // https://www.webvideocaster.com/integrations diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt index 458d1dc99..e3916df01 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 8535592d4..dc31b6ca1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -33,8 +33,8 @@ import com.lagradost.cloudstream3.plugins.RepositoryManager.ONLINE_PLUGINS_FOLDE import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES import com.lagradost.cloudstream3.plugins.RepositoryManager.downloadPluginToFile import com.lagradost.cloudstream3.plugins.RepositoryManager.getRepoPlugins -import com.lagradost.cloudstream3.ui.result.UiText -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.UiText +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY import com.lagradost.cloudstream3.ui.settings.extensions.RepositoryData import com.lagradost.cloudstream3.utils.AppContextUtils.getApiProviderLangSettings diff --git a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt b/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt index 00c74dfff..aef92a6ab 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt @@ -14,7 +14,7 @@ import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.plugins.PluginManager -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.AppContextUtils.createNotificationChannel import com.lagradost.cloudstream3.utils.AppContextUtils.getApiDubstatusSettings import com.lagradost.cloudstream3.utils.Coroutines.ioWork diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncApi.kt index dcb8bbead..9d43685c8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncApi.kt @@ -3,7 +3,7 @@ package com.lagradost.cloudstream3.syncproviders import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.ui.SyncWatchType import com.lagradost.cloudstream3.ui.library.ListSorting -import com.lagradost.cloudstream3.ui.result.UiText +import com.lagradost.cloudstream3.utils.UiText import me.xdrop.fuzzywuzzy.FuzzySearch import java.util.Date diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 6112c7dbe..68a4a9a54 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -15,7 +15,7 @@ import com.lagradost.cloudstream3.syncproviders.SyncAPI 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.txt import com.lagradost.cloudstream3.utils.AppContextUtils.splitQuery import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt index 0d9a4d138..2b51f7efd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/LocalList.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.library.ListSorting -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.Coroutines.ioWork diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index 08c186531..4836eca13 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -18,7 +18,7 @@ import com.lagradost.cloudstream3.syncproviders.SyncAPI 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.txt import com.lagradost.cloudstream3.utils.AppContextUtils.splitQuery import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt index 50517f9d1..92a181dd1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt @@ -29,7 +29,7 @@ import com.lagradost.cloudstream3.syncproviders.SyncAPI 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.txt import com.lagradost.cloudstream3.utils.AppUtils.toJson import com.lagradost.cloudstream3.utils.DataStoreHelper.toYear import okhttp3.Interceptor diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt index de0b5c058..41ecbe7ff 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt @@ -6,17 +6,17 @@ import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding +import coil.transform.RoundedCornersTransformation import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.AccountListItemAddBinding import com.lagradost.cloudstream3.databinding.AccountListItemBinding import com.lagradost.cloudstream3.databinding.AccountListItemEditBinding import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountEditDialog -import com.lagradost.cloudstream3.ui.result.setImage import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage class AccountAdapter( private val accounts: List, @@ -45,7 +45,7 @@ class AccountAdapter( val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex accountName.text = account.name - accountImage.setImage(account.image) + accountImage.loadImage(account.image) lockIcon.isVisible = account.lockPin != null outline.isVisible = !isTv && isLastUsedAccount @@ -87,11 +87,9 @@ class AccountAdapter( val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex accountName.text = account.name - accountImage.setImage( - account.image, - fadeIn = false, - radius = 10 - ) + accountImage.loadImage(account.image) { + RoundedCornersTransformation(10f) + } lockIcon.isVisible = account.lockPin != null outline.isVisible = !isTv && isLastUsedAccount diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt index d2aca862b..6abf0a348 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt @@ -16,6 +16,7 @@ import androidx.core.widget.doOnTextChanged import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import coil.load import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity import com.lagradost.cloudstream3.MainActivity @@ -25,11 +26,11 @@ import com.lagradost.cloudstream3.databinding.AccountSelectLinearBinding import com.lagradost.cloudstream3.databinding.LockPinDialogBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe -import com.lagradost.cloudstream3.ui.result.setImage import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.getDefaultAccount +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod @@ -90,12 +91,12 @@ object AccountHelper { } // Handle the profile picture and its interactions - binding.accountImage.setImage(account.image) + binding.accountImage.loadImage(account.image) binding.accountImage.setOnClickListener { // Roll the image forwards once currentEditAccount = currentEditAccount.copy(defaultImageIndex = (currentEditAccount.defaultImageIndex + 1) % DataStoreHelper.profileImages.size) - binding.accountImage.setImage(currentEditAccount.image) + binding.accountImage.loadImage(currentEditAccount.image) } // Handle applying changes diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt index d211cb87c..a0e5cabc4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt @@ -18,7 +18,7 @@ import com.lagradost.cloudstream3.ui.download.button.DownloadStatusTell import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos -import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.VideoDownloadHelper const val DOWNLOAD_ACTION_PLAY_FILE = 0 @@ -108,7 +108,7 @@ class DownloadAdapter( } downloadHeaderPoster.apply { - setImage(data.poster) + loadImage(data.poster) if (isMultiDeleteState) { setOnClickListener { toggleIsChecked(deleteCheckbox, data.id) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 2189c9e44..e8daa4eb4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -22,7 +22,6 @@ import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.chip.Chip -import com.lagradost.api.Log import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.CommonActivity.showToast @@ -37,7 +36,7 @@ import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountSelectLinear -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.ui.search.* import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR @@ -58,7 +57,6 @@ import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes -import com.lagradost.cloudstream3.utils.UIHelper.setImage import java.util.* diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index 29186e83a..4c4dd2d84 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -13,7 +13,7 @@ import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout -import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage class HomeScrollAdapter( fragment: Fragment @@ -47,7 +47,7 @@ class HomeScrollAdapter( when (binding) { is HomeScrollViewBinding -> { - binding.homeScrollPreview.setImage(posterUrl) + binding.homeScrollPreview.loadImage(posterUrl) binding.homeScrollPreviewTags.apply { text = item.tags?.joinToString(" • ") ?: "" isGone = item.tags.isNullOrEmpty() @@ -57,7 +57,7 @@ class HomeScrollAdapter( } is HomeScrollViewTvBinding -> { - binding.homeScrollPreview.setImage(posterUrl) + binding.homeScrollPreview.loadImage(posterUrl) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index 5b240693b..8e3abf058 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -46,7 +46,7 @@ import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.ui.AutofitRecyclerView import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 70f7e3686..f974436cb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -53,8 +53,8 @@ import com.lagradost.cloudstream3.databinding.SubtitleOffsetBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive 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.utils.setText +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt index 45f6aa660..886c8837b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/ProfilesAdapter.kt @@ -8,13 +8,14 @@ import android.view.ViewGroup import android.widget.ImageView import android.widget.TextView import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.toBitmap import androidx.core.view.isVisible +import androidx.palette.graphics.Palette import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.PlayerQualityProfileItemBinding -import com.lagradost.cloudstream3.ui.result.UiImage import com.lagradost.cloudstream3.utils.AppContextUtils -import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage class ProfilesAdapter( override val items: MutableList, @@ -81,15 +82,25 @@ class ProfilesAdapter( outline.isVisible = currentItem?.second?.id == item.id - profileBg.setImage(UiImage.Drawable(art[index % art.size]), null, false) { palette -> - val color = palette.getDarkVibrantColor( - ContextCompat.getColor( - itemView.context, - R.color.dubColorBg - ) - ) - wifiText.backgroundTintList = ColorStateList.valueOf(color) - dataText.backgroundTintList = ColorStateList.valueOf(color) + profileBg.loadImage(art[index % art.size]) { + target { drawable -> + // Convert drawable to bitmap to extract palette colors + val bitmap = drawable.toBitmap() + + Palette.from(bitmap).generate { palette -> + palette?.let { + val color = it.getDarkVibrantColor( + ContextCompat.getColor( + itemView.context, + R.color.dubColorBg + ) + ) + + wifiText.backgroundTintList = ColorStateList.valueOf(color) + dataText.backgroundTintList = ColorStateList.valueOf(color) + } + } + } } val textStyle = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt index 3267efd73..0922bdb5a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt @@ -6,8 +6,8 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.debugAssert -import com.lagradost.cloudstream3.ui.result.UiText -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.UiText +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount import com.lagradost.cloudstream3.utils.Qualities diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt index 0537092c1..19e98138c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityProfileDialog.kt @@ -7,7 +7,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.PlayerQualityProfileDialogBinding import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfileName import com.lagradost.cloudstream3.ui.player.source_priority.QualityDataHelper.getProfiles -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt index bc6282af2..4c74ec80f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/SourcePriorityDialog.kt @@ -7,7 +7,7 @@ import androidx.annotation.StyleRes import androidx.appcompat.app.AlertDialog import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.PlayerSelectSourcePriorityBinding -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.Qualities import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt index 0ca326ddf..8ffe2e04b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ActorAdaptor.kt @@ -10,7 +10,7 @@ import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.ActorRole import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.CastItemBinding -import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage class ActorAdaptor( private var nextFocusUpId: Int? = null, @@ -102,7 +102,7 @@ class ActorAdaptor( } binding.apply { - actorImage.setImage(mainImg) + actorImage.loadImage(mainImg) actorName.text = actor.actor.name actor.role?.let { @@ -136,7 +136,9 @@ class ActorAdaptor( voiceActorName.isVisible = false } else { voiceActorName.text = actor.voiceActor?.name - voiceActorImageHolder.isVisible = voiceActorImage.setImage(vaImage) + if (!vaImage.isNullOrEmpty()) + voiceActorImageHolder.isVisible = true + voiceActorImage.loadImage(vaImage) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt index 2dd8e2ab4..51acd15a4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -23,9 +23,11 @@ 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.utils.AppContextUtils.html -import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.VideoDownloadHelper +import com.lagradost.cloudstream3.utils.setText +import com.lagradost.cloudstream3.utils.txt import java.text.DateFormat import java.text.SimpleDateFormat import java.util.Date @@ -225,7 +227,7 @@ class EpisodeAdapter( episodeProgress.isVisible = displayPos > 0L } - episodePoster.isVisible = episodePoster.setImage(card.poster) == true + episodePoster.loadImage(card.poster) if (card.rating != null) { episodeRating.text = episodeRating.context?.getString(R.string.rated_format) @@ -260,7 +262,7 @@ class EpisodeAdapter( episodePlayIcon.isVisible = false episodeUpcomingIcon.isVisible = !episodePoster.isVisible episodeDate.setText( - txt( + com.lagradost.cloudstream3.utils.txt( R.string.episode_upcoming_format, secondsToReadable( card.airDate.minus(unixTimeMS).div(1000).toInt(), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index 97bc49eae..119bd57f0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -64,6 +64,7 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.openBrowser import com.lagradost.cloudstream3.utils.AppContextUtils.updateHasTrailers import com.lagradost.cloudstream3.utils.BatteryOptimizationChecker.openBatteryOptimizationSettings import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog @@ -75,8 +76,9 @@ import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage import com.lagradost.cloudstream3.utils.UIHelper.populateChips import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes -import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.VideoDownloadHelper +import com.lagradost.cloudstream3.utils.setText +import com.lagradost.cloudstream3.utils.setTextHtml open class ResultFragmentPhone : FullScreenPlayer() { private val gestureRegionsListener = @@ -449,8 +451,8 @@ open class ResultFragmentPhone : FullScreenPlayer() { } val name = (viewModel.page.value as? Resource.Success)?.value?.title - ?: txt(R.string.no_data).asStringNull(context) ?: "" - showToast(txt(message, name), Toast.LENGTH_SHORT) + ?: com.lagradost.cloudstream3.utils.txt(R.string.no_data).asStringNull(context) ?: "" + showToast(com.lagradost.cloudstream3.utils.txt(message, name), Toast.LENGTH_SHORT) } context?.let { openBatteryOptimizationSettings(it) } } @@ -465,8 +467,8 @@ open class ResultFragmentPhone : FullScreenPlayer() { } val name = (viewModel.page.value as? Resource.Success)?.value?.title - ?: txt(R.string.no_data).asStringNull(context) ?: "" - showToast(txt(message, name), Toast.LENGTH_SHORT) + ?: com.lagradost.cloudstream3.utils.txt(R.string.no_data).asStringNull(context) ?: "" + showToast(com.lagradost.cloudstream3.utils.txt(message, name), Toast.LENGTH_SHORT) } } mediaRouteButton.apply { @@ -703,8 +705,12 @@ open class ResultFragmentPhone : FullScreenPlayer() { resultCastText.setText(d.actorsText) resultNextAiring.setText(d.nextAiringEpisode) resultNextAiringTime.setText(d.nextAiringDate) - resultPoster.setImage(d.posterImage) - resultPosterBackground.setImage(d.posterBackgroundImage) + resultPoster.loadImage(d.posterImage, headers = d.posterHeaders) { + error(R.drawable.default_cover) + } + resultPosterBackground.loadImage(d.posterBackgroundImage, headers = d.posterHeaders) { + error(R.drawable.default_cover) + } var isExpanded = false resultDescription.apply { @@ -776,7 +782,7 @@ open class ResultFragmentPhone : FullScreenPlayer() { resultReloadConnectionOpenInBrowser.isVisible = data is Resource.Failure resultTitle.setOnLongClickListener { - clipboardHelper(txt(R.string.title), resultTitle.text) + clipboardHelper(com.lagradost.cloudstream3.utils.txt(R.string.title), resultTitle.text) true } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index 1878f0b8f..b92cc250c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -46,6 +46,7 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.html import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl import com.lagradost.cloudstream3.utils.AppContextUtils.loadCache import com.lagradost.cloudstream3.utils.AppContextUtils.updateHasTrailers +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant import com.lagradost.cloudstream3.utils.UIHelper @@ -53,7 +54,8 @@ import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.navigate -import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.setText +import com.lagradost.cloudstream3.utils.setTextHtml class ResultFragmentTv : Fragment() { private lateinit var viewModel: ResultViewModel2 @@ -150,7 +152,9 @@ class ResultFragmentTv : Fragment() { rec?.map { it.apiName }?.distinct()?.let { apiNames -> // very dirty selection resultRecommendationsFilterSelection.isVisible = apiNames.size > 1 - resultRecommendationsFilterSelection.update(apiNames.map { txt(it) to it }) + resultRecommendationsFilterSelection.update(apiNames.map { com.lagradost.cloudstream3.utils.txt( + it + ) to it }) resultRecommendationsFilterSelection.select(apiNames.indexOf(matchAgainst)) } ?: run { resultRecommendationsFilterSelection.isVisible = false @@ -579,8 +583,12 @@ class ResultFragmentTv : Fragment() { } val name = (viewModel.page.value as? Resource.Success)?.value?.title - ?: txt(R.string.no_data).asStringNull(context) ?: "" - CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) + ?: com.lagradost.cloudstream3.utils.txt(R.string.no_data).asStringNull(context) ?: "" + CommonActivity.showToast( + com.lagradost.cloudstream3.utils.txt( + message, + name + ), Toast.LENGTH_SHORT) } } } @@ -622,8 +630,12 @@ class ResultFragmentTv : Fragment() { } val name = (viewModel.page.value as? Resource.Success)?.value?.title - ?: txt(R.string.no_data).asStringNull(context) ?: "" - CommonActivity.showToast(txt(message, name), Toast.LENGTH_SHORT) + ?: com.lagradost.cloudstream3.utils.txt(R.string.no_data).asStringNull(context) ?: "" + CommonActivity.showToast( + com.lagradost.cloudstream3.utils.txt( + message, + name + ), Toast.LENGTH_SHORT) } } @@ -839,7 +851,7 @@ class ResultFragmentTv : Fragment() { resultCastText.setText(d.actorsText) resultNextAiring.setText(d.nextAiringEpisode) resultNextAiringTime.setText(d.nextAiringDate) - resultPoster.setImage(d.posterImage) + resultPoster.loadImage(d.posterImage) var isExpanded = false resultDescription.apply { @@ -874,11 +886,9 @@ class ResultFragmentTv : Fragment() { //Change poster crop area to 20% from Top backgroundPoster.cropYCenterOffsetPct = 0.20F - backgroundPoster.setImage( - d.posterBackgroundImage ?: UiImage.Drawable(error), - radius = 0, - errorImageDrawable = error - ) + backgroundPoster.loadImage( + d.posterBackgroundImage + ) { error(error) } comingSoon = d.comingSoon resultTvComingSoon.isVisible = d.comingSoon diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index bf3b82a91..4cd3d4da5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -81,7 +81,6 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.setResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.setSubscribedData import com.lagradost.cloudstream3.utils.DataStoreHelper.setVideoWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.updateSubscribedData -import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper import com.lagradost.cloudstream3.utils.UIHelper.navigate import kotlinx.coroutines.* import java.util.concurrent.TimeUnit @@ -114,8 +113,8 @@ data class ResultData( val title: String, var syncData: Map, - val posterImage: UiImage?, - val posterBackgroundImage: UiImage?, + val posterImage: String?, + val posterBackgroundImage: String?, val plotText: UiText, val apiName: UiText, val ratingText: UiText?, @@ -131,6 +130,7 @@ data class ResultData( val nextAiringDate: UiText?, val nextAiringEpisode: UiText?, val plotHeaderText: UiText, + val posterHeaders: Map? = null, ) data class CheckDuplicateData( @@ -215,12 +215,9 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { ), nextAiringDate = nextAiringDate, nextAiringEpisode = nextAiringEpisode, - posterImage = img( - posterUrl, posterHeaders - ) ?: img(R.drawable.default_cover), - posterBackgroundImage = img( - backgroundPosterUrl ?: posterUrl, posterHeaders - ) ?: img(R.drawable.default_cover), + posterImage = posterUrl ?: backgroundPosterUrl, + posterHeaders = posterHeaders, + posterBackgroundImage = backgroundPosterUrl ?: posterUrl, titleText = txt(name), url = url, tags = tags ?: emptyList(), diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt index 8752e275c..ad5d89d18 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/SelectAdaptor.kt @@ -8,6 +8,8 @@ import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.databinding.ResultSelectionBinding import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout +import com.lagradost.cloudstream3.utils.UiText +import com.lagradost.cloudstream3.utils.setText typealias SelectData = Pair diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt index 92575e58f..37e658bfc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt @@ -23,8 +23,8 @@ import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.SubtitleHelper -import com.lagradost.cloudstream3.utils.UIHelper.setImage object SearchResultBuilder { private val showCache: MutableMap = mutableMapOf() @@ -120,10 +120,7 @@ object SearchResultBuilder { cardText?.text = card.name cardText?.isVisible = showTitle cardView.isVisible = true - - if (!cardView.setImage(card.posterUrl, card.posterHeaders, colorCallback = colorCallback)) { - cardView.setImageResource(R.drawable.default_cover) - } + cardView.loadImage(card.posterUrl) { error(R.drawable.default_cover) } fun click(view: View?) { clickCallback.invoke( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/AccountAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/AccountAdapter.kt index d7bd69f11..ba53f96f1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/AccountAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/AccountAdapter.kt @@ -9,7 +9,7 @@ import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.AccountSingleBinding import com.lagradost.cloudstream3.syncproviders.AuthAPI -import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage class AccountClickCallback(val action: Int, val view: View, val card: AuthAPI.LoginInfo) @@ -43,22 +43,23 @@ class AccountAdapter( return cardList[position].accountIndex.toLong() } - 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)!! + class CardViewHolder(val binding: AccountSingleBinding?, private val clickCallback: (AccountClickCallback) -> Unit) : + RecyclerView.ViewHolder(binding?.root!!) { @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( - binding.accountName.context.getString(R.string.account), - card.accountIndex - ) - binding.accountProfilePicture.isVisible = binding.accountProfilePicture.setImage(card.profilePicture) + binding?.apply { + accountName.text = card.name ?: "%s %d".format( + binding.accountName.context.getString(R.string.account), + card.accountIndex + ) + accountProfilePicture.isVisible = true + accountProfilePicture.loadImage(card.profilePicture) - itemView.setOnClickListener { - clickCallback.invoke(AccountClickCallback(0, itemView, card)) + itemView.setOnClickListener { + clickCallback.invoke(AccountClickCallback(0, itemView, card)) + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt index 15f8735fe..daeab0500 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt @@ -34,10 +34,6 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.subDlAp import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.InAppAuthAPI import com.lagradost.cloudstream3.syncproviders.OAuth2API -import com.lagradost.cloudstream3.ui.result.img -import com.lagradost.cloudstream3.ui.result.setImage -import com.lagradost.cloudstream3.ui.result.setText -import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.PHONE import com.lagradost.cloudstream3.ui.settings.Globals.TV @@ -57,11 +53,13 @@ import com.lagradost.cloudstream3.utils.BiometricAuthenticator.isAuthEnabled import com.lagradost.cloudstream3.utils.BiometricAuthenticator.promptInfo import com.lagradost.cloudstream3.utils.BiometricAuthenticator.startBiometricAuthentication import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogText import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard -import com.lagradost.cloudstream3.utils.UIHelper.setImage +import com.lagradost.cloudstream3.utils.setText +import com.lagradost.cloudstream3.utils.txt import qrcode.QRCode class SettingsAccount : PreferenceFragmentCompat(), BiometricCallback { @@ -80,8 +78,9 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricCallback { .setView(binding.root) val dialog = builder.show() - binding.accountMainProfilePictureHolder.isVisible = - binding.accountMainProfilePicture.setImage(info.profilePicture) + binding.accountMainProfilePictureHolder.isVisible = !info.profilePicture.isNullOrEmpty() + binding.accountMainProfilePicture.loadImage(info.profilePicture) + binding.accountLogout.setOnClickListener { api.logOut() @@ -198,9 +197,7 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricCallback { pinCodeData.verificationUrl ) ) - deviceAuthQrcode.setImage( - img(qrCodeImage) - ) + deviceAuthQrcode.loadImage(qrCodeImage) } val expirationMillis = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 339fe752d..ef35971ce 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -20,17 +20,17 @@ import com.lagradost.cloudstream3.databinding.MainSettingsBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.accountManagers -import com.lagradost.cloudstream3.ui.home.HomeFragment -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.errorProfilePic +import com.lagradost.cloudstream3.utils.txt 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.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper import com.lagradost.cloudstream3.utils.UIHelper.navigate -import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.toPx import java.io.File import java.text.DateFormat @@ -184,14 +184,14 @@ class SettingsFragment : Fragment() { val login = syncApi.loginInfo() val pic = login?.profilePicture ?: continue - if (binding?.settingsProfilePic?.setImage( - pic, - errorImageDrawable = HomeFragment.errorProfilePic - ) == true - ) { - binding?.settingsProfileText?.text = login.name - return true // sync profile exists + binding?.settingsProfilePic?.let { imageView -> + imageView.loadImage(pic) { + crossfade(true) // Optional: for a fade-in animation + error(errorProfilePic) // Fallback to random error drawable + } } + return true // sync profile exists + } return false // not syncing } @@ -209,7 +209,7 @@ class SettingsFragment : Fragment() { null } - binding?.settingsProfilePic?.setImage(currentAccount?.image) + binding?.settingsProfilePic?.loadImage(currentAccount?.image) binding?.settingsProfileText?.text = currentAccount?.name } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 4c45480cd..46d32a2d2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -18,7 +18,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.services.BackupWorkManager -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index 1b4876293..bd1e219d0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -28,7 +28,7 @@ import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.plugins.RepositoryManager import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.setLinearListLayout -import com.lagradost.cloudstream3.ui.result.setText +import com.lagradost.cloudstream3.utils.setText import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt index 866d167c1..ebe9fc888 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt @@ -12,8 +12,8 @@ import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.PluginManager.getPluginsOnline import com.lagradost.cloudstream3.plugins.RepositoryManager import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES -import com.lagradost.cloudstream3.ui.result.UiText -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.UiText +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.Coroutines.ioSafe data class RepositoryData( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt index d159539d6..ef74851ea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt @@ -5,7 +5,6 @@ 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 @@ -18,23 +17,23 @@ import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.databinding.RepositoryItemBinding import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.VotingApi.getVotes -import com.lagradost.cloudstream3.ui.result.setText -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.setText +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.AppContextUtils.html import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage 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 -import org.junit.Test -import org.junit.Assert data class PluginViewData( val plugin: Plugin, @@ -89,9 +88,7 @@ class PluginAdapter( // Clear glide image because setImageResource doesn't override override fun onViewRecycled(holder: RecyclerView.ViewHolder) { if (holder is PluginViewHolder) { - holder.binding.entryIcon.let { pluginIcon -> - com.bumptech.glide.Glide.with(pluginIcon).clear(pluginIcon) - } + holder.binding.entryIcon.loadImage(R.drawable.ic_github_logo) } super.onViewRecycled(holder) } @@ -200,20 +197,15 @@ class PluginAdapter( binding.actionSettings.isVisible = false } - if (!binding.entryIcon.setImage(//itemView.entry_icon?.height ?: + binding.entryIcon.loadImage( metadata.iconUrl?.replace( "%size%", "$iconSize" )?.replace( "%exact_size%", "$iconSizeExact" - ), - null, - errorImageDrawable = R.drawable.ic_baseline_extension_24 - ) - ) { - binding.entryIcon.setImageResource(R.drawable.ic_baseline_extension_24) - } + ) + ) { error(R.drawable.ic_baseline_extension_24) } binding.extVersion.isVisible = true binding.extVersion.text = "v${metadata.version}" diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt index 7d733be09..9f0ca7ae8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt @@ -19,10 +19,10 @@ import com.lagradost.cloudstream3.plugins.VotingApi.hasVoted import com.lagradost.cloudstream3.plugins.VotingApi.vote import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.SubtitleHelper.fromTwoLettersToLanguage import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute -import com.lagradost.cloudstream3.utils.UIHelper.setImage import com.lagradost.cloudstream3.utils.UIHelper.toPx @@ -61,19 +61,9 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen super.onViewCreated(view, savedInstanceState) val metadata = data.plugin.second binding?.apply { - if (!pluginIcon.setImage(//plugin_icon?.height ?: - metadata.iconUrl?.replace( - "%size%", - "$iconSize" - )?.replace( - "%exact_size%", - "$iconSizeExact" - ), - null, - errorImageDrawable = R.drawable.ic_baseline_extension_24 - ) - ) { - pluginIcon.setImageResource(R.drawable.ic_baseline_extension_24) + pluginIcon.loadImage(metadata.iconUrl?.replace("%size%", "$iconSize") + ?.replace("%exact_size%", "$iconSizeExact")) { + error(R.drawable.ic_baseline_extension_24) } pluginName.text = metadata.name.removeSuffix("Provider") pluginVersion.text = metadata.version.toString() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt index fd5422b2d..a6f914898 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt @@ -19,7 +19,7 @@ import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.PluginManager.getPluginPath import com.lagradost.cloudstream3.plugins.RepositoryManager import com.lagradost.cloudstream3.plugins.SitePlugin -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt index faf6d38bf..f647e5533 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt @@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.RepositoryItemBinding import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES -import com.lagradost.cloudstream3.ui.result.txt +import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AniSkip.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AniSkip.kt index f0c948a4a..820a01f9f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AniSkip.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AniSkip.kt @@ -7,7 +7,6 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.LoadResponse.Companion.getMalId import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.result.ResultEpisode -import com.lagradost.cloudstream3.ui.result.txt import java.lang.Long.min object EpisodeSkip { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt index a44af8773..7584e9124 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt @@ -29,7 +29,6 @@ import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_U import com.lagradost.cloudstream3.syncproviders.providers.MALApi.Companion.MAL_USER_KEY import com.lagradost.cloudstream3.syncproviders.providers.OpenSubtitlesApi.Companion.OPEN_SUBTITLES_USER_KEY import com.lagradost.cloudstream3.syncproviders.providers.SubDlApi.Companion.SUBDL_SUBTITLES_USER_KEY -import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getDefaultSharedPrefs diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 7ef7bc576..2e16fc8c7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -22,7 +22,6 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.library.ListSorting -import com.lagradost.cloudstream3.ui.result.UiImage import com.lagradost.cloudstream3.ui.result.VideoWatchState import com.lagradost.cloudstream3.utils.AppContextUtils.filterProviderByPreferredMedia import java.util.Calendar @@ -133,10 +132,7 @@ object DataStoreHelper { @JsonProperty("lockPin") val lockPin: String? = null, ) { - val image: UiImage - get() = customImage?.let { UiImage.Image(it) } ?: UiImage.Drawable( - profileImages.getOrNull(defaultImageIndex) ?: profileImages.first() - ) + val image get() = customImage?.let { UiImage.Image(it) } ?: profileImages.getOrNull(defaultImageIndex)?.let { UiImage.Drawable(it) } ?: UiImage.Drawable(profileImages.first()) } const val TAG = "data_store_helper" diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/GlideApp.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/GlideApp.kt deleted file mode 100644 index 38d3fe9ef..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/GlideApp.kt +++ /dev/null @@ -1,56 +0,0 @@ -package com.lagradost.cloudstream3.utils - -import android.annotation.SuppressLint -import android.content.Context -import com.bumptech.glide.Glide -import com.bumptech.glide.GlideBuilder -import com.bumptech.glide.Registry -import com.bumptech.glide.annotation.GlideModule -import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory -import com.bumptech.glide.load.model.GlideUrl -import com.bumptech.glide.module.AppGlideModule -import com.bumptech.glide.request.RequestOptions -import com.bumptech.glide.signature.ObjectKey -import com.lagradost.cloudstream3.USER_AGENT -import com.lagradost.cloudstream3.network.DdosGuardKiller -import com.lagradost.cloudstream3.network.initClient -import com.lagradost.nicehttp.Requests -import java.io.InputStream - -@GlideModule -class GlideModule : AppGlideModule() { - @SuppressLint("CheckResult") - override fun applyOptions(context: Context, builder: GlideBuilder) { - super.applyOptions(context, builder) - builder.apply { - RequestOptions() - .diskCacheStrategy(DiskCacheStrategy.ALL) - .signature(ObjectKey(System.currentTimeMillis().toShort())) - }.setDiskCache { - // Possible to make this a setting in the future. - val memoryCacheSizeBytes: Long = 1024 * 1024 * 100 // 100mb - InternalCacheDiskCacheFactory(context, memoryCacheSizeBytes).build() - } - } - - // Needed for DOH - // https://stackoverflow.com/a/61634041 - override fun registerComponents(context: Context, glide: Glide, registry: Registry) { - val client = - Requests().apply { - defaultHeaders = mapOf("user-agent" to USER_AGENT) - }.initClient(context) - .newBuilder() - .addInterceptor(DdosGuardKiller(false)) - .build() - - registry.replace( - GlideUrl::class.java, - InputStream::class.java, - OkHttpUrlLoader.Factory(client) - ) - super.registerComponents(context, glide, registry) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ImageModuleCoil.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ImageModuleCoil.kt new file mode 100644 index 000000000..49ece60b5 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ImageModuleCoil.kt @@ -0,0 +1,162 @@ +package com.lagradost.cloudstream3.utils + +import android.content.Context +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.util.Log +import android.widget.ImageView +import androidx.annotation.DrawableRes +import coil.EventListener +import coil.ImageLoader +import coil.load +import coil.memory.MemoryCache +import coil.request.CachePolicy +import coil.request.ErrorResult +import coil.request.ImageRequest +import coil.request.SuccessResult +import com.lagradost.cloudstream3.USER_AGENT +import com.lagradost.cloudstream3.network.DdosGuardKiller +import okhttp3.HttpUrl +import okhttp3.OkHttpClient +import java.io.File +import java.nio.ByteBuffer + +object ImageLoader { + + private var instance: ImageLoader? = null + + fun initializeCoilImageLoader(context: Context) { + if (instance == null) { + synchronized(this) { + instance = buildImageLoader(context.applicationContext) + } + } + } + + private fun buildImageLoader(context: Context): ImageLoader { + val okHttpClient = OkHttpClient.Builder() + .addInterceptor(DdosGuardKiller(alwaysBypass = false)) + .build() + + return ImageLoader.Builder(context) + .crossfade(true) + .respectCacheHeaders(false) + /** !Only use default placeholders and errors, if not using this instance for local + * image buttons because when animating this will appear or in more cases **/ + //.placeholder(R.drawable.logo) + //.error(R.drawable.logo) + .allowHardware(true) + .memoryCache { + MemoryCache.Builder(context) + .maxSizePercent(0.15) // Use 15% of the app's available memory for image caching + .build() + } + .diskCache { + coil.disk.DiskCache.Builder() + .maxSizeBytes(128 * 1024 * 1024) // 128 MB + .directory(context.cacheDir.resolve("cs3_image_cache")) + .maxSizePercent(0.02) // Use 2% of the device's storage space for disk caching + .build() + } + .diskCachePolicy(CachePolicy.ENABLED) + /** Pass interceptors with care, unnecessary passing tokens to servers + or image hosting services causes unauthorized exceptions **/ + .okHttpClient(okHttpClient) + .eventListener(object : EventListener { + override fun onStart(request: ImageRequest) { + super.onStart(request) + Log.i("CoilImageLoader", "Loading Image ${request.data}") + } + + override fun onSuccess(request: ImageRequest, result: SuccessResult) { + super.onSuccess(request, result) + Log.d("CoilImageLoader", "Image Loading successful") + } + + override fun onError(request: ImageRequest, result: ErrorResult) { + super.onError(request, result) + Log.e("CoilImageLoadError", "Error loading image: ${result.throwable}") + } + }) + .build() + } + + fun ImageView.loadImage( + imageData: UiImage?, + builder: ImageRequest.Builder.() -> Unit = {} + ) = when(imageData) { + is UiImage.Image -> loadImageInternal(imageData = imageData.url, headers = imageData.headers, builder = builder) + is UiImage.Bitmap -> loadImageInternal(imageData = imageData.bitmap, builder = builder) + is UiImage.Drawable -> loadImageInternal(imageData = imageData.resId, builder = builder) + null -> loadImageInternal(null, builder = builder) + } + + fun ImageView.loadImage( + imageData: String?, + headers: Map? = null, + builder: ImageRequest.Builder.() -> Unit = {} + ) = loadImageInternal(imageData = imageData, headers = headers, builder = builder) + + fun ImageView.loadImage( + imageData: Uri?, + headers: Map? = null, + builder: ImageRequest.Builder.() -> Unit = {} + ) = loadImageInternal(imageData = imageData,headers = headers, builder = builder) + + fun ImageView.loadImage( + imageData: HttpUrl?, + headers: Map? = null, + builder: ImageRequest.Builder.() -> Unit = {} + ) = loadImageInternal(imageData = imageData, headers = headers, builder = builder) + + fun ImageView.loadImage( + imageData: File?, + builder: ImageRequest.Builder.() -> Unit = {} + ) = loadImageInternal(imageData = imageData, builder = builder) + + fun ImageView.loadImage( + @DrawableRes imageData: Int?, + builder: ImageRequest.Builder.() -> Unit = {} + ) = loadImageInternal(imageData = imageData, builder = builder) + + fun ImageView.loadImage( + imageData: Drawable?, + builder: ImageRequest.Builder.() -> Unit = {} + ) = loadImageInternal(imageData = imageData, builder = builder) + + fun ImageView.loadImage( + imageData: Bitmap?, + builder: ImageRequest.Builder.() -> Unit = {} + ) = loadImageInternal(imageData = imageData, builder = builder) + + fun ImageView.loadImage( + imageData: ByteArray?, + builder: ImageRequest.Builder.() -> Unit = {} + ) = loadImageInternal(imageData = imageData, builder = builder) + + fun ImageView.loadImage( + imageData: ByteBuffer?, + builder: ImageRequest.Builder.() -> Unit = {} + ) = loadImageInternal(imageData = imageData, builder = builder) + + /** we use coil's built in loader with our global synchronized instance, this way we achieve + latest and complete functionality as well as stability **/ + private fun ImageView.loadImageInternal( + imageData: Any?, + headers: Map? = null, + builder: ImageRequest.Builder.() -> Unit = {} // for placeholder, error & transformations + ) { + // clear image to avoid loading issues at fast scrolling (e.g, an image recycler) + this.load(null) + + // Use Coil's built-in load method but with our custom module + this.load(imageData, instance ?: return) { + addHeader("User-Agent", USER_AGENT) + headers?.forEach { (key, value) -> + addHeader(key, value) + } + builder() // if passed + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ImageUtil.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ImageUtil.kt new file mode 100644 index 000000000..3f9d10de4 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ImageUtil.kt @@ -0,0 +1,14 @@ +package com.lagradost.cloudstream3.utils + +import androidx.annotation.DrawableRes + +/// Type safe any image, because THIS IS NOT PYTHON +sealed class UiImage { + data class Image( + val url: String, + val headers: Map? = null + ) : UiImage() + + data class Drawable(@DrawableRes val resId: Int) : UiImage() + data class Bitmap(val bitmap: android.graphics.Bitmap) : UiImage() +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt index 70edf80c7..180131473 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SingleSelectionHelper.kt @@ -24,9 +24,9 @@ import com.lagradost.cloudstream3.databinding.OptionsPopupTvBinding import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes -import com.lagradost.cloudstream3.utils.UIHelper.setImage object SingleSelectionHelper { fun Activity?.showOptionSelectStringRes( @@ -79,7 +79,7 @@ object SingleSelectionHelper { binding.imageView.apply { isGone = poster.isNullOrEmpty() - setImage(poster) + loadImage(poster) } } else { view?.popupMenuNoIconsAndNoStringRes(options.mapIndexed { index, s -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SnackbarHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SnackbarHelper.kt index e6a77795e..b43b51c74 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SnackbarHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SnackbarHelper.kt @@ -8,7 +8,6 @@ import com.google.android.material.snackbar.Snackbar import com.lagradost.api.Log import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.ui.result.UiText import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute object SnackbarHelper { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TextUtil.kt similarity index 63% rename from app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt rename to app/src/main/java/com/lagradost/cloudstream3/utils/TextUtil.kt index 709199430..4f3a74737 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/UiText.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TextUtil.kt @@ -1,17 +1,13 @@ -package com.lagradost.cloudstream3.ui.result +package com.lagradost.cloudstream3.utils import android.content.Context -import android.graphics.Bitmap import android.util.Log -import android.widget.ImageView import android.widget.TextView -import androidx.annotation.DrawableRes import androidx.annotation.StringRes import androidx.core.view.isGone import androidx.core.view.isVisible import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.AppContextUtils.html -import com.lagradost.cloudstream3.utils.UIHelper.setImage sealed class UiText { companion object { @@ -77,71 +73,6 @@ sealed class UiText { } } -sealed class UiImage { - data class Image( - val url: String, - val headers: Map? = null, - @DrawableRes val errorDrawable: Int? = null - ) : UiImage() - - data class Drawable(@DrawableRes val resId: Int) : UiImage() - data class Bitmap(val bitmap: android.graphics.Bitmap) : UiImage() -} - -fun ImageView?.setImage(value: UiImage?, fadeIn: Boolean = true) { - when (value) { - is UiImage.Image -> setImageImage(value, fadeIn) - is UiImage.Drawable -> setImageDrawable(value) - is UiImage.Bitmap -> setImageBitmap(value) - null -> { - this?.isVisible = false - } - } -} - -fun ImageView?.setImageImage(value: UiImage.Image, fadeIn: Boolean = true) { - if (this == null) return - this.isVisible = setImage(value.url, value.headers, value.errorDrawable, fadeIn) -} - -fun ImageView?.setImageDrawable(value: UiImage.Drawable) { - if (this == null) return - this.isVisible = true - this.setImage(UiImage.Drawable(value.resId)) -} - -fun ImageView?.setImageBitmap(value: UiImage.Bitmap) { - if (this == null) return - this.isVisible = true - this.setImageBitmap(value.bitmap) -} - -@JvmName("imgNull") -fun img( - url: String?, - headers: Map? = null, - @DrawableRes errorDrawable: Int? = null -): UiImage? { - if (url.isNullOrBlank()) return null - return UiImage.Image(url, headers, errorDrawable) -} - -fun img( - url: String, - headers: Map? = null, - @DrawableRes errorDrawable: Int? = null -): UiImage { - return UiImage.Image(url, headers, errorDrawable) -} - -fun img(@DrawableRes drawable: Int): UiImage { - return UiImage.Drawable(drawable) -} - -fun img(bitmap: Bitmap): UiImage { - return UiImage.Bitmap(bitmap) -} - fun txt(value: String): UiText { return UiText.DynamicString(value) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index ad1b6502d..e3164301f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -13,23 +13,25 @@ import android.content.res.Configuration import android.content.res.Resources import android.graphics.Bitmap import android.graphics.Color -import android.graphics.drawable.Drawable import android.os.Build import android.os.Bundle import android.os.Handler import android.os.Looper import android.os.TransactionTooLargeException import android.util.Log -import android.view.* +import android.view.Gravity +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup import android.view.ViewGroup.MarginLayoutParams +import android.view.WindowInsets +import android.view.WindowManager import android.view.inputmethod.InputMethodManager -import android.widget.ImageView import android.widget.ListAdapter import android.widget.ListView import android.widget.Toast.LENGTH_LONG import androidx.annotation.AttrRes import androidx.annotation.ColorInt -import androidx.annotation.DrawableRes import androidx.annotation.IdRes import androidx.annotation.StyleRes import androidx.appcompat.view.ContextThemeWrapper @@ -40,7 +42,6 @@ import androidx.core.content.ContextCompat import androidx.core.content.getSystemService import androidx.core.graphics.alpha import androidx.core.graphics.blue -import androidx.core.graphics.drawable.toBitmapOrNull import androidx.core.graphics.green import androidx.core.graphics.red import androidx.core.view.marginBottom @@ -53,14 +54,6 @@ import androidx.fragment.app.FragmentActivity import androidx.navigation.fragment.NavHostFragment import androidx.palette.graphics.Palette import androidx.preference.PreferenceManager -import com.bumptech.glide.load.DataSource -import com.bumptech.glide.load.engine.DiskCacheStrategy -import com.bumptech.glide.load.engine.GlideException -import com.bumptech.glide.load.model.GlideUrl -import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions -import com.bumptech.glide.request.RequestListener -import com.bumptech.glide.request.RequestOptions.bitmapTransform -import com.bumptech.glide.request.target.Target import com.google.android.material.appbar.AppBarLayout import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipDrawable @@ -70,13 +63,9 @@ import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.ui.result.UiImage -import com.lagradost.cloudstream3.ui.result.UiText -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.isLayout -import jp.wasabeef.glide.transformations.BlurTransformation import kotlin.math.roundToInt object UIHelper { @@ -266,172 +255,6 @@ object UIHelper { } } - /*inline fun bindViewBinding( - inflater: LayoutInflater?, - container: ViewGroup?, - layout: Int - ): Pair { - return try { - val localInflater = inflater ?: container?.context?.let { LayoutInflater.from(it) } - ?: return null to txt( - R.string.unable_to_inflate, - "Requires inflater OR container" - )//throw IllegalArgumentException("Requires inflater OR container")) - - //println("methods: ${T::class.java.methods.map { it.name }}") - val bind = T::class.java.methods.first { it.name == "bind" } - //val inflate = T::class.java.methods.first { it.name == "inflate" } - val root = localInflater.inflate(layout, container, false) - bind.invoke(null, root) as T to null - } catch (t: Throwable) { - logError(t) - val message = txt(R.string.unable_to_inflate, t.message ?: "Primary constructor") - // if the desired layout is not found then we inflate the casted layout - /*try { - val localInflater = inflater ?: container?.context?.let { LayoutInflater.from(it) } - ?: return null to txt( - R.string.unable_to_inflate, - "Requires inflater OR container" - )//throw IllegalArgumentException("Requires inflater OR container")) - - // we don't know what method to use as there are 2, but first *should* always be true - return try { - val inflate = T::class.java.methods.first { it.name == "inflate" } - inflate.invoke(null, localInflater, container, false) as T - } catch (_: Throwable) { - val inflate = T::class.java.methods.last { it.name == "inflate" } - inflate.invoke(null, localInflater, container, false) as T - } to message - } catch (t: Throwable) { - logError(t) - }*/ - - null to message - } - }*/ - - fun ImageView?.setImage( - url: String?, - headers: Map? = null, - @DrawableRes - errorImageDrawable: Int? = null, - fadeIn: Boolean = true, - radius: Int = 0, - sample: Int = 3, - colorCallback: ((Palette) -> Unit)? = null - ): Boolean { - if (url.isNullOrBlank()) return false - this.setImage( - UiImage.Image(url, headers, errorImageDrawable), - errorImageDrawable, - fadeIn, - radius, - sample, - colorCallback - ) - return true - } - - fun ImageView?.setImage( - uiImage: UiImage?, - @DrawableRes - errorImageDrawable: Int? = null, - fadeIn: Boolean = true, - radius: Int = 0, - sample: Int = 3, - colorCallback: ((Palette) -> Unit)? = null, - ): Boolean { - if (this == null || uiImage == null) return false - - val (glideImage, identifier) = - (uiImage as? UiImage.Drawable)?.resId?.let { - it to it.toString() - } ?: (uiImage as? UiImage.Image)?.let { image -> - GlideUrl(image.url) { image.headers ?: emptyMap() } to image.url - } ?: return false - - return try { - var builder = com.bumptech.glide.Glide.with(this) - .load(glideImage) - .skipMemoryCache(true) - .diskCacheStrategy(DiskCacheStrategy.ALL).let { req -> - if (fadeIn) - req.transition(DrawableTransitionOptions.withCrossFade()) - else req - } - - if (radius > 0) { - builder = builder.apply(bitmapTransform(BlurTransformation(radius, sample))) - } - - if (colorCallback != null) { - builder = builder.listener(object : RequestListener { - - override fun onResourceReady( - resource: Drawable, - model: Any, - target: Target?, - dataSource: DataSource, - isFirstResource: Boolean - ): Boolean { - resource.toBitmapOrNull() - ?.let { bitmap -> - createPaletteAsync( - identifier, - bitmap, - colorCallback - ) - } - return false - } - - override fun onLoadFailed( - e: GlideException?, - model: Any?, - target: Target, - isFirstResource: Boolean - ): Boolean { - return false - } - }) - } - - val res = if (errorImageDrawable != null) - builder.error(errorImageDrawable).into(this) - else - builder.into(this) - res.clearOnDetach() - - true - } catch (e: Exception) { - logError(e) - false - } - } - - fun ImageView?.setImageBlur( - url: String?, - radius: Int, - sample: Int = 3, - headers: Map? = null - ) { - if (this == null || url.isNullOrBlank()) return - try { - val res = com.bumptech.glide.Glide.with(this) - .load(GlideUrl(url) { headers ?: emptyMap() }) - .apply(bitmapTransform(BlurTransformation(radius, sample))) - .transition( - DrawableTransitionOptions.withCrossFade() - ) - .skipMemoryCache(true) - .diskCacheStrategy(DiskCacheStrategy.ALL) - .into(this) - res.clearOnDetach() - } catch (e: Exception) { - logError(e) - } - } - fun adjustAlpha(@ColorInt color: Int, factor: Float): Int { val alpha = (Color.alpha(color) * factor).roundToInt() val red = Color.red(color) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index e0b78543a..bdaaeb246 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -12,14 +12,16 @@ import android.util.Log import androidx.annotation.DrawableRes import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat +import androidx.core.graphics.drawable.toBitmap import androidx.core.net.toUri import androidx.preference.PreferenceManager import androidx.work.Data import androidx.work.ExistingWorkPolicy import androidx.work.OneTimeWorkRequest import androidx.work.WorkManager -import com.bumptech.glide.Glide -import com.bumptech.glide.load.model.GlideUrl +import coil.ImageLoader +import coil.request.ImageRequest +import coil.request.SuccessResult import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey @@ -52,6 +54,7 @@ import kotlinx.coroutines.cancel import kotlinx.coroutines.delay import kotlinx.coroutines.isActive import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.withContext @@ -59,7 +62,6 @@ import java.io.Closeable import java.io.File import java.io.IOException import java.io.OutputStream -import java.lang.IllegalArgumentException import java.util.* const val DOWNLOAD_CHANNEL_ID = "cloudstream3.general" @@ -231,15 +233,27 @@ object VideoDownloadManager { return cachedBitmaps[url] } - val bitmap = Glide.with(this) - .asBitmap() - .load(GlideUrl(url) { headers ?: emptyMap() }) - .submit(720, 720) - .get() + val imageLoader = ImageLoader(this) - if (bitmap != null) { - cachedBitmaps[url] = bitmap + val request = ImageRequest.Builder(this) + .data(url) + .apply { + headers?.forEach { (key, value) -> + addHeader(key, value) + } + } + .allowHardware(false) // Disable hardware bitmaps for compatibility + .build() + + val bitmap = runBlocking { + val result = imageLoader.execute(request) + (result as? SuccessResult)?.drawable?.toBitmap() } + + bitmap?.let { + cachedBitmaps[url] = it + } + return bitmap } catch (e: Exception) { logError(e) From 71943913754de1d9aa158b8d9420809ff7118db6 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sat, 26 Oct 2024 17:24:53 +0000 Subject: [PATCH 022/962] fixed ugly M3 style on tv --- .../res/color/color_primary_transparent.xml | 4 + .../main/res/layout/fragment_result_tv.xml | 227 +++++++++--------- app/src/main/res/values/styles.xml | 6 + 3 files changed, 120 insertions(+), 117 deletions(-) create mode 100644 app/src/main/res/color/color_primary_transparent.xml diff --git a/app/src/main/res/color/color_primary_transparent.xml b/app/src/main/res/color/color_primary_transparent.xml new file mode 100644 index 000000000..e6d1f8c9e --- /dev/null +++ b/app/src/main/res/color/color_primary_transparent.xml @@ -0,0 +1,4 @@ + + + + diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 893c19ff8..e9d090172 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -91,15 +91,13 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_gravity="center" android:alpha="0.8" android:scaleType="matrix" - tools:src="@drawable/profile_bg_dark_blue" > - + tools:src="@drawable/profile_bg_dark_blue" /> - + android:src="@drawable/background_shadow" /> + android:layout_marginTop="225dp" + android:orientation="vertical"> + tools:text="8 Episodes" + tools:visibility="visible" /> - + + + + + - - - - - + android:maxLines="1" + android:textColor="?attr/grayTextColor" + tools:ignore="RtlSymmetry" + tools:text="69m remaining" /> + - + android:tag="@string/tv_no_focus_tag" + app:icon="@drawable/ic_baseline_play_arrow_24" + app:iconPadding="0dp" /> + tools:visibility="visible"> - + android:tag="@string/tv_no_focus_tag" + app:icon="@drawable/ic_baseline_play_arrow_24" + app:iconPadding="0dp" /> - + android:tag="@string/tv_no_focus_tag" + app:icon="@drawable/ic_baseline_resume_arrow2" + app:iconPadding="0dp" /> - + android:tag="@string/tv_no_focus_tag" + app:icon="@drawable/ic_baseline_film_roll_24" + app:iconPadding="0dp" /> - + android:tag="@string/tv_no_focus_tag" + app:icon="@drawable/outline_bookmark_add_24" + app:iconPadding="0dp" /> - + android:tag="@string/tv_no_focus_tag" + app:icon="@drawable/ic_baseline_favorite_border_24" + app:iconPadding="0dp" /> - + android:tag="@string/tv_no_focus_tag" + app:icon="@drawable/baseline_notifications_none_24" + app:iconPadding="0dp" /> - + android:tag="@string/tv_no_focus_tag" + app:icon="@drawable/search_icon" + app:iconPadding="0dp" /> + tools:visibility="visible"> - + android:tag="@string/tv_no_focus_tag" + app:icon="@drawable/ic_baseline_sort_24" + app:iconPadding="0dp" /> + android:baselineAligned="false" + android:orientation="horizontal"> @@ -570,9 +563,9 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_height="wrap_content" android:ellipsize="end" android:fadingEdgeLength="30dp" + android:focusable="true" android:foreground="@drawable/outline_drawable" android:maxLines="7" - android:focusable="true" android:nextFocusUp="@id/result_play_parent" android:nextFocusDown="@id/result_cast_items" android:padding="5dp" @@ -617,14 +610,14 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:textColor="?attr/textColor" android:textSize="20sp" android:textStyle="bold" - tools:visibility="visible" - android:visibility="gone" /> + android:visibility="gone" + tools:visibility="visible" /> @@ -634,9 +627,9 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="10dp" + android:descendantFocusability="afterDescendants" android:nextFocusUp="@id/result_cast_items" android:nextFocusDown="@id/result_recommendations_list" - android:descendantFocusability="afterDescendants" android:orientation="horizontal" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" tools:itemCount="2" @@ -1042,19 +1035,19 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_height="wrap_content" android:orientation="vertical"> - + - + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index db0066700..bfe435ef9 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -99,6 +99,12 @@ 0dp + + - + + + + + diff --git a/app/src/main/res/xml/settings_ui.xml b/app/src/main/res/xml/settings_ui.xml index 9909478dc..08c0fba72 100644 --- a/app/src/main/res/xml/settings_ui.xml +++ b/app/src/main/res/xml/settings_ui.xml @@ -17,7 +17,7 @@ android:title="@string/app_layout" /> @@ -33,6 +33,18 @@ app:min="0" app:seekBarIncrement="1" app:showSeekBarValue="true" /> + + From fabef62ea6dfea0b7cb9dcbab925c6f46d0c93b0 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 26 Jun 2025 21:57:04 +0200 Subject: [PATCH 245/962] Fix(TV): Made backbutton hide episodes instead of exit, Closes #1268 --- .../ui/result/ResultFragmentTv.kt | 62 ++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index ea055376e..a91f513e6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -46,6 +46,8 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.html import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl import com.lagradost.cloudstream3.utils.AppContextUtils.loadCache import com.lagradost.cloudstream3.utils.AppContextUtils.updateHasTrailers +import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback +import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.detachBackPressedCallback import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant @@ -153,9 +155,11 @@ class ResultFragmentTv : Fragment() { rec?.map { it.apiName }?.distinct()?.let { apiNames -> // very dirty selection resultRecommendationsFilterSelection.isVisible = apiNames.size > 1 - resultRecommendationsFilterSelection.update(apiNames.map { com.lagradost.cloudstream3.utils.txt( - it - ) to it }) + resultRecommendationsFilterSelection.update(apiNames.map { + com.lagradost.cloudstream3.utils.txt( + it + ) to it + }) resultRecommendationsFilterSelection.select(apiNames.indexOf(matchAgainst)) } ?: run { resultRecommendationsFilterSelection.isVisible = false @@ -224,8 +228,20 @@ class ResultFragmentTv : Fragment() { } } + override fun onDestroy() { + super.onDestroy() + activity?.detachBackPressedCallback(this@ResultFragmentTv.toString()) + } + private fun toggleEpisodes(show: Boolean) { binding?.apply { + if (show) { + activity?.attachBackPressedCallback(this@ResultFragmentTv.toString()) { + toggleEpisodes(false) + } + } else { + activity?.detachBackPressedCallback(this@ResultFragmentTv.toString()) + } episodesShadow.fade(show) episodeHolderTv.fade(show) if (episodesShadow.isRtl()) { @@ -315,7 +331,7 @@ class ResultFragmentTv : Fragment() { resultSubscribeButton to resultSubscribeText, resultSearchButton to resultSearchText, resultEpisodesShowButton to resultEpisodesShowText - ).forEach { (button , text) -> + ).forEach { (button, text) -> button.setOnFocusChangeListener { view, hasFocus -> if (!hasFocus) { @@ -325,13 +341,14 @@ class ResultFragmentTv : Fragment() { } text.isSelected = true - if (button.tag == context?.getString(R.string.tv_no_focus_tag)){ - resultFinishLoading.scrollTo(0,0) + if (button.tag == context?.getString(R.string.tv_no_focus_tag)) { + resultFinishLoading.scrollTo(0, 0) } when (button.id) { R.id.result_episodes_show_button -> { toggleEpisodes(true) } + else -> { toggleEpisodes(false) } @@ -488,7 +505,12 @@ class ResultFragmentTv : Fragment() { when { resume.isMovie -> context?.getString(R.string.resume) resume.result.season != null -> - "${getString(R.string.season_short)}${resume.result.season}:${getString(R.string.episode_short)}${resume.result.episode}" + "${getString(R.string.season_short)}${resume.result.season}:${ + getString( + R.string.episode_short + ) + }${resume.result.episode}" + else -> "${getString(R.string.episode)} ${resume.result.episode}" } @@ -585,12 +607,14 @@ class ResultFragmentTv : Fragment() { } val name = (viewModel.page.value as? Resource.Success)?.value?.title - ?: com.lagradost.cloudstream3.utils.txt(R.string.no_data).asStringNull(context) ?: "" + ?: com.lagradost.cloudstream3.utils.txt(R.string.no_data) + .asStringNull(context) ?: "" CommonActivity.showToast( com.lagradost.cloudstream3.utils.txt( message, name - ), Toast.LENGTH_SHORT) + ), Toast.LENGTH_SHORT + ) } } } @@ -632,12 +656,14 @@ class ResultFragmentTv : Fragment() { } val name = (viewModel.page.value as? Resource.Success)?.value?.title - ?: com.lagradost.cloudstream3.utils.txt(R.string.no_data).asStringNull(context) ?: "" + ?: com.lagradost.cloudstream3.utils.txt(R.string.no_data) + .asStringNull(context) ?: "" CommonActivity.showToast( com.lagradost.cloudstream3.utils.txt( message, name - ), Toast.LENGTH_SHORT) + ), Toast.LENGTH_SHORT + ) } } @@ -653,7 +679,7 @@ class ResultFragmentTv : Fragment() { } observeNullable(viewModel.movie) { data -> - if (data == null ) { + if (data == null) { return@observeNullable } @@ -796,13 +822,19 @@ class ResultFragmentTv : Fragment() { ep.getWatchProgress() >= NEXT_WATCH_EPISODE_PERCENTAGE.toFloat() / 100.0f || ep.videoWatchState == VideoWatchState.Watched } - val firstUnwatched = episodes.value.getOrElse(lastWatchedIndex + 1) { episodes.value.firstOrNull() } + val firstUnwatched = + episodes.value.getOrElse(lastWatchedIndex + 1) { episodes.value.firstOrNull() } if (firstUnwatched != null) { resultPlaySeriesText.text = when { firstUnwatched.season != null -> - "${getString(R.string.season_short)}${firstUnwatched.season}:${getString(R.string.episode_short)}${firstUnwatched.episode}" + "${getString(R.string.season_short)}${firstUnwatched.season}:${ + getString( + R.string.episode_short + ) + }${firstUnwatched.episode}" + else -> "${getString(R.string.episode)} ${firstUnwatched.episode}" } resultPlaySeriesButton.setOnClickListener { @@ -887,7 +919,7 @@ class ResultFragmentTv : Fragment() { ).random() //Change poster crop area to 20% from Top backgroundPoster.cropYCenterOffsetPct = 0.20F - + backgroundPoster.loadImage(d.posterBackgroundImage) { error { getImageFromDrawable(context ?: return@error null, error) } } From d2e64f29c70f76d3a6694cb84e481fca1f9e78b2 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 26 Jun 2025 23:34:58 +0200 Subject: [PATCH 246/962] Chore: Replaced all instances of outdated mvvm apis --- .../lagradost/cloudstream3/AcraApplication.kt | 8 ++++---- .../com/lagradost/cloudstream3/MainActivity.kt | 12 ++++++------ .../cloudstream3/network/CloudflareKiller.kt | 6 +++--- .../cloudstream3/network/RequestsHelper.kt | 4 ++-- .../cloudstream3/plugins/PluginManager.kt | 8 ++++---- .../cloudstream3/plugins/RepositoryManager.kt | 18 +++++++++--------- .../cloudstream3/syncproviders/SyncRepo.kt | 6 +++--- .../syncproviders/providers/AniListApi.kt | 6 +++--- .../syncproviders/providers/SimklApi.kt | 4 ++-- .../ui/download/DownloadFragment.kt | 6 +++--- .../ui/player/AbstractPlayerFragment.kt | 4 ++-- .../cloudstream3/ui/player/CS3IPlayer.kt | 4 ++-- .../ui/player/DownloadedPlayerActivity.kt | 4 ++-- .../cloudstream3/ui/player/GeneratorPlayer.kt | 14 +++++++------- .../ui/player/PlayerGeneratorViewModel.kt | 18 +++++++++--------- .../cloudstream3/ui/player/PlayerPipHelper.kt | 4 ++-- .../ui/result/ResultFragmentPhone.kt | 6 +++--- .../cloudstream3/ui/result/ResultViewModel2.kt | 4 ++-- .../ui/settings/SettingsGeneral.kt | 4 ++-- .../ui/settings/SettingsUpdates.kt | 4 ++-- .../ui/settings/testing/TestFragment.kt | 4 ++-- .../ui/setup/SetupFragmentExtensions.kt | 6 +++--- .../ui/setup/SetupFragmentLanguage.kt | 8 ++++---- .../ui/setup/SetupFragmentLayout.kt | 6 +++--- .../ui/setup/SetupFragmentMedia.kt | 6 +++--- .../ui/setup/SetupFragmentProviderLanguage.kt | 6 +++--- .../cloudstream3/utils/AppContextUtils.kt | 8 ++++---- .../network/WebViewResolver.android.kt | 4 ++-- .../com/lagradost/cloudstream3/MainAPI.kt | 10 +++++----- .../cloudstream3/extractors/Pelisplus.kt | 4 ++-- .../extractors/helper/GogoHelper.kt | 4 ++-- .../metaproviders/SyncRedirector.kt | 4 ++-- .../cloudstream3/utils/ExtractorApi.kt | 2 +- 33 files changed, 108 insertions(+), 108 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index 003d79a77..9f493fbbc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -11,8 +11,8 @@ import androidx.fragment.app.FragmentActivity import coil3.PlatformContext import coil3.SingletonImageLoader import com.lagradost.api.setContext -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe +import com.lagradost.cloudstream3.mvvm.safeAsync import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV @@ -54,7 +54,7 @@ class CustomReportSender : ReportSender { thread { // to not run it on main thread runBlocking { - suspendSafeApiCall { + safeAsync { app.post(url, data = data) //println("Report response: $post") } @@ -62,7 +62,7 @@ class CustomReportSender : ReportSender { } runOnMainThread { // to run it on main looper - normalSafeApiCall { + safe { Toast.makeText(context, R.string.acra_report_toast, Toast.LENGTH_SHORT).show() } }*/ diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index dedb7fadd..682e69e34 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -81,7 +81,7 @@ import com.lagradost.cloudstream3.databinding.ActivityMainTvBinding import com.lagradost.cloudstream3.databinding.BottomResultviewPreviewBinding import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.network.initClient @@ -266,7 +266,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa // TODO MUCH BETTER HANDLING // Invalid URIs can crash - fun safeURI(uri: String) = normalSafeApiCall { URI(uri) } + fun safeURI(uri: String) = safe { URI(uri) } if (str != null && this != null) { if (str.startsWith("https://cs.repo")) { @@ -1138,15 +1138,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa updateTv() // backup when we update the app, I don't trust myself to not boot lock users, might want to make this a setting? - normalSafeApiCall { + safe { val appVer = BuildConfig.VERSION_NAME val lastAppAutoBackup: String = getKey("VERSION_NAME") ?: "" if (appVer != lastAppAutoBackup) { setKey("VERSION_NAME", BuildConfig.VERSION_NAME) - normalSafeApiCall { + safe { backup(this) } - normalSafeApiCall { + safe { // Recompile oat on new version PluginManager.deleteAllOatFiles(this) } @@ -1251,7 +1251,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa ioSafe { SafeFile.check(this@MainActivity) } if (PluginManager.checkSafeModeFile()) { - normalSafeApiCall { + safe { showToast(R.string.safe_mode_file, Toast.LENGTH_LONG) } } else if (lastError == null) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt b/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt index 85a9db5db..9efa88a37 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/CloudflareKiller.kt @@ -5,7 +5,7 @@ import android.webkit.CookieManager import androidx.annotation.AnyThread import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.debugWarning -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.nicehttp.Requests.Companion.await import com.lagradost.nicehttp.cookies import kotlinx.coroutines.runBlocking @@ -32,7 +32,7 @@ class CloudflareKiller : Interceptor { init { // Needs to clear cookies between sessions to generate new cookies. - normalSafeApiCall { + safe { // This can throw an exception on unsupported devices :( CookieManager.getInstance().removeAllCookies(null) } @@ -77,7 +77,7 @@ class CloudflareKiller : Interceptor { } private fun getWebViewCookie(url: String): String? { - return normalSafeApiCall { + return safe { CookieManager.getInstance()?.getCookie(url) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt index 1565d92cf..ec486d61d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/network/RequestsHelper.kt @@ -4,7 +4,7 @@ import android.content.Context import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.USER_AGENT -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.nicehttp.Requests import com.lagradost.nicehttp.ignoreAllSSLErrors import okhttp3.Cache @@ -20,7 +20,7 @@ fun Requests.initClient(context: Context) { } fun buildDefaultClient(context: Context): OkHttpClient { - normalSafeApiCall { Security.insertProviderAt(Conscrypt.newProvider(), 1) } + safe { Security.insertProviderAt(Conscrypt.newProvider(), 1) } val settingsManager = PreferenceManager.getDefaultSharedPreferences(context) val dns = settingsManager.getInt(context.getString(R.string.dns_pref), 0) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 524d571d2..6e59696ae 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -38,7 +38,7 @@ import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.apmap import com.lagradost.cloudstream3.mvvm.debugPrint import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.plugins.RepositoryManager.ONLINE_PLUGINS_FOLDER import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES import com.lagradost.cloudstream3.plugins.RepositoryManager.downloadPluginToFile @@ -144,7 +144,7 @@ object PluginManager { !it.filePath.contains(repositoryPath) } val file = File(repositoryPath) - normalSafeApiCall { + safe { if (file.exists()) file.deleteRecursively() } setKey(PLUGINS_KEY, plugins) @@ -577,9 +577,9 @@ object PluginManager { * @return true if safe mode file is present **/ fun checkSafeModeFile(): Boolean { - return normalSafeApiCall { + return safe { val folder = File(CLOUD_STREAM_FOLDER) - if (!folder.exists()) return@normalSafeApiCall false + if (!folder.exists()) return@safe false val files = folder.listFiles { _, name -> name.equals("safe", ignoreCase = true) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index c6ec9df7f..d92e81acd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -9,8 +9,8 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall -import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe +import com.lagradost.cloudstream3.mvvm.safeAsync import com.lagradost.cloudstream3.plugins.PluginManager.getPluginSanitizedFileName import com.lagradost.cloudstream3.plugins.PluginManager.unloadPlugin import com.lagradost.cloudstream3.ui.settings.extensions.REPOSITORIES_KEY @@ -94,12 +94,12 @@ object RepositoryManager { else fixedUrl } } else if (fixedUrl.matches("^[a-zA-Z0-9!_-]+$".toRegex())) { - suspendSafeApiCall { + safeAsync { app.get("https://cutt.ly/${fixedUrl}", allowRedirects = false).let { it2 -> it2.headers["Location"]?.let { url -> - if (url.startsWith("https://cutt.ly/404")) return@suspendSafeApiCall null - if (url.removeSuffix("/") == "https://cutt.ly") return@suspendSafeApiCall null - return@suspendSafeApiCall url + if (url.startsWith("https://cutt.ly/404")) return@safeAsync null + if (url.removeSuffix("/") == "https://cutt.ly") return@safeAsync null + return@safeAsync url } } } @@ -107,7 +107,7 @@ object RepositoryManager { } suspend fun parseRepository(url: String): Repository? { - return suspendSafeApiCall { + return safeAsync { // Take manifestVersion and such into account later app.get(convertRawGitUrl(url)).parsedSafe() } @@ -142,7 +142,7 @@ object RepositoryManager { pluginUrl: String, file: File ): File? { - return suspendSafeApiCall { + return safeAsync { file.mkdirs() // Overwrite if exists @@ -191,7 +191,7 @@ object RepositoryManager { // Unload all plugins, not using deletePlugin since we // delete all data and files in deleteRepositoryData - normalSafeApiCall { + safe { file.listFiles { plugin: File -> unloadPlugin(plugin.absolutePath) false diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt index 9363cb6fb..df88eeb71 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/SyncRepo.kt @@ -2,7 +2,7 @@ package com.lagradost.cloudstream3.syncproviders import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.mvvm.Resource -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.safeApiCall class SyncRepo(private val repo: SyncAPI) { @@ -39,10 +39,10 @@ class SyncRepo(private val repo: SyncAPI) { } fun hasAccount(): Boolean { - return normalSafeApiCall { repo.loginInfo() != null } ?: false + return safe { repo.loginInfo() != null } ?: false } - fun getIdFromUrl(url: String): String? = normalSafeApiCall { + fun getIdFromUrl(url: String): String? = safe { repo.getIdFromUrl(url) } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index 370ad2304..f8e824095 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -8,7 +8,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall +import com.lagradost.cloudstream3.mvvm.safeAsync import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.SyncAPI @@ -530,13 +530,13 @@ class AniListApi(index: Int) : AccountManager(index), SyncAPI { } private suspend fun postApi(q: String, cache: Boolean = false): String? { - return suspendSafeApiCall { + return safeAsync { if (!checkToken()) { app.post( "https://graphql.anilist.co/", headers = mapOf( "Authorization" to "Bearer " + (getAuth() - ?: return@suspendSafeApiCall null), + ?: return@safeAsync null), if (cache) "Cache-Control" to "max-stale=$MAX_STALE" else "Cache-Control" to "no-cache" ), cacheTime = 0, diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt index 92a181dd1..519fb4c3a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt @@ -21,7 +21,7 @@ import com.lagradost.cloudstream3.mapper import com.lagradost.cloudstream3.mvvm.debugAssert import com.lagradost.cloudstream3.mvvm.debugPrint import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall +import com.lagradost.cloudstream3.mvvm.safeAsync import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthAPI import com.lagradost.cloudstream3.syncproviders.OAuth2API @@ -760,7 +760,7 @@ class SimklApi(index: Int) : AccountManager(index), SyncAPI { } private suspend fun getUser(): SettingsResponse.User? { - return suspendSafeApiCall { + return safeAsync { app.post("$mainUrl/users/settings", interceptor = interceptor) .parsedSafe()?.user } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index d8c2fb2e1..fff70e975 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -29,7 +29,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding import com.lagradost.cloudstream3.databinding.StreamInputBinding import com.lagradost.cloudstream3.isEpisodeBased -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.player.BasicLink @@ -309,7 +309,7 @@ class DownloadFragment : Fragment() { .setType("video/*") .addCategory(Intent.CATEGORY_OPENABLE) .addFlags(FLAG_GRANT_READ_URI_PERMISSION) // Request temporary access - normalSafeApiCall { + safe { videoResultLauncher.launch( Intent.createChooser( intent, @@ -367,7 +367,7 @@ class DownloadFragment : Fragment() { } private fun activateSwitchOnHls(text: String?, binding: StreamInputBinding) { - binding.hlsSwitch.isChecked = normalSafeApiCall { + binding.hlsSwitch.isChecked = safe { URI(text).path?.substringAfterLast(".")?.contains("m3u") } == true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index fc4e38a0e..7dcf04c07 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -44,7 +44,7 @@ import com.lagradost.cloudstream3.CommonActivity.screenWidth import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.unixTimeMs import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle import com.lagradost.cloudstream3.ui.subtitles.SubtitlesFragment @@ -243,7 +243,7 @@ abstract class AbstractPlayerFragment( exitedPipMode() pipReceiver?.let { // Prevents java.lang.IllegalArgumentException: Receiver not registered - normalSafeApiCall { + safe { activity?.unregisterReceiver(it) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 9d5539355..e311f7b0d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -72,7 +72,7 @@ import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.debugAssert import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.ui.subtitles.SaveCaptionStyle @@ -1166,7 +1166,7 @@ class CS3IPlayer : IPlayer { exoPlayer?.addListener(object : Player.Listener { override fun onTracksChanged(tracks: Tracks) { - normalSafeApiCall { + safe { val textTracks = tracks.groups.filter { it.type == TRACK_TYPE_TEXT } playerSelectedSubtitleTracks = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt index a50d1f6ca..88787c76b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadedPlayerActivity.kt @@ -7,7 +7,7 @@ import android.view.KeyEvent import androidx.appcompat.app.AppCompatActivity import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.ui.player.OfflinePlaybackHelper.playLink import com.lagradost.cloudstream3.ui.player.OfflinePlaybackHelper.playUri import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback @@ -36,7 +36,7 @@ class DownloadedPlayerActivity : AppCompatActivity() { val data = intent.data if (intent?.action == Intent.ACTION_SEND || intent?.action == Intent.ACTION_OPEN_DOCUMENT || intent?.action == Intent.ACTION_VIEW) { - val extraText = normalSafeApiCall { // I dont trust android + val extraText = safe { // I dont trust android intent.getStringExtra(Intent.EXTRA_TEXT) } val cd = intent.clipData diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index e4a8b2088..0eb28e5b5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -66,7 +66,7 @@ import com.lagradost.cloudstream3.isLiveStream import com.lagradost.cloudstream3.isMovieType import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.subtitles.AbstractSubApi @@ -910,10 +910,10 @@ class GeneratorPlayer : FullScreenPlayer() { // Open file picker private val subsPathPicker = registerForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> - normalSafeApiCall { + safe { // It lies, it can be null if file manager quits. - if (uri == null) return@normalSafeApiCall - val ctx = context ?: AcraApplication.context ?: return@normalSafeApiCall + if (uri == null) return@safe + val ctx = context ?: AcraApplication.context ?: return@safe // RW perms for the path ctx.contentResolver.takePersistableUriPermission( uri, @@ -1060,7 +1060,7 @@ class GeneratorPlayer : FullScreenPlayer() { var shouldDismiss = true binding.subtitleSettingsBtt.setOnClickListener { - normalSafeApiCall { + safe { SubtitlesFragment().show(this.parentFragmentManager, "SubtitleSettings") } } @@ -1732,7 +1732,7 @@ class GeneratorPlayer : FullScreenPlayer() { private fun autoSelectSubtitles() { //Log.i(TAG, "autoSelectSubtitles") - normalSafeApiCall { + safe { if (!autoSelectFromSettings()) { autoSelectFromDownloads() } @@ -2024,7 +2024,7 @@ class GeneratorPlayer : FullScreenPlayer() { val wasGone = binding?.overlayLoadingSkipButton?.isGone == true binding?.overlayLoadingSkipButton?.isVisible = turnVisible - normalSafeApiCall { + safe { if (currentLinks.any { link -> getLinkPriority(currentQualityProfile, link) >= QualityDataHelper.AUTO_SKIP_PRIORITY diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt index cb170d125..023cedd8a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerGeneratorViewModel.kt @@ -9,7 +9,7 @@ import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.Coroutines.ioSafe @@ -114,20 +114,20 @@ class PlayerGeneratorViewModel : ViewModel() { } fun getLoadResponse(): LoadResponse? { - return normalSafeApiCall { (generator as? RepoLinkGenerator?)?.page } + return safe { (generator as? RepoLinkGenerator?)?.page } } fun getMeta(): Any? { - return normalSafeApiCall { generator?.getCurrent() } + return safe { generator?.getCurrent() } } fun getAllMeta(): List? { - return normalSafeApiCall { generator?.getAll() } + return safe { generator?.getAll() } } fun getNextMeta(): Any? { - return normalSafeApiCall { - if (generator?.hasNext() == false) return@normalSafeApiCall null + return safe { + if (generator?.hasNext() == false) return@safe null generator?.getCurrent(offset = 1) } } @@ -204,8 +204,8 @@ class PlayerGeneratorViewModel : ViewModel() { synchronized(currentLinks) { currentLinks.add(it) // Clone to prevent ConcurrentModificationException - normalSafeApiCall { - // Extra normalSafeApiCall since .toSet() iterates. + safe { + // Extra safe since .toSet() iterates. _currentLinks.postValue(currentLinks.toSet()) } } @@ -213,7 +213,7 @@ class PlayerGeneratorViewModel : ViewModel() { subtitleCallback = { synchronized(extraSubtitles) { currentSubs.add(it) - normalSafeApiCall { + safe { _currentSubs.postValue(currentSubs + extraSubtitles) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerPipHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerPipHelper.kt index 972f8cf3d..cc99b585f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerPipHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PlayerPipHelper.kt @@ -11,7 +11,7 @@ import android.util.Rational import androidx.annotation.RequiresApi import androidx.annotation.StringRes import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import kotlin.math.roundToInt class PlayerPipHelper { @@ -102,7 +102,7 @@ class PlayerPipHelper { Rational((it * ratioAccuracy).roundToInt(), ratioAccuracy) } - normalSafeApiCall { + safe { activity.setPictureInPictureParams( PictureInPictureParams.Builder() .apply { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index c873f2697..bd830aa2b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -44,7 +44,7 @@ import com.lagradost.cloudstream3.databinding.ResultRecommendationsBinding import com.lagradost.cloudstream3.databinding.ResultSyncBinding import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.services.SubscriptionWorkManager @@ -891,7 +891,7 @@ open class ResultFragmentPhone : FullScreenPlayer() { fun setSyncMaxEpisodes(totalEpisodes: Int?) { syncBinding?.resultSyncEpisodes?.max = (totalEpisodes ?: 0) * 1000 - normalSafeApiCall { + safe { val ctx = syncBinding?.resultSyncEpisodes?.context syncBinding?.resultSyncMaxEpisodes?.text = totalEpisodes?.let { episodes -> @@ -961,7 +961,7 @@ open class ResultFragmentPhone : FullScreenPlayer() { } resultSyncCurrentEpisodes.text = Editable.Factory.getInstance()?.newEditable(watchedEpisodes.toString()) - normalSafeApiCall { // format might fail + safe { // format might fail context?.getString(R.string.sync_score_format)?.format(d.score ?: 0) ?.let { resultSyncScoreText.text = it diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index fdd2f11d5..0e7e1fdb6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -1212,7 +1212,7 @@ class ResultViewModel2 : ViewModel() { } private fun getImdbIdFromSyncData(syncData: Map?): String? { - return normalSafeApiCall { + return safe { val imdbId = readIdFromString( syncData?.get(AccountManager.simklApi.idPrefix) )[SimklSyncServices.Imdb] @@ -1221,7 +1221,7 @@ class ResultViewModel2 : ViewModel() { } private fun getTMDbIdFromSyncData(syncData: Map?): String? { - return normalSafeApiCall { + return safe { val tmdbId = readIdFromString( syncData?.get(AccountManager.simklApi.idPrefix) )[SimklSyncServices.Tmdb] diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index 74910ecbc..e82481ffa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -22,7 +22,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.databinding.AddRemoveSitesBinding import com.lagradost.cloudstream3.databinding.AddSiteInputBinding import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV @@ -319,7 +319,7 @@ class SettingsGeneral : PreferenceFragmentCompat() { } fun getDownloadDirs(): List { - return normalSafeApiCall { + return safe { context?.let { ctx -> val defaultDir = VideoDownloadManager.getDefaultDir(ctx)?.filePath() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 4c69cb38f..bacca67ec 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -16,7 +16,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.databinding.LogcatBinding import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.network.initClient import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.services.BackupWorkManager @@ -264,7 +264,7 @@ class SettingsUpdates : PreferenceFragmentCompat() { } private fun getBackupDirsForDisplay(): List { - return normalSafeApiCall { + return safe { context?.let { ctx -> val defaultDir = BackupUtils.getDefaultBackupDir(ctx)?.filePath() val first = listOf(defaultDir) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt index 7878afaac..921ac0674 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/testing/TestFragment.kt @@ -8,7 +8,7 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentTestingBinding -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.settings.Globals.TV @@ -47,7 +47,7 @@ class TestFragment : Fragment() { } observeNullable(testViewModel.providerResults) { - normalSafeApiCall { + safe { val newItems = it.sortedBy { api -> api.first.name } (providerTestRecyclerView.adapter as? TestResultAdapter)?.updateList( newItems diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt index 4369b22f9..0dccd5cc4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt @@ -11,7 +11,7 @@ import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.MainActivity.Companion.afterRepositoryLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentSetupExtensionsBinding -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.plugins.RepositoryManager import com.lagradost.cloudstream3.plugins.RepositoryManager.PREBUILT_REPOSITORIES import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel @@ -93,8 +93,8 @@ class SetupFragmentExtensions : Fragment() { // openBrowser(PUBLIC_REPOSITORIES_LIST, isTvSettings(), this) // } - normalSafeApiCall { - // val ctx = context ?: return@normalSafeApiCall + safe { + // val ctx = context ?: return@safe setRepositories() binding?.apply { if (!isSetup) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt index fc7fe093f..f57d4f159 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt @@ -14,7 +14,7 @@ import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentSetupLanguageBinding -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.ui.settings.appLanguages import com.lagradost.cloudstream3.ui.settings.getCurrentLocale @@ -46,10 +46,10 @@ class SetupFragmentLanguage : Fragment() { super.onViewCreated(view, savedInstanceState) // We don't want a crash for all users - normalSafeApiCall { + safe { fixPaddingStatusbar(binding?.setupRoot) - val ctx = context ?: return@normalSafeApiCall + val ctx = context ?: return@safe val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) val arrayAdapter = @@ -57,7 +57,7 @@ class SetupFragmentLanguage : Fragment() { binding?.apply { // Icons may crash on some weird android versions? - normalSafeApiCall { + safe { val drawable = when { BuildConfig.DEBUG -> R.drawable.cloud_2_gradient_debug BuildConfig.FLAVOR == "prerelease" -> R.drawable.cloud_2_gradient_beta diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt index 9de6038e3..85eabefa4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt @@ -12,7 +12,7 @@ import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentSetupLayoutBinding -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import org.acra.ACRA @@ -40,8 +40,8 @@ class SetupFragmentLayout : Fragment() { super.onViewCreated(view, savedInstanceState) fixPaddingStatusbar(binding?.setupRoot) - normalSafeApiCall { - val ctx = context ?: return@normalSafeApiCall + safe { + val ctx = context ?: return@safe val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt index 49a93608c..9db967dcb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt @@ -13,7 +13,7 @@ import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.databinding.FragmentSetupMediaBinding -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar @@ -39,10 +39,10 @@ class SetupFragmentMedia : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - normalSafeApiCall { + safe { fixPaddingStatusbar(binding?.setupRoot) - val ctx = context ?: return@normalSafeApiCall + val ctx = context ?: return@safe val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) val arrayAdapter = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt index c12e9eb83..353e735e9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt @@ -14,7 +14,7 @@ import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentSetupProviderLanguagesBinding -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.utils.AppContextUtils.getApiProviderLangSettings import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar @@ -42,8 +42,8 @@ class SetupFragmentProviderLanguage : Fragment() { super.onViewCreated(view, savedInstanceState) fixPaddingStatusbar(binding?.setupRoot) - normalSafeApiCall { - val ctx = context ?: return@normalSafeApiCall + safe { + val ctx = context ?: return@safe val settingsManager = PreferenceManager.getDefaultSharedPreferences(ctx) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt index 34096f76a..28b194531 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt @@ -73,7 +73,7 @@ import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.isMovieType import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.plugins.RepositoryManager import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_RESUME_WATCHING import com.lagradost.cloudstream3.syncproviders.providers.Kitsu @@ -134,9 +134,9 @@ object AppContextUtils { //fun Context.deleteFavorite(data: SearchResponse) { // if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return - // normalSafeApiCall { + // safe { // val existingId = - // getWatchNextProgramByVideoId(data.url, this).second ?: return@normalSafeApiCall + // getWatchNextProgramByVideoId(data.url, this).second ?: return@safe // contentResolver.delete( // // TvContractCompat.buildWatchNextProgramUri(existingId), @@ -655,7 +655,7 @@ object AppContextUtils { fun openWebView(fragment: Fragment?, url: String) { if (fragment?.context?.hasWebView() == true) - normalSafeApiCall { + safe { fragment .findNavController() .navigate(R.id.navigation_webview, WebviewFragment.newInstance(url)) diff --git a/library/src/androidMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.android.kt b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.android.kt index 0fbc5749d..fc11c4721 100644 --- a/library/src/androidMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.android.kt +++ b/library/src/androidMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.android.kt @@ -10,7 +10,7 @@ import com.lagradost.api.getContext import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.debugException import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.mainWork import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf @@ -281,7 +281,7 @@ fun WebResourceRequest.toRequest(): Request? { // If invalid url then it can crash with // java.lang.IllegalArgumentException: Expected URL scheme 'http' or 'https' but was 'data' // At Request.Builder().url(addParamsToUrl(url, params)) - return normalSafeApiCall { + return safe { requestCreator( this.method, webViewUrl, diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 8ee632909..beb954035 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -11,7 +11,7 @@ import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.module.kotlin.kotlinModule import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.syncproviders.SyncIdName import com.lagradost.cloudstream3.utils.* import com.lagradost.cloudstream3.utils.AppUtils.toJson @@ -1373,10 +1373,10 @@ interface LoadResponse { database: SimklSyncServices, id: String? ) { - normalSafeApiCall { + safe { this.syncData[simklIdPrefix] = addIdToString(this.syncData[simklIdPrefix], database, id.toString()) - ?: return@normalSafeApiCall + ?: return@safe } } @@ -1394,13 +1394,13 @@ interface LoadResponse { } fun LoadResponse.getImdbId(): String? { - return normalSafeApiCall { + return safe { readIdFromString(this.syncData[simklIdPrefix])[SimklSyncServices.Imdb] } } fun LoadResponse.getTMDbId(): String? { - return normalSafeApiCall { + return safe { readIdFromString(this.syncData[simklIdPrefix])[SimklSyncServices.Tmdb] } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Pelisplus.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Pelisplus.kt index ef10f46ef..c871280a2 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Pelisplus.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Pelisplus.kt @@ -3,7 +3,7 @@ package com.lagradost.cloudstream3.extractors import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app -import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall +import com.lagradost.cloudstream3.mvvm.safeAsync import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.INFER_TYPE import com.lagradost.cloudstream3.utils.extractorApis @@ -44,7 +44,7 @@ open class Pelisplus(val mainUrl: String) { val extractorUrl = getExtractorUrl(id) /** Stolen from GogoanimeProvider.kt extractor */ - suspendSafeApiCall { + safeAsync { val link = getDownloadUrl(id) println("Generated vidstream download link: $link") val page = app.get(link, referer = extractorUrl) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt index 4109196f5..a16d41943 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/GogoHelper.kt @@ -5,7 +5,7 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.base64Decode import com.lagradost.cloudstream3.base64DecodeArray import com.lagradost.cloudstream3.base64Encode -import com.lagradost.cloudstream3.mvvm.normalSafeApiCall +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.ExtractorLink @@ -25,7 +25,7 @@ object GogoHelper { * @return the encryption key * */ private fun getKey(id: String): String? { - return normalSafeApiCall { + return safe { id.map { it.code.toString(16) }.joinToString("").substring(0, 32) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt index bc646a8d2..2a8524e00 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/SyncRedirector.kt @@ -1,7 +1,7 @@ package com.lagradost.cloudstream3.metaproviders import com.lagradost.cloudstream3.MainAPI -import com.lagradost.cloudstream3.mvvm.suspendSafeApiCall +import com.lagradost.cloudstream3.mvvm.safeAsync import com.lagradost.cloudstream3.syncproviders.SyncIdName object SyncRedirector { @@ -44,7 +44,7 @@ object SyncRedirector { return syncIds.firstNotNullOfOrNull { (syncName, syncRegex) -> if (providerApi.supportedSyncNames.contains(syncName)) { syncRegex.find(url)?.value?.let { - suspendSafeApiCall { + safeAsync { providerApi.getLoadUrl(syncName, it) } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 67197e3e6..2ff3cf002 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -1309,7 +1309,7 @@ abstract class ExtractorApi { var sourcePlugin: String? = null //suspend fun getSafeUrl(url: String, referer: String? = null): List? { - // return suspendSafeApiCall { getUrl(url, referer) } + // return safeAsync { getUrl(url, referer) } //} // this is the new extractorapi, override to add subtitles and stuff From 63aaba29934a3a6188ea1a7f2fb70f0664d31eed Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 26 Jun 2025 23:44:52 +0200 Subject: [PATCH 247/962] Chore: Replaced all instances of outdated mvvm apis (amap) --- .../lagradost/cloudstream3/MainActivity.kt | 8 +++-- .../cloudstream3/plugins/PluginManager.kt | 30 +++++++++---------- .../services/SubscriptionWorkManager.kt | 8 ++--- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 682e69e34..7f5c499ff 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -305,9 +305,11 @@ 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 == "$APP_STRING:") { - PluginManager.___DO_NOT_CALL_FROM_A_PLUGIN_hotReloadAllLocalPlugins( - activity - ) + ioSafe { + PluginManager.___DO_NOT_CALL_FROM_A_PLUGIN_hotReloadAllLocalPlugins( + activity + ) + } } } else if (safeURI(str)?.scheme == APP_STRING_REPO) { val url = str.replaceFirst(APP_STRING_REPO, "https") diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 6e59696ae..1cffa7c1b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -35,7 +35,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.actions.VideoClickActionHolder -import com.lagradost.cloudstream3.apmap +import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.mvvm.debugPrint import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safe @@ -265,7 +265,7 @@ object PluginManager { level = DeprecationLevel.ERROR ) @Throws - fun ___DO_NOT_CALL_FROM_A_PLUGIN_updateAllOnlinePluginsAndLoadThem(activity: Activity) { + suspend fun ___DO_NOT_CALL_FROM_A_PLUGIN_updateAllOnlinePluginsAndLoadThem(activity: Activity) { assertNonRecursiveCallstack() // Load all plugins as fast as possible! @@ -275,7 +275,7 @@ object PluginManager { val urls = (getKey>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES - val onlinePlugins = urls.toList().apmap { + val onlinePlugins = urls.toList().amap { getRepoPlugins(it.url)?.toList() ?: emptyList() }.flatten().distinctBy { it.second.url } @@ -296,7 +296,7 @@ object PluginManager { val updatedPlugins = mutableListOf() - outdatedPlugins.apmap { pluginData -> + outdatedPlugins.amap { pluginData -> if (pluginData.isDisabled) { //updatedPlugins.add(activity.getString(R.string.single_plugin_disabled, pluginData.onlineData.second.name)) unloadPlugin(pluginData.savedData.filePath) @@ -346,7 +346,7 @@ object PluginManager { level = DeprecationLevel.ERROR ) @Throws - fun ___DO_NOT_CALL_FROM_A_PLUGIN_downloadNotExistingPluginsAndLoad( + suspend fun ___DO_NOT_CALL_FROM_A_PLUGIN_downloadNotExistingPluginsAndLoad( activity: Activity, mode: AutoDownloadMode ) { @@ -355,7 +355,7 @@ object PluginManager { val newDownloadPlugins = mutableListOf() val urls = (getKey>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES - val onlinePlugins = urls.toList().apmap { + val onlinePlugins = urls.toList().amap { getRepoPlugins(it.url)?.toList() ?: emptyList() }.flatten().distinctBy { it.second.url } @@ -415,7 +415,7 @@ object PluginManager { } //Log.i(TAG, "notDownloadedPlugins => ${notDownloadedPlugins.toJson()}") - notDownloadedPlugins.apmap { pluginData -> + notDownloadedPlugins.amap { pluginData -> downloadPlugin( activity, pluginData.onlineData.second.url, @@ -460,11 +460,11 @@ object PluginManager { level = DeprecationLevel.ERROR ) @Throws - fun ___DO_NOT_CALL_FROM_A_PLUGIN_loadAllOnlinePlugins(context: Context) { + suspend fun ___DO_NOT_CALL_FROM_A_PLUGIN_loadAllOnlinePlugins(context: Context) { assertNonRecursiveCallstack() // Load all plugins as fast as possible! - (getPluginsOnline()).toList().apmap { pluginData -> + (getPluginsOnline()).toList().amap { pluginData -> loadPlugin( context, File(pluginData.filePath), @@ -486,7 +486,7 @@ object PluginManager { replaceWith = ReplaceWith("loadPlugin"), level = DeprecationLevel.ERROR ) - fun ___DO_NOT_CALL_FROM_A_PLUGIN_hotReloadAllLocalPlugins(activity: FragmentActivity?) { + suspend fun ___DO_NOT_CALL_FROM_A_PLUGIN_hotReloadAllLocalPlugins(activity: FragmentActivity?) { assertNonRecursiveCallstack() Log.d(TAG, "Reloading all local plugins!") @@ -511,7 +511,7 @@ object PluginManager { level = DeprecationLevel.ERROR ) @Throws - fun ___DO_NOT_CALL_FROM_A_PLUGIN_loadAllLocalPlugins(context: Context, forceReload: Boolean) { + suspend fun ___DO_NOT_CALL_FROM_A_PLUGIN_loadAllLocalPlugins(context: Context, forceReload: Boolean) { assertNonRecursiveCallstack() val dir = File(LOCAL_PLUGINS_PATH) @@ -540,7 +540,7 @@ object PluginManager { // Make sure all local plugins are fully refreshed. removeKey(PLUGINS_KEY_LOCAL) - sortedPlugins?.sortedBy { it.name }?.apmap { file -> + sortedPlugins?.sortedBy { it.name }?.amap { file -> try { val destinationFile = File(pluginDirectory, file.name) @@ -815,7 +815,7 @@ object PluginManager { replaceWith = ReplaceWith("loadPlugin"), level = DeprecationLevel.ERROR ) - fun ___DO_NOT_CALL_FROM_A_PLUGIN_manuallyReloadAndUpdatePlugins(activity: Activity) { + suspend fun ___DO_NOT_CALL_FROM_A_PLUGIN_manuallyReloadAndUpdatePlugins(activity: Activity) { assertNonRecursiveCallstack() showToast(activity.getString(R.string.starting_plugin_update_manually), Toast.LENGTH_LONG) @@ -825,7 +825,7 @@ object PluginManager { val urls = (getKey>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES - val onlinePlugins = urls.toList().apmap { + val onlinePlugins = urls.toList().amap { getRepoPlugins(it.url)?.toList() ?: emptyList() }.flatten().distinctBy { it.second.url } @@ -839,7 +839,7 @@ object PluginManager { val updatedPlugins = mutableListOf() - allPlugins.apmap { pluginData -> + allPlugins.amap { pluginData -> if (pluginData.isDisabled) { Log.e( "PluginManager", diff --git a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt b/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt index c30d1208b..fc31c1f3e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/services/SubscriptionWorkManager.kt @@ -131,15 +131,15 @@ class SubscriptionWorkManager(val context: Context, workerParams: WorkerParamete PluginManager.___DO_NOT_CALL_FROM_A_PLUGIN_loadAllOnlinePlugins(context) PluginManager.___DO_NOT_CALL_FROM_A_PLUGIN_loadAllLocalPlugins(context, false) - subscriptions.apmap { savedData -> + subscriptions.amap { savedData -> try { - val id = savedData.id ?: return@apmap null - val api = getApiFromNameNull(savedData.apiName) ?: return@apmap null + val id = savedData.id ?: return@amap null + val api = getApiFromNameNull(savedData.apiName) ?: return@amap null // Reasonable timeout to prevent having this worker run forever. val response = withTimeoutOrNull(60_000) { api.load(savedData.url) as? EpisodeResponse - } ?: return@apmap null + } ?: return@amap null val dubPreference = getDub(id) ?: if ( From 6609a11a23fce164f07218dca94b4aa1cd3d5345 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 27 Jun 2025 00:03:57 +0200 Subject: [PATCH 248/962] Feat: Minor change to skip loading to show how many links are loaded --- .../cloudstream3/ui/player/GeneratorPlayer.kt | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 0eb28e5b5..e24fc4ec3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -1939,6 +1939,7 @@ class GeneratorPlayer : FullScreenPlayer() { } } + @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) var langFilterList = listOf() @@ -2022,7 +2023,16 @@ class GeneratorPlayer : FullScreenPlayer() { currentLinks = it val turnVisible = it.isNotEmpty() && lastUsedGenerator?.canSkipLoading == true val wasGone = binding?.overlayLoadingSkipButton?.isGone == true - binding?.overlayLoadingSkipButton?.isVisible = turnVisible + + binding?.overlayLoadingSkipButton?.apply { + isVisible = turnVisible + val value = viewModel.currentLinks.value + if (value.isNullOrEmpty()) { + setText(R.string.skip_loading) + } else { + text = "${context.getString(R.string.skip_loading)} (${value.size})" + } + } safe { if (currentLinks.any { link -> From 76c80e2c89a0ec9b36ae68408eb1277620a8bf73 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 27 Jun 2025 18:14:44 +0200 Subject: [PATCH 249/962] Feat: Added skip loading for acquire links, and updated UI for the dialog --- .../ui/result/ResultFragmentPhone.kt | 38 +++++---- .../ui/result/ResultFragmentTv.kt | 17 +++- .../ui/result/ResultViewModel2.kt | 58 +++++++++----- app/src/main/res/layout/bottom_loading.xml | 80 +++++++++++++------ 4 files changed, 130 insertions(+), 63 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt index bd830aa2b..3d992d873 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentPhone.kt @@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.ui.result import android.annotation.SuppressLint import android.app.Dialog -import android.content.Context import android.content.Intent import android.content.res.ColorStateList import android.graphics.Rect @@ -19,6 +18,7 @@ import android.widget.AbsListView import android.widget.ArrayAdapter import android.widget.Toast import androidx.core.view.isGone +import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView import androidx.core.widget.doOnTextChanged @@ -30,6 +30,7 @@ import com.google.android.gms.cast.framework.CastButtonFactory import com.google.android.gms.cast.framework.CastContext import com.google.android.gms.cast.framework.CastState import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.DubStatus @@ -37,16 +38,15 @@ import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse -import com.lagradost.cloudstream3.databinding.BottomSelectionDialogBinding import com.lagradost.cloudstream3.databinding.FragmentResultBinding import com.lagradost.cloudstream3.databinding.FragmentResultSwipeBinding import com.lagradost.cloudstream3.databinding.ResultRecommendationsBinding import com.lagradost.cloudstream3.databinding.ResultSyncBinding import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError -import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.services.SubscriptionWorkManager import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD @@ -1072,20 +1072,27 @@ open class ResultFragmentPhone : FullScreenPlayer() { loadingDialog = null } loadingDialog = loadingDialog ?: context?.let { ctx -> - val builder = - BottomSheetDialog(ctx) + val builder = BottomSheetDialog(ctx) builder.setContentView(R.layout.bottom_loading) builder.setOnDismissListener { loadingDialog = null viewModel.cancelLinks() } - //builder.setOnCancelListener { - // it?.dismiss() - //} builder.setCanceledOnTouchOutside(true) builder.show() builder } + loadingDialog?.findViewById(R.id.overlay_loading_skip_button)?.apply { + if (load.linksLoaded <= 0) { + isInvisible = true + } else { + setOnClickListener { + viewModel.skipLoading() + } + isVisible = true + text = "${context.getString(R.string.skip_loading)} (${load.linksLoaded})" + } + } } observeNullable(viewModel.selectedSeason) { text -> @@ -1127,13 +1134,14 @@ open class ResultFragmentPhone : FullScreenPlayer() { observe(viewModel.dubSubSelections) { range -> resultBinding?.resultDubSelect?.setOnClickListener { view -> view?.context?.let { ctx -> - view.popupMenuNoIconsAndNoStringRes(range - .mapNotNull { (text, status) -> - Pair( - status.ordinal, - text?.asStringNull(ctx) ?: return@mapNotNull null - ) - }) { + view.popupMenuNoIconsAndNoStringRes( + range + .mapNotNull { (text, status) -> + Pair( + status.ordinal, + text?.asStringNull(ctx) ?: return@mapNotNull null + ) + }) { viewModel.changeDubStatus(DubStatus.entries[itemId]) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index a91f513e6..ebbe0cd8f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -11,12 +11,14 @@ import android.view.animation.DecelerateInterpolator import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone +import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.core.widget.NestedScrollView import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.LoadResponse @@ -753,14 +755,21 @@ class ResultFragmentTv : Fragment() { loadingDialog = null viewModel.cancelLinks() } - //builder.setOnCancelListener { - // it?.dismiss() - //} builder.setCanceledOnTouchOutside(true) builder.show() builder } - + loadingDialog?.findViewById(R.id.overlay_loading_skip_button)?.apply { + if (load.linksLoaded <= 0) { + isInvisible = true + } else { + setOnClickListener { + viewModel.skipLoading() + } + isVisible = true + text = "${context.getString(R.string.skip_loading)} (${load.linksLoaded})" + } + } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 0e7e1fdb6..bf31969f3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -235,9 +235,9 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { R.string.cast_format, actors?.joinToString { it.actor.name }), plotText = - if (plot.isNullOrBlank()) txt(if (this is TorrentLoadResponse) R.string.torrent_no_plot else R.string.normal_no_plot) else txt( - plot!! - ), + if (plot.isNullOrBlank()) txt(if (this is TorrentLoadResponse) R.string.torrent_no_plot else R.string.normal_no_plot) else txt( + plot!! + ), backgroundPosterUrl = backgroundPosterUrl, title = name, typeText = txt( @@ -274,7 +274,7 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { } ), metaText = - if (repo.providerType == ProviderType.MetaProvider) txt(R.string.provider_info_meta) else null, + if (repo.providerType == ProviderType.MetaProvider) txt(R.string.provider_info_meta) else null, durationText = if (dur == null || dur <= 0) null else txt( secondsToReadable(dur * 60, "0 mins") ), @@ -288,9 +288,9 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData { ) } else null, noEpisodesFoundText = - if ((this is TvSeriesLoadResponse && this.episodes.isEmpty()) || (this is AnimeLoadResponse && !this.episodes.any { it.value.isNotEmpty() })) txt( - R.string.no_episodes_found - ) else null + if ((this is TvSeriesLoadResponse && this.episodes.isEmpty()) || (this is AnimeLoadResponse && !this.episodes.any { it.value.isNotEmpty() })) txt( + R.string.no_episodes_found + ) else null ) } @@ -1306,15 +1306,22 @@ class ResultViewModel2 : ViewModel() { ) { currentLoadLinkJob?.cancel() currentLoadLinkJob = ioSafe { - val links = loadLinks( - result, - isVisible = isVisible, - sourceTypes = sourceTypes, - clearCache = clearCache, - isCasting = isCasting - ) - if (!this.isActive) return@ioSafe - work(links) + val parentJob = this.coroutineContext.job + launch { + val links = loadLinks( + result, + isVisible = isVisible, + sourceTypes = sourceTypes, + clearCache = clearCache, + isCasting = isCasting + ) + // Cancel child = skip link loading + // Cancel parent = dismiss dialog + if (parentJob.isCancelled) { + return@launch + } + work(links) + } } } @@ -1358,6 +1365,11 @@ class ResultViewModel2 : ViewModel() { } } + fun skipLoading() { + currentLoadLinkJob?.cancelChildren() + currentLoadLinkJob = null + } + private suspend fun CoroutineScope.loadLinks( result: ResultEpisode, isVisible: Boolean, @@ -1391,6 +1403,8 @@ class ResultViewModel2 : ViewModel() { }, isCasting = isCasting ) + } catch (e : CancellationException) { + // Do nothing } catch (e: Exception) { logError(e) } finally { @@ -1840,7 +1854,11 @@ class ResultViewModel2 : ViewModel() { } fun changeDubStatus(status: DubStatus) { - postEpisodeRange(currentIndex?.copy(dubStatus = status), currentRange, currentSorting ?: DataStoreHelper.resultsSortingMode) + postEpisodeRange( + currentIndex?.copy(dubStatus = status), + currentRange, + currentSorting ?: DataStoreHelper.resultsSortingMode + ) } fun changeRange(range: EpisodeRange) { @@ -1848,7 +1866,11 @@ class ResultViewModel2 : ViewModel() { } fun changeSeason(season: Int) { - postEpisodeRange(currentIndex?.copy(season = season), currentRange, currentSorting ?: DataStoreHelper.resultsSortingMode) + postEpisodeRange( + currentIndex?.copy(season = season), + currentRange, + currentSorting ?: DataStoreHelper.resultsSortingMode + ) } fun setSort(sortType: EpisodeSortType) { diff --git a/app/src/main/res/layout/bottom_loading.xml b/app/src/main/res/layout/bottom_loading.xml index ab05889d0..1637aa5ad 100644 --- a/app/src/main/res/layout/bottom_loading.xml +++ b/app/src/main/res/layout/bottom_loading.xml @@ -1,33 +1,61 @@ + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical"> - + + + android:layout_height="wrap_content" + android:orientation="vertical"> + + + + + + + + + + + + + + + + - + android:id="@+id/progressBar" + style="@android:style/Widget.Material.ProgressBar.Horizontal" + android:layout_width="match_parent" + android:layout_height="15dp" + android:layout_gravity="center" + android:layout_marginBottom="-6.5dp" + android:indeterminate="true" + android:indeterminateTint="?attr/colorPrimary" + android:progressTint="?attr/colorPrimary" + android:visibility="gone" /> From 5cbbc1bd123147817c393b8b2f7aae27afa8a186 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 27 Jun 2025 19:37:39 +0200 Subject: [PATCH 250/962] Fix(TV UI): Made focus in settings highlight with border for consistency and reliability --- .../lagradost/cloudstream3/CommonActivity.kt | 44 +++--- .../ui/settings/SettingsAccount.kt | 10 +- .../cloudstream3/ui/settings/SettingsUI.kt | 3 + .../custom_preference_category_material.xml | 64 +++++++++ .../res/layout/custom_preference_material.xml | 76 ++++++++++ .../custom_preference_widget_seekbar.xml | 131 ++++++++++++++++++ app/src/main/res/values/attrs.xml | 4 + app/src/main/res/values/styles.xml | 30 +++- app/src/main/res/xml/settings_account.xml | 2 +- 9 files changed, 339 insertions(+), 25 deletions(-) create mode 100644 app/src/main/res/layout/custom_preference_category_material.xml create mode 100644 app/src/main/res/layout/custom_preference_material.xml create mode 100644 app/src/main/res/layout/custom_preference_widget_seekbar.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 8fd652b9c..9a8e274f5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -36,6 +36,8 @@ import com.lagradost.cloudstream3.databinding.ToastBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.player.PlayerEventType import com.lagradost.cloudstream3.ui.player.Torrent +import com.lagradost.cloudstream3.ui.settings.Globals.TV +import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.UiText import com.lagradost.cloudstream3.ui.settings.Globals.updateTv import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl @@ -167,7 +169,8 @@ object CommonActivity { toast.duration = duration ?: Toast.LENGTH_SHORT toast.setGravity(Gravity.CENTER_HORIZONTAL or Gravity.BOTTOM, 0, 5.toPx) @Suppress("DEPRECATION") - 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. + 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() @@ -196,7 +199,10 @@ object CommonActivity { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.createConfigurationContext(config) @Suppress("DEPRECATION") - resources.updateConfiguration(config, resources.displayMetrics) // FIXME this should be replaced + resources.updateConfiguration( + config, + resources.displayMetrics + ) // FIXME this should be replaced } fun Context.updateLocale() { @@ -222,16 +228,19 @@ object CommonActivity { componentActivity.updateTv() NewPipe.init(DownloaderTestImpl.getInstance()) - MainActivity.activityResultLauncher = componentActivity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> - if (result.resultCode == AppCompatActivity.RESULT_OK) { - val actionUid = getKey("last_click_action") ?: return@registerForActivityResult - Log.d(TAG, "Loading action $actionUid result handler") - val action = VideoClickActionHolder.getByUniqueId(actionUid) as? OpenInAppAction ?: return@registerForActivityResult - action.onResultSafe(act, result.data) - removeKey("last_click_action") - removeKey("last_opened_id") + MainActivity.activityResultLauncher = + componentActivity.registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> + if (result.resultCode == AppCompatActivity.RESULT_OK) { + val actionUid = + getKey("last_click_action") ?: return@registerForActivityResult + Log.d(TAG, "Loading action $actionUid result handler") + val action = VideoClickActionHolder.getByUniqueId(actionUid) as? OpenInAppAction + ?: return@registerForActivityResult + action.onResultSafe(act, result.data) + removeKey("last_click_action") + removeKey("last_opened_id") + } } - } // Ask for notification permissions on Android 13 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU && @@ -282,8 +291,9 @@ object CommonActivity { fun updateTheme(act: Activity) { val settingsManager = PreferenceManager.getDefaultSharedPreferences(act) if (settingsManager - .getString(act.getString(R.string.app_theme_key), "AmoledLight") == "System" - && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + .getString(act.getString(R.string.app_theme_key), "AmoledLight") == "System" + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q + ) { loadThemes(act) } } @@ -347,9 +357,11 @@ object CommonActivity { else -> R.style.OverlayPrimaryColorNormal } + act.theme.applyStyle(currentTheme, true) act.theme.applyStyle(currentOverlayTheme, true) - + act.updateTv() + if (isLayout(TV)) act.theme.applyStyle(R.style.AppThemeTvOverlay, true) act.theme.applyStyle( R.style.LoadedStyle, true @@ -489,7 +501,7 @@ object CommonActivity { } - fun onKeyDown(act: Activity?, keyCode: Int, event: KeyEvent?) : Boolean? { + fun onKeyDown(act: Activity?, keyCode: Int, event: KeyEvent?): Boolean? { // 149 keycode_numpad 5 val playerEvent = when (keyCode) { @@ -560,7 +572,7 @@ object CommonActivity { else -> return null } val listener = playerEventListener - if(listener != null) { + if (listener != null) { listener.invoke(playerEvent) return true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt index daeab0500..ebcc61b67 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt @@ -9,12 +9,13 @@ import android.view.inputmethod.EditorInfo import android.widget.TextView import androidx.annotation.UiThread import androidx.appcompat.app.AlertDialog +import androidx.core.content.edit import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.FragmentActivity import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager -import androidx.preference.SwitchPreferenceCompat +import androidx.preference.SwitchPreference import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent @@ -366,9 +367,10 @@ class SettingsAccount : PreferenceFragmentCompat(), BiometricCallback { private fun updateAuthPreference(enabled: Boolean) { val biometricKey = getString(R.string.biometric_key) - PreferenceManager.getDefaultSharedPreferences(context ?: return).edit() - .putBoolean(biometricKey, enabled).apply() - findPreference(biometricKey)?.isChecked = enabled + PreferenceManager.getDefaultSharedPreferences(context ?: return).edit { + putBoolean(biometricKey, enabled) + } + findPreference(biometricKey)?.isChecked = enabled } override fun onAuthenticationError() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt index 98db81e6a..6446ae75d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt @@ -14,6 +14,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.search.SearchResultBuilder 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.updateTv import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.hideOn @@ -39,6 +40,8 @@ class SettingsUI : PreferenceFragmentCompat() { setPreferencesFromResource(R.xml.settings_ui, rootKey) val settingsManager = PreferenceManager.getDefaultSharedPreferences(requireContext()) + getPref(R.string.random_button_key)?.hideOn(EMULATOR or TV) + (getPref(R.string.overscan_key)?.hideOn(PHONE or EMULATOR) as? SeekBarPreference)?.setOnPreferenceChangeListener { perf, newValue -> val padding = (newValue as? Int)?.toPx ?: return@setOnPreferenceChangeListener true (perf.context.getActivity() as? MainActivity)?.binding?.homeRoot?.setPadding(padding, padding, padding, padding) diff --git a/app/src/main/res/layout/custom_preference_category_material.xml b/app/src/main/res/layout/custom_preference_category_material.xml new file mode 100644 index 000000000..06db99017 --- /dev/null +++ b/app/src/main/res/layout/custom_preference_category_material.xml @@ -0,0 +1,64 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/custom_preference_material.xml b/app/src/main/res/layout/custom_preference_material.xml new file mode 100644 index 000000000..0ab98c22b --- /dev/null +++ b/app/src/main/res/layout/custom_preference_material.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/custom_preference_widget_seekbar.xml b/app/src/main/res/layout/custom_preference_widget_seekbar.xml new file mode 100644 index 000000000..02c5ec1be --- /dev/null +++ b/app/src/main/res/layout/custom_preference_widget_seekbar.xml @@ -0,0 +1,131 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml index 7b90beb0c..c7af48e08 100644 --- a/app/src/main/res/values/attrs.xml +++ b/app/src/main/res/values/attrs.xml @@ -13,6 +13,10 @@ ?attr/colorPrimary + + + + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 7ff14f8c6..05d87f1ce 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -67,16 +67,19 @@ @color/iconColor @color/white @color/black - + ?android:attr/selectableItemBackground @style/CustomPreferenceThemeOverlay - ?attr/white ?attr/white ?attr/white 1.0 + + + + + + + + + + + + + + + + + + + ?attr/textColor + 10sp + + @drawable/kid_star_24px + + + + + + + + + From c783e82c80a1f8cf7577db4e29cc44b64e5b3edf Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:06:24 +0200 Subject: [PATCH 378/962] Fix: Minor typo + testing fix --- .../java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt | 2 +- app/src/main/res/layout/fragment_result_tv.xml | 2 +- app/src/test/java/com/lagradost/cloudstream3/ProviderTests.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt index 3d5367fc6..4c5cdea5b 100644 --- a/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt +++ b/app/src/androidTest/java/com/lagradost/cloudstream3/ExampleInstrumentedTest.kt @@ -143,7 +143,7 @@ class ExampleInstrumentedTest { Assert.assertTrue("Api does not contain a name", api.name != "NONE") Assert.assertTrue( "Api ${api.name} does not contain a valid language code", - langTagsIETF.contains(api.lang, ignoreCase = true) + langTagsIETF.contains(api.lang) ) Assert.assertTrue( "Api ${api.name} does not contain any supported types", diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 461a712d9..dbebecfcc 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -285,7 +285,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_gravity="center_vertical" tools:text="69m\nremaining" /> - e + Date: Thu, 2 Oct 2025 19:16:13 +0200 Subject: [PATCH 379/962] Chore: Cleanup unused xml files --- app/src/main/res/layout/player_episodes.xml | 8 -- .../main/res/layout/player_episodes_large.xml | 105 ---------------- .../main/res/layout/player_episodes_small.xml | 56 --------- .../res/layout/result_episode_both_old.xml | 14 --- .../res/layout/result_episode_both_tv_old.xml | 21 ---- .../layout/result_episode_large_tv_old.xml | 112 ------------------ .../main/res/layout/result_episode_tv_old.xml | 59 --------- 7 files changed, 375 deletions(-) delete mode 100644 app/src/main/res/layout/player_episodes.xml delete mode 100644 app/src/main/res/layout/player_episodes_large.xml delete mode 100644 app/src/main/res/layout/player_episodes_small.xml delete mode 100644 app/src/main/res/layout/result_episode_both_old.xml delete mode 100644 app/src/main/res/layout/result_episode_both_tv_old.xml delete mode 100644 app/src/main/res/layout/result_episode_large_tv_old.xml delete mode 100644 app/src/main/res/layout/result_episode_tv_old.xml diff --git a/app/src/main/res/layout/player_episodes.xml b/app/src/main/res/layout/player_episodes.xml deleted file mode 100644 index a491bc096..000000000 --- a/app/src/main/res/layout/player_episodes.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/player_episodes_large.xml b/app/src/main/res/layout/player_episodes_large.xml deleted file mode 100644 index 476965635..000000000 --- a/app/src/main/res/layout/player_episodes_large.xml +++ /dev/null @@ -1,105 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/player_episodes_small.xml b/app/src/main/res/layout/player_episodes_small.xml deleted file mode 100644 index 62dd4bca1..000000000 --- a/app/src/main/res/layout/player_episodes_small.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/result_episode_both_old.xml b/app/src/main/res/layout/result_episode_both_old.xml deleted file mode 100644 index 6472ecc10..000000000 --- a/app/src/main/res/layout/result_episode_both_old.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/result_episode_both_tv_old.xml b/app/src/main/res/layout/result_episode_both_tv_old.xml deleted file mode 100644 index f273a1180..000000000 --- a/app/src/main/res/layout/result_episode_both_tv_old.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/result_episode_large_tv_old.xml b/app/src/main/res/layout/result_episode_large_tv_old.xml deleted file mode 100644 index 3a7cef3ca..000000000 --- a/app/src/main/res/layout/result_episode_large_tv_old.xml +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/result_episode_tv_old.xml b/app/src/main/res/layout/result_episode_tv_old.xml deleted file mode 100644 index 62546cf94..000000000 --- a/app/src/main/res/layout/result_episode_tv_old.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file From 373d746a3f0baeba695c6496a300f90fa5e404a4 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Thu, 2 Oct 2025 23:04:48 +0530 Subject: [PATCH 380/962] feat(library):scroll top on sort (#1938) --- .../com/lagradost/cloudstream3/ui/library/LibraryFragment.kt | 1 + .../com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index bfac72067..7555c2aca 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -416,6 +416,7 @@ class LibraryFragment : Fragment() { libraryViewModel.currentPage.value?.let { page -> binding?.viewpager?.setCurrentItem(page, false) + binding?.searchBar?.setExpanded(true) } updateRandom() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt index 0110187f6..392bf9cb3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/ViewpagerAdapter.kt @@ -58,7 +58,6 @@ class ViewpagerAdapter( LibraryViewpagerPageBinding.inflate(LayoutInflater.from(parent.context), parent, false) ) } - override fun onUpdateContent( holder: ViewHolderState, item: SyncAPI.Page, @@ -67,6 +66,7 @@ class ViewpagerAdapter( val binding = holder.view if (binding !is LibraryViewpagerPageBinding) return (binding.pageRecyclerview.adapter as? PageAdapter)?.updateList(item.items) + binding.pageRecyclerview.scrollToPosition(0) } override fun onBindContent(holder: ViewHolderState, item: SyncAPI.Page, position: Int) { From 6d4c530e16d90ee946efa4ce6599efe4140e0760 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:52:08 +0200 Subject: [PATCH 381/962] Chore: Bump media3 to 1.8.0 --- .../cloudstream3/ui/player/UpdatedMatroskaExtractor.kt | 2 +- gradle/libs.versions.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/UpdatedMatroskaExtractor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/UpdatedMatroskaExtractor.kt index 06b4c12c3..016e7d203 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/UpdatedMatroskaExtractor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/UpdatedMatroskaExtractor.kt @@ -41,7 +41,7 @@ import androidx.media3.container.NalUnitUtil import androidx.media3.extractor.AacUtil import androidx.media3.extractor.AvcConfig import androidx.media3.extractor.ChunkIndex -import androidx.media3.extractor.DolbyVisionConfig +import androidx.media3.container.DolbyVisionConfig import androidx.media3.extractor.Extractor import androidx.media3.extractor.ExtractorInput import androidx.media3.extractor.ExtractorOutput diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7a3fd9165..987ad3cd6 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,7 @@ kotlinxCoroutinesCore = "1.10.1" lifecycleLivedataKtx = "2.8.7" lifecycleViewmodelKtx = "2.8.7" material = "1.12.0" -media3 = "1.6.1" +media3 = "1.8.0" navigationKtx = "2.8.9" newpipeextractor = "v0.24.6" nextlibMedia3 = "0.8.4" From 26570d688fce0cc4d8c51c32135dfa7575b1b476 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 2 Oct 2025 19:54:54 +0200 Subject: [PATCH 382/962] Chore: Bump versionName --- app/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 080f9dd2b..b40b19f20 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -63,7 +63,7 @@ android { minSdk = libs.versions.minSdk.get().toInt() targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 66 - versionName = "4.5.5" + versionName = "4.5.6" resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "commit_hash", getGitCommitHash()) From ccf3b0508841782e7db7e2530b01ba1ffc72dd9b Mon Sep 17 00:00:00 2001 From: Phisher98 <153359846+phisher98@users.noreply.github.com> Date: Fri, 3 Oct 2025 21:05:44 +0530 Subject: [PATCH 383/962] Dailymotion Fix (#1947) * Dailymotion Improvement * Dailymotion Improvement Minor Fix --- .../cloudstream3/extractors/Dailymotion.kt | 51 +++++++++++++------ 1 file changed, 36 insertions(+), 15 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt index aa5e60c32..f15273c37 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Dailymotion.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.extractors +import com.google.gson.Gson import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi @@ -8,6 +9,7 @@ import com.lagradost.cloudstream3.utils.M3u8Helper.Companion.generateM3u8 import java.net.URI + class Geodailymotion : Dailymotion() { override val name = "GeoDailymotion" override val mainUrl = "https://geo.dailymotion.com" @@ -30,29 +32,26 @@ open class Dailymotion : ExtractorApi() { val embedUrl = getEmbedUrl(url) ?: return val id = getVideoId(embedUrl) ?: return val metaDataUrl = "$baseUrl/player/metadata/video/$id" + val response = app.get(metaDataUrl, referer = embedUrl).text - val qualityUrlRegex = Regex(""""url"\s*:\s*"([^"]+)"""") - val subtitlesRegex = Regex(""""subtitles"\s*:\s*\{[^}]*"data"\s*:\s*(\[[^\]]*\])""") + val gson = Gson() + val meta = gson.fromJson(response, MetaData::class.java) - val urls = qualityUrlRegex.findAll(response) - .map { it.groupValues[1] } - .toList().filter { it.contains(".m3u8") } - - urls.forEach { videoUrl -> - getStream(videoUrl, this.name, callback) + meta.qualities?.get("auto")?.forEach { quality -> + val videoUrl = quality.url + if (!videoUrl.isNullOrEmpty() && videoUrl.contains(".m3u8")) { + getStream(videoUrl, this.name, callback) + } } - val subtitlesMatches = subtitlesRegex.findAll(response).map { it.groupValues[1] }.toList() - subtitlesMatches.forEach { subtitleJson -> - val subRegex = Regex("""\{\s*"label"\s*:\s*"([^"]+)",\s*"urls"\s*:\s*\["([^"]+)"""") - subRegex.findAll(subtitleJson).forEach { match -> - val label = match.groupValues[1] - val subUrl = match.groupValues[2] - subtitleCallback(SubtitleFile(label, subUrl)) + meta.subtitles?.data?.forEach { (_, subData) -> + subData.urls.forEach { subUrl -> + subtitleCallback(SubtitleFile(subData.label, subUrl)) } } } + private fun getEmbedUrl(url: String): String? { if (url.contains("/embed/") || url.contains("/video/")) return url if (url.contains("geo.dailymotion.com")) { @@ -76,4 +75,26 @@ open class Dailymotion : ExtractorApi() { ) { return generateM3u8(name, streamLink, "").forEach(callback) } + + + data class MetaData( + val qualities: Map>?, + val subtitles: SubtitlesWrapper? + ) + + data class Quality( + val type: String?, + val url: String? + ) + + data class SubtitlesWrapper( + val enable: Boolean, + val data: Map? + ) + + data class SubtitleData( + val label: String, + val urls: List + ) + } \ No newline at end of file From 6632f764b0cdb53a7e843057916cf524fa862cf7 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 3 Oct 2025 22:22:39 +0200 Subject: [PATCH 384/962] Feat: New TV UI for Homepage (#1942) --- .../lagradost/cloudstream3/MainActivity.kt | 41 ++- .../ui/home/HomeParentItemAdapterPreview.kt | 66 ++-- app/src/main/res/layout/activity_main_tv.xml | 9 +- .../main/res/layout/fragment_home_head_tv.xml | 347 +++++++++--------- app/src/main/res/layout/fragment_home_tv.xml | 2 +- app/src/main/res/layout/rail_header.xml | 2 +- app/src/main/res/values/dimens.xml | 2 +- 7 files changed, 244 insertions(+), 225 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index c8d9d6a8b..4a3886d50 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -404,6 +404,24 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa } return false } + + + fun centerView(view: View?) { + if (view == null) return + try { + Log.v(TAG, "centerView: $view") + val r = Rect(0, 0, 0, 0) + view.getDrawingRect(r) + val x = r.centerX() + val y = r.centerY() + val dx = r.width() / 2 //screenWidth / 2 + val dy = screenHeight / 2 + val r2 = Rect(x - dx, y - dy, x + dx, y + dy) + view.requestRectangleOnScreen(r2, false) + // TvFocus.current =TvFocus.current.copy(y=y.toFloat()) + } catch (_: Throwable) { + } + } } @@ -484,7 +502,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa ).contains(destination.id) - val dontPush = listOf( + /*val dontPush = listOf( R.id.navigation_home, R.id.navigation_search, R.id.navigation_results_phone, @@ -515,7 +533,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa } layoutParams = params - } + }*/ val landscape = when (resources.configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> { @@ -1140,23 +1158,6 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa } } - private fun centerView(view: View?) { - if (view == null) return - try { - Log.v(TAG, "centerView: $view") - val r = Rect(0, 0, 0, 0) - view.getDrawingRect(r) - val x = r.centerX() - val y = r.centerY() - val dx = r.width() / 2 //screenWidth / 2 - val dy = screenHeight / 2 - val r2 = Rect(x - dx, y - dy, x + dx, y + dy) - view.requestRectangleOnScreen(r2, false) - // TvFocus.current =TvFocus.current.copy(y=y.toFloat()) - } catch (_: Throwable) { - } - } - @Suppress("DEPRECATION_ERROR") override fun onCreate(savedInstanceState: Bundle?) { app.initClient(this) @@ -1228,7 +1229,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa if (isLayout(TV)) { // Put here any button you don't want focusing it to center the view val exceptionButtons = listOf( - R.id.home_preview_play_btt, + //R.id.home_preview_play_btt, R.id.home_preview_info_btt, R.id.home_preview_hidden_next_focus, R.id.home_preview_hidden_prev_focus, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index dd2bf7bbc..58167e0c2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -108,7 +108,7 @@ class HomeParentItemAdapterPreview( ) } - return HeaderViewHolder(binding, viewModel,accountViewModel, fragment = fragment) + return HeaderViewHolder(binding, viewModel, accountViewModel, fragment = fragment) } override fun onBindHeader(holder: ViewHolderState) { @@ -116,7 +116,10 @@ class HomeParentItemAdapterPreview( } private class HeaderViewHolder( - val binding: ViewBinding, val viewModel: HomeViewModel,accountViewModel: AccountViewModel, fragment: Fragment, + val binding: ViewBinding, + val viewModel: HomeViewModel, + accountViewModel: AccountViewModel, + fragment: Fragment, ) : ViewHolderState(binding) { @@ -305,10 +308,13 @@ class HomeParentItemAdapterPreview( itemView.findViewById(R.id.home_bookmarked_child_recyclerview) private val headProfilePic: ImageView? = itemView.findViewById(R.id.home_head_profile_pic) - private val headProfilePicCard: View? = itemView.findViewById(R.id.home_head_profile_padding) + private val headProfilePicCard: View? = + itemView.findViewById(R.id.home_head_profile_padding) - private val alternateHeadProfilePic: ImageView? = itemView.findViewById(R.id.alternate_home_head_profile_pic) - private val alternateHeadProfilePicCard: View? = itemView.findViewById(R.id.alternate_home_head_profile_padding) + private val alternateHeadProfilePic: ImageView? = + itemView.findViewById(R.id.alternate_home_head_profile_pic) + private val alternateHeadProfilePicCard: View? = + itemView.findViewById(R.id.alternate_home_head_profile_padding) private val topPadding: View? = itemView.findViewById(R.id.home_padding) @@ -334,7 +340,7 @@ class HomeParentItemAdapterPreview( homePreviewTags.isGone = item.tags.isNullOrEmpty() - homePreviewPlayBtt.setOnClickListener { view -> + /*homePreviewPlayBtt.setOnClickListener { view -> viewModel.click( LoadClickCallback( START_ACTION_RESUME_LATEST, @@ -343,7 +349,7 @@ class HomeParentItemAdapterPreview( item ) ) - } + }*/ homePreviewInfoBtt.setOnClickListener { view -> viewModel.click( @@ -494,7 +500,7 @@ class HomeParentItemAdapterPreview( activity?.showAccountSelectLinear() } - fun showAccountEditBox(context:Context): Boolean { + fun showAccountEditBox(context: Context): Boolean { val currentAccount = DataStoreHelper.getCurrentAccount() return if (currentAccount != null) { showAccountEditDialog( @@ -502,16 +508,21 @@ class HomeParentItemAdapterPreview( account = currentAccount, isNewAccount = false, accountEditCallback = { accountViewModel.handleAccountUpdate(it, context) }, - accountDeleteCallback = { accountViewModel.handleAccountDelete(it, context) } + accountDeleteCallback = { + accountViewModel.handleAccountDelete( + it, + context + ) + } ) true - }else false + } else false } - alternateHeadProfilePicCard?.setOnLongClickListener{ + alternateHeadProfilePicCard?.setOnLongClickListener { showAccountEditBox(it.context) } - headProfilePicCard?.setOnLongClickListener{ + headProfilePicCard?.setOnLongClickListener { showAccountEditBox(it.context) } @@ -525,8 +536,12 @@ class HomeParentItemAdapterPreview( viewModel.loadAndCancel(api, forceReload = true, fromUI = true) } } - homePreviewReloadProvider.setOnClickListener{ - viewModel.loadAndCancel(viewModel.apiName.value ?: noneApi.name, forceReload = true, fromUI = true) + homePreviewReloadProvider.setOnClickListener { + viewModel.loadAndCancel( + viewModel.apiName.value ?: noneApi.name, + forceReload = true, + fromUI = true + ) showToast(R.string.action_reload, Toast.LENGTH_SHORT) true } @@ -535,14 +550,18 @@ class HomeParentItemAdapterPreview( viewModel.queryTextSubmit("") } - // This makes the hidden next buttons only available when on the info button - // Otherwise you might be able to go to the next item without being at the info button - homePreviewInfoBtt.setOnFocusChangeListener { _, hasFocus -> - homePreviewHiddenNextFocus.isFocusable = hasFocus - } - - homePreviewPlayBtt.setOnFocusChangeListener { _, hasFocus -> - homePreviewHiddenPrevFocus.isFocusable = hasFocus + // A workaround to the focus problem of always centering the view on focus + // as that causes higher android versions to stretch the ui when switching between shows + var lastFocusTimeoutMs = 0L + homePreviewInfoBtt.setOnFocusChangeListener { view, hasFocus -> + val lastFocusMs = lastFocusTimeoutMs + // Always reset timer, as we only want to update + // it if we have not interacted in half a second + lastFocusTimeoutMs = System.currentTimeMillis() + if (!hasFocus) return@setOnFocusChangeListener + if (lastFocusMs + 500L < System.currentTimeMillis()) { + MainActivity.centerView(view) + } } homePreviewHiddenNextFocus.setOnFocusChangeListener { _, hasFocus -> @@ -560,7 +579,8 @@ class HomeParentItemAdapterPreview( )?.requestFocus() } else { previewViewpager.setCurrentItem(previewViewpager.currentItem - 1, true) - binding.homePreviewPlayBtt.requestFocus() + binding.homePreviewInfoBtt.requestFocus() + //binding.homePreviewPlayBtt.requestFocus() } } } diff --git a/app/src/main/res/layout/activity_main_tv.xml b/app/src/main/res/layout/activity_main_tv.xml index 0003b2618..1c4ebd8ef 100644 --- a/app/src/main/res/layout/activity_main_tv.xml +++ b/app/src/main/res/layout/activity_main_tv.xml @@ -15,12 +15,13 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + android:nextFocusDown="@id/home_preview_info_btt"> + @@ -64,8 +144,8 @@ android:padding="10dp" android:src="@drawable/ic_refresh" android:tag="@string/tv_no_focus_tag" - app:tint="@color/player_on_button_tv_attr" - android:visibility="gone"/> + android:visibility="gone" + app:tint="@color/player_on_button_tv_attr" /> @@ -130,89 +210,6 @@ android:src="@drawable/ic_outline_account_circle_24" /> --> - - - - - - - - - - - - - - - - - - - - @@ -252,9 +249,9 @@ android:layout_height="wrap_content" android:layout_marginStart="@dimen/navbar_width" android:layout_marginEnd="0dp" + android:background="?android:attr/selectableItemBackground" android:padding="12dp" android:text="@string/continue_watching" - android:background="?android:attr/selectableItemBackground" app:drawableTint="?attr/white" /> - - - + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:paddingStart="12dp" + android:paddingTop="5dp" + android:paddingEnd="12dp" - + + + android:orientation="horizontal"> - + android:nextFocusUp="@id/home_watch_child_recyclerview" + android:nextFocusDown="@id/home_bookmarked_child_recyclerview" + android:text="@string/type_watching" /> - + android:nextFocusUp="@id/home_watch_child_recyclerview" + android:nextFocusDown="@id/home_bookmarked_child_recyclerview" + android:text="@string/type_plan_to_watch" /> - + android:nextFocusUp="@id/home_watch_child_recyclerview" + android:nextFocusDown="@id/home_bookmarked_child_recyclerview" + android:text="@string/type_on_hold" /> - - - + android:nextFocusUp="@id/home_watch_child_recyclerview" + android:nextFocusDown="@id/home_bookmarked_child_recyclerview" + android:text="@string/type_dropped" /> - + + + + + + android:nextFocusDown="@id/home_preview_info_btt" > diff --git a/app/src/main/res/layout/rail_header.xml b/app/src/main/res/layout/rail_header.xml index 8868b57be..16ccd6c39 100644 --- a/app/src/main/res/layout/rail_header.xml +++ b/app/src/main/res/layout/rail_header.xml @@ -13,7 +13,7 @@ android:layout_height="24dp" app:itemIconTint="@color/item_select_color" android:focusable="true" - android:nextFocusRight="@id/home_preview_play_btt" + android:nextFocusRight="@id/home_preview_info_btt" android:src="@drawable/notifications_icon_selector" android:contentDescription="@string/account" app:tint="@color/item_select_color" /> diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index cf632e0d3..5bf1e87ed 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -16,7 +16,7 @@ 2000 3dp - 62dp + 0dp 50dp 1dp From a70fb87594c71eab2396db4b77d9e8294bf993c2 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sat, 4 Oct 2025 02:02:42 +0200 Subject: [PATCH 385/962] Feat: Infinite scrolling on quicksearch and trakt (#1949) --- .../ui/quicksearch/QuickSearchFragment.kt | 37 ++++++++-- .../cloudstream3/ui/search/SearchAdaptor.kt | 9 +-- .../cloudstream3/ui/search/SearchFragment.kt | 5 +- .../cloudstream3/ui/search/SearchViewModel.kt | 70 ++++++++++++------- .../metaproviders/TraktProvider.kt | 12 ++-- 5 files changed, 87 insertions(+), 46 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt index d2e308a3c..f428321d2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt @@ -16,6 +16,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.CommonActivity.activity @@ -39,6 +40,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.AppContextUtils.filterProviderByPreferredMedia import com.lagradost.cloudstream3.utils.AppContextUtils.filterSearchResultByFilmQuality +import com.lagradost.cloudstream3.utils.AppContextUtils.isRecyclerScrollable import com.lagradost.cloudstream3.utils.AppContextUtils.ownShow import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.UIHelper @@ -137,7 +139,6 @@ class QuickSearchFragment : Fragment() { HomeFragment.currentSpan = it } binding?.quickSearchAutofitResults?.spanCount = HomeFragment.currentSpan - HomeFragment.currentSpan = HomeFragment.currentSpan HomeFragment.configEvent.invoke(HomeFragment.currentSpan) } @@ -160,7 +161,8 @@ class QuickSearchFragment : Fragment() { getApiFromNameNull(providers?.first())?.hasQuickSearch ?: false } else false - if (isSingleProvider) { + val firstProvider = providers?.firstOrNull() + if (isSingleProvider && firstProvider != null) { binding?.quickSearchAutofitResults?.apply { adapter = SearchAdapter( ArrayList(), @@ -170,9 +172,31 @@ class QuickSearchFragment : Fragment() { } } + binding?.quickSearchAutofitResults?.addOnScrollListener(object : + RecyclerView.OnScrollListener() { + var expandCount = 0 + + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + + val adapter = recyclerView.adapter + if (adapter !is SearchAdapter) return + + val count = adapter.itemCount + val currentHasNext = adapter.hasNext + + if (!recyclerView.isRecyclerScrollable() && currentHasNext && expandCount != count) { + expandCount = count + ioSafe { + searchViewModel.expandAndReturn(firstProvider) + } + } + } + }) + try { binding?.quickSearch?.queryHint = - getString(R.string.search_hint_site).format(providers?.first()) + getString(R.string.search_hint_site).format(firstProvider) } catch (e: Exception) { logError(e) } @@ -273,9 +297,12 @@ class QuickSearchFragment : Fragment() { when (it) { is Resource.Success -> { it.value.let { data -> - (binding?.quickSearchAutofitResults?.adapter as? SearchAdapter)?.updateList( - context?.filterSearchResultByFilmQuality(data) ?: data + val adapter = + (binding?.quickSearchAutofitResults?.adapter as? SearchAdapter) + adapter?.updateList( + context?.filterSearchResultByFilmQuality(data.list) ?: data.list ) + adapter?.hasNext = data.hasNext } searchExitIcon?.alpha = 1f binding?.quickSearchLoadingBar?.alpha = 0f diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index f318401c0..ed3fabe71 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -31,7 +31,7 @@ class SearchClickCallback( ) class SearchAdapter( - private val cardList: MutableList, + private var cardList: List, private val resView: AutofitRecyclerView, private val clickCallback: (SearchClickCallback) -> Unit, ) : RecyclerView.Adapter() { @@ -74,12 +74,9 @@ class SearchAdapter( fun updateList(newList: List) { val diffResult = DiffUtil.calculateDiff( - SearchResponseDiffCallback(this.cardList, newList) + SearchResponseDiffCallback(cardList, newList) ) - - cardList.clear() - cardList.addAll(newList) - + cardList = newList diffResult.dispatchUpdatesTo(this) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 2c5f80090..e8fcbc851 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -492,8 +492,9 @@ class SearchFragment : Fragment() { when (it) { is Resource.Success -> { it.value.let { data -> - if (data.isNotEmpty()) { - (binding?.searchAutofitResults?.adapter as? SearchAdapter)?.updateList(data) + val list = data.list + if (list.isNotEmpty()) { + (binding?.searchAutofitResults?.adapter as? SearchAdapter)?.updateList(list) } } searchExitIcon?.alpha = 1f diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt index a0d533545..72a2bcb60 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt @@ -32,9 +32,9 @@ data class ExpandableSearchList( const val SEARCH_HISTORY_KEY = "search_history" class SearchViewModel : ViewModel() { - private val _searchResponse: MutableLiveData>> = + private val _searchResponse: MutableLiveData> = MutableLiveData() - val searchResponse: LiveData>> get() = _searchResponse + val searchResponse: LiveData> get() = _searchResponse private val _currentSearch: MutableLiveData> = MutableLiveData() @@ -46,7 +46,7 @@ class SearchViewModel : ViewModel() { private var repos = synchronized(apis) { apis.map { APIRepository(it) } } fun clearSearch() { - _searchResponse.postValue(Resource.Success(ArrayList())) + _searchResponse.postValue(Resource.Success(ExpandableSearchList(emptyList(), 0, false))) _currentSearch.postValue(emptyMap()) expandableSearches.clear() } @@ -105,15 +105,16 @@ class SearchViewModel : ViewModel() { if (next is Resource.Success) { val nextValue = next.value expandableSearches[name]?.apply { - hasNext = nextValue.hasNext - currentPage = nextPage + this.hasNext = nextValue.hasNext + this.currentPage = nextPage debugWarning({ nextValue.items.any { outer -> this.list.any { it.url == outer.url } } }) { "Expanded search contained an item that was previously already in the list.\nQuery = $query, ${nextValue.items} = ${this.list}" } - this.list += nextValue.items - this.list.distinctBy { it.url } // just to be sure we are not adding the same shit for some reason + // just to be sure we are not adding the same shit for some reason + // Avoids weird behavior in the recyclerview by recreating the list + this.list = (this.list + nextValue.items).distinctBy { it.url } } ?: debugWarning { "Expanded an item not in search load named $name, current list is ${expandableSearches.keys}" } @@ -121,14 +122,44 @@ class SearchViewModel : ViewModel() { current.hasNext = false } + _searchResponse.postValue(Resource.Success(bundleSearch(expandableSearches))) _currentSearch.postValue(expandableSearches) } - lock -= name val item = expandableSearches[name] ?: return null - return HomeViewModel.ExpandableHomepageList(HomePageList(name, item.list), item.currentPage, item.hasNext) + return HomeViewModel.ExpandableHomepageList( + HomePageList(name, item.list), + item.currentPage, + item.hasNext + ) + } + + private fun bundleSearch(lists: MutableMap): ExpandableSearchList { + if (lists.size == 1) { + return lists.values.first() + } + + val list = ArrayList() + val nestedList = + lists.map { it.value.list } + + // I do it this way to move the relevant search results to the top + var index = 0 + while (true) { + var added = 0 + for (sublist in nestedList) { + if (sublist.size > index) { + list.add(sublist[index]) + added++ + } + } + if (added == 0) break + index++ + } + + return ExpandableSearchList(list, 1, false) } private fun search( @@ -172,7 +203,8 @@ class SearchViewModel : ViewModel() { if (currentSearchIndex != currentIndex) return@amap if (search is Resource.Success) { val searchValue = search.value - expandableSearches[a.name] = ExpandableSearchList(searchValue.items, 1, searchValue.hasNext) + expandableSearches[a.name] = + ExpandableSearchList(searchValue.items, 1, searchValue.hasNext) } _currentSearch.postValue(expandableSearches) @@ -181,23 +213,7 @@ class SearchViewModel : ViewModel() { if (currentSearchIndex != currentIndex) return@withContext // this should prevent rewrite of existing data bug _currentSearch.postValue(expandableSearches) - val list = ArrayList() - val nestedList = - expandableSearches.map { it.value.list } - - // I do it this way to move the relevant search results to the top - var index = 0 - while (true) { - var added = 0 - for (sublist in nestedList) { - if (sublist.size > index) { - list.add(sublist[index]) - added++ - } - } - if (added == 0) break - index++ - } + val list = bundleSearch(expandableSearches) _searchResponse.postValue(Resource.Success(list)) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt index 8ac8f42bc..63f6d564c 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/metaproviders/TraktProvider.kt @@ -17,6 +17,7 @@ import com.lagradost.cloudstream3.NextAiring import com.lagradost.cloudstream3.ProviderType import com.lagradost.cloudstream3.Score import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.SearchResponseList import com.lagradost.cloudstream3.ShowStatus import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.addDate @@ -27,6 +28,7 @@ import com.lagradost.cloudstream3.newEpisode import com.lagradost.cloudstream3.newHomePageResponse import com.lagradost.cloudstream3.newMovieLoadResponse import com.lagradost.cloudstream3.newMovieSearchResponse +import com.lagradost.cloudstream3.newSearchResponseList import com.lagradost.cloudstream3.newTvSeriesLoadResponse import com.lagradost.cloudstream3.newTvSeriesSearchResponse import com.lagradost.cloudstream3.utils.AppUtils.parseJson @@ -56,7 +58,6 @@ open class TraktProvider : MainAPI() { ) override suspend fun getMainPage(page: Int, request: MainPageRequest): HomePageResponse { - val apiResponse = getApi("${request.data}?extended=full,images&page=$page") val results = parseJson>(apiResponse).map { element -> @@ -97,17 +98,16 @@ open class TraktProvider : MainAPI() { } } - override suspend fun search(query: String): List? { + override suspend fun search(query: String, page: Int): SearchResponseList? { val apiResponse = - getApi("$traktApiUrl/search/movie,show?extended=full,images&limit=20&page=1&query=$query") + getApi("$traktApiUrl/search/movie,show?extended=full,images&limit=20&page=$page&query=$query") - return parseJson>(apiResponse).map { element -> + return newSearchResponseList(parseJson>(apiResponse).map { element -> element.toSearchResponse() - } + }) } override suspend fun load(url: String): LoadResponse { - val data = parseJson(url) val mediaDetails = data.mediaDetails val moviesOrShows = if (data.type == TvType.Movie) "movies" else "shows" From 60fab250327d95d49d45f152e40f7102e70e4667 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sat, 4 Oct 2025 02:17:19 +0200 Subject: [PATCH 386/962] Fix: Minor margin issue (Phone) + Minor 1px rendering issue (TV) --- app/src/main/res/layout/fragment_result.xml | 4 +++- app/src/main/res/layout/fragment_result_tv.xml | 1 + 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/layout/fragment_result.xml b/app/src/main/res/layout/fragment_result.xml index 87de47336..79f89af25 100644 --- a/app/src/main/res/layout/fragment_result.xml +++ b/app/src/main/res/layout/fragment_result.xml @@ -644,6 +644,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="5dp" + android:layout_marginBottom="10dp" android:orientation="vertical" android:visibility="gone" tools:visibility="visible"> @@ -686,7 +687,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:paddingVertical="15dp" + android:paddingTop="5dp" + android:paddingBottom="15dp" android:visibility="gone" tools:visibility="visible"> diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index dbebecfcc..57624bbf1 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -36,6 +36,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit tools:src="@drawable/profile_bg_dark_blue" /> Date: Sun, 5 Oct 2025 01:04:31 +0300 Subject: [PATCH 387/962] feat(TV UI): Crop the Main banner from the Top (#1952) --- .../com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt | 3 +++ app/src/main/res/layout/fragment_home_head_tv.xml | 3 +++ app/src/main/res/layout/home_scroll_view_tv.xml | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index 4c4dd2d84..e3b96ade2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -57,6 +57,9 @@ class HomeScrollAdapter( } is HomeScrollViewTvBinding -> { + //Change poster crop area to 20% from Top + binding.homeScrollPreview.cropYCenterOffsetPct = 0.2f + binding.homeScrollPreview.loadImage(posterUrl) } } diff --git a/app/src/main/res/layout/fragment_home_head_tv.xml b/app/src/main/res/layout/fragment_home_head_tv.xml index f9d44da24..88320eb96 100644 --- a/app/src/main/res/layout/fragment_home_head_tv.xml +++ b/app/src/main/res/layout/fragment_home_head_tv.xml @@ -125,6 +125,7 @@ android:gravity="center_vertical" android:nextFocusLeft="@id/home_preview_info_btt" android:nextFocusRight="@id/home_preview_reload_provider" + android:nextFocusUp="@id/home_preview_change_api" android:nextFocusDown="@id/home_preview_info_btt"> @@ -140,6 +141,7 @@ android:focusable="true" android:nextFocusLeft="@id/home_preview_change_api" android:nextFocusRight="@id/home_preview_search_button" + android:nextFocusUp="@id/home_preview_reload_provider" android:nextFocusDown="@id/home_preview_info_btt" android:padding="10dp" android:src="@drawable/ic_refresh" @@ -157,6 +159,7 @@ android:focusable="true" android:nextFocusLeft="@id/home_preview_reload_provider" android:nextFocusRight="@id/home_preview_switch_account" + android:nextFocusUp="@id/home_preview_search_button" android:nextFocusDown="@id/home_preview_info_btt" android:padding="10dp" android:src="@drawable/search_icon" diff --git a/app/src/main/res/layout/home_scroll_view_tv.xml b/app/src/main/res/layout/home_scroll_view_tv.xml index e0da81975..d819c91c7 100644 --- a/app/src/main/res/layout/home_scroll_view_tv.xml +++ b/app/src/main/res/layout/home_scroll_view_tv.xml @@ -5,11 +5,11 @@ android:background="?attr/primaryGrayBackground" android:layout_height="match_parent"> - Date: Sun, 5 Oct 2025 00:26:27 +0200 Subject: [PATCH 388/962] Fix: HTML encoded description, and clickable homepage hero --- .../ui/home/HomeParentItemAdapterPreview.kt | 27 +++++++------------ .../cloudstream3/ui/home/HomeScrollAdapter.kt | 9 +++++-- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index 58167e0c2..c2f06c021 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -14,9 +14,7 @@ import androidx.core.content.ContextCompat import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.findViewTreeLifecycleOwner -import androidx.lifecycle.findViewTreeViewModelStoreOwner import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import androidx.viewpager2.widget.ViewPager2 @@ -55,9 +53,9 @@ import com.lagradost.cloudstream3.ui.search.SearchClickCallback import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout +import com.lagradost.cloudstream3.utils.AppContextUtils.html import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.DataStoreHelper.getDefaultAccount import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes @@ -145,7 +143,12 @@ class HomeParentItemAdapterPreview( } } - val previewAdapter = HomeScrollAdapter(fragment = fragment) + val previewAdapter = HomeScrollAdapter(fragment = fragment) { view, position, item -> + viewModel.click( + LoadClickCallback(0, view, position, item) + ) + } + private val resumeAdapter = ResumeItemAdapter( fragment, nextFocusUp = itemView.nextFocusUpId, @@ -328,9 +331,9 @@ class HomeParentItemAdapterPreview( homePreviewDescription.isGone = item.plot.isNullOrBlank() homePreviewDescription.text = - item.plot ?: "" + item.plot?.html() ?: "" - homePreviewText.text = item.name + homePreviewText.text = item.name.html() populateChips( homePreviewTags, item.tags?.take(6) ?: emptyList(), @@ -340,23 +343,11 @@ class HomeParentItemAdapterPreview( homePreviewTags.isGone = item.tags.isNullOrEmpty() - /*homePreviewPlayBtt.setOnClickListener { view -> - viewModel.click( - LoadClickCallback( - START_ACTION_RESUME_LATEST, - view, - position, - item - ) - ) - }*/ - homePreviewInfoBtt.setOnClickListener { view -> viewModel.click( LoadClickCallback(0, view, position, item) ) } - } (binding as? FragmentHomeHeadBinding)?.apply { //homePreviewImage.setImage(item.posterUrl, item.posterHeaders) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index e3b96ade2..3a6ed4923 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.home import android.content.res.Configuration import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import androidx.core.view.isGone import androidx.fragment.app.Fragment @@ -16,7 +17,8 @@ import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.ImageLoader.loadImage class HomeScrollAdapter( - fragment: Fragment + fragment: Fragment, + val callback : ((View, Int, LoadResponse) -> Unit) ) : NoStateAdapter(fragment) { var hasMoreItems: Boolean = false @@ -59,7 +61,10 @@ class HomeScrollAdapter( is HomeScrollViewTvBinding -> { //Change poster crop area to 20% from Top binding.homeScrollPreview.cropYCenterOffsetPct = 0.2f - + binding.homeScrollPreview.isFocusable = false + binding.homeScrollPreview.setOnClickListener { view -> + callback.invoke(view ?: return@setOnClickListener, position, item) + } binding.homeScrollPreview.loadImage(posterUrl) } } From 233fe87cdc9c40b6155e9dded26937180f03d23f Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sun, 5 Oct 2025 01:05:55 +0200 Subject: [PATCH 389/962] Fix(TV): Focus issue with account --- app/src/main/res/layout/rail_footer.xml | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/main/res/layout/rail_footer.xml b/app/src/main/res/layout/rail_footer.xml index 137d12692..de0406a6e 100644 --- a/app/src/main/res/layout/rail_footer.xml +++ b/app/src/main/res/layout/rail_footer.xml @@ -1,32 +1,33 @@ + android:layout_marginBottom="10dp" + android:padding="1dp" + android:visibility="gone" + tools:visibility="visible"> + android:foreground="@drawable/outline_drawable_round_20" + android:nextFocusDown="@id/nav_footer_profile_card" + app:cardCornerRadius="20dp"> + tools:src="@drawable/profile_bg_orange" /> From ca737b11948aeeb872d8cc0382b76a15fdefe8b7 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sun, 5 Oct 2025 02:20:45 +0200 Subject: [PATCH 390/962] Fix(TV): Poster size for remove item --- .../ui/home/HomeChildItemAdapter.kt | 132 +++++++++++------- .../cloudstream3/ui/home/HomeFragment.kt | 2 + .../ui/home/HomeParentItemAdapter.kt | 22 ++- .../ui/result/LinearListLayout.kt | 23 ++- 4 files changed, 115 insertions(+), 64 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt index ae22afdb2..7ffb7ed06 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt @@ -1,8 +1,10 @@ package com.lagradost.cloudstream3.ui.home +import android.content.Context import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager import androidx.viewbinding.ViewBinding @@ -13,6 +15,7 @@ import com.lagradost.cloudstream3.databinding.HomeRemoveGridExpandedBinding import com.lagradost.cloudstream3.databinding.HomeResultGridBinding import com.lagradost.cloudstream3.databinding.HomeResultGridExpandedBinding import com.lagradost.cloudstream3.ui.BaseAdapter +import com.lagradost.cloudstream3.ui.BaseDiffCallback import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD import com.lagradost.cloudstream3.ui.search.SearchClickCallback @@ -69,18 +72,25 @@ class ResumeItemAdapter( override fun onBindFooter(holder: ViewHolderState) { this.applyBinding(holder, false) + when (val binding = holder.view) { + is HomeRemoveGridBinding -> { + updateLayoutParms(binding.backgroundCard, setWidth, setHeight) + } + + is HomeRemoveGridExpandedBinding -> { + updateLayoutParms(binding.backgroundCard, setWidth, setHeight) + } + } holder.itemView.apply { if (isLayout(TV)) { isFocusableInTouchMode = true isFocusable = true } - - if (nextFocusUp != null) { - nextFocusUpId = nextFocusUp + nextFocusUp?.let { + nextFocusUpId = it } - - if (nextFocusDown != null) { - nextFocusDownId = nextFocusDown + nextFocusDown?.let { + nextFocusDownId = it } setOnClickListener { v -> @@ -90,16 +100,50 @@ class ResumeItemAdapter( } } +/** Remember to set `updatePosterSize` to cache the poster size, + * otherwise the width and height is unset */ open class HomeChildItemAdapter( fragment: Fragment, id: Int, - protected val nextFocusUp: Int? = null, - protected val nextFocusDown: Int? = null, - private val clickCallback: (SearchClickCallback) -> Unit, + var nextFocusUp: Int? = null, + var nextFocusDown: Int? = null, + var clickCallback: (SearchClickCallback) -> Unit, ) : - BaseAdapter(fragment, id) { - var isHorizontal: Boolean = false + BaseAdapter( + fragment, id, diffCallback = BaseDiffCallback( + itemSame = { a, b -> + a.url == b.url + }, + contentSame = { a, b -> + a == b + }) + ) { var hasNext: Boolean = false + var isHorizontal: Boolean = false + set(value) { + field = value + updateCachedPosterSize() + } + + private fun updateCachedPosterSize() { + setWidth = if (!isHorizontal) { + minPosterSize + } else { + maxPosterSize + } + setHeight = if (!isHorizontal) { + maxPosterSize + } else { + minPosterSize + } + } + + init { + updateCachedPosterSize() + } + + protected var setWidth = 0 + protected var setHeight = 0 override fun onCreateContent(parent: ViewGroup): ViewHolderState { val expanded = parent.context.isBottomLayout() @@ -112,52 +156,38 @@ open class HomeChildItemAdapter( return HomeScrollViewHolderState(binding) } - protected fun applyBinding(holder: ViewHolderState, isFirstItem: Boolean) { - val context = holder.view.root.context - val scale = PreferenceManager.getDefaultSharedPreferences(context) - ?.getInt(context.getString(R.string.poster_size_key), 0) ?: 0 - // Scale by +10% per step - val mul = 1.0f + scale * 0.1f - val min = (114.toPx.toFloat() * mul).toInt() - val max = (180.toPx.toFloat() * mul).toInt() + companion object { + var minPosterSize: Int = 0 + var maxPosterSize: Int = 0 + fun updatePosterSize(context: Context) { + val scale = PreferenceManager.getDefaultSharedPreferences(context) + ?.getInt(context.getString(R.string.poster_size_key), 0) ?: 0 + // Scale by +10% per step + val mul = 1.0f + scale * 0.1f + minPosterSize = (114.toPx.toFloat() * mul).toInt() + maxPosterSize = (180.toPx.toFloat() * mul).toInt() + } + + fun updateLayoutParms(layout: FrameLayout, width: Int, height: Int) { + val params = layout.layoutParams + if (params.height == height && params.width == width) return + + params.width = width + params.height = height + + layout.layoutParams = params + } + } + + protected fun applyBinding(holder: ViewHolderState, isFirstItem: Boolean) { when (val binding = holder.view) { is HomeResultGridBinding -> { - binding.backgroundCard.apply { - - layoutParams = - layoutParams.apply { - width = if (!isHorizontal) { - min - } else { - max - } - height = if (!isHorizontal) { - max - } else { - min - } - } - } + updateLayoutParms(binding.backgroundCard, setWidth, setHeight) } is HomeResultGridExpandedBinding -> { - binding.backgroundCard.apply { - - layoutParams = - layoutParams.apply { - width = if (!isHorizontal) { - min - } else { - max - } - height = if (!isHorizontal) { - max - } else { - min - } - } - } + updateLayoutParms(binding.backgroundCard, setWidth, setHeight) if (isFirstItem) { // to fix tv binding.backgroundCard.nextFocusLeftId = R.id.nav_rail_view diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 77bb163ee..5d6cbfb35 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -604,6 +604,8 @@ class HomeFragment : Fragment() { super.onViewCreated(view, savedInstanceState) fixGrid() + context?.let { HomeChildItemAdapter.updatePosterSize(it) } + binding?.apply { //homeChangeApiLoading.setOnClickListener(apiChangeClickListener) //homeChangeApiLoading.setOnClickListener(apiChangeClickListener) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index 8bc0aa287..7cdd56705 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -60,7 +60,7 @@ open class ParentItemAdapter( override fun restore(state: Bundle) { (binding as? HomepageParentBinding)?.homeChildRecyclerview?.layoutManager?.onRestoreInstanceState( - state.getSafeParcelable("value") + state.getSafeParcelable("value") ) } } @@ -101,6 +101,26 @@ open class ParentItemAdapter( hasNext = item.hasNext submitList(item.list.list) } + + /** We can reuse the HomeChildItemAdapter, but that causes weird a fade-in + * That is not really desirable. + * */ + /* + val currentAdapter = homeChildRecyclerview.adapter as? HomeChildItemAdapter + if (currentAdapter == null) { + ... + } else { + currentAdapter.apply { + isHorizontal = info.isHorizontalImages + hasNext = item.hasNext + this.clickCallback = this@ParentItemAdapter.clickCallback + nextFocusUp = homeChildRecyclerview.nextFocusUpId + nextFocusDown = homeChildRecyclerview.nextFocusDownId + submitList(item.list.list) + } + } + */ + homeChildRecyclerview.setLinearListLayout( isHorizontal = true, nextLeft = startFocus, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt index 7d0061cb6..408d213cf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/LinearListLayout.kt @@ -23,18 +23,17 @@ fun RecyclerView?.setLinearListLayout( ) { if (this == null) return val ctx = this.context ?: return - this.layoutManager = - LinearListLayout(ctx).apply { - if (isHorizontal) setHorizontal() else setVertical() - nextFocusLeft = - if (nextLeft == FOCUS_INHERIT) this@setLinearListLayout.nextFocusLeftId else nextLeft - nextFocusRight = - if (nextRight == FOCUS_INHERIT) this@setLinearListLayout.nextFocusRightId else nextRight - nextFocusUp = - if (nextUp == FOCUS_INHERIT) this@setLinearListLayout.nextFocusUpId else nextUp - nextFocusDown = - if (nextDown == FOCUS_INHERIT) this@setLinearListLayout.nextFocusDownId else nextDown - } + this.layoutManager = (this.layoutManager as? LinearListLayout ?: LinearListLayout(ctx)).apply { + if (isHorizontal) setHorizontal() else setVertical() + nextFocusLeft = + if (nextLeft == FOCUS_INHERIT) this@setLinearListLayout.nextFocusLeftId else nextLeft + nextFocusRight = + if (nextRight == FOCUS_INHERIT) this@setLinearListLayout.nextFocusRightId else nextRight + nextFocusUp = + if (nextUp == FOCUS_INHERIT) this@setLinearListLayout.nextFocusUpId else nextUp + nextFocusDown = + if (nextDown == FOCUS_INHERIT) this@setLinearListLayout.nextFocusDownId else nextDown + } } open class LinearListLayout(context: Context?) : From dcdf15f484904291cb70832dc0b185bf6a6dd574 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sun, 5 Oct 2025 17:18:30 +0200 Subject: [PATCH 391/962] Fix: Optimized homepage by properly recycling all nested recycleviews --- .../lagradost/cloudstream3/ui/BaseAdapter.kt | 16 +++++++++ .../ui/home/HomeParentItemAdapter.kt | 33 ++++++++----------- 2 files changed, 29 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt index e930961c5..d09583196 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseAdapter.kt @@ -85,6 +85,22 @@ abstract class BaseAdapter< AsyncDifferConfig.Builder(diffCallback).build() ) + /** + * Instantly submits a **new and fresh** list. This means that no changes like moves are done as + * we assume the new list is not the same thing as the old list, nothing is shared. + * + * The views are rendered instantly as a result, so no fade/pop-ins or similar. + * + * Use `submitList` for general use, as that can reuse old views. + * */ + open fun submitIncomparableList(list: List?) { + // This leverages a quirk in the submitList function that has a fast case for null arrays + // What this implies is that as long as we do a double submit we can ensure no pop-ins, + // as the changes are the entire list instead of calculating deltas + submitList(null) + submitList(list) + } + open fun submitList(list: List?) { // deep copy at least the top list, because otherwise adapter can go crazy mDiffer.submitList(list?.let { CopyOnWriteArrayList(it) }) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index 7cdd56705..cc4bd4935 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -90,25 +90,19 @@ open class ParentItemAdapter( if (binding !is HomepageParentBinding) return val info = item.list binding.apply { - homeChildRecyclerview.adapter = HomeChildItemAdapter( - fragment = fragment, - id = id + position + 100, - clickCallback = clickCallback, - nextFocusUp = homeChildRecyclerview.nextFocusUpId, - nextFocusDown = homeChildRecyclerview.nextFocusDownId, - ).apply { - isHorizontal = info.isHorizontalImages - hasNext = item.hasNext - submitList(item.list.list) - } - - /** We can reuse the HomeChildItemAdapter, but that causes weird a fade-in - * That is not really desirable. - * */ - /* val currentAdapter = homeChildRecyclerview.adapter as? HomeChildItemAdapter if (currentAdapter == null) { - ... + homeChildRecyclerview.adapter = HomeChildItemAdapter( + fragment = fragment, + id = id + position + 100, + clickCallback = clickCallback, + nextFocusUp = homeChildRecyclerview.nextFocusUpId, + nextFocusDown = homeChildRecyclerview.nextFocusDownId, + ).apply { + isHorizontal = info.isHorizontalImages + hasNext = item.hasNext + submitList(item.list.list) + } } else { currentAdapter.apply { isHorizontal = info.isHorizontalImages @@ -116,10 +110,9 @@ open class ParentItemAdapter( this.clickCallback = this@ParentItemAdapter.clickCallback nextFocusUp = homeChildRecyclerview.nextFocusUpId nextFocusDown = homeChildRecyclerview.nextFocusDownId - submitList(item.list.list) + submitIncomparableList(item.list.list) } - } - */ + } homeChildRecyclerview.setLinearListLayout( isHorizontal = true, From 2ca106ecb10d70ff2510dc1e34904945329b0586 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sun, 5 Oct 2025 22:50:04 +0200 Subject: [PATCH 392/962] Fix: Recycle setMaxViewPoolSize on HomeChildItemAdapter for larger devices --- .../lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index cc4bd4935..a628d491e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -25,6 +25,7 @@ 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.utils.AppContextUtils.isRecyclerScrollable +import com.lagradost.cloudstream3.utils.AppContextUtils.setMaxViewPoolSize class LoadClickCallback( val action: Int = 0, @@ -103,6 +104,9 @@ open class ParentItemAdapter( hasNext = item.hasNext submitList(item.list.list) } + + // The vast majority of the lag comes from creating the view + homeChildRecyclerview.setMaxViewPoolSize(0, 20) } else { currentAdapter.apply { isHorizontal = info.isHorizontalImages From 716b809ea2879dc52c356d6650d03dda0edb0b77 Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Mon, 6 Oct 2025 21:40:00 +0200 Subject: [PATCH 393/962] Emergency patch for TV performance --- .../ui/home/HomeChildItemAdapter.kt | 11 +++ .../cloudstream3/ui/home/HomeFragment.kt | 73 +++++++++++++------ .../ui/home/HomeParentItemAdapter.kt | 11 ++- 3 files changed, 68 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt index 7ffb7ed06..bb7ab8e71 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeChildItemAdapter.kt @@ -5,9 +5,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import android.widget.ImageView import androidx.fragment.app.Fragment import androidx.preference.PreferenceManager import androidx.viewbinding.ViewBinding +import coil3.load import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.databinding.HomeRemoveGridBinding @@ -41,6 +43,15 @@ class HomeScrollViewHolderState(view: ViewBinding) : ViewHolderState(vi } } } + + override fun onViewRecycled() { + super.onViewRecycled() + + // Clear the image, idk if this saves ram or not, but I guess? + view.root.findViewById(R.id.imageView)?.apply { + load(null) + } + } } class ResumeItemAdapter( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 5d6cbfb35..42601c8b0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -399,16 +399,23 @@ class HomeFragment : Fragment() { val listView = dialog.findViewById(R.id.listview1) - val arrayAdapter = object : ArrayAdapter(this, R.layout.sort_bottom_single_provider_choice, + val arrayAdapter = object : ArrayAdapter( + this, R.layout.sort_bottom_single_provider_choice, mutableListOf() ) { - override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { - val view = convertView ?: LayoutInflater.from(context).inflate(R.layout.sort_bottom_single_provider_choice, parent, false) + override fun getView( + position: Int, + convertView: View?, + parent: ViewGroup + ): View { + val view = convertView ?: LayoutInflater.from(context) + .inflate(R.layout.sort_bottom_single_provider_choice, parent, false) val titleText = view.findViewById(R.id.text1) val pinIcon = view.findViewById(R.id.pinicon) val name = getItem(position) titleText?.text = name - val isPinned = pinnedphashset.contains(currentValidApis[position].name ?: "") + val isPinned = + pinnedphashset.contains(currentValidApis[position].name ?: "") pinIcon.visibility = if (isPinned) View.VISIBLE else View.GONE return view } @@ -420,7 +427,7 @@ class HomeFragment : Fragment() { if (currentValidApis.isNotEmpty()) { currentApiName = currentValidApis[i].name //to switch to apply simply remove this - currentApiName?.let(callback) + currentApiName.let(callback) dialog.dismissSafe() } } @@ -431,7 +438,11 @@ class HomeFragment : Fragment() { pinnedphashset = pinnedp.toHashSet() arrayAdapter.clear() val sortedApis = validAPIs - .filter {it.hasMainPage && (pinnedphashset.contains(it.name) || it.supportedTypes.any(preSelectedTypes::contains)) } + .filter { + it.hasMainPage && (pinnedphashset.contains(it.name) || it.supportedTypes.any( + preSelectedTypes::contains + )) + } .sortedBy { it.name.lowercase() } val sortedApiMap = LinkedHashMap().apply { @@ -459,12 +470,12 @@ class HomeFragment : Fragment() { } // pin provider on hold listView?.setOnItemLongClickListener { _, _, i, _ -> - if (currentValidApis.isNotEmpty() && i>1) { + if (currentValidApis.isNotEmpty() && i > 1) { val pinnedp = DataStoreHelper.pinnedProviders.toMutableList() val thisapi = currentValidApis[i].name - if(pinnedp.contains(thisapi)){ + if (pinnedp.contains(thisapi)) { pinnedp.remove(thisapi) - }else{ + } else { pinnedp.add(thisapi) } DataStoreHelper.pinnedProviders = pinnedp.toTypedArray() @@ -490,7 +501,7 @@ class HomeFragment : Fragment() { private val homeViewModel: HomeViewModel by activityViewModels() private val accountViewModel: AccountViewModel by activityViewModels() - fun addMovies(cards: List) { + fun addMovies(cards: List) { val ctx = context ?: run { Log.e(TAG, "Context is null, aborting addMovies") return @@ -515,6 +526,7 @@ class HomeFragment : Fragment() { Log.e(TAG, "Error adding movies: $e") } } + private fun deleteAll() { val ctx = context ?: run { Log.e(TAG, "Context is null, aborting deleteAll") @@ -599,6 +611,28 @@ class HomeFragment : Fragment() { private var bottomSheetDialog: BottomSheetDialog? = null private var homeMasterAdapter: HomeParentItemAdapterPreview? = null + var lastSavedHomepage: String? = null + + fun saveHomepageToTV(page : Map) { + // No need to update for phone + if(isLayout(PHONE)) { + return + } + val (name, data) = page.entries.firstOrNull() ?: return + // Modifying homepage is an expensive operation, and therefore we avoid it at all cost + if(name == lastSavedHomepage) { + return + } + Log.i(TAG, "Adding programs $name to TV") + lastSavedHomepage = name + ioSafe { + // empty the channel + deleteAll() + // insert the program from first array + addMovies(data.list.list) + } + } + @SuppressLint("SetTextI18n") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -610,10 +644,10 @@ class HomeFragment : Fragment() { //homeChangeApiLoading.setOnClickListener(apiChangeClickListener) //homeChangeApiLoading.setOnClickListener(apiChangeClickListener) homeApiFab.setOnClickListener(apiChangeClickListener) - homeApiFab.setOnLongClickListener{ - if(currentApiName == noneApi.name) return@setOnLongClickListener false + homeApiFab.setOnLongClickListener { + if (currentApiName == noneApi.name) return@setOnLongClickListener false homeViewModel.loadAndCancel(currentApiName, forceReload = true, fromUI = true) - showToast(R.string.action_reload,Toast.LENGTH_SHORT) + showToast(R.string.action_reload, Toast.LENGTH_SHORT) true } homeChangeApi.setOnClickListener(apiChangeClickListener) @@ -628,7 +662,7 @@ class HomeFragment : Fragment() { } homeMasterAdapter = HomeParentItemAdapterPreview( fragment = this@HomeFragment, - homeViewModel,accountViewModel + homeViewModel, accountViewModel ) homeMasterRecycler.adapter = homeMasterAdapter //fixPaddingStatusbar(homeLoadingStatusbar) @@ -676,16 +710,7 @@ class HomeFragment : Fragment() { homeLoadingShimmer.stopShimmer() val d = data.value - val k = d.values.firstOrNull() - if (k != null) { - // empty the channel - deleteAll() - // insert the program from first array - addMovies(k.list.list) - } else { - Log.w("SafeAccess", "Map values are empty — cannot access first element") - } - + saveHomepageToTV(d) val mutableListOfResponse = mutableListOf() listHomepageItems.clear() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt index a628d491e..7c136839c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapter.kt @@ -49,6 +49,13 @@ open class ParentItemAdapter( a.list.list == b.list.list }) ) { + companion object { + // The vast majority of the lag comes from creating the view + // This simply shares the views between all HomeChildItemAdapter + private val sharedPool = + RecyclerView.RecycledViewPool().apply { this.setMaxRecycledViews(0, 20) } + } + data class ParentItemHolder(val binding: ViewBinding) : ViewHolderState(binding) { override fun save(): Bundle = Bundle().apply { val recyclerView = (binding as? HomepageParentBinding)?.homeChildRecyclerview @@ -93,6 +100,7 @@ open class ParentItemAdapter( binding.apply { val currentAdapter = homeChildRecyclerview.adapter as? HomeChildItemAdapter if (currentAdapter == null) { + homeChildRecyclerview.setRecycledViewPool(sharedPool) homeChildRecyclerview.adapter = HomeChildItemAdapter( fragment = fragment, id = id + position + 100, @@ -104,9 +112,6 @@ open class ParentItemAdapter( hasNext = item.hasNext submitList(item.list.list) } - - // The vast majority of the lag comes from creating the view - homeChildRecyclerview.setMaxViewPoolSize(0, 20) } else { currentAdapter.apply { isHorizontal = info.isHorizontalImages From 6611c8a6075aebc0c15e9945c4a22aa28b576c89 Mon Sep 17 00:00:00 2001 From: GCDarcy <104531578+gcdarcy@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:49:19 +0800 Subject: [PATCH 394/962] Added the option in settings for people to display the series/movie title instead of the name of the current source (partial support for languages) (#1956) --- .../cloudstream3/ui/player/GeneratorPlayer.kt | 20 +++++++++++-------- app/src/main/res/values-b+es/array.xml | 4 ++++ app/src/main/res/values-b+pl/array.xml | 6 +++++- app/src/main/res/values-b+tr/array.xml | 4 ++++ app/src/main/res/values-b+vi/array.xml | 4 ++++ app/src/main/res/values/array.xml | 4 ++++ app/src/main/res/values/strings.xml | 2 ++ 7 files changed, 35 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 4d72214a5..7370dea75 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -1761,6 +1761,13 @@ class GeneratorPlayer : FullScreenPlayer() { } } + private fun getHeaderName(): String? { + return when (val meta = currentMeta) { + is ResultEpisode -> meta.headerName + is ExtractorUri -> meta.headerName + else -> null + } + } private fun getPlayerVideoTitle(): String { var headerName: String? = null var subName: String? = null @@ -1831,20 +1838,17 @@ class GeneratorPlayer : FullScreenPlayer() { @SuppressLint("SetTextI18n") fun setPlayerDimen(widthHeight: Pair?) { - val extra = if (widthHeight != null) { - val (width, height) = widthHeight - "- ${width}x${height}" - } else { - "" - } - + val extra = widthHeight?.let { (w, h) -> "- ${w}x${h}" } ?: "" val source = currentSelectedLink?.first?.name ?: currentSelectedLink?.second?.name ?: "NULL" + val headerName = getHeaderName().orEmpty() val title = when (titleRez) { 0 -> "" 1 -> extra 2 -> source 3 -> "$source $extra" + 4 -> headerName + 5 -> "$headerName $extra" else -> "" } playerBinding?.playerVideoTitleRez?.apply { @@ -2172,4 +2176,4 @@ inline fun Bundle.getSafeSerializable(key: String): T if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) getSerializable(key) as? T else getSerializable( key, T::class.java - ) \ No newline at end of file + ) diff --git a/app/src/main/res/values-b+es/array.xml b/app/src/main/res/values-b+es/array.xml index 9c85a5404..40a1be6e0 100644 --- a/app/src/main/res/values-b+es/array.xml +++ b/app/src/main/res/values-b+es/array.xml @@ -15,6 +15,8 @@ + @string/resolution_and_name + @string/name @string/resolution_and_title @string/title @string/resolution @@ -22,6 +24,8 @@ + 5 + 4 3 2 1 diff --git a/app/src/main/res/values-b+pl/array.xml b/app/src/main/res/values-b+pl/array.xml index 708bab010..db9db9659 100644 --- a/app/src/main/res/values-b+pl/array.xml +++ b/app/src/main/res/values-b+pl/array.xml @@ -24,6 +24,8 @@ + @string/resolution_and_name + @string/name @string/resolution_and_title @string/title @string/resolution @@ -31,12 +33,14 @@ + 5 + 4 3 2 1 0 - + @string/none 16 znaków diff --git a/app/src/main/res/values-b+tr/array.xml b/app/src/main/res/values-b+tr/array.xml index ef99bd389..c4c4e4cda 100644 --- a/app/src/main/res/values-b+tr/array.xml +++ b/app/src/main/res/values-b+tr/array.xml @@ -38,6 +38,8 @@ + @string/resolution_and_name + @string/name @string/resolution_and_title @string/title @string/resolution @@ -45,6 +47,8 @@ + 5 + 4 3 2 1 diff --git a/app/src/main/res/values-b+vi/array.xml b/app/src/main/res/values-b+vi/array.xml index f34042839..dbbd0d3c9 100644 --- a/app/src/main/res/values-b+vi/array.xml +++ b/app/src/main/res/values-b+vi/array.xml @@ -16,6 +16,8 @@ + @string/resolution_and_name + @string/name @string/resolution_and_title @string/title @string/resolution @@ -23,6 +25,8 @@ + 5 + 4 3 2 1 diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index c436ab3b1..5440a1f0e 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -55,6 +55,8 @@ + @string/resolution_and_name + @string/name @string/resolution_and_title @string/title @string/resolution @@ -62,6 +64,8 @@ + 5 + 4 3 2 1 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6bef3cbca..0874e6a12 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -894,4 +894,6 @@ Remove watched up to this episode Reloaded Reload Provider + Name + Resolution and name From 6d935602526e8fdf67777018936abbc2a7716cba Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 7 Oct 2025 18:12:46 +0200 Subject: [PATCH 395/962] Fix: Deduplicated subtitles --- .../cloudstream3/ui/player/CS3IPlayer.kt | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index d0461346b..e10d1db97 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -100,6 +100,8 @@ import java.util.UUID import javax.net.ssl.HttpsURLConnection import javax.net.ssl.SSLContext import javax.net.ssl.SSLSession +import kotlin.collections.HashSet +import kotlin.text.StringBuilder const val TAG = "CS3ExoPlayer" const val PREFERRED_AUDIO_LANGUAGE_KEY = "preferred_audio_language" @@ -961,7 +963,8 @@ class CS3IPlayer : IPlayer { // Custom TextOutput to apply cue styling and rules to all subtitles val customTextOutput = TextOutput { cue -> // Do not remove filterNotNull as Java typesystem is fucked - val (bitmapCues, textCues) = cue.cues.filterNotNull().partition { it.bitmap != null } + val (bitmapCues, textCues) = cue.cues.filterNotNull() + .partition { it.bitmap != null } val styledBitmapCues = bitmapCues.map { bitmapCue -> bitmapCue @@ -971,16 +974,38 @@ class CS3IPlayer : IPlayer { .build() } + // Reuse memory, to avoid many allocations + val set = HashSet() + val buffer = StringBuilder() + // Move cues into one single one // This is to prevent text overlap in vtt (and potentially other) subtitle files val styledTextCues = textCues.groupBy { // Groups cues which share the same positon it.lineAnchor to it.position.times(1000.0f).toInt() }.mapNotNull { (_, entries) -> - val combinedCueText = entries.joinToString("\n") { - it.text?.toString() ?: "" + set.clear() + buffer.clear() + var count = 0 + for (x in entries) { + // Only allow non null text, otherwise we might have "a\n\nb" + val text = x.text ?: continue + + // Prevent duplicate entries, this often happens when the subtitle file + // uses multiple text lines as outlines. Most commonly found in fansubs + // with fancy subtitle styling. + if (!set.add(text)) { + continue + } + if (++count > 1) buffer.append('\n') + + // Trim to avoid weird formatting if the last line ends with a newline + buffer.append(text.trim()) } + val combinedCueText = buffer.toString() + + // Use the style of the first entry as the base entries .firstOrNull() ?.buildUpon() From bb77d93b5d133366bc43be6eb40336931884c48b Mon Sep 17 00:00:00 2001 From: Sovan Sinha Date: Tue, 7 Oct 2025 21:53:30 +0530 Subject: [PATCH 396/962] Enhancement : hide controls during volume/brightness gestures and restore on pause (#1954) * player: hide controls during volume/brightness gestures and restore on gesture end if paused * player: reshow controls only if visible pre-gesture; reveal immediately on gesture completion when paused --- .../ui/player/FullScreenPlayer.kt | 32 ++++++++++++++----- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 9a1075f12..08f34470b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -65,6 +65,8 @@ 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.utils.AppContextUtils.isUsingMobileData +import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback +import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.detachBackPressedCallback import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute @@ -85,8 +87,6 @@ import kotlin.math.max import kotlin.math.min import kotlin.math.round import kotlin.math.roundToInt -import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback -import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.detachBackPressedCallback const val MINIMUM_SEEK_TIME = 7000L // when swipe seeking @@ -110,6 +110,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { // state of player UI protected var isShowing = false + private var uiShowingBeforeGesture = false protected var isLocked = false protected var hasEpisodes = false @@ -883,6 +884,13 @@ open class FullScreenPlayer : AbstractPlayerFragment() { delayHide() } + protected fun hidePlayerUI(){ + if (isShowing) { + isShowing = false + animateLayoutChanges() + } + } + override fun playerStatusChanged() { super.playerStatusChanged() delayHide() @@ -1202,6 +1210,15 @@ open class FullScreenPlayer : AbstractPlayerFragment() { currentClickCount = 0 } + // If we hid the UI for a gesture and playback is paused, show it again + if (!player.getIsPlaying()) { + val didGesture = currentTouchAction != null || currentLastTouchAction != null + if (didGesture && uiShowingBeforeGesture && !isShowing) { + isShowing = true + animateLayoutChanges() + } + } + // call auto hide as it wont hide when you have your finger down autoHide() @@ -1213,6 +1230,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { currentTouchStartPlayerTime = null currentTouchLast = null currentTouchStartTime = null + uiShowingBeforeGesture = false // resets UI playerTimeText.isVisible = false @@ -1231,20 +1249,18 @@ open class FullScreenPlayer : AbstractPlayerFragment() { if (currentTouchAction == null) { val diffFromStart = startTouch - currentTouch - if (swipeVerticalEnabled) { if (abs(diffFromStart.y * 100 / screenHeightWithOrientation) > MINIMUM_VERTICAL_SWIPE) { // left = Brightness, right = Volume, but the UI is reversed to show the UI better + uiShowingBeforeGesture = isShowing currentTouchAction = if (startTouch.x < screenWidthWithOrientation / 2) { // hide the UI if you hold brightness to show screen better, better UX - if (isShowing) { - isShowing = false - animateLayoutChanges() - } - + hidePlayerUI() TouchAction.Brightness } else { + // hide the UI if you hold volume to show screen better, better UX + hidePlayerUI() TouchAction.Volume } } From 1f71acabcb691d53564b03dab85efe1dc93562fe Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:44:26 +0200 Subject: [PATCH 397/962] Fix: Single mirror syncdata --- .../actions/temp/PlayMirrorAction.kt | 5 +- .../ui/result/ResultViewModel2.kt | 77 ++++++++++++------- 2 files changed, 52 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayMirrorAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayMirrorAction.kt index acc4d7308..d69619b45 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayMirrorAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayMirrorAction.kt @@ -13,7 +13,6 @@ import com.lagradost.cloudstream3.ui.result.LinkLoadingResult import com.lagradost.cloudstream3.ui.result.ResultEpisode import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.ExtractorLinkType -import com.lagradost.cloudstream3.utils.FillerEpisodeCheck.list import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.txt @@ -53,11 +52,11 @@ class PlayMirrorAction : VideoClickAction() { return true } } - // Took logic from PLAY_EPISODE_IN_APP + activity.navigate( R.id.global_to_navigation_player, GeneratorPlayer.newInstance( - generatorMirror, list + generatorMirror, result.syncData ) ) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 0a37a5b41..183f60ff6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -338,6 +338,7 @@ data class ResumeWatchingStatus( data class LinkLoadingResult( val links: List, val subs: List, + val syncData: HashMap ) sealed class SelectPopup { @@ -766,15 +767,19 @@ class ResultViewModel2 : ViewModel() { subs?.filter { subtitle -> downloadList.any { langTagIETF -> subtitle.languageCode == langTagIETF || - subtitle.originalName.contains(fromTagToEnglishLanguageName(langTagIETF) ?: langTagIETF) + subtitle.originalName.contains( + fromTagToEnglishLanguageName( + langTagIETF + ) ?: langTagIETF + ) } } - ?.map { ExtractorSubtitleLink(it.name, it.url, "", it.headers) } - ?.take(3) // max subtitles download hardcoded (?_?) - ?.forEach { link -> - val fileName = VideoDownloadManager.getFileName(context, meta) - downloadSubtitle(context, link, fileName, folder) - } + ?.map { ExtractorSubtitleLink(it.name, it.url, "", it.headers) } + ?.take(3) // max subtitles download hardcoded (?_?) + ?.forEach { link -> + val fileName = VideoDownloadManager.getFileName(context, meta) + downloadSubtitle(context, link, fileName, folder) + } } catch (e: Exception) { logError(e) } @@ -1415,7 +1420,11 @@ class ResultViewModel2 : ViewModel() { _loadedLinks.postValue(null) } - return LinkLoadingResult(sortUrls(links), sortSubs(subs)) + return LinkLoadingResult( + sortUrls(links), + sortSubs(subs), + HashMap(currentResponse?.syncData ?: emptyMap()) + ) } fun handleAction(click: EpisodeClickEvent) = @@ -1427,16 +1436,23 @@ class ResultViewModel2 : ViewModel() { _episodeSynopsis.postValue(null) } - private fun markEpisodes(editor: Editor,episodeIds: Array,watchState: VideoWatchState) { + private fun markEpisodes( + editor: Editor, + episodeIds: Array, + watchState: VideoWatchState + ) { val watchStateString = DataStore.mapper.writeValueAsString(watchState) episodeIds.forEach { - if(getVideoWatchState(it.toInt()) != watchState){ - editor.setKeyRaw(getFolderName("$currentAccount/$VIDEO_WATCH_STATE", it),watchStateString) + if (getVideoWatchState(it.toInt()) != watchState) { + editor.setKeyRaw( + getFolderName("$currentAccount/$VIDEO_WATCH_STATE", it), + watchStateString + ) } } } - private fun getEpisodesIdsBySeason(season: Int): HashMap> { + private fun getEpisodesIdsBySeason(season: Int): HashMap> { val result = currentEpisodes.entries .asSequence() .filter { it.key.season <= season && it.key.dubStatus == preferDubStatus } @@ -1447,7 +1463,7 @@ class ResultViewModel2 : ViewModel() { .mapValues { (_, ids) -> ids.toTypedArray() } .toMap(HashMap()) - if(season != 0){ + if (season != 0) { result.remove(0) } return result @@ -1490,8 +1506,9 @@ class ResultViewModel2 : ViewModel() { val watchedText = if (isWatched) R.string.action_remove_from_watched else R.string.action_mark_as_watched - val markUpToText = if(isWatched) R.string.action_remove_mark_watched_up_to_this_episode - else R.string.action_mark_watched_up_to_this_episode + val markUpToText = + if (isWatched) R.string.action_remove_mark_watched_up_to_this_episode + else R.string.action_mark_watched_up_to_this_episode options.add(txt(watchedText) to ACTION_MARK_AS_WATCHED) @@ -1643,9 +1660,8 @@ class ResultViewModel2 : ViewModel() { } ACTION_PLAY_EPISODE_IN_PLAYER -> { - val data = currentResponse?.syncData?.toList() ?: emptyList() - val list = - HashMap().apply { putAll(data) } + val list = HashMap(currentResponse?.syncData ?: emptyMap()) + generator?.also { it.getAll() // I know kinda shit to iterate all, but it is 100% sure to work ?.indexOfFirst { value -> value is ResultEpisode && value.id == click.data.id } @@ -1682,18 +1698,22 @@ class ResultViewModel2 : ViewModel() { reloadEpisodes() } - ACTION_MARK_WATCHED_UP_TO_THIS_EPISODE -> ioSafe{ - val editor = context?.let { it1 -> editor(it1,false) } + ACTION_MARK_WATCHED_UP_TO_THIS_EPISODE -> ioSafe { + val editor = context?.let { it1 -> editor(it1, false) } if (editor != null) { - val (clickSeason,clickEpisode) = click.data.let { (it.season ?: 0) to it.episode } - val watchState = if (getVideoWatchState(click.data.id) == VideoWatchState.Watched) VideoWatchState.None else VideoWatchState.Watched - val seasons = getEpisodesIdsBySeason(clickSeason) + val (clickSeason, clickEpisode) = click.data.let { + (it.season ?: 0) to it.episode + } + val watchState = + if (getVideoWatchState(click.data.id) == VideoWatchState.Watched) VideoWatchState.None else VideoWatchState.Watched + val seasons = getEpisodesIdsBySeason(clickSeason) - seasons.keys.forEach {currentSeason -> + seasons.keys.forEach { currentSeason -> var episodeIds = seasons[currentSeason] ?: emptyArray() - if(currentSeason == clickSeason) episodeIds = episodeIds.sliceArray(0 until clickEpisode) - markEpisodes(editor,episodeIds,watchState) + if (currentSeason == clickSeason) episodeIds = + episodeIds.sliceArray(0 until clickEpisode) + markEpisodes(editor, episodeIds, watchState) } editor.apply() reloadEpisodes() @@ -1992,7 +2012,10 @@ class ResultViewModel2 : ViewModel() { return when (sorting) { EpisodeSortType.NUMBER_ASC -> episodes.sortedBy { it.episode } EpisodeSortType.NUMBER_DESC -> episodes.sortedByDescending { it.episode } - EpisodeSortType.RATING_HIGH_LOW -> episodes.sortedByDescending { it.score?.toDouble() ?: 0.0 } + EpisodeSortType.RATING_HIGH_LOW -> episodes.sortedByDescending { + it.score?.toDouble() ?: 0.0 + } + EpisodeSortType.RATING_LOW_HIGH -> episodes.sortedBy { it.score?.toDouble() ?: 0.0 } EpisodeSortType.DATE_NEWEST -> episodes.sortedByDescending { it.airDate } EpisodeSortType.DATE_OLDEST -> episodes.sortedBy { it.airDate } From b63990da38cdb6d084a0bf2ab5bd554da526ebe3 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Tue, 7 Oct 2025 23:54:33 +0200 Subject: [PATCH 398/962] Fix: Hero banner shown in None. Closes #1957 --- .../cloudstream3/ui/home/HomeParentItemAdapterPreview.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index c2f06c021..9df98e439 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -624,6 +624,9 @@ class HomeParentItemAdapterPreview( previewViewpager.isVisible = true previewViewpagerText.isVisible = true alternativeAccountPadding?.isVisible = false + (binding as? FragmentHomeHeadTvBinding)?.apply { + homePreviewInfoBtt.isVisible = true + } } else -> { @@ -632,6 +635,9 @@ class HomeParentItemAdapterPreview( previewViewpager.isVisible = false previewViewpagerText.isVisible = false alternativeAccountPadding?.isVisible = true + (binding as? FragmentHomeHeadTvBinding)?.apply { + homePreviewInfoBtt.isVisible = false + } //previewHeader.isVisible = false } } From 3a56d6d36c3a96eb850f49b52737b561bd60f3e4 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Wed, 8 Oct 2025 01:11:15 +0200 Subject: [PATCH 399/962] Feat(UI): New speed dialog, Closes #1889 --- .../ui/player/FullScreenPlayer.kt | 108 +++++++++---- app/src/main/res/layout/speed_dialog.xml | 145 ++++++++++++++++++ 2 files changed, 219 insertions(+), 34 deletions(-) create mode 100644 app/src/main/res/layout/speed_dialog.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 08f34470b..f6f68eaf2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -5,6 +5,7 @@ import android.annotation.SuppressLint import android.app.Activity import android.app.Dialog import android.content.Context +import android.content.DialogInterface import android.content.pm.ActivityInfo import android.content.res.ColorStateList import android.content.res.Configuration @@ -33,6 +34,7 @@ import android.view.animation.Animation import android.view.animation.AnimationUtils import android.widget.LinearLayout import androidx.annotation.OptIn +import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.graphics.blue import androidx.core.graphics.green @@ -56,6 +58,7 @@ import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.PlayerCustomLayoutBinding +import com.lagradost.cloudstream3.databinding.SpeedDialogBinding import com.lagradost.cloudstream3.databinding.SubtitleOffsetBinding import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.player.GeneratorPlayer.Companion.subsProvidersIsActive @@ -68,7 +71,6 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.isUsingMobileData import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.detachBackPressedCallback import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.getNavigationBarHeight @@ -491,7 +493,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { activity?.attachBackPressedCallback("FullScreenPlayer") { if (isShowingEpisodeOverlay) { // isShowingEpisodeOverlay pauses, so this makes it easier to unpause - if(isLayout(TV or EMULATOR)) { + if (isLayout(TV or EMULATOR)) { playerPausePlay?.requestFocus() } toggleEpisodesOverlay(show = false) @@ -655,39 +657,76 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } - private fun showSpeedDialog() { - val speedsText = - listOf( - "0.5x", - "0.75x", - "0.85x", - "1x", - "1.15x", - "1.25x", - "1.4x", - "1.5x", - "1.75x", - "2x" - ) - val speedsNumbers = - listOf(0.5f, 0.75f, 0.85f, 1f, 1.15f, 1.25f, 1.4f, 1.5f, 1.75f, 2f) - val speedIndex = speedsNumbers.indexOf(player.getPlaybackSpeed()) + @SuppressLint("SetTextI18n") + fun updateSpeedDialogBinding(binding: SpeedDialogBinding) { + val speed = player.getPlaybackSpeed() + binding.speedText.text = "%.2fx".format(speed).replace(".0x", "x") + // Android crashes if you don't round to an exact step size + binding.speedBar.value = (speed.coerceIn(0.1f, 2.0f) / binding.speedBar.stepSize).roundToInt().toFloat() * binding.speedBar.stepSize + } - activity?.let { act -> - act.showDialog( - speedsText, - speedIndex, - act.getString(R.string.player_speed), - false, - { - if (isFullScreenPlayer) - activity?.hideSystemUI() - }) { index -> - if (isFullScreenPlayer) - activity?.hideSystemUI() - setPlayBackSpeed(speedsNumbers[index]) + private fun showSpeedDialog() { + val act = activity ?: return + val isPlaying = player.getIsPlaying() + player.handleEvent(CSPlayerEvent.Pause, PlayerEventSource.UI) + + val binding: SpeedDialogBinding = SpeedDialogBinding.inflate( + LayoutInflater.from(act) + ) + + updateSpeedDialogBinding(binding) + for ((view, speed) in arrayOf( + binding.speed25 to 0.25f, + binding.speed100 to 1.0f, + binding.speed125 to 1.25f, + binding.speed150 to 1.5f, + binding.speed200 to 2.0f, + )) { + view.setOnClickListener { + setPlayBackSpeed(speed) + updateSpeedDialogBinding(binding) } } + + binding.speedMinus.setOnClickListener { + setPlayBackSpeed(maxOf((player.getPlaybackSpeed() - 0.1f), 0.1f)) + updateSpeedDialogBinding(binding) + } + + binding.speedPlus.setOnClickListener { + setPlayBackSpeed(minOf((player.getPlaybackSpeed() + 0.1f), 2.0f)) + updateSpeedDialogBinding(binding) + } + + binding.speedBar.addOnChangeListener { slider, value, fromUser -> + if (fromUser) { + setPlayBackSpeed(value) + updateSpeedDialogBinding(binding) + } + } + + val dismiss = DialogInterface.OnDismissListener { + if (isFullScreenPlayer) + activity?.hideSystemUI() + if (isPlaying) { + player.handleEvent(CSPlayerEvent.Play, PlayerEventSource.UI) + } + } + + //if (isLayout(PHONE)) { + // val builder = + // BottomSheetDialog(act, R.style.AlertDialogCustom) + // builder.setContentView(binding.root) + // builder.setOnDismissListener(dismiss) + // builder.show() + //} else { + val builder = + AlertDialog.Builder(act, R.style.AlertDialogCustom) + .setView(binding.root) + builder.setOnDismissListener(dismiss) + val dialog = builder.create() + dialog.show() + //} } fun resetRewindText() { @@ -884,7 +923,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { delayHide() } - protected fun hidePlayerUI(){ + protected fun hidePlayerUI() { if (isShowing) { isShowing = false animateLayoutChanges() @@ -1212,7 +1251,8 @@ open class FullScreenPlayer : AbstractPlayerFragment() { // If we hid the UI for a gesture and playback is paused, show it again if (!player.getIsPlaying()) { - val didGesture = currentTouchAction != null || currentLastTouchAction != null + val didGesture = + currentTouchAction != null || currentLastTouchAction != null if (didGesture && uiShowingBeforeGesture && !isShowing) { isShowing = true animateLayoutChanges() diff --git a/app/src/main/res/layout/speed_dialog.xml b/app/src/main/res/layout/speed_dialog.xml new file mode 100644 index 000000000..bb97914f1 --- /dev/null +++ b/app/src/main/res/layout/speed_dialog.xml @@ -0,0 +1,145 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 0281eb418561b28effe39c6576f679c8d480b95a Mon Sep 17 00:00:00 2001 From: KingLucius Date: Fri, 10 Oct 2025 20:27:53 +0300 Subject: [PATCH 400/962] PercentageCropImageView KDoc and XML usage (#1970) --- .../cloudstream3/ui/home/HomeScrollAdapter.kt | 2 - .../ui/result/ResultFragmentTv.kt | 2 - .../utils/PercentageCropImageView.kt | 75 ++++++++++++++++++- .../main/res/layout/fragment_result_tv.xml | 1 + .../main/res/layout/home_scroll_view_tv.xml | 2 + app/src/main/res/values/attrs.xml | 5 ++ 6 files changed, 81 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt index 3a6ed4923..7c87542af 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeScrollAdapter.kt @@ -59,8 +59,6 @@ class HomeScrollAdapter( } is HomeScrollViewTvBinding -> { - //Change poster crop area to 20% from Top - binding.homeScrollPreview.cropYCenterOffsetPct = 0.2f binding.homeScrollPreview.isFocusable = false binding.homeScrollPreview.setOnClickListener { view -> callback.invoke(view ?: return@setOnClickListener, position, item) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt index bb6c28f0b..cacf02942 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultFragmentTv.kt @@ -939,8 +939,6 @@ class ResultFragmentTv : Fragment() { R.drawable.profile_bg_red, R.drawable.profile_bg_teal ).random() - //Change poster crop area to 20% from Top - backgroundPoster.cropYCenterOffsetPct = 0.20F backgroundPoster.loadImage(d.posterBackgroundImage) { error { getImageFromDrawable(context ?: return@error null, error) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt index 1e572fb7c..5f070ef06 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PercentageCropImageView.kt @@ -4,16 +4,65 @@ import android.content.Context import android.graphics.Matrix import android.graphics.drawable.Drawable import android.util.AttributeSet +import com.lagradost.cloudstream3.R +/** + * A custom [AppCompatImageView] that allows precise control over the visible crop area + * of an image by adjusting its horizontal and vertical center offset percentages. + * + * ### Key Features: + * - Allows **manual vertical or horizontal cropping** via percentage offsets. + * - Works seamlessly with Coil, Glide, or any image loading library. + * + * ### Usage (XML): + * You can set the crop offset directly in XML using custom attributes: + * ```xml + * + * ``` + * - `app:cropYCenterOffsetPct` → controls how far vertically the image shifts + * `0.0` = top-aligned, `0.5` = centered, `1.0` = bottom-aligned. + * - `app:cropXCenterOffsetPct` → controls how far horizontally the image shifts + * `0.0` = left, `0.5` = center, `1.0` = right. + * + * ### Programmatic Example: + * ```kotlin + * imageView.cropYCenterOffsetPct = 0.15f // Show slightly more (15%) of the top area + * imageView.cropXCenterOffsetPct = 0.5f // Keep image centered horizontally + * imageView.redraw() //Only needed if you changed cropYCenterOffsetPct/cropXCenterOffsetPct at runtime + * ``` + * + * ### Notes: + * - Must use `android:scaleType="matrix"` to enable manual matrix transformations. + * - Reference: https://stackoverflow.com/a/29055283 + * + * @property cropYCenterOffsetPct the vertical crop percentage (0.0–1.0) + * @property cropXCenterOffsetPct the horizontal crop percentage (0.0–1.0) + * + * @see ImageView.ScaleType.MATRIX + */ class PercentageCropImageView : androidx.appcompat.widget.AppCompatImageView { private var mCropYCenterOffsetPct: Float? = null private var mCropXCenterOffsetPct: Float? = null + constructor(context: Context?) : super(context!!) - constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) + + constructor(context: Context?, attrs: AttributeSet?) : super(context!!, attrs) { + initAttrs(context, attrs) + } + constructor( context: Context?, attrs: AttributeSet?, defStyle: Int - ) : super(context!!, attrs, defStyle) + ) : super(context!!, attrs, defStyle) { + initAttrs(context, attrs) + } var cropYCenterOffsetPct: Float get() = mCropYCenterOffsetPct!! @@ -80,6 +129,7 @@ class PercentageCropImageView : androidx.appcompat.widget.AppCompatImageView { super.setImageResource(resId) myConfigureBounds() } + // In case you can change the ScaleType in code you have to call redraw() //fullsizeImageView.setScaleType(ScaleType.FIT_CENTER); //fullsizeImageView.redraw(); @@ -91,4 +141,25 @@ class PercentageCropImageView : androidx.appcompat.widget.AppCompatImageView { setImageDrawable(d) } } + + private fun initAttrs(context: Context, attrs: AttributeSet?) { + attrs ?: return + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.PercentageCropImageView) + try { + if (typedArray.hasValue(R.styleable.PercentageCropImageView_cropYCenterOffsetPct)) { + mCropYCenterOffsetPct = typedArray.getFloat( + R.styleable.PercentageCropImageView_cropYCenterOffsetPct, + 0.5f + ) + } + if (typedArray.hasValue(R.styleable.PercentageCropImageView_cropXCenterOffsetPct)) { + mCropXCenterOffsetPct = typedArray.getFloat( + R.styleable.PercentageCropImageView_cropXCenterOffsetPct, + 0.5f + ) + } + } finally { + typedArray.recycle() + } + } } \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_result_tv.xml b/app/src/main/res/layout/fragment_result_tv.xml index 57624bbf1..aa674c44b 100644 --- a/app/src/main/res/layout/fragment_result_tv.xml +++ b/app/src/main/res/layout/fragment_result_tv.xml @@ -33,6 +33,7 @@ https://developer.android.com/design/ui/tv/samples/jet-fit android:layout_gravity="center" android:alpha="0.8" android:scaleType="matrix" + app:cropYCenterOffsetPct="0.20" tools:src="@drawable/profile_bg_dark_blue" /> @@ -10,6 +11,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="matrix" + app:cropYCenterOffsetPct="0.20" tools:src="@drawable/example_poster" /> + + + + + \ No newline at end of file From 141a1a2adb9ec4ca15ee22a1d7a6c25a5f362d66 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Fri, 10 Oct 2025 19:28:06 +0200 Subject: [PATCH 401/962] Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (818 of 818 strings) Translated using Weblate (Spanish) Currently translated at 99.5% (814 of 818 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (818 of 818 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (818 of 818 strings) Translated using Weblate (Polish) Currently translated at 100.0% (818 of 818 strings) Translated using Weblate (Czech) Currently translated at 100.0% (818 of 818 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Malay) Currently translated at 74.5% (608 of 816 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 98.8% (807 of 816 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Turkish) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Macedonian) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Arabic (Levantine)) Currently translated at 99.1% (809 of 816 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 98.7% (806 of 816 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Spanish) Currently translated at 99.5% (812 of 816 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Polish) Currently translated at 100.0% (816 of 816 strings) Translated using Weblate (Czech) Currently translated at 100.0% (816 of 816 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Vietnamese) Currently translated at 100.0% (815 of 815 strings) Co-authored-by: Adrián Gelmotto Ruiz Co-authored-by: Dört Koldan Taciz Co-authored-by: Fjuro Co-authored-by: Hosted Weblate Co-authored-by: Jimly Asshiddiqy Co-authored-by: Lenny Tran Co-authored-by: Matthaiks Co-authored-by: Mohammad Abdallah Co-authored-by: Oğuz Ersen Co-authored-by: Saúl Palacios Co-authored-by: diogob003 Co-authored-by: kerklangsi Co-authored-by: stojkovskistefan Co-authored-by: Максим Горпиніч Co-authored-by: 大王叫我来巡山 Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/apc/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/cs/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/es/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/id/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/mk/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ms/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pl/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/tr/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/uk/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/vi/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/zh_Hans/ Translation: Cloudstream/App --- app/src/main/res/values-b+apc/strings.xml | 8 +- app/src/main/res/values-b+cs/strings.xml | 5 +- app/src/main/res/values-b+es/strings.xml | 9 +- app/src/main/res/values-b+in/strings.xml | 3 +- app/src/main/res/values-b+mk/strings.xml | 7 +- app/src/main/res/values-b+ms/strings.xml | 34 +++- app/src/main/res/values-b+pl/strings.xml | 5 +- app/src/main/res/values-b+pt+BR/strings.xml | 189 ++++++++++---------- app/src/main/res/values-b+tr/strings.xml | 9 +- app/src/main/res/values-b+uk/strings.xml | 5 +- app/src/main/res/values-b+vi/strings.xml | 6 +- app/src/main/res/values-b+zh/strings.xml | 5 +- 12 files changed, 166 insertions(+), 119 deletions(-) diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index c6a429fb7..0e7a88ab0 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -716,9 +716,13 @@ حجم الپوستر دايمًا سئالوني خليك كابس حتى تسرع - خليك كابس كرمال صرعة الـ2x + خليك كابس كرمال سرعة الـ2x %1$dساعة %2$dد %3$dث %1$dد %2$dث %1$dث لايبل الرايتينگ - \ No newline at end of file + لا يوجد حساب + عدّل صورة الملف + ادخل لينك ( عنوان ال URL ) تبع صورة الملف + تم تعديل الصورة بنجاح + diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index f58b65859..e06b0cbbe 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -750,4 +750,7 @@ Odebrat zhlédnutí po tuto epizodu Znovu načteno Znovu načíst poskytovatele - \ No newline at end of file + Přehrát ze zrcadla" + Název + Rozlišení a název + diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index 98adc1bec..0209824e1 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -700,7 +700,7 @@ Preguntar siempre Descargas en paralelo Conexiones concurrentes - Cuántas conexiones concurrentes para cada descarga se pueden usar + Cuántas conexiones concurrentes para cada descarga se pueden usar durante un proceso de descarga Ir a Descargas Sin conexión a internet. \n\nConéctese a internet y vuelva a intentarlo, o mire sus descargas mientras está sin conexión. Cambios en los límites de la pantalla @@ -719,4 +719,9 @@ Marcar como vigilado en este episodio Retirar vigilado para este episodio Recargado - \ No newline at end of file + Reproducir en espejo" + Editar imagen de perfil + Introducir URL de imagen de perfil + Recargar proveedor + Nombre + diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index 75e544b30..e435f5c96 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -746,4 +746,5 @@ Hapus penandaan telah ditonton hingga episode ini Dimuat Ulang Muat Ulang Penyedia - \ No newline at end of file + Putar mirror" + diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml index 9c1cddd0f..478077260 100644 --- a/app/src/main/res/values-b+mk/strings.xml +++ b/app/src/main/res/values-b+mk/strings.xml @@ -235,7 +235,7 @@ Изглед на емулатор Видео Исчисти - Положен + Успешна верификација Име на сајт Неважечки податоци Поддршка @@ -401,7 +401,7 @@ Прикажан плеер - Барај износ Андроид ТВ Не успеа да ги врати податоците од датотеката %s - Не успеа + Неуспешна верификација Документарец Мрежен проток %d мин @@ -702,4 +702,5 @@ Избриши изгледано до оваа епизода Повторно вчитано Повторно вчитај провајдер - \ No newline at end of file + Пушти друг линк" + diff --git a/app/src/main/res/values-b+ms/strings.xml b/app/src/main/res/values-b+ms/strings.xml index bd4462e21..77532da7d 100644 --- a/app/src/main/res/values-b+ms/strings.xml +++ b/app/src/main/res/values-b+ms/strings.xml @@ -106,7 +106,7 @@ Sambung muat turun Storan dalaman Carian mengikut sumber - %d Benenes yang diberikan kepada pemaju + %d Pisang yang diberikan kepada pemaju Saiz Teks Mohon Warna teks @@ -118,7 +118,7 @@ Sambung tonton Buang Lebih Maklumat - Sumber ini adalah torrent, penggunaan VPN disyorkan + Sumber ini adalah torrent, penggunaan VPN digalakkan Metadata tidak diberikan oleh laman web, video lalai akan gagala sekiranya tidak wujud di laman web. Penerangan Papar Logcat🐈 @@ -137,13 +137,13 @@ Pilih semua Nyahpilih semua VPN mungkin diperlukan untuk sumber ini berjalan dengan lancar - Sambung pasang dalam pemain mini di atas aplikasi lain + Sambung Rakaman didalam pemain mini di atas aplikasi lain Sari kata Tetapan sari kata pemain Chromecast Sari kata Kelajuan pemain Auto-pasang episod seterusnya - Gunakan terang system + Gunakan keterangan sistem Cuba sambungan talian… Informasi Lihat info ralat @@ -401,7 +401,7 @@ Beri hasil carian dipisah mengikut pelbagai sumber Aplikasi Light novel oleh pembangun sama Aplikasi Anime oleh pembangun sama - Masuk Discord + Menyertai Discord Padam Fail Padam (%1$d | %2$s) Muatkan ke skrin @@ -523,4 +523,26 @@ Gagal pulihkan data dari fail %s Ralat sandaran %s Hanya hantar data apabila mengalami kegagalan - \ No newline at end of file + + Episode + + Akan datang pada %s + Ini akan memadamkan secara kekal %s\nAdakah anda pasti? + Adakah anda pasti mahu memadamkan item berikut secara kekal?\n\n%s + Adakah anda pasti mahu memadamkan episod berikut secara kekal %1$s?\n\n%2$s + Anda juga akan memadamkan semua episod dalam siri berikut secara kekal:\n\n%s + Adakah anda pasti mahu memadamkan semua episod dalam siri berikut secara kekal?\n\n%s + %s\nyang tinggal + Sedang berlangsung + Penilaian + Filem + Torrents + Dokumentari + Siaran Langsung + Movie + Torrent + Dokumentari + Siaran Langsung + Audio + Podcast + diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index 2326d9d8f..4d153a031 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -727,4 +727,7 @@ Oznacz jako obejrzane do tego odcinka Przeładowano Przeładuj dostawcę - \ No newline at end of file + Odtwarzaj inne źródło" + Nazwa + Rozdzielczość i nazwa + diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index 688dbfcad..0557b5399 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -12,16 +12,15 @@ Poster Pôster Pôster do episódio - Pôster Principal + Pôster principal Próximo Aleatório Voltar - Alterar Provedor + Alterar provedor Visualizar plano de fundo Velocidade (%.2fx) Avaliado: %.1f - Nova atualização encontrada! -\n%1$s -> %2$s + Nova atualização encontrada! \n%1$s → %2$s Preenchimento %d min CloudStream @@ -42,8 +41,8 @@ Assistindo Em espera Concluído - Desistido - Planejando assistir + Desisti de ver + Planejo assistir Reassistindo Reproduzir filme Transmitir Torrent @@ -54,7 +53,7 @@ Reproduzir episódio Download - Transferido + Baixado Baixando Download pausado Download iniciado @@ -65,20 +64,20 @@ Erro ao carregar links Armazenamento interno Dub - Sub - Deletar arquivo + Leg + Apagar arquivo Reproduzir arquivo - Retomar download + Continuar download Pausar download Desative o relatório automático de erros Mais informações - Esconder + Ocultar Reproduzir Informações Filtrar marcadores Marcadores Remover - Definir como assistido/não assistido + Adicionar a lista Aplicar Copiar Fechar @@ -88,7 +87,7 @@ Configurações de legendas Cor do texto Cor do contorno - Cor de fundo + Cor do fundo Cor da janela Tipo de borda Elevação da legenda @@ -98,14 +97,14 @@ Pesquisar usando tipos %d Benenes doados aos desenvolvedores Nenhuma Benenes doada - Seleção automática de idioma - Baixar idiomas - Idioma da Legenda + Escolher automaticamente idioma + Salvar idiomas ao baixar + Idioma da legenda Segure para redefinir para o padrão Importe fontes colocando-as em %s Continuar assistindo Remover - Mais Info + Mais detalhes @string/home_play Uma VPN pode ser necessária para esse fornecedor funcionar corretamente Esse fornecedor é um torrent, uma VPN é recomendada @@ -115,16 +114,16 @@ Descrição não encontrada Mostrar Logcat 🐈 Picture-in-picture - Continua a reprodução em um player miniatura que sobrepõe outros aplicativos + Continua o vídeo em tamanho miniatura flutuando sobre outros aplicativos Redimensionar player Remover bordas pretas - Legendas - Configurações de legendas do Player - Legendas do Chromecast - Configurações de legendas do Chromecast - Velocidade de playback + Modificar legendas + Alterar legendas exibidas no player de vídeo + Modificar legendas do Chromecast + Alterar legendas exibidas no Chromecast + Velocidade do vídeo Deslize para avançar o vídeo - Deslize de lado à lado para controlar a posição no vídeo + Deslizar dedo para os lados para voltar ou avançar o vídeo Deslize para mudar as configurações Deslize para cima ou para baixo, para ajustar brilho ou volume Toque duplo para avançar o vídeo @@ -143,54 +142,53 @@ Dados salvos Permissões de armazenamento faltando. Por favor tente novamente. Erro no backup de %s - Procurar + Pesquisar Contas e Segurança Atualizações e Backup Info - Procura Avançada + Pesquisa avançada Mostrar resultados separados por fornecedor - Só enviar dados sobre travamentos + Enviar apenas dados de falhas Não enviar nenhum dado - Mostrar episódios de Filler em anime + Mostrar episódios extras de animes Mostrar trailers Mostrar posters do Kitsu Esconder qualidades de vídeo selecionadas nos resultados da pesquisa - Atualizações de plugin automáticas + Atualização automática de plugins Mostrar atualizações do app Automaticamente procurar por novas atualizações ao abrir. - Atualizar para pré-lançamento - Procura atualizações do pré-lançamento ao invés de apenas do lançamento oficial - Github - App de Light novel pelos mesmos desenvolvedores - App de Anime pelos mesmos desenvolvedores - Junte-se ao Discord - Dar um benene para os desenvolvedores + Permitir versões em pré-lançamento + Permitir a instação de versões em pré-lançamento ao invés de somente versões estáveis + GitHub + App de Light novel criado pelos mesmos desenvolvedores + App de Anime criado pelos mesmos desenvolvedores + Entre no servidor do Discord + Dar um benene aos desenvolvedores Benene dada Idioma do aplicativo Esse fornecedor não possui suporte para Chromecast Nenhum link encontrado Link copiado para área de transferência - Assistir Episódio + Assistir episódio Restaurar para o padrão - Desculpe, a aplicação travou. Um relatório de erro anônimo será enviado para os desenvolvedores + Desculpe :/, o aplicativo travou. Um relatório de erro anônimo será enviado aos desenvolvedores Temporada - Nenhuma Temporada + Nenhuma temporada Episódio Episódios - S + T E - Nenhum Episódio encontrado - Apagar Arquivo - Deletar + Nenhum episódio encontrado + Apagar arquivo + Apagar Cancelar Pausar - Retomar + Continuar -30 +30 Isso apagará %s permanentemente \nVocê tem certeza? - %dm -\nsobrando + %dm\nrestantes Em andamento Concluído Estado @@ -200,15 +198,15 @@ Site Sinopse Na fila - Sem Legendas + Sem legendas Padrão Livre Usado - App + CloudStream Filmes Séries - Desenhos Animados + Desenhos animados Anime Torrents Documentários @@ -226,48 +224,48 @@ Documentário Drama Asiático Transmissão ao vivo - Erro de fornecimento - Erro remoto + Erro ao abrir arquivo + Erro no servidor Erro de renderização - Erro de player inesperado + Erro inesperado no player Erro ao baixar, verifique as permissões de armazenamento Episódio pelo Chromecast Alternativa pelo Chromecast Assistir no App Assistir no %s - Auto download - Baixar por servidor alternativo + Baixar primeiro disponível + Escolher onde baixar Recarregar links Baixar legendas - Etiqueta de qualidade - Etiqueta Dub - Etiqueta Sub + Indicação de qualidade do vídeo + Indicação Dub + Indicação Sub Título - Alternar elementos da interface no pôster - Nenhuma Atualização encontrada - Procurar nova Atualização - Bloquear - Mudar Tamanho + Escolher o que mostrar sobre o pôster + Nenhuma atualização encontrada + Procurar atualização + Bloquear toques + Redimensionar Fonte - Pular Abertura + Pular abertura Não mostrar de novo - Pular essa Atualização + Pular esta atualização Atualizar Qualidade preferida de reprodução (Wi-fi) Máximo de caracteres do título de vídeos Resolução do player de vídeo Tamanho do buffer do vídeo - Comprimento do buffer do vídeo + Duração do buffer do vídeo Cache do vídeo em disco Limpar cache de vídeo e imagem Causará travamentos se o valor escolhido for muito alto em dispositivos com pouca memória RAM, como um Android TV. Causa problemas em sistemas com pouco espaço de armazenamento se definido muito alto, como em dispositivos Android TV. - DNS sobre HTTPS + DNS através de HTTPS Útil para burlar bloqueios de provedores de internet - Clonar site + Adicionar site alternativo Remover site Adiciona um clone de um site existente, com uma URL diferente - Caminho para Download + Onde salvar Downloads URL do servidor NGINX Mostrar Anime Dublado/Legendado Ajustar para a Tela @@ -444,7 +442,7 @@ Log do Teste Baixar plugins automaticamente Selecione o modo para filtrar os plugins baixados - Teste falhou + Reprovou nos testes A Barra de Progresso pode ser usada quando o player estiver visível Organizar Sim @@ -458,7 +456,7 @@ Alfabética(A => Z) Abrir com Selecionar Biblioteca - Passou + Passou nos testes Sua biblioteca está vazia :0 \nEntre numa conta de biblioteca ou adicione Midias para sua biblioteca local. Qualidade preferida de reprodução (Dados Móveis) @@ -470,7 +468,7 @@ Atualização iniciada Conteúdo +18 Ajuda - Processo de configuração de Redo + Refazer configurações iniciais Não foi possível instalar a nova versão do aplicativo instalador de pacotes Organizar por @@ -491,7 +489,7 @@ Episódio %d lançado! Selecionar padrão Inscrição cancelada de %s - Alguns aparelhos não possuem suporte para este pacote de instalação. Tente a opção legada se a atualização não instalar. + Alguns aparelhos não permitem instalação automática. Tente a opção legada (manual) se a atualização falhar. Dados móveis Perfil %d Atualizando shows inscritos @@ -533,7 +531,7 @@ Limpar historico Tem Muito texto. Não é possível salvar no clipboard. Player de vídeo preferido - Começar + Iniciar Suportado Status Abrindo mistura @@ -546,7 +544,7 @@ 18+ Links Funcionalidades do Player - Instalador APK + Instalador de APK Aparência Desativar Usar @@ -607,7 +605,7 @@ Notificação de novo episódio Pesquisar em outras extensões Mostrar recomendações - Adiciona uma opção de velocidade no reprodutor + Mostrar botão de velocidade do vídeo Testar todas as extensões Esse teste é feito somente para desenvolvedores e não verifica ou nega o funcionamento de qualquer extensão. Desbloquear CloudStream @@ -634,8 +632,8 @@ Áudio-livro Mídia Redefinir - Próximos em %s - Temporada %1$d Episódio %2$d será lançado em + Próximo em %s + %2$dº episódio da %1$dª temporada estreia em Selecione o dispositivo de transmissão Espelhar transmissão CloudStream Wiki @@ -651,30 +649,22 @@ O código expira em %1$dm %2$ds hide_player_control_names_key Reproduzir do começo - Atenção + Reprovou alguns testes Excluir plugin - Não há downloads no momento. + Você não baixou nada :/ Ocultar os nomes dos controles do player - Abrir vídeo local + Abrir arquivo de vídeo Data de lançamento (do novo ao antigo) Data de lançamento (do antigo para o novo) Selecionar itens para excluir - Excluir arquivos + Apagar arquivos Disponível para assistir offline Selecionar todos - Tem certeza de que deseja excluir permanentemente os seguintes itens? -\n -\n%s - Tem certeza de que deseja excluir permanentemente os seguintes episódios em %1$s? -\n -\n%2$s - Você também excluirá permanentemente todos os episódios da seguinte série: -\n -\n%s - Tem certeza de que deseja excluir permanentemente todos os episódios da seguinte série? -\n -\n%s - Excluir (%1$d | %2$s) + Tem certeza que deseja apagar permanentemente os seguintes itens?\n\n%s + Tem certeza que deseja apagar permanentemente os seguintes episódios em %1$s?\n\n%2$s + Você também apagará permanentemente todos os episódios da seguinte série:\n\n%s + Tem certeza que deseja apagar permanentemente todos os episódios da seguinte série?\n\n%s + Apagar %1$d (%2$s) Desmarcar todos Ativar visualização de miniatura na barra de busca Visualização da barra de busca @@ -686,9 +676,9 @@ Local da pasta de backup Personalizado Tamanho da borda - Este vídeo é um Torrent, o que significa que sua atividade de vídeo pode ser rastreada.\nCertifique-se de que você entendeu o que é Torrenting antes de continuar. + Este vídeo é um Torrent, isso significa que sua atividade de vídeo pode ser rastreada.\nTenha certeza de entender o que é Torrent antes de continuar. Erro de codificação - Erro não suportado + Erro, formato não suportado Carregar primeiro disponível Áudio Podcast @@ -712,7 +702,7 @@ Atualizar plugins manualmente Notificações de reprodução Notificação do player para controlar a reprodução em segundo plano - O reconhecimento de fala não está disponível + Reconhecimento de fala indisponível Comece a falar… Incorporada Online @@ -724,7 +714,7 @@ %1$dh %2$dm %3$ds %1$dm %2$ds %1$d s - Rótulo de Classificação + Indicação da classificação Perguntar sempre Quantos itens diferentes podem ser baixados em paralelo Downloads em paralelo @@ -739,4 +729,5 @@ Alternar Velocidade do Pressionamento Longo Segure para duplicar a velocidade Sem conta - \ No newline at end of file + Espelhar vídeo" + diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml index b2ff41831..c9a16a858 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -766,4 +766,11 @@ URL bulunamadı Geçersiz Bağlantı veya Görsel Görsel Başarıyla Güncellendi - \ No newline at end of file + Ekranda oynat" + Bu bölümü izlenmiş olarak işaretle + Bu bölümü izlenmemiş olarak işaretle + Yeniden yükle + Sağlayıcıyı yeniden yükle + Ad + Çözünürlük ve ad + diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index 222c60e5f..0c324f0ae 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -698,4 +698,7 @@ Вилучити переглянуті до цього епізоду Перезавантажено Постачальник послуг поповнення рахунку - \ No newline at end of file + Грати в дзеркало" + Ім\'я + Роздільна здатність та назва + diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index 0bc141d24..67f31a41f 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -735,4 +735,8 @@ Không tìm thấy url URL hoặc hình không hợp lệ Tải hình lên thành công - \ No newline at end of file + Đánh dấu là đã xem đến tập này + Xóa những tập đã xem đến tập này + Đã tải lại + Tải lại nguồn phát + diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 1f1c3645f..b5c9a1ecf 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -770,4 +770,7 @@ 去除这一集和之前集数的已观看状态 已重新加载 重新加载视频源 - \ No newline at end of file + 播放镜像" + 名称 + 分辨率和名称 + From 053692b6310934dcd4e62112e348eaa585c5952f Mon Sep 17 00:00:00 2001 From: Red-To-Tel Date: Fri, 10 Oct 2025 19:38:45 +0200 Subject: [PATCH 402/962] Update InAppUpdater.kt to fix white text on white background issue (#1967) --- .../main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 8bce8f639..12befafe0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -288,7 +288,7 @@ class InAppUpdater { ) } - val builder: AlertDialog.Builder = AlertDialog.Builder(this) + val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) builder.setTitle( getString(R.string.new_update_format).format( currentVersion?.versionName, From 0b73841e095223403351a6f8b14bc4f06230da17 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 10 Oct 2025 19:39:35 +0200 Subject: [PATCH 403/962] Fix: AlertDialogCustom inheritance fix --- app/src/main/res/values/styles.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index d3981e073..b1d76b39e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -531,7 +531,7 @@ ?attr/colorPrimary - + + + + + + + + + + + + + + + + + From a95d8ddc781f4e7d99c18d9f05b1e9e227849896 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 24 Nov 2025 09:53:55 -0700 Subject: [PATCH 603/962] Remove unnecessary overrideLibrary for torrServer (#2235) --- app/src/main/AndroidManifest.xml | 2 -- 1 file changed, 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f0ad909f8..2a1709320 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,8 +2,6 @@ - - From 3be396216f659b30823b0d977ffb884a3f5666e9 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:04:51 -0700 Subject: [PATCH 604/962] Remove acra and replace AcraApplication with CloudStreamApp (#2207) --- app/build.gradle.kts | 4 - app/src/main/AndroidManifest.xml | 2 +- .../lagradost/cloudstream3/AcraApplication.kt | 300 ++++-------------- .../lagradost/cloudstream3/CloudStreamApp.kt | 178 +++++++++++ .../lagradost/cloudstream3/CommonActivity.kt | 4 +- .../lagradost/cloudstream3/MainActivity.kt | 6 +- .../cloudstream3/actions/OpenInAppAction.kt | 4 +- .../cloudstream3/actions/temp/VlcPackage.kt | 2 +- .../actions/temp/fcast/FcastAction.kt | 2 +- .../cloudstream3/plugins/PluginManager.kt | 6 +- .../cloudstream3/plugins/RepositoryManager.kt | 6 +- .../cloudstream3/plugins/VotingApi.kt | 6 +- .../syncproviders/AccountManager.kt | 4 +- .../cloudstream3/syncproviders/AuthAPI.kt | 6 +- .../cloudstream3/syncproviders/AuthRepo.kt | 2 +- .../syncproviders/providers/AniListApi.kt | 4 +- .../syncproviders/providers/MALApi.kt | 4 +- .../syncproviders/providers/SimklApi.kt | 12 +- .../cloudstream3/ui/account/AccountHelper.kt | 2 +- .../ui/account/AccountViewModel.kt | 4 +- .../ui/download/DownloadButtonSetup.kt | 4 +- .../ui/download/button/PieFetchButton.kt | 2 +- .../ui/home/HomeParentItemAdapterPreview.kt | 2 +- .../cloudstream3/ui/home/HomeViewModel.kt | 4 +- .../ui/library/LibraryFragment.kt | 6 +- .../ui/library/LibraryViewModel.kt | 4 +- .../cloudstream3/ui/player/CS3IPlayer.kt | 4 +- .../ui/player/DownloadFileGenerator.kt | 2 +- .../cloudstream3/ui/player/GeneratorPlayer.kt | 6 +- .../ui/player/PreviewGenerator.kt | 4 +- .../source_priority/QualityDataHelper.kt | 6 +- .../ui/result/ResultViewModel2.kt | 6 +- .../cloudstream3/ui/search/SearchFragment.kt | 4 +- .../cloudstream3/ui/search/SearchViewModel.kt | 6 +- .../ui/settings/SettingsAccount.kt | 2 +- .../ui/settings/SettingsGeneral.kt | 10 +- .../ui/settings/SettingsProviders.kt | 2 +- .../cloudstream3/ui/settings/SettingsUI.kt | 2 +- .../ui/settings/SettingsUpdates.kt | 8 +- .../extensions/ExtensionsViewModel.kt | 2 +- .../ui/settings/extensions/PluginAdapter.kt | 2 +- .../extensions/PluginDetailsFragment.kt | 2 +- .../ui/settings/utils/DirectoryPicker.kt | 4 +- .../ui/setup/SetupFragmentLanguage.kt | 2 +- .../ui/setup/SetupFragmentLayout.kt | 2 +- .../subtitles/ChromecastSubtitlesFragment.kt | 2 +- .../ui/subtitles/SubtitlesFragment.kt | 4 +- .../cloudstream3/utils/AppContextUtils.kt | 2 +- .../cloudstream3/utils/BackupUtils.kt | 2 +- .../lagradost/cloudstream3/utils/DataStore.kt | 6 +- .../cloudstream3/utils/DataStoreHelper.kt | 19 +- .../utils/DownloadFileWorkManager.kt | 2 +- .../cloudstream3/utils/PackageInstaller.kt | 2 +- .../lagradost/cloudstream3/utils/UIHelper.kt | 2 +- .../utils/VideoDownloadManager.kt | 4 +- .../drawable/ic_baseline_bug_report_24.xml | 5 - .../main/res/layout/fragment_setup_layout.xml | 41 --- app/src/main/res/values-b+af/strings.xml | 1 - app/src/main/res/values-b+am/strings.xml | 1 - app/src/main/res/values-b+apc/strings.xml | 5 - app/src/main/res/values-b+ar/strings.xml | 5 - app/src/main/res/values-b+ars/strings.xml | 4 - app/src/main/res/values-b+as/strings.xml | 5 - app/src/main/res/values-b+az/strings.xml | 2 - app/src/main/res/values-b+bg/strings.xml | 5 - app/src/main/res/values-b+bn/strings.xml | 4 - app/src/main/res/values-b+ckb/strings.xml | 1 - app/src/main/res/values-b+cs/strings.xml | 5 - app/src/main/res/values-b+de/strings.xml | 5 - app/src/main/res/values-b+el/strings.xml | 5 - app/src/main/res/values-b+es/strings.xml | 5 - app/src/main/res/values-b+fa/strings.xml | 4 - app/src/main/res/values-b+fil/strings.xml | 3 - app/src/main/res/values-b+fr/strings.xml | 5 - app/src/main/res/values-b+gl/strings.xml | 4 - app/src/main/res/values-b+hi/strings.xml | 4 - app/src/main/res/values-b+hr/strings.xml | 5 - app/src/main/res/values-b+hu/strings.xml | 5 - app/src/main/res/values-b+in/strings.xml | 5 - app/src/main/res/values-b+it/strings.xml | 5 - app/src/main/res/values-b+iw/strings.xml | 5 - app/src/main/res/values-b+ja/strings.xml | 5 - app/src/main/res/values-b+kn/strings.xml | 1 - app/src/main/res/values-b+ko/strings.xml | 5 - app/src/main/res/values-b+lt/strings.xml | 1 - app/src/main/res/values-b+lv/strings.xml | 5 - app/src/main/res/values-b+mk/strings.xml | 5 - app/src/main/res/values-b+ml/strings.xml | 4 - app/src/main/res/values-b+ms/strings.xml | 5 - app/src/main/res/values-b+mt/strings.xml | 1 - app/src/main/res/values-b+my/strings.xml | 5 - app/src/main/res/values-b+ne/strings.xml | 1 - app/src/main/res/values-b+nl/strings.xml | 5 - app/src/main/res/values-b+nn/strings.xml | 2 - app/src/main/res/values-b+no/strings.xml | 5 - app/src/main/res/values-b+pl/strings.xml | 5 - app/src/main/res/values-b+pt+BR/strings.xml | 5 - app/src/main/res/values-b+pt/strings.xml | 5 - app/src/main/res/values-b+qt/strings.xml | 5 - app/src/main/res/values-b+ro/strings.xml | 5 - app/src/main/res/values-b+ru/strings.xml | 5 - app/src/main/res/values-b+sk/strings.xml | 5 - app/src/main/res/values-b+so/strings.xml | 5 - app/src/main/res/values-b+sv/strings.xml | 5 - app/src/main/res/values-b+ta/strings.xml | 5 - app/src/main/res/values-b+tl/strings.xml | 4 - app/src/main/res/values-b+tr/strings.xml | 5 - app/src/main/res/values-b+uk/strings.xml | 5 - app/src/main/res/values-b+ur/strings.xml | 5 - app/src/main/res/values-b+vi/strings.xml | 5 - app/src/main/res/values-b+zh+TW/strings.xml | 5 - app/src/main/res/values-b+zh/strings.xml | 5 - app/src/main/res/values-ca/strings.xml | 2 - app/src/main/res/values/strings.xml | 7 - app/src/main/res/xml/settings_updates.xml | 7 - gradle/libs.versions.toml | 3 - 116 files changed, 354 insertions(+), 644 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt delete mode 100644 app/src/main/res/drawable/ic_baseline_bug_report_24.xml diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 198524374..1a98ac2f3 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -199,10 +199,6 @@ dependencies { // FFmpeg Decoding implementation(libs.bundles.nextlibMedia3) - // Crash Reports (AcraApplication.kt) - implementation(libs.acra.core) - implementation(libs.acra.toast) - // UI Stuff implementation(libs.shimmer) // Shimmering Effect (Loading Skeleton) implementation(libs.palette.ktx) // Palette for Images -> Colors diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2a1709320..9e1bc9ac9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -33,7 +33,7 @@ setKey(path: String, value: T) = + CloudStreamApp.setKey(path, value) - runOnMainThread { // to run it on main looper - safe { - Toast.makeText(context, R.string.acra_report_toast, Toast.LENGTH_SHORT).show() - } - }*/ - } + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.setKey(folder, path, value)"), + level = DeprecationLevel.WARNING + )*/ + fun setKey(folder: String, path: String, value: T) = + CloudStreamApp.setKey(folder, path, value) + + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(path, defVal)"), + level = DeprecationLevel.WARNING + )*/ + inline fun getKey(path: String, defVal: T?): T? = + CloudStreamApp.getKey(path, defVal) + + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(path)"), + level = DeprecationLevel.WARNING + )*/ + inline fun getKey(path: String): T? = + CloudStreamApp.getKey(path) + + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(folder, path)"), + level = DeprecationLevel.WARNING + )*/ + inline fun getKey(folder: String, path: String): T? = + CloudStreamApp.getKey(folder, path) + + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(folder, path, defVal)"), + level = DeprecationLevel.WARNING + )*/ + inline fun getKey(folder: String, path: String, defVal: T?): T? = + CloudStreamApp.getKey(folder, path, defVal) + } } - -class CustomSenderFactory : ReportSenderFactory { - override fun create(context: Context, config: CoreConfiguration): ReportSender { - return CustomReportSender() - } - - override fun enabled(config: CoreConfiguration): Boolean { - return true - } -} - -class ExceptionHandler(val errorFile: File, val onError: (() -> Unit)) : - Thread.UncaughtExceptionHandler { - override fun uncaughtException(thread: Thread, error: Throwable) { - ACRA.errorReporter.handleException(error) - try { - val threadId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) { - thread.threadId() - } else { - @Suppress("DEPRECATION") - thread.id - } - - PrintStream(errorFile).use { ps -> - ps.println("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}") - ps.println("Fatal exception on thread ${thread.name} (${threadId})") - error.printStackTrace(ps) - } - } catch (ignored: FileNotFoundException) { - } - try { - onError.invoke() - } catch (ignored: Exception) { - } - exitProcess(1) - } -} - -class AcraApplication : Application(), SingletonImageLoader.Factory { - - override fun onCreate() { - super.onCreate() - // if we want to initialise coil at earliest - // (maybe when loading an image or gif using in splash screen activity) - //ImageLoader.buildImageLoader(applicationContext) - - ExceptionHandler(filesDir.resolve("last_error")) { - val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName) - startActivity(Intent.makeRestartActivityTask(intent!!.component)) - }.also { - exceptionHandler = it - Thread.setDefaultUncaughtExceptionHandler(it) - } - } - - override fun attachBaseContext(base: Context?) { - super.attachBaseContext(base) - context = base - - initAcra { - //core configuration: - buildConfigClass = BuildConfig::class.java - reportFormat = StringFormat.JSON - - reportContent = listOf( - ReportField.BUILD_CONFIG, ReportField.USER_CRASH_DATE, - ReportField.ANDROID_VERSION, ReportField.PHONE_MODEL, - ReportField.STACK_TRACE, - ) - - // removed this due to bug when starting the app, moved it to when it actually crashes - //each plugin you chose above can be configured in a block like this: - /*toast { - text = getString(R.string.acra_report_toast) - //opening this block automatically enables the plugin. - }*/ - } - } - - override fun newImageLoader(context: PlatformContext): coil3.ImageLoader { - // Coil Module will be initialized & setSafe globally when first loadImage() is invoked - return ImageLoader.buildImageLoader(applicationContext) - } - - companion object { - var exceptionHandler: ExceptionHandler? = null - - /** Use to get activity from Context */ - tailrec fun Context.getActivity(): Activity? { - return when (this) { - is Activity -> this - is ContextWrapper -> baseContext.getActivity() - else -> null - } - } - - private var _context: WeakReference? = null - var context - get() = _context?.get() - private set(value) { - _context = WeakReference(value) - setContext(WeakReference(value)) - } - - fun getKeyClass(path: String, valueType: Class): T? { - return context?.getKey(path, valueType) - } - - fun setKeyClass(path: String, value: T) { - context?.setKey(path, value) - } - - fun removeKeys(folder: String): Int? { - return context?.removeKeys(folder) - } - - fun setKey(path: String, value: T) { - context?.setKey(path, value) - } - - fun setKey(folder: String, path: String, value: T) { - context?.setKey(folder, path, value) - } - - inline fun getKey(path: String, defVal: T?): T? { - return context?.getKey(path, defVal) - } - - inline fun getKey(path: String): T? { - return context?.getKey(path) - } - - inline fun getKey(folder: String, path: String): T? { - return context?.getKey(folder, path) - } - - inline fun getKey(folder: String, path: String, defVal: T?): T? { - return context?.getKey(folder, path, defVal) - } - - fun getKeys(folder: String): List? { - return context?.getKeys(folder) - } - - fun removeKey(folder: String, path: String) { - context?.removeKey(folder, path) - } - - fun removeKey(path: String) { - context?.removeKey(path) - } - - /** - * If fallbackWebview is true and a fragment is supplied then it will open a webview with the url if the browser fails. - * */ - fun openBrowser(url: String, fallbackWebview: Boolean = false, fragment: Fragment? = null) { - context?.openBrowser(url, fallbackWebview, fragment) - } - - /** Will fallback to webview if in TV layout */ - fun openBrowser(url: String, activity: FragmentActivity?) { - openBrowser( - url, - isLayout(TV or EMULATOR), - activity?.supportFragmentManager?.fragments?.lastOrNull() - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt new file mode 100644 index 000000000..6421f38c2 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt @@ -0,0 +1,178 @@ +package com.lagradost.cloudstream3 + +import android.app.Activity +import android.app.Application +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.os.Build +import android.widget.Toast +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import coil3.ImageLoader +import coil3.PlatformContext +import coil3.SingletonImageLoader +import com.lagradost.api.setContext +import com.lagradost.cloudstream3.mvvm.safe +import com.lagradost.cloudstream3.mvvm.safeAsync +import com.lagradost.cloudstream3.plugins.PluginManager +import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR +import com.lagradost.cloudstream3.ui.settings.Globals.TV +import com.lagradost.cloudstream3.ui.settings.Globals.isLayout +import com.lagradost.cloudstream3.utils.AppContextUtils.openBrowser +import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread +import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DataStore.getKeys +import com.lagradost.cloudstream3.utils.DataStore.removeKey +import com.lagradost.cloudstream3.utils.DataStore.removeKeys +import com.lagradost.cloudstream3.utils.DataStore.setKey +import com.lagradost.cloudstream3.utils.ImageLoader.buildImageLoader +import kotlinx.coroutines.runBlocking +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 + +class ExceptionHandler( + val errorFile: File, + val onError: (() -> Unit) +) : Thread.UncaughtExceptionHandler { + + override fun uncaughtException(thread: Thread, error: Throwable) { + try { + val threadId = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA) { + thread.threadId() + } else { + @Suppress("DEPRECATION") + thread.id + } + + PrintStream(errorFile).use { ps -> + ps.println("Currently loading extension: ${PluginManager.currentlyLoading ?: "none"}") + ps.println("Fatal exception on thread ${thread.name} ($threadId)") + error.printStackTrace(ps) + } + } catch (_: FileNotFoundException) { + } + try { + onError() + } catch (_: Exception) { + } + exitProcess(1) + } +} + +@Prerelease +class CloudStreamApp : Application(), SingletonImageLoader.Factory { + + override fun onCreate() { + super.onCreate() + // If we want to initialize Coil as early as possible, maybe when + // loading an image or GIF in a splash screen activity. + // buildImageLoader(applicationContext) + + ExceptionHandler(filesDir.resolve("last_error")) { + val intent = context!!.packageManager.getLaunchIntentForPackage(context!!.packageName) + startActivity(Intent.makeRestartActivityTask(intent!!.component)) + }.also { + exceptionHandler = it + Thread.setDefaultUncaughtExceptionHandler(it) + } + } + + override fun attachBaseContext(base: Context?) { + super.attachBaseContext(base) + context = base + } + + override fun newImageLoader(context: PlatformContext): ImageLoader { + // Coil module will be initialized globally when first loadImage() is invoked. + return buildImageLoader(applicationContext) + } + + companion object { + var exceptionHandler: ExceptionHandler? = null + + /** Use to get Activity from Context. */ + tailrec fun Context.getActivity(): Activity? { + return when (this) { + is Activity -> this + is ContextWrapper -> baseContext.getActivity() + else -> null + } + } + + private var _context: WeakReference? = null + var context + get() = _context?.get() + private set(value) { + _context = WeakReference(value) + setContext(WeakReference(value)) + } + + fun getKeyClass(path: String, valueType: Class): T? { + return context?.getKey(path, valueType) + } + + fun setKeyClass(path: String, value: T) { + context?.setKey(path, value) + } + + fun removeKeys(folder: String): Int? { + return context?.removeKeys(folder) + } + + fun setKey(path: String, value: T) { + context?.setKey(path, value) + } + + fun setKey(folder: String, path: String, value: T) { + context?.setKey(folder, path, value) + } + + inline fun getKey(path: String, defVal: T?): T? { + return context?.getKey(path, defVal) + } + + inline fun getKey(path: String): T? { + return context?.getKey(path) + } + + inline fun getKey(folder: String, path: String): T? { + return context?.getKey(folder, path) + } + + inline fun getKey(folder: String, path: String, defVal: T?): T? { + return context?.getKey(folder, path, defVal) + } + + fun getKeys(folder: String): List? { + return context?.getKeys(folder) + } + + fun removeKey(folder: String, path: String) { + context?.removeKey(folder, path) + } + + fun removeKey(path: String) { + context?.removeKey(path) + } + + /** If fallbackWebView is true and a fragment is supplied then it will open a WebView with the URL if the browser fails. */ + fun openBrowser(url: String, fallbackWebView: Boolean = false, fragment: Fragment? = null) { + context?.openBrowser(url, fallbackWebView, fragment) + } + + /** Will fall back to WebView if in TV or emulator layout. */ + fun openBrowser(url: String, activity: FragmentActivity?) { + openBrowser( + url, + isLayout(TV or EMULATOR), + activity?.supportFragmentManager?.fragments?.lastOrNull() + ) + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 1e4754869..e4e7c69f4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -28,8 +28,8 @@ import androidx.preference.PreferenceManager import com.google.android.gms.cast.framework.CastSession import com.google.android.material.chip.ChipGroup import com.google.android.material.navigationrail.NavigationRailView -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey import com.lagradost.cloudstream3.actions.OpenInAppAction import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.databinding.ToastBinding diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index cd3fde7f9..42d9c1869 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -65,9 +65,9 @@ import com.jaredrummler.android.colorpicker.ColorPickerDialogListener import com.lagradost.cloudstream3.APIHolder.allProviders import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.initAll -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.loadThemes import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt index eb6b1f936..ac912cbeb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/OpenInAppAction.kt @@ -6,8 +6,8 @@ import android.content.Context import android.content.Intent import androidx.core.content.FileProvider import androidx.core.net.toUri -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.result.LinkLoadingResult diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt index e1fc22d3c..46b46a2c2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/VlcPackage.kt @@ -6,7 +6,7 @@ import android.content.Intent import android.os.Build import androidx.core.net.toUri import com.lagradost.api.Log -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey import com.lagradost.cloudstream3.actions.OpenInAppAction import com.lagradost.cloudstream3.actions.makeTempM3U8Intent import com.lagradost.cloudstream3.actions.updateDurationAndPosition diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt index e3916df01..1036a7055 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastAction.kt @@ -1,7 +1,7 @@ package com.lagradost.cloudstream3.actions.temp.fcast import android.content.Context -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.USER_AGENT import com.lagradost.cloudstream3.actions.VideoClickAction diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 1cffa7c1b..1b5d2909c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -20,11 +20,11 @@ import androidx.fragment.app.FragmentActivity import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.APIHolder.removePluginMapping -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.AutoDownloadMode +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.MainAPI.Companion.settingsForProvider diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index f8f0ccd7f..45ed65611 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -2,9 +2,9 @@ package com.lagradost.cloudstream3.plugins import android.content.Context import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/VotingApi.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/VotingApi.kt index d1b702f4c..930106644 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/VotingApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/VotingApi.kt @@ -2,9 +2,9 @@ package com.lagradost.cloudstream3.plugins import android.util.Log import android.widget.Toast -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import java.security.MessageDigest import com.lagradost.cloudstream3.app diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt index 7e796fbd0..93df0fd26 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt @@ -1,7 +1,7 @@ package com.lagradost.cloudstream3.syncproviders -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.syncproviders.providers.Addic7ed import com.lagradost.cloudstream3.syncproviders.providers.AniListApi diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt index e6b155a05..0303e03c6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthAPI.kt @@ -4,10 +4,10 @@ import android.util.Base64 import androidx.annotation.WorkerThread import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder.unixTime -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.ActorData +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.openBrowser +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.LoadResponse diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthRepo.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthRepo.kt index 9444c6367..4ae629ab9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthRepo.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AuthRepo.kt @@ -1,6 +1,6 @@ package com.lagradost.cloudstream3.syncproviders -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser +import com.lagradost.cloudstream3.CloudStreamApp.Companion.openBrowser import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.R diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt index a4cd42848..7a46b4113 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/AniListApi.kt @@ -2,11 +2,11 @@ package com.lagradost.cloudstream3.syncproviders.providers import androidx.annotation.StringRes import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.Actor import com.lagradost.cloudstream3.ActorData import com.lagradost.cloudstream3.ActorRole +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.NextAiring import com.lagradost.cloudstream3.R diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt index e8c343519..ba0195be6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/MALApi.kt @@ -2,8 +2,8 @@ package com.lagradost.cloudstream3.syncproviders.providers import androidx.annotation.StringRes import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.Score import com.lagradost.cloudstream3.ShowStatus diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt index 9518f5a20..c4095e2d8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/providers/SimklApi.kt @@ -4,12 +4,12 @@ import androidx.annotation.StringRes import androidx.core.net.toUri import com.fasterxml.jackson.annotation.JsonInclude import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.BuildConfig +import com.lagradost.cloudstream3.CloudStreamApp +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.LoadResponse.Companion.readIdFromString import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.Score @@ -96,7 +96,7 @@ class SimklApi : SyncAPI() { fun cleanOldCache() { getKeys(SIMKL_CACHE_KEY)?.forEach { - val isOld = AcraApplication.getKey>(it)?.isFresh() == false + val isOld = CloudStreamApp.getKey>(it)?.isFresh() == false if (isOld) { removeKey(it) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt index 05253f987..1d6b41e5b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt @@ -21,7 +21,7 @@ import coil3.ImageLoader import coil3.request.ImageRequest import coil3.request.allowHardware import com.google.android.material.bottomsheet.BottomSheetDialog -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt index af62a2b08..96eaf52a7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt @@ -4,8 +4,8 @@ import android.content.Context import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKeys import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.ui.account.AccountHelper.showPinInputDialog import com.lagradost.cloudstream3.utils.DataStoreHelper diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt index 83e0d0167..e9855ef3a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadButtonSetup.kt @@ -4,8 +4,8 @@ import android.content.DialogInterface import android.net.Uri import androidx.appcompat.app.AlertDialog import com.google.android.material.snackbar.Snackbar -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeys import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt index 29c2daa2c..3181a1bcd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/PieFetchButton.kt @@ -12,7 +12,7 @@ import androidx.annotation.MainThread import androidx.core.content.ContextCompat import androidx.core.view.isGone import androidx.core.view.isVisible -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DELETE_FILE diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index 042b05f98..e6b82e473 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -20,7 +20,7 @@ import androidx.viewpager2.widget.ViewPager2 import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipGroup import com.google.android.material.navigation.NavigationBarItemView -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.LoadResponse diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index bb82ec1a3..b7a322a84 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -7,8 +7,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.LoadResponse diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt index c2c226051..c9be2ed5c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryFragment.kt @@ -24,9 +24,9 @@ import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.APIHolder.allProviders -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.openBrowser +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt index f7713e9b2..38f7fcf9d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt @@ -4,8 +4,8 @@ import androidx.annotation.StringRes import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.Resource diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index 8a1756e52..b7712cd79 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -63,8 +63,8 @@ import androidx.media3.exoplayer.trackselection.TrackSelector import androidx.media3.ui.SubtitleView import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt index 7aac845a5..4c27dbc97 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/DownloadFileGenerator.kt @@ -1,7 +1,7 @@ package com.lagradost.cloudstream3.ui.player import android.net.Uri -import com.lagradost.cloudstream3.AcraApplication.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ui.player.PlayerSubtitleHelper.Companion.toSubtitleMimeType diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index a0d1e65d4..48353736b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -45,10 +45,10 @@ import androidx.media3.ui.PlayerNotificationManager.MediaDescriptionAdapter import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.AcraApplication -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull +import com.lagradost.cloudstream3.CloudStreamApp +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.databinding.DialogOnlineSubtitlesBinding import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding @@ -931,7 +931,7 @@ class GeneratorPlayer : FullScreenPlayer() { safe { // It lies, it can be null if file manager quits. if (uri == null) return@safe - val ctx = context ?: AcraApplication.context ?: return@safe + val ctx = context ?: CloudStreamApp.context ?: return@safe // RW perms for the path ctx.contentResolver.takePersistableUriPermission( uri, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt index 30e8d99ad..2893bcc47 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/PreviewGenerator.kt @@ -9,7 +9,7 @@ import android.util.Log import androidx.annotation.WorkerThread import androidx.core.graphics.scale import androidx.preference.PreferenceManager -import com.lagradost.cloudstream3.AcraApplication +import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.ui.settings.Globals.TV @@ -65,7 +65,7 @@ interface IPreviewGenerator { companion object { fun new(): IPreviewGenerator { - val userDisabled = AcraApplication.context?.let { ctx -> + val userDisabled = CloudStreamApp.context?.let { ctx -> PreferenceManager.getDefaultSharedPreferences(ctx)?.getBoolean( ctx.getString(R.string.preview_seekbar_key), true ) == false diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt index 0922bdb5a..467efa68b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/source_priority/QualityDataHelper.kt @@ -1,9 +1,9 @@ package com.lagradost.cloudstream3.ui.player.source_priority import androidx.annotation.StringRes -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.debugAssert import com.lagradost.cloudstream3.utils.UiText diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt index 5ad8f4f9a..106c05a3c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/ResultViewModel2.kt @@ -11,14 +11,14 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.* -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.actions.AlwaysAskAction import com.lagradost.cloudstream3.actions.VideoClickActionHolder import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.unixTime import com.lagradost.cloudstream3.APIHolder.unixTimeMS +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.getCastSession import com.lagradost.cloudstream3.CommonActivity.showToast @@ -1368,7 +1368,7 @@ class ResultViewModel2 : ViewModel() { // TODO Add skip loading here loadLinks(result, isVisible = true, sourceTypes, isCasting = isCasting) { links -> // Could not find a better way to do this - //val context = AcraApplication.context + //val context = CloudStreamApp.context postPopup( text, links.links.map { txt("${it.name} ${Qualities.getStringByInt(it.quality)}") } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 8f9793ab6..d1efe6205 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -26,10 +26,10 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.AnimeSearchResponse +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKeys import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.MainAPI diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt index de51ba009..63fb8c10e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt @@ -5,9 +5,9 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.APIHolder.apis -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.HomePageList import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.amap diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt index 0693d0442..53d29cdb8 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsAccount.kt @@ -17,7 +17,7 @@ import androidx.fragment.app.FragmentActivity import androidx.preference.PreferenceManager import androidx.preference.SwitchPreference import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser +import com.lagradost.cloudstream3.CloudStreamApp.Companion.openBrowser import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.ErrorLoadingException diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index fe2db95ec..e89865fc4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -10,9 +10,9 @@ import androidx.core.os.ConfigurationCompat import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder.allProviders -import com.lagradost.cloudstream3.AcraApplication -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity @@ -154,7 +154,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { ) private val pathPicker = getChooseFolderLauncher { uri, path -> - val context = context ?: AcraApplication.context ?: return@getChooseFolderLauncher + val context = context ?: CloudStreamApp.context ?: return@getChooseFolderLauncher (path ?: uri.toString()).let { PreferenceManager.getDefaultSharedPreferences(context).edit() .putString(getString(R.string.download_path_key), uri.toString()) @@ -317,7 +317,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { true, {}) { settingsManager.edit().putInt(getString(R.string.dns_pref), prefValues[it]).apply() - (context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) } + (context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt index ddad2070d..8bc3371ea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt @@ -97,7 +97,7 @@ class SettingsProviders : BasePreferenceFragmentCompat() { selectedList.map { it.toString() }.toMutableSet() ).apply() DataStoreHelper.currentHomePage = null - //(context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) } + //(context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt index 924e8bc1a..a991f9297 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt @@ -5,7 +5,7 @@ import android.os.Bundle import android.view.View import androidx.preference.PreferenceManager import androidx.preference.SeekBarPreference -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchQuality diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 168ffecea..30cd00470 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -8,8 +8,8 @@ import androidx.appcompat.app.AlertDialog import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager -import com.lagradost.cloudstream3.AcraApplication import com.lagradost.cloudstream3.AutoDownloadMode +import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.app @@ -56,7 +56,7 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { } private val pathPicker = getChooseFolderLauncher { uri, path -> - val context = context ?: AcraApplication.context ?: return@getChooseFolderLauncher + val context = context ?: CloudStreamApp.context ?: return@getChooseFolderLauncher (path ?: uri.toString()).let { PreferenceManager.getDefaultSharedPreferences(context).edit() .putString(getString(R.string.backup_path_key), uri.toString()) @@ -90,7 +90,7 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { settingsManager.edit() .putInt(getString(R.string.automatic_backup_key), prefValues[index]).apply() BackupWorkManager.enqueuePeriodicWork( - context ?: AcraApplication.context, + context ?: CloudStreamApp.context, prefValues[index].toLong() ) } @@ -250,7 +250,7 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { {}) { num -> settingsManager.edit() .putInt(getString(R.string.auto_download_plugins_key), prefValues[num]).apply() - (context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) } + (context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt index 6d5e2ce27..482251b78 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt @@ -4,7 +4,7 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.mvvm.debugAssert diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt index c57013c51..47b0b3da3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt @@ -10,7 +10,7 @@ import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.TvType diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt index 1fc10058b..0dcbece6c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt @@ -5,7 +5,7 @@ import android.text.format.Formatter.formatFileSize import android.util.Log import android.view.View import androidx.core.view.isVisible -import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser +import com.lagradost.cloudstream3.CloudStreamApp.Companion.openBrowser import com.lagradost.cloudstream3.databinding.FragmentPluginDetailsBinding import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.VotingApi.canVote diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt index 9e126b7a6..08a79b4b4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/utils/DirectoryPicker.kt @@ -4,14 +4,14 @@ import android.content.Intent import android.net.Uri import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.Fragment -import com.lagradost.cloudstream3.AcraApplication +import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.safefile.SafeFile fun Fragment.getChooseFolderLauncher(dirSelected: (uri: Uri?, path: String?) -> Unit) = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri -> // It lies, it can be null if file manager quits. if (uri == null) return@registerForActivityResult - val context = context ?: AcraApplication.context ?: return@registerForActivityResult + val context = context ?: CloudStreamApp.context ?: return@registerForActivityResult // RW perms for the path val flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt index 946f7eeae..5ff85c53b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt @@ -6,8 +6,8 @@ import android.widget.ArrayAdapter import androidx.core.content.ContextCompat import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.BuildConfig +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.databinding.FragmentSetupLanguageBinding import com.lagradost.cloudstream3.mvvm.safe diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt index 6c4dfc863..11cc12066 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt @@ -5,7 +5,7 @@ import android.widget.AbsListView import android.widget.ArrayAdapter import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentSetupLayoutBinding import com.lagradost.cloudstream3.mvvm.safe diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt index ca7a33d89..4f41b436d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt @@ -18,7 +18,7 @@ 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.CloudStreamApp.Companion.getKey import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent import com.lagradost.cloudstream3.CommonActivity.showToast diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt index 2653e5011..9b0d31212 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt @@ -26,8 +26,8 @@ import androidx.media3.ui.SubtitleView import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty import com.jaredrummler.android.colorpicker.ColorPickerDialog -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.onColorSelectedEvent import com.lagradost.cloudstream3.CommonActivity.onDialogDismissedEvent import com.lagradost.cloudstream3.CommonActivity.showToast diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt index 087f09b6d..8334833e4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt @@ -55,8 +55,8 @@ import com.google.android.gms.common.GoogleApiAvailability import com.google.android.gms.common.wrappers.Wrappers import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.APIHolder.apis -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity import com.lagradost.cloudstream3.AllLanguagesName +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.DubStatus diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt index 3e22bc65e..1b67fe90c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BackupUtils.kt @@ -12,7 +12,7 @@ import androidx.fragment.app.FragmentActivity import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.module.kotlin.readValue -import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt index e33a8f5e6..20d33c112 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStore.kt @@ -6,9 +6,9 @@ import androidx.preference.PreferenceManager import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.json.JsonMapper import com.fasterxml.jackson.module.kotlin.kotlinModule -import com.lagradost.cloudstream3.AcraApplication.Companion.getKeyClass -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKeyClass +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeyClass +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKeyClass import com.lagradost.cloudstream3.mvvm.logError import kotlin.reflect.KClass import kotlin.reflect.KProperty diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index ddb3c2cbb..217dc2a52 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -3,13 +3,14 @@ package com.lagradost.cloudstream3.utils import android.content.Context import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder.unixTimeMS -import com.lagradost.cloudstream3.AcraApplication -import com.lagradost.cloudstream3.AcraApplication.Companion.context -import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeyClass +import com.lagradost.cloudstream3.CloudStreamApp.Companion.getKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKeys +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKeyClass import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.DubStatus import com.lagradost.cloudstream3.EpisodeResponse @@ -56,7 +57,7 @@ class UserPreferenceDelegate( private val klass: KClass = default::class private val realKey get() = "${DataStoreHelper.currentAccount}/$key" operator fun getValue(self: Any?, property: KProperty<*>) = - AcraApplication.getKeyClass(realKey, klass.java) ?: default + getKeyClass(realKey, klass.java) ?: default operator fun setValue( self: Any?, @@ -66,7 +67,7 @@ class UserPreferenceDelegate( if (t == null) { removeKey(realKey) } else { - AcraApplication.setKeyClass(realKey, t) + setKeyClass(realKey, t) } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt index 4eeb4e5da..0b9b81e40 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DownloadFileWorkManager.kt @@ -7,7 +7,7 @@ import android.os.Build.VERSION.SDK_INT import androidx.work.CoroutineWorker import androidx.work.ForegroundInfo import androidx.work.WorkerParameters -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt index 4be0dd56c..67851f629 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PackageInstaller.kt @@ -11,7 +11,7 @@ import android.content.pm.PackageInstaller import android.os.Build import android.util.Log import android.widget.Toast -import com.lagradost.cloudstream3.AcraApplication.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.services.PackageInstallerService diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index ebafd4d75..e114abe29 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -67,7 +67,7 @@ import com.google.android.material.appbar.AppBarLayout import com.google.android.material.chip.Chip import com.google.android.material.chip.ChipDrawable import com.google.android.material.chip.ChipGroup -import com.lagradost.cloudstream3.AcraApplication.Companion.context +import com.lagradost.cloudstream3.CloudStreamApp.Companion.context import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 022faeed4..9748bd296 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -32,9 +32,9 @@ import coil3.request.ImageRequest import coil3.request.SuccessResult import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull -import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey -import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.BuildConfig +import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKey +import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey import com.lagradost.cloudstream3.IDownloadableMinimum import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R diff --git a/app/src/main/res/drawable/ic_baseline_bug_report_24.xml b/app/src/main/res/drawable/ic_baseline_bug_report_24.xml deleted file mode 100644 index dad38dca6..000000000 --- a/app/src/main/res/drawable/ic_baseline_bug_report_24.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/layout/fragment_setup_layout.xml b/app/src/main/res/layout/fragment_setup_layout.xml index a742c27b5..18815ced3 100644 --- a/app/src/main/res/layout/fragment_setup_layout.xml +++ b/app/src/main/res/layout/fragment_setup_layout.xml @@ -6,47 +6,6 @@ android:layout_height="match_parent" android:orientation="vertical"> - - Voorskou Agtergrond Speel Fliek Hou in om terug te stel na standaard - Deaktiveer outomatiese foutrapportering %1$dh %2$dm Maak leeg Omtrek kleur diff --git a/app/src/main/res/values-b+am/strings.xml b/app/src/main/res/values-b+am/strings.xml index f1b82c0bd..34b5c3433 100644 --- a/app/src/main/res/values-b+am/strings.xml +++ b/app/src/main/res/values-b+am/strings.xml @@ -109,5 +109,4 @@ ዓይነቶችን በመጠቀም ይፈልጉ ቅርጸ-ቁምፊዎችን በ%s ውስጥ በማስቀመጥ ያጫኑ hide_player_control_names_key - አውቶማቲክ የሳንካ ሪፖርት ማድረግን አሰናክል \ No newline at end of file diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index 28bfa7e14..e5e184435 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -6,9 +6,7 @@ التنزيلات %1$sالحلقة %2$d محي الملف - سوري، الآپ تعطل. رح ينبعت تقرير عن المشكلة للمطورين %1$d %2$s - بس بعات البيانات وقت ما يتعطل الآپ شوف الخلفية مشّي الفيلم ضلّ كابس لترجع السَتِنگز كيف كانة أول ما نزلتو الآپ @@ -63,7 +61,6 @@ لغة الترجمة لون الخلفية بتسيّڤ تاريخ المشاهدة و لوين وصلت بال ڤيديو - م بتبعت بيانات نبّش… هيدا المصدر مش عاطي \"ميتا داتا\". إزا مش موجودة بال مصدر، م رح يمشي الڤيديو. مافي أجزاء @@ -281,7 +278,6 @@ إشارات المرجعية التنزيل بَلَش فتّو على الأكونت \"%s\" - وقِف الإعلان الأتوماتيكي عن المشاكل يللي بال آپ محل عنوان الپوستر الشكل %1$d ساعة %2$d ديقة @@ -462,7 +458,6 @@ الإفتتاح الجودة وال عنوان شيل الإشتراك - بَلِغ عن الأعطال جودة وصوت ستبدل /؟؟ diff --git a/app/src/main/res/values-b+ar/strings.xml b/app/src/main/res/values-b+ar/strings.xml index 84e1d6321..2e1a4cdba 100644 --- a/app/src/main/res/values-b+ar/strings.xml +++ b/app/src/main/res/values-b+ar/strings.xml @@ -63,7 +63,6 @@ تشغيل الملف متابعة التنزيل إيقاف التنزيل مؤقتًا - قم بتعطيل الإبلاغ عن الأخطاء تلقائيًا مزيد من المعلومات إخفاء تشغيل @@ -143,8 +142,6 @@ معلومات البحث المتقدم يعطيك نتائج البحث مفصولة عن طريق المزود - إرسال البيانات عند الأعطال فقط - لا ترسل أي بيانات عرض حلقات الفلر للأنمي عرض المقاطع الدعائية عرض ملصقات من كيتسو @@ -167,7 +164,6 @@ تم نسخ الرابط إلى الحافظة تشغيل الحلقة إعادة التعيين إلى القيمة الافتراضية - عذرا، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين موسم لا موسم حلقة @@ -404,7 +400,6 @@ السابق تخطي الإعداد قم بتغيير مظهر التطبيق ليناسب جهازك - إبلاغ الأعطال ماذا تريد ان تري تم الإضافات diff --git a/app/src/main/res/values-b+ars/strings.xml b/app/src/main/res/values-b+ars/strings.xml index d1efdbbc5..80d348550 100644 --- a/app/src/main/res/values-b+ars/strings.xml +++ b/app/src/main/res/values-b+ars/strings.xml @@ -85,11 +85,9 @@ معلومات التحديثات والنسخ الاحتياطي يعطيك نتائج البحث مفصولة حسب المزود - يرسل فقط البيانات عن الأعطال عرض المقطورات عرض الملصقات من كيتسو حسابات - لا يرسل أي بيانات عرض حلقة حشو للأنمي إخفاء جودة الفيديو المحددة في نتائج البحث تحديثات البرنامج المساعد التلقائي @@ -132,7 +130,6 @@ لا يتمتع هذا المزود بدعم كرومكاست لم يتم العثور على أي روابط تشغيل الحلقة - عذرًا، تعطل التطبيق. سيتم إرسال تقرير خطأ مجهول إلى المطورين %1$s%2$d%3$s لا يوجد موسم حلقة @@ -218,7 +215,6 @@ لون النافذة ارتفاع الترجمة حذف ملف - تعطيل الإبلاغ التلقائي عن الأخطاء بدأ التحديث انسخ بث diff --git a/app/src/main/res/values-b+as/strings.xml b/app/src/main/res/values-b+as/strings.xml index 26d47be69..3a3db9ab6 100644 --- a/app/src/main/res/values-b+as/strings.xml +++ b/app/src/main/res/values-b+as/strings.xml @@ -138,7 +138,6 @@ চাব ফাইল মচক ফাইল প্লে কৰক - স্বয়ংক্ৰিয় বাগ ৰিপ’ৰ্টিং নিষ্ক্ৰিয় কৰক ডাউনলোড পুনৰ আৰম্ভ কৰক ডাউনলোড ৰখা অধিক তথ্য @@ -212,8 +211,6 @@ ব্যাকআপ ফাইল ল\'ড কৰা হৈছে উন্নত সন্ধান আপোনাক প্ৰদানকাৰীৰে পৃথককৃত সন্ধান ফলাফল দিব - কেৱল ভূলৰ ক্ষেত্ৰত ডাটা প্ৰেৰণ কৰে - কোনো ডাটা প্ৰেৰণ নকৰে এনিমেৰ ফিলাৰ খণ্ড দেখুৱাওক ট্ৰেইলাৰ দেখুৱাওক কিৎসুৰ পৰা প\'ষ্টাৰ দেখুৱাওক @@ -234,7 +231,6 @@ এই প্ৰদানকাৰীৰ ক্ৰোমকাষ্ট সমৰ্থন নাই লিংক ক্লিপব\'ৰ্ডত কপিকৰ কৰা হ’ল এপিচ’ড প্লে কৰক - দুখিত, এপ্পটোৰ এক্সিডেণ্ট হৈছে। এজনামবাগ বাগ ৰিপ’ৰ্ট ডেভেলপাৰক পঠোৱা হ’ব চিজন ডিফল্ট মানলৈ পুনৰছেট কৰক %1$s %2$d%3$s @@ -435,7 +431,6 @@ অপ্রয়োজনীয়তাবোৰ সাবটাইটেলৰ পৰা আঁতৰাওক অতিৰিক্ত ট্ৰেইলাৰ - ক্ৰেছৰ ৰিপৰ্টিং https://example.com/example.mp4 পূৰ্বৰ ছেটআপ এৰি দিয়া diff --git a/app/src/main/res/values-b+az/strings.xml b/app/src/main/res/values-b+az/strings.xml index 17ac40a07..430cd4593 100644 --- a/app/src/main/res/values-b+az/strings.xml +++ b/app/src/main/res/values-b+az/strings.xml @@ -87,12 +87,10 @@ Kitabxana Hesablar və Təhlükəsizlik Təkmilləşdirilmiş Axtarış - Məlumat göndərmə Treylerləri göstər Kitsu afişalarını, posterlərini göstər Link mübadilə buferinə nüsxələndi Standart dəyərlərə sıfırla - Üzr istəyirik, tətbiqdə xəta baş verdi. Gizli xəta hesabatı tərtibatçılara göndəriləcək Sezon Dayandır Başlat diff --git a/app/src/main/res/values-b+bg/strings.xml b/app/src/main/res/values-b+bg/strings.xml index ce9d11a2b..2bd90287f 100644 --- a/app/src/main/res/values-b+bg/strings.xml +++ b/app/src/main/res/values-b+bg/strings.xml @@ -67,7 +67,6 @@ Възпроизвеждане на файл Възобновете изтеглянето Пауза на изтеглянето - Деактивирайте автоматичното докладване на грешки Повече информация Скрий Пусни @@ -148,8 +147,6 @@ Информация Подробно търсене Дава ви резултатите от търсенето, разделени по доставчик - Изпраща данни само за сривове - Не изпраща данни Показване заместващ епизод за аниме Показване на трейлъри Покажете плакати от Kitsu @@ -171,7 +168,6 @@ Връзката е копирана в клипборда Пусни епизода Възстановяване на стойността по подразбиране - За съжаление приложението се срина. Анонимен доклад за грешка ще бъде изпратен до разработчиците Сезон %1$s %2$d%3$s Без сезон @@ -390,7 +386,6 @@ Предишен Пропуснете настройката Променете външния вид на приложението, за да отговаря на вашето устройство - Докладване за сривове Какво искате да видите Край Разширения diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index f1bc8f82a..b21958770 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -62,7 +62,6 @@ সাব ফাইল চালান ডাউনলোড থামান - আটো বাগ রিপোর্ট বন্ধ করুন আরো তথ্য বন্ধ করুন চালান @@ -120,7 +119,6 @@ কালো প্রান্ত অপসারণ করুন অনুসন্ধান করুন অ্যাকাউন্টসমূহ এবং নিরাপত্তা - কোনো উপাত্ত পাঠাবে না বিরতি দিতে মাঝে দুইবার চাপুন সিস্টেম এর উজ্জ্বলতা ব্যবহার করুন ট্রেইলার চালু করুন @@ -150,8 +148,6 @@ প্লেয়ারে এগিয়ে যাওয়ার পরিমাণ (সেকেন্ডে) সামনে বা পিছনের দিকে যেতে ডান বা বাম দিকে দুবার আলতো চাপুন ফাইল ডিলিট - দুঃখিত, অ্যাপ্লিকেশন ক্র্যাশ হয়েছে। ডেভেলপারদের কাছে একটি বেনামী বাগ রিপোর্ট পাঠানো হবে - শুধুমাত্র ক্র্যাশ এর তথ্য পাঠায় মান ডিফল্ট এ রিসেট করুন ফুল রিলিজের পরিবর্তে শুধুমাত্র প্রি-রিলিজ আপডেটের জন্য অনুসন্ধান করুন স্টার্টআপে নতুন আপডেটের জন্য স্বয়ংক্রিয়ভাবে অনুসন্ধান করুন diff --git a/app/src/main/res/values-b+ckb/strings.xml b/app/src/main/res/values-b+ckb/strings.xml index 79941c8e3..c47af36a3 100644 --- a/app/src/main/res/values-b+ckb/strings.xml +++ b/app/src/main/res/values-b+ckb/strings.xml @@ -72,7 +72,6 @@ سڕینەوەی فایل دووبارە دەستپێکردنەوەی دابەزاندن وەستاندنی دابەزاندن - ڕاپۆرتکردنی هەڵە بە شێوەیەکی ئۆتۆماتیکی لەکاربخە زانیاری زیاتر شاردنەوە زانیاری diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index e848582d3..70b740079 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -65,7 +65,6 @@ Přehrát soubor Pokračovat ve stahování Pozastavit stahování - Zakázat automatické nahlašování chyb Více informací Skrýt Přehrát @@ -144,8 +143,6 @@ Informace Pokročilé hledání Zobrazí vám výsledky hledání oddělené poskytovatelem - Odešle data pouze při pádech - Nebude odesílat žádná data Zobrazit výplňové epizody u anime Zobrazit aktualizace aplikace Při spuštění aplikace automaticky zkontrolovat nové aktualizace. @@ -163,7 +160,6 @@ Odkaz zkopírován do schránky Přehrát epizodu Obnovit na výchozí hodnoty - Omlouváme se, aplikace spadla. Vývojářům bude odesláno anonymní hlášení o pádu Sezóna Žádná sezóna Epizoda @@ -425,7 +421,6 @@ NovýNázevWebu Povolit NSFW u podporovaných rozšíření Poskytovatelé - Hlášení pádů Předchozí Změnit vzhled aplikace tak, aby vám vyhovoval Co chcete vidět diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml index 8d85c8448..fb4066d01 100644 --- a/app/src/main/res/values-b+de/strings.xml +++ b/app/src/main/res/values-b+de/strings.xml @@ -78,7 +78,6 @@ Datei abspielen Download fortsetzen Download pausieren - Automatische Fehlerberichtserstattung deaktivieren Mehr Infos Verstecken Abspielen @@ -155,8 +154,6 @@ Info Erweiterte Suche Liefert die Suchergebnisse getrennt nach Anbietern - Sendet Daten nur bei Abstürzen - Sendet keine Daten Füller-Episoden für Animes anzeigen Trailer anzeigen Vorschaubilder von Kitsu anzeigen @@ -177,7 +174,6 @@ Link in die Zwischenablage kopiert Episode abspielen Auf Standardwert zurücksetzen - Sorry, die Anwendung ist abgestürzt. Ein anonymer Fehlerbericht wird an die Entwickler gesendet Staffel Keine Staffel Episode @@ -385,7 +381,6 @@ Vorherige Einrichtung überspringen Aussehen der App passend zu dem des Geräts ändern - Absturzmeldung Was möchten Sie sehen Fertig Erweiterungen diff --git a/app/src/main/res/values-b+el/strings.xml b/app/src/main/res/values-b+el/strings.xml index 933d8b53c..9c6eb86a2 100644 --- a/app/src/main/res/values-b+el/strings.xml +++ b/app/src/main/res/values-b+el/strings.xml @@ -42,8 +42,6 @@ Αναπαραγωγή αρχείου Συνέχιση λήψης Παύση λήψης - Λυπόμαστε, η εφαρμογή κατέρρευσε. Μια ανώνυμη αναφορά σφαλμάτων θα σταλεί στους προγραμματιστές - Απενεργοποιήστε την αυτόματη αναφορά σφαλμάτων Εμφάνιση Logcat 🐈 Περαιτέρω πληροφορίες Απόκρυψη @@ -102,8 +100,6 @@ Πληροφορίες Προχωρημένη Αναζήτηση Δίνει τα αποτελέσματα αναζήτησης ταξινομημένα ανά πάροχο - Αποστέλλει δεδομένα μόνο για καταρρεύσεις - Δεν στέλνει δεδομένα Εμφάνιση ενημερώσεων Αυτόματη αναζήτηση νέων ενημερώσεων κατά την εκκίνηση της εφαρμογής. Ενημέρωση σε προ-εκδόσεις (beta) @@ -307,7 +303,6 @@ Προηγούμενο Παράλειψη διαμόρφωσης της εφαρμογής Αλλαγή της εμφάνισης της συσκευής για να ταιριάζει με την συσκευή σας - Αναφορά κατάρρευσης Τι θα θέλατε να δείτε Έγινε Extensions diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index b5cadb42b..220f88ea3 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -223,8 +223,6 @@ Información Búsqueda Avanzada Mostrar los resultados de la búsqueda por proveedor - Solo envíar los datos si la App se cierra / falla inesperadamente - No enviar datos Mostrar avances Mostrar pósters de Kitsu Actualizar a las versiones preliminares @@ -239,7 +237,6 @@ Enlaces no encontrados Enlaces copiados al portapapeles Reiniciar a valores predefinidos - Lo sentimos, la aplicación se bloqueó. Se enviará un informe de error anónimo a los desarrolladores Temporada %1$s %2$d%3$s Ninguna Temporada @@ -286,7 +283,6 @@ Mostrar actualizaciones de la aplicación Instalador de APK Algunos dispositivos no soportan el nuevo instalador de paquetes. Pruebe la opción antigua (legacy) si las actualizaciones no se instalan. - Desactivar reporte automático de bugs Sincronizar automáticamente el progreso de su episodio actual Actualización automática de plugins Características del reproductor @@ -467,7 +463,6 @@ Instalando actualización de la aplicación… No se pudo instalar la nueva versión de la aplicación App no encontrada - Reporte de fallos Plugin Borrado No puede cargar %s Descripción diff --git a/app/src/main/res/values-b+fa/strings.xml b/app/src/main/res/values-b+fa/strings.xml index 2e348017c..496373cdd 100644 --- a/app/src/main/res/values-b+fa/strings.xml +++ b/app/src/main/res/values-b+fa/strings.xml @@ -55,7 +55,6 @@ زیرنویس‌ها حذف دانلود آغاز شد - غیرفعال کردن گذارش باگ خودکار پاک کردن به‌روزرسانی آغاز شد کپی @@ -308,8 +307,6 @@ به‌روزرسانی پیشرفت ساعت شروع کنید به صحبت کردن… جواب سرچ های شما با تامین کننده - فقط درصورت کرش اطلاعات فرستاده می شوند - هیچ اطلاعاتی (دیتایی) فرستاده نمی شود نشان دادن آپدیت های برنامه دوباره پروسه راه اندازی را انجام بده نسخه های پیشین را آپدیت کن @@ -317,7 +314,6 @@ برنامه سبک رمان از توسعه دهندگان یکسان برنامه انیمه از توسعه دهندگان یکسان این ارائه دهنده هیچ پشتیبانی از کروم کست ندارد - متأسفیم، برنامه کرش کرد. یک گزارش از این باگ بطور ناشناس به توسعه دهندگان فرستاده می‌شود با شکست مواجه شد اخطار امتیاز diff --git a/app/src/main/res/values-b+fil/strings.xml b/app/src/main/res/values-b+fil/strings.xml index dbea6c62f..7ea370852 100644 --- a/app/src/main/res/values-b+fil/strings.xml +++ b/app/src/main/res/values-b+fil/strings.xml @@ -40,7 +40,6 @@ Available para mapanood offline Piliin Lahat Alisin sa pagkakapili ang Lahat - Huwag paganahin ang awtomatikong pag-uulat ng bug Maaaring kailanganin ng VPN para gumana ang provider na ito Ang provider na ito ay isang torrent, inirerekomendang gumamit ng VPN Ang metadata ay hindi ibinigay ng site, hindi gagana ang video kung wala ito sa site. @@ -51,8 +50,6 @@ Awtomatikong i-sync ang iyong kasalukuyang episode Nawawala ang mga pahintulot sa storage. Pakisubukang muli. Binibigyan ka ng mga resulta ng paghahanap na pinaghihiwalay ng provider - Nagpapadala lamang ng data sa mga pag-crash - Hindi nagpapadala ng data Itago ang napiling quality ng video sa mga resulta ng paghahanap Pumili ng mode upang i-filter ang pag-download ng mga plugin Awtomatikong i-install ang lahat ng hindi pa naka-install na plugin mula sa mga idinagdag na repository. diff --git a/app/src/main/res/values-b+fr/strings.xml b/app/src/main/res/values-b+fr/strings.xml index c8b971a5b..f7b638f0b 100644 --- a/app/src/main/res/values-b+fr/strings.xml +++ b/app/src/main/res/values-b+fr/strings.xml @@ -47,7 +47,6 @@ Lire le fichier Reprendre le téléchargement Mettre en pause le téléchargement - Désactiver le rapport de bug automatique Plus d\'information Cacher Affiche principale @@ -69,7 +68,6 @@ Lien copié dans le presse-papier Lecture de l\'episode Réinitialiser aux valeurs par défault - Désolé, l\'application à crashé. Un rapport de bug anonyme va être envoyé aux développeurs Saison Pas de Saison Épisode @@ -282,8 +280,6 @@ Info Recherche avancée Vous donne les résultats de la recherche séparés par fournisseur - Envoi de données uniquement en cas d\'accident - N\'envoie aucune donnée Afficher les épisodes spéciaux pour les animés Montrer les bandes-annonces Montrer les affiches provenant de Kitsu @@ -435,7 +431,6 @@ Certains téléphones ne supporte pas le nouvel installateur d\'application. Essayez l\'option de l\'ancien installateur si les mises-à-jour ne s\'installe pas. Précédent Ignorer la configuration - Rapport de crash Nom de dépôt (optionnel) plugin Supprimer le repository diff --git a/app/src/main/res/values-b+gl/strings.xml b/app/src/main/res/values-b+gl/strings.xml index d5a88bcec..263a2acc3 100644 --- a/app/src/main/res/values-b+gl/strings.xml +++ b/app/src/main/res/values-b+gl/strings.xml @@ -76,7 +76,6 @@ Reproducir Arquivo Continuar Descarga Pausar Descarga - Desactivar reporte automático de bugs Máis información Agochar Reproducir @@ -153,8 +152,6 @@ Subtítulos Sincronizar automáticamente o progreso do episodio actual Use o brillo do sistema no reprodutor da aplicación en lugar dunha superposición oscura - Só envía datos se a aplicacción falla inesperadamente - Non enviar datos Mostrar episodio de recheo para Anime Mostrar Trailers Mostrar pósters de Kitsu @@ -278,7 +275,6 @@ Ligazóns non atopadas Ligazón copiada ó portapapeis Reproducir capítulo - O sentimos, o aplicativo bloqueouse. Enviarase un informe de error anónimo aos desenvolvedores Temporada Capítulos diff --git a/app/src/main/res/values-b+hi/strings.xml b/app/src/main/res/values-b+hi/strings.xml index 3d2216816..453718af9 100644 --- a/app/src/main/res/values-b+hi/strings.xml +++ b/app/src/main/res/values-b+hi/strings.xml @@ -40,7 +40,6 @@ फ़ाइल चलाएं डाउनलोड फिर शुरू करें डाउनलोड रोकें - स्वचालित बग रिपोर्टिंग अक्षम करें और जानकारी छिपाएं चलाएं @@ -72,8 +71,6 @@ खोजें जानकारी खोज नतीजों को प्रोवाइडरों के हिसाब से अलग-अलग आपको दिखाता है - केवल क्रैश पर जानकारी भेजी जाएगी - आपकी जानकारी नहीं भेजी जाएगी हर बार एप खुलने पर स्वचालित रूप से नए अपडेट खोजें। केवल पूर्ण रिलीज़ के बजाय प्रीरिलीज़ अपडेट खोजें उन्हीं डेवलपर्स द्वारा Light novel ऐप @@ -86,7 +83,6 @@ कोई लिंक नहीं मिले लिंक क्लिपबोर्ड पर कॉपी किया गया एपिसोड चलायें - क्षमा करें, एप्प क्रैश हो गया है । निर्माताओं को एक अनाम बग रिपोर्ट भेजी जाएगी फ़ाइल डिलीट करें डिलीट रद्द करें diff --git a/app/src/main/res/values-b+hr/strings.xml b/app/src/main/res/values-b+hr/strings.xml index f65765e7e..b34a691f7 100644 --- a/app/src/main/res/values-b+hr/strings.xml +++ b/app/src/main/res/values-b+hr/strings.xml @@ -80,7 +80,6 @@ Pokreni datoteku Nastavi preuzimanje Pauziraj preuzimanje - Onemogući automatsko izvješćivanje o greškama Više informacija Sakrij Pokreni @@ -161,8 +160,6 @@ Informacije Napredna pretraga Daje rezultate pretrage odvojene prema pružatelju usluga - Šalje samo podatke o padovima aplikacije - Ne šalje podatke Prikaži dodatnu epizodu za anime Prikaži trailere Prikaži postere iz Kitsua @@ -184,7 +181,6 @@ Poveznica je kopirana u međuspremnik Pokreni epizodu Vrati na zadanu vrijednost - Nažalost se aplikacija srušila. Anonimno izvješće o grešci će se poslati programerima Sezona Nema sezone Epizoda @@ -405,7 +401,6 @@ Prethodno Preskoči postavljanje Promijeni izgled aplikacije kako bi odgovarao tvom uređaju - Izvještavanje o rušenju Što želiš vidjeti Gotovo Proširenja diff --git a/app/src/main/res/values-b+hu/strings.xml b/app/src/main/res/values-b+hu/strings.xml index 02a07a3f6..593f1dc12 100644 --- a/app/src/main/res/values-b+hu/strings.xml +++ b/app/src/main/res/values-b+hu/strings.xml @@ -80,7 +80,6 @@ Fájl lejátszása Letöltés folytatása Letöltés szüneteltetése - Automatikus hibajelentés kikapcsolása Több információ Elrejtés Lejátszás @@ -188,7 +187,6 @@ Hiba a biztonsági mentés során %s Fiókok és Biztonság Szolgáltató szerint elkülönítve adja meg a keresési eredményeket - Nem küld adatokat Poszterek megjelenítése Kitsu-ról Kiválasztott videóminőségek elrejtése keresési eredményekbe Automatikus bővítményfrissítések @@ -206,7 +204,6 @@ A link vágólapra másolva Epizód lejátszása Alapértelmezett értékre visszaállítása - Az alkalmazás összeomlott. Egy névtelen hibabejelentés küldünk a fejlesztőknek Évad %1$s %2$d%3$s Nincs évad @@ -253,7 +250,6 @@ Frissítés elkezdődött Nem sikerült visszaállítani az adatokat a %s fájlból Tárolási engedélyek hiányoznak. Kérjük próbálja újra. - Csak összeomlásokról küld adatokat APK Telepítő Egyes telefonok nem támogatják az új csomag telepítőt. Ha a frissítések nem települnek, próbálja meg a régi opciót. Banán adva @@ -405,7 +401,6 @@ Betöltés fájlból HDR Az alkalmazás megjelenésének módosítása, hogy az megfeleljen az eszközödnek - Összeomlás jelentése Nyilvános lista Állapot Összefoglaló diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index 592653734..404b6ae65 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -63,7 +63,6 @@ Putar Berkas Lanjutkan Unduh Jeda Unduh - Nonaktifkan pelaporan bug otomatis Lebih banyak info Sembunyikan Putar @@ -141,8 +140,6 @@ Info Pencarian Lanjutan Memberikan hasil pencarian yang dipisahkan berdasarkan penyedia - Hanya mengirim data saat ngecrash - Tidak mengirim data Tampilkan episode filler untuk anime Tampilkan update aplikasi Secara otomatis mencari update terbaru setelah aplikasi dibuka. @@ -160,7 +157,6 @@ Tautan disalin ke papan klip Putar Episode Ulang ke pengaturan default - Maaf, aplikasi ngecrash. Laporan bug anonim akan dikirim ke developer Season Tidak Ada Season Episode @@ -453,7 +449,6 @@ Selanjutnya Sebelumnya Ubah tampilan aplikasi - Laporkan Crash Selesai Ekstensi Hapus Repositori diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index ac24a39d2..0c59b494b 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -70,7 +70,6 @@ Riproduci file Riprendi download Ferma download - Disabilita segnalazione automatica di bug Più info Nascondi Riproduci @@ -151,8 +150,6 @@ Info Ricerca avanzata Dividi i risultati della ricerca per provider - Invia i dati solo in caso di crash - Non inviare alcun dato Mostra tag [filler] per anime Mostra trailer Mostra poster da Kitsu @@ -174,7 +171,6 @@ Link copiato negli appunti Riproduci episodio Ripristina il valore predefinito - Spiacente, l\'applicazione è andata in crash. Una segnalazione anonima di bug sarà inviata agli sviluppatori Stagione %1$s %2$d%3$s Nessuna stagione @@ -395,7 +391,6 @@ Precedente Salta configurazione Cambia l\'aspetto dell\'app per adattarla al proprio dispositivo - Segnala crash Cosa vuoi vedere Fatto Estensioni diff --git a/app/src/main/res/values-b+iw/strings.xml b/app/src/main/res/values-b+iw/strings.xml index 037286ce1..ed83fcb8d 100644 --- a/app/src/main/res/values-b+iw/strings.xml +++ b/app/src/main/res/values-b+iw/strings.xml @@ -58,7 +58,6 @@ נגן קובץ המשך הורדה השהה את ההורדה - השבת את דיווח הבאגים האוטומטי נגן מידע סנן סימניות @@ -191,7 +190,6 @@ עדכוני תוספים אוטומטיים כמות בנינים שניתנו עונה - מצטערים, האפליקציה קרסה. דוח באג אנונימי יישלח למפתחים טורנטים NSFW שגיאת מעבד @@ -283,8 +281,6 @@ חשבונות ובטיחות לא ניתנו בנינים מהירות ניגון - שולח נתונים רק בקריסות - לא שולח נתונים הצג טריילרים הצג פוסטרים מKitsu הסתר את איכות הסרטון שנבחרה בתוצאות החיפוש @@ -413,7 +409,6 @@ צפה בסרטונים בשפות אלה קודם שנה את מראה האפליקציה כך שיתאים למכשירך - דיווח על קריסה סוים הרחבות הוסף מאגר diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml index 107c14434..12de7a8fc 100644 --- a/app/src/main/res/values-b+ja/strings.xml +++ b/app/src/main/res/values-b+ja/strings.xml @@ -213,7 +213,6 @@ リンクの読み込みエラー 137905 リンクがリロードされました - 自動バグ報告を無効にする プレーヤーの速度 字幕設定 ダブ @@ -238,7 +237,6 @@ hide_player_control_names_key ブックマークのフィルタ プロットが見つかりません - データを送信しない ダブルタップで一時停止 トレーラーを表示 GitHub プロキシ @@ -315,7 +313,6 @@ アプリ起動後に自動的に更新を探します。 プレリリースにアップデート 初期値にリセット - 残念ながらアプリがクラッシュしました。匿名のバグ報告が開発者に送信されます %1$d %2$s エピソードが見つかりません アプリのテーマ @@ -565,12 +562,10 @@ 凹んだ QRコード画像 前へ - クラッシュレポート 表示したいもの %1$d %2$s をダウンロードしました プロフィールをロック パスワード/PIN認証 - クラッシュ時のみデータを送信 検索結果で選択したビデオ品質を非表示 追加されたリポジトリから未インストールのすべてのプラグインを自動的にインストールします。 セットアッププロセスを再実行 diff --git a/app/src/main/res/values-b+kn/strings.xml b/app/src/main/res/values-b+kn/strings.xml index 8be5a737b..fcfaa9045 100644 --- a/app/src/main/res/values-b+kn/strings.xml +++ b/app/src/main/res/values-b+kn/strings.xml @@ -26,7 +26,6 @@ ಇಂಟರ್ನಲ್ ಸ್ಟೋರೇಜ್ ಡಬ್ ಸಬ್ - ಸ್ವಯಂಚಾಲಿತ ದೋಷ ವರದಿಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ ಹೈಡ್ ಪ್ಲೇ ಮಾಹಿತಿ diff --git a/app/src/main/res/values-b+ko/strings.xml b/app/src/main/res/values-b+ko/strings.xml index 9ac7e1185..7ec22c991 100644 --- a/app/src/main/res/values-b+ko/strings.xml +++ b/app/src/main/res/values-b+ko/strings.xml @@ -48,7 +48,6 @@ 파일 재생 계속 다운로드 다운로드 일시정지 - 자동 오류 보고 비활성화 상세 정보 닫기 재생 @@ -156,7 +155,6 @@ 클립보드에 링크 복사됨 에피소드 재생 기본값으로 재설정 - 죄송합니다, 애플리케이션이 충돌했습니다. 버그 보고서가 익명으로 개발자에게 전송됩니다 에피소드 %1$d-%2$d 진행중 @@ -329,14 +327,12 @@ Picture-in-picture 플레이어 크기 조정 버튼 다른 앱 위에 있는 미니어처 플레이어에서 재생을 계속합니다 - 충돌에 관한 데이터만 전송 검은색 테두리 제거 오른쪽 또는 왼쪽을 두 번 탭하여 앞뒤로 탐색하기 자막 로드된 백업 파일 정보 고급 검색 - 데이터를 보내지 않습니다 설정 프로세스 다시 실행 APK 인스톨러 Github @@ -424,7 +420,6 @@ 플레이어 HDR TC - 충돌 보고 완료 다운로드되지 않음: %d HQ diff --git a/app/src/main/res/values-b+lt/strings.xml b/app/src/main/res/values-b+lt/strings.xml index cc68d77eb..6493e33e2 100644 --- a/app/src/main/res/values-b+lt/strings.xml +++ b/app/src/main/res/values-b+lt/strings.xml @@ -9,7 +9,6 @@ Sekantis atsitiktinai Peržiūros fonas Paleisti filmą - Išjungti automatini klaidų pateikimą Atstatyti į numatytą reikšmę Išvalyti Plakatas diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index cd981810e..bff199dfb 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -56,7 +56,6 @@ Palaist failu Atsākt ielādi Pauzēt ielādi - Atslēgt automātisko kļūdu ziņošanu Vairāk informācijas Slēpt Atskaņot @@ -153,8 +152,6 @@ Informācija Advancēta meklēšana Dod tev meklēšanas rezultātus citus no devēja - Tikai sūtīt datus no kļudām - Nesutīt datus Radīt fillera epizodi priekš animē Radīt feel Radīt plakātu no kitsu @@ -389,7 +386,6 @@ Iepriekšējais Izlaist uzstādīšanu Mainiet lietotnes izskatu, lai tā atbilstu savai ierīcei - Avārijas ziņošana Ko tu vēlies redzēt Pabeigts Papildinājumi @@ -473,7 +469,6 @@ Github Nav subtitru Atskaņot epizodi - Piedodiet, bet aplikācijā bija kļūda. Anonīms kļūdas ziņojums tika aizsūtīts izstrādātājiem Iet Bezmaksas Ieslēgt elementus uz plakātiem diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml index 95f6fa7ec..1af176a12 100644 --- a/app/src/main/res/values-b+mk/strings.xml +++ b/app/src/main/res/values-b+mk/strings.xml @@ -47,7 +47,6 @@ Пушти датотека Продолжи со преземање Паузирај со преземање - Оневозможи автоматско известување за грешки Повеќе информации Скриј Пушти @@ -101,8 +100,6 @@ Информации Напредно пребарување Ви ги дава резултатите од пребарувањето одделени по провајдер - Испраќај податоци само за падови на апликацијата - Не испраќа податоци Прикажи епизода за полнење за аниме Прикажи ажурирања на апликации Автоматски пребарувај нови ажурирања откако ќе ја стартуваш апликацијата. @@ -120,7 +117,6 @@ Линкот е копиран во таблата со исечоци Пушти ја епизодата Ресетирање на стандардните вредности - За жал, апликацијата падна. Ќе се испрати анонимен извештај за грешка до програмерите Сезона Нема сезона Епизода @@ -466,7 +462,6 @@ Претходно Прескокни го поставувањето Промени го изгледот на апликацијата за да одговара на твојот уред - Известување за пад Што сакате да видите Име на изворот (Опционално) Приклучокот е преземен diff --git a/app/src/main/res/values-b+ml/strings.xml b/app/src/main/res/values-b+ml/strings.xml index bf0fede72..c10777681 100644 --- a/app/src/main/res/values-b+ml/strings.xml +++ b/app/src/main/res/values-b+ml/strings.xml @@ -45,7 +45,6 @@ ഫയൽ പ്ലേയ് ചെയ്യുക ഡൌൺലോഡ് തുടരുക ഡൌൺലോഡ് നിർത്തുക - ഓട്ടോമാറ്റിക് ബഗ് റിപ്പോർട്ടിംഗ് പ്രവർത്തനരഹിതമാക്കുക കൂടുതൽ വിവരം ഒളിക്കുക പ്ലേയ് @@ -94,8 +93,6 @@ വിവരം സ്ട്രോതസായി തിരിച്ച ഫലം തരുക - ക്രാഷാകുമ്പോൾ മാത്രം അയക്കുക - ടാറ്റ അയക്കാതിരിക്കുക അപ്ഡേറ്റുകൾ അറിയിക്കുക ആരംഭത്തിൽ അപ്ഡേറ്റുകൾ തിരയുക പരീക്ഷണാത്മക അപ്ഡേറ് @@ -111,7 +108,6 @@ ലിങ്കുകൾ ലഭ്യമല്ല ലിങ്ക് പകർത്തിയിരിക്കുന്നു എപ്പിസോഡ് പ്ലേയ് ചെയ്യുക - ആപ്പ് നിശ്ചലമായിരിക്കുന്നതിന് ക്ഷമിക്കണം സീസൺ സീസണില്ല എപ്പിസോഡ് diff --git a/app/src/main/res/values-b+ms/strings.xml b/app/src/main/res/values-b+ms/strings.xml index e93dc5015..9038a51e7 100644 --- a/app/src/main/res/values-b+ms/strings.xml +++ b/app/src/main/res/values-b+ms/strings.xml @@ -66,7 +66,6 @@ Buang disalin! Kelajuan Pemain - Lumpuhkan pelaporan pepijat automatik Buang Mulakan episod seterusnya apabila episod semasa tamat Buka video tempatan @@ -163,7 +162,6 @@ Pemain Aplikasi akan dikemas kini ketika keluar Pemain dalaman - Tiada data dihantar Durasi Laman web Sinopsis @@ -416,7 +414,6 @@ HD Sebelum Tukar kelihatan aplikasi mengikut peranti - Laporkan ralat Tamat Tambahan Tambah repositori @@ -514,7 +511,6 @@ Pautan disalin ke papan klip Main episod Tetapkan semula ke nilai lalai - Maaf, permohonan itu terhempas. Laporan bug tanpa nama akan dihantar kepada pemaju Musim %1$s %2$d%3$s Tiada musim @@ -522,7 +518,6 @@ Sandarkan data Gagal pulihkan data dari fail %s Ralat sandaran %s - Hanya hantar data apabila mengalami kegagalan Episode diff --git a/app/src/main/res/values-b+mt/strings.xml b/app/src/main/res/values-b+mt/strings.xml index ee890fbbd..bd605bb37 100644 --- a/app/src/main/res/values-b+mt/strings.xml +++ b/app/src/main/res/values-b+mt/strings.xml @@ -115,7 +115,6 @@ Hassar il-fajl Kompli Nizzel Ieqaf Nizzel - Iddiżattiva r-rappurtar awtomatiku tal-bugs Iktar Informazzjoni Aħbi Iffiltra l-Bookmarks diff --git a/app/src/main/res/values-b+my/strings.xml b/app/src/main/res/values-b+my/strings.xml index 9d82dd47d..6a991f885 100644 --- a/app/src/main/res/values-b+my/strings.xml +++ b/app/src/main/res/values-b+my/strings.xml @@ -92,7 +92,6 @@ အပ်ဒိတ်များနှင့်အရန်သိမ်းဆည်းမှု နက်နက်ရှိုင်းရှိုင်းရှာခြင်း သင့်ကိုဝန်ဆောင်မှုပေးသူအလိုက်ရှာဖွေမှုရလဒ်များပေးမည် - ချို့ယွင်းမှုအကြီးစားဖြစ်မှသာဒေတာများပေးပို့ပါ anime များအတွက်ဖြည့်စွက်အပိုင်းကိုပြရန် ထွေလာများကိုပြရန် Kitsu မှ ပိုစတာများကိုပြရန် @@ -239,7 +238,6 @@ ဖိုင်ကို ဖွင့်ရန် ဒေါင်းလုဒ် ဆက်လုပ်ရန် ဒေါင်းလုဒ် ရပ်ရန် - အလိုအလျောက်အက်ပ်ချို့ယွင်းချက်ပေးပို့ခြင်းကိုပိတ်မည် ပိုမို၍ ပ့ံပိုးပေးသောဝန်ဆောင်မှုများအသုံးပြု၍ရှာရန် ဝုက်ရန် @@ -267,7 +265,6 @@ ရှာရန် အကောင့်များ အချက်အလက် - ဒေတာများမပို့ရန် ကြည့်ရှုပြီးသောအချိန်ပမာဏ Android TV ကဲ့သို့သော သိုလှောင်မှုနေရာနည်းပါးသော စက်ပစ္စည်းများတွင် အလွန်မြင့်မားစွာ သတ်မှတ်ပါက ပြဿနာများ ဖြစ်လာနိုင်သည်။ ISP ပိတ်ဆို့ခြင်းကို ကျော်လွှားရန်အတွက် အသုံးဝင်သည် @@ -287,7 +284,6 @@ ပေးခဲ့သောစာအရေအတွက် အက်ပ်ဘာသာစကား မူလအခြေအနေများကိုပြန်ထားပါ - စိတ်မကောင်းပါ။အက်ပ်ရပ်တန့်သွားပါတယ်။အမည်မဖော်ထားတဲ့တင်ပြချက်ကို အက်ပ်ရေးသားသူများထံ ပို့မှာဖြစ်ပါတယ် %1$s %2$d%3$s အတွဲ %1$d %2$s @@ -457,7 +453,6 @@ TC အရည်အသွေး အစီအစဥ်ချခြင်းကိုကျော်မည် - ချို့ယွင်းမှုသတင်းပေးပို့ခြင်း ဘာတွေကြည့်ချင်လဲ ပြီးပြီ အဆက်များ diff --git a/app/src/main/res/values-b+ne/strings.xml b/app/src/main/res/values-b+ne/strings.xml index bff19dd3a..4eea78b9c 100644 --- a/app/src/main/res/values-b+ne/strings.xml +++ b/app/src/main/res/values-b+ne/strings.xml @@ -83,7 +83,6 @@ पुन: हेर्दै स्ट्रिम टोरेन्ट स्रोतहरू - स्वचालित बग रिपोर्टिङ असक्षम गर्नुहोस् लागू गर्नुहोस् साइट ले मेटाडाटा दिएको छैन,मेटाडाटा बिना भिडियो लोड नहुन सक्छ। प्रकरण %1$d प्रसङ्ग %2$d प्रशारण हुनेवाला छ diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml index b10821a35..2497c0eba 100644 --- a/app/src/main/res/values-b+nl/strings.xml +++ b/app/src/main/res/values-b+nl/strings.xml @@ -71,7 +71,6 @@ Bestand afspelen Download hervatten Download pauzeren - Automatische bugrapportage uitschakelen Meer info Verberg Speel @@ -150,8 +149,6 @@ Info Geavanceerd zoeken Geeft u de zoekresultaten gescheiden door provider - Stuurt alleen gegevens bij crashes - Verstuurt geen gegevens Toon filler episode voor anime Toon trailers Toon posters van Kitsu @@ -171,7 +168,6 @@ Link gekopieerd naar klembord Aflevering afspelen Reset naar standaardwaarde - Sorry, de applicatie is gecrasht. Er wordt een anoniem bugrapport naar de ontwikkelaars gestuurd Seizoen Geen seizoen Aflevering @@ -392,7 +388,6 @@ Vorige Instelling overslaan Pas het uiterlijk van de app aan uw apparaat aan - Crashrapportage Wat wil je zien Klaar Markeer als bekeken diff --git a/app/src/main/res/values-b+nn/strings.xml b/app/src/main/res/values-b+nn/strings.xml index 5b5577c2e..6989a85da 100644 --- a/app/src/main/res/values-b+nn/strings.xml +++ b/app/src/main/res/values-b+nn/strings.xml @@ -54,7 +54,6 @@ Slett fil Spel av fil Sett nedlasting på vent - Deaktiver automatisk feilrapportering Gøym Spel(e) av Informasjon @@ -103,7 +102,6 @@ Kopiert lenke til utklipptavle Spel av episode Tilbakestill til standardverdi - Beklager, programmet har krasjet. Ein anonymisert feilrapport vil bli sendt til utviklarane %1$s %2$d%3$s Ingen sesong Episode diff --git a/app/src/main/res/values-b+no/strings.xml b/app/src/main/res/values-b+no/strings.xml index ac13f57f9..8fa0d8eca 100644 --- a/app/src/main/res/values-b+no/strings.xml +++ b/app/src/main/res/values-b+no/strings.xml @@ -57,7 +57,6 @@ Spill av fil Fortsett nedlasting Stopp nedlastingen - Deaktiver automatisk feilrapportering Mer informasjon Gjemme seg Spille @@ -110,8 +109,6 @@ Informasjon Avansert søk Gir deg søkeresultatene atskilt etter leverandør - Sender bare rapportere om krasjer - Sender ingen rapportere Vis filler-episode til anime Vis appoppdateringer Søk automatisk etter nye oppdateringer etter at appen har startet. @@ -129,7 +126,6 @@ Linken er kopiert til utklippstavlen spille episode tilbakestill standardverdien - Beklager, programmet krasjet. En anonym feilrapport vil bli sendt til utviklerne Sesong Ingen sesong Episode @@ -346,7 +342,6 @@ passord123 Språkkode (nb_NO) Forrige - Krasjrapportering Utvidelser %1$d %2$s MittKuleBrukernavn diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index 3bff02c2b..9d5043d3a 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -61,7 +61,6 @@ Odtwórz plik Wznów pobieranie Wstrzymaj pobieranie - Wyłącz przekazywanie błędów Więcej informacji Ukryj Odtwórz @@ -141,8 +140,6 @@ Informacje Zaawansowane wyszukiwanie Szukaj z podziałem na źródła - Wysyłaj dane tylko przy awariach - Nie wysyłaj żadnych danych Pokaż fillery dla anime Pokaż zwiastuny Pokaż obrazki z Kitsu @@ -165,7 +162,6 @@ Skopiowano do schowka Odtwórz odcinek Zresetowano - Awaria aplikacji. Anonimowe zgłoszenie błędu zostanie wysłane programistom Sezon %1$s %2$d%3$s Brak sezonu @@ -368,7 +364,6 @@ Poprzedni Pomiń konfigurację Dostosuj wygląd aplikacji do urządzenia - Zgłaszanie błędów Co chcesz obejrzeć Gotowe Rozszerzenia diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index e5871672d..e82aa0b8c 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -69,7 +69,6 @@ Reproduzir arquivo Continuar download Pausar download - Desative o relatório automático de erros Mais informações Ocultar Reproduzir @@ -148,8 +147,6 @@ Info Pesquisa avançada Mostrar resultados separados por fornecedor - Enviar apenas dados de falhas - Não enviar nenhum dado Mostrar episódios extras de animes Mostrar trailers Mostrar posters do Kitsu @@ -171,7 +168,6 @@ Link copiado para área de transferência Assistir episódio Restaurar para o padrão - Desculpe :/, o aplicativo travou. Um relatório de erro anônimo será enviado aos desenvolvedores Temporada Nenhuma temporada Episódio @@ -390,7 +386,6 @@ Anterior Saltar setup Change the look of the app to suit your device - Crash reporting What do you want to see Feito Extensões diff --git a/app/src/main/res/values-b+pt/strings.xml b/app/src/main/res/values-b+pt/strings.xml index f6ccf2d03..67706ed12 100644 --- a/app/src/main/res/values-b+pt/strings.xml +++ b/app/src/main/res/values-b+pt/strings.xml @@ -68,7 +68,6 @@ Reproduzir Ficheiro Retomar Transferência Pausar Transferência - Desativar relatório automático de erros Mais info Esconder Reproduzir @@ -147,8 +146,6 @@ Info Procura Avançada Mostra resultados separados por fornecedor - Só envia dados sobre falhas - Não envia nenhum dado Mostrar episódios de enchimento para anime Mostrar trailers Mostrar posters do kitsu @@ -169,7 +166,6 @@ Link copiado para a área de transferência Reproduzir episódio Restaurar para o padrão - Desculpe, a aplicação falhou. Um relatório de erro anónimo será enviado para os desenvolvedores Temporada Nenhuma Temporada Episódio @@ -349,7 +345,6 @@ Anterior Saltar setup Change the look of the app to suit your device - Crash reporting What do you want to see Feito Extensões diff --git a/app/src/main/res/values-b+qt/strings.xml b/app/src/main/res/values-b+qt/strings.xml index 2ca5a5881..246134c66 100644 --- a/app/src/main/res/values-b+qt/strings.xml +++ b/app/src/main/res/values-b+qt/strings.xml @@ -38,7 +38,6 @@ ahooo ouuhhh ahhaauugghh - ooo-ahahoohahoooooo-ahahouuhhhoouuhaaahhu ahooo a aaaghhouuhhh oh ahhh ahhhahaaaaaahhhaaaghh @@ -91,8 +90,6 @@ aauugghh ah aah ouuhhhooo-ahah aaaghh aauugghh ahahooo ouuhhhahh ooh oouuhahoooahhaaahhu ohaooh oouuhooo-ahah - aaaaa ahhhahhohoouuhahoooaaaghh aahhhaaaaa - oouuhoooohh ahhooo-ahah haa oohaauugghhooh oh aaaagggoooogg uuuugg aak aah aaaahhh ooogg uuuuuukh aah ooh aaaghh ahhhahoooooo-ahah aaaghh @@ -109,7 +106,6 @@ ooh oouuh ahoooah ohaa oh ouuhhh ouuhhhoouuh ouuhhh haaahhh ooo-ahah haaoh haaooh - oooooahoohaaaghh oouuhoooohhaaaaaoha ohaaauugghh oohaaaaaahooo ooo-ahah aaaaaa ahooo ahooo oouuhoooohh oooohhaaaghh oouuh ooo-ahah aauuh ohaahooo @@ -407,7 +403,6 @@ uuuugg aaaaggg ug aah uug aahh ug uuuuhhh oohh aah memory, oohh og oooohhh ug aaaahhh oooogggh - ooohh aaaaaakag ooogg aaaaggg aaaahhhuuhh %1$d %2$s… uuuhh aaaagg diff --git a/app/src/main/res/values-b+ro/strings.xml b/app/src/main/res/values-b+ro/strings.xml index a49bcb6f9..4c1685319 100644 --- a/app/src/main/res/values-b+ro/strings.xml +++ b/app/src/main/res/values-b+ro/strings.xml @@ -68,7 +68,6 @@ Redare fișier Continuați descărcarea Opriți descărcarea - Dezactivați raportarea automată a erorilor Mai multe informații Ascunde Începe @@ -147,8 +146,6 @@ Informații Căutare avansată Împărțiți rezultatele căutării în funcție de furnizor - Trimiteți date numai în cazul unui accident - Nu trimiteți niciun fel de date Afișează etichetele [filler] pentru anime Arată trailerul Arată afișele de la Kitsu @@ -168,7 +165,6 @@ Link copiat în clipboard Redare episod Restabilirea la valorile implicite - Ne pare rău, aplicația s-a blocat. Un raport de eroare anonim va fi trimis dezvoltatorilor Sezonul Nu există sezon Episodul @@ -436,7 +432,6 @@ Linkuri Funcții Autori - Raportarea accidentelor Adaugă depozit Biblioteca ta este goală :( \nConectați-vă într-un cont de bibliotecă sau adăugați emisiuni la biblioteca locală. diff --git a/app/src/main/res/values-b+ru/strings.xml b/app/src/main/res/values-b+ru/strings.xml index aa5e24ccc..bf22e8901 100644 --- a/app/src/main/res/values-b+ru/strings.xml +++ b/app/src/main/res/values-b+ru/strings.xml @@ -117,7 +117,6 @@ Внутренняя память Продолжить Скачать Остановить скачивание - Отключить автоматическое информирование об ошибках Импортируйте шрифты поместив их в %s Продолжить смотреть Убрать @@ -165,7 +164,6 @@ Ссылок не найдено Ссылка скопирована в буфер обмена Восстановить по умолчанию - Извините, приложение прекратило работу. Анонимный отчёт об ошибке будет отправлен разработчикам Серия Серии С @@ -220,7 +218,6 @@ Дополнения Плеер Резервное копирование данных - Отправлять данные только при вылетах Использовано Двойное нажатие для паузы Коснитесь дважды правой или левой стороны для поиска вперед или назад @@ -453,7 +450,6 @@ Скачивание обновления приложения… Недопустимый URL Перезапустите приложение, чтобы увидеть изменения. - Отчеты ошибках Что вы хотите увидеть Смотрите видео на этих языках Скачано файл @@ -485,7 +481,6 @@ Отображать случайную кнопку в библиотеке и главной странице Случайная кнопка Устаревший - Не отправляет данные Перезагрузить ссылки Предпочтительные медиа Опущенные diff --git a/app/src/main/res/values-b+sk/strings.xml b/app/src/main/res/values-b+sk/strings.xml index b13cbcc11..19be22684 100644 --- a/app/src/main/res/values-b+sk/strings.xml +++ b/app/src/main/res/values-b+sk/strings.xml @@ -50,7 +50,6 @@ Načítavanie… Dokončené Plánujem pozerať - Zakázať automatické nahlasovanie chýb Viac informácií Záložky Prehrať film @@ -151,7 +150,6 @@ Dvojitým ťuknutím pretočiť Automaticky sťahovať doplnky Pripojte sa na Discord - Neodosiela žiadne dáta Odstrániť čierne okraje Automaticky vyhľadať nové aktualizácie po spustení aplikácie. Prehrať epizódu @@ -167,7 +165,6 @@ V prehrávači použiť systémový jas namiesto tmavého prekrytia Zobraziť upútavky Automaticky nainštalovať všetky ešte nenainštalované doplnky z pridaných repozitárov. - Odosiela dáta len pri pádoch Knižnica GitHub Hľadať @@ -180,7 +177,6 @@ Zobraziť aktualizácie aplikácie Aktualizácia na predbežné vydania Vyhľadať aktualizácie predbežných vydaní namiesto plných vydaní - Ospravedlňujeme sa, aplikácia spadla. Vývojárom bude odoslané anonymné hlásenie o páde Obnoviť predvolenú hodnotu Sezóna Synopsa @@ -435,7 +431,6 @@ Neplatné dáta Neplatná URL adresa Odstráňte uzavreté titulky od titulkov - Správy zlyhania Zobraziť odporúčania Otestujte všetky rozšírenia Nadchádzajúce o %s diff --git a/app/src/main/res/values-b+so/strings.xml b/app/src/main/res/values-b+so/strings.xml index 8b1e4cc15..44a9b6d90 100644 --- a/app/src/main/res/values-b+so/strings.xml +++ b/app/src/main/res/values-b+so/strings.xml @@ -67,7 +67,6 @@ Trj Way bilaabmatay soo dejintu Daalaco - Jooji cillad gudbinta iskeed ah Waa la joojiyey dejintan Way bilaabantay cuaboonaysiintu Soo raridda lifaaqyadu way fashilantay @@ -96,7 +95,6 @@ Gurmadka iyo cusbooneysiinta Waa la kaydiyet xogta gurmadka Raadinta waxay kuugu kala qaybinaysaa mid kasta iyo qaybiyihiisa - Ma jirto xog la dirayo Way fashilantay in xogta laga soo celiyo faylka gurmadka%s Moobillada qaar ayaa awoodin iney Rakibaha cusub een isticmaalnay, kan ku day haddii cusbooneysiintu kuu shaqayn weydo. +30 @@ -108,7 +106,6 @@ Si iskii ah usoo deji sidkanaha Qari tayada muqaalka aad dooratay natiijada waxa aad raadiso Raadin heer-sare ah - Keliya xogta waxaa ka dirayaa marka appku duqeeyo Xalqad buuxis ah tusi anemiga Dhiseyaasha appka sii beniin Way fashilantay dejintu, hubi ogolaanshaha isticmaalka kaydka @@ -176,7 +173,6 @@ Bixiyahan kuma shaqeeyo koromakaastiga Asalkiisii hore kusoo celi Dulucuda - Raalliahow, wuu duqeeyey appku, waxa warbixin cillad-saarka ka caawisa loo direy dhiseyaasha Kalka %1$s %2$d%3$s Xalqad @@ -453,7 +449,6 @@ Kii hore Is dhaafi fadhiisintan Beddel sida appku u muuqdo si uu ugu habboonaado moobilekaaga - Warbixinra cillad-saarka Maxaad rabtaa inaad daawato Dhan Ku dar kayd-weyne diff --git a/app/src/main/res/values-b+sv/strings.xml b/app/src/main/res/values-b+sv/strings.xml index 2b6de30af..3bc16b041 100644 --- a/app/src/main/res/values-b+sv/strings.xml +++ b/app/src/main/res/values-b+sv/strings.xml @@ -40,7 +40,6 @@ Sub Radera fil Spela upp fil - Inaktivera automatisk felrapportering Mer information Göm @string/result_poster_img_des @@ -94,8 +93,6 @@ Information Avancerade sökresultat Presenterar sökresultaten i flera olika rader baserat på leverantören - Skickar endast data när appen kraschar - Skickar ingen data Visa appuppdateringar Sök automatiskt efter nya uppdateringar vid start. Uppdatera till beta-version @@ -112,7 +109,6 @@ Länken kopierades till urklipp Spela upp avsnitt Återställd till standardvärdet - Programmet kraschade tyvärr. En anonym felrapport kommer att skickas till utvecklarna Fel uppstod vid laddning av länkarna Säsong Ingen Säsong @@ -310,7 +306,6 @@ Slumpmässig Kommer snart… Filtrera efter föredraget språk - Kraschrapportering Kunde inte logga in på %s Utseende Layout diff --git a/app/src/main/res/values-b+ta/strings.xml b/app/src/main/res/values-b+ta/strings.xml index a71e4df2b..da1353229 100644 --- a/app/src/main/res/values-b+ta/strings.xml +++ b/app/src/main/res/values-b+ta/strings.xml @@ -34,7 +34,6 @@ கோப்பை அழி கோப்பு இடைநிறுத்தம் பதிவிறக்கம் - தானியங்கி பிழை அறிக்கையை முடக்கு மேலும் செய்தி மறை அகற்று @@ -203,7 +202,6 @@ காப்பு அதிர்வெண் தரவு சேமிக்கப்பட்டது சேமிப்பக அனுமதிகள் இல்லை. தயவு செய்து மீண்டும் முயற்சிக்கவும். - செயலிழப்புகள் குறித்த தரவை மட்டுமே அனுப்புகிறது சுவரொட்டியில் இடைமுகம் கூறுகளை மாற்றவும் மேம்படுத்தல் சோதிக்க பூட்டு @@ -213,7 +211,6 @@ மூலம் கேம் சுவரொட்டி படம் - செயலிழப்பு அறிக்கை பொது பட்டியல் பதிப்பு நூலகத்தைத் தேர்ந்தெடுக்கவும் @@ -410,13 +407,11 @@ பிழையைப் பதிவிறக்குங்கள், சேமிப்பக அனுமதிகளை சரிபார்க்கவும் வழங்குநரை மாற்றவும் முன்னோட்டம் பின்னணி - எந்த தரவை அனுப்பவில்லை அனிமேசுக்கு நிரப்பு அத்தியாயத்தைக் காட்டு கூடுதல் களஞ்சியங்களிலிருந்து இன்னும் நிறுவப்படாத அனைத்து செருகுநிரல்களையும் தானாக நிறுவவும். முழு வெளியீடுகளுக்கு பதிலாக மட்டுமே புதுப்பிப்புகளைத் தேடுங்கள் அதே தேவ்சின் அனிம் பயன்பாடு அத்தியாயங்கள் - மன்னிக்கவும், விண்ணப்பம் செயலிழந்தது. ஒரு அநாமதேய பிழை அறிக்கை டெவலப்பர்களுக்கு அனுப்பப்படும் முடிந்தது வசன வரிகள் இல்லை கார்ட்டூன்கள் diff --git a/app/src/main/res/values-b+tl/strings.xml b/app/src/main/res/values-b+tl/strings.xml index 5fd25031e..87c15f527 100644 --- a/app/src/main/res/values-b+tl/strings.xml +++ b/app/src/main/res/values-b+tl/strings.xml @@ -60,7 +60,6 @@ I-play ang file I-resume ang download I-pause ang download - Huwag awtomatikong mag-ulat ng bug More Info Itago I-play @@ -113,8 +112,6 @@ Impormasyon Adbans na maghanap Magbibigay ng pinaghiwa-hiwalay na resulta - Nagpapadala lamang ng data kung nag-crash ang app - Hindi magpapadala ng kahit anong data Ipakita ang filler episode sa anime Ipakita kung may bagong update Awtomatikong mag-check ng bagong update pagbukas ng app @@ -132,7 +129,6 @@ Link copied to clipboard Play Episode I-reset sa default value - Paumanhin, ang app ay nag-crash. Isang anonymous bug report ang ipapadala sa developers Season No season Episode diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml index 34e702f91..6f9940160 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -84,7 +84,6 @@ Dosyayı Oynat İndirmeyi Sürdür İndirmeyi Duraklat - Otomatik hata raporlamayı kapat Daha fazla bilgi Gizle Oynat @@ -165,8 +164,6 @@ Bilgi Gelişmiş arama Arama sonuçlarını sağlayıcıya göre ayırır - Yalnızca çökmelerle ilgili verileri gönderir - Veri göndermez Anime için dolgu bölümünü göster Fragmanları göster Kitsu posterlerini göster @@ -188,7 +185,6 @@ Bağlantı panoya kopyalandı Bölümü oynat Varsayılan değere sıfırla - Üzgünüz, uygulama çöktü. Geliştiricilere anonim bir hata raporu gönderilecek Sezon %1$s %2$d%3$s Sezon yok @@ -427,7 +423,6 @@ Önceki Kurulumu atla Cihazınıza uygun uygulama görünümünü seçin - Çökme raporları Ne izlemek istiyorsunuz Bitti Eklentiler diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index eb7a25930..5761cf318 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -47,7 +47,6 @@ Суб. Видалити файл Відновити завантаження - Вимкнути автонадсилання звітів про помилки Приховати Переглянути Подробиці @@ -144,8 +143,6 @@ Подробиці Розширений пошук Показувати результати пошуку, розділені за постачальниками - Надсилати лише дані про збої - Не надсилати дані Показувати наповнювачі для аніме Показувати трейлери Приховати вибрану якість відео у результатах пошуку @@ -233,7 +230,6 @@ Дано бананів Рік +30 - Вибачте, у застосунку стався збій. Буде відправлено анонімне повідомлення про помилку розробникам %1$s %2$d%3$s Епізод %1$d-%2$d @@ -398,7 +394,6 @@ Далі Переглядайте відео на цих мовах Пропустити налаштування - Звітування про збої Що ви хочете побачити Готово Розширення diff --git a/app/src/main/res/values-b+ur/strings.xml b/app/src/main/res/values-b+ur/strings.xml index a6afb22de..494e3118e 100644 --- a/app/src/main/res/values-b+ur/strings.xml +++ b/app/src/main/res/values-b+ur/strings.xml @@ -124,8 +124,6 @@ اکاؤنٹس اور سیکیورٹی معلومات آپ کو فراہم کنندہ کے ذریعہ علیحدہ کردہ تلاش کے نتائج فراہم کرتا ہے - صرف کریش پر ڈیٹا بھیجتا ہے - کوئی ڈیٹا نہیں بھیجتا تلاش کے نتائج میں منتخب ویڈیو کوالٹی چھپائیں خودکار پلگ ان اپ ڈیٹس ایپ کی تازہ کاریاں نمایش کریں @@ -156,7 +154,6 @@ ڈاؤن لوڈ ہو گیا انٹرنل سٹوریج ڈاؤن لوڈ کو روکیں - خودکار بگ رپورٹنگ کو غیر فعال کریں ذرائع کا استعمال کرتے ہوئے تلاش کریں اس فراہم کنندہ کو مناسب طریقے سے کام کرنے کے لیے VPN کی ضرورت پڑ سکتی ہے ڈیفالٹ سیٹنگز پر ری سیٹ کرنے کے لیے دبائیں اور تھامیں @@ -177,7 +174,6 @@ شامل کردہ ذخیروں سے خود بخود تمام ابھی تک انسٹال نہیں ہوئے پلگ ان انسٹال کریں۔ اپلیکیشن کو شروع کرنے کے بعد خود بخود نئی اپ ڈیٹس کی تلاش کریں۔ کچھ فون نئے پیکیج انسٹالر کو سپورٹ نہیں کرتے ہیں. اگر اپ ڈیٹس انسٹال نہیں ہوتے ہیں تو لیگیسی آپشن کو آزمائیں. - معذرت، ایپلی کیشن کریش ہو گئی. ایک گمنام بگ رپورٹ ڈویلپرز کو بھیجی جائے گی سیزن %1$s %2$d%3$s کوئی سیزن نہیں @@ -396,7 +392,6 @@ حوالہ دینے والا (مرضی پر) ان زبانوں میں ویڈیوز دیکھیں سیٹ اپ کو چھوڑ دیں - کریش رپورٹنگ ہو گیا ایکسٹینشنز ذخیرہ URL diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index b1e166126..0a7474435 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -72,7 +72,6 @@ Xem Tệp Tiếp tục tải Tạm dừng tải - Tắt tự động gửi dữ liệu khi xảy ra lỗi Thông tin thêm Ẩn Xem ngay @@ -152,8 +151,6 @@ Thông tin Tìm kiếm nâng cao Cho phép tìm kiếm theo bộ lọc từng nhà cung cấp - Chỉ gửi dữ liệu khi xảy ra lỗi đến nhà phát triển - Không gửi dữ liệu Hiển thị tập phụ cho anime Hiển thị trailer Hiển thị poster từ Kitsu @@ -175,7 +172,6 @@ Đã sao chép liên kết vào bộ nhớ tạm Xem Phim Thiết lập lại giá trị mặc định - Rất tiếc! Ứng dụng đã xảy ra lỗi. Một báo cáo lỗi ẩn danh sẽ được gửi đến nhà phát triển Mùa Không có mùa nào Tập @@ -395,7 +391,6 @@ Trước đó Bỏ qua cài đặt setup Chọn giao diện phù hợp với thiết bị của bạn - Báo cáo sự cố Bạn muốn xem gì Hoàn tất Tiện ích mở rộng diff --git a/app/src/main/res/values-b+zh+TW/strings.xml b/app/src/main/res/values-b+zh+TW/strings.xml index e4d7f9fa2..c29a8e206 100644 --- a/app/src/main/res/values-b+zh+TW/strings.xml +++ b/app/src/main/res/values-b+zh+TW/strings.xml @@ -84,7 +84,6 @@ 播放檔案 繼續下載 暫停下載 - 停用自動錯誤報告 更多資訊 隱藏 播放 @@ -165,8 +164,6 @@ 資訊 進階搜尋 為您提供按片源分開的搜尋結果 - 僅在程式崩潰時傳送相關資料 - 不傳送資料 顯示動畫外傳 顯示預告片 顯示來自 Kitsu 的封面 @@ -188,7 +185,6 @@ 連結已複製到剪貼簿 播放劇集 重設為預設值 - 很抱歉,應用程式崩潰了,將傳送一份匿名錯誤報告給開發者 %1$s %2$d%3$s 無季 @@ -428,7 +424,6 @@ 上一個 跳過設定 更改應用程式的外觀以適應你的設備 - 程式崩潰報告 你想要看什麼 完成 擴充功能 diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 4a5b8c1b2..9dfb9d904 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -84,7 +84,6 @@ 播放文件 继续下载 暂停下载 - 禁用自动错误报告 更多信息 隐藏 播放 @@ -165,8 +164,6 @@ 信息 高级搜索 按片源分割搜索结果 - 仅发送关于崩溃的数据 - 不发送数据 显示动画外传 显示预告片 显示来自 Kitsu 的封面 @@ -189,7 +186,6 @@ 链接已复制到剪贴板 播放剧集 重置为默认值 - 抱歉,应用崩溃了,将发送一份匿名错误报告给开发者 %1$s %2$d%3$s 无季 @@ -429,7 +425,6 @@ 上一个 跳过设置向导 更改为适应您的设备的应用布局 - 崩溃报告 您想要看什么 完成 扩展 diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 9d1bfd1f6..10f61e40e 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -83,7 +83,6 @@ Reprodueix el fitxer Continua la descàrrega Posa la descàrrega en espera - Deshabilita el report automàtic d\'errors Més informació Amaga Reprodueix @@ -290,7 +289,6 @@ Anterior Omet la configuració Canvia l\'aspecte de la aplicació perquè s\'adapti al vostre dispositiu - Informe d\'errors Què vols veure Fet Extensions diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c6fdfa092..2409c8878 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -179,7 +179,6 @@ Play File Resume Download Pause Download - Disable automatic bug reporting More info Hide Play @@ -273,8 +272,6 @@ Info Advanced Search Gives you the search results separated by provider - Only sends data on crashes - Sends no data Show filler episode for anime Show trailers Show posters from Kitsu @@ -302,9 +299,6 @@ Link copied to clipboard Play Episode Reset to default value - Sorry, the application crashed. An anonymous bug report will be sent to the - developers - Season %1$s %2$d%3$s No Season @@ -623,7 +617,6 @@ Previous Skip setup Change the look of the app to suit your device - Crash reporting What do you want to see Done Extensions diff --git a/app/src/main/res/xml/settings_updates.xml b/app/src/main/res/xml/settings_updates.xml index 2c2de0431..3eb63b28f 100644 --- a/app/src/main/res/xml/settings_updates.xml +++ b/app/src/main/res/xml/settings_updates.xml @@ -84,12 +84,5 @@ android:icon="@drawable/ic_baseline_construction_24" android:title="@string/redo_setup_process" app:key="@string/redo_setup_key" /> - diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 088cad6d5..d30e384ef 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,4 @@ [versions] -acraCore = "5.13.1" activityKtx = "1.11.0" androidGradlePlugin = "8.13.1" appcompat = "1.7.1" @@ -54,8 +53,6 @@ compileSdk = "36" targetSdk = "36" [libraries] -acra-core = { module = "ch.acra:acra-core", version.ref = "acraCore" } -acra-toast = { module = "ch.acra:acra-toast", version.ref = "acraCore" } activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" } android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } From 009dcc2b895de671bde1f0dbc16dded63d490843 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Mon, 24 Nov 2025 10:26:07 -0700 Subject: [PATCH 605/962] Use version catalog for plugins (#2206) --- app/build.gradle.kts | 6 +++--- build.gradle.kts | 27 ++++++++------------------- docs/build.gradle.kts | 6 +++--- gradle/libs.versions.toml | 13 +++++++++---- library/build.gradle.kts | 12 ++++++------ settings.gradle.kts | 24 ++++++++++++++++++++---- 6 files changed, 49 insertions(+), 39 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 1a98ac2f3..fb8f2e836 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -6,9 +6,9 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { - id("com.android.application") - id("kotlin-android") - id("org.jetbrains.dokka") + alias(libs.plugins.android.application) + alias(libs.plugins.dokka) + alias(libs.plugins.kotlin.android) } val javaTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get()) diff --git a/build.gradle.kts b/build.gradle.kts index 22cdc4ba3..a5c4f9fbc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,25 +1,14 @@ -buildscript { - repositories { - google() - mavenCentral() - } - - dependencies { - classpath(libs.android.gradle.plugin) - classpath(libs.buildkonfig.gradle.plugin) // Universal build config - classpath(libs.dokka.gradle.plugin) - classpath(libs.kotlin.gradle.plugin) - } +plugins { + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.buildkonfig) apply false // Universal build config + alias(libs.plugins.dokka) apply false + alias(libs.plugins.kotlin.android) apply false + alias(libs.plugins.kotlin.jvm) apply false + alias(libs.plugins.kotlin.multiplatform) apply false } allprojects { - repositories { - google() - mavenCentral() - mavenLocal() - maven("https://jitpack.io") - } - // https://docs.gradle.org/current/userguide/upgrading_major_version_9.html#test_task_fails_when_no_tests_are_discovered tasks.withType().configureEach { failOnNoDiscoveredTests = false diff --git a/docs/build.gradle.kts b/docs/build.gradle.kts index 203b93818..8f5be2a2d 100644 --- a/docs/build.gradle.kts +++ b/docs/build.gradle.kts @@ -1,6 +1,6 @@ plugins { - kotlin("jvm") - id("org.jetbrains.dokka") + alias(libs.plugins.kotlin.jvm) + alias(libs.plugins.dokka) } dependencies { @@ -10,4 +10,4 @@ dependencies { dokka { moduleName = "Cloudstream" -} \ No newline at end of file +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d30e384ef..fdef44ef8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,3 +1,5 @@ +# https://docs.gradle.org/current/userguide/plugins.html#sec:version_catalog_plugin_application +# https://docs.gradle.org/current/userguide/dependency_versions.html#sec:strict-version [versions] activityKtx = "1.11.0" androidGradlePlugin = "8.13.1" @@ -54,10 +56,8 @@ targetSdk = "36" [libraries] activity-ktx = { module = "androidx.activity:activity-ktx", version.ref = "activityKtx" } -android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "androidGradlePlugin" } appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } biometric = { module = "androidx.biometric:biometric", version.ref = "biometric" } -buildkonfig-gradle-plugin = { module = "com.codingfeline.buildkonfig:buildkonfig-gradle-plugin", version.ref = "buildkonfigGradlePlugin" } coil = { module = "io.coil-kt.coil3:coil", version.ref = "coil" } coil-network-okhttp = { module = "io.coil-kt.coil3:coil-network-okhttp", version.ref = "coil" } colorpicker = { module = "com.github.recloudstream:color-picker-android", version.ref = "colorpicker" } @@ -67,7 +67,6 @@ core = { module = "androidx.test:core" } core-ktx = { module = "androidx.core:core-ktx", version.ref = "coreKtx" } databinding = { module = "androidx.databinding:viewbinding", version.ref = "androidGradlePlugin" } desugar_jdk_libs_nio = { module = "com.android.tools:desugar_jdk_libs_nio", version.ref = "desugar_jdk_libs_nio" } -dokka-gradle-plugin = { module = "org.jetbrains.dokka:dokka-gradle-plugin", version.ref = "dokkaGradlePlugin" } espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } ext-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" } fuzzywuzzy = { module = "me.xdrop:fuzzywuzzy", version.ref = "fuzzywuzzy" } @@ -77,7 +76,6 @@ jsoup = { module = "org.jsoup:jsoup", version.ref = "jsoup" } junit = { module = "junit:junit", version.ref = "junit" } junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "junitKtx" } juniversalchardet = { module = "com.github.albfernandez:juniversalchardet", version.ref = "juniversalchardet" } -kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlinGradlePlugin" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" } lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } @@ -116,6 +114,13 @@ work-runtime = { module = "androidx.work:work-runtime", version.ref = "workRunti work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } [plugins] +android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } +buildkonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfigGradlePlugin" } +dokka = { id = "org.jetbrains.dokka", version.ref = "dokkaGradlePlugin" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlinGradlePlugin" } +kotlin-jvm = { id = "org.jetbrains.kotlin.jvm" , version.ref = "kotlinGradlePlugin" } +kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlinGradlePlugin" } [bundles] media3 = ["media3-cast", "media3-common", "media3-container", "media3-datasource-cronet", "media3-datasource-okhttp", "media3-exoplayer", "media3-exoplayer-dash", "media3-exoplayer-hls", "media3-session", "media3-ui"] diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 3fc1ce8dd..a418efaab 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -6,11 +6,11 @@ import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { - kotlin("multiplatform") - id("maven-publish") - id("com.android.library") - id("com.codingfeline.buildkonfig") - id("org.jetbrains.dokka") + id("maven-publish") // Gradle core plugin + alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.library) + alias(libs.plugins.buildkonfig) + alias(libs.plugins.dokka) } val javaTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get()) @@ -125,4 +125,4 @@ dokka { } } } -} \ No newline at end of file +} diff --git a/settings.gradle.kts b/settings.gradle.kts index bd26f9f34..73bf5a195 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,5 +1,21 @@ -rootProject.name = "CloudStream" +// https://developer.android.com/build#settings-file +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} -include(":app") -include(":library") -include(":docs") \ No newline at end of file +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + mavenLocal() + maven("https://jitpack.io") + } +} + +rootProject.name = "CloudStream" +include(":app", ":library", ":docs") From 7f9f89cbf66a5a48778d54f45245b8ece959fc60 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 25 Nov 2025 07:16:37 -0700 Subject: [PATCH 606/962] Use version catalog bundles for coil and lifecycle (#2237) --- app/build.gradle.kts | 14 ++++++-------- gradle/libs.versions.toml | 13 +++++++------ 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fb8f2e836..385b4af6d 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -173,9 +173,8 @@ dependencies { implementation(libs.core.ktx) implementation(libs.activity.ktx) implementation(libs.appcompat) - implementation(libs.bundles.navigationKtx) - implementation(libs.lifecycle.livedata.ktx) - implementation(libs.lifecycle.viewmodel.ktx) + implementation(libs.bundles.lifecycle) + implementation(libs.bundles.navigation) // Design & UI implementation(libs.preference.ktx) @@ -184,21 +183,20 @@ dependencies { implementation(libs.swiperefreshlayout) // Coil Image Loading - implementation(libs.coil) - implementation(libs.coil.network.okhttp) + implementation(libs.bundles.coil) // Media 3 (ExoPlayer) implementation(libs.bundles.media3) implementation(libs.video) + // FFmpeg Decoding + implementation(libs.bundles.nextlib) + // PlayBack implementation(libs.colorpicker) // Subtitle Color Picker implementation(libs.newpipeextractor) // For Trailers implementation(libs.juniversalchardet) // Subtitle Decoding - // FFmpeg Decoding - implementation(libs.bundles.nextlibMedia3) - // UI Stuff implementation(libs.shimmer) // Shimmering Effect (Loading Skeleton) implementation(libs.palette.ktx) // Palette for Images -> Colors diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index fdef44ef8..4ebe1ce2d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,8 +24,7 @@ junitVersion = "1.3.0" juniversalchardet = "2.5.0" kotlinGradlePlugin = "2.2.21" kotlinxCoroutinesCore = "1.10.2" -lifecycleLivedataKtx = "2.9.4" -lifecycleViewmodelKtx = "2.9.4" +lifecycleKtx = "2.9.4" material = "1.14.0-alpha06" media3 = "1.8.0" navigationKtx = "2.9.6" @@ -77,8 +76,8 @@ junit = { module = "junit:junit", version.ref = "junit" } junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "junitKtx" } juniversalchardet = { module = "com.github.albfernandez:juniversalchardet", version.ref = "juniversalchardet" } kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" } -lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleLivedataKtx" } -lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleViewmodelKtx" } +lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "lifecycleKtx" } +lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "lifecycleKtx" } material = { module = "com.google.android.material:material", version.ref = "material" } media3-cast = { module = "androidx.media3:media3-cast", version.ref = "media3" } media3-common = { module = "androidx.media3:media3-common", version.ref = "media3" } @@ -123,6 +122,8 @@ kotlin-jvm = { id = "org.jetbrains.kotlin.jvm" , version.ref = "kotlinGradlePlug kotlin-multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlinGradlePlugin" } [bundles] +coil = ["coil", "coil-network-okhttp"] +lifecycle = ["lifecycle-livedata-ktx", "lifecycle-viewmodel-ktx"] media3 = ["media3-cast", "media3-common", "media3-container", "media3-datasource-cronet", "media3-datasource-okhttp", "media3-exoplayer", "media3-exoplayer-dash", "media3-exoplayer-hls", "media3-session", "media3-ui"] -navigationKtx = ["navigation-fragment-ktx", "navigation-ui-ktx"] -nextlibMedia3 = ["nextlib-media3ext", "nextlib-mediainfo"] +navigation = ["navigation-fragment-ktx", "navigation-ui-ktx"] +nextlib = ["nextlib-media3ext", "nextlib-mediainfo"] From 9d651f1f8264d008540fb7eb9bdf35d80b34c41d Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 25 Nov 2025 07:24:21 -0700 Subject: [PATCH 607/962] Remove work-runtime dependency (#2234) We only really need to include the Kotlin version, work-runtime-ktx here. --- app/build.gradle.kts | 1 - gradle/libs.versions.toml | 2 -- 2 files changed, 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 385b4af6d..6cc801591 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -220,7 +220,6 @@ dependencies { implementation(libs.torrentserver) // Downloading & Networking - implementation(libs.work.runtime) implementation(libs.work.runtime.ktx) implementation(libs.nicehttp) // HTTP Lib diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4ebe1ce2d..8e22a64b2 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,7 +44,6 @@ tmdbJava = "2.13.0" torrentserver = "7861970e038b35cd8c6918384e49caf26903e09e" tvprovider = "1.1.0" video = "1.0.0" -workRuntime = "2.10.5" workRuntimeKtx = "2.10.5" jvmTarget = "1.8" @@ -109,7 +108,6 @@ tmdb-java = { module = "com.uwetrottmann.tmdb2:tmdb-java", version.ref = "tmdbJa torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref = "torrentserver" } tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" } video = { module = "com.google.android.mediahome:video", version.ref = "video" } -work-runtime = { module = "androidx.work:work-runtime", version.ref = "workRuntime" } work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } [plugins] From d43a371b15eb6806cb549adaeeda343ec939cd92 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:34:14 -0700 Subject: [PATCH 608/962] Better backward compatibility for AcraApplication (#2265) --- .../lagradost/cloudstream3/AcraApplication.kt | 48 ++++++++++++++----- .../lagradost/cloudstream3/CloudStreamApp.kt | 2 + 2 files changed, 37 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index 92993f78b..262f57522 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -1,5 +1,11 @@ package com.lagradost.cloudstream3 +import android.content.Context +import com.lagradost.api.setContext +import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DataStore.setKey +import java.lang.ref.WeakReference + /** * Deprecated alias for CloudStreamApp for backwards compatibility with plugins. * Use CloudStreamApp instead. @@ -12,61 +18,77 @@ package com.lagradost.cloudstream3 level = DeprecationLevel.WARNING )*/ class AcraApplication { + // All methods here can be changed to be a wrapper around CloudStream app + // without a seperate deprecation after next stable. All methods should + // also be deprecated at that time. companion object { + // This can be removed without deprecation after next stable + private var _context: WeakReference? = null /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.context"), level = DeprecationLevel.WARNING )*/ - val context get() = CloudStreamApp.context + var context + get() = _context?.get() + internal set(value) { + _context = WeakReference(value) + setContext(WeakReference(value)) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.setKey(path, value)"), level = DeprecationLevel.WARNING )*/ - fun setKey(path: String, value: T) = - CloudStreamApp.setKey(path, value) + fun setKey(path: String, value: T) { + context?.setKey(path, value) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.setKey(folder, path, value)"), level = DeprecationLevel.WARNING )*/ - fun setKey(folder: String, path: String, value: T) = - CloudStreamApp.setKey(folder, path, value) + fun setKey(folder: String, path: String, value: T) { + context?.setKey(folder, path, value) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(path, defVal)"), level = DeprecationLevel.WARNING )*/ - inline fun getKey(path: String, defVal: T?): T? = - CloudStreamApp.getKey(path, defVal) + inline fun getKey(path: String, defVal: T?): T? { + return context?.getKey(path, defVal) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(path)"), level = DeprecationLevel.WARNING )*/ - inline fun getKey(path: String): T? = - CloudStreamApp.getKey(path) + inline fun getKey(path: String): T? { + return context?.getKey(path) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(folder, path)"), level = DeprecationLevel.WARNING )*/ - inline fun getKey(folder: String, path: String): T? = - CloudStreamApp.getKey(folder, path) + inline fun getKey(folder: String, path: String): T? { + return context?.getKey(folder, path) + } /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.getKey(folder, path, defVal)"), level = DeprecationLevel.WARNING )*/ - inline fun getKey(folder: String, path: String, defVal: T?): T? = - CloudStreamApp.getKey(folder, path, defVal) + inline fun getKey(folder: String, path: String, defVal: T?): T? { + return context?.getKey(folder, path, defVal) + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt index 6421f38c2..b78327998 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CloudStreamApp.kt @@ -86,6 +86,8 @@ class CloudStreamApp : Application(), SingletonImageLoader.Factory { override fun attachBaseContext(base: Context?) { super.attachBaseContext(base) context = base + // This can be removed without deprecation after next stable + AcraApplication.context = context } override fun newImageLoader(context: PlatformContext): ImageLoader { From 7fb6f3f5353306699f9a9779f96cd8797a37f5b8 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 27 Nov 2025 11:37:47 -0700 Subject: [PATCH 609/962] Add explicit dependsOn for copyJar (#2261) --- .github/workflows/prerelease.yml | 4 +--- app/build.gradle.kts | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 62fd571f7..164a8458e 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -48,9 +48,7 @@ jobs: echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT - name: Run Gradle - run: | - ./gradlew assemblePrerelease build androidSourcesJar - ./gradlew makeJar # for classes.jar, has to be done after assemblePrerelease + run: ./gradlew assemblePrerelease build androidSourcesJar makeJar env: SIGNING_KEY_ALIAS: "key0" SIGNING_KEY_PASSWORD: ${{ steps.fetch_keystore.outputs.key_pwd }} diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 6cc801591..c0e55071b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -241,6 +241,7 @@ tasks.register("androidSourcesJar") { } tasks.register("copyJar") { + dependsOn("build", ":library:jvmJar") from( "build/intermediates/compile_app_classes_jar/prereleaseDebug/bundlePrereleaseDebugClassesToCompileJar", "../library/build/libs" From 38296bfb1a791d07e4611068b292a2537c51dbad Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:24:31 +0100 Subject: [PATCH 610/962] Fixed the atrocity of download selection along with some crash fixes and bugs. --- .../lagradost/cloudstream3/ui/BaseFragment.kt | 29 +- .../ui/download/DownloadAdapter.kt | 491 +++++++++--------- .../ui/download/DownloadChildFragment.kt | 45 +- .../ui/download/DownloadFragment.kt | 42 +- .../ui/download/DownloadViewModel.kt | 176 ++++--- .../ui/settings/SettingsFragment.kt | 3 +- .../ui/settings/extensions/PluginsFragment.kt | 4 +- .../cloudstream3/utils/ConsistentLiveData.kt | 44 ++ 8 files changed, 464 insertions(+), 370 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt index 14901dda2..72955e7cf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/BaseFragment.kt @@ -126,6 +126,33 @@ abstract class BaseFragment( ) : Fragment(), BaseFragmentHelper { override var _binding: T? = null + /** Safer activity?.onBackPressedDispatcher?.onBackPressed() with fallback behavior instead of app crash */ + fun dispatchBackPressed() { + try { + activity?.onBackPressedDispatcher?.onBackPressed() + } catch (_: IllegalStateException) { + // FragmentManager is already executing transactions, so try again + delayedDispatchBackPressed(5) + } catch (t: Throwable) { + logError(t) + } + } + + /** Recursive back press when available */ + private fun delayedDispatchBackPressed(remaining: Int) { + if (remaining <= 0) return + binding?.root?.postDelayed({ + try { + activity?.onBackPressedDispatcher?.onBackPressed() + } catch (_: IllegalStateException) { + // FragmentManager is already executing transactions, so try again + delayedDispatchBackPressed(remaining - 1) + } catch (t: Throwable) { + logError(t) + } + }, 200) + } + override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -238,7 +265,7 @@ abstract class BaseBottomSheetDialogFragment( } } -abstract class BasePreferenceFragmentCompat(): PreferenceFragmentCompat() { +abstract class BasePreferenceFragmentCompat() : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) setSystemBarsPadding() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt index e6daf0f2f..b26e99d81 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.download +import android.annotation.SuppressLint import android.text.format.Formatter.formatShortFileSize import android.view.LayoutInflater import android.view.ViewGroup @@ -7,16 +8,15 @@ import android.widget.CheckBox import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.NoStateAdapter +import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.download.button.DownloadStatusTell import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull -import com.lagradost.cloudstream3.utils.DataStoreHelper.fixVisual import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.VideoDownloadHelper @@ -69,7 +69,7 @@ class DownloadAdapter( private val onHeaderClickEvent: (DownloadHeaderClickEvent) -> Unit, private val onItemClickEvent: (DownloadClickEvent) -> Unit, private val onItemSelectionChanged: (Int, Boolean) -> Unit, -) : ListAdapter(DiffCallback()) { +) : NoStateAdapter(DiffCallback()) { private var isMultiDeleteState: Boolean = false @@ -78,97 +78,194 @@ class DownloadAdapter( private const val VIEW_TYPE_CHILD = 1 } - inner class DownloadViewHolder( - private val binding: ViewBinding - ) : RecyclerView.ViewHolder(binding.root) { - fun bind(card: VisualDownloadCached?) { - when (binding) { - is DownloadHeaderEpisodeBinding -> bindHeader(card as? VisualDownloadCached.Header) - is DownloadChildEpisodeBinding -> bindChild(card as? VisualDownloadCached.Child) - } - } - - private fun bindHeader(card: VisualDownloadCached.Header?) { - if (binding !is DownloadHeaderEpisodeBinding || card == null) return - - val data = card.data - binding.apply { - episodeHolder.apply { - if (isMultiDeleteState) { - setOnClickListener { - toggleIsChecked(deleteCheckbox, data.id) - } - } - - setOnLongClickListener { - toggleIsChecked(deleteCheckbox, data.id) - true - } - } - - downloadHeaderPoster.apply { - loadImage(data.poster) - if (isMultiDeleteState) { - setOnClickListener { - toggleIsChecked(deleteCheckbox, data.id) - } - } else { - setOnClickListener { - onHeaderClickEvent.invoke( - DownloadHeaderClickEvent( - DOWNLOAD_ACTION_LOAD_RESULT, - data - ) - ) - } - } - - setOnLongClickListener { - toggleIsChecked(deleteCheckbox, data.id) - true - } - } - downloadHeaderTitle.text = data.name - val formattedSize = formatShortFileSize(itemView.context, card.totalBytes) - - if (card.child != null) { - handleChildDownload(card, formattedSize) - } else handleParentDownload(card, formattedSize) + private fun bindHeader(binding: ViewBinding, card: VisualDownloadCached.Header?) { + if (binding !is DownloadHeaderEpisodeBinding || card == null) return + val data = card.data + binding.apply { + episodeHolder.apply { if (isMultiDeleteState) { - deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> - onItemSelectionChanged.invoke(data.id, isChecked) + setOnClickListener { + toggleIsChecked(deleteCheckbox, data.id) } - } else deleteCheckbox.setOnCheckedChangeListener(null) + setOnLongClickListener { + toggleIsChecked(deleteCheckbox, data.id) + true + } + } else { + setOnLongClickListener { + onItemSelectionChanged.invoke(data.id, true) + true + } + } + } - deleteCheckbox.apply { - isVisible = isMultiDeleteState - isChecked = card.isSelected + downloadHeaderPoster.apply { + loadImage(data.poster) + if (isMultiDeleteState) { + setOnClickListener { + toggleIsChecked(deleteCheckbox, data.id) + } + } else { + setOnClickListener { + onHeaderClickEvent.invoke( + DownloadHeaderClickEvent( + DOWNLOAD_ACTION_LOAD_RESULT, + data + ) + ) + } + } + + setOnLongClickListener { + toggleIsChecked(deleteCheckbox, data.id) + true + } + } + downloadHeaderTitle.text = data.name + val formattedSize = formatShortFileSize(binding.root.context, card.totalBytes) + + if (card.child != null) { + handleChildDownload(card, formattedSize) + } else handleParentDownload(card, formattedSize) + + if (isMultiDeleteState) { + deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> + onItemSelectionChanged.invoke(data.id, isChecked) + } + } else deleteCheckbox.setOnCheckedChangeListener(null) + + deleteCheckbox.apply { + isVisible = isMultiDeleteState + isChecked = card.isSelected + } + } + } + + private fun DownloadHeaderEpisodeBinding.handleChildDownload( + card: VisualDownloadCached.Header, + formattedSize: String + ) { + card.child ?: return + downloadHeaderGotoChild.isVisible = false + + val posDur = getViewPos(card.data.id) + watchProgressContainer.isVisible = true + downloadHeaderEpisodeProgress.apply { + isVisible = posDur != null + posDur?.let { + val max = (it.duration / 1000).toInt() + val progress = (it.position / 1000).toInt() + + if (max > 0 && progress >= (0.95 * max).toInt()) { + playIcon.setImageResource(R.drawable.ic_baseline_check_24) + isVisible = false + } else { + playIcon.setImageResource(R.drawable.netflix_play) + this.max = max + this.progress = progress + isVisible = true } } } - private fun DownloadHeaderEpisodeBinding.handleChildDownload( - card: VisualDownloadCached.Header, - formattedSize: String - ) { - card.child ?: return - downloadHeaderGotoChild.isVisible = false + val status = downloadButton.getStatus(card.child.id, card.currentBytes, card.totalBytes) + if (status == DownloadStatusTell.IsDone) { + // We do this here instead if we are finished downloading + // so that we can use the value from the view model + // rather than extra unneeded disk operations and to prevent a + // delay in updating download icon state. + downloadButton.setProgress(card.currentBytes, card.totalBytes) + downloadButton.applyMetaData(card.child.id, card.currentBytes, card.totalBytes) + // We will let the view model handle this + downloadButton.doSetProgress = false + downloadButton.progressBar.progressDrawable = + downloadButton.getDrawableFromStatus(status) + ?.let { ContextCompat.getDrawable(downloadButton.context, it) } + downloadHeaderInfo.text = formattedSize + } else { + // We need to make sure we restore the correct progress + // when we refresh data in the adapter. + downloadButton.resetView() + val drawable = downloadButton.getDrawableFromStatus(status)?.let { + ContextCompat.getDrawable(downloadButton.context, it) + } + downloadButton.statusView.setImageDrawable(drawable) + downloadButton.progressBar.progressDrawable = + ContextCompat.getDrawable( + downloadButton.context, + downloadButton.progressDrawable + ) + } - val posDur = getViewPos(card.data.id) - watchProgressContainer.isVisible = true - downloadHeaderEpisodeProgress.apply { + downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, onItemClickEvent) + downloadButton.isVisible = !isMultiDeleteState + + if (!isMultiDeleteState) { + episodeHolder.setOnClickListener { + onItemClickEvent.invoke( + DownloadClickEvent( + DOWNLOAD_ACTION_PLAY_FILE, + card.child + ) + ) + } + } + } + + private fun DownloadHeaderEpisodeBinding.handleParentDownload( + card: VisualDownloadCached.Header, + formattedSize: String + ) { + downloadButton.isVisible = false + downloadHeaderEpisodeProgress.isVisible = false + downloadHeaderGotoChild.isVisible = !isMultiDeleteState + + try { + downloadHeaderInfo.text = + downloadHeaderInfo.context.getString(R.string.extra_info_format).format( + card.totalDownloads, + downloadHeaderInfo.context.resources.getQuantityString( + R.plurals.episodes, + card.totalDownloads + ), + formattedSize + ) + } catch (e: Exception) { + downloadHeaderInfo.text = null + logError(e) + } + + if (!isMultiDeleteState) { + episodeHolder.setOnClickListener { + onHeaderClickEvent.invoke( + DownloadHeaderClickEvent( + DOWNLOAD_ACTION_GO_TO_CHILD, + card.data + ) + ) + } + } + } + + private fun bindChild(binding: ViewBinding, card: VisualDownloadCached.Child?) { + if (binding !is DownloadChildEpisodeBinding || card == null) return + + val data = card.data + binding.apply { + val posDur = getViewPos(data.id) + downloadChildEpisodeProgress.apply { isVisible = posDur != null posDur?.let { val max = (it.duration / 1000).toInt() val progress = (it.position / 1000).toInt() if (max > 0 && progress >= (0.95 * max).toInt()) { - playIcon.setImageResource(R.drawable.ic_baseline_check_24) + downloadChildEpisodePlay.setImageResource(R.drawable.ic_baseline_check_24) isVisible = false } else { - playIcon.setImageResource(R.drawable.netflix_play) + downloadChildEpisodePlay.setImageResource(R.drawable.play_button_transparent) this.max = max this.progress = progress isVisible = true @@ -176,20 +273,21 @@ class DownloadAdapter( } } - val status = downloadButton.getStatus(card.child.id, card.currentBytes, card.totalBytes) + val status = downloadButton.getStatus(data.id, card.currentBytes, card.totalBytes) if (status == DownloadStatusTell.IsDone) { // We do this here instead if we are finished downloading // so that we can use the value from the view model // rather than extra unneeded disk operations and to prevent a // delay in updating download icon state. downloadButton.setProgress(card.currentBytes, card.totalBytes) - downloadButton.applyMetaData(card.child.id, card.currentBytes, card.totalBytes) + downloadButton.applyMetaData(data.id, card.currentBytes, card.totalBytes) // We will let the view model handle this downloadButton.doSetProgress = false downloadButton.progressBar.progressDrawable = downloadButton.getDrawableFromStatus(status) ?.let { ContextCompat.getDrawable(downloadButton.context, it) } - downloadHeaderInfo.text = formattedSize + downloadChildEpisodeTextExtra.text = + formatShortFileSize(downloadChildEpisodeTextExtra.context, card.totalBytes) } else { // We need to make sure we restore the correct progress // when we refresh data in the adapter. @@ -205,208 +303,105 @@ class DownloadAdapter( ) } - downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, onItemClickEvent) + downloadButton.setDefaultClickListener( + data, + downloadChildEpisodeTextExtra, + onItemClickEvent + ) downloadButton.isVisible = !isMultiDeleteState - if (!isMultiDeleteState) { - episodeHolder.setOnClickListener { - onItemClickEvent.invoke( - DownloadClickEvent( - DOWNLOAD_ACTION_PLAY_FILE, - card.child - ) - ) - } - } - } - - private fun DownloadHeaderEpisodeBinding.handleParentDownload( - card: VisualDownloadCached.Header, - formattedSize: String - ) { - downloadButton.isVisible = false - downloadHeaderEpisodeProgress.isVisible = false - downloadHeaderGotoChild.isVisible = !isMultiDeleteState - - try { - downloadHeaderInfo.text = - downloadHeaderInfo.context.getString(R.string.extra_info_format).format( - card.totalDownloads, - downloadHeaderInfo.context.resources.getQuantityString( - R.plurals.episodes, - card.totalDownloads - ), - formattedSize - ) - } catch (e: Exception) { - downloadHeaderInfo.text = null - logError(e) + downloadChildEpisodeText.apply { + text = context.getNameFull(data.name, data.episode, data.season) + isSelected = true // Needed for text repeating } - if (!isMultiDeleteState) { - episodeHolder.setOnClickListener { - onHeaderClickEvent.invoke( - DownloadHeaderClickEvent( - DOWNLOAD_ACTION_GO_TO_CHILD, - card.data - ) - ) - } + downloadChildEpisodeHolder.setOnClickListener { + onItemClickEvent.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, data)) } - } - private fun bindChild(card: VisualDownloadCached.Child?) { - if (binding !is DownloadChildEpisodeBinding || card == null) return - - val data = card.data - binding.apply { - val posDur = getViewPos(data.id) - downloadChildEpisodeProgress.apply { - isVisible = posDur != null - posDur?.let { - val max = (it.duration / 1000).toInt() - val progress = (it.position / 1000).toInt() - - if (max > 0 && progress >= (0.95 * max).toInt()) { - downloadChildEpisodePlay.setImageResource(R.drawable.ic_baseline_check_24) - isVisible = false - } else { - downloadChildEpisodePlay.setImageResource(R.drawable.play_button_transparent) - this.max = max - this.progress = progress - isVisible = true + downloadChildEpisodeHolder.apply { + when { + isMultiDeleteState -> { + setOnClickListener { + toggleIsChecked(deleteCheckbox, data.id) + } + setOnLongClickListener { + toggleIsChecked(deleteCheckbox, data.id) + true } } - } - val status = downloadButton.getStatus(data.id, card.currentBytes, card.totalBytes) - if (status == DownloadStatusTell.IsDone) { - // We do this here instead if we are finished downloading - // so that we can use the value from the view model - // rather than extra unneeded disk operations and to prevent a - // delay in updating download icon state. - downloadButton.setProgress(card.currentBytes, card.totalBytes) - downloadButton.applyMetaData(data.id, card.currentBytes, card.totalBytes) - // We will let the view model handle this - downloadButton.doSetProgress = false - downloadButton.progressBar.progressDrawable = - downloadButton.getDrawableFromStatus(status) - ?.let { ContextCompat.getDrawable(downloadButton.context, it) } - downloadChildEpisodeTextExtra.text = - formatShortFileSize(downloadChildEpisodeTextExtra.context, card.totalBytes) - } else { - // We need to make sure we restore the correct progress - // when we refresh data in the adapter. - downloadButton.resetView() - val drawable = downloadButton.getDrawableFromStatus(status)?.let { - ContextCompat.getDrawable(downloadButton.context, it) - } - downloadButton.statusView.setImageDrawable(drawable) - downloadButton.progressBar.progressDrawable = - ContextCompat.getDrawable( - downloadButton.context, - downloadButton.progressDrawable - ) - } - - downloadButton.setDefaultClickListener( - data, - downloadChildEpisodeTextExtra, - onItemClickEvent - ) - downloadButton.isVisible = !isMultiDeleteState - - downloadChildEpisodeText.apply { - text = context.getNameFull(data.name, data.episode, data.season) - isSelected = true // Needed for text repeating - } - - downloadChildEpisodeHolder.setOnClickListener { - onItemClickEvent.invoke(DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, data)) - } - - downloadChildEpisodeHolder.apply { - when { - isMultiDeleteState -> { - setOnClickListener { - toggleIsChecked(deleteCheckbox, data.id) - } - } - - else -> { - setOnClickListener { - onItemClickEvent.invoke( - DownloadClickEvent( - DOWNLOAD_ACTION_PLAY_FILE, - data - ) + else -> { + setOnClickListener { + onItemClickEvent.invoke( + DownloadClickEvent( + DOWNLOAD_ACTION_PLAY_FILE, + data ) - } + ) + } + + setOnLongClickListener { + onItemSelectionChanged.invoke(data.id, true) + true } } - - setOnLongClickListener { - toggleIsChecked(deleteCheckbox, data.id) - true - } } + } - if (isMultiDeleteState) { - deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> - onItemSelectionChanged.invoke(data.id, isChecked) - } - } else deleteCheckbox.setOnCheckedChangeListener(null) - - deleteCheckbox.apply { - isVisible = isMultiDeleteState - isChecked = card.isSelected + if (isMultiDeleteState) { + deleteCheckbox.setOnCheckedChangeListener { _, isChecked -> + onItemSelectionChanged.invoke(data.id, isChecked) } + } else deleteCheckbox.setOnCheckedChangeListener(null) + + deleteCheckbox.apply { + isVisible = isMultiDeleteState + isChecked = card.isSelected } } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DownloadViewHolder { + override fun onCreateCustomContent(parent: ViewGroup, viewType: Int): ViewHolderState { val inflater = LayoutInflater.from(parent.context) val binding = when (viewType) { VIEW_TYPE_HEADER -> DownloadHeaderEpisodeBinding.inflate(inflater, parent, false) VIEW_TYPE_CHILD -> DownloadChildEpisodeBinding.inflate(inflater, parent, false) else -> throw IllegalArgumentException("Invalid view type") } - return DownloadViewHolder(binding) + return ViewHolderState(binding) } - override fun onBindViewHolder(holder: DownloadViewHolder, position: Int) { - holder.bind(getItem(position)) - } + override fun onBindContent( + holder: ViewHolderState, + item: VisualDownloadCached, + position: Int + ) { + when (val binding = holder.view) { + is DownloadHeaderEpisodeBinding -> bindHeader( + binding, + item as? VisualDownloadCached.Header + ) - override fun getItemViewType(position: Int): Int { - return when (getItem(position)) { - is VisualDownloadCached.Child -> VIEW_TYPE_CHILD - is VisualDownloadCached.Header -> VIEW_TYPE_HEADER - else -> throw IllegalArgumentException("Invalid data type at position $position") + is DownloadChildEpisodeBinding -> bindChild( + binding, + item as? VisualDownloadCached.Child + ) } } + override fun customContentViewType(item: VisualDownloadCached): Int { + return when (item) { + is VisualDownloadCached.Child -> VIEW_TYPE_CHILD + is VisualDownloadCached.Header -> VIEW_TYPE_HEADER + } + } + + @SuppressLint("NotifyDataSetChanged") fun setIsMultiDeleteState(value: Boolean) { if (isMultiDeleteState == value) return isMultiDeleteState = value - notifyItemRangeChanged(0, itemCount) - } - - fun notifyAllSelected() { - currentList.indices.forEach { index -> - if (!currentList[index].isSelected) { - notifyItemChanged(index) - } - } - } - - fun notifySelectionStates() { - currentList.indices.forEach { index -> - if (currentList[index].isSelected) { - notifyItemChanged(index) - } - } + notifyDataSetChanged() // This is shit, but what can you do? } private fun toggleIsChecked(checkbox: CheckBox, itemId: Int) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt index 980d08a9e..08194fd31 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt @@ -9,6 +9,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding +import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.BaseFragment import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick @@ -41,6 +42,7 @@ class DownloadChildFragment : BaseFragment( override fun onDestroyView() { activity?.detachBackPressedCallback("Downloads") + downloadViewModel.clearChildren() super.onDestroyView() } @@ -68,27 +70,22 @@ class DownloadChildFragment : BaseFragment( downloadViewModel.setIsMultiDeleteState(false) } - /** - * We have to make sure selected items are - * cleared here as well so we don't run in an - * inconsistent state where selected items do - * not match the multi delete state we are in. - */ - downloadViewModel.clearSelectedItems() val folder = arguments?.getString("folder") val name = arguments?.getString("name") if (folder == null) { - activity?.onBackPressedDispatcher?.onBackPressed() + dispatchBackPressed() return } + context?.let { downloadViewModel.updateChildList(it, folder) } + binding.downloadChildToolbar.apply { title = name if (isLayout(PHONE or EMULATOR)) { setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) setNavigationOnClickListener { - activity?.onBackPressedDispatcher?.onBackPressed() + dispatchBackPressed() } } setAppBarNoScrollFlagsOnTV() @@ -96,13 +93,18 @@ class DownloadChildFragment : BaseFragment( binding.downloadDeleteAppbar.setAppBarNoScrollFlagsOnTV() - observe(downloadViewModel.childCards) { - if (it.isEmpty()) { - activity?.onBackPressedDispatcher?.onBackPressed() - return@observe + observe(downloadViewModel.childCards) { cards -> + when (cards) { + is Resource.Success -> { + if (cards.value.isEmpty()) { + dispatchBackPressed() + } + (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(cards.value) + } + else -> { + (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(null) + } } - - (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(it) } observe(downloadViewModel.isMultiDeleteState) { isMultiDeleteState -> val adapter = binding.downloadChildList.adapter as? DownloadAdapter @@ -124,7 +126,7 @@ class DownloadChildFragment : BaseFragment( binding.btnDelete.isVisible = it.isNotEmpty() binding.selectItemsText.isVisible = it.isEmpty() - val allSelected = downloadViewModel.isAllSelected() + val allSelected = downloadViewModel.isAllChildrenSelected() if (allSelected) { binding.btnToggleAll.setText(R.string.deselect_all) } else binding.btnToggleAll.setText(R.string.select_all) @@ -156,11 +158,9 @@ class DownloadChildFragment : BaseFragment( nextDown = FOCUS_SELF, ) } - - context?.let { downloadViewModel.updateChildList(it, folder) } } - private fun handleSelectedChange(selected: MutableSet) { + private fun handleSelectedChange(selected: Set) { if (selected.isNotEmpty()) { binding?.downloadDeleteAppbar?.isVisible = true binding?.downloadChildToolbar?.isVisible = false @@ -179,14 +179,11 @@ class DownloadChildFragment : BaseFragment( } binding?.btnToggleAll?.setOnClickListener { - val allSelected = downloadViewModel.isAllSelected() - val adapter = binding?.downloadChildList?.adapter as? DownloadAdapter + val allSelected = downloadViewModel.isAllChildrenSelected() if (allSelected) { - adapter?.notifySelectionStates() downloadViewModel.clearSelectedItems() } else { - adapter?.notifyAllSelected() - downloadViewModel.selectAllItems() + downloadViewModel.selectAllChildren() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index 46bb0c7dc..e3d77abac 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -25,6 +25,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentDownloadsBinding import com.lagradost.cloudstream3.databinding.StreamInputBinding import com.lagradost.cloudstream3.isEpisodeBased +import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.BaseFragment @@ -101,19 +102,27 @@ class DownloadFragment : BaseFragment( downloadViewModel.setIsMultiDeleteState(false) } - /** - * We have to make sure selected items are - * cleared here as well so we don't run in an - * inconsistent state where selected items do - * not match the multi delete state we are in. - */ - downloadViewModel.clearSelectedItems() + observe(downloadViewModel.headerCards) { cards -> + when (cards) { + is Resource.Success -> { + (binding.downloadList.adapter as? DownloadAdapter)?.submitList(cards.value) + binding.textNoDownloads.isVisible = cards.value.isEmpty() + binding.downloadLoading.isVisible = false + binding.downloadList.isVisible = true + } - observe(downloadViewModel.headerCards) { - (binding.downloadList.adapter as? DownloadAdapter)?.submitList(it) - binding.downloadLoading.isVisible = false - binding.textNoDownloads.isVisible = it.isEmpty() + is Resource.Loading -> { + binding.downloadList.isVisible = false + binding.downloadLoading.isVisible = true + } + + is Resource.Failure -> { + binding.downloadList.isVisible = true + binding.downloadLoading.isVisible = false + } + } } + observe(downloadViewModel.availableBytes) { updateStorageInfo( binding.root.context, @@ -173,7 +182,7 @@ class DownloadFragment : BaseFragment( binding.btnDelete.isVisible = it.isNotEmpty() binding.selectItemsText.isVisible = it.isEmpty() - val allSelected = downloadViewModel.isAllSelected() + val allSelected = downloadViewModel.isAllHeadersSelected() if (allSelected) { binding.btnToggleAll.setText(R.string.deselect_all) } else binding.btnToggleAll.setText(R.string.select_all) @@ -251,7 +260,7 @@ class DownloadFragment : BaseFragment( } } - private fun handleSelectedChange(selected: MutableSet) { + private fun handleSelectedChange(selected: Set) { if (selected.isNotEmpty()) { binding?.downloadDeleteAppbar?.isVisible = true binding?.downloadAppbar?.isVisible = false @@ -270,14 +279,11 @@ class DownloadFragment : BaseFragment( } binding?.btnToggleAll?.setOnClickListener { - val allSelected = downloadViewModel.isAllSelected() - val adapter = binding?.downloadList?.adapter as? DownloadAdapter + val allSelected = downloadViewModel.isAllHeadersSelected() if (allSelected) { - adapter?.notifySelectionStates() downloadViewModel.clearSelectedItems() } else { - adapter?.notifyAllSelected() - downloadViewModel.selectAllItems() + downloadViewModel.selectAllHeaders() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt index 137f1355e..bf81e6069 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt @@ -6,20 +6,22 @@ import android.os.Environment import android.os.StatFs import androidx.appcompat.app.AlertDialog import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.isEpisodeBased +import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus +import com.lagradost.cloudstream3.utils.ConsistentLiveData import com.lagradost.cloudstream3.utils.DOWNLOAD_EPISODE_CACHE import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE import com.lagradost.cloudstream3.utils.DataStore.getFolderName import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKeys +import com.lagradost.cloudstream3.utils.ResourceLiveData import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadManager.deleteFilesAndUpdateSettings import com.lagradost.cloudstream3.utils.VideoDownloadManager.getDownloadFileInfoAndUpdateSettings @@ -27,69 +29,80 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext class DownloadViewModel : ViewModel() { + private val _headerCards = ResourceLiveData>(Resource.Loading()) + val headerCards: LiveData>> = _headerCards - private val _headerCards = MutableLiveData>() - val headerCards: LiveData> = _headerCards + private val _childCards = ResourceLiveData>(Resource.Loading()) + val childCards: LiveData>> = _childCards - private val _childCards = MutableLiveData>() - val childCards: LiveData> = _childCards - - private val _usedBytes = MutableLiveData() + private val _usedBytes = ConsistentLiveData() val usedBytes: LiveData = _usedBytes - private val _availableBytes = MutableLiveData() + private val _availableBytes = ConsistentLiveData() val availableBytes: LiveData = _availableBytes - private val _downloadBytes = MutableLiveData() + private val _downloadBytes = ConsistentLiveData() val downloadBytes: LiveData = _downloadBytes - private val _selectedBytes = MutableLiveData(0) + private val _selectedBytes = ConsistentLiveData(0) val selectedBytes: LiveData = _selectedBytes - private val _isMultiDeleteState = MutableLiveData(false) + private val _isMultiDeleteState = ConsistentLiveData(false) val isMultiDeleteState: LiveData = _isMultiDeleteState - private val _selectedItemIds = MutableLiveData>(mutableSetOf()) - val selectedItemIds: LiveData> = _selectedItemIds - - private var previousVisual: List? = null + private val _selectedItemIds = ConsistentLiveData>(mutableSetOf()) + val selectedItemIds: LiveData> = _selectedItemIds fun setIsMultiDeleteState(value: Boolean) { _isMultiDeleteState.postValue(value) } fun addSelected(itemId: Int) { - updateSelectedItems { it.add(itemId) } + updateSelectedItems { it + itemId } } fun removeSelected(itemId: Int) { - updateSelectedItems { it.remove(itemId) } + updateSelectedItems { it - itemId } } - fun selectAllItems() { - val items = headerCards.value.orEmpty() + childCards.value.orEmpty() - updateSelectedItems { it.addAll(items.map { item -> item.data.id }) } + fun selectAllHeaders() { + updateSelectedItems { + _headerCards.success.orEmpty() + .map { item -> item.data.id }.toSet() + } + } + + fun selectAllChildren() { + updateSelectedItems { + _childCards.success.orEmpty() + .map { item -> item.data.id }.toSet() + } } fun clearSelectedItems() { // We need this to be done immediately // so we can't use postValue - _selectedItemIds.value = mutableSetOf() - updateSelectedItems { it.clear() } + updateSelectedItems { emptySet() } } - fun isAllSelected(): Boolean { + fun isAllChildrenSelected(): Boolean { val currentSelected = selectedItemIds.value ?: return false - val items = headerCards.value.orEmpty() + childCards.value.orEmpty() - return items.count() == currentSelected.count() && items.all { it.data.id in currentSelected } + val children = _childCards.success.orEmpty() + return currentSelected.size == children.size && children.all { it.data.id in currentSelected } } - private fun updateSelectedItems(action: (MutableSet) -> Unit) { - val currentSelected = selectedItemIds.value ?: mutableSetOf() - action(currentSelected) + fun isAllHeadersSelected(): Boolean { + val currentSelected = selectedItemIds.value ?: return false + val headers = _headerCards.success.orEmpty() + return currentSelected.size == headers.size && headers.all { it.data.id in currentSelected } + } + + private fun updateSelectedItems(action: (Set) -> Set) { + val currentSelected = action(selectedItemIds.value ?: mutableSetOf()) _selectedItemIds.postValue(currentSelected) + postHeaders() + postChildren() updateSelectedBytes() - updateSelectedCards() } private fun updateSelectedBytes() = viewModelScope.launchSafe { @@ -98,25 +111,12 @@ class DownloadViewModel : ViewModel() { _selectedBytes.postValue(totalSelectedBytes) } - private fun updateSelectedCards() = viewModelScope.launchSafe { - val currentSelected = selectedItemIds.value ?: return@launchSafe - - headerCards.value?.let { headers -> - headers.forEach { header -> - header.isSelected = header.data.id in currentSelected - } - _headerCards.postValue(headers) - } - - childCards.value?.let { children -> - children.forEach { child -> - child.isSelected = child.data.id in currentSelected - } - _childCards.postValue(children) - } - } fun updateHeaderList(context: Context) = viewModelScope.launchSafe { + // Do not push loading as it interrupts the UI + //_headerCards.postValue(Resource.Loading()) + clearSelectedItems() + val visual = withContext(Dispatchers.IO) { val children = context.getKeys(DOWNLOAD_EPISODE_CACHE) .mapNotNull { context.getKey(it) } @@ -133,11 +133,32 @@ class DownloadViewModel : ViewModel() { ) } - if (visual != previousVisual) { - previousVisual = visual - updateStorageStats(visual) - _headerCards.postValue(visual) - } + updateStorageStats(visual) + postHeaders(visual) + } + + fun postHeaders(newValue: List? = null) { + val newValue = newValue ?: _headerCards.success ?: return + val selection = selectedItemIds.value ?: emptySet() + _headerCards.postValue(Resource.Success(newValue.map { + it.copy( + isSelected = selection.contains( + it.data.id + ) + ) + })) + } + + fun postChildren(newValue: List? = null) { + val newValue = newValue ?: _childCards.success ?: return + val selection = selectedItemIds.value ?: emptySet() + _childCards.postValue(Resource.Success(newValue.map { + it.copy( + isSelected = selection.contains( + it.data.id + ) + ) + })) } private fun calculateDownloadStats( @@ -152,7 +173,8 @@ class DownloadViewModel : ViewModel() { val totalDownloads = mutableMapOf() children.forEach { child -> - val childFile = getDownloadFileInfoAndUpdateSettings(context, child.id) ?: return@forEach + val childFile = + getDownloadFileInfoAndUpdateSettings(context, child.id) ?: return@forEach if (childFile.fileLength <= 1) return@forEach val len = childFile.totalBytes @@ -179,10 +201,11 @@ class DownloadViewModel : ViewModel() { if (bytes <= 0 || downloads <= 0) return@mapNotNull null val isSelected = selectedItemIds.value?.contains(it.id) ?: false - val movieEpisode = if (it.type.isEpisodeBased()) null else context.getKey( - DOWNLOAD_EPISODE_CACHE, - getFolderName(it.id.toString(), it.id.toString()) - ) + val movieEpisode = + if (it.type.isEpisodeBased()) null else context.getKey( + DOWNLOAD_EPISODE_CACHE, + getFolderName(it.id.toString(), it.id.toString()) + ) VisualDownloadCached.Header( currentBytes = currentBytes, @@ -208,12 +231,16 @@ class DownloadViewModel : ViewModel() { } fun updateChildList(context: Context, folder: String) = viewModelScope.launchSafe { + _childCards.postValue(Resource.Loading()) // always push loading + clearSelectedItems() + val visual = withContext(Dispatchers.IO) { context.getKeys(folder).mapNotNull { key -> context.getKey(key) }.mapNotNull { val isSelected = selectedItemIds.value?.contains(it.id) ?: false - val info = getDownloadFileInfoAndUpdateSettings(context, it.id) ?: return@mapNotNull null + val info = + getDownloadFileInfoAndUpdateSettings(context, it.id) ?: return@mapNotNull null VisualDownloadCached.Child( currentBytes = info.fileLength, totalBytes = info.totalBytes, @@ -221,24 +248,20 @@ class DownloadViewModel : ViewModel() { data = it, ) } - }.sortedWith(compareBy( - // Sort by season first, and then by episode number, - // to ensure sorting is consistent. - { it.data.season ?: 0 }, - { it.data.episode } - )) + }.sortedWith( + compareBy( + // Sort by season first, and then by episode number, + // to ensure sorting is consistent. + { it.data.season ?: 0 }, + { it.data.episode } + )) - if (previousVisual != visual) { - previousVisual = visual - _childCards.postValue(visual) - } + postChildren(visual) } private fun removeItems(idsToRemove: Set) = viewModelScope.launchSafe { - val updatedHeaders = headerCards.value.orEmpty().filter { it.data.id !in idsToRemove } - val updatedChildren = childCards.value.orEmpty().filter { it.data.id !in idsToRemove } - _headerCards.postValue(updatedHeaders) - _childCards.postValue(updatedChildren) + postHeaders(_headerCards.success?.filter { it.data.id !in idsToRemove }) + postChildren(_childCards.success?.filter { it.data.id !in idsToRemove }) } private fun updateStorageStats(visual: List) { @@ -414,8 +437,8 @@ class DownloadViewModel : ViewModel() { } private fun getSelectedItemsData(): List? { - val headers = headerCards.value.orEmpty() - val children = childCards.value.orEmpty() + val headers = _headerCards.success.orEmpty() + val children = _childCards.success.orEmpty() return selectedItemIds.value?.mapNotNull { id -> headers.find { it.data.id == id } ?: children.find { it.data.id == id } @@ -423,10 +446,11 @@ class DownloadViewModel : ViewModel() { } private fun getItemDataFromId(itemId: Int): List { - val headers = headerCards.value.orEmpty() - val children = childCards.value.orEmpty() + return (_headerCards.success.orEmpty() + _childCards.success.orEmpty()).filter { it.data.id == itemId } + } - return (headers + children).filter { it.data.id == itemId } + fun clearChildren() { + _childCards.postValue(Resource.Loading()) } private data class DeleteData( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index 6ad0fffc6..c2d5e43e9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -16,6 +16,7 @@ import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.MainSettingsBinding import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.AuthRepo import com.lagradost.cloudstream3.ui.BaseFragment @@ -136,7 +137,7 @@ class SettingsFragment : BaseFragment( setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) children.firstOrNull { it is ImageView }?.tag = getString(R.string.tv_no_focus_tag) setNavigationOnClickListener { - activity?.onBackPressedDispatcher?.onBackPressed() + safe { activity?.onBackPressedDispatcher?.onBackPressed() } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt index 152131111..ee333abad 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt @@ -66,7 +66,7 @@ class PluginsFragment : BaseFragment( val downloadAllButton = binding.settingsToolbar.menu?.findItem(R.id.download_all) if (url == null || name == null) { - activity?.onBackPressedDispatcher?.onBackPressed() + dispatchBackPressed() return } @@ -126,7 +126,7 @@ class PluginsFragment : BaseFragment( if (searchView?.isIconified == false) { searchView.isIconified = true } else { - activity?.onBackPressedDispatcher?.onBackPressed() + dispatchBackPressed() } } searchView?.setOnQueryTextFocusChangeListener { _, hasFocus -> diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt new file mode 100644 index 000000000..7bb777bc4 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt @@ -0,0 +1,44 @@ +package com.lagradost.cloudstream3.utils + +import androidx.annotation.MainThread +import androidx.lifecycle.LiveData +import com.lagradost.cloudstream3.mvvm.Resource + +/** + * This is an atomic LiveData where you can do .value instantly after doing .postValue + * + * Fuck all that is LiveData, because we want this value to be accessible everywhere instantly. + * */ +open class ConsistentLiveData(initValue : T? = null) : LiveData() { + @Volatile private var internalValue : T? = initValue + + override fun getValue(): T? { + return internalValue + } + + /** If someone want the old behavior then good for them */ + val postedValue : T? get() = super.getValue() + + public override fun postValue(value : T?) { + super.postValue(value) + internalValue = value + } + + @MainThread + public override fun setValue(value: T?) { + super.setValue(value) + internalValue = value + } +} + +/** Atomic resource livedata, to make it easier to work with resources without local copies */ +class ResourceLiveData(initValue : Resource? = null) : ConsistentLiveData>(initValue) { + var success + get() = when(val output = this.value) { + is Resource.Success -> { + output.value + } + else -> null + } + set(value) = this.postValue(value?.let { Resource.Success(it) } ) +} From 1aa6a6215df2dc994e3a3f851d2fbba775749cbf Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Fri, 28 Nov 2025 21:37:55 +0100 Subject: [PATCH 611/962] Minor fix to ConsistentLiveData --- .../com/lagradost/cloudstream3/utils/ConsistentLiveData.kt | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt index 7bb777bc4..def41d7a0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/ConsistentLiveData.kt @@ -5,11 +5,14 @@ import androidx.lifecycle.LiveData import com.lagradost.cloudstream3.mvvm.Resource /** - * This is an atomic LiveData where you can do .value instantly after doing .postValue + * This is an atomic LiveData where you can do .value instantly after doing .postValue. + * + * The default behavior is a footgun that will cause race conditions, + * as we do not really care if it is posted as we only want the latest data (even in the binding). * * Fuck all that is LiveData, because we want this value to be accessible everywhere instantly. * */ -open class ConsistentLiveData(initValue : T? = null) : LiveData() { +open class ConsistentLiveData(initValue : T? = null) : LiveData(initValue) { @Volatile private var internalValue : T? = initValue override fun getValue(): T? { From b68fadc956b789e5d1b26c8649f784537de29ca9 Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Sun, 30 Nov 2025 00:24:08 +0100 Subject: [PATCH 612/962] Minor fixes to recycled DownloadAdapter cards --- .../com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt | 4 ++++ .../cloudstream3/ui/download/button/BaseFetchButton.kt | 1 + 2 files changed, 5 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt index b26e99d81..d0740f66a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt @@ -199,6 +199,7 @@ class DownloadAdapter( ) } + downloadHeaderInfo.isVisible = true downloadButton.setDefaultClickListener(card.child, downloadHeaderInfo, onItemClickEvent) downloadButton.isVisible = !isMultiDeleteState @@ -218,11 +219,14 @@ class DownloadAdapter( card: VisualDownloadCached.Header, formattedSize: String ) { + downloadButton.resetViewData() + watchProgressContainer.isVisible = false downloadButton.isVisible = false downloadHeaderEpisodeProgress.isVisible = false downloadHeaderGotoChild.isVisible = !isMultiDeleteState try { + downloadHeaderInfo.isVisible = true downloadHeaderInfo.text = downloadHeaderInfo.context.getString(R.string.extra_info_format).format( card.totalDownloads, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt index 908e3a80a..36a84d9f3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/button/BaseFetchButton.kt @@ -62,6 +62,7 @@ abstract class BaseFetchButton(context: Context, attributeSet: AttributeSet) : open fun resetViewData() { // lastRequest = null + progressText = null isZeroBytes = true doSetProgress = true persistentId = null From d794f6182efe9cecf1118bc1ec51d85fa2bc3783 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:11:05 -0700 Subject: [PATCH 613/962] Add Prerelease annotation to extractors that are not in stable (#2281) --- .../com/lagradost/cloudstream3/extractors/HubCloud.kt | 2 ++ .../com/lagradost/cloudstream3/extractors/OkRuExtractor.kt | 4 ++++ .../cloudstream3/extractors/PixelDrainExtractor.kt | 6 +++--- .../com/lagradost/cloudstream3/extractors/VkExtractor.kt | 2 ++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt index f8b289469..d8a3fb1ec 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/HubCloud.kt @@ -1,6 +1,7 @@ package com.lagradost.cloudstream3.extractors import com.lagradost.api.Log +import com.lagradost.cloudstream3.Prerelease import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.app @@ -11,6 +12,7 @@ import com.lagradost.cloudstream3.utils.loadExtractor import com.lagradost.cloudstream3.utils.newExtractorLink import java.net.URI +@Prerelease class HubCloud : ExtractorApi() { override val name = "Hub-Cloud" override val mainUrl = "https://hubcloud.*" diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OkRuExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OkRuExtractor.kt index d9803fa3e..f5f258cfe 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OkRuExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/OkRuExtractor.kt @@ -2,6 +2,8 @@ package com.lagradost.cloudstream3.extractors +import com.lagradost.cloudstream3.Prerelease + open class OkRuSSL : Odnoklassniki() { override var name = "OkRuSSL" override var mainUrl = "https://ok.ru" @@ -12,10 +14,12 @@ open class OkRuHTTP : Odnoklassniki() { override var mainUrl = "http://ok.ru" } +@Prerelease class OkRuSSLMobile : OkRuSSL() { override var mainUrl = "https://m.ok.ru" } +@Prerelease class OkRuHTTPMobile : OkRuHTTP() { override var mainUrl = "http://m.ok.ru" } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PixelDrainExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PixelDrainExtractor.kt index 4651f769a..3426289f8 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PixelDrainExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/PixelDrainExtractor.kt @@ -5,9 +5,11 @@ package com.lagradost.cloudstream3.extractors import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.utils.* -class PixelDrainDev : PixelDrain(){ +@Prerelease +class PixelDrainDev : PixelDrain() { override var mainUrl = "https://pixeldrain.dev" } + open class PixelDrain : ExtractorApi() { override val name = "PixelDrain" override val mainUrl = "https://pixeldrain.com" @@ -40,5 +42,3 @@ open class PixelDrain : ExtractorApi() { } } } - - diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VkExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VkExtractor.kt index 8e4540874..5009cea3e 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VkExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VkExtractor.kt @@ -1,6 +1,7 @@ // Made by @kraptor123 for cs-kraptor package com.lagradost.cloudstream3.extractors +import com.lagradost.cloudstream3.Prerelease import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi @@ -9,6 +10,7 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkType import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink +@Prerelease open class VkExtractor : ExtractorApi() { override val name = "Vk" override val mainUrl = "https://vkvideo.ru" From dad6b92ae3bdc34026a322104b3801a280c76adb Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:15:11 -0700 Subject: [PATCH 614/962] Fix downloads loading background (#2279) --- .../main/res/layout/fragment_downloads.xml | 106 +++++++++--------- 1 file changed, 50 insertions(+), 56 deletions(-) diff --git a/app/src/main/res/layout/fragment_downloads.xml b/app/src/main/res/layout/fragment_downloads.xml index 48e8bb074..d6f41d6b0 100644 --- a/app/src/main/res/layout/fragment_downloads.xml +++ b/app/src/main/res/layout/fragment_downloads.xml @@ -266,68 +266,62 @@ - + app:layout_behavior="@string/appbar_scrolling_view_behavior"> - - - - - - - + android:layout_height="match_parent" + android:paddingTop="20dp" + app:shimmer_auto_start="true" + app:shimmer_base_alpha="0.2" + app:shimmer_duration="@integer/loading_time" + app:shimmer_highlight_alpha="0.3" + tools:visibility="gone"> - - - - - - - - + + + + + + + + + + + + + + + Date: Sun, 30 Nov 2025 12:19:52 -0700 Subject: [PATCH 615/962] Fix no poster showing wrong poster (#2278) --- .../ui/search/SearchResultBuilder.kt | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt index 4c0aeb1bb..93526b57e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchResultBuilder.kt @@ -128,18 +128,11 @@ object SearchResultBuilder { cardText?.text = card.name cardText?.isVisible = showTitle cardView.isVisible = true - cardView.loadImage(card.posterUrl, card.posterHeaders) { - error { getImageFromDrawable(itemView.context, R.drawable.default_cover) } - /* - createPaletteAsync is currently disabled as we use hardware acceleration on images - val posterUrl = card.posterUrl - if (posterUrl != null && colorCallback != null) { - this.listener(onSuccess = { _,success -> - val bitmap = success.image.toBitmap() - createPaletteAsync(posterUrl, bitmap, colorCallback) - }) - }*/ - } + if (!card.posterUrl.isNullOrEmpty()) { + cardView.loadImage(card.posterUrl, card.posterHeaders) { + error { getImageFromDrawable(itemView.context, R.drawable.default_cover) } + } + } else cardView.loadImage(R.drawable.default_cover) fun click(view: View?) { clickCallback.invoke( @@ -330,4 +323,4 @@ object SearchResultBuilder { backgroundTintList = ColorStateList.valueOf(context.colorFromAttribute(R.attr.textColor)) } } -} \ No newline at end of file +} From 1dd477a9658e4730f3e67339559ac953612b944e Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:21:09 -0700 Subject: [PATCH 616/962] Disable MissingTranslation lint (#2276) Translations are handled by weblate, so we don't really care about missing translations here. --- app/build.gradle.kts | 1 + 1 file changed, 1 insertion(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c0e55071b..e2bdb2079 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -150,6 +150,7 @@ android { lint { abortOnError = false checkReleaseBuilds = false + disable.add("MissingTranslation") } buildFeatures { From 2ac0698bd23dda0d8f3ef8cbee488ce24b6c8cff Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 30 Nov 2025 12:32:20 -0700 Subject: [PATCH 617/962] Handle new Android 16 biometrics error type (#2275) Adds handling for `BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS` which was added in API level 36. --- .../utils/BiometricAuthenticator.kt | 67 ++++++++++++------- 1 file changed, 44 insertions(+), 23 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt index 1d9cf5f46..bce8f09dc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/BiometricAuthenticator.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.utils +import android.annotation.SuppressLint import android.app.Activity import android.app.KeyguardManager import android.content.Context @@ -100,31 +101,51 @@ object BiometricAuthenticator { } private fun isBiometricHardWareAvailable(): Boolean { - // authentication occurs only when this is true and device is truly capable + // Authentication occurs only when this is true and device is truly capable. var result = false - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { - when (biometricManager?.canAuthenticate( - DEVICE_CREDENTIAL or BIOMETRIC_STRONG or BIOMETRIC_WEAK - )) { - BiometricManager.BIOMETRIC_SUCCESS -> result = true - BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false - BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false - BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false - BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true - BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true - BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false + when { + Build.VERSION.SDK_INT >= Build.VERSION_CODES.BAKLAVA -> { + @SuppressLint("RestrictedApi") + when (biometricManager?.canAuthenticate( + DEVICE_CREDENTIAL or BIOMETRIC_STRONG or BIOMETRIC_WEAK + )) { + BiometricManager.BIOMETRIC_SUCCESS -> result = true + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false + BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false + BiometricManager.BIOMETRIC_ERROR_NOT_ENABLED_FOR_APPS -> result = false + BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true + BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true + BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false + } } - } else { - @Suppress("DEPRECATION") - when (biometricManager?.canAuthenticate()) { - BiometricManager.BIOMETRIC_SUCCESS -> result = true - BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false - BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false - BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false - BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true - BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true - BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false + + Build.VERSION.SDK_INT >= Build.VERSION_CODES.R -> { + @Suppress("SwitchIntDef") + when (biometricManager?.canAuthenticate( + DEVICE_CREDENTIAL or BIOMETRIC_STRONG or BIOMETRIC_WEAK + )) { + BiometricManager.BIOMETRIC_SUCCESS -> result = true + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false + BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false + BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true + BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true + BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false + } + } + + else -> { + @Suppress("DEPRECATION", "SwitchIntDef") + when (biometricManager?.canAuthenticate()) { + BiometricManager.BIOMETRIC_SUCCESS -> result = true + BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE -> result = false + BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE -> result = false + BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED -> result = false + BiometricManager.BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED -> result = true + BiometricManager.BIOMETRIC_ERROR_UNSUPPORTED -> result = true + BiometricManager.BIOMETRIC_STATUS_UNKNOWN -> result = false + } } } From 81b2718129639efe6d08750d502a53e43a5788a1 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Fri, 5 Dec 2025 05:04:52 +0530 Subject: [PATCH 618/962] horizontal poster in expanded list (#2286) --- .../com/lagradost/cloudstream3/ui/home/HomeFragment.kt | 10 +++++++--- .../lagradost/cloudstream3/ui/search/SearchAdaptor.kt | 5 ++++- .../java/com/lagradost/cloudstream3/utils/UIHelper.kt | 8 ++++---- app/src/main/res/layout/search_result_grid.xml | 2 +- .../main/res/layout/search_result_grid_expanded.xml | 2 +- 5 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 30b6b29d9..bc085df7e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -195,10 +195,10 @@ class HomeFragment : BaseFragment( // Span settings - binding.homeExpandedRecycler.spanCount = currentSpan + binding.homeExpandedRecycler.spanCount = context.getSpanCount(item.isHorizontalImages) binding.homeExpandedRecycler.setRecycledViewPool(SearchAdapter.sharedPool) binding.homeExpandedRecycler.adapter = - SearchAdapter(binding.homeExpandedRecycler) { callback -> + SearchAdapter(binding.homeExpandedRecycler,item.isHorizontalImages) { callback -> handleSearchClickCallback(callback) if (callback.action == SEARCH_ACTION_LOAD || callback.action == SEARCH_ACTION_PLAY_FILE) { bottomSheetDialogBuilder.ownHide() // we hide here because we want to resume it later @@ -238,7 +238,11 @@ class HomeFragment : BaseFragment( }) val spanListener = { span: Int -> - binding.homeExpandedRecycler.spanCount = span + if(item.isHorizontalImages){ + binding.homeExpandedRecycler.spanCount = context.getSpanCount(true) + }else{ + binding.homeExpandedRecycler.spanCount = span + } //(recycle.adapter as SearchAdapter).notifyDataSetChanged() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt index 7c763bf42..9338d4942 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchAdaptor.kt @@ -32,6 +32,7 @@ class SearchClickCallback( class SearchAdapter( private val resView: AutofitRecyclerView, + private val isHorizontal:Boolean = false, private val clickCallback: (SearchClickCallback) -> Unit, ) : NoStateAdapter(diffCallback = BaseDiffCallback(itemSame = { a, b -> if (a.id != null || b.id != null) { @@ -47,7 +48,9 @@ class SearchAdapter( var hasNext: Boolean = false - private val coverHeight: Int get() = (resView.itemWidth / 0.68).roundToInt() + private val coverRatio = if(isHorizontal) 1.8 else 0.68 + + private val coverHeight: Int get() = (resView.itemWidth / coverRatio).roundToInt() override fun onCreateContent(parent: ViewGroup): ViewHolderState { val inflater = LayoutInflater.from(parent.context) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt index e114abe29..6a8dabada 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/UIHelper.kt @@ -200,10 +200,10 @@ object UIHelper { listView.requestLayout() } - fun Context.getSpanCount(): Int { - val compactView = false - val spanCountLandscape = if (compactView) 2 else 6 - val spanCountPortrait = if (compactView) 1 else 3 + fun Context.getSpanCount(isHorizontal:Boolean=false): Int { +// val compactView = false + val spanCountLandscape = if (isHorizontal) 3 else 6 + val spanCountPortrait = if (isHorizontal) 2 else 3 val orientation = resources.configuration.orientation return if (orientation == Configuration.ORIENTATION_LANDSCAPE) { diff --git a/app/src/main/res/layout/search_result_grid.xml b/app/src/main/res/layout/search_result_grid.xml index 601860b48..02fdf0211 100644 --- a/app/src/main/res/layout/search_result_grid.xml +++ b/app/src/main/res/layout/search_result_grid.xml @@ -14,7 +14,7 @@ Date: Thu, 4 Dec 2025 23:50:56 +0000 Subject: [PATCH 619/962] Fix: Configuration change view invalidation on AutofitRecyclerView popup --- .../cloudstream3/ui/home/HomeFragment.kt | 29 +++++++++---------- .../ui/quicksearch/QuickSearchFragment.kt | 2 +- .../cloudstream3/ui/search/SearchFragment.kt | 2 +- .../com/lagradost/cloudstream3/utils/Event.kt | 25 ++++++++++++++++ 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index bc085df7e..6c58fac9a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -16,7 +16,9 @@ import android.widget.ListView import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.net.toUri import androidx.core.view.isGone +import androidx.core.view.isInvisible import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.preference.PreferenceManager @@ -64,7 +66,7 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.ownShow import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.Event +import com.lagradost.cloudstream3.utils.EmptyEvent import com.lagradost.cloudstream3.utils.SubtitleHelper.getFlagFromIso import com.lagradost.cloudstream3.utils.TvChannelUtils import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe @@ -73,17 +75,15 @@ import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.toPx -import com.lagradost.cloudstream3.utils.txt -import androidx.core.net.toUri -import androidx.core.view.isInvisible private const val TAG = "HomeFragment" class HomeFragment : BaseFragment( - BaseFragment.BindingCreator.Bind(FragmentHomeBinding::bind) + BindingCreator.Bind(FragmentHomeBinding::bind) ) { companion object { - val configEvent = Event() + // Used for configuration changed events to fix any popups that are not attached to a fragment + val configEvent = EmptyEvent() var currentSpan = 1 val listHomepageItems = mutableListOf() @@ -114,6 +114,7 @@ class HomeFragment : BaseFragment( //} // returns a BottomSheetDialog that will be hidden with OwnHidden upon hide, and must be saved to be able call ownShow in onCreateView + fun Activity.loadHomepageList( expand: HomeViewModel.ExpandableHomepageList, deleteCallback: (() -> Unit)? = null, @@ -237,13 +238,12 @@ class HomeFragment : BaseFragment( } }) - val spanListener = { span: Int -> - if(item.isHorizontalImages){ - binding.homeExpandedRecycler.spanCount = context.getSpanCount(true) - }else{ - binding.homeExpandedRecycler.spanCount = span - } - //(recycle.adapter as SearchAdapter).notifyDataSetChanged() + val spanListener = Runnable { + binding.homeExpandedRecycler.spanCount = context.getSpanCount(item.isHorizontalImages) + // We want to rebind everything to update the UI, however we also want to avoid + // any animations ect, this is the easiest way to do this, and the most correct + @SuppressLint("NotifyDataSetChanged") + binding.homeExpandedRecycler.adapter?.notifyDataSetChanged() } configEvent += spanListener @@ -621,8 +621,7 @@ class HomeFragment : BaseFragment( ) // Fix grid - currentSpan = view.context.getSpanCount() - configEvent.invoke(currentSpan) + configEvent.invoke() } @SuppressLint("SetTextI18n") diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt index a716cab41..724276ab7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/quicksearch/QuickSearchFragment.kt @@ -98,7 +98,7 @@ class QuickSearchFragment : BaseFragment( // Fix grid HomeFragment.currentSpan = view.context.getSpanCount() binding?.quickSearchAutofitResults?.spanCount = HomeFragment.currentSpan - HomeFragment.configEvent.invoke(HomeFragment.currentSpan) + HomeFragment.configEvent.invoke() } override fun onCreateView( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index d1efe6205..ae31d03fb 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -220,7 +220,7 @@ class SearchFragment : BaseFragment( // Fix grid currentSpan = view.context.getSpanCount() binding?.searchAutofitResults?.spanCount = currentSpan - HomeFragment.configEvent.invoke(currentSpan) + HomeFragment.configEvent.invoke() } override fun onBindingCreated( diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/Event.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/Event.kt index a0dfe734e..f66da4e5f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/Event.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/Event.kt @@ -24,3 +24,28 @@ class Event { } } } + +class EmptyEvent { + private val observers = mutableSetOf() + + val size: Int get() = observers.size + + operator fun plusAssign(observer: Runnable) { + synchronized(observers) { + observers.add(observer) + } + } + + operator fun minusAssign(observer: Runnable) { + synchronized(observers) { + observers.remove(observer) + } + } + + operator fun invoke() { + synchronized(observers) { + for (observer in observers) + observer.run() + } + } +} From 0b3aa24e66fce2b1428502fc1189f012c764c21d Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:09:54 -0700 Subject: [PATCH 620/962] Some cleanup/improvements to layouts (#2274) --- .../player_select_source_and_subs.xml | 1 + .../player_select_source_priority.xml | 5 +- app/src/main/res/layout/add_account_input.xml | 1 + app/src/main/res/layout/add_repo_input.xml | 1 + app/src/main/res/layout/add_site_input.xml | 1 + .../main/res/layout/bottom_input_dialog.xml | 71 ++++++++++--------- .../layout/chromecast_subtitle_settings.xml | 2 +- .../custom_preference_category_material.xml | 3 - .../res/layout/custom_preference_material.xml | 5 -- .../custom_preference_widget_seekbar.xml | 15 ++-- app/src/main/res/layout/empty_layout.xml | 33 ++++----- .../main/res/layout/fragment_extensions.xml | 4 +- app/src/main/res/layout/fragment_player.xml | 1 - .../main/res/layout/fragment_player_tv.xml | 1 - .../res/layout/fragment_plugin_details.xml | 2 +- .../main/res/layout/fragment_result_tv.xml | 2 +- app/src/main/res/layout/fragment_trailer.xml | 1 - app/src/main/res/layout/lock_pin_dialog.xml | 1 + app/src/main/res/layout/options_popup_tv.xml | 4 +- .../main/res/layout/player_custom_layout.xml | 2 +- .../res/layout/player_custom_layout_tv.xml | 1 - .../layout/player_select_source_and_subs.xml | 1 + .../layout/player_select_source_priority.xml | 3 +- app/src/main/res/layout/result_episode.xml | 3 +- app/src/main/res/layout/result_sync.xml | 1 + app/src/main/res/layout/stream_input.xml | 2 + app/src/main/res/layout/subtitle_offset.xml | 1 + app/src/main/res/layout/subtitle_settings.xml | 2 +- .../main/res/layout/trailer_custom_layout.xml | 1 - 29 files changed, 83 insertions(+), 88 deletions(-) diff --git a/app/src/main/res/layout-port/player_select_source_and_subs.xml b/app/src/main/res/layout-port/player_select_source_and_subs.xml index 06aed87fc..4710473d4 100644 --- a/app/src/main/res/layout-port/player_select_source_and_subs.xml +++ b/app/src/main/res/layout-port/player_select_source_and_subs.xml @@ -120,6 +120,7 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1" + android:baselineAligned="false" android:orientation="horizontal"> + tools:text="@string/profile_number" + tools:ignore="LabelFor" /> + xmlns:tools="http://schemas.android.com/tools" + android:nextFocusDown="@id/nginx_text_input" + android:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent"> + android:id="@+id/nginx_text_input" + android:nextFocusRight="@id/cancel_btt" + android:nextFocusLeft="@id/apply_btt" + android:layout_marginBottom="60dp" + android:layout_marginHorizontal="10dp" + android:paddingTop="10dp" + android:requiresFadingEdge="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_rowWeight="1" + android:autofillHints="no" + android:inputType="text" + tools:text="nginx.com" + tools:ignore="LabelFor" /> + android:id="@+id/apply_btt_holder" + android:orientation="horizontal" + android:layout_gravity="bottom" + android:gravity="bottom|end" + android:layout_marginTop="-60dp" + android:layout_width="match_parent" + android:layout_height="60dp"> + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|end" + android:text="@string/sort_apply" + android:id="@+id/apply_btt" + style="@style/WhiteButton" /> + android:layout_width="wrap_content" + android:layout_gravity="center_vertical|end" + android:text="@string/sort_cancel" + android:id="@+id/cancel_btt" + style="@style/BlackButton" /> diff --git a/app/src/main/res/layout/chromecast_subtitle_settings.xml b/app/src/main/res/layout/chromecast_subtitle_settings.xml index b1073190c..92d0bd350 100644 --- a/app/src/main/res/layout/chromecast_subtitle_settings.xml +++ b/app/src/main/res/layout/chromecast_subtitle_settings.xml @@ -12,7 +12,7 @@ diff --git a/app/src/main/res/layout/custom_preference_widget_seekbar.xml b/app/src/main/res/layout/custom_preference_widget_seekbar.xml index 02c5ec1be..132091e5f 100644 --- a/app/src/main/res/layout/custom_preference_widget_seekbar.xml +++ b/app/src/main/res/layout/custom_preference_widget_seekbar.xml @@ -18,13 +18,12 @@ + android:ellipsize="marquee" + tools:ignore="LabelFor" /> @@ -99,9 +96,7 @@ android:layout_width="0dp" android:layout_weight="1" android:layout_height="wrap_content" - android:paddingLeft="@dimen/preference_seekbar_padding_horizontal" android:paddingStart="@dimen/preference_seekbar_padding_horizontal" - android:paddingRight="@dimen/preference_seekbar_padding_horizontal" android:paddingEnd="@dimen/preference_seekbar_padding_horizontal" android:paddingTop="@dimen/preference_seekbar_padding_vertical" android:paddingBottom="@dimen/preference_seekbar_padding_vertical" @@ -113,13 +108,11 @@ - + - - \ No newline at end of file + + diff --git a/app/src/main/res/layout/fragment_extensions.xml b/app/src/main/res/layout/fragment_extensions.xml index fd1d0dade..b7cf4b6cd 100644 --- a/app/src/main/res/layout/fragment_extensions.xml +++ b/app/src/main/res/layout/fragment_extensions.xml @@ -70,8 +70,8 @@ android:nextFocusUp="@id/repo_recycler_view" android:orientation="horizontal" android:padding="10dp" - - android:visibility="visible"> + android:visibility="visible" + android:baselineAligned="false"> diff --git a/app/src/main/res/layout/result_episode.xml b/app/src/main/res/layout/result_episode.xml index 361ec0231..19f668889 100644 --- a/app/src/main/res/layout/result_episode.xml +++ b/app/src/main/res/layout/result_episode.xml @@ -54,7 +54,8 @@ + android:foreground="?android:attr/selectableItemBackgroundBorderless" + android:orientation="horizontal"> @@ -38,6 +39,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_weight="20" + android:autofillHints="no" android:hint="@string/referer" android:inputType="textUri" tools:ignore="LabelFor" /> diff --git a/app/src/main/res/layout/subtitle_offset.xml b/app/src/main/res/layout/subtitle_offset.xml index 6741de807..8570e9a26 100644 --- a/app/src/main/res/layout/subtitle_offset.xml +++ b/app/src/main/res/layout/subtitle_offset.xml @@ -4,6 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:baselineAligned="false" android:orientation="horizontal"> From 2c0fa701016da996f2a762942a7c42b02148c947 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:12:14 -0700 Subject: [PATCH 621/962] Clear home page adapter pools when reloading (#2272) --- .../java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt index b7a322a84..6df5bbbef 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeViewModel.kt @@ -501,6 +501,9 @@ class HomeViewModel : ViewModel() { return@ioSafe } + HomeChildItemAdapter.sharedPool.clear() + ParentItemAdapter.sharedPool.clear() + val api = getApiFromNameNull(preferredApiName) if (preferredApiName == noneApi.name) { // just set to random From b2e06c5966ec6f40d85291b378d73aa8e2629486 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:13:38 -0700 Subject: [PATCH 622/962] Remove `BuildConfig.BETA` (#2290) It's unused and can be accessed with `BuildConfig.FLAVOR == "prerelease"` --- app/build.gradle.kts | 1 - 1 file changed, 1 deletion(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index e2bdb2079..c386aca89 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -121,7 +121,6 @@ android { create("prerelease") { dimension = "state" resValue("bool", "is_prerelease", "true") - buildConfigField("boolean", "BETA", "true") applicationIdSuffix = ".prerelease" if (signingConfigs.names.contains("prerelease")) { signingConfig = signingConfigs.getByName("prerelease") From cd69597a54290e135972a673c3036a534e474515 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:20:08 -0700 Subject: [PATCH 623/962] Move app version to BuildConfig (#2291) Also, the intent seems to be to be to set the version to `-PRE` when in pre release, which doesn't currently work, but this fixes that. --- app/build.gradle.kts | 11 +++++++++- .../ui/settings/SettingsFragment.kt | 3 ++- .../ui/settings/SettingsUpdates.kt | 22 +++++++++++-------- app/src/main/res/layout/main_settings.xml | 6 ++--- app/src/main/res/xml/settings_updates.xml | 3 +-- 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index c386aca89..3a1bdc5f7 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -65,7 +65,6 @@ android { versionCode = 67 versionName = "4.6.1" - resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "commit_hash", getGitCommitHash()) resValue("bool", "is_prerelease", "false") @@ -79,6 +78,11 @@ android { "BUILD_DATE", "${System.currentTimeMillis()}" ) + buildConfigField( + "String", + "APP_VERSION", + "\"$versionName\"" + ) buildConfigField( "String", "SIMKL_CLIENT_ID", @@ -128,6 +132,11 @@ android { logger.warn("No prerelease signing config!") } versionNameSuffix = "-PRE" + buildConfigField( + "String", + "APP_VERSION", + "\"${defaultConfig.versionName}$versionNameSuffix\"" + ) versionCode = (System.currentTimeMillis() / 60000).toInt() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index c2d5e43e9..755620027 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -246,13 +246,14 @@ class SettingsFragment : BaseFragment( } } - val appVersion = getString(R.string.app_version) + val appVersion = BuildConfig.APP_VERSION val commitInfo = getString(R.string.commit_hash) val buildTimestamp = SimpleDateFormat.getDateTimeInstance(DateFormat.LONG, DateFormat.LONG, Locale.getDefault() ).apply { timeZone = TimeZone.getTimeZone("UTC") }.format(Date(BuildConfig.BUILD_DATE)).replace("UTC", "") + binding.appVersion.text = appVersion binding.buildDate.text = buildTimestamp binding.appVersionInfo.setOnLongClickListener { clipboardHelper(txt(R.string.extension_version), "$appVersion $commitInfo $buildTimestamp") diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 30cd00470..0d34cb988 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -9,6 +9,7 @@ import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager import com.lagradost.cloudstream3.AutoDownloadMode +import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CloudStreamApp import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R @@ -221,18 +222,21 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { return@setOnPreferenceClickListener true } - getPref(R.string.manual_check_update_key)?.setOnPreferenceClickListener { - ioSafe { - if (activity?.runAutoUpdate(false) == false) { - activity?.runOnUiThread { - showToast( - R.string.no_update_found, - Toast.LENGTH_SHORT - ) + getPref(R.string.manual_check_update_key)?.let { pref -> + pref.summary = BuildConfig.APP_VERSION + pref.setOnPreferenceClickListener { + ioSafe { + if (activity?.runAutoUpdate(false) == false) { + activity?.runOnUiThread { + showToast( + R.string.no_update_found, + Toast.LENGTH_SHORT + ) + } } } + return@setOnPreferenceClickListener true } - return@setOnPreferenceClickListener true } getPref(R.string.auto_download_plugins_key)?.setOnPreferenceClickListener { diff --git a/app/src/main/res/layout/main_settings.xml b/app/src/main/res/layout/main_settings.xml index 0b931843d..4a41759e0 100644 --- a/app/src/main/res/layout/main_settings.xml +++ b/app/src/main/res/layout/main_settings.xml @@ -115,12 +115,12 @@ android:orientation="horizontal"> + android:textColor="?attr/textColor" + tools:text="0.0.0" /> + app:key="@string/manual_check_update_key" /> Date: Thu, 4 Dec 2025 17:25:18 -0700 Subject: [PATCH 624/962] Bump material (#2241) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 8e22a64b2..4c507ef2b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -25,7 +25,7 @@ juniversalchardet = "2.5.0" kotlinGradlePlugin = "2.2.21" kotlinxCoroutinesCore = "1.10.2" lifecycleKtx = "2.9.4" -material = "1.14.0-alpha06" +material = "1.14.0-alpha07" media3 = "1.8.0" navigationKtx = "2.9.6" newpipeextractor = "v0.24.8" From 93255dfc22b03db2cbfba23d8a22f3eb5e5bc3fb Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:33:30 -0700 Subject: [PATCH 625/962] Add explicit dependency on fragment (#2233) As with some of my other PRs, explicit dependencies allow for better version control. --- app/build.gradle.kts | 1 + gradle/libs.versions.toml | 2 ++ 2 files changed, 3 insertions(+) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 3a1bdc5f7..d16730d4b 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -182,6 +182,7 @@ dependencies { implementation(libs.core.ktx) implementation(libs.activity.ktx) implementation(libs.appcompat) + implementation(libs.fragment.ktx) implementation(libs.bundles.lifecycle) implementation(libs.bundles.navigation) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 4c507ef2b..ebbc7a2f5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,6 +14,7 @@ coreKtx = "1.17.0" desugar_jdk_libs_nio = "2.1.5" dokkaGradlePlugin = "2.1.0" espressoCore = "3.7.0" +fragmentKtx = "1.8.9" fuzzywuzzy = "1.4.0" jacksonModuleKotlin = { strictly = "2.13.1" } # Later versions don't support minSdk <26 (Crashes on Android TV's and FireSticks) json = "20250517" @@ -67,6 +68,7 @@ databinding = { module = "androidx.databinding:viewbinding", version.ref = "andr desugar_jdk_libs_nio = { module = "com.android.tools:desugar_jdk_libs_nio", version.ref = "desugar_jdk_libs_nio" } espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espressoCore" } ext-junit = { module = "androidx.test.ext:junit", version.ref = "junitVersion" } +fragment-ktx = { module = "androidx.fragment:fragment-ktx", version.ref = "fragmentKtx" } fuzzywuzzy = { module = "me.xdrop:fuzzywuzzy", version.ref = "fuzzywuzzy" } jackson-module-kotlin = { module = "com.fasterxml.jackson.module:jackson-module-kotlin", version.ref = "jacksonModuleKotlin" } json = { module = "org.json:json", version.ref = "json" } From 472d0bab8b7b22a5bdbadb2e0d478781517a56fd Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:48:29 -0700 Subject: [PATCH 626/962] Remove unused swiperefreshlayout dependency (#2296) --- app/build.gradle.kts | 1 - gradle/libs.versions.toml | 2 -- 2 files changed, 3 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index d16730d4b..4e58c7ea0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -190,7 +190,6 @@ dependencies { implementation(libs.preference.ktx) implementation(libs.material) implementation(libs.constraintlayout) - implementation(libs.swiperefreshlayout) // Coil Image Loading implementation(libs.bundles.coil) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ebbc7a2f5..2f8556cf4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -40,7 +40,6 @@ qrcodeKotlin = "4.5.0" rhino = "1.8.0" safefile = "0.0.8" shimmer = "0.5.0" -swiperefreshlayout = "1.1.0" tmdbJava = "2.13.0" torrentserver = "7861970e038b35cd8c6918384e49caf26903e09e" tvprovider = "1.1.0" @@ -105,7 +104,6 @@ quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" } rhino = { module = "org.mozilla:rhino", version.ref = "rhino" } safefile = { module = "com.github.LagradOst:SafeFile", version.ref = "safefile" } shimmer = { module = "com.facebook.shimmer:shimmer", version.ref = "shimmer" } -swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version.ref = "swiperefreshlayout" } tmdb-java = { module = "com.uwetrottmann.tmdb2:tmdb-java", version.ref = "tmdbJava" } torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref = "torrentserver" } tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" } From f2a008922d449052a3a6ec84e93089ab46d694e7 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:51:12 -0700 Subject: [PATCH 627/962] Bump rhino to 1.8.1 (#2295) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2f8556cf4..1015a89c5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ paletteKtx = "1.0.0" preferenceKtx = "1.2.1" previewseekbarMedia3 = "1.1.1.0" qrcodeKotlin = "4.5.0" -rhino = "1.8.0" +rhino = "1.8.1" safefile = "0.0.8" shimmer = "0.5.0" tmdbJava = "2.13.0" From f77df2f3bfc397e0ddac903042243dca973b76a7 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 4 Dec 2025 17:52:24 -0700 Subject: [PATCH 628/962] Use temurin distribution for setup-java action (#2297) Per the note on the README for `actions/setup-java`: "AdoptOpenJDK got moved to Eclipse Temurin and won't be updated anymore. It is highly recommended to migrate workflows from `adopt` and `adopt-openj9`, to `temurin` and `semeru` respectively, to keep receiving software and security updates." --- .github/workflows/build_to_archive.yml | 2 +- .github/workflows/generate_dokka.yml | 2 +- .github/workflows/prerelease.yml | 2 +- .github/workflows/pull_request.yml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_to_archive.yml b/.github/workflows/build_to_archive.yml index ef7acc9df..ce920002e 100644 --- a/.github/workflows/build_to_archive.yml +++ b/.github/workflows/build_to_archive.yml @@ -38,8 +38,8 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v5 with: + distribution: temurin java-version: 17 - distribution: adopt cache: gradle - name: Grant execute permission for gradlew diff --git a/.github/workflows/generate_dokka.yml b/.github/workflows/generate_dokka.yml index e3490730b..e082b79f4 100644 --- a/.github/workflows/generate_dokka.yml +++ b/.github/workflows/generate_dokka.yml @@ -43,8 +43,8 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v5 with: + distribution: temurin java-version: 17 - distribution: adopt cache: gradle - name: Set up Android SDK diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index 164a8458e..cee9538bd 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -29,8 +29,8 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v5 with: + distribution: temurin java-version: 17 - distribution: adopt cache: gradle - name: Grant execute permission for gradlew diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 023647d2a..0eb363682 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -11,8 +11,8 @@ jobs: - name: Set up JDK 17 uses: actions/setup-java@v5 with: + distribution: temurin java-version: 17 - distribution: adopt cache: gradle - name: Grant execute permission for gradlew From fdad31c10ef4470c538cd977c889d43590002130 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 6 Dec 2025 08:29:57 -0700 Subject: [PATCH 629/962] Add backward compatibility for one more AcraApplication method (#2302) `removeKeys()` only seems to be used by one single extension, but I suppose it doesn't hurt to still add back compat for it. --- .../com/lagradost/cloudstream3/AcraApplication.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt index 262f57522..80f084b08 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/AcraApplication.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3 import android.content.Context import com.lagradost.api.setContext import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DataStore.removeKeys import com.lagradost.cloudstream3.utils.DataStore.setKey import java.lang.ref.WeakReference @@ -11,8 +12,7 @@ import java.lang.ref.WeakReference * Use CloudStreamApp instead. */ // Deprecate after next stable -/* -@Deprecated( +/*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp"), level = DeprecationLevel.WARNING @@ -37,6 +37,15 @@ class AcraApplication { setContext(WeakReference(value)) } + /*@Deprecated( + message = "AcraApplication is deprecated, use CloudStreamApp instead", + replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.removeKeys(folder)"), + level = DeprecationLevel.WARNING + )*/ + fun removeKeys(folder: String): Int? { + return context?.removeKeys(folder) + } + /*@Deprecated( message = "AcraApplication is deprecated, use CloudStreamApp instead", replaceWith = ReplaceWith("com.lagradost.cloudstream3.CloudStreamApp.setKey(path, value)"), From 1a852f1f4cdd4377600a1799abbd7ef4a9ac2acd Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 6 Dec 2025 08:35:14 -0700 Subject: [PATCH 630/962] Use SharedPreferences.edit extension function (#2299) --- .../lagradost/cloudstream3/MainActivity.kt | 5 +- .../ui/settings/SettingsGeneral.kt | 35 +++++---- .../ui/settings/SettingsPlayer.kt | 78 +++++++++++-------- .../ui/settings/SettingsProviders.kt | 43 ++++++---- .../cloudstream3/ui/settings/SettingsUI.kt | 55 +++++++------ .../ui/settings/SettingsUpdates.kt | 45 ++++++----- .../ui/setup/SetupFragmentLanguage.kt | 6 +- .../ui/setup/SetupFragmentLayout.kt | 7 +- .../ui/setup/SetupFragmentMedia.kt | 7 +- .../ui/setup/SetupFragmentProviderLanguage.kt | 11 ++- .../ui/subtitles/SubtitlesFragment.kt | 8 +- .../cloudstream3/utils/PowerManagerAPI.kt | 8 +- 12 files changed, 180 insertions(+), 128 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 42d9c1869..53ba51f52 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -32,6 +32,7 @@ import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.content.edit import androidx.core.view.children import androidx.core.view.get import androidx.core.view.isGone @@ -680,7 +681,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa .setNegativeButton(R.string.no) { _, _ -> /*NO-OP*/ } .setPositiveButton(R.string.yes) { _, _ -> if (dontShowAgainCheck.isChecked) { - settingsManager.edit().putInt(getString(R.string.confirm_exit_key), 1).commit() + settingsManager.edit(commit = true) { + putInt(getString(R.string.confirm_exit_key), 1) + } } // finish() causes a bug on some TVs where player // may keep playing after closing the app. diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt index e89865fc4..4c64b175b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt @@ -6,6 +6,7 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.content.edit import androidx.core.os.ConfigurationCompat import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty @@ -156,10 +157,10 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { private val pathPicker = getChooseFolderLauncher { uri, path -> val context = context ?: CloudStreamApp.context ?: return@getChooseFolderLauncher (path ?: uri.toString()).let { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putString(getString(R.string.download_path_key), uri.toString()) - .putString(getString(R.string.download_path_key_visual), it) - .apply() + PreferenceManager.getDefaultSharedPreferences(context).edit { + putString(getString(R.string.download_path_key), uri.toString()) + putString(getString(R.string.download_path_key_visual), it) + } } } @@ -185,7 +186,9 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { try { val langTagIETF = languageTagsIETF[selectedLangIndex] CommonActivity.setLocale(activity, langTagIETF) - settingsManager.edit().putString(getString(R.string.locale_key), langTagIETF).apply() + settingsManager.edit { + putString(getString(R.string.locale_key), langTagIETF) + } activity?.recreate() } catch (e: Exception) { logError(e) @@ -316,7 +319,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { getString(R.string.dns_pref), true, {}) { - settingsManager.edit().putInt(getString(R.string.dns_pref), prefValues[it]).apply() + settingsManager.edit { putInt(getString(R.string.dns_pref), prefValues[it]) } (context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true @@ -341,7 +344,7 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { } ?: emptyList() } - settingsManager.edit().putBoolean(getString(R.string.jsdelivr_proxy_key), getKey(getString(R.string.jsdelivr_proxy_key), false) ?: false).apply() + settingsManager.edit { putBoolean(getString(R.string.jsdelivr_proxy_key), getKey(getString(R.string.jsdelivr_proxy_key), false) ?: false) } getPref(R.string.jsdelivr_proxy_key)?.setOnPreferenceChangeListener { _, newValue -> setKey(getString(R.string.jsdelivr_proxy_key), newValue) return@setOnPreferenceChangeListener true @@ -371,10 +374,10 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { // Sets both visual and actual paths. // key = used path // visual = visual path - settingsManager.edit() - .putString(getString(R.string.download_path_key), dirs[it]) - .putString(getString(R.string.download_path_key_visual), dirs[it]) - .apply() + settingsManager.edit { + putString(getString(R.string.download_path_key), dirs[it]) + putString(getString(R.string.download_path_key_visual), dirs[it]) + } } } return@setOnPreferenceClickListener true @@ -397,10 +400,12 @@ class SettingsGeneral : BasePreferenceFragmentCompat() { if (beneneCount%20 == 0) { activity?.navigate(R.id.action_navigation_settings_general_to_easterEggMonkeFragment) } - settingsManager.edit().putInt( - getString(R.string.benene_count), - beneneCount - ).apply() + settingsManager.edit { + putInt( + getString(R.string.benene_count), + beneneCount + ) + } it.summary = getString(R.string.benene_count_text).format(beneneCount) } catch (e: Exception) { logError(e) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt index 4a1fad907..e301e8cc4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.settings import android.os.Bundle import android.text.format.Formatter.formatShortFileSize import android.view.View +import androidx.core.content.edit import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.VideoClickActionHolder @@ -63,10 +64,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(currentPrefSize), getString(R.string.video_buffer_length_settings), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.video_buffer_length_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.video_buffer_length_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -81,10 +83,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.limit_title), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.prefer_limit_title_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.prefer_limit_title_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -99,10 +102,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.software_decoding), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.software_decoding_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.software_decoding_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -117,10 +121,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.limit_title_rez), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.prefer_limit_title_rez_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.prefer_limit_title_rez_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -144,9 +149,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(currentQuality), getString(R.string.watch_quality_pref), true, - {}) { - settingsManager.edit().putInt(getString(R.string.quality_pref_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.quality_pref_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -168,9 +175,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(currentQuality), getString(R.string.watch_quality_pref_data), true, - {}) { - settingsManager.edit().putInt(getString(R.string.quality_pref_mobile_data_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.quality_pref_mobile_data_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -192,8 +201,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.player_pref), true, - {}) { - settingsManager.edit().putString(getString(R.string.player_default_key), prefValues[it]).apply() + {} + ) { + settingsManager.edit { + putString(getString(R.string.player_default_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -220,10 +232,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(currentPrefSize), getString(R.string.video_buffer_disk_settings), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.video_buffer_disk_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.video_buffer_disk_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } @@ -239,10 +252,11 @@ class SettingsPlayer : BasePreferenceFragmentCompat() { prefValues.indexOf(currentPrefSize), getString(R.string.video_buffer_size_settings), true, - {}) { - settingsManager.edit() - .putInt(getString(R.string.video_buffer_size_key), prefValues[it]) - .apply() + {} + ) { + settingsManager.edit { + putInt(getString(R.string.video_buffer_size_key), prefValues[it]) + } } return@setOnPreferenceClickListener true } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt index 8bc3371ea..076f17a0a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsProviders.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.settings import android.os.Bundle import android.view.View +import androidx.core.content.edit import androidx.navigation.fragment.findNavController import androidx.navigation.NavOptions import androidx.preference.PreferenceManager @@ -46,13 +47,15 @@ class SettingsProviders : BasePreferenceFragmentCompat() { names, currentList, getString(R.string.display_subbed_dubbed_settings), - {}) { selectedList -> + {} + ) { selectedList -> APIRepository.dubStatusActive = selectedList.map { dublist[it] }.toHashSet() - - settingsManager.edit().putStringSet( - this.getString(R.string.display_sub_key), - selectedList.map { names[it] }.toMutableSet() - ).apply() + settingsManager.edit { + putStringSet( + getString(R.string.display_sub_key), + selectedList.map { names[it] }.toMutableSet() + ) + } } } @@ -91,11 +94,14 @@ class SettingsProviders : BasePreferenceFragmentCompat() { names, currentList, getString(R.string.preferred_media_settings), - {}) { selectedList -> - settingsManager.edit().putStringSet( - this.getString(R.string.prefer_media_type_key), - selectedList.map { it.toString() }.toMutableSet() - ).apply() + {} + ) { selectedList -> + settingsManager.edit { + putStringSet( + getString(R.string.prefer_media_type_key), + selectedList.map { it.toString() }.toMutableSet() + ) + } DataStoreHelper.currentHomePage = null //(context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } @@ -119,12 +125,15 @@ class SettingsProviders : BasePreferenceFragmentCompat() { languagesTagName.map { it.second }, currentIndexList, getString(R.string.provider_lang_settings), - {}) { selectedList -> - settingsManager.edit().putStringSet( - this.getString(R.string.provider_lang_key), - selectedList.map { languagesTagName[it].first }.toSet() - ).apply() - //APIRepository.providersActive = it.context.getApiSettings() + {} + ) { selectedList -> + settingsManager.edit { + putStringSet( + getString(R.string.provider_lang_key), + selectedList.map { languagesTagName[it].first }.toSet() + ) + } + // APIRepository.providersActive = it.context.getApiSettings() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt index a991f9297..33add0e95 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUI.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.settings import android.os.Build import android.os.Bundle import android.view.View +import androidx.core.content.edit import androidx.preference.PreferenceManager import androidx.preference.SeekBarPreference import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity @@ -81,12 +82,13 @@ class SettingsUI : BasePreferenceFragmentCompat() { prefNames.toList(), prefValues, getString(R.string.poster_ui_settings), - {}) { list -> - val edit = settingsManager.edit() - for ((i, key) in keys.withIndex()) { - edit.putBoolean(key, list.contains(i)) + {} + ) { list -> + settingsManager.edit { + for ((i, key) in keys.withIndex()) { + putBoolean(key, list.contains(i)) + } } - edit.apply() SearchResultBuilder.updateCache(it.context) } @@ -108,9 +110,9 @@ class SettingsUI : BasePreferenceFragmentCompat() { dismissCallback = {}, callback = { try { - settingsManager.edit() - .putInt(getString(R.string.app_layout_key), prefValues[it]) - .apply() + settingsManager.edit { + putInt(getString(R.string.app_layout_key), prefValues[it]) + } context?.updateTv() activity?.recreate() } catch (e: Exception) { @@ -150,11 +152,12 @@ class SettingsUI : BasePreferenceFragmentCompat() { prefValues.indexOf(currentLayout), getString(R.string.app_theme_settings), true, - {}) { + {} + ) { try { - settingsManager.edit() - .putString(getString(R.string.app_theme_key), prefValues[it]) - .apply() + settingsManager.edit { + putString(getString(R.string.app_theme_key), prefValues[it]) + } activity?.recreate() } catch (e: Exception) { logError(e) @@ -187,11 +190,12 @@ class SettingsUI : BasePreferenceFragmentCompat() { prefValues.indexOf(currentLayout), getString(R.string.primary_color_settings), true, - {}) { + {} + ) { try { - settingsManager.edit() - .putString(getString(R.string.primary_color_key), prefValues[it]) - .apply() + settingsManager.edit { + putString(getString(R.string.primary_color_key), prefValues[it]) + } activity?.recreate() } catch (e: Exception) { logError(e) @@ -213,11 +217,14 @@ class SettingsUI : BasePreferenceFragmentCompat() { names, currentList, getString(R.string.pref_filter_search_quality), - {}) { selectedList -> - settingsManager.edit().putStringSet( - this.getString(R.string.pref_filter_search_quality_key), - selectedList.map { it.toString() }.toMutableSet() - ).apply() + {} + ) { selectedList -> + settingsManager.edit { + putStringSet( + getString(R.string.pref_filter_search_quality_key), + selectedList.map { it.toString() }.toMutableSet() + ) + } } return@setOnPreferenceClickListener true @@ -235,9 +242,9 @@ class SettingsUI : BasePreferenceFragmentCompat() { showApply = true, dismissCallback = {}, callback = { selectedOption -> - settingsManager.edit() - .putInt(getString(R.string.confirm_exit_key), prefValues[selectedOption]) - .apply() + settingsManager.edit { + putInt(getString(R.string.confirm_exit_key), prefValues[selectedOption]) + } } ) return@setOnPreferenceClickListener true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 0d34cb988..6ff072038 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.content.edit import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import androidx.recyclerview.widget.LinearLayoutManager @@ -59,10 +60,10 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { private val pathPicker = getChooseFolderLauncher { uri, path -> val context = context ?: CloudStreamApp.context ?: return@getChooseFolderLauncher (path ?: uri.toString()).let { - PreferenceManager.getDefaultSharedPreferences(context).edit() - .putString(getString(R.string.backup_path_key), uri.toString()) - .putString(getString(R.string.backup_dir_key), it) - .apply() + PreferenceManager.getDefaultSharedPreferences(context).edit { + putString(getString(R.string.backup_path_key), uri.toString()) + putString(getString(R.string.backup_dir_key), it) + } } } @@ -87,9 +88,11 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.backup_frequency), true, - {}) { index -> - settingsManager.edit() - .putInt(getString(R.string.automatic_backup_key), prefValues[index]).apply() + {} + ) { index -> + settingsManager.edit { + putInt(getString(R.string.automatic_backup_key), prefValues[index]) + } BackupWorkManager.enqueuePeriodicWork( context ?: CloudStreamApp.context, prefValues[index].toLong() @@ -118,7 +121,8 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { dirs.indexOf(currentDir), getString(R.string.backup_path_title), true, - {}) { + {} + ) { // Last = custom if (it == dirs.size) { try { @@ -130,10 +134,10 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { // Sets both visual and actual paths. // path = used uri // dir = dir path - settingsManager.edit() - .putString(getString(R.string.backup_path_key), dirs[it]) - .putString(getString(R.string.backup_dir_key), dirs[it]) - .apply() + settingsManager.edit { + putString(getString(R.string.backup_path_key), dirs[it]) + putString(getString(R.string.backup_dir_key), dirs[it]) + } } } return@setOnPreferenceClickListener true @@ -210,11 +214,12 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { prefValues.indexOf(currentInstaller), getString(R.string.apk_installer_settings), true, - {}) { num -> + {} + ) { num -> try { - settingsManager.edit() - .putInt(getString(R.string.apk_installer_key), prefValues[num]) - .apply() + settingsManager.edit { + putInt(getString(R.string.apk_installer_key), prefValues[num]) + } } catch (e: Exception) { logError(e) } @@ -251,9 +256,11 @@ class SettingsUpdates : BasePreferenceFragmentCompat() { prefValues.indexOf(current), getString(R.string.automatic_plugin_download_mode_title), true, - {}) { num -> - settingsManager.edit() - .putInt(getString(R.string.auto_download_plugins_key), prefValues[num]).apply() + {} + ) { num -> + settingsManager.edit { + putInt(getString(R.string.auto_download_plugins_key), prefValues[num]) + } (context ?: CloudStreamApp.context)?.let { ctx -> app.initClient(ctx) } } return@setOnPreferenceClickListener true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt index 5ff85c53b..e96a662c3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt @@ -4,6 +4,7 @@ import android.view.View import android.widget.AbsListView import android.widget.ArrayAdapter import androidx.core.content.ContextCompat +import androidx.core.content.edit import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.BuildConfig @@ -62,8 +63,9 @@ class SetupFragmentLanguage : BaseFragment( listview1.setOnItemClickListener { _, _, selectedLangIndex, _ -> val langTagIETF = languageTagsIETF[selectedLangIndex] CommonActivity.setLocale(activity, langTagIETF) - settingsManager.edit().putString(getString(R.string.locale_key), langTagIETF) - .apply() + settingsManager.edit { + putString(getString(R.string.locale_key), langTagIETF) + } } nextBtt.setOnClickListener { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt index 11cc12066..4a8e784a1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLayout.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.setup import android.view.View import android.widget.AbsListView import android.widget.ArrayAdapter +import androidx.core.content.edit import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.CloudStreamApp.Companion.setKey @@ -44,9 +45,9 @@ class SetupFragmentLayout : BaseFragment( ) listview1.setOnItemClickListener { _, _, position, _ -> - settingsManager.edit() - .putInt(getString(R.string.app_layout_key), prefValues[position]) - .apply() + settingsManager.edit { + putInt(getString(R.string.app_layout_key), prefValues[position]) + } activity?.recreate() } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt index ca5e63cce..8da121daa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentMedia.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.setup import android.view.View import android.widget.AbsListView import android.widget.ArrayAdapter +import androidx.core.content.edit import androidx.core.util.forEach import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager @@ -53,9 +54,9 @@ class SetupFragmentMedia : BaseFragment( val itemVal = TvType.valueOf(item) itemVal.ordinal.toString() }.toSet() - settingsManager.edit() - .putStringSet(getString(R.string.prefer_media_type_key), prefValues) - .apply() + settingsManager.edit { + putStringSet(getString(R.string.prefer_media_type_key), prefValues) + } // Regenerate set homepage DataStoreHelper.currentHomePage = null diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt index 6032af56d..3c4a09ade 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentProviderLanguage.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.setup import android.view.View import android.widget.AbsListView import android.widget.ArrayAdapter +import androidx.core.content.edit import androidx.core.util.forEach import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager @@ -58,10 +59,12 @@ class SetupFragmentProviderLanguage : BaseFragment if (value) selectedLanguages.add(languagesTagName[key].first) } - settingsManager.edit().putStringSet( - ctx.getString(R.string.provider_lang_key), - selectedLanguages.toSet() - ).apply() + settingsManager.edit { + putStringSet( + ctx.getString(R.string.provider_lang_key), + selectedLanguages.toSet() + ) + } } nextBtt.setOnClickListener { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt index 9b0d31212..09fd23abd 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/SubtitlesFragment.kt @@ -18,6 +18,7 @@ import android.widget.Toast import androidx.annotation.FontRes import androidx.annotation.OptIn import androidx.annotation.Px +import androidx.core.content.edit import androidx.core.content.res.ResourcesCompat import androidx.media3.common.text.Cue import androidx.media3.common.util.UnstableApi @@ -636,10 +637,9 @@ class SubtitlesFragment : BaseDialogFragment( subtitlesFilterSubLang.setOnCheckedChangeListener { _, b -> context?.let { ctx -> - PreferenceManager.getDefaultSharedPreferences(ctx) - .edit() - .putBoolean(getString(R.string.filter_sub_lang_key), b) - .apply() + PreferenceManager.getDefaultSharedPreferences(ctx).edit { + putBoolean(getString(R.string.filter_sub_lang_key), b) + } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt index 0d7a8abc4..63f7e1559 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt @@ -9,6 +9,7 @@ import android.os.PowerManager import android.provider.Settings import android.util.Log import androidx.appcompat.app.AlertDialog +import androidx.core.content.edit import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity.showToast @@ -38,7 +39,6 @@ object BatteryOptimizationChecker { fun Context.showBatteryOptimizationDialog() { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - try { AlertDialog.Builder(this) .setTitle(R.string.battery_dialog_title) @@ -46,9 +46,9 @@ object BatteryOptimizationChecker { .setMessage(R.string.battery_dialog_message) .setPositiveButton(R.string.ok) { _, _ -> showRequestIgnoreBatteryOptDialog() } .setNegativeButton(R.string.cancel) { _, _ -> - settingsManager.edit() - .putBoolean(getString(R.string.battery_optimisation_key), false) - .apply() + settingsManager.edit { + putBoolean(getString(R.string.battery_optimisation_key), false) + } } .show() } catch (t: Throwable) { From e25847cb647951ab9e7682d7f717f4fb0bcde681 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sun, 7 Dec 2025 15:24:42 -0700 Subject: [PATCH 631/962] Add API for minimum media duration --- .../cloudstream3/ui/player/CS3IPlayer.kt | 24 ++++++++++++------- .../com/lagradost/cloudstream3/MainAPI.kt | 6 +++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt index b7712cd79..41dc052a7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CS3IPlayer.kt @@ -1572,16 +1572,22 @@ class CS3IPlayer : IPlayer { } Log.i(TAG, "Rendered first frame") hasUsedFirstRender = true - val invalid = exoPlayer?.duration?.let { duration -> - // Only errors short playback when not playing downloaded files - duration < 20_000L && currentDownloadedFile == null - // Concatenated sources (non 1 periodCount) bypasses the invalid check as exoPlayer.duration gives only the current period - // If you can get the total time that'd be better, but this is already niche. - && exoPlayer?.currentTimeline?.periodCount == 1 - && exoPlayer?.isCurrentMediaItemLive != true - } ?: false - if (invalid) { + // Only errors short playback when not playing downloaded files + val tooShort = if (currentDownloadedFile == null) { + val provider = getApiFromNameNull(currentLink?.source) + val minimumDurationMs = provider?.minimumDurationMs + exoPlayer?.duration?.let { duration -> + minimumDurationMs != null && + duration < minimumDurationMs && + // Concatenated sources (non 1 periodCount) bypasses the invalid check as exoPlayer.duration gives only the current period + // If you can get the total time that'd be better, but this is already niche. + exoPlayer?.currentTimeline?.periodCount == 1 && + exoPlayer?.isCurrentMediaItemLive != true + } ?: false + } else false + + if (tooShort) { releasePlayer(saveTime = false) event(ErrorEvent(InvalidFileException("Too short playback"))) return diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index cce42da19..84b010759 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -557,6 +557,12 @@ abstract class MainAPI { * */ open val loadTimeoutMs: Long? = null + /** + * The minimum media duration in milliseconds. If the duration is smaller + * than this value, it will result in to short playback errors. + */ + @Prerelease + open val minimumDurationMs: Long? = null /** * A set of which ids the provider can open with getLoadUrl() From a46b0ac6e678f5fc5b82d0b1081fa6d8610c3d32 Mon Sep 17 00:00:00 2001 From: Osten <11805592+LagradOst@users.noreply.github.com> Date: Mon, 8 Dec 2025 22:35:11 +0100 Subject: [PATCH 632/962] Download selection fix + sub del fix + Del dialog fix (#2308) --- .../ui/download/DownloadChildFragment.kt | 107 +++++++----------- .../ui/download/DownloadFragment.kt | 99 ++++++---------- .../ui/download/DownloadViewModel.kt | 33 +++--- .../cloudstream3/utils/SubtitleUtils.kt | 25 ++-- .../utils/VideoDownloadManager.kt | 2 +- 5 files changed, 108 insertions(+), 158 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt index 08194fd31..d44ea0020 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadChildFragment.kt @@ -1,16 +1,16 @@ package com.lagradost.cloudstream3.ui.download import android.os.Bundle -import android.os.Handler -import android.os.Looper import android.text.format.Formatter.formatShortFileSize import android.view.View +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentChildDownloadsBinding import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.BaseFragment import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.result.FOCUS_SELF @@ -55,22 +55,6 @@ class DownloadChildFragment : BaseFragment( } override fun onBindingCreated(binding: FragmentChildDownloadsBinding) { - /** - * We never want to retain multi-delete state - * when navigating to downloads. Setting this state - * immediately can sometimes result in the observer - * not being notified in time to update the UI. - * - * By posting to the main looper, we ensure that this - * operation is executed after the view has been fully created - * and all initializations are completed, allowing the - * observer to properly receive and handle the state change. - */ - Handler(Looper.getMainLooper()).post { - downloadViewModel.setIsMultiDeleteState(false) - } - - val folder = arguments?.getString("folder") val name = arguments?.getString("name") if (folder == null) { @@ -101,30 +85,56 @@ class DownloadChildFragment : BaseFragment( } (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(cards.value) } + else -> { (binding.downloadChildList.adapter as? DownloadAdapter)?.submitList(null) } } } - observe(downloadViewModel.isMultiDeleteState) { isMultiDeleteState -> - val adapter = binding.downloadChildList.adapter as? DownloadAdapter - adapter?.setIsMultiDeleteState(isMultiDeleteState) - binding.downloadDeleteAppbar.isVisible = isMultiDeleteState - if (!isMultiDeleteState) { - activity?.detachBackPressedCallback("Downloads") - downloadViewModel.clearSelectedItems() - binding.downloadChildToolbar.isVisible = true - } - } + observe(downloadViewModel.selectedBytes) { updateDeleteButton(downloadViewModel.selectedItemIds.value?.count() ?: 0, it) } - observe(downloadViewModel.selectedItemIds) { - handleSelectedChange(it) - updateDeleteButton(it.count(), downloadViewModel.selectedBytes.value ?: 0L) - binding.btnDelete.isVisible = it.isNotEmpty() - binding.selectItemsText.isVisible = it.isEmpty() + + binding.apply { + btnDelete.setOnClickListener { view -> + downloadViewModel.handleMultiDelete(view.context ?: return@setOnClickListener) + } + + btnCancel.setOnClickListener { + downloadViewModel.cancelSelection() + } + + btnToggleAll.setOnClickListener { + val allSelected = downloadViewModel.isAllChildrenSelected() + if (allSelected) { + downloadViewModel.clearSelectedItems() + } else { + downloadViewModel.selectAllChildren() + } + } + } + + observeNullable(downloadViewModel.selectedItemIds) { selection -> + val isMultiDeleteState = selection != null + val adapter = binding.downloadChildList.adapter as? DownloadAdapter + adapter?.setIsMultiDeleteState(isMultiDeleteState) + binding.downloadDeleteAppbar.isVisible = isMultiDeleteState + binding.downloadChildToolbar.isGone = isMultiDeleteState + + if (selection == null) { + activity?.detachBackPressedCallback("Downloads") + return@observeNullable + } + activity?.attachBackPressedCallback("Downloads") { + downloadViewModel.cancelSelection() + } + + updateDeleteButton(selection.count(), downloadViewModel.selectedBytes.value ?: 0L) + + binding.btnDelete.isVisible = selection.isNotEmpty() + binding.selectItemsText.isVisible = selection.isEmpty() val allSelected = downloadViewModel.isAllChildrenSelected() if (allSelected) { @@ -160,37 +170,6 @@ class DownloadChildFragment : BaseFragment( } } - private fun handleSelectedChange(selected: Set) { - if (selected.isNotEmpty()) { - binding?.downloadDeleteAppbar?.isVisible = true - binding?.downloadChildToolbar?.isVisible = false - activity?.attachBackPressedCallback("Downloads") { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnDelete?.setOnClickListener { - context?.let { ctx -> - downloadViewModel.handleMultiDelete(ctx) - } - } - - binding?.btnCancel?.setOnClickListener { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnToggleAll?.setOnClickListener { - val allSelected = downloadViewModel.isAllChildrenSelected() - if (allSelected) { - downloadViewModel.clearSelectedItems() - } else { - downloadViewModel.selectAllChildren() - } - } - - downloadViewModel.setIsMultiDeleteState(true) - } - } - private fun updateDeleteButton(count: Int, selectedBytes: Long) { val formattedSize = formatShortFileSize(context, selectedBytes) binding?.btnDelete?.text = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt index e3d77abac..3bd424640 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadFragment.kt @@ -7,8 +7,6 @@ import android.content.Context import android.content.Intent import android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION import android.os.Build -import android.os.Handler -import android.os.Looper import android.text.format.Formatter.formatShortFileSize import android.view.View import android.widget.LinearLayout @@ -28,6 +26,7 @@ import com.lagradost.cloudstream3.isEpisodeBased import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.BaseFragment import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick import com.lagradost.cloudstream3.ui.player.BasicLink @@ -87,21 +86,6 @@ class DownloadFragment : BaseFragment( binding.downloadAppbar.setAppBarNoScrollFlagsOnTV() binding.downloadDeleteAppbar.setAppBarNoScrollFlagsOnTV() - /** - * We never want to retain multi-delete state - * when navigating to downloads. Setting this state - * immediately can sometimes result in the observer - * not being notified in time to update the UI. - * - * By posting to the main looper, we ensure that this - * operation is executed after the view has been fully created - * and all initializations are completed, allowing the - * observer to properly receive and handle the state change. - */ - Handler(Looper.getMainLooper()).post { - downloadViewModel.setIsMultiDeleteState(false) - } - observe(downloadViewModel.headerCards) { cards -> when (cards) { is Resource.Success -> { @@ -161,26 +145,44 @@ class DownloadFragment : BaseFragment( observe(downloadViewModel.selectedBytes) { updateDeleteButton(downloadViewModel.selectedItemIds.value?.count() ?: 0, it) } - observe(downloadViewModel.isMultiDeleteState) { isMultiDeleteState -> - val adapter = binding.downloadList.adapter as? DownloadAdapter - adapter?.setIsMultiDeleteState(isMultiDeleteState) - binding.downloadDeleteAppbar.isVisible = isMultiDeleteState - if (!isMultiDeleteState) { - activity?.detachBackPressedCallback("Downloads") - downloadViewModel.clearSelectedItems() - // Prevent race condition and make sure - // we don't display it early - if (downloadViewModel.usedBytes.value?.let { it > 0 } == true) { - binding.downloadAppbar.isVisible = true + + binding.apply { + btnDelete.setOnClickListener { view -> + downloadViewModel.handleMultiDelete(view.context ?: return@setOnClickListener) + } + + btnCancel.setOnClickListener { + downloadViewModel.cancelSelection() + } + + btnToggleAll.setOnClickListener { + val allSelected = downloadViewModel.isAllHeadersSelected() + if (allSelected) { + downloadViewModel.clearSelectedItems() + } else { + downloadViewModel.selectAllHeaders() } } } - observe(downloadViewModel.selectedItemIds) { - handleSelectedChange(it) - updateDeleteButton(it.count(), downloadViewModel.selectedBytes.value ?: 0L) - binding.btnDelete.isVisible = it.isNotEmpty() - binding.selectItemsText.isVisible = it.isEmpty() + observeNullable(downloadViewModel.selectedItemIds) { selection -> + val isMultiDeleteState = selection != null + val adapter = binding.downloadList.adapter as? DownloadAdapter + adapter?.setIsMultiDeleteState(isMultiDeleteState) + binding.downloadDeleteAppbar.isVisible = isMultiDeleteState + binding.downloadAppbar.isGone = isMultiDeleteState + + if (selection == null) { + activity?.detachBackPressedCallback("Downloads") + return@observeNullable + } + activity?.attachBackPressedCallback("Downloads") { + downloadViewModel.cancelSelection() + } + updateDeleteButton(selection.count(), downloadViewModel.selectedBytes.value ?: 0L) + + binding.btnDelete.isVisible = selection.isNotEmpty() + binding.selectItemsText.isVisible = selection.isEmpty() val allSelected = downloadViewModel.isAllHeadersSelected() if (allSelected) { @@ -260,37 +262,6 @@ class DownloadFragment : BaseFragment( } } - private fun handleSelectedChange(selected: Set) { - if (selected.isNotEmpty()) { - binding?.downloadDeleteAppbar?.isVisible = true - binding?.downloadAppbar?.isVisible = false - activity?.attachBackPressedCallback("Downloads") { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnDelete?.setOnClickListener { - context?.let { ctx -> - downloadViewModel.handleMultiDelete(ctx) - } - } - - binding?.btnCancel?.setOnClickListener { - downloadViewModel.setIsMultiDeleteState(false) - } - - binding?.btnToggleAll?.setOnClickListener { - val allSelected = downloadViewModel.isAllHeadersSelected() - if (allSelected) { - downloadViewModel.clearSelectedItems() - } else { - downloadViewModel.selectAllHeaders() - } - } - - downloadViewModel.setIsMultiDeleteState(true) - } - } - private fun updateDeleteButton(count: Int, selectedBytes: Long) { val formattedSize = formatShortFileSize(context, selectedBytes) binding?.btnDelete?.text = diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt index bf81e6069..ee69390ff 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/download/DownloadViewModel.kt @@ -29,7 +29,8 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext class DownloadViewModel : ViewModel() { - private val _headerCards = ResourceLiveData>(Resource.Loading()) + private val _headerCards = + ResourceLiveData>(Resource.Loading()) val headerCards: LiveData>> = _headerCards private val _childCards = ResourceLiveData>(Resource.Loading()) @@ -47,22 +48,20 @@ class DownloadViewModel : ViewModel() { private val _selectedBytes = ConsistentLiveData(0) val selectedBytes: LiveData = _selectedBytes - private val _isMultiDeleteState = ConsistentLiveData(false) - val isMultiDeleteState: LiveData = _isMultiDeleteState + private val _selectedItemIds = ConsistentLiveData?>(null) + val selectedItemIds: LiveData?> = _selectedItemIds - private val _selectedItemIds = ConsistentLiveData>(mutableSetOf()) - val selectedItemIds: LiveData> = _selectedItemIds - fun setIsMultiDeleteState(value: Boolean) { - _isMultiDeleteState.postValue(value) + fun cancelSelection() { + updateSelectedItems { null } } fun addSelected(itemId: Int) { - updateSelectedItems { it + itemId } + updateSelectedItems { it?.plus(itemId) ?: setOf(itemId) } } fun removeSelected(itemId: Int) { - updateSelectedItems { it - itemId } + updateSelectedItems { it?.minus(itemId) ?: emptySet() } } fun selectAllHeaders() { @@ -97,8 +96,8 @@ class DownloadViewModel : ViewModel() { return currentSelected.size == headers.size && headers.all { it.data.id in currentSelected } } - private fun updateSelectedItems(action: (Set) -> Set) { - val currentSelected = action(selectedItemIds.value ?: mutableSetOf()) + private fun updateSelectedItems(action: (Set?) -> Set?) { + val currentSelected = action(selectedItemIds.value) _selectedItemIds.postValue(currentSelected) postHeaders() postChildren() @@ -115,7 +114,6 @@ class DownloadViewModel : ViewModel() { fun updateHeaderList(context: Context) = viewModelScope.launchSafe { // Do not push loading as it interrupts the UI //_headerCards.postValue(Resource.Loading()) - clearSelectedItems() val visual = withContext(Dispatchers.IO) { val children = context.getKeys(DOWNLOAD_EPISODE_CACHE) @@ -232,7 +230,6 @@ class DownloadViewModel : ViewModel() { fun updateChildList(context: Context, folder: String) = viewModelScope.launchSafe { _childCards.postValue(Resource.Loading()) // always push loading - clearSelectedItems() val visual = withContext(Dispatchers.IO) { context.getKeys(folder).mapNotNull { key -> @@ -260,6 +257,7 @@ class DownloadViewModel : ViewModel() { } private fun removeItems(idsToRemove: Set) = viewModelScope.launchSafe { + _selectedItemIds.postValue(null) postHeaders(_headerCards.success?.filter { it.data.id !in idsToRemove }) postChildren(_childCards.success?.filter { it.data.id !in idsToRemove }) } @@ -368,16 +366,16 @@ class DownloadViewModel : ViewModel() { .joinToString(separator = "\n") { "• $it" } return when { + data.seriesNames.isNotEmpty() && data.names.isEmpty() -> { + context.getString(R.string.delete_message_series_only).format(formattedSeriesNames) + } + data.ids.count() == 1 -> { context.getString(R.string.delete_message).format( data.names.firstOrNull() ) } - data.seriesNames.isNotEmpty() && data.names.isEmpty() -> { - context.getString(R.string.delete_message_series_only).format(formattedSeriesNames) - } - data.parentName != null && data.names.isNotEmpty() -> { context.getString(R.string.delete_message_series_episodes) .format(data.parentName, formattedNames) @@ -406,7 +404,6 @@ class DownloadViewModel : ViewModel() { when (which) { DialogInterface.BUTTON_POSITIVE -> { viewModelScope.launchSafe { - setIsMultiDeleteState(false) deleteFilesAndUpdateSettings(context, ids, this) { successfulIds -> // We always remove parent because if we are deleting from here // and we have it as non-empty, it was triggered on diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt index 66a6e156c..97be98aea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/SubtitleUtils.kt @@ -2,8 +2,7 @@ package com.lagradost.cloudstream3.utils import android.content.Context import com.lagradost.api.Log -import com.lagradost.cloudstream3.utils.VideoDownloadManager.getFolder -import com.lagradost.safefile.SafeFile +import com.lagradost.cloudstream3.utils.VideoDownloadManager.basePathToFile object SubtitleUtils { @@ -14,16 +13,20 @@ object SubtitleUtils { ) fun deleteMatchingSubtitles(context: Context, info: VideoDownloadManager.DownloadedFileInfo) { - val relative = info.relativePath - val display = info.displayName - val cleanDisplay = cleanDisplayName(display) + val cleanDisplay = cleanDisplayName(info.displayName) - getFolder(context, relative, info.basePath)?.forEach { (name, uri) -> - if (isMatchingSubtitle(name, display, cleanDisplay)) { - val subtitleFile = SafeFile.fromUri(context, uri) - if (subtitleFile == null || subtitleFile.delete() != true) { - Log.e("SubtitleDeletion", "Failed to delete subtitle file: ${subtitleFile?.name()}") - } + val base = basePathToFile(context, info.basePath) + val folder = + base?.gotoDirectory(info.relativePath, createMissingDirectories = false) ?: return + val folderFiles = folder.listFiles() ?: return + + for (file in folderFiles) { + val name = file.name() ?: continue + if (!isMatchingSubtitle(name, info.displayName, cleanDisplay)) { + continue + } + if (file.delete() != true) { + Log.e("SubtitleDeletion", "Failed to delete subtitle file: $name") } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt index 9748bd296..cdda11868 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadManager.kt @@ -1628,7 +1628,7 @@ object VideoDownloadManager { * Turns a string to an UniFile. Used for stored string paths such as settings. * Should only be used to get a download path. * */ - private fun basePathToFile(context: Context, path: String?): SafeFile? { + fun basePathToFile(context: Context, path: String?): SafeFile? { return when { path.isNullOrBlank() -> getDefaultDir(context) path.startsWith("content://") -> SafeFile.fromUri(context, path.toUri()) From 8fabb5c572fd1542145ef7b7af507075d04c09c9 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:17:25 -0700 Subject: [PATCH 633/962] Suppress an UnspecifiedRegisterReceiverFlag lint issue (#2316) Part of my work to fix all error level lint issues, in order to eventually enable `failOnError` and ensure better compatability with older API levels and a more consistent reporting of issues. --- .../cloudstream3/ui/player/AbstractPlayerFragment.kt | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index 413bc5d89..929550dc2 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -221,11 +221,16 @@ abstract class AbstractPlayerFragment( ) } } + val filter = IntentFilter() filter.addAction(ACTION_MEDIA_CONTROL) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { activity?.registerReceiver(pipReceiver, filter, Context.RECEIVER_EXPORTED) - } else activity?.registerReceiver(pipReceiver, filter) + } else { + @SuppressLint("UnspecifiedRegisterReceiverFlag") + activity?.registerReceiver(pipReceiver, filter) + } + val isPlaying = player.getIsPlaying() val isPlayingValue = if (isPlaying) CSPlayerLoading.IsPlaying else CSPlayerLoading.IsPaused From d5eba57bc0a6e026db25de45ade3a29710a6fbf6 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:20:22 -0700 Subject: [PATCH 634/962] Cleanup UnstableApi usage (#2314) * Remove `@UnstableApi` from GeneratorPlayer and use OptIn instead. * Remove `@OptIn` from WebviewFragment as it was unnecessary. * Move `@OptIn` in SaveCaptionStyle to the actual single line we need to OptIn. * Split `setCues` logic to a new method in ChromcastSubtitlesFragment and only add `@OptIn` to that method as it's only necessary there. * Add some missing `@OptIn` annotations to fix all remaining `UnsafeOptInUsageError` lint errors. --- .../com/lagradost/cloudstream3/ui/WebviewFragment.kt | 5 +---- .../cloudstream3/ui/player/AbstractPlayerFragment.kt | 3 +++ .../ui/player/CustomSubtitleDecoderFactory.kt | 8 ++++---- .../cloudstream3/ui/player/FullScreenPlayer.kt | 4 +--- .../cloudstream3/ui/player/GeneratorPlayer.kt | 7 +------ .../ui/subtitles/ChromecastSubtitlesFragment.kt | 10 +++++++++- .../cloudstream3/ui/subtitles/SubtitlesFragment.kt | 5 +++-- 7 files changed, 22 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt index efc9b51c9..0d951bf6a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/WebviewFragment.kt @@ -6,9 +6,7 @@ import android.webkit.JavascriptInterface import android.webkit.WebResourceRequest import android.webkit.WebView import android.webkit.WebViewClient -import androidx.annotation.OptIn 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 @@ -28,7 +26,6 @@ class WebviewFragment : BaseFragment( } binding.webView.webViewClient = object : WebViewClient() { - @OptIn(UnstableApi::class) override fun shouldOverrideUrlLoading( view: WebView?, request: WebResourceRequest? @@ -43,6 +40,7 @@ class WebviewFragment : BaseFragment( return super.shouldOverrideUrlLoading(view, request) } } + binding.webView.apply { WebViewResolver.webViewUserAgent = settings.userAgentString @@ -53,7 +51,6 @@ class WebviewFragment : BaseFragment( loadUrl(url) } - } companion object { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt index 929550dc2..de04e386f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/AbstractPlayerFragment.kt @@ -20,11 +20,13 @@ import android.widget.ImageView import android.widget.ProgressBar import android.widget.Toast import androidx.annotation.LayoutRes +import androidx.annotation.OptIn import androidx.annotation.StringRes 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.AspectRatioFrameLayout @@ -74,6 +76,7 @@ const val NEXT_WATCH_EPISODE_PERCENTAGE = 90 // when the player should sync the progress of "watched", TODO MAKE SETTING const val UPDATE_SYNC_PROGRESS_PERCENTAGE = 80 +@OptIn(UnstableApi::class) abstract class AbstractPlayerFragment( var player: IPlayer = CS3IPlayer() ) : Fragment() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt index 40aff83e1..ffcd83664 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/CustomSubtitleDecoderFactory.kt @@ -35,8 +35,8 @@ 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?) : SubtitleParser { companion object { fun updateForcedEncoding(context: Context) { @@ -392,7 +392,7 @@ class CustomSubtitleDecoderFactory : SubtitleDecoderFactory { /** * Decoders created here persists across reset() * Do not save state in the decoder which you want to reset (e.g subtitle offset) - **/ + */ override fun createDecoder(format: Format): SubtitleDecoder { val parser = CustomDecoder(format) // Allow garbage collection if player releases the decoder @@ -404,8 +404,8 @@ class CustomSubtitleDecoderFactory : SubtitleDecoderFactory { } } -@OptIn(UnstableApi::class) /** We need to convert the newer SubtitleParser to an older SubtitleDecoder */ +@OptIn(UnstableApi::class) class DelegatingSubtitleDecoder(name: String, private val parser: SubtitleParser) : SimpleSubtitleDecoder(name) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 9cc829e95..3821d880a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -103,6 +103,7 @@ const val DOUBLE_TAB_PAUSE_PERCENTAGE = 0.15 // in both directions private const val SUBTITLE_DELAY_BUNDLE_KEY = "subtitle_delay" // All the UI Logic for the player +@OptIn(UnstableApi::class) open class FullScreenPlayer : AbstractPlayerFragment() { private var isVerticalOrientation: Boolean = false protected open var lockRotation = true @@ -274,7 +275,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { private fun animateLayoutChangesForSubtitles() = // Post here as bottomPlayerBar is gone the first frame => bottomPlayerBar.height = 0 playerBinding?.bottomPlayerBar?.post { - @OptIn(UnstableApi::class) val sView = subView ?: return@post val sStyle = CustomDecoder.style val binding = playerBinding ?: return@post @@ -378,7 +378,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } } - @OptIn(UnstableApi::class) override fun subtitlesChanged() { val tracks = player.getVideoTracks() val isBuiltinSubtitles = tracks.currentTextTracks.all { track -> @@ -1526,7 +1525,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { private var loudnessEnhancer: LoudnessEnhancer? = null - @OptIn(UnstableApi::class) private fun handleVolumeAdjustment( delta: Float, fromButton: Boolean, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt index 48353736b..6e362ed80 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/GeneratorPlayer.kt @@ -132,7 +132,7 @@ import kotlinx.coroutines.isActive import kotlinx.coroutines.Job import kotlinx.coroutines.launch -@UnstableApi +@OptIn(UnstableApi::class) class GeneratorPlayer : FullScreenPlayer() { companion object { const val NOTIFICATION_ID = 2326 @@ -266,12 +266,8 @@ class GeneratorPlayer : FullScreenPlayer() { return PendingIntent.getBroadcast(context, instanceId, intent, pendingFlags) } - @OptIn(UnstableApi::class) - @UnstableApi private var cachedPlayerNotificationManager: PlayerNotificationManager? = null - @OptIn(UnstableApi::class) - @UnstableApi private fun getMediaNotification(context: Context): PlayerNotificationManager { val cache = cachedPlayerNotificationManager if (cache != null) return cache @@ -876,7 +872,6 @@ class GeneratorPlayer : FullScreenPlayer() { //dialog.subtitles_search_year?.setText(currentTempMeta.year) } - @OptIn(UnstableApi::class) private fun openSubPicker() { try { subsPathPicker.launch( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt index 4f41b436d..f9b1cb1fe 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/subtitles/ChromecastSubtitlesFragment.kt @@ -10,7 +10,9 @@ import android.util.TypedValue import android.view.View import android.widget.TextView import android.widget.Toast +import androidx.annotation.OptIn import androidx.media3.common.text.Cue +import androidx.media3.common.util.UnstableApi import com.fasterxml.jackson.annotation.JsonProperty import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_DEPRESSED import com.google.android.gms.cast.TextTrackStyle.EDGE_TYPE_DROP_SHADOW @@ -49,7 +51,7 @@ data class SaveChromeCaptionStyle( @JsonProperty("fontScale") var fontScale: Float = 1.05f, @JsonProperty("windowColor") var windowColor: Int = Color.TRANSPARENT, ) -@androidx.annotation.OptIn(androidx.media3.common.util.UnstableApi::class) + class ChromecastSubtitlesFragment : BaseFragment( BaseFragment.BindingCreator.Inflate(ChromecastSubtitleSettingsBinding::inflate) ) { @@ -330,6 +332,12 @@ class ChromecastSubtitlesFragment : BaseFragment( BaseFragment.BindingCreator.Inflate(SubtitleSettingsBinding::inflate) ) { From ae5e25726df720898811ae3047ba339bcd77c3b9 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:31:36 -0700 Subject: [PATCH 635/962] Use String.toUri consistently (#2304) --- .../java/com/lagradost/cloudstream3/MainActivity.kt | 6 +++--- .../cloudstream3/actions/temp/MpvKtPackage.kt | 3 +-- .../lagradost/cloudstream3/actions/temp/MpvPackage.kt | 3 +-- .../cloudstream3/actions/temp/PlayInBrowserAction.kt | 4 ++-- .../cloudstream3/actions/temp/WebVideoCastPackage.kt | 3 +-- .../lagradost/cloudstream3/utils/AppContextUtils.kt | 10 +++++----- .../com/lagradost/cloudstream3/utils/CastHelper.kt | 4 ++-- .../lagradost/cloudstream3/utils/PowerManagerAPI.kt | 4 ++-- .../com/lagradost/cloudstream3/utils/TvChannelUtils.kt | 10 +++++----- 9 files changed, 22 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 53ba51f52..c12b10f36 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -9,7 +9,6 @@ import android.content.SharedPreferences import android.content.res.ColorStateList import android.content.res.Configuration import android.graphics.Rect -import android.net.Uri import android.os.Bundle import android.util.AttributeSet import android.util.Log @@ -33,6 +32,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.cardview.widget.CardView import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.edit +import androidx.core.net.toUri import androidx.core.view.children import androidx.core.view.get import androidx.core.view.isGone @@ -344,7 +344,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa activity?.findViewById(R.id.nav_rail_view)?.selectedItemId = R.id.navigation_search } else if (safeURI(str)?.scheme == APP_STRING_PLAYER) { - val uri = Uri.parse(str) + val uri = str.toUri() val name = uri.getQueryParameter("name") val url = URLDecoder.decode(uri.authority, "UTF-8") @@ -1924,7 +1924,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa fun buildMediaQueueItem(video: String): MediaQueueItem { // val movieMetadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_PHOTO) //movieMetadata.putString(MediaMetadata.KEY_TITLE, "CloudStream") - val mediaInfo = MediaInfo.Builder(Uri.parse(video).toString()) + val mediaInfo = MediaInfo.Builder(video.toUri().toString()) .setStreamType(MediaInfo.STREAM_TYPE_NONE) .setContentType(MimeTypes.IMAGE_JPEG) // .setMetadata(movieMetadata).build() diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt index 102f0ac8b..faae39212 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvKtPackage.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity import android.content.Context import android.content.Intent -import android.net.Uri import androidx.core.net.toUri import com.lagradost.cloudstream3.actions.OpenInAppAction import com.lagradost.cloudstream3.actions.updateDurationAndPosition @@ -45,7 +44,7 @@ open class MpvKtPackage( intent.apply { putExtra("subs", result.subs.map { it.url.toUri() }.toTypedArray()) - setDataAndType(Uri.parse(link.url), "video/*") + setDataAndType(link.url.toUri(), "video/*") // m3u8 plays, but changing sources feature is not available // makeTempM3U8Intent(activity, this, result) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt index 68e619c92..95d05aa3a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity import android.content.Context import android.content.Intent -import android.net.Uri import androidx.core.net.toUri import com.lagradost.api.Log import com.lagradost.cloudstream3.actions.OpenInAppAction @@ -44,7 +43,7 @@ open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv putExtra("title", video.name) if (index != null) { - setDataAndType(Uri.parse(result.links.getOrNull(index)?.url ?: return), "video/*") + setDataAndType((result.links.getOrNull(index)?.url ?: return).toUri(), "video/*") } else { makeTempM3U8Intent(context, this, result) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt index 7c1b68c05..bfd2926bf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/PlayInBrowserAction.kt @@ -2,7 +2,7 @@ package com.lagradost.cloudstream3.actions.temp import android.content.Context import android.content.Intent -import android.net.Uri +import androidx.core.net.toUri import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.actions.VideoClickAction import com.lagradost.cloudstream3.ui.result.LinkLoadingResult @@ -33,7 +33,7 @@ class PlayInBrowserAction: VideoClickAction() { ) { val link = result.links.getOrNull(index ?: 0) ?: return val i = Intent(Intent.ACTION_VIEW) - i.data = Uri.parse(link.url) + i.data = link.url.toUri() launch(i) } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt index 9f7eee7b8..963221bb3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/WebVideoCastPackage.kt @@ -3,7 +3,6 @@ package com.lagradost.cloudstream3.actions.temp import android.app.Activity import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import androidx.core.net.toUri import com.lagradost.cloudstream3.USER_AGENT @@ -38,7 +37,7 @@ class WebVideoCastPackage: OpenInAppAction( val link = result.links[index ?: 0] intent.apply { - setDataAndType(Uri.parse(link.url), "video/*") + setDataAndType(link.url.toUri(), "video/*") val title = video.name ?: video.headerName diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt index 8334833e4..0376b6835 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt @@ -18,7 +18,6 @@ import android.media.tv.TvContract.Channels.COLUMN_INTERNAL_PROVIDER_ID import android.net.ConnectivityManager import android.net.Network import android.net.NetworkCapabilities -import android.net.Uri import android.os.Build import android.os.Handler import android.os.Looper @@ -33,6 +32,7 @@ import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.RequiresApi import androidx.annotation.WorkerThread import androidx.appcompat.app.AlertDialog +import androidx.core.net.toUri import androidx.core.text.HtmlCompat import androidx.core.text.toSpanned import androidx.core.widget.ContentLoadingProgressBar @@ -170,10 +170,10 @@ object AppContextUtils { ) .setWatchNextType(TvContractCompat.WatchNextPrograms.WATCH_NEXT_TYPE_CONTINUE) .setTitle(title) - .setPosterArtUri(Uri.parse(card.posterUrl)) - .setIntentUri(Uri.parse(card.id?.let { + .setPosterArtUri(card.posterUrl?.toUri()) + .setIntentUri((card.id?.let { "$APP_STRING_RESUME_WATCHING://$it" - } ?: card.url)) + } ?: card.url).toUri()) .setInternalProviderId(card.url) .setLastEngagementTimeUtcMillis( resumeWatching?.updateTime ?: System.currentTimeMillis() @@ -603,7 +603,7 @@ object AppContextUtils { ) = (this.getActivity() ?: activity)?.runOnUiThread { try { val intent = Intent(Intent.ACTION_VIEW) - intent.data = Uri.parse(url) + intent.data = url.toUri() intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) // activityResultRegistry is used to fall back to webview if a browser is missing diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt index d83731658..b48c8d40a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/CastHelper.kt @@ -1,6 +1,6 @@ package com.lagradost.cloudstream3.utils -import android.net.Uri +import androidx.core.net.toUri import androidx.media3.common.MimeTypes import com.google.android.gms.cast.* import com.google.android.gms.cast.framework.CastSession @@ -41,7 +41,7 @@ object CastHelper { val srcPoster = epData.poster ?: holder.poster if (srcPoster != null) { - movieMetadata.addImage(WebImage(Uri.parse(srcPoster))) + movieMetadata.addImage(WebImage(srcPoster.toUri())) } var subIndex = 0 diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt index 63f7e1559..e3c7d68df 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/PowerManagerAPI.kt @@ -3,13 +3,13 @@ package com.lagradost.cloudstream3.utils import android.content.ActivityNotFoundException import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Build.VERSION.SDK_INT import android.os.PowerManager import android.provider.Settings import android.util.Log import androidx.appcompat.app.AlertDialog import androidx.core.content.edit +import androidx.core.net.toUri import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity.showToast @@ -67,7 +67,7 @@ object BatteryOptimizationChecker { try { val intent = Intent().apply { action = Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS - data = Uri.parse("package:$PACKAGE_NAME") + data = "package:$PACKAGE_NAME".toUri() } startActivity(intent) } catch (t: Throwable) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt index 17568c8d2..798cb9d07 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt @@ -4,8 +4,8 @@ import android.content.ComponentName import android.content.ContentUris import android.content.Context import android.content.Intent -import android.net.Uri import android.util.Log +import androidx.core.net.toUri import androidx.tvprovider.media.tv.Channel import androidx.tvprovider.media.tv.PreviewProgram import androidx.tvprovider.media.tv.TvContractCompat @@ -84,12 +84,12 @@ object TvChannelUtils { } .setContentId(item.url) .setType(TvContractCompat.PreviewPrograms.TYPE_MOVIE) - .setIntentUri(Uri.parse(csshareUri)) + .setIntentUri(csshareUri.toUri()) .setPosterArtAspectRatio(TvContractCompat.PreviewPrograms.ASPECT_RATIO_2_3) // Validate poster URL before setting if (!poster.isNullOrBlank() && poster.startsWith("http")) { - builder.setPosterArtUri(Uri.parse(poster)) + builder.setPosterArtUri(poster.toUri()) } val program = builder.build() @@ -135,14 +135,14 @@ object TvChannelUtils { fun createTvChannel(context: Context) { val componentName = ComponentName(context, MainActivity::class.java) - val iconUri = Uri.parse("android.resource://${context.packageName}/mipmap/ic_launcher") + val iconUri = "android.resource://${context.packageName}/mipmap/ic_launcher".toUri() val inputId = TvContractCompat.buildInputId(componentName) val channel = Channel.Builder() .setType(TvContractCompat.Channels.TYPE_PREVIEW) .setAppLinkIconUri(iconUri) .setDisplayName(context.getString(R.string.app_name)) .setAppLinkIntent(Intent(Intent.ACTION_VIEW).apply { - data = Uri.parse("cloudstreamapp://open") + data = "cloudstreamapp://open".toUri() }) .setInputId(inputId) .build() From e0231520d587ec5ab8e7fb9de2928a997e3dcb39 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Thu, 11 Dec 2025 22:07:19 +0530 Subject: [PATCH 636/962] added mpvex (#2309) --- .../com/lagradost/cloudstream3/actions/VideoClickAction.kt | 2 ++ .../com/lagradost/cloudstream3/actions/temp/MpvPackage.kt | 7 +++++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt index d4f35f081..4843b7617 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/VideoClickAction.kt @@ -16,6 +16,7 @@ import com.lagradost.cloudstream3.actions.temp.BiglyBTPackage import com.lagradost.cloudstream3.actions.temp.CopyClipboardAction import com.lagradost.cloudstream3.actions.temp.JustPlayerPackage import com.lagradost.cloudstream3.actions.temp.LibreTorrentPackage +import com.lagradost.cloudstream3.actions.temp.MpvExPackage import com.lagradost.cloudstream3.actions.temp.MpvKtPackage import com.lagradost.cloudstream3.actions.temp.MpvKtPreviewPackage import com.lagradost.cloudstream3.actions.temp.MpvPackage @@ -51,6 +52,7 @@ object VideoClickActionHolder { // main support external apps VlcPackage(), MpvPackage(), + MpvExPackage(), NextPlayerPackage(), JustPlayerPackage(), FcastAction(), diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt index 95d05aa3a..cd49eb994 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/MpvPackage.kt @@ -17,6 +17,9 @@ import com.lagradost.cloudstream3.utils.ExtractorLinkType // https://github.com/mpv-android/mpv-android/blob/0eb3cdc6f1632636b9c30d52ec50e4b017661980/app/src/main/java/is/xyz/mpv/MPVActivity.kt#L904 // https://mpv-android.github.io/mpv-android/intent.html +//https://github.com/marlboro-advance/mpvEx +class MpvExPackage: MpvPackage("mpvEx","app.marlboroadvance.mpvex","app.marlboroadvance.mpvex.ui.player.PlayerActivity") + class MpvYTDLPackage : MpvPackage("MPV YTDL", "is.xyz.mpv.ytdl") { override val sourceTypes = setOf( ExtractorLinkType.VIDEO, @@ -25,10 +28,10 @@ class MpvYTDLPackage : MpvPackage("MPV YTDL", "is.xyz.mpv.ytdl") { ) } -open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv"): OpenInAppAction( +open class MpvPackage(appName: String = "MPV", packageName: String = "is.xyz.mpv",intentClass:String = "is.xyz.mpv.MPVActivity"): OpenInAppAction( txt(appName), packageName, - "is.xyz.mpv.MPVActivity" + intentClass ) { override val oneSource = true // mpv has poor playlist support on TV override suspend fun putExtra( From 9a9e71354c0068d4d3be5a7d2fd359ffebdf6edb Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:55:00 -0700 Subject: [PATCH 637/962] Remove check for SearchAutoComplete (#2313) --- .../java/com/lagradost/cloudstream3/CommonActivity.kt | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index e4e7c69f4..58d65b516 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -51,7 +51,7 @@ import com.lagradost.cloudstream3.ui.settings.extensions.PluginAdapter import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Event -import com.lagradost.cloudstream3.utils.UIHelper +import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod import com.lagradost.cloudstream3.utils.UIHelper.toPx import com.lagradost.cloudstream3.utils.UiText import java.lang.ref.WeakReference @@ -648,6 +648,7 @@ object CommonActivity { else -> null } + // println("NEXT FOCUS : $nextView") if (nextView != null) { nextView.requestFocus() @@ -655,10 +656,8 @@ object CommonActivity { return true } - if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && - (act.currentFocus is SearchView || act.currentFocus is SearchView.SearchAutoComplete) - ) { - UIHelper.showInputMethod(act.currentFocus?.findFocus()) + if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && currentFocus is SearchView) { + showInputMethod(currentFocus.findFocus()) } //println("Keycode: $keyCode") @@ -667,7 +666,6 @@ object CommonActivity { // "Got Keycode $keyCode | ${KeyEvent.keyCodeToString(keyCode)} \n ${event?.action}", // Toast.LENGTH_LONG //) - } // if someone else want to override the focus then don't handle the event as it is already From 7ded6a4fa1c16a7234a96aff81996c9a3994b459 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 09:56:34 -0700 Subject: [PATCH 638/962] Add tools:targetApi to appease lint (#2315) Part of my work to fix all error level lint issues, in order to eventually enable `failOnError` and ensure better compatability with older API levels and a more consistent reporting of issues. --- .../main/res/layout/settings_title_top.xml | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/layout/settings_title_top.xml b/app/src/main/res/layout/settings_title_top.xml index 1e3671a6f..646371405 100644 --- a/app/src/main/res/layout/settings_title_top.xml +++ b/app/src/main/res/layout/settings_title_top.xml @@ -1,22 +1,24 @@ + xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:id="@+id/settings_top_root" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/primaryGrayBackground" + android:orientation="vertical"> + android:id="@android:id/list_container" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:background="?attr/primaryBlackBackground" + app:layout_behavior="@string/appbar_scrolling_view_behavior" + tools:targetApi="n" /> + \ No newline at end of file From 350d19bd6be3c0b1084ab6c28869ef2889ba4411 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 10:27:54 -0700 Subject: [PATCH 639/962] Some minor miscellaneous cleanup (#2306) * Some minor miscellaneous cleanup * Remove classes --- .../lagradost/cloudstream3/CommonActivity.kt | 6 +-- .../HeaderDecorationBindingAdapter.kt | 11 ----- .../cloudstream3/ui/HeaderViewDecoration.kt | 42 ------------------- 3 files changed, 3 insertions(+), 56 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 58d65b516..2a994b8b7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -24,6 +24,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.SearchView import androidx.core.content.ContextCompat import androidx.core.view.children +import androidx.core.view.isNotEmpty import androidx.preference.PreferenceManager import com.google.android.gms.cast.framework.CastSession import com.google.android.material.chip.ChipGroup @@ -421,8 +422,7 @@ object CommonActivity { private fun View.hasContent(): Boolean { return isShown && when (this) { - //is RecyclerView -> this.childCount > 0 - is ViewGroup -> this.childCount > 0 + is ViewGroup -> this.isNotEmpty() else -> true } } @@ -452,7 +452,7 @@ object CommonActivity { // if cant focus but visible then break and let android decide // the exception if is the view is a parent and has children that wants focus val hasChildrenThatWantsFocus = (next as? ViewGroup)?.let { parent -> - parent.descendantFocusability == ViewGroup.FOCUS_AFTER_DESCENDANTS && parent.childCount > 0 + parent.descendantFocusability == ViewGroup.FOCUS_AFTER_DESCENDANTS && parent.isNotEmpty() } ?: false if (!next.isFocusable && shown && !hasChildrenThatWantsFocus) return null diff --git a/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt deleted file mode 100644 index 045a7963a..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/HeaderDecorationBindingAdapter.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.lagradost.cloudstream3 - -import android.view.LayoutInflater -import androidx.annotation.LayoutRes -import androidx.recyclerview.widget.RecyclerView -import com.lagradost.cloudstream3.ui.HeaderViewDecoration - -fun setHeaderDecoration(view: RecyclerView, @LayoutRes headerViewRes: Int) { - val headerView = LayoutInflater.from(view.context).inflate(headerViewRes, null) - view.addItemDecoration(HeaderViewDecoration(headerView)) -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt deleted file mode 100644 index 40c03012a..000000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/HeaderViewDecoration.kt +++ /dev/null @@ -1,42 +0,0 @@ -package com.lagradost.cloudstream3.ui - -import android.graphics.Canvas -import android.graphics.Rect -import android.view.View -import androidx.recyclerview.widget.RecyclerView - -class HeaderViewDecoration(private val customView: View) : RecyclerView.ItemDecoration() { - override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) { - super.onDraw(c, parent, state) - customView.layout(parent.left, 0, parent.right, customView.measuredHeight) - for (i in 0 until parent.childCount) { - val view = parent.getChildAt(i) - if (parent.getChildAdapterPosition(view) == 0) { - c.save() - val height = customView.measuredHeight - val top = view.top - height - c.translate(0f, top.toFloat()) - customView.draw(c) - c.restore() - break - } - } - } - - override fun getItemOffsets( - outRect: Rect, - view: View, - parent: RecyclerView, - state: RecyclerView.State - ) { - if (parent.getChildAdapterPosition(view) == 0) { - customView.measure( - View.MeasureSpec.makeMeasureSpec(parent.measuredWidth, View.MeasureSpec.AT_MOST), - View.MeasureSpec.makeMeasureSpec(parent.measuredHeight, View.MeasureSpec.AT_MOST) - ) - outRect.set(0, customView.measuredHeight, 0, 0) - } else { - outRect.setEmpty() - } - } -} \ No newline at end of file From 74ceaf9a3ffeee73a1831d983050da527749f2c6 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:27:36 -0700 Subject: [PATCH 640/962] Add a lint suppression for RestrictedApi (#2312) --- .../java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt index 798cb9d07..feecbe312 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/TvChannelUtils.kt @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.utils +import android.annotation.SuppressLint import android.content.ComponentName import android.content.ContentUris import android.content.Context @@ -66,6 +67,7 @@ object TvChannelUtils { } /** Insert programs into a channel */ + @SuppressLint("RestrictedApi") fun addPrograms(context: Context, channelId: Long, items: List) { for (item in items) { try { From a836b268495c1c988455eede70203594be09a70a Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:32:32 -0700 Subject: [PATCH 641/962] Cleanup InAppUpdater (#2298) The only functional change here is that the commit in the updater dialog was normalized to what it is everywhere else, meaning it is 7 not 10 characters now. I also have another patch prepared to convert this entire class to an actual object rather than just a class with only a companion object but since that touches every single line due to indentation changes, I decided to split it in order to make it easier to review. --- .../cloudstream3/utils/InAppUpdater.kt | 237 ++++++++---------- 1 file changed, 104 insertions(+), 133 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 12befafe0..3ad6eb0f7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -4,32 +4,34 @@ import android.app.Activity import android.content.Context import android.content.Intent import android.net.Uri +import android.text.TextUtils import android.util.Log import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.content.ContextCompat import androidx.core.content.FileProvider +import androidx.core.content.edit import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty -import com.lagradost.cloudstream3.* +import com.lagradost.cloudstream3.BuildConfig import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.services.PackageInstallerService +import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import java.io.BufferedReader +import java.io.File +import java.io.InputStreamReader +import java.io.IOException import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import okio.BufferedSink import okio.buffer import okio.sink -import java.io.File -import android.text.TextUtils -import com.lagradost.cloudstream3.MainActivity.Companion.deleteFileOnExit -import com.lagradost.cloudstream3.services.PackageInstallerService -import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus -import java.io.BufferedReader -import java.io.IOException -import java.io.InputStreamReader - class InAppUpdater { companion object { @@ -38,53 +40,51 @@ class InAppUpdater { private const val LOG_TAG = "InAppUpdater" - // === IN APP UPDATER === - data class GithubAsset( + private data class GithubAsset( @JsonProperty("name") val name: String, - @JsonProperty("size") val size: Int, // Size bytes - @JsonProperty("browser_download_url") val browserDownloadUrl: String, // download link + @JsonProperty("size") val size: Int, // Size in bytes + @JsonProperty("browser_download_url") val browserDownloadUrl: String, @JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive ) - data class GithubRelease( + private data class GithubRelease( @JsonProperty("tag_name") val tagName: String, // Version code - @JsonProperty("body") val body: String, // Desc + @JsonProperty("body") val body: String, // Description @JsonProperty("assets") val assets: List, - @JsonProperty("target_commitish") val targetCommitish: String, // branch + @JsonProperty("target_commitish") val targetCommitish: String, // Branch @JsonProperty("prerelease") val prerelease: Boolean, - @JsonProperty("node_id") val nodeId: String //Node Id + @JsonProperty("node_id") val nodeId: String, ) - data class GithubObject( - @JsonProperty("sha") val sha: String, // sha 256 hash - @JsonProperty("type") val type: String, // object type + private data class GithubObject( + @JsonProperty("sha") val sha: String, // SHA-256 hash + @JsonProperty("type") val type: String, @JsonProperty("url") val url: String, ) - data class GithubTag( + private data class GithubTag( @JsonProperty("object") val githubObject: GithubObject, ) - data class Update( + private data class Update( @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, @JsonProperty("updateURL") val updateURL: String?, @JsonProperty("updateVersion") val updateVersion: String?, @JsonProperty("changelog") val changelog: String?, - @JsonProperty("updateNodeId") val updateNodeId: String? + @JsonProperty("updateNodeId") val updateNodeId: String?, ) private suspend fun Activity.getAppUpdate(): Update { return try { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if (settingsManager.getBoolean( + if ( + settingsManager.getBoolean( getString(R.string.prerelease_update_key), resources.getBoolean(R.bool.is_prerelease) ) ) { getPreReleaseUpdate() - } else { - getReleaseUpdate() - } + } else getReleaseUpdate() } catch (e: Exception) { Log.e(LOG_TAG, Log.getStackTraceString(e)) Update(false, null, null, null, null) @@ -94,56 +94,44 @@ class InAppUpdater { private suspend fun Activity.getReleaseUpdate(): Update { val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = - parseJson>( - app.get( - url, - headers = headers - ).text - ) + val response = parseJson>( + app.get(url, headers = headers).text + ) val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") - /* - val releases = response.map { it.assets }.flatten() - .filter { it.content_type == "application/vnd.android.package-archive" } - val found = - releases.sortedWith(compareBy { - versionRegex.find(it.name)?.groupValues?.get(2) - }).toList().lastOrNull()*/ - val foundList = - response.filter { rel -> - !rel.prerelease - }.sortedWith(compareBy { release -> - release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> - versionRegex.find( - it1 - )?.groupValues?.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - } - }).toList() + val foundList = response.filter { rel -> + !rel.prerelease + }.sortedWith(compareBy { release -> + release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> + versionRegex.find( + it1 + )?.groupValues?.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + } + }).toList() + val found = foundList.lastOrNull() val foundAsset = found?.assets?.getOrNull(0) val currentVersion = packageName?.let { - packageManager.getPackageInfo( - it, - 0 - ) + packageManager.getPackageInfo(it, 0) } foundAsset?.name?.let { assetName -> val foundVersion = versionRegex.find(assetName) val shouldUpdate = - 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() - } - }?.compareTo( - foundVersion.groupValues.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - )!! < 0 else false + 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() + } + }?.compareTo( + foundVersion.groupValues.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + )!! < 0 + } else false return if (foundVersion != null) { Update( shouldUpdate, @@ -152,57 +140,43 @@ class InAppUpdater { found.body, found.nodeId ) - } else { - Update(false, null, null, null, null) - } + } else Update(false, null, null, null, null) } + return Update(false, null, null, null, null) } private suspend fun Activity.getPreReleaseUpdate(): Update { - val tagUrl = - "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" + val tagUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = - parseJson>(app.get(releaseUrl, headers = headers).text) + val response = parseJson>( + app.get(releaseUrl, headers = headers).text + ) + + val found = response.lastOrNull { rel -> + rel.prerelease || rel.tagName == "pre-release" + } - val found = - response.lastOrNull { rel -> - rel.prerelease || rel.tagName == "pre-release" - } val foundAsset = found?.assets?.filter { it -> it.contentType == "application/vnd.android.package-archive" }?.getOrNull(0) - val tagResponse = - parseJson(app.get(tagUrl, headers = headers).text) - - 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.githubObject.sha - .trim { c -> c.isWhitespace() } - .take(7)) - return if (foundAsset != null) { + val tagResponse = parseJson(app.get(tagUrl, headers = headers).text) + val updateCommitHash = tagResponse.githubObject.sha.trim().take(7) + Log.d(LOG_TAG, "Fetched GitHub tag: $updateCommitHash") + Update( - shouldUpdate, + getString(R.string.commit_hash) != updateCommitHash, foundAsset.browserDownloadUrl, - tagResponse.githubObject.sha.take(10), + updateCommitHash, found.body, found.nodeId ) - } else { - Update(false, null, null, null, null) - } + } else Update(false, null, null, null, null) } - private val updateLock = Mutex() private suspend fun Activity.downloadUpdate(url: String): Boolean { @@ -214,9 +188,7 @@ class InAppUpdater { // Delete all old updates this.cacheDir.listFiles()?.filter { it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix - }?.forEach { - deleteFileOnExit(it) - } + }?.forEach { deleteFileOnExit(it) } val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix") val sink: BufferedSink = downloadedFile.sink().buffer() @@ -226,8 +198,10 @@ class InAppUpdater { sink.close() openApk(this, Uri.fromFile(downloadedFile)) } + return true } catch (e: Exception) { + logError(e) return false } } @@ -255,23 +229,20 @@ class InAppUpdater { /** * @param checkAutoUpdate if the update check was launched automatically - **/ + */ suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean { val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if (!checkAutoUpdate || settingsManager.getBoolean( getString(R.string.auto_update_key), true ) ) { val update = getAppUpdate() - if ( - update.shouldUpdate && - update.updateURL != null) { - + if (update.shouldUpdate && update.updateURL != null) { // Check if update should be skipped - val updateNodeId = - settingsManager.getString(getString(R.string.skip_update_key), "") + val updateNodeId = settingsManager.getString( + getString(R.string.skip_update_key), "" + ) // Skips the update if its an automatic update and the update is skipped // This allows updating manually @@ -282,10 +253,7 @@ class InAppUpdater { runOnUiThread { try { val currentVersion = packageName?.let { - packageManager.getPackageInfo( - it, - 0 - ) + packageManager.getPackageInfo(it, 0) } val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) @@ -302,8 +270,6 @@ class InAppUpdater { } // Sanitized because it looks cluttered builder.setMessage(sanitizedChangelog) - - val context = this builder.apply { setPositiveButton(R.string.update) { _, _ -> // Forcefully start any delayed installations @@ -312,42 +278,45 @@ class InAppUpdater { showToast(R.string.download_started, Toast.LENGTH_LONG) // Check if the setting hasn't been changed - if (settingsManager.getInt( + if ( + settingsManager.getInt( getString(R.string.apk_installer_key), -1 ) == -1 ) { - if (isMiUi()) // Set to legacy if using miui - settingsManager.edit() - .putInt(getString(R.string.apk_installer_key), 1) - .apply() + // Set to legacy installer if using MIUI + if (isMiUi()) { + settingsManager.edit { + putInt(getString(R.string.apk_installer_key), 1) + } + } } - val currentInstaller = - settingsManager.getInt( - getString(R.string.apk_installer_key), - 0 - ) + val currentInstaller = settingsManager.getInt( + getString(R.string.apk_installer_key), + 0 + ) when (currentInstaller) { // New method 0 -> { val intent = PackageInstallerService.Companion.getIntent( - context, + this@runAutoUpdate, update.updateURL ) - ContextCompat.startForegroundService(context, intent) + ContextCompat.startForegroundService(this@runAutoUpdate, intent) } // Legacy 1 -> { ioSafe { - if (!downloadUpdate(update.updateURL)) + if (!downloadUpdate(update.updateURL)) { runOnUiThread { showToast( R.string.download_failed, Toast.LENGTH_LONG ) } + } } } } @@ -357,10 +326,12 @@ class InAppUpdater { if (checkAutoUpdate) { setNeutralButton(R.string.skip_update) { _, _ -> - settingsManager.edit().putString( - getString(R.string.skip_update_key), - update.updateNodeId ?: "" - ).apply() + settingsManager.edit { + putString( + getString(R.string.skip_update_key), + update.updateNodeId ?: "" + ) + } } } } @@ -386,7 +357,7 @@ class InAppUpdater { BufferedReader(InputStreamReader(p.inputStream), 1024).use { it.readLine() } - } catch (ex: IOException) { + } catch (_: IOException) { null } } From 70121f45482fbd6cd6551930681a32a7c4151296 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 12:41:51 -0700 Subject: [PATCH 642/962] Fix crash on Android 5 (#2320) I just realized I hadn't done a PR to fix this issue yet but this issue is why I've been working on fixing all error level lint issues so that we can enable `failOnError` which would have prevented this. --- .../lagradost/cloudstream3/ui/player/FullScreenPlayer.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt index 3821d880a..4be6c250c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt @@ -1875,7 +1875,6 @@ open class FullScreenPlayer : AbstractPlayerFragment() { } playerBinding?.apply { - if (isLayout(TV or EMULATOR)) { mapOf( playerGoBack to playerGoBackText, @@ -1990,8 +1989,10 @@ open class FullScreenPlayer : AbstractPlayerFragment() { return@setOnTouchListener handleMotionEvent(callView, event) } - playerControlsScroll.setOnScrollChangeListener { _, _, _, _, _ -> - autoHide() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + playerControlsScroll.setOnScrollChangeListener { _, _, _, _, _ -> + autoHide() + } } exoProgress.setOnTouchListener { _, event -> From 5d2e432614a4841702ce66390e4a1f02b86b5796 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:03:37 -0700 Subject: [PATCH 643/962] Make InAppUpdater an object (#2321) Better than a class with only a companion object I think. --- .../lagradost/cloudstream3/MainActivity.kt | 2 +- .../ui/settings/SettingsUpdates.kt | 2 +- .../cloudstream3/utils/InAppUpdater.kt | 532 +++++++++--------- 3 files changed, 267 insertions(+), 269 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index c12b10f36..f6b722fea 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -159,7 +159,7 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.ImageLoader.loadImage -import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate +import com.lagradost.cloudstream3.utils.InAppUpdater.runAutoUpdate import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SnackbarHelper.showSnackbar import com.lagradost.cloudstream3.utils.UIHelper.changeStatusBarState diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt index 6ff072038..e2805c402 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsUpdates.kt @@ -33,7 +33,7 @@ import com.lagradost.cloudstream3.ui.settings.utils.getChooseFolderLauncher import com.lagradost.cloudstream3.utils.BackupUtils import com.lagradost.cloudstream3.utils.BackupUtils.restorePrompt import com.lagradost.cloudstream3.utils.Coroutines.ioSafe -import com.lagradost.cloudstream3.utils.InAppUpdater.Companion.runAutoUpdate +import com.lagradost.cloudstream3.utils.InAppUpdater.runAutoUpdate import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog import com.lagradost.cloudstream3.utils.UIHelper.clipboardHelper diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt index 3ad6eb0f7..45891a5d0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/InAppUpdater.kt @@ -33,333 +33,331 @@ import okio.BufferedSink import okio.buffer import okio.sink -class InAppUpdater { - companion object { - private const val GITHUB_USER_NAME = "recloudstream" - private const val GITHUB_REPO = "cloudstream" +object InAppUpdater { + private const val GITHUB_USER_NAME = "recloudstream" + private const val GITHUB_REPO = "cloudstream" - private const val LOG_TAG = "InAppUpdater" + private const val LOG_TAG = "InAppUpdater" - private data class GithubAsset( - @JsonProperty("name") val name: String, - @JsonProperty("size") val size: Int, // Size in bytes - @JsonProperty("browser_download_url") val browserDownloadUrl: String, - @JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive - ) + private data class GithubAsset( + @JsonProperty("name") val name: String, + @JsonProperty("size") val size: Int, // Size in bytes + @JsonProperty("browser_download_url") val browserDownloadUrl: String, + @JsonProperty("content_type") val contentType: String, // application/vnd.android.package-archive + ) - private data class GithubRelease( - @JsonProperty("tag_name") val tagName: String, // Version code - @JsonProperty("body") val body: String, // Description - @JsonProperty("assets") val assets: List, - @JsonProperty("target_commitish") val targetCommitish: String, // Branch - @JsonProperty("prerelease") val prerelease: Boolean, - @JsonProperty("node_id") val nodeId: String, - ) + private data class GithubRelease( + @JsonProperty("tag_name") val tagName: String, // Version code + @JsonProperty("body") val body: String, // Description + @JsonProperty("assets") val assets: List, + @JsonProperty("target_commitish") val targetCommitish: String, // Branch + @JsonProperty("prerelease") val prerelease: Boolean, + @JsonProperty("node_id") val nodeId: String, + ) - private data class GithubObject( - @JsonProperty("sha") val sha: String, // SHA-256 hash - @JsonProperty("type") val type: String, - @JsonProperty("url") val url: String, - ) + private data class GithubObject( + @JsonProperty("sha") val sha: String, // SHA-256 hash + @JsonProperty("type") val type: String, + @JsonProperty("url") val url: String, + ) - private data class GithubTag( - @JsonProperty("object") val githubObject: GithubObject, - ) + private data class GithubTag( + @JsonProperty("object") val githubObject: GithubObject, + ) - private data class Update( - @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, - @JsonProperty("updateURL") val updateURL: String?, - @JsonProperty("updateVersion") val updateVersion: String?, - @JsonProperty("changelog") val changelog: String?, - @JsonProperty("updateNodeId") val updateNodeId: String?, - ) + private data class Update( + @JsonProperty("shouldUpdate") val shouldUpdate: Boolean, + @JsonProperty("updateURL") val updateURL: String?, + @JsonProperty("updateVersion") val updateVersion: String?, + @JsonProperty("changelog") val changelog: String?, + @JsonProperty("updateNodeId") val updateNodeId: String?, + ) - private suspend fun Activity.getAppUpdate(): Update { - return try { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if ( - settingsManager.getBoolean( - getString(R.string.prerelease_update_key), - resources.getBoolean(R.bool.is_prerelease) - ) - ) { - getPreReleaseUpdate() - } else getReleaseUpdate() - } catch (e: Exception) { - Log.e(LOG_TAG, Log.getStackTraceString(e)) - Update(false, null, null, null, null) - } + private suspend fun Activity.getAppUpdate(): Update { + return try { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + if ( + settingsManager.getBoolean( + getString(R.string.prerelease_update_key), + resources.getBoolean(R.bool.is_prerelease) + ) + ) { + getPreReleaseUpdate() + } else getReleaseUpdate() + } catch (e: Exception) { + Log.e(LOG_TAG, Log.getStackTraceString(e)) + Update(false, null, null, null, null) } + } - private suspend fun Activity.getReleaseUpdate(): Update { - val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" - val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = parseJson>( - app.get(url, headers = headers).text - ) + private suspend fun Activity.getReleaseUpdate(): Update { + val url = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" + val headers = mapOf("Accept" to "application/vnd.github.v3+json") + val response = parseJson>( + app.get(url, headers = headers).text + ) - val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") - val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") - val foundList = response.filter { rel -> - !rel.prerelease - }.sortedWith(compareBy { release -> - release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> - versionRegex.find( - it1 - )?.groupValues?.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } + val versionRegex = Regex("""(.*?((\d+)\.(\d+)\.(\d+))\.apk)""") + val versionRegexLocal = Regex("""(.*?((\d+)\.(\d+)\.(\d+)).*)""") + val foundList = response.filter { rel -> + !rel.prerelease + }.sortedWith(compareBy { release -> + release.assets.firstOrNull { it.contentType == "application/vnd.android.package-archive" }?.name?.let { it1 -> + versionRegex.find( + it1 + )?.groupValues?.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() } - }).toList() - - val found = foundList.lastOrNull() - val foundAsset = found?.assets?.getOrNull(0) - val currentVersion = packageName?.let { - packageManager.getPackageInfo(it, 0) } + }).toList() - foundAsset?.name?.let { assetName -> - val foundVersion = versionRegex.find(assetName) - val shouldUpdate = - 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() - } - }?.compareTo( - foundVersion.groupValues.let { - it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() - } - )!! < 0 - } else false - return if (foundVersion != null) { - Update( - shouldUpdate, - foundAsset.browserDownloadUrl, - foundVersion.groupValues[2], - found.body, - found.nodeId - ) - } else Update(false, null, null, null, null) - } - - return Update(false, null, null, null, null) + val found = foundList.lastOrNull() + val foundAsset = found?.assets?.getOrNull(0) + val currentVersion = packageName?.let { + packageManager.getPackageInfo(it, 0) } - private suspend fun Activity.getPreReleaseUpdate(): Update { - val tagUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" - val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" - val headers = mapOf("Accept" to "application/vnd.github.v3+json") - val response = parseJson>( - app.get(releaseUrl, headers = headers).text - ) - - val found = response.lastOrNull { rel -> - rel.prerelease || rel.tagName == "pre-release" - } - - val foundAsset = found?.assets?.filter { it -> - it.contentType == "application/vnd.android.package-archive" - }?.getOrNull(0) - - return if (foundAsset != null) { - val tagResponse = parseJson(app.get(tagUrl, headers = headers).text) - val updateCommitHash = tagResponse.githubObject.sha.trim().take(7) - Log.d(LOG_TAG, "Fetched GitHub tag: $updateCommitHash") - + foundAsset?.name?.let { assetName -> + val foundVersion = versionRegex.find(assetName) + val shouldUpdate = + 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() + } + }?.compareTo( + foundVersion.groupValues.let { + it[3].toInt() * 100_000_000 + it[4].toInt() * 10_000 + it[5].toInt() + } + )!! < 0 + } else false + return if (foundVersion != null) { Update( - getString(R.string.commit_hash) != updateCommitHash, + shouldUpdate, foundAsset.browserDownloadUrl, - updateCommitHash, + foundVersion.groupValues[2], found.body, found.nodeId ) } else Update(false, null, null, null, null) } - private val updateLock = Mutex() + return Update(false, null, null, null, null) + } - private suspend fun Activity.downloadUpdate(url: String): Boolean { - try { - Log.d(LOG_TAG, "Downloading update: $url") - val appUpdateName = "CloudStream" - val appUpdateSuffix = "apk" + private suspend fun Activity.getPreReleaseUpdate(): Update { + val tagUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/git/ref/tags/pre-release" + val releaseUrl = "https://api.github.com/repos/$GITHUB_USER_NAME/$GITHUB_REPO/releases" + val headers = mapOf("Accept" to "application/vnd.github.v3+json") + val response = parseJson>( + app.get(releaseUrl, headers = headers).text + ) - // Delete all old updates - this.cacheDir.listFiles()?.filter { - it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix - }?.forEach { deleteFileOnExit(it) } - - val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix") - val sink: BufferedSink = downloadedFile.sink().buffer() - - updateLock.withLock { - sink.writeAll(app.get(url).body.source()) - sink.close() - openApk(this, Uri.fromFile(downloadedFile)) - } - - return true - } catch (e: Exception) { - logError(e) - return false - } + val found = response.lastOrNull { rel -> + rel.prerelease || rel.tagName == "pre-release" } - private fun openApk(context: Context, uri: Uri) { - try { - uri.path?.let { - val contentUri = FileProvider.getUriForFile( - context, - BuildConfig.APPLICATION_ID + ".provider", - File(it) - ) - val installIntent = Intent(Intent.ACTION_VIEW).apply { - addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) - putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) - data = contentUri - } - context.startActivity(installIntent) - } - } catch (e: Exception) { - logError(e) - } - } + val foundAsset = found?.assets?.filter { it -> + it.contentType == "application/vnd.android.package-archive" + }?.getOrNull(0) - /** - * @param checkAutoUpdate if the update check was launched automatically - */ - suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean { - val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) - if (!checkAutoUpdate || settingsManager.getBoolean( - getString(R.string.auto_update_key), - true + return if (foundAsset != null) { + val tagResponse = parseJson(app.get(tagUrl, headers = headers).text) + val updateCommitHash = tagResponse.githubObject.sha.trim().take(7) + Log.d(LOG_TAG, "Fetched GitHub tag: $updateCommitHash") + + Update( + getString(R.string.commit_hash) != updateCommitHash, + foundAsset.browserDownloadUrl, + updateCommitHash, + found.body, + found.nodeId + ) + } else Update(false, null, null, null, null) + } + + private val updateLock = Mutex() + + private suspend fun Activity.downloadUpdate(url: String): Boolean { + try { + Log.d(LOG_TAG, "Downloading update: $url") + val appUpdateName = "CloudStream" + val appUpdateSuffix = "apk" + + // Delete all old updates + this.cacheDir.listFiles()?.filter { + it.name.startsWith(appUpdateName) && it.extension == appUpdateSuffix + }?.forEach { deleteFileOnExit(it) } + + val downloadedFile = File.createTempFile(appUpdateName, ".$appUpdateSuffix") + val sink: BufferedSink = downloadedFile.sink().buffer() + + updateLock.withLock { + sink.writeAll(app.get(url).body.source()) + sink.close() + openApk(this, Uri.fromFile(downloadedFile)) + } + + return true + } catch (e: Exception) { + logError(e) + return false + } + } + + private fun openApk(context: Context, uri: Uri) { + try { + uri.path?.let { + val contentUri = FileProvider.getUriForFile( + context, + BuildConfig.APPLICATION_ID + ".provider", + File(it) ) - ) { - val update = getAppUpdate() - if (update.shouldUpdate && update.updateURL != null) { - // Check if update should be skipped - val updateNodeId = settingsManager.getString( - getString(R.string.skip_update_key), "" - ) + val installIntent = Intent(Intent.ACTION_VIEW).apply { + addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) + data = contentUri + } + context.startActivity(installIntent) + } + } catch (e: Exception) { + logError(e) + } + } - // Skips the update if its an automatic update and the update is skipped - // This allows updating manually - if (update.updateNodeId.equals(updateNodeId) && checkAutoUpdate) { - return false - } + /** + * @param checkAutoUpdate if the update check was launched automatically + */ + suspend fun Activity.runAutoUpdate(checkAutoUpdate: Boolean = true): Boolean { + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + if (!checkAutoUpdate || settingsManager.getBoolean( + getString(R.string.auto_update_key), + true + ) + ) { + val update = getAppUpdate() + if (update.shouldUpdate && update.updateURL != null) { + // Check if update should be skipped + val updateNodeId = settingsManager.getString( + getString(R.string.skip_update_key), "" + ) - runOnUiThread { - try { - val currentVersion = packageName?.let { - packageManager.getPackageInfo(it, 0) - } + // Skips the update if its an automatic update and the update is skipped + // This allows updating manually + if (update.updateNodeId.equals(updateNodeId) && checkAutoUpdate) { + return false + } - val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) - builder.setTitle( - getString(R.string.new_update_format).format( - currentVersion?.versionName, - update.updateVersion - ) + runOnUiThread { + try { + val currentVersion = packageName?.let { + packageManager.getPackageInfo(it, 0) + } + + val builder = AlertDialog.Builder(this, R.style.AlertDialogCustom) + builder.setTitle( + getString(R.string.new_update_format).format( + currentVersion?.versionName, + update.updateVersion ) + ) - val logRegex = Regex("\\[(.*?)\\]\\((.*?)\\)") - val sanitizedChangelog = update.changelog?.replace(logRegex) { matchResult -> - matchResult.groupValues[1] - } // Sanitized because it looks cluttered + val logRegex = Regex("\\[(.*?)\\]\\((.*?)\\)") + val sanitizedChangelog = update.changelog?.replace(logRegex) { matchResult -> + matchResult.groupValues[1] + } // Sanitized because it looks cluttered - builder.setMessage(sanitizedChangelog) - builder.apply { - setPositiveButton(R.string.update) { _, _ -> - // Forcefully start any delayed installations - if (ApkInstaller.delayedInstaller?.startInstallation() == true) return@setPositiveButton + builder.setMessage(sanitizedChangelog) + builder.apply { + setPositiveButton(R.string.update) { _, _ -> + // Forcefully start any delayed installations + if (ApkInstaller.delayedInstaller?.startInstallation() == true) return@setPositiveButton - showToast(R.string.download_started, Toast.LENGTH_LONG) + showToast(R.string.download_started, Toast.LENGTH_LONG) - // Check if the setting hasn't been changed - if ( - settingsManager.getInt( - getString(R.string.apk_installer_key), - -1 - ) == -1 - ) { - // Set to legacy installer if using MIUI - if (isMiUi()) { - settingsManager.edit { - putInt(getString(R.string.apk_installer_key), 1) - } + // Check if the setting hasn't been changed + if ( + settingsManager.getInt( + getString(R.string.apk_installer_key), + -1 + ) == -1 + ) { + // Set to legacy installer if using MIUI + if (isMiUi()) { + settingsManager.edit { + putInt(getString(R.string.apk_installer_key), 1) } } + } - val currentInstaller = settingsManager.getInt( - getString(R.string.apk_installer_key), - 0 - ) + val currentInstaller = settingsManager.getInt( + getString(R.string.apk_installer_key), + 0 + ) - when (currentInstaller) { - // New method - 0 -> { - val intent = PackageInstallerService.Companion.getIntent( - this@runAutoUpdate, - update.updateURL - ) - ContextCompat.startForegroundService(this@runAutoUpdate, intent) - } - // Legacy - 1 -> { - ioSafe { - if (!downloadUpdate(update.updateURL)) { - runOnUiThread { - showToast( - R.string.download_failed, - Toast.LENGTH_LONG - ) - } + when (currentInstaller) { + // New method + 0 -> { + val intent = PackageInstallerService.Companion.getIntent( + this@runAutoUpdate, + update.updateURL + ) + ContextCompat.startForegroundService(this@runAutoUpdate, intent) + } + // Legacy + 1 -> { + ioSafe { + if (!downloadUpdate(update.updateURL)) { + runOnUiThread { + showToast( + R.string.download_failed, + Toast.LENGTH_LONG + ) } } } } } + } - setNegativeButton(R.string.cancel) { _, _ -> } + setNegativeButton(R.string.cancel) { _, _ -> } - if (checkAutoUpdate) { - setNeutralButton(R.string.skip_update) { _, _ -> - settingsManager.edit { - putString( - getString(R.string.skip_update_key), - update.updateNodeId ?: "" - ) - } + if (checkAutoUpdate) { + setNeutralButton(R.string.skip_update) { _, _ -> + settingsManager.edit { + putString( + getString(R.string.skip_update_key), + update.updateNodeId ?: "" + ) } } } - builder.show().setDefaultFocus() - } catch (e: Exception) { - logError(e) } + builder.show().setDefaultFocus() + } catch (e: Exception) { + logError(e) } - return true } - return false + return true } return false } + return false + } - private fun isMiUi(): Boolean { - return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.name")) - } + private fun isMiUi(): Boolean { + return !TextUtils.isEmpty(getSystemProperty("ro.miui.ui.version.name")) + } - private fun getSystemProperty(propName: String): String? { - return try { - val p = Runtime.getRuntime().exec("getprop $propName") - BufferedReader(InputStreamReader(p.inputStream), 1024).use { - it.readLine() - } - } catch (_: IOException) { - null + private fun getSystemProperty(propName: String): String? { + return try { + val p = Runtime.getRuntime().exec("getprop $propName") + BufferedReader(InputStreamReader(p.inputStream), 1024).use { + it.readLine() } + } catch (_: IOException) { + null } } } From eaf2ac07921af26d69285f9127ea6f13fd6d3b54 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Thu, 11 Dec 2025 13:04:11 -0700 Subject: [PATCH 644/962] Add some tools:targetApi to styles to appease lint (#2319) Part of my work to fix all error level lint issues, in order to eventually enable `failOnError` and ensure better compatability with older API levels and a more consistent reporting of issues. --- app/src/main/res/values/styles.xml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 486872657..f0d937ea5 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,4 +1,4 @@ - + + + + + + + From 0d77f7b91ae1bb0f878d425934a63f5481e64af7 Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Sun, 21 Dec 2025 01:27:09 +0000 Subject: [PATCH 676/962] chore(locales): fix locale issues --- app/src/main/res/values-b+af/strings.xml | 2 +- app/src/main/res/values-b+am/strings.xml | 2 +- app/src/main/res/values-b+apc/strings.xml | 2 +- app/src/main/res/values-b+ar/strings.xml | 2 +- app/src/main/res/values-b+ars/strings.xml | 2 +- app/src/main/res/values-b+as/strings.xml | 2 +- app/src/main/res/values-b+az/strings.xml | 2 +- app/src/main/res/values-b+bg/strings.xml | 2 +- app/src/main/res/values-b+bn/strings.xml | 2 +- app/src/main/res/values-b+ckb/strings.xml | 2 +- app/src/main/res/values-b+cs/strings.xml | 2 +- app/src/main/res/values-b+de/strings.xml | 2 +- app/src/main/res/values-b+el/strings.xml | 2 +- app/src/main/res/values-b+eo/strings.xml | 2 +- app/src/main/res/values-b+es/strings.xml | 2 +- app/src/main/res/values-b+fa/strings.xml | 2 +- app/src/main/res/values-b+fil/strings.xml | 2 +- app/src/main/res/values-b+fr/strings.xml | 2 +- app/src/main/res/values-b+gl/strings.xml | 2 +- app/src/main/res/values-b+hi/strings.xml | 2 +- app/src/main/res/values-b+hr/strings.xml | 2 +- app/src/main/res/values-b+hu/strings.xml | 2 +- app/src/main/res/values-b+in/strings.xml | 2 +- app/src/main/res/values-b+it/strings.xml | 2 +- app/src/main/res/values-b+iw/strings.xml | 2 +- app/src/main/res/values-b+ja/strings.xml | 2 +- app/src/main/res/values-b+kn/strings.xml | 2 +- app/src/main/res/values-b+ko/strings.xml | 2 +- app/src/main/res/values-b+lt/strings.xml | 2 +- app/src/main/res/values-b+lv/strings.xml | 2 +- app/src/main/res/values-b+mk/strings.xml | 2 +- app/src/main/res/values-b+ml/strings.xml | 2 +- app/src/main/res/values-b+ms/strings.xml | 2 +- app/src/main/res/values-b+mt/strings.xml | 2 +- app/src/main/res/values-b+my/strings.xml | 2 +- app/src/main/res/values-b+ne/strings.xml | 2 +- app/src/main/res/values-b+nl/strings.xml | 2 +- app/src/main/res/values-b+nn/strings.xml | 2 +- app/src/main/res/values-b+no/strings.xml | 2 +- app/src/main/res/values-b+or/strings.xml | 2 +- app/src/main/res/values-b+pl/strings.xml | 2 +- app/src/main/res/values-b+pt+BR/strings.xml | 2 +- app/src/main/res/values-b+pt/strings.xml | 2 +- app/src/main/res/values-b+qt/strings.xml | 2 +- app/src/main/res/values-b+ro/strings.xml | 2 +- app/src/main/res/values-b+ru/strings.xml | 2 +- app/src/main/res/values-b+sk/strings.xml | 2 +- app/src/main/res/values-b+so/strings.xml | 2 +- app/src/main/res/values-b+sv/strings.xml | 2 +- app/src/main/res/values-b+ta/strings.xml | 2 +- app/src/main/res/values-b+ti/strings.xml | 2 +- app/src/main/res/values-b+tl/strings.xml | 2 +- app/src/main/res/values-b+tr/strings.xml | 2 +- app/src/main/res/values-b+uk/strings.xml | 2 +- app/src/main/res/values-b+ur/strings.xml | 2 +- app/src/main/res/values-b+vi/strings.xml | 2 +- app/src/main/res/values-b+zh+TW/strings.xml | 2 +- app/src/main/res/values-b+zh/strings.xml | 2 +- 58 files changed, 58 insertions(+), 58 deletions(-) diff --git a/app/src/main/res/values-b+af/strings.xml b/app/src/main/res/values-b+af/strings.xml index 9bcfb3f63..71a18f7a5 100644 --- a/app/src/main/res/values-b+af/strings.xml +++ b/app/src/main/res/values-b+af/strings.xml @@ -121,4 +121,4 @@ PIN moet 4 karakters bevat Verkeerde PIN. Probeer weer. Verskaffers - \ No newline at end of file + diff --git a/app/src/main/res/values-b+am/strings.xml b/app/src/main/res/values-b+am/strings.xml index 34b5c3433..26fb84dd3 100644 --- a/app/src/main/res/values-b+am/strings.xml +++ b/app/src/main/res/values-b+am/strings.xml @@ -109,4 +109,4 @@ ዓይነቶችን በመጠቀም ይፈልጉ ቅርጸ-ቁምፊዎችን በ%s ውስጥ በማስቀመጥ ያጫኑ hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index b911207ad..7d3263652 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -718,4 +718,4 @@ عدّل صورة الملف ادخل لينك ( عنوان ال URL ) تبع صورة الملف تم تعديل الصورة بنجاح - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ar/strings.xml b/app/src/main/res/values-b+ar/strings.xml index c86e91132..ff697d99f 100644 --- a/app/src/main/res/values-b+ar/strings.xml +++ b/app/src/main/res/values-b+ar/strings.xml @@ -765,4 +765,4 @@ اعلى وسط اعلى يمين شاهد المسلسل كاملاً - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ars/strings.xml b/app/src/main/res/values-b+ars/strings.xml index dabedeed1..b8240dc2e 100644 --- a/app/src/main/res/values-b+ars/strings.xml +++ b/app/src/main/res/values-b+ars/strings.xml @@ -346,4 +346,4 @@ عنوان مشغل الفيديو بحد أقصى لعدد الأحرف hide_player_control_names_key DNS عبر HTTPS - \ No newline at end of file + diff --git a/app/src/main/res/values-b+as/strings.xml b/app/src/main/res/values-b+as/strings.xml index fd6c35842..68fc2e163 100644 --- a/app/src/main/res/values-b+as/strings.xml +++ b/app/src/main/res/values-b+as/strings.xml @@ -665,4 +665,4 @@ সংহতিসমূহ/প্ৰদানকাৰী/পছন্দৰ মাধ্যমত টৰেণ্ট সামৰ্থবান কৰক চফ্টৱেৰ ডিকোডিং চফ্টৱেৰ ডিকোডিঙে প্লেয়াৰক আপোনাৰ ফোন দ্বাৰা সমৰ্থিত নোহোৱা ভিডিঅ\' ফাইলসমূহ চলাবলৈ সামৰ্থবান কৰে, কিন্তু উচ্চ ৰিজ\'লিউচনত লেগি বা অস্থিৰ প্লেবেকৰ সৃষ্টি কৰিব পাৰে - \ No newline at end of file + diff --git a/app/src/main/res/values-b+az/strings.xml b/app/src/main/res/values-b+az/strings.xml index 430cd4593..ffbd9d37d 100644 --- a/app/src/main/res/values-b+az/strings.xml +++ b/app/src/main/res/values-b+az/strings.xml @@ -144,4 +144,4 @@ Dəstəklənməyən xəta Gözlənilməyən oynadıcı xətası Ekran yansıtma - \ No newline at end of file + diff --git a/app/src/main/res/values-b+bg/strings.xml b/app/src/main/res/values-b+bg/strings.xml index 4116724d1..9eb439c88 100644 --- a/app/src/main/res/values-b+bg/strings.xml +++ b/app/src/main/res/values-b+bg/strings.xml @@ -703,4 +703,4 @@ Винаги изпращай запитване Задръжте, за да удвоите скоростта Дълго задържане за смяна на скоростта - \ No newline at end of file + diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index 4208f70f0..f65d673ae 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -352,4 +352,4 @@ প্রস্থান %1$d%2$s hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ckb/strings.xml b/app/src/main/res/values-b+ckb/strings.xml index c47af36a3..b38a7956c 100644 --- a/app/src/main/res/values-b+ckb/strings.xml +++ b/app/src/main/res/values-b+ckb/strings.xml @@ -83,4 +83,4 @@ کۆپی داخستن سەیڤ - \ No newline at end of file + diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index 9da634e73..07a37beb8 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -757,4 +757,4 @@ Uprostřed nahoře Vpravo nahoře Přehrát celý seriál - \ No newline at end of file + diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml index a6cb66b52..08b76d927 100644 --- a/app/src/main/res/values-b+de/strings.xml +++ b/app/src/main/res/values-b+de/strings.xml @@ -729,4 +729,4 @@ Oben links Oben mitte Oben rechts - \ No newline at end of file + diff --git a/app/src/main/res/values-b+el/strings.xml b/app/src/main/res/values-b+el/strings.xml index c5d3e5444..96da7f206 100644 --- a/app/src/main/res/values-b+el/strings.xml +++ b/app/src/main/res/values-b+el/strings.xml @@ -684,4 +684,4 @@ Κάντε όλους τους υπότιτλους πλάγιους Ακτίνα φόντου Σύρετε ξανά προς τα πάνω για να υπερβείτε το 100% - \ No newline at end of file + diff --git a/app/src/main/res/values-b+eo/strings.xml b/app/src/main/res/values-b+eo/strings.xml index 4a23a93da..e3e428075 100644 --- a/app/src/main/res/values-b+eo/strings.xml +++ b/app/src/main/res/values-b+eo/strings.xml @@ -128,4 +128,4 @@ Elŝutante Elŝuto Malsukcesite hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index 35343b9ad..c4fcc75dc 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -722,4 +722,4 @@ Inferior izquierda Inferior central Inferior derecho - \ No newline at end of file + diff --git a/app/src/main/res/values-b+fa/strings.xml b/app/src/main/res/values-b+fa/strings.xml index ae6ffae39..146236651 100644 --- a/app/src/main/res/values-b+fa/strings.xml +++ b/app/src/main/res/values-b+fa/strings.xml @@ -352,4 +352,4 @@ ­همه افزونه ها را تست کنید خودکار رنگ اصلی - \ No newline at end of file + diff --git a/app/src/main/res/values-b+fil/strings.xml b/app/src/main/res/values-b+fil/strings.xml index 7ea370852..f8ba8fa47 100644 --- a/app/src/main/res/values-b+fil/strings.xml +++ b/app/src/main/res/values-b+fil/strings.xml @@ -53,4 +53,4 @@ Itago ang napiling quality ng video sa mga resulta ng paghahanap Pumili ng mode upang i-filter ang pag-download ng mga plugin Awtomatikong i-install ang lahat ng hindi pa naka-install na plugin mula sa mga idinagdag na repository. - \ No newline at end of file + diff --git a/app/src/main/res/values-b+fr/strings.xml b/app/src/main/res/values-b+fr/strings.xml index 3edc7fa88..d8ecc4dae 100644 --- a/app/src/main/res/values-b+fr/strings.xml +++ b/app/src/main/res/values-b+fr/strings.xml @@ -723,4 +723,4 @@ En haut au centre En haut à droite Jouer la série complète - \ No newline at end of file + diff --git a/app/src/main/res/values-b+gl/strings.xml b/app/src/main/res/values-b+gl/strings.xml index a5ad765d0..aeb76080e 100644 --- a/app/src/main/res/values-b+gl/strings.xml +++ b/app/src/main/res/values-b+gl/strings.xml @@ -293,4 +293,4 @@ Redimensionar Omitir introducción Non volver a amosar - \ No newline at end of file + diff --git a/app/src/main/res/values-b+hi/strings.xml b/app/src/main/res/values-b+hi/strings.xml index c0ee55e70..e4eec65f3 100644 --- a/app/src/main/res/values-b+hi/strings.xml +++ b/app/src/main/res/values-b+hi/strings.xml @@ -340,4 +340,4 @@ टीवी में देखे Play mirror" कितना hd है वो वाला लेबल - \ No newline at end of file + diff --git a/app/src/main/res/values-b+hr/strings.xml b/app/src/main/res/values-b+hr/strings.xml index e5ae7752d..902a75500 100644 --- a/app/src/main/res/values-b+hr/strings.xml +++ b/app/src/main/res/values-b+hr/strings.xml @@ -743,4 +743,4 @@ Ukloni gledano do ove epizode Ponovo učitano Usluga ponovnog učitavanja - \ No newline at end of file + diff --git a/app/src/main/res/values-b+hu/strings.xml b/app/src/main/res/values-b+hu/strings.xml index 13210d6dd..1e97719c0 100644 --- a/app/src/main/res/values-b+hu/strings.xml +++ b/app/src/main/res/values-b+hu/strings.xml @@ -592,4 +592,4 @@ Elérhető offline megtekintésre Mindet Kiválaszt Mindent Kijelölés Eltávolítása - \ No newline at end of file + diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index 6f57647b8..ca3a39906 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -753,4 +753,4 @@ Atas tengah Atas kanan Putar Seri Penuh - \ No newline at end of file + diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index 331cdf061..56defcbcd 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -753,4 +753,4 @@ In alto a destra In centro a sinistra Riproduci serie completa - \ No newline at end of file + diff --git a/app/src/main/res/values-b+iw/strings.xml b/app/src/main/res/values-b+iw/strings.xml index 910b1ee0d..558d98a4b 100644 --- a/app/src/main/res/values-b+iw/strings.xml +++ b/app/src/main/res/values-b+iw/strings.xml @@ -560,4 +560,4 @@ הראה המלצות מוסיף אפשרות מהירות בנגן תדירות גיבוי - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml index dddecc389..614fe14b9 100644 --- a/app/src/main/res/values-b+ja/strings.xml +++ b/app/src/main/res/values-b+ja/strings.xml @@ -705,4 +705,4 @@ 上中央 右上 全シリーズを再生 - \ No newline at end of file + diff --git a/app/src/main/res/values-b+kn/strings.xml b/app/src/main/res/values-b+kn/strings.xml index fcfaa9045..3a77aeef0 100644 --- a/app/src/main/res/values-b+kn/strings.xml +++ b/app/src/main/res/values-b+kn/strings.xml @@ -130,4 +130,4 @@ ಈಗಿನ ಎಪಿಸೋಡ್ ಮುಗಿದಾಗ ಮುಂದಿನ ಎಪಿಸೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ko/strings.xml b/app/src/main/res/values-b+ko/strings.xml index 77e6ec748..7ab550913 100644 --- a/app/src/main/res/values-b+ko/strings.xml +++ b/app/src/main/res/values-b+ko/strings.xml @@ -659,4 +659,4 @@ %1$d시간 %2$d분 %3$d초 %1$d분 %2$d초 %1$d초 - \ No newline at end of file + diff --git a/app/src/main/res/values-b+lt/strings.xml b/app/src/main/res/values-b+lt/strings.xml index 6493e33e2..357192018 100644 --- a/app/src/main/res/values-b+lt/strings.xml +++ b/app/src/main/res/values-b+lt/strings.xml @@ -255,4 +255,4 @@ Pašalinti iš žiūrimų Garso takelis hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index 1aae0639c..287d79048 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -590,4 +590,4 @@ Nepareizs URL vai nederīgs attēls Profila attēls veiksmīgi nomainīts Rādīt ieteikumus - \ No newline at end of file + diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml index c70f62fc5..bccc2a00d 100644 --- a/app/src/main/res/values-b+mk/strings.xml +++ b/app/src/main/res/values-b+mk/strings.xml @@ -709,4 +709,4 @@ Горе во центар Горе на десно Пушти ја целата серија - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ml/strings.xml b/app/src/main/res/values-b+ml/strings.xml index fb956d011..dcb9e5270 100644 --- a/app/src/main/res/values-b+ml/strings.xml +++ b/app/src/main/res/values-b+ml/strings.xml @@ -273,4 +273,4 @@ ഔട്ട്ലൈൻ നിറം പശ്ചാത്തല നിറം hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ms/strings.xml b/app/src/main/res/values-b+ms/strings.xml index 6905abed4..8bbb2a7e0 100644 --- a/app/src/main/res/values-b+ms/strings.xml +++ b/app/src/main/res/values-b+ms/strings.xml @@ -538,4 +538,4 @@ Siaran Langsung Audio Podcast - \ No newline at end of file + diff --git a/app/src/main/res/values-b+mt/strings.xml b/app/src/main/res/values-b+mt/strings.xml index bd605bb37..ca62a043b 100644 --- a/app/src/main/res/values-b+mt/strings.xml +++ b/app/src/main/res/values-b+mt/strings.xml @@ -123,4 +123,4 @@ Neħħi Falla t-tniżżil hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+my/strings.xml b/app/src/main/res/values-b+my/strings.xml index b982e2717..1d35cbaa0 100644 --- a/app/src/main/res/values-b+my/strings.xml +++ b/app/src/main/res/values-b+my/strings.xml @@ -538,4 +538,4 @@ လိုက်ဘရီရွေးချယ်ရန် ဖြင့်ဖွင့်မည် hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ne/strings.xml b/app/src/main/res/values-b+ne/strings.xml index 4eea78b9c..9345cab2b 100644 --- a/app/src/main/res/values-b+ne/strings.xml +++ b/app/src/main/res/values-b+ne/strings.xml @@ -128,4 +128,4 @@ रिपोजिटरी को नाम र यूआरएल कपी गरियो! hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml index e1078212c..6e0982c79 100644 --- a/app/src/main/res/values-b+nl/strings.xml +++ b/app/src/main/res/values-b+nl/strings.xml @@ -634,4 +634,4 @@ Fout: wordt niet ondersteund Beveiliging Accounts - \ No newline at end of file + diff --git a/app/src/main/res/values-b+nn/strings.xml b/app/src/main/res/values-b+nn/strings.xml index 6989a85da..2cf83c183 100644 --- a/app/src/main/res/values-b+nn/strings.xml +++ b/app/src/main/res/values-b+nn/strings.xml @@ -192,4 +192,4 @@ Fortsett å sjå Prøv tilkopling på nytt… hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+no/strings.xml b/app/src/main/res/values-b+no/strings.xml index eea8a95fa..a981609cf 100644 --- a/app/src/main/res/values-b+no/strings.xml +++ b/app/src/main/res/values-b+no/strings.xml @@ -526,4 +526,4 @@ Hjelp Profilbakgrunn hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+or/strings.xml b/app/src/main/res/values-b+or/strings.xml index 8d0f604fb..807a3bcc6 100644 --- a/app/src/main/res/values-b+or/strings.xml +++ b/app/src/main/res/values-b+or/strings.xml @@ -159,4 +159,4 @@ ଏପିସୋଡ୍ %d ମୁକ୍ତିଲାଭ କରିବ ସିଜିନ୍ %1$d ଏପିସୋଡ୍ %2$d ମୁକ୍ତିଲାଭ କରିବ %1$dଘଣ୍ଟା %2$dମିନିଟ୍ %3$dସେକେଣ୍ଡ - \ No newline at end of file + diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index 0a234db96..5d67069be 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -734,4 +734,4 @@ Górne środkowe Górne prawe Odtwórz całą serię - \ No newline at end of file + diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index cb2e4b730..53df149ba 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -745,4 +745,4 @@ Superior direito Assistir à série completa Resolução e nome - \ No newline at end of file + diff --git a/app/src/main/res/values-b+pt/strings.xml b/app/src/main/res/values-b+pt/strings.xml index 31e01661d..a789e9739 100644 --- a/app/src/main/res/values-b+pt/strings.xml +++ b/app/src/main/res/values-b+pt/strings.xml @@ -731,4 +731,4 @@ Centro em cima Direita em cima Reproduzir Série Inteira - \ No newline at end of file + diff --git a/app/src/main/res/values-b+qt/strings.xml b/app/src/main/res/values-b+qt/strings.xml index 258a30df7..3de0f32df 100644 --- a/app/src/main/res/values-b+qt/strings.xml +++ b/app/src/main/res/values-b+qt/strings.xml @@ -654,4 +654,4 @@ Boo Oo-ahh oo-chit ar-ar Boooooo - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ro/strings.xml b/app/src/main/res/values-b+ro/strings.xml index b97397919..642eea0c3 100644 --- a/app/src/main/res/values-b+ro/strings.xml +++ b/app/src/main/res/values-b+ro/strings.xml @@ -656,4 +656,4 @@ Încărcați prima disponibilă Șterge pluginul Închide - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ru/strings.xml b/app/src/main/res/values-b+ru/strings.xml index 7589647f6..a37b0f373 100644 --- a/app/src/main/res/values-b+ru/strings.xml +++ b/app/src/main/res/values-b+ru/strings.xml @@ -719,4 +719,4 @@ Вверху центр Вверху правый Смотреть полностью - \ No newline at end of file + diff --git a/app/src/main/res/values-b+sk/strings.xml b/app/src/main/res/values-b+sk/strings.xml index 1b1cba51b..fb65841f2 100644 --- a/app/src/main/res/values-b+sk/strings.xml +++ b/app/src/main/res/values-b+sk/strings.xml @@ -458,4 +458,4 @@ Podcast Všetko Chyba kódovania - \ No newline at end of file + diff --git a/app/src/main/res/values-b+so/strings.xml b/app/src/main/res/values-b+so/strings.xml index 8366f1eea..fc42c63f7 100644 --- a/app/src/main/res/values-b+so/strings.xml +++ b/app/src/main/res/values-b+so/strings.xml @@ -473,4 +473,4 @@ Bilow isku qasan Qoraalka dhamaadka hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+sv/strings.xml b/app/src/main/res/values-b+sv/strings.xml index 6b62e66a0..dfbfce4b5 100644 --- a/app/src/main/res/values-b+sv/strings.xml +++ b/app/src/main/res/values-b+sv/strings.xml @@ -680,4 +680,4 @@ Betyg%s Uppdatera Plugins Gå till Hämtade filer - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ta/strings.xml b/app/src/main/res/values-b+ta/strings.xml index 368717dfb..94b6f717a 100644 --- a/app/src/main/res/values-b+ta/strings.xml +++ b/app/src/main/res/values-b+ta/strings.xml @@ -677,4 +677,4 @@ %1$d மணி %2$d நிமிடம் %3$d விநாடி %1$d நிமிடம் %2$d விநாடி %1$d விநாடி - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ti/strings.xml b/app/src/main/res/values-b+ti/strings.xml index 99e083351..6c154c8d8 100644 --- a/app/src/main/res/values-b+ti/strings.xml +++ b/app/src/main/res/values-b+ti/strings.xml @@ -4,4 +4,4 @@ ክፋል %d በ ላይ ይወጣል ተዋሳእቲ፡ %s hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+tl/strings.xml b/app/src/main/res/values-b+tl/strings.xml index cf63eceb6..94bb8ea1d 100644 --- a/app/src/main/res/values-b+tl/strings.xml +++ b/app/src/main/res/values-b+tl/strings.xml @@ -258,4 +258,4 @@ Mga setting ng mga subtitle ng Chromecast Maglaro ng Trailer hide_player_control_names_key - \ No newline at end of file + diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml index 1e49ba60a..f6a125b91 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -777,4 +777,4 @@ Sağ üst Altyazı Hizalama Tüm Seriyi Oynat - \ No newline at end of file + diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index 2cb4e5da5..519e311c3 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -705,4 +705,4 @@ Верхній центр Угорі праворуч Відтворити повну серію - \ No newline at end of file + diff --git a/app/src/main/res/values-b+ur/strings.xml b/app/src/main/res/values-b+ur/strings.xml index 563bf085d..d2c3d9f1c 100644 --- a/app/src/main/res/values-b+ur/strings.xml +++ b/app/src/main/res/values-b+ur/strings.xml @@ -621,4 +621,4 @@ آواز تاریخ %s اپنے اسمارٹ فون یا کمپیوٹر پر یہ %s وزٹ کریں اور مندرجہ بالا کوڈ ڈالیں - \ No newline at end of file + diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index 8804e0876..aa9caffd1 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -746,4 +746,4 @@ Giữa trái Giữa phải Phát trọn bộ loạt phim - \ No newline at end of file + diff --git a/app/src/main/res/values-b+zh+TW/strings.xml b/app/src/main/res/values-b+zh+TW/strings.xml index 7979c9d95..251df543c 100644 --- a/app/src/main/res/values-b+zh+TW/strings.xml +++ b/app/src/main/res/values-b+zh+TW/strings.xml @@ -703,4 +703,4 @@ 軟體解碼 不接受的種子 載入第一個可用的 - \ No newline at end of file + diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 2b149356e..0b893327f 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -777,4 +777,4 @@ 顶部居中 右上 播放全剧 - \ No newline at end of file + From 0593cfbc01c9e3ff7b5bc7f4a6d41adb6aa9367f Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:11:54 -0700 Subject: [PATCH 677/962] Add linting to library (#2301) --- build.gradle.kts | 1 + gradle/libs.versions.toml | 1 + library/build.gradle.kts | 1 + library/lint.xml | 6 +++ .../com/lagradost/cloudstream3/MainAPI.kt | 2 +- .../extractors/ContentXExtractor.kt | 6 +-- .../cloudstream3/extractors/DoodExtractor.kt | 29 +++++++-------- .../cloudstream3/extractors/Filegram.kt | 37 +++++++++---------- .../extractors/RapidVidExtractor.kt | 6 +-- .../cloudstream3/extractors/TRsTXExtractor.kt | 9 ++--- .../extractors/VidMoxyExtractor.kt | 6 +-- .../lagradost/cloudstream3/extractors/Vtbe.kt | 28 +++++++------- .../extractors/helper/CryptoJSHelper.kt | 8 ++-- .../cloudstream3/utils/HlsPlaylistParser.kt | 4 +- 14 files changed, 73 insertions(+), 71 deletions(-) create mode 100644 library/lint.xml diff --git a/build.gradle.kts b/build.gradle.kts index defd28515..cca263dd4 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,5 +1,6 @@ plugins { alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.lint) apply false alias(libs.plugins.android.multiplatform.library) apply false alias(libs.plugins.buildkonfig) apply false // Universal build config alias(libs.plugins.dokka) apply false diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1d29aab04..24f69824c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -112,6 +112,7 @@ work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "w [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +android-lint = { id = "com.android.lint", version.ref = "androidGradlePlugin" } android-multiplatform-library = { id = "com.android.kotlin.multiplatform.library", version.ref = "androidGradlePlugin" } buildkonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfigGradlePlugin" } dokka = { id = "org.jetbrains.dokka", version.ref = "dokkaGradlePlugin" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 5e2b59098..392552739 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -7,6 +7,7 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { id("maven-publish") // Gradle core plugin + alias(libs.plugins.android.lint) alias(libs.plugins.kotlin.multiplatform) alias(libs.plugins.android.multiplatform.library) alias(libs.plugins.buildkonfig) diff --git a/library/lint.xml b/library/lint.xml new file mode 100644 index 000000000..6f4e20222 --- /dev/null +++ b/library/lint.xml @@ -0,0 +1,6 @@ + + + + + + diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index 43f7f4070..ada8d5cd8 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -2502,7 +2502,7 @@ constructor( fun Episode.addDate(date: String?, format: String = "yyyy-MM-dd") { try { - this.date = SimpleDateFormat(format).parse(date ?: return)?.time + this.date = SimpleDateFormat(format, Locale.getDefault()).parse(date ?: return)?.time } catch (e: Exception) { logError(e) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt index 06c5ec321..dba2e9267 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/ContentXExtractor.kt @@ -17,12 +17,12 @@ open class ContentX : ExtractorApi() { val iSource = app.get(url, referer=extRef).text val iExtract = Regex("""window\.openPlayer\('([^']+)'""").find(iSource)!!.groups[1]?.value ?: throw ErrorLoadingException("iExtract is null") - val subUrls = mutableSetOf() + val subUrls = mutableSetOf() Regex("""\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(iSource).forEach { val (subUrl, subLang) = it.destructured - if (subUrl in subUrls) { return@forEach } - subUrls.add(subUrl) + if (subUrl in subUrls) { return@forEach } + subUrls.add(subUrl) subtitleCallback.invoke( newSubtitleFile( diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt index 58b6396a8..e1fd47fff 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/DoodExtractor.kt @@ -77,7 +77,7 @@ open class DoodLaExtractor : ExtractorApi() { override var name = "DoodStream" override var mainUrl = "https://dood.la" override val requiresReferer = false - + private val alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" override suspend fun getUrl( @@ -87,18 +87,17 @@ open class DoodLaExtractor : ExtractorApi() { callback: (ExtractorLink) -> Unit ) { val embedUrl = url.replace("/d/", "/e/") - val req = app.get(embedUrl) + val req = app.get(embedUrl) val host = getBaseUrl(req.url) val response0 = req.text - val md5 = host + (Regex("/pass_md5/[^']*").find(response0)?.value ?: return) + val md5 = host + (Regex("/pass_md5/[^']*").find(response0)?.value ?: return) val trueUrl = app.get(md5, referer = req.url).text + createHashTable() + "?token=" + md5.substringAfterLast("/") - - val quality = Regex("\\d{3,4}p") + val quality = Regex("\\d{3,4}p") .find(response0.substringAfter("").substringBefore("")) ?.groupValues ?.getOrNull(0) - - callback.invoke( + + callback.invoke( newExtractorLink( this.name, this.name, @@ -108,19 +107,17 @@ open class DoodLaExtractor : ExtractorApi() { this.quality = getQualityFromName(quality) } ) - } - -private fun createHashTable(): String { - return buildString { - repeat(10) { - append(alphabet.random()) + + private fun createHashTable(): String { + return buildString { + repeat(10) { + append(alphabet.random()) + } } } -} - -private fun getBaseUrl(url: String): String { + private fun getBaseUrl(url: String): String { return URI(url).let { "${it.scheme}://${it.host}" } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt index df7e03373..7baa62710 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filegram.kt @@ -32,32 +32,29 @@ open class Filegram : ExtractorApi() { "Sec-Fetch-Site" to "same-site", "user-agent" to USER_AGENT, ) - + val doc = app.get(getEmbedUrl(url), referer = referer).document val unpackedJs = unpackJs(doc).toString() - val videoUrl = Regex("""file:\s*"([^"]+\.m3u8[^"]*)"""").find(unpackedJs)?.groupValues?.get(1) + val videoUrl = Regex("""file:\s*"([^"]+\.m3u8[^"]*)"""").find(unpackedJs)?.groupValues?.get(1) if (videoUrl != null) { M3u8Helper.generateM3u8( - this.name, - fixUrl(videoUrl), - "$mainUrl/", - headers = header - ).forEach(callback) + this.name, + fixUrl(videoUrl), + "$mainUrl/", + headers = header + ).forEach(callback) } } - private fun unpackJs(script: Element): String? { - return script.select("script").find { it.data().contains("eval(function(p,a,c,k,e,d)") } - ?.data()?.let { getAndUnpack(it) } - } - - private fun getEmbedUrl(url: String): String { - return if (!url.contains("/embed-")) { - val videoId = url.substringAfter("$mainUrl/") - "$mainUrl/embed-$videoId" - } else { - url - } - } + private fun unpackJs(script: Element): String? { + return script.select("script").find { it.data().contains("eval(function(p,a,c,k,e,d)") } + ?.data()?.let { getAndUnpack(it) } + } + private fun getEmbedUrl(url: String): String { + return if (!url.contains("/embed-")) { + val videoId = url.substringAfter("$mainUrl/") + "$mainUrl/embed-$videoId" + } else url + } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt index bacd658bb..9654e5f38 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/RapidVidExtractor.kt @@ -15,12 +15,12 @@ open class RapidVid : ExtractorApi() { val extRef = referer ?: "" val videoReq = app.get(url, referer=extRef).text - val subUrls = mutableSetOf() + val subUrls = mutableSetOf() Regex("""captions\",\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(videoReq).forEach { val (subUrl, subLang) = it.destructured - if (subUrl in subUrls) { return@forEach } - subUrls.add(subUrl) + if (subUrl in subUrls) { return@forEach } + subUrls.add(subUrl) subtitleCallback.invoke( newSubtitleFile( diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt index c8796896c..1348f74d5 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/TRsTXExtractor.kt @@ -29,7 +29,7 @@ open class TRsTX : ExtractorApi() { ) } - val vidLinks = mutableSetOf() + val vidLinks = mutableSetOf() val vidMap = mutableListOf>() for (item in postJson) { if (item.file == null || item.title == null) continue @@ -37,8 +37,8 @@ open class TRsTX : ExtractorApi() { val fileUrl = "${mainUrl}/playlist/" + item.file.substring(1) + ".txt" val videoData = app.post(fileUrl, referer=extRef).text - if (videoData in vidLinks) { continue } - vidLinks.add(videoData) + if (videoData in vidLinks) { continue } + vidLinks.add(videoData) vidMap.add(mapOf( "title" to item.title, @@ -46,12 +46,11 @@ open class TRsTX : ExtractorApi() { )) } - for (mapEntry in vidMap) { val title = mapEntry["title"] ?: continue val m3uLink = mapEntry["videoData"] ?: continue - callback.invoke( + callback.invoke( newExtractorLink( source = this.name, name = "${this.name} - ${title}", diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt index e4cb69603..36acf7f7a 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/VidMoxyExtractor.kt @@ -15,12 +15,12 @@ open class VidMoxy : ExtractorApi() { val extRef = referer ?: "" val videoReq = app.get(url, referer=extRef).text - val subUrls = mutableSetOf() + val subUrls = mutableSetOf() Regex("""captions\",\"file\":\"([^\"]+)\",\"label\":\"([^\"]+)\"""").findAll(videoReq).forEach { val (subUrl, subLang) = it.destructured - if (subUrl in subUrls) { return@forEach } - subUrls.add(subUrl) + if (subUrl in subUrls) { return@forEach } + subUrls.add(subUrl) subtitleCallback.invoke( newSubtitleFile( diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt index 0f078b3bb..37b8ecb23 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Vtbe.kt @@ -20,20 +20,20 @@ open class Vtbe : ExtractorApi() { override suspend fun getUrl(url: String, referer: String?): List? { val response = app.get(url,referer=mainUrl).document val extractedpack =response.selectFirst("script:containsData(function(p,a,c,k,e,d))")?.data().toString() - JsUnpacker(extractedpack).unpack()?.let { unPacked -> - Regex("sources:\\[\\{file:\"(.*?)\"").find(unPacked)?.groupValues?.get(1)?.let { link -> - return listOf( - newExtractorLink( - this.name, - this.name, - link, - ) { - this.referer = referer ?: "" - this.quality = Qualities.Unknown.value - } - ) - } + JsUnpacker(extractedpack).unpack()?.let { unPacked -> + Regex("sources:\\[\\{file:\"(.*?)\"").find(unPacked)?.groupValues?.get(1)?.let { link -> + return listOf( + newExtractorLink( + this.name, + this.name, + link, + ) { + this.referer = referer ?: "" + this.quality = Qualities.Unknown.value + } + ) } - return null + } + return null } } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt index a13db65cc..af59b6f7d 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/helper/CryptoJSHelper.kt @@ -1,7 +1,8 @@ package com.lagradost.cloudstream3.extractors.helper +import com.lagradost.cloudstream3.base64DecodeArray +import com.lagradost.cloudstream3.base64Encode import java.util.Arrays -import java.util.Base64 import java.security.MessageDigest import java.security.SecureRandom import javax.crypto.Cipher @@ -51,8 +52,7 @@ object CryptoJS { System.arraycopy(saltBytes, 0, b, sBytes.size, saltBytes.size) System.arraycopy(cipherText, 0, b, sBytes.size + saltBytes.size, cipherText.size) - val bEncode = Base64.getEncoder().encode(b) - return String(bEncode) + return base64Encode(b) } /** @@ -62,7 +62,7 @@ object CryptoJS { * @param cipherText encrypted string */ fun decrypt(password: String, cipherText: String): String { - val ctBytes = Base64.getDecoder().decode(cipherText.toByteArray()) + val ctBytes = base64DecodeArray(cipherText) val saltBytes = Arrays.copyOfRange(ctBytes, 8, 16) val cipherTextBytes = Arrays.copyOfRange(ctBytes, 16, ctBytes.size) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt index dbc3c92f6..01e5bb862 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/HlsPlaylistParser.kt @@ -222,7 +222,7 @@ object HlsPlaylistParser { if (codecs.isNullOrEmpty()) { return arrayOf() } - return split(codecs.trim { it <= ' ' }, "(\\s*,\\s*)") + return split(codecs.trim(), "(\\s*,\\s*)") } fun getCodecsOfType( @@ -928,7 +928,7 @@ object HlsPlaylistParser { fun getMediaMimeType(codecOrNull: String?): String? { var codec = codecOrNull ?: return null - codec = codec.trim { it <= ' ' }.lowercase() + codec = codec.trim().lowercase() if (codec.startsWith("avc1") || codec.startsWith("avc3")) { return MimeTypes.VIDEO_H264 } else if (codec.startsWith("hev1") || codec.startsWith("hvc1")) { From bc68b3d7c69bdf8e203a6303e1734398b2d90524 Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Sun, 21 Dec 2025 02:19:29 +0000 Subject: [PATCH 678/962] chore(locales): fix locale issues --- app/src/main/res/values-b+hi/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-b+hi/strings.xml b/app/src/main/res/values-b+hi/strings.xml index 7fdb37c28..8b7c79650 100644 --- a/app/src/main/res/values-b+hi/strings.xml +++ b/app/src/main/res/values-b+hi/strings.xml @@ -314,7 +314,7 @@ खोज परिणामों में चयनित वीडियो गुणवत्ता छुपाएं प्रकरण - + %1$d-%2$d @string/home_play From be78306c553dabb83ed19c8d02d668aba2b1d03d Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:22:07 -0700 Subject: [PATCH 679/962] Minor order fix for lint plugin (#2355) --- library/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/build.gradle.kts b/library/build.gradle.kts index 392552739..e73ed970d 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -7,8 +7,8 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile plugins { id("maven-publish") // Gradle core plugin - alias(libs.plugins.android.lint) alias(libs.plugins.kotlin.multiplatform) + alias(libs.plugins.android.lint) alias(libs.plugins.android.multiplatform.library) alias(libs.plugins.buildkonfig) alias(libs.plugins.dokka) From 6c2228b964a097e0cf558fea638b1a260a3309a7 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 20 Dec 2025 19:48:37 -0700 Subject: [PATCH 680/962] Improve caching system for actions (#2249) --- .github/workflows/build_to_archive.yml | 6 +++++- .github/workflows/generate_dokka.yml | 6 +++++- .github/workflows/prerelease.yml | 6 +++++- .github/workflows/pull_request.yml | 7 ++++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_to_archive.yml b/.github/workflows/build_to_archive.yml index ce920002e..07096014a 100644 --- a/.github/workflows/build_to_archive.yml +++ b/.github/workflows/build_to_archive.yml @@ -40,7 +40,6 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -56,6 +55,11 @@ jobs: echo "::add-mask::${KEY_PWD}" echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + - name: Run Gradle run: ./gradlew assemblePrerelease env: diff --git a/.github/workflows/generate_dokka.yml b/.github/workflows/generate_dokka.yml index e082b79f4..e3dac3857 100644 --- a/.github/workflows/generate_dokka.yml +++ b/.github/workflows/generate_dokka.yml @@ -45,7 +45,11 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} - name: Set up Android SDK uses: android-actions/setup-android@v3 diff --git a/.github/workflows/prerelease.yml b/.github/workflows/prerelease.yml index cee9538bd..c7dee13eb 100644 --- a/.github/workflows/prerelease.yml +++ b/.github/workflows/prerelease.yml @@ -31,7 +31,6 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew @@ -47,6 +46,11 @@ jobs: echo "::add-mask::${KEY_PWD}" echo "key_pwd=$KEY_PWD" >> $GITHUB_OUTPUT + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + - name: Run Gradle run: ./gradlew assemblePrerelease build androidSourcesJar makeJar env: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index 381331f0b..090e7a2ec 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -13,11 +13,16 @@ jobs: with: distribution: temurin java-version: 17 - cache: gradle - name: Grant execute permission for gradlew run: chmod +x gradlew + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v5 + with: + cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} + cache-read-only: false + - name: Run Gradle run: ./gradlew assemblePrereleaseDebug From 7fd490218039a64251d221fe5ce455de87afc755 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 23 Dec 2025 20:00:28 +0100 Subject: [PATCH 681/962] Translated using Weblate (Belarusian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 33.4% (277 of 828 strings) Translated using Weblate (Arabic (Levantine)) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Belarusian) Currently translated at 27.5% (228 of 828 strings) Translated using Weblate (Latvian) Currently translated at 84.9% (703 of 828 strings) Translated using Weblate (Belarusian) Currently translated at 25.4% (211 of 828 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Belarusian) Currently translated at 23.0% (191 of 828 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Italian) Currently translated at 100.0% (828 of 828 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (828 of 828 strings) Co-authored-by: Hosted Weblate Co-authored-by: Juan Rubin Co-authored-by: Massimo Pissarello Co-authored-by: Pizza Party Co-authored-by: Sasha Glazko Co-authored-by: soldado-do-wolfenstein Co-authored-by: Максим Горпиніч Co-authored-by: ℂ𝕠𝕠𝕠𝕝 (𝕘𝕚𝕥𝕙𝕦𝕓.𝕔𝕠𝕞/ℂ𝕠𝕠𝕠𝕝) Co-authored-by: 大王叫我来巡山 Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/apc/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/be/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/it/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/lv/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pt/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pt_BR/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/uk/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/zh_Hans/ Translation: Cloudstream/App --- app/src/main/res/values-b+apc/strings.xml | 28 +++++- app/src/main/res/values-b+it/strings.xml | 1 + app/src/main/res/values-b+lv/strings.xml | 2 +- app/src/main/res/values-b+pt+BR/strings.xml | 4 + app/src/main/res/values-b+pt/strings.xml | 4 + app/src/main/res/values-b+uk/strings.xml | 1 + app/src/main/res/values-b+zh/strings.xml | 1 + app/src/main/res/values-be/strings.xml | 104 ++++++++++++++++++++ 8 files changed, 142 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index 7d3263652..56e1a7e34 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -75,7 +75,7 @@ تلقائيًا نَزِل كل الإضافات من الريپويات يللي نزادِت. محي بلش - فيه تِلِفونات م فيها تعوز الطريقة الجديدة لتجديد الآپات. جربو \"الطريقة القديمة\" إزا م عم تنزل التجديدات. + فيه أجهزة م فيها تعوز الطريقة الجديدة لتجديد الآپات. جربو \"الطريقة القديمة\" إزا م عم تنزل التجديدات. بعد ما تسكر \"كلود ستريم\"، بكفي الڤيديو بشِباك زغير فوق غير آپ هيدا المصدر م بيدعم \"كروم كاست\" تنبيش منظّم @@ -670,7 +670,7 @@ أفّي اللودينگ تلقائيًا سماح ب إستعمال الـTorrent بال سَتِنگز/المصادر/المحتوى المفضل سكر الآپ و رجاع فتحه، و قبال دعم التورنت ت تكفي. - السوفتوار ديكودينگ بخلي مشغل الڤيديو يمشّي أنواع فيلات ڤيديو م بيدعمها جهازك، بس هال شي معقول يخلي الڤيديو يعلق، خاصتًا إزا كانت الجودة عالية + السوفتوار ديكودينگ بخلي مشغل الڤيديو يمشّي أنواع فيلات ڤيديو م بيدعمها جهازك، بس هال شي معقول يخلي الڤيديو يعلق، خاصتًا إزا كانت الجودة عالية. سوفتوار ديكودينگ رايتينگ (أوطا) نهار اللي نزل (أجدد) @@ -718,4 +718,28 @@ عدّل صورة الملف ادخل لينك ( عنوان ال URL ) تبع صورة الملف تم تعديل الصورة بنجاح + بلّش كل المسلسل + نزل النسخة التجريبية من الآپ + أصلًا عندك النسخة التجريبية. + فشل تنزيل النسخة التجريبية. + ستعمل مصدر بديل" + كتيبة الحلقة + م لقينا الرابط + الرابط أو الصورة مش صالحة + عتبر الحلقات محدورة لحد هون + وقف إعتبار الحلقات محدورة لحد هون + عملنا ري-لوود + عمول ري-لوود للمصدر + اسم + الجودة وال اسم + ميلة الترجمة + تحت، عال شمال + تحت، بال نُص + تحت، عال يمين + نُص، شمال + نُص النُص + نُص، عال يمين + فوق، عال شمال + فوق، بال نُص + فوق، عال يمين diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index 4ea21be51..fee5b56f3 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -756,4 +756,5 @@ Installa la versione pre-release La versione pre-release è già installata. Impossibile installare la versione pre-release. + Testo dell\'episodio diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index e8fb46baa..444a59a5f 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -85,7 +85,7 @@ Settingi Žanrs Dalities - Atvērt internetā + Atvērt pārlūkā Ieladēts Lādējas Aizvērt diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index 6a6ba2111..3fbc4fb28 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -745,4 +745,8 @@ Superior direito Assistir à série completa Resolução e nome + Falha ao instalar a versão antecipada. + Versão antecipada instalada. + Instalar versão antecipada + Episódio Text diff --git a/app/src/main/res/values-b+pt/strings.xml b/app/src/main/res/values-b+pt/strings.xml index a789e9739..23b49195f 100644 --- a/app/src/main/res/values-b+pt/strings.xml +++ b/app/src/main/res/values-b+pt/strings.xml @@ -731,4 +731,8 @@ Centro em cima Direita em cima Reproduzir Série Inteira + Instalar versão de pré-lançamento + Versão de pré-lançamento já instalada. + Falha ao instalar pré-lançamento. + Texto do Episódio diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index f5c9c2c18..54562f263 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -708,4 +708,5 @@ Встановити передрелізну версію Попередня версія вже встановлена. Не вдалося встановити попередню версію. + Текст епізоду diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 243b56a47..224041e49 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -780,4 +780,5 @@ 安装预发行版 已安装预发行版。 安装预发行版失败。 + 剧集文本 diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index a81f30ef7..2111713d6 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -52,4 +52,108 @@ Прайграць фільм Прайграць трэйлер Прайграць трансляцыю + Трансліраваць Torrent + Прайграць серыял поўнасцю + Гэта відэа — Torrent, гэта значыць, што ваша актыўнасць можа быць адсочана.\nУпэўніцеся, што вы ведаеце, як працуюць Torrent-файлы, перад працягам. + Крыніцы + Субцітры + Паспрабаваць перападлучыцца… + Назад + Прайграць серыю + Спампаваць + Спампавана + Ідзе спампоўванне + Спампоўванне прыпынена + Спампоўванне пачалося + Не ўдалося спампаваць + Спампоўванне скасавана + Спампоўванне завершана + Выберыце элементы для выдалення + Спамповак пакуль што няма. + Даступна для прагляду па-за сеткай + Выбраць ўсё + Зняць выбар + Пачалося абнаўленне + Сеткавая трансляцыя + Адкрыць лакальнае відэа + Памылка пры загрузцы спасылак + Спасылкі перазагружаны + Унутранае сховішча + Дуб + Суб + Выдаліць файл + Прайграць файл + Узнавіць спампоўванне + Прыпыніць спампоўванне + Дадатковыя звесткі + Схаваць + Прайграць + Звесткі + Фільтр закладак + Закладкі + Выдаліць + Задаць статус прагляду + Прымяніць + Скапіраваць + Закрыць + Ачысціць + Захаваць + Назва рэпазіторыя і спасылка + скапіравана! + Паведамленне аб новай серыі + Пошук у іншых пашырэннях + Паказваць рэкамендацыі + Хуткасць прайгравальніка + Налады субцітраў + Колер тэксту + Колер контуру + Колер фону + Колер акна + Тып контуру + Уздым субцітраў + Шрыфт + Памер шрыфту + Пошук праз пастаўшчыкоў + Пошук праз тыпы + %d бенена(ў) дадзена распрацоўшчыкам + Бененаў не дадзена + Выбраць мову аўтаматычна + Спампаваць мовы + Мова субцітраў + Утрымайце, каб скінуць + Усталёўвайце шрыфты, перацягваючы іх да %s + Працягнуць прагляд + Выдаліць + Больш інфармацыі + \@string/home_play + Для карэктнай працы гэтага пастаўшчыка можа спатрэбіцца VPN + Гэты пастаўшчык — Torrent, рэкамендуецца VPN + Сайт не пастаўляе метаданых, загрузіць відэа не ўдасца, калі на сайце яго няма. + Апісанне + Сюжэту не знойдзена + Апісання не знойдзена + Паказаць Logcat 🐈 + Журнал + Відарыс у відарысе + Працягвае прайграванне ў мініяцюры зверху іншых праграм + Кнопка змены памеру прайгравальніка + Прыбраць чорныя межы + Субцітры + Налады субцітраў прайгравальніка + Субцітры Chromecast + Налады субцітраў Chromecast + Хуткасць прайгравання + Дадаць параметр хуткасці да прайгравальніка + Чырканне для перамоткі + Правядзіце пальцам з боку ў бок, каб кіраваць пазіцыяй у відэа + Чырканне для змены налад + Правядзіце пальцам уверх або ўніз злева ці справа, каб змяніць яркасць або гучнасць + Аўтаматычнае прайграванне наступнай серыі + Прайграць наступную серыю пасля сканчэння бягучай + Падвойнае націсканне для перамоткі + Падвойнае націсканне для прыпынення + Крок перамоткі (у секундах) + Двойчы націсніце справа ці злева, каб перайсці наперад ці назад + Двойчы націсніце пасярэдзіне, каб прыпыніць прайграванне + Выкарыстоўваць сістэмную яркасць From 063d960c3a0d3c19eef2c38b8b86cf6371c81e68 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 23 Dec 2025 18:57:28 -0700 Subject: [PATCH 682/962] Pin rhino version (#2369) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 24f69824c..692fd3b4c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -37,7 +37,7 @@ paletteKtx = "1.0.0" preferenceKtx = "1.2.1" previewseekbarMedia3 = "1.1.1.0" qrcodeKotlin = "4.5.0" -rhino = "1.8.1" +rhino = { strictly = "1.8.1" } # Requires minSdk 26 or later beginning at version 1.9.0 safefile = "0.0.8" shimmer = "0.5.0" tmdbJava = "2.13.0" From 3fe6a7853ae08e8df4ccf319e19d87b2d6ba8b01 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 23 Dec 2025 19:07:08 -0700 Subject: [PATCH 683/962] Replace QuickJS with Zipline (#2256) QuickJS was renamed to Zipline all the way back in 2021. Unlike old QuickJS, newer Zipline versions are 16kb aligned. Current Zipline is also compatible back to minSdk 21. --- app/build.gradle.kts | 2 +- gradle/libs.versions.toml | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index a7536da0d..e8a07b571 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -204,12 +204,12 @@ dependencies { // Extensions & Other Libs implementation(libs.jsoup) // HTML Parser implementation(libs.rhino) // Run JavaScript - implementation(libs.quickjs) implementation(libs.fuzzywuzzy) // Library/Ext Searching with Levenshtein Distance implementation(libs.safefile) // To Prevent the URI File Fu*kery coreLibraryDesugaring(libs.desugar.jdk.libs.nio) // NIO Flavor Needed for NewPipeExtractor implementation(libs.conscrypt.android) // To Fix SSL Fu*kery on Android 9 implementation(libs.jackson.module.kotlin) // JSON Parser + implementation(libs.zipline) // Torrent Support implementation(libs.torrentserver) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 692fd3b4c..f03f2d868 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -45,6 +45,7 @@ torrentserver = "7861970e038b35cd8c6918384e49caf26903e09e" tvprovider = "1.1.0" video = "1.0.0" workRuntimeKtx = "2.10.5" +zipline = "1.24.0" jvmTarget = "1.8" jdkToolchain = "17" @@ -100,7 +101,6 @@ palette-ktx = { module = "androidx.palette:palette-ktx", version.ref = "paletteK preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "preferenceKtx" } previewseekbar-media3 = { module = "com.github.rubensousa:previewseekbar-media3", version.ref = "previewseekbarMedia3" } qrcode-kotlin = { module = "io.github.g0dkar:qrcode-kotlin", version.ref = "qrcodeKotlin" } -quickjs = { module = "app.cash.quickjs:quickjs-android", version = "0.9.2" } rhino = { module = "org.mozilla:rhino", version.ref = "rhino" } safefile = { module = "com.github.LagradOst:SafeFile", version.ref = "safefile" } shimmer = { module = "com.facebook.shimmer:shimmer", version.ref = "shimmer" } @@ -109,6 +109,7 @@ torrentserver = { module = "com.github.recloudstream:torrentserver", version.ref tvprovider = { module = "androidx.tvprovider:tvprovider", version.ref = "tvprovider" } video = { module = "com.google.android.mediahome:video", version.ref = "video" } work-runtime-ktx = { module = "androidx.work:work-runtime-ktx", version.ref = "workRuntimeKtx" } +zipline = { module = "app.cash.zipline:zipline-android", version.ref = "zipline" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } From 81d9ecde674e23ad2d53b335230369aedaea4a51 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Tue, 23 Dec 2025 19:21:59 -0700 Subject: [PATCH 684/962] Move untranslatable strings to seperate file (#2273) This could cause crashes or poisoned data on some languages as some untranslatable strings were being translated, including keys and format strings that shouldn't be translatable. Also when translating the episodes key on weblate it caused a conflict between the plural version (which weblate does support) and the actual episodes key, meaning the episodes key was translating as the singular version of the plural episodes version in some cases. Moving to a separate resource file should hopefully prevent these issues. --- app/src/main/res/values-b+af/strings.xml | 1 - app/src/main/res/values-b+am/strings.xml | 1 - app/src/main/res/values-b+apc/strings.xml | 3 - app/src/main/res/values-b+ar/strings.xml | 22 --- app/src/main/res/values-b+ars/strings.xml | 1 - app/src/main/res/values-b+as/strings.xml | 1 - app/src/main/res/values-b+bg/strings.xml | 3 - app/src/main/res/values-b+bn/strings.xml | 2 - app/src/main/res/values-b+cs/strings.xml | 12 -- app/src/main/res/values-b+de/strings.xml | 1 - app/src/main/res/values-b+el/strings.xml | 1 - app/src/main/res/values-b+eo/strings.xml | 1 - app/src/main/res/values-b+es/array.xml | 44 ----- app/src/main/res/values-b+es/strings.xml | 3 - app/src/main/res/values-b+fa/strings.xml | 1 - app/src/main/res/values-b+fil/strings.xml | 1 - app/src/main/res/values-b+fr/strings.xml | 1 - app/src/main/res/values-b+gl/strings.xml | 5 - app/src/main/res/values-b+hi/strings.xml | 5 - app/src/main/res/values-b+hr/strings.xml | 13 -- app/src/main/res/values-b+hu/strings.xml | 1 - app/src/main/res/values-b+in/strings.xml | 10 -- app/src/main/res/values-b+it/strings.xml | 1 - app/src/main/res/values-b+iw/strings.xml | 1 - app/src/main/res/values-b+ja/strings.xml | 1 - app/src/main/res/values-b+kn/strings.xml | 1 - app/src/main/res/values-b+ko/strings.xml | 1 - app/src/main/res/values-b+lt/strings.xml | 1 - app/src/main/res/values-b+lv/strings.xml | 1 - app/src/main/res/values-b+mk/strings.xml | 1 - app/src/main/res/values-b+ml/strings.xml | 1 - app/src/main/res/values-b+ms/strings.xml | 4 - app/src/main/res/values-b+mt/strings.xml | 1 - app/src/main/res/values-b+my/strings.xml | 1 - app/src/main/res/values-b+ne/strings.xml | 1 - app/src/main/res/values-b+nl/strings.xml | 9 -- app/src/main/res/values-b+nn/strings.xml | 1 - app/src/main/res/values-b+no/strings.xml | 3 - app/src/main/res/values-b+or/strings.xml | 1 - app/src/main/res/values-b+pl/array.xml | 44 ----- app/src/main/res/values-b+pl/strings.xml | 1 - app/src/main/res/values-b+pt+BR/strings.xml | 10 -- app/src/main/res/values-b+pt/strings.xml | 1 - app/src/main/res/values-b+qt/strings.xml | 1 - app/src/main/res/values-b+ro/strings.xml | 2 - app/src/main/res/values-b+ru/strings.xml | 3 - app/src/main/res/values-b+sk/strings.xml | 1 - app/src/main/res/values-b+so/strings.xml | 1 - app/src/main/res/values-b+sv/strings.xml | 3 - app/src/main/res/values-b+ta/strings.xml | 1 - app/src/main/res/values-b+ti/strings.xml | 1 - app/src/main/res/values-b+tl/strings.xml | 1 - app/src/main/res/values-b+tr/array.xml | 44 ----- app/src/main/res/values-b+tr/strings.xml | 35 ---- app/src/main/res/values-b+uk/strings.xml | 3 - app/src/main/res/values-b+ur/strings.xml | 1 - app/src/main/res/values-b+vi/array.xml | 44 ----- app/src/main/res/values-b+vi/strings.xml | 1 - app/src/main/res/values-b+zh+TW/strings.xml | 35 ---- app/src/main/res/values-b+zh/strings.xml | 33 ---- .../res/values/donottranslate-strings.xml | 152 ++++++++++++++++++ app/src/main/res/values/strings.xml | 152 +----------------- 62 files changed, 153 insertions(+), 580 deletions(-) create mode 100644 app/src/main/res/values/donottranslate-strings.xml diff --git a/app/src/main/res/values-b+af/strings.xml b/app/src/main/res/values-b+af/strings.xml index 71a18f7a5..81d7a96ae 100644 --- a/app/src/main/res/values-b+af/strings.xml +++ b/app/src/main/res/values-b+af/strings.xml @@ -105,7 +105,6 @@ Voer lettertipes in deur dit in %s te plaas Rolverdeling: %s Nuwe episode notifikasie - hide_player_control_names_key Gratis Gebruik Wis Uit diff --git a/app/src/main/res/values-b+am/strings.xml b/app/src/main/res/values-b+am/strings.xml index 26fb84dd3..7fd3274b9 100644 --- a/app/src/main/res/values-b+am/strings.xml +++ b/app/src/main/res/values-b+am/strings.xml @@ -108,5 +108,4 @@ ተጨማሪ መረጃ ዓይነቶችን በመጠቀም ይፈልጉ ቅርጸ-ቁምፊዎችን በ%s ውስጥ በማስቀመጥ ያጫኑ - hide_player_control_names_key diff --git a/app/src/main/res/values-b+apc/strings.xml b/app/src/main/res/values-b+apc/strings.xml index 56e1a7e34..9bc697acf 100644 --- a/app/src/main/res/values-b+apc/strings.xml +++ b/app/src/main/res/values-b+apc/strings.xml @@ -573,8 +573,6 @@ حطو الأرقام السرية الحالية صوت حط كبسة لبرم إتجاه الشاشة - rotate_video_key - auto_rotate_video_key برم الشاشة أوتوماتيكيًا برومو غير إتجاه الشاشة أوتوماتيكيًا حسب شكل الڤيديو @@ -625,7 +623,6 @@ تجاهل فتاح الريپو فتاح %s ع تلفونك أو كمپيوترك، وحط الكود اللي فوق - hide_player_control_names_key بلشه من الأول تحذير فتاح الڤيديو اللي ع جهازك diff --git a/app/src/main/res/values-b+ar/strings.xml b/app/src/main/res/values-b+ar/strings.xml index ff697d99f..487b29d84 100644 --- a/app/src/main/res/values-b+ar/strings.xml +++ b/app/src/main/res/values-b+ar/strings.xml @@ -235,10 +235,6 @@ ملصق مدبلج ملصق مترجم العنوان - show_hd_key - show_dub_key - show_sub_key - show_title_key التحكم في عناصر الواجهة على الملصق لم يتم العثور على تحديثات تحقق من التحديثات @@ -270,8 +266,6 @@ امتداد تكبير إخلاء مسؤولية - legal_notice_key - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. عام زر العشوائي إظهار زر عشوائي على الصفحة الرئيسية والمكتبة @@ -291,10 +285,6 @@ مكان عنوان الملصق وضع العنوان تحت الملصق - anilist_key - mal_key - opensubtitles_key - nginx_key كلمة المرور إسم المستخدم البريد الإلكتروني @@ -302,14 +292,6 @@ إسم الموقع الجديد رابط الموقع مثلا : https://example.com اللغة (الإنجليزية) - %1$s %2$s حساب تسجيل الخروج @@ -332,7 +314,6 @@ الكل الحد الاقصي الحد الأدنى - @string/none الخطوط المحيطة النمط المنخفض ظل @@ -602,8 +583,6 @@ تعديل الحساب تم إعادة تحميل الروابط عرض زر تبديل لاتجاه الشاشة - تدوير الفيديو - مفتاح تدوير الفيديو التلقائي الدوران التلقائي تدوير تمكين التبديل التلقائي لاتجاه الشاشة بناءً على اتجاه الفيديو @@ -652,7 +631,6 @@ قم بزيارة %s على هاتفك الذكي أو جهاز الكمبيوتر وأدخل الرمز أعلاه لا يمكن الحصول على رمز PIN للجهاز، حاول المصادقة المحلية تنتهي صلاحية الرمز خلال %1$dm %2$ds - hide_player_control_names_key تشغيل من البداية فتح فيديو محلي تحذير diff --git a/app/src/main/res/values-b+ars/strings.xml b/app/src/main/res/values-b+ars/strings.xml index b8240dc2e..3104e6a9a 100644 --- a/app/src/main/res/values-b+ars/strings.xml +++ b/app/src/main/res/values-b+ars/strings.xml @@ -344,6 +344,5 @@ وثائقي موقع عنوان مشغل الفيديو بحد أقصى لعدد الأحرف - hide_player_control_names_key DNS عبر HTTPS diff --git a/app/src/main/res/values-b+as/strings.xml b/app/src/main/res/values-b+as/strings.xml index 68fc2e163..eb6ad4aa4 100644 --- a/app/src/main/res/values-b+as/strings.xml +++ b/app/src/main/res/values-b+as/strings.xml @@ -607,7 +607,6 @@ চাবটাইটলসমূহ এপিচ\'ড প্লে কৰক প্ৰয়োগ কৰক - hide_player_control_names_key ফাইলসমূহ ডিলিট কৰক ডিলিট (%1$d | %2$s) আপুনি স্থায়ীভাৱে তলত দিয়া আইটেমসমূহ ডিলিট কৰিবলৈ নিশ্চিত নেকি? diff --git a/app/src/main/res/values-b+bg/strings.xml b/app/src/main/res/values-b+bg/strings.xml index 9eb439c88..2c238b968 100644 --- a/app/src/main/res/values-b+bg/strings.xml +++ b/app/src/main/res/values-b+bg/strings.xml @@ -296,8 +296,6 @@ НовоИмеНаСайт example.com Езиков код (en) - %1$s %2$s Акаунт Излизане @@ -588,7 +586,6 @@ Покажи предложения Добавя опция за промяна на скоростта в плеъра Този тест е направен за програмисти и не проверява работата на никакви добавки. - hide_player_control_names_key Предстоящо в %s Име на хранилището и URL адрес копирани! diff --git a/app/src/main/res/values-b+bn/strings.xml b/app/src/main/res/values-b+bn/strings.xml index f65d673ae..2e37f43f3 100644 --- a/app/src/main/res/values-b+bn/strings.xml +++ b/app/src/main/res/values-b+bn/strings.xml @@ -238,7 +238,6 @@ অ্যান্ড্রয়েড টিভির মতো, কম মেমরির ডিভাইসে খুব বেশি সেট করা হলে সমস্যা করবে। ক্লোন সাইট প্লেয়ারের ফিচার - MAL AniList TMDB IMDB Kitsu Trakt %1$s%2$s অ্যাপ থিম রিকমেন্ডেশনগুলো দেখাও প্লেয়ারে গতির বিকল্প যোগ কর @@ -351,5 +350,4 @@ অ্যাকাউন্ট প্রস্থান %1$d%2$s - hide_player_control_names_key diff --git a/app/src/main/res/values-b+cs/strings.xml b/app/src/main/res/values-b+cs/strings.xml index c42cb4c18..e553ccd73 100644 --- a/app/src/main/res/values-b+cs/strings.xml +++ b/app/src/main/res/values-b+cs/strings.xml @@ -253,7 +253,6 @@ Roztáhnout Přiblížit Odmítnutí odpovědnosti - Jakékoli právní otázky týkající se obsahu této aplikace je třeba řešit se samotnými hostiteli a poskytovateli souborů, protože s nimi nejsme nijak spojeni. V případě porušení autorských práv se obraťte přímo na odpovědné strany nebo na webové stránky, na kterých se streamování odehrává. Aplikace je určena výhradně pro vzdělávací a osobní účely. CloudStream 3 v aplikaci nehostuje žádný obsah a nemá žádnou kontrolu nad tím, jaká média jsou v aplikaci umístěna nebo odstraněna. CloudStream 3 funguje jako jakýkoli jiný vyhledávač, například Google. Služba CloudStream 3 nehostuje, nenahrává ani nespravuje žádná videa, filmy ani obsah. Pouze vyhledává, agreguje a zobrazuje odkazy v pohodlném, uživatelsky přívětivém rozhraní. Pouze shromažďuje webové stránky třetích stran, které jsou veřejně přístupné prostřednictvím jakéhokoli běžného webového prohlížeče. Je odpovědností uživatele, aby se vyvaroval jakýchkoli akcí, které by mohly porušovat zákony platné v jeho lokalitě. Použijte CloudStream 3 na vlastní nebezpečí. Obecné Náhodné tlačítko Zobrazit na domovské stránce a v knihovně náhodné tlačítko @@ -275,14 +274,6 @@ Uživatelské jméno ahoj@svete.cz 127.0.0.1 - %1$s %2$s účet Odhlásit se @@ -594,8 +585,6 @@ Upravit účet Odkazy znovu načteny Zobrazit tlačítko pro přepnutí otočení obrazovky - rotate_video_key - auto_rotate_video_key Automatické otáčení Otočení Zapnout automatické otáčení obrazovky v závislosti na orientaci videa @@ -644,7 +633,6 @@ Účty Lokální ověření PIN kód vypršel! - hide_player_control_names_key Přehrát od začátku Aktuálně neprobíhají žádná stahování. Otevřít místní video diff --git a/app/src/main/res/values-b+de/strings.xml b/app/src/main/res/values-b+de/strings.xml index eb5734ca1..67cf55fd7 100644 --- a/app/src/main/res/values-b+de/strings.xml +++ b/app/src/main/res/values-b+de/strings.xml @@ -602,7 +602,6 @@ Zurücksetzen Akkuverbrauch der App ist bereits auf unbeschränkt eingestellt CloudStreams App-Info kann nicht geöffnet werden. - hide_player_control_names_key Staffel %1$d Episode %2$d wird veröffentlicht in Wird veröffentlicht in %s Sicherheit diff --git a/app/src/main/res/values-b+el/strings.xml b/app/src/main/res/values-b+el/strings.xml index 96da7f206..4b671644b 100644 --- a/app/src/main/res/values-b+el/strings.xml +++ b/app/src/main/res/values-b+el/strings.xml @@ -611,7 +611,6 @@ Τα δεδομένα σας στο CloudStream έχουν κάνει back up. Αν και η πιθανότητα είναι πολύ χαμηλή, όλες οι συσκευές συμπεριφέρονται διαφορετικά. Στη σπάνια περίπτωση, που απαγορευτεί η πρόσβασή σας από την εφαρμογή, διαγράψτε τα δεδομένα εφαρμογής και επαναφέρετέ τα από ένα ήδη υπάρχον backup. Συγνώμη για οποιαδήποτε ταλαιπωρία. Λογαριασμοί Ασφάλεια - hide_player_control_names_key Απόρριψη Ενσωματωμένο Συνδεμένοι diff --git a/app/src/main/res/values-b+eo/strings.xml b/app/src/main/res/values-b+eo/strings.xml index e3e428075..f957da076 100644 --- a/app/src/main/res/values-b+eo/strings.xml +++ b/app/src/main/res/values-b+eo/strings.xml @@ -127,5 +127,4 @@ Elŝutite Elŝutante Elŝuto Malsukcesite - hide_player_control_names_key diff --git a/app/src/main/res/values-b+es/array.xml b/app/src/main/res/values-b+es/array.xml index 376519bf3..1a7ca4608 100644 --- a/app/src/main/res/values-b+es/array.xml +++ b/app/src/main/res/values-b+es/array.xml @@ -290,48 +290,4 @@ Vietnamita (VISCII) Vietnamita (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+es/strings.xml b/app/src/main/res/values-b+es/strings.xml index 8cd37933b..390c2df58 100644 --- a/app/src/main/res/values-b+es/strings.xml +++ b/app/src/main/res/values-b+es/strings.xml @@ -567,8 +567,6 @@ Editar la cuenta Enlaces recargados Mostrar un botón para cambiar la orientación de la pantalla - rotate_video_key - auto_rotate_video_key Giro automático Girar Activar el cambio automático de la orientación de la pantalla en función de la orientación del vídeo @@ -617,7 +615,6 @@ ¡El código PIN ya ha caducado! El código caduca en %1$d mín y %2$d s No puedo obtener el código PIN del dispositivo; intente con la autenticación local - hide_player_control_names_key Reproducir desde el principio Abrir vídeo de forma local Advertencia diff --git a/app/src/main/res/values-b+fa/strings.xml b/app/src/main/res/values-b+fa/strings.xml index 146236651..da6f04d8e 100644 --- a/app/src/main/res/values-b+fa/strings.xml +++ b/app/src/main/res/values-b+fa/strings.xml @@ -190,7 +190,6 @@ پیش‌فرض کارتون تورنت - hide_player_control_names_key این ارائه‌دهنده تورنتی است، استفاده از VPN توصیه می‌شود بارگزاری پرونده پشتیبانی‌ ارتفاع زیرنویس diff --git a/app/src/main/res/values-b+fil/strings.xml b/app/src/main/res/values-b+fil/strings.xml index f8ba8fa47..d4844d1d7 100644 --- a/app/src/main/res/values-b+fil/strings.xml +++ b/app/src/main/res/values-b+fil/strings.xml @@ -1,6 +1,5 @@ - hide_player_control_names_key Maling PIN. Pakisubukang muli. Alisin ang napanood hanggang sa episode na ito Walang koneksyon sa internet.\n\nKumonekta sa internet at subukang muli, o panoorin ang iyong mga na-download habang ikaw ay offline. diff --git a/app/src/main/res/values-b+fr/strings.xml b/app/src/main/res/values-b+fr/strings.xml index d8ecc4dae..a4e669ccf 100644 --- a/app/src/main/res/values-b+fr/strings.xml +++ b/app/src/main/res/values-b+fr/strings.xml @@ -606,7 +606,6 @@ Verrouillage biométrique Sélectionnez un appareil de diffusion Saison %1$d Episode %2$d sera publié dans - hide_player_control_names_key Regarder depuis le début Ouvrir une vidéo locale Attention diff --git a/app/src/main/res/values-b+gl/strings.xml b/app/src/main/res/values-b+gl/strings.xml index aeb76080e..1b8f068e3 100644 --- a/app/src/main/res/values-b+gl/strings.xml +++ b/app/src/main/res/values-b+gl/strings.xml @@ -161,7 +161,6 @@ Selecciona o modo para filtrar a descarga dos complementos Instala automáticamente todos os complementos aínda non instalados dos repositorios engadidos. Mostrar actualizacións da aplicación - hide_player_control_names_key Reiniciar aos valores predefinidos -30 Audiolibro @@ -274,10 +273,6 @@ Ligazón copiada ó portapapeis Reproducir capítulo Temporada - - Capítulos - - %1$d-%2$d %1$d %2$s T diff --git a/app/src/main/res/values-b+hi/strings.xml b/app/src/main/res/values-b+hi/strings.xml index 8b7c79650..2c5247238 100644 --- a/app/src/main/res/values-b+hi/strings.xml +++ b/app/src/main/res/values-b+hi/strings.xml @@ -202,7 +202,6 @@ रूपरेखा रंग उपशीर्षक ऊंचाई मुद्रलिपि - hide_player_control_names_key वर्तमान में कोई डाउनलोड नहीं है। आरम्भ से शुरू करें मिटाने के लिए वस्तु चुनें @@ -312,10 +311,6 @@ लाइब्रेरी मीडिया खोज परिणामों में चयनित वीडियो गुणवत्ता छुपाएं - - प्रकरण - - %1$d-%2$d @string/home_play प्लेयर में आगे पीछे जाने का समय (सेकंड्स) diff --git a/app/src/main/res/values-b+hr/strings.xml b/app/src/main/res/values-b+hr/strings.xml index 902a75500..1e2c344ab 100644 --- a/app/src/main/res/values-b+hr/strings.xml +++ b/app/src/main/res/values-b+hr/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s epizoda %2$d Glumačka postava: %s Epizoda %d će izaći za @@ -285,7 +276,6 @@ Rastegni Zoom Pravna obavijest - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Općenito Gumb za slučajni odabir Prikaži gumb za slučajni odabir na početnoj stranici i biblioteci @@ -597,8 +587,6 @@ Prikaži gumb za prebacivanje orijentacije zaslona Omogućuje automatsko mijenjanje orijentacije zaslona na temelju orijentacije videa Automatsko rotiranje - rotiraj_video_tipka - automatski_rotiraj_video_tipka Obavijest za novu epizodu Traži u drugim proširenjima Dodaje opciju za brzinu u playeru @@ -636,7 +624,6 @@ CloudStream Wiki Računi Sigurnost - hide_player_control_names_key Lokalna autentifikacija Otvori lokalni video Posjeti %s na svom mobitelu ili računalu i unesi gore navedeni kod diff --git a/app/src/main/res/values-b+hu/strings.xml b/app/src/main/res/values-b+hu/strings.xml index 1e97719c0..ae018207b 100644 --- a/app/src/main/res/values-b+hu/strings.xml +++ b/app/src/main/res/values-b+hu/strings.xml @@ -579,7 +579,6 @@ A PIN 4 karakter hosszú kell legyen Auto elforgatás Az automatikus videó orientáció alapján való képernyő elforgatás bekapcsolása - hide_player_control_names_key Helyi videó megnyitása Tárhely név és URL Ez a videó egy Torrent, ami azt jelenti, hogy a videótevékenységed nyomon követhető.\nGyőződj meg róla, hogy megérted a torrentezés működését, mielőtt folytatnád. diff --git a/app/src/main/res/values-b+in/strings.xml b/app/src/main/res/values-b+in/strings.xml index ca3a39906..17b4f075d 100644 --- a/app/src/main/res/values-b+in/strings.xml +++ b/app/src/main/res/values-b+in/strings.xml @@ -250,7 +250,6 @@ Regang Zoom Disclaimer - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Umum Tombol Acak Tampilkan tombol acak di Beranda dan Pustaka @@ -267,14 +266,6 @@ Lokasi judul poster Meletakkan judul di bawah poster - %1$s %2$s akun Keluar @@ -632,7 +623,6 @@ CloudStream Wiki Keamanan Akun - hide_player_control_names_key Hati hati Kode PIN kini telah kedaluwarsa! Gambar Kode QR diff --git a/app/src/main/res/values-b+it/strings.xml b/app/src/main/res/values-b+it/strings.xml index fee5b56f3..3962f5d23 100644 --- a/app/src/main/res/values-b+it/strings.xml +++ b/app/src/main/res/values-b+it/strings.xml @@ -640,7 +640,6 @@ Impossibile ottenere il codice PIN del dispositivo, prova l\'autenticazione locale Il codice PIN è scaduto! Il codice scadrà tra %1$dm %2$ds - hide_player_control_names_key Apri il video locale Al momento non ci sono download. Riproduci dall\'inizio diff --git a/app/src/main/res/values-b+iw/strings.xml b/app/src/main/res/values-b+iw/strings.xml index 558d98a4b..ef4cb9202 100644 --- a/app/src/main/res/values-b+iw/strings.xml +++ b/app/src/main/res/values-b+iw/strings.xml @@ -537,7 +537,6 @@ \nיגרמו לעדיפות הסרטון להיות 10. \n \nשימו לב: אם הסכום הוא 10 או יותר, הנגן ידלג על טעינת הסרטון כאשר הלינק נטען! - hide_player_control_names_key עונה %1$d פרק %2$d תשודר ב: %1$d שעות %2$d דקות %3$d שניות %1$d דקות %2$d שניות diff --git a/app/src/main/res/values-b+ja/strings.xml b/app/src/main/res/values-b+ja/strings.xml index df0559ef7..e246c6f27 100644 --- a/app/src/main/res/values-b+ja/strings.xml +++ b/app/src/main/res/values-b+ja/strings.xml @@ -234,7 +234,6 @@ 現在のエピソードが終了したら次のエピソードを開始する 長押しするとデフォルトにリセットされます ダウンロードを再開 - hide_player_control_names_key ブックマークのフィルタ プロットが見つかりません ダブルタップで一時停止 diff --git a/app/src/main/res/values-b+kn/strings.xml b/app/src/main/res/values-b+kn/strings.xml index 3a77aeef0..22a45b906 100644 --- a/app/src/main/res/values-b+kn/strings.xml +++ b/app/src/main/res/values-b+kn/strings.xml @@ -129,5 +129,4 @@ Brightness ಅಥವಾ volume ಬದಲಾಯಿಸಲು ಎಡ ಅಥವಾ ಬಲಭಾಗದಲ್ಲಿ ಮೇಲಕ್ಕೆ ಅಥವಾ ಕೆಳಕ್ಕೆ ಸ್ಲೈಡ್ ಮಾಡಿ ಈಗಿನ ಎಪಿಸೋಡ್ ಮುಗಿದಾಗ ಮುಂದಿನ ಎಪಿಸೋಡ್ ಅನ್ನು ಪ್ರಾರಂಭಿಸಿ ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ಬದಲಾಯಿಸಲು ಸ್ವೈಪ್ ಮಾಡಿ - hide_player_control_names_key diff --git a/app/src/main/res/values-b+ko/strings.xml b/app/src/main/res/values-b+ko/strings.xml index 7ab550913..af84eb3a9 100644 --- a/app/src/main/res/values-b+ko/strings.xml +++ b/app/src/main/res/values-b+ko/strings.xml @@ -620,7 +620,6 @@ %s의 PIN 입력 즐겨찾기에서 제거 캐스트미러 - hide_player_control_names_key 플러그인 삭제 경고 탐색바 미리보기 diff --git a/app/src/main/res/values-b+lt/strings.xml b/app/src/main/res/values-b+lt/strings.xml index 357192018..cb2d816f3 100644 --- a/app/src/main/res/values-b+lt/strings.xml +++ b/app/src/main/res/values-b+lt/strings.xml @@ -254,5 +254,4 @@ Ar tikrai norite išeiti? Pašalinti iš žiūrimų Garso takelis - hide_player_control_names_key diff --git a/app/src/main/res/values-b+lv/strings.xml b/app/src/main/res/values-b+lv/strings.xml index 444a59a5f..b87e9e4fe 100644 --- a/app/src/main/res/values-b+lv/strings.xml +++ b/app/src/main/res/values-b+lv/strings.xml @@ -509,7 +509,6 @@ Abonēto šovu atjaunināšana Abonēts Abonēts %s - hide_player_control_names_key %1$d. sezona un %2$d. sērija tiks izlaista pēc %1$dh %2$dm %3$ds %1$dm %2$ds diff --git a/app/src/main/res/values-b+mk/strings.xml b/app/src/main/res/values-b+mk/strings.xml index bccc2a00d..6998c49db 100644 --- a/app/src/main/res/values-b+mk/strings.xml +++ b/app/src/main/res/values-b+mk/strings.xml @@ -593,7 +593,6 @@ Грешка при пристапот до таблата со исечоци, обиди се повторно. Грешка при копирање, молам копирај го логот и контактирај ја поддршката на апликацијата. Аудио книга - hide_player_control_names_key Безбедност Отфрли Отвори извор diff --git a/app/src/main/res/values-b+ml/strings.xml b/app/src/main/res/values-b+ml/strings.xml index dcb9e5270..d1c9409a3 100644 --- a/app/src/main/res/values-b+ml/strings.xml +++ b/app/src/main/res/values-b+ml/strings.xml @@ -272,5 +272,4 @@ എഡ്ജ് തരം ഔട്ട്ലൈൻ നിറം പശ്ചാത്തല നിറം - hide_player_control_names_key diff --git a/app/src/main/res/values-b+ms/strings.xml b/app/src/main/res/values-b+ms/strings.xml index 8bbb2a7e0..83492a5ff 100644 --- a/app/src/main/res/values-b+ms/strings.xml +++ b/app/src/main/res/values-b+ms/strings.xml @@ -54,7 +54,6 @@ Kongsi Tetapan Tutup - hide_player_control_names_key Pratonton Resensi:%.1f Kemas kini baru dijumpai! @@ -516,9 +515,6 @@ Sandarkan data Gagal pulihkan data dari fail %s Ralat sandaran %s - - Episode - Akan datang pada %s Ini akan memadamkan secara kekal %s\nAdakah anda pasti? Adakah anda pasti mahu memadamkan item berikut secara kekal?\n\n%s diff --git a/app/src/main/res/values-b+mt/strings.xml b/app/src/main/res/values-b+mt/strings.xml index ca62a043b..ea859ee29 100644 --- a/app/src/main/res/values-b+mt/strings.xml +++ b/app/src/main/res/values-b+mt/strings.xml @@ -122,5 +122,4 @@ Bookmarks Neħħi Falla t-tniżżil - hide_player_control_names_key diff --git a/app/src/main/res/values-b+my/strings.xml b/app/src/main/res/values-b+my/strings.xml index 1d35cbaa0..4a7a50aa7 100644 --- a/app/src/main/res/values-b+my/strings.xml +++ b/app/src/main/res/values-b+my/strings.xml @@ -537,5 +537,4 @@ သင်နဂိုတည်းကသတ်မှတ်ပြီး လိုက်ဘရီရွေးချယ်ရန် ဖြင့်ဖွင့်မည် - hide_player_control_names_key diff --git a/app/src/main/res/values-b+ne/strings.xml b/app/src/main/res/values-b+ne/strings.xml index 9345cab2b..8a432a505 100644 --- a/app/src/main/res/values-b+ne/strings.xml +++ b/app/src/main/res/values-b+ne/strings.xml @@ -127,5 +127,4 @@ प्लेयरको उपशीर्षकको सेटिङ रिपोजिटरी को नाम र यूआरएल कपी गरियो! - hide_player_control_names_key diff --git a/app/src/main/res/values-b+nl/strings.xml b/app/src/main/res/values-b+nl/strings.xml index 6e0982c79..54508e652 100644 --- a/app/src/main/res/values-b+nl/strings.xml +++ b/app/src/main/res/values-b+nl/strings.xml @@ -292,14 +292,6 @@ MyCoolSite voorbeeld.com Taalcode (nl) - %1$s %2$s account Log uit @@ -594,7 +586,6 @@ Link opnieuw geladen Autoroteer Roteer - hide_player_control_names_key Er zijn momenteel geen downloads beschikbaar. Gekopieerd! Verbergen diff --git a/app/src/main/res/values-b+nn/strings.xml b/app/src/main/res/values-b+nn/strings.xml index 2cf83c183..245bf6618 100644 --- a/app/src/main/res/values-b+nn/strings.xml +++ b/app/src/main/res/values-b+nn/strings.xml @@ -191,5 +191,4 @@ Bilde i bilde Fortsett å sjå Prøv tilkopling på nytt… - hide_player_control_names_key diff --git a/app/src/main/res/values-b+no/strings.xml b/app/src/main/res/values-b+no/strings.xml index a981609cf..374b033c6 100644 --- a/app/src/main/res/values-b+no/strings.xml +++ b/app/src/main/res/values-b+no/strings.xml @@ -192,8 +192,6 @@ Primær Farge App Tema Foretrukket Videoinnhold - Disclaimer - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Besetning: %s %dm Tøm @@ -525,5 +523,4 @@ Bruk Hjelp Profilbakgrunn - hide_player_control_names_key diff --git a/app/src/main/res/values-b+or/strings.xml b/app/src/main/res/values-b+or/strings.xml index 807a3bcc6..8c9379f5b 100644 --- a/app/src/main/res/values-b+or/strings.xml +++ b/app/src/main/res/values-b+or/strings.xml @@ -155,7 +155,6 @@ କୌଣସି ତଥ୍ୟ ନାହିଁ %1$s ଅ %2$d ଆଦ୍ୟ ବାଦ୍ ଦିଅ - hide_player_control_names_key ଏପିସୋଡ୍ %d ମୁକ୍ତିଲାଭ କରିବ ସିଜିନ୍ %1$d ଏପିସୋଡ୍ %2$d ମୁକ୍ତିଲାଭ କରିବ %1$dଘଣ୍ଟା %2$dମିନିଟ୍ %3$dସେକେଣ୍ଡ diff --git a/app/src/main/res/values-b+pl/array.xml b/app/src/main/res/values-b+pl/array.xml index 7b1683b41..466066852 100644 --- a/app/src/main/res/values-b+pl/array.xml +++ b/app/src/main/res/values-b+pl/array.xml @@ -299,48 +299,4 @@ Vietnamese (VISCII) Vietnamese (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+pl/strings.xml b/app/src/main/res/values-b+pl/strings.xml index e067b391c..59ac0db4b 100644 --- a/app/src/main/res/values-b+pl/strings.xml +++ b/app/src/main/res/values-b+pl/strings.xml @@ -621,7 +621,6 @@ Odrzuć Otwórz repozytorium Odwiedź %s na swoim smartfonie lub komputerze i wprowadź powyższy kod - hide_player_control_names_key Odtwarzaj od początku Usuń wtyczkę Uwaga diff --git a/app/src/main/res/values-b+pt+BR/strings.xml b/app/src/main/res/values-b+pt+BR/strings.xml index 3fbc4fb28..e74a7db74 100644 --- a/app/src/main/res/values-b+pt+BR/strings.xml +++ b/app/src/main/res/values-b+pt+BR/strings.xml @@ -266,7 +266,6 @@ Esticar Zoom Aviso Legal - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Geral Botão Aleatório Mostrar botão aleatório na página inicial e na biblioteca @@ -291,14 +290,6 @@ NovoNomedoSite https://example.com Codigo da Língua (bp) - %1$s %2$s Conta Sair @@ -640,7 +631,6 @@ Não é possível obter o código PIN do dispositivo, tente a autenticação local O código PIN expirou! O código expira em %1$dm %2$ds - hide_player_control_names_key Reproduzir do começo Reprovou alguns testes Excluir plugin diff --git a/app/src/main/res/values-b+pt/strings.xml b/app/src/main/res/values-b+pt/strings.xml index 23b49195f..e7b3623e6 100644 --- a/app/src/main/res/values-b+pt/strings.xml +++ b/app/src/main/res/values-b+pt/strings.xml @@ -606,7 +606,6 @@ Temporada %1$d Episódio %2$d será lançado em Escolha o dispositivo Transmitir - hide_player_control_names_key Abrir vídeo local Não é possível receber o PIN do dispositivo, tentando autenticação local Pré-visualização na barra de progresso diff --git a/app/src/main/res/values-b+qt/strings.xml b/app/src/main/res/values-b+qt/strings.xml index 3de0f32df..d60a4e32c 100644 --- a/app/src/main/res/values-b+qt/strings.xml +++ b/app/src/main/res/values-b+qt/strings.xml @@ -239,7 +239,6 @@ oooooh uuaagh @string/home_play oouuhhh ahhooo-ahah - hide_player_control_names_key uuuugg aaaahh oogg aagg uuuuggg og %1$dm %2$ds aaaahhh ag diff --git a/app/src/main/res/values-b+ro/strings.xml b/app/src/main/res/values-b+ro/strings.xml index 642eea0c3..e2dbd0e32 100644 --- a/app/src/main/res/values-b+ro/strings.xml +++ b/app/src/main/res/values-b+ro/strings.xml @@ -261,7 +261,6 @@ Întindere Mărire Aviz juridic (declinarea responsabilității și drepturi de autor) - Orice probleme legale privind conținutul acestei aplicații ar trebui să fie rezolvate cu furnizorii și gazdele actuale de fișiere, întrucât noi nu suntem afiliați cu aceștia. În caz de încălcare a drepturilor de autor, vă rugăm să contactați direct părțile responsabile sau site-urile de streaming. Aplicația este destinată exclusiv utilizării educaționale și personale. CloudStream 3 nu găzduiește niciun fel de conținut în aplicație și nu are niciun control asupra conținutului media care este pus sau retras. CloudStream 3 funcționează ca orice alt motor de căutare, cum ar fi Google. CloudStream 3 nu găzduiește, nu încarcă și nu gestionează niciun videoclip, film sau conținut. Pur și simplu navighează, adună și afișează linkuri într-o interfață convenabilă și ușor de utilizat. Pur și simplu, acesta extrage paginile web ale unor terțe părți care sunt accesibile publicului prin intermediul oricărui browser web obișnuit. Este responsabilitatea utilizatorului de a evita orice acțiune care ar putea încălca legile care guvernează locația sa. Utilizați CloudStream 3 pe propria răspundere. General Aleatoriu Afișează butonul pentru aleatoriu pe Pagina Principală și în Bibliotecă @@ -627,7 +626,6 @@ Sezonul %1$d Episod %2$d va fi lansat în Selectați divece-ul pe care doriți să faceți cast Cast mirror - hide_player_control_names_key Redă de la început Nu există descărcări. Selectionati elementele de sters diff --git a/app/src/main/res/values-b+ru/strings.xml b/app/src/main/res/values-b+ru/strings.xml index d25f7e274..d8b369a3d 100644 --- a/app/src/main/res/values-b+ru/strings.xml +++ b/app/src/main/res/values-b+ru/strings.xml @@ -555,12 +555,10 @@ Добавить в любимое Включить автоматическую смену ориентации экрана на основе ориентации видео Автоповорот - rotate_video_key Использовать учётную запись по умолчанию Отписаться Заменить Введите текущий ПИН-код - auto_rotate_video_key Любимые %s добавлено в любимые Введите ПИН-код от %s @@ -600,7 +598,6 @@ Сезон %1$d серия %2$d выйдет Выйдет %s Выберите устройство для трансляции - hide_player_control_names_key В данный момент скачиваний нет. Играть с самого начала Открыть локальное видео diff --git a/app/src/main/res/values-b+sk/strings.xml b/app/src/main/res/values-b+sk/strings.xml index fb65841f2..93505971c 100644 --- a/app/src/main/res/values-b+sk/strings.xml +++ b/app/src/main/res/values-b+sk/strings.xml @@ -369,7 +369,6 @@ Pridať repozitár Názov repozitára Zobraziť komunitné repozitáre - hide_player_control_names_key HD Prehrávač Rozlíšenie a titul diff --git a/app/src/main/res/values-b+so/strings.xml b/app/src/main/res/values-b+so/strings.xml index fc42c63f7..09499af00 100644 --- a/app/src/main/res/values-b+so/strings.xml +++ b/app/src/main/res/values-b+so/strings.xml @@ -472,5 +472,4 @@ Bilowga Bilow isku qasan Qoraalka dhamaadka - hide_player_control_names_key diff --git a/app/src/main/res/values-b+sv/strings.xml b/app/src/main/res/values-b+sv/strings.xml index dfbfce4b5..75c7efda4 100644 --- a/app/src/main/res/values-b+sv/strings.xml +++ b/app/src/main/res/values-b+sv/strings.xml @@ -465,7 +465,6 @@ Redigera konto Loggat in som %s Hoppa över val av konto vid start - auto_rotera_video_nyckel Gå förbi blockering av rå GitHub-URL:er med jsDelivr. Kan göra att uppdateringar försenas med några dagar. Funktion Önskad media @@ -557,7 +556,6 @@ %s togs bort från favoriter %s har lagts till i favoriter Använd standard konto - rotera_video_nyckel PIN-kod Sök mängden som används när spelaren är dold Det verkar som om ett potentiellt duplicerat objekt redan finns i ditt bibliotek: @@ -612,7 +610,6 @@ CloudStream Wiki Konton Säkerhet - hide_player_control_names_key Avfärda Öppna databasen Koden löper ut om %1$dm %2$ds diff --git a/app/src/main/res/values-b+ta/strings.xml b/app/src/main/res/values-b+ta/strings.xml index 94b6f717a..e223f6c60 100644 --- a/app/src/main/res/values-b+ta/strings.xml +++ b/app/src/main/res/values-b+ta/strings.xml @@ -576,7 +576,6 @@ ஆதாரங்கள் எவ்வாறு உத்தரவிடப்படுகின்றன என்பதை இங்கே மாற்றலாம். ஒரு வீடியோவுக்கு அதிக முன்னுரிமை இருந்தால், அது மூல தேர்வில் அதிகமாகத் தோன்றும். மூல முன்னுரிமையின் தொகை மற்றும் தரமான முன்னுரிமை ஆகியவை வீடியோ முன்னுரிமை. \n\n சான்று A: 3 \n தகுதி பி: 7 \n 10 இன் ஒருங்கிணைந்த வீடியோ முன்னுரிமை இருக்கும். \n\n குறிப்பு: தொகை 10 அல்லது அதற்கு மேற்பட்டதாக இருந்தால், அந்த இணைப்பு ஏற்றப்படும்போது பிளேயர் தானாகவே ஏற்றுவதைத் தவிர்க்கும்! உங்கள் கிளவுட்ச்ட்ரீம் தரவு இப்போது காப்புப் பிரதி எடுக்கப்பட்டுள்ளது. இதன் சாத்தியம் மிகக் குறைவு என்றாலும், எல்லா சாதனங்களும் வித்தியாசமாக நடந்து கொள்ளலாம். அரிய விசயத்தில், பயன்பாட்டை அணுகுவதிலிருந்து நீங்கள் பூட்டப்படுகிறீர்கள், பயன்பாட்டு தரவை முழுவதுமாக அழித்து, காப்புப்பிரதியிலிருந்து மீட்டெடுக்கவும். இதிலிருந்து எழும் ஏதேனும் சிரமத்திற்கு நாங்கள் மிகவும் வருந்துகிறோம். ஊடகம் - hide_player_control_names_key கணக்குகள் எச்சரிக்கை தற்போது பதிவிறக்கங்கள் எதுவும் இல்லை. diff --git a/app/src/main/res/values-b+ti/strings.xml b/app/src/main/res/values-b+ti/strings.xml index 6c154c8d8..46235bbd7 100644 --- a/app/src/main/res/values-b+ti/strings.xml +++ b/app/src/main/res/values-b+ti/strings.xml @@ -3,5 +3,4 @@ %1$s ክፋል %2$d ክፋል %d በ ላይ ይወጣል ተዋሳእቲ፡ %s - hide_player_control_names_key diff --git a/app/src/main/res/values-b+tl/strings.xml b/app/src/main/res/values-b+tl/strings.xml index 94bb8ea1d..4050ddbd7 100644 --- a/app/src/main/res/values-b+tl/strings.xml +++ b/app/src/main/res/values-b+tl/strings.xml @@ -257,5 +257,4 @@ Mga Subtitle ng Chromecast Mga setting ng mga subtitle ng Chromecast Maglaro ng Trailer - hide_player_control_names_key diff --git a/app/src/main/res/values-b+tr/array.xml b/app/src/main/res/values-b+tr/array.xml index 56cea4c9c..dbc2d3e66 100644 --- a/app/src/main/res/values-b+tr/array.xml +++ b/app/src/main/res/values-b+tr/array.xml @@ -319,48 +319,4 @@ Vietnamese (VISCII) Vietnamese (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+tr/strings.xml b/app/src/main/res/values-b+tr/strings.xml index f6a125b91..10137e7b5 100644 --- a/app/src/main/res/values-b+tr/strings.xml +++ b/app/src/main/res/values-b+tr/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s B. %2$d Cast: %s Bölüm %d şu tarihte yayınlanacak @@ -22,9 +13,7 @@ Bölüm Afişi Ana Afiş Sonraki Rastgele - @string/play_episode Geri git - @string/home_change_provider_img_des Sağlayıcıyı Değiştir Arkaplanı Önizle @@ -45,7 +34,6 @@ Veri Yok Daha Fazla Seçenek Sonraki bölüm - @string/synopsis Türler Paylaş Tarayıcıda aç @@ -74,7 +62,6 @@ İndirme Başarısız İndirme İptal Edildi İndirme Tamamlandı - %s - %s Ağ akışı Bağlantılar yüklenirken hata oluştu Dahili Depolama @@ -258,10 +245,6 @@ Dublaj etiketi Altyazı etiketi Başlık - show_hd_key - show_dub_key - show_sub_key - show_title_key Afiş üzerindeki öğeleri değiştir Güncelleme bulunamadı Güncellemeleri denetle @@ -293,8 +276,6 @@ Uzat Yakınlaştır Yasal uyarı - legal_notice_key - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. Genel Rastgele düğmesi Ana sayfa ve kütüphane üstünde rastgele düğmesini göster @@ -314,10 +295,6 @@ Afiş başlık konumu Başlığı afişin altına yerleştir - anilist_key - mal_key - opensubtitles_key - nginx_key şifre123 Kullanıcı Adı hello@world.com @@ -325,14 +302,6 @@ YeniSiteAdı https://ornek.com Dil kodu (tr) - %1$s %2$s hesap Çıkış yap @@ -355,7 +324,6 @@ Tümü Azami Asgari - @string/none Dış hat Çökmüş Gölge @@ -614,8 +582,6 @@ PIN Geçerli PIN\'i Giriniz Ekran yönü için bir geçiş düğmesi göster - rotate_video_key - auto_rotate_video_key Otomatik döndür Döndür Video yönüne göre ekran yönünün otomatik olarak değişmesini sağla @@ -664,7 +630,6 @@ Cihaz PIN kodu alınamıyor, yerel kimlik doğrulamayı deneyin PIN kodunun süresi doldu! Kodun süresi %1$dm %2$ds içinde doluyor - hide_player_control_names_key Şu an hiç bir indirme bulunmamaktadır. Eklentiyi sil Yerel videoyu aç diff --git a/app/src/main/res/values-b+uk/strings.xml b/app/src/main/res/values-b+uk/strings.xml index 54562f263..20bd9e6e2 100644 --- a/app/src/main/res/values-b+uk/strings.xml +++ b/app/src/main/res/values-b+uk/strings.xml @@ -550,10 +550,8 @@ Керувати обліковими записами Редагувати обліковий запис Показувати кнопку перемикання орієнтації екрана - rotate_video_key Обернути Покликання перезавантажено - auto_rotate_video_key Автообертання Увімкнути автоматичну зміну орієнтації екрана відповідно до відео Додати налаштування швидкості до програвача @@ -600,7 +598,6 @@ Термін дії коду закінчується через %1$dхв %2$dс Локальна автентифікація Відхилити - hide_player_control_names_key Відтворити з початку Попередження Видалити розширення diff --git a/app/src/main/res/values-b+ur/strings.xml b/app/src/main/res/values-b+ur/strings.xml index d2c3d9f1c..5f6d8aa14 100644 --- a/app/src/main/res/values-b+ur/strings.xml +++ b/app/src/main/res/values-b+ur/strings.xml @@ -604,7 +604,6 @@ دیگر ایکسٹینشنز میں تلاش کریں سفارشات دکھائیں آپ کے CloudStream ڈیٹا کا اب بیک اپ لیا گیا ہے۔ اگرچہ اس کا امکان بہت کم ہے، لیکن مختلف ڈیوائس مختلف طریقے سے کام کر سکتے ہیں۔ اگر آپ ایپ تک رسائی حاصل کرنے سے قاصر ہیں تو، ایپ کا ڈیٹا مکمل طور پر صاف کریں اور بیک اپ سے بحال کریں۔ اس سے ہونے والی کسی بھی تکلیف کے لیے ہم بہت معذرت خواہ ہیں۔ - hide_player_control_names_key سیزن %1$d کی قسط %2$d جاری ہوگی شروع سےپلے کریں کلام شناسی دستیاب نہیں diff --git a/app/src/main/res/values-b+vi/array.xml b/app/src/main/res/values-b+vi/array.xml index d1887505e..16d45e516 100644 --- a/app/src/main/res/values-b+vi/array.xml +++ b/app/src/main/res/values-b+vi/array.xml @@ -291,48 +291,4 @@ Vietnamese (VISCII) Vietnamese (Windows-1258) - - - UTF-8 - UTF-16 - UTF-16BE - UTF-16LE - GB18030 - ISO-8859-15 - Windows-1252 - IBM850 - ISO-8859-2 - Windows-1250 - ISO-8859-3 - ISO-8859-10 - Windows-1251 - KOI8-R - KOI8-U - ISO-8859-6 - Windows-1256 - ISO-8859-7 - Windows-1253 - ISO-8859-8 - Windows-1255 - ISO-8859-9 - Windows-1254 - ISO-8859-11 - Windows-874 - ISO-8859-13 - Windows-1257 - ISO-8859-14 - ISO-8859-16 - ISO-2022-CN-EXT - EUC-CN - ISO-2022-JP-2 - EUC-JP - Shift_JIS - CP949 - ISO-2022-KR - Big5 - ISO-2022-TW - Big5-HKSCS - VISCII - Windows-1258 - diff --git a/app/src/main/res/values-b+vi/strings.xml b/app/src/main/res/values-b+vi/strings.xml index aa9caffd1..b26c715f3 100644 --- a/app/src/main/res/values-b+vi/strings.xml +++ b/app/src/main/res/values-b+vi/strings.xml @@ -630,7 +630,6 @@ Truy cập %s trên điện thoại hoặc máy tính và nhập mã bên trên Mã PIN đã hết hạn! Mã sẽ hết hạn trong %1$dm %2$ds - hide_player_control_names_key Không lấy được mã PIN, hãy thử xác thực cục bộ Hiện không có bản tải xuống nào. Xác thực cục bộ diff --git a/app/src/main/res/values-b+zh+TW/strings.xml b/app/src/main/res/values-b+zh+TW/strings.xml index 251df543c..7dc4b48f2 100644 --- a/app/src/main/res/values-b+zh+TW/strings.xml +++ b/app/src/main/res/values-b+zh+TW/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s 共 %2$d 集 演員:%s 第 %d 集即將發佈於 @@ -22,9 +13,7 @@ 劇集封面 主封面 隨機下一個 - @string/play_episode 返回 - @string/home_change_provider_img_des 更改片源 預覽背景 @@ -45,7 +34,6 @@ 無資料 更多選項 下一集 - @string/synopsis 類型 分享 在瀏覽器中打開 @@ -74,7 +62,6 @@ 下載失敗 下載取消 下載完畢 - %s - %s 網路串流 載入連結錯誤 內部儲存空間 @@ -259,10 +246,6 @@ 配音標籤 字幕標籤 標題 - show_hd_key - show_dub_key - show_sub_key - show_title_key 封面內容 未找到更新 檢查更新 @@ -294,8 +277,6 @@ 拉伸 縮放 免責聲明 - legal_notice_key - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. 通用 隨機按鈕 在主畫面與媒體庫中顯示隨機按鈕 @@ -315,10 +296,6 @@ 封面標題位置 將標題移到封面下方 - anilist_key - mal_key - opensubtitles_key - nginx_key 密碼 使用者名稱 電子郵件 @@ -326,14 +303,6 @@ 新網站名稱 https://example.com 語言代號 (zh_TW) - %1$s %2$s 帳號 登出 @@ -356,7 +325,6 @@ 全部 最大 最小 - @string/none 輪廓 凹陷 陰影 @@ -614,9 +582,7 @@ \n注意:如果加總達到 10 或更高,則載入該連結時播放器將自動跳過載入! 輸入目前的 PIN 碼 顯示切換畫面方向的按鈕 - rotate_video_key 選擇篩選外掛程式下載的模式 - auto_rotate_video_key 自動旋轉 旋轉 根據影片方向自動切換畫面方向 @@ -656,7 +622,6 @@ 為了確保下載與通知已訂閱的電視節目的不間斷,CloudStream 需要取得在背景執行的權限。若點選「確定」,將移至「應用程式資訊」,請找到「應用程式電池使用」並將電池用量設置為「無限制」。請注意,取得此權限並不表示 CS3 會明顯增加電池用量,而是只在必要時在背景執行,例如取得通知或使用官方擴充功能下載影片時。若選擇「取消」,您可以稍後在「一般設定」中調整此設定。 CloudStream Wiki 此裝置不支援生物特徵認證 - hide_player_control_names_key 無法取得裝置 PIN 碼,請嘗試本機驗證 刪除外掛程式 開啟資源庫 diff --git a/app/src/main/res/values-b+zh/strings.xml b/app/src/main/res/values-b+zh/strings.xml index 224041e49..0301a3a2d 100644 --- a/app/src/main/res/values-b+zh/strings.xml +++ b/app/src/main/res/values-b+zh/strings.xml @@ -1,15 +1,6 @@ - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %d %1$s 共 %2$d 集 演员:%s 第 %d 集将发布于 @@ -22,9 +13,7 @@ 剧集封面 主封面 随机下一个 - @string/play_episode 返回 - @string/home_change_provider_img_des 更改片源 预览背景 @@ -45,7 +34,6 @@ 无数据 更多选项 下一集 - @string/synopsis 类型 分享 在浏览器中打开 @@ -74,7 +62,6 @@ 下载失败 下载取消 下载完毕 - %s - %s 播放 加载链接错误 内部存储 @@ -260,10 +247,6 @@ 配音标签 字幕标签 标题 - show_hd_key - show_dub_key - show_sub_key - show_title_key 封面内容 未找到更新 检查更新 @@ -295,8 +278,6 @@ 拉伸 缩放 免责声明 - legal_notice_key - Any legal issues regarding the content on this application should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. In case of copyright infringement, please directly contact the responsible parties or the streaming websites. The app is purely for educational and personal use. CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, user-friendly interface. It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use CloudStream 3 at your own risk. 通用 随机按钮 在主页和库中显示随机按钮 @@ -316,10 +297,6 @@ 封面标题位置 将标题移至封面下方 - anilist_key - mal_key - opensubtitles_key - nginx_key 密码 用户名 邮箱 @@ -327,14 +304,6 @@ 网站名称 网站链接 语言代码 (zh) - %1$s %2$s 账户 注销 @@ -357,7 +326,6 @@ 全部 最大 最小 - @string/none 轮廓 凹陷 阴影 @@ -654,7 +622,6 @@ 选择投射设备 %1$d季%2$d集将在 投射镜像 - hide_player_control_names_key 目前尚无下载。 打开本地视频 安全 diff --git a/app/src/main/res/values/donottranslate-strings.xml b/app/src/main/res/values/donottranslate-strings.xml new file mode 100644 index 000000000..5f2186fae --- /dev/null +++ b/app/src/main/res/values/donottranslate-strings.xml @@ -0,0 +1,152 @@ + + + + search_providers_list + app_locale + search_type_list + auto_update + auto_update_plugins + auto_download_plugins_key2 + skip_update_key + install_prerelease_key + manual_check_update + fast_forward_button_time + benene_count + subtitle_settings_key + test_providers_key + subtitle_settings_chromecast_key + quality_pref_key + quality_pref_mobile_data_key + player_default_key + prefer_limit_title_key + prefer_limit_title_rez_key + apk_installer_key + video_buffer_size_key + video_buffer_length_key + video_buffer_clear_key + video_buffer_disk_key + use_system_brightness_key + swipe_enabled_key + playback_speed_enabled_key + player_resize_enabled_key + pip_enabled_key + double_tap_enabled_key + double_tap_pause_enabled_key + double_tap_seek_time_key2 + android_tv_interface_off_seek_key + android_tv_interface_on_seek_key + swipe_vertical_enabled_key + autoplay_next_key + display_sub_key + show_fillers_key + show_trailers_key + show_kitsu_posters_key + random_button_key + provider_lang_key + dns_key + jsdelivr_proxy_key + download_path_key + download_parallel_key + download_concurrent_key + download_path_key_visual + Cloudstream + app_layout_key + primary_color_key + restore_key + backup_key + automatic_backup_key + prefer_media_type_key_2 + app_theme_key + episode_sync_enabled_key + log_enabled_key + show_logcat_key + bottom_title_key + poster_ui_key + overscan_key + poster_size_key + subtitles_encoding_key + override_site_key + redo_setup_key + filter_sub_lang_key + pref_filter_search_quality_key + enable_nsfw_on_providers_key + skip_startup_account_select_key + enable_skip_op_from_database + rotate_video_key + auto_rotate_video_key + biometric_key + battery_optimisation + show_hd_key + show_dub_key + show_sub_key + show_rating_key + show_title_key + show_episode_text_key + hide_player_control_names_key + preview_seekbar_key + backup_path_key + backup_dir_key + confirm_exit_key + software_decoding_key2 + manual_update_plugins + legal_notice_key + speedup_key + anilist_key + simkl_key + mal_key + opensubtitles_key + subdl_key + + pref_category_security_key + pref_category_gestures_key + pref_category_android_tv_key + + tv_no_focus_tag + + + %1$d %2$s | %3$s + %1$s • %2$s + %1$s - %2$s + %1$s / %2$s + %1$s %2$s + S1E1 + +%d + -%d + %d + %d + %s/10.0 + %d + + @string/play_episode + @string/home_change_provider_img_des + @string/synopsis + + @string/none + @string/none + @string/cancel + @string/cancel + @string/action_default + @string/action_default + + Any legal issues regarding the content on this application + should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. + + In case of copyright infringement, please directly contact the responsible parties or the streaming websites. + + The app is purely for educational and personal use. + + CloudStream does not host any content on the app, and has no control over what media is put up or taken down. + CloudStream functions like any other search engine, such as Google. CloudStream does not host, upload or + manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, + user-friendly interface. + + It merely scrapes 3rd-party websites that are publicly accessible via any regular web browser. It is the + responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use + CloudStream at your own risk. + + + + @string/episode + @string/episodes + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9aa586e8a..8ad0ec423 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1,91 +1,6 @@ - - search_providers_list - app_locale - search_type_list - auto_update - auto_update_plugins - auto_download_plugins_key2 - skip_update_key - install_prerelease_key - manual_check_update - fast_forward_button_time - benene_count - subtitle_settings_key - test_providers_key - subtitle_settings_chromecast_key - quality_pref_key - quality_pref_mobile_data_key - player_default_key - prefer_limit_title_key - prefer_limit_title_rez_key - apk_installer_key - video_buffer_size_key - video_buffer_length_key - video_buffer_clear_key - video_buffer_disk_key - use_system_brightness_key - swipe_enabled_key - playback_speed_enabled_key - player_resize_enabled_key - pip_enabled_key - double_tap_enabled_key - double_tap_pause_enabled_key - double_tap_seek_time_key2 - android_tv_interface_off_seek_key - android_tv_interface_on_seek_key - swipe_vertical_enabled_key - autoplay_next_key - display_sub_key - show_fillers_key - show_trailers_key - show_kitsu_posters_key - random_button_key - provider_lang_key - dns_key - jsdelivr_proxy_key - download_path_key - download_parallel_key - download_concurrent_key - download_path_key_visual - Cloudstream - app_layout_key - primary_color_key - restore_key - backup_key - automatic_backup_key - prefer_media_type_key_2 - app_theme_key - episode_sync_enabled_key - log_enabled_key - show_logcat_key - bottom_title_key - poster_ui_key - overscan_key - poster_size_key - subtitles_encoding_key - override_site_key - redo_setup_key - filter_sub_lang_key - pref_filter_search_quality_key - enable_nsfw_on_providers_key - skip_startup_account_select_key - enable_skip_op_from_database - rotate_video_key - auto_rotate_video_key - biometric_key - %d %s | %s - %s • %s - %s / %s - %s %s - +%d - -%d - %d - %d - %s/10.0 - %d %1$s Ep %2$d Cast: %s Episode %d will be released in @@ -102,10 +17,8 @@ Episode Poster Main Poster Next Random - @string/play_episode Go back Play from the Beginning - @string/home_change_provider_img_des Change Provider Preview Background @@ -127,7 +40,6 @@ No Data More Options Next episode - @string/synopsis Genres Share Open In Browser @@ -139,7 +51,6 @@ Completed Dropped Plan to Watch - @string/none Rewatching Play Movie Play Trailer @@ -161,7 +72,6 @@ Download Failed Download Canceled Download Done - %s - %s Select Items to Delete There are currently no downloads. Available for watching offline @@ -188,7 +98,6 @@ Remove Set watch status Apply - @string/cancel Copy Close Clear @@ -342,8 +251,6 @@ queued No Subtitles Default - @string/action_default - @string/action_default Free Used App @@ -359,10 +266,6 @@ Livestreams NSFW Others - - @string/episode - @string/episodes - Movie Series @@ -403,12 +306,6 @@ Rating Label Title Episode Text - show_hd_key - show_dub_key - show_sub_key - show_rating_key - show_title_key - show_episode_text_key Toggle UI elements on poster No Update Found Check for Update @@ -448,23 +345,6 @@ Stretch Zoom Disclaimer - legal_notice_key - Any legal issues regarding the content on this application - should be taken up with the actual file hosts and providers themselves as we are not affiliated with them. - - In case of copyright infringement, please directly contact the responsible parties or the streaming websites. - - The app is purely for educational and personal use. - - CloudStream 3 does not host any content on the app, and has no control over what media is put up or taken down. - CloudStream 3 functions like any other search engine, such as Google. CloudStream 3 does not host, upload or - manage any videos, films or content. It simply crawls, aggregates and displayes links in a convenient, - user-friendly interface. - - It merely scrapes 3rd-party websites that are publicly accessable via any regular web browser. It is the - responsibility of user to avoid any actions that might violate the laws governing his/her locality. Use - CloudStream 3 at your own risk. - ISP Bypasses Links App updates @@ -473,11 +353,8 @@ Actions Cache Android TV - pref_category_android_tv_key Gestures - pref_category_gestures_key Security - pref_category_security_key Accounts Player features Subtitles @@ -507,12 +384,6 @@ Poster title location Put the title under the poster - anilist_key - simkl_key - mal_key - opensubtitles_key - subdl_key - nginx_key password123 Username hello@world.com @@ -520,14 +391,6 @@ NewSiteName https://example.com Language code (en) - %1$s %2$s account Log out @@ -552,7 +415,6 @@ All Max Min - @string/none Outline Depressed Shadow @@ -653,7 +515,7 @@ View community repositories Public list Uppercase all subtitles - Warning: CloudStream 3 does not take any responsibility for using third-party extensions and does not provide any support for them! + Warning: CloudStream does not take any responsibility for using third-party extensions and does not provide any support for them! %s (Disabled) Tracks Audio tracks @@ -707,7 +569,6 @@ TV shows, CloudStream needs permission to run in background. By pressing "OK", you\'ll be shown a request dialog. Please press \'Allow\'.\n\nPlease note, this permission does not mean CS3 will drain your battery. It will only operate in the background when necessary, such as when receiving notifications or downloading videos from official extensions. - battery_optimisation App battery usage is already set to unrestricted Unable to open CloudStream\'s App info. Downloading app update… @@ -777,7 +638,6 @@ Add Replace Replace All - @string/cancel It appears that a potentially duplicate item already exists in your library: \'%s.\' @@ -790,7 +650,6 @@ \n\nWould you like to add this item anyway, replace the existing ones, or cancel the action? - tv_no_focus_tag Enter PIN Enter PIN for %s Enter Current PIN @@ -827,27 +686,21 @@ Code expires in %1$dm %2$ds Release Date (New to Old) Release Date (Old to New) - hide_player_control_names_key Hide names of the player\'s controls - preview_seekbar_key Seekbar preview Enable preview thumbnail on seekbar No subtitles loaded yet - backup_path_key Backup folder location - backup_dir_key Custom Confirm before exiting Show dialog before exiting the app - confirm_exit_key Show Don\'t Show Edge Size Enable torrent in Settings/Providers/Preferred media Restart app and accept Stream Torrent pop-up to proceed. - software_decoding_key2 Software decoding Software decoding enables the player to play video files not supported by your device, but may cause laggy or unstable playback on high resolution. Volume has exceeded 100% @@ -855,7 +708,6 @@ Update Plugins Update plugins manually - manual_update_plugins Starting plugin update process! Successfully updated %d plugin(s)! No plugins were updated. @@ -879,7 +731,6 @@ Overscan Changes size of posters Poster size - speedup_key LongPress Speed Toggle Hold to get 2x speed Edit Profile Image @@ -903,5 +754,4 @@ Top left Top center Top right - S1E1 From 2795e9e0e2856dfd4d43177eb0be4981da1270b3 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sun, 4 Jan 2026 10:08:15 +0100 Subject: [PATCH 685/962] feat(extractors): add vidnest extractor (#2390) --- .../lagradost/cloudstream3/extractors/AsianLoad.kt | 12 +++++++----- .../com/lagradost/cloudstream3/utils/ExtractorApi.kt | 2 ++ 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt index 256681679..70e869f55 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/AsianLoad.kt @@ -4,9 +4,12 @@ import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.utils.ExtractorApi import com.lagradost.cloudstream3.utils.ExtractorLink import com.lagradost.cloudstream3.utils.M3u8Helper -import com.lagradost.cloudstream3.utils.getQualityFromName import com.lagradost.cloudstream3.utils.newExtractorLink -import java.net.URI + +class Vidnest : AsianLoad() { + override var name = "Vidnest" + override var mainUrl = "https://vidnest.io" +} open class AsianLoad : ExtractorApi() { override var name = "AsianLoad" @@ -20,7 +23,7 @@ open class AsianLoad : ExtractorApi() { sourceRegex.findAll(this.text).forEach { sourceMatch -> val extractedUrl = sourceMatch.groupValues[1] // Trusting this isn't mp4, may fuck up stuff - if (URI(extractedUrl).path.endsWith(".m3u8")) { + if (extractedUrl.contains(".m3u8")) { M3u8Helper.generateM3u8( name, extractedUrl, @@ -29,7 +32,7 @@ open class AsianLoad : ExtractorApi() { ).forEach { link -> extractedLinksList.add(link) } - } else if (extractedUrl.endsWith(".mp4")) { + } else if (extractedUrl.contains(".mp4")) { extractedLinksList.add( newExtractorLink( source = name, @@ -37,7 +40,6 @@ open class AsianLoad : ExtractorApi() { url = extractedUrl, ) { this.referer = url.replace(" ", "%20") - this.quality = getQualityFromName(sourceMatch.groupValues[2]) } ) } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index 641c91319..b9a147fc7 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -263,6 +263,7 @@ import com.lagradost.cloudstream3.extractors.Vidguardto3 import com.lagradost.cloudstream3.extractors.VidhideExtractor import com.lagradost.cloudstream3.extractors.Vidmoly import com.lagradost.cloudstream3.extractors.Vidmolyme +import com.lagradost.cloudstream3.extractors.Vidnest import com.lagradost.cloudstream3.extractors.Vido import com.lagradost.cloudstream3.extractors.Vidstreamz import com.lagradost.cloudstream3.extractors.VinovoSi @@ -1169,6 +1170,7 @@ val extractorApis: MutableList = arrayListOf( FlaswishCom(), SfastwishCom(), Playerwish(), + Vidnest(), EmturbovidExtractor(), Vtbe(), EPlayExtractor(), From dc6b9f435d988cd09c7d101eb1c043ccdb4a4e3c Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sun, 4 Jan 2026 10:09:50 +0100 Subject: [PATCH 686/962] feat(extractors): add up4stream extractor (#2389) --- .../cloudstream3/extractors/Up4Stream.kt | 60 +++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 4 ++ 2 files changed, 64 insertions(+) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt new file mode 100644 index 000000000..91150992b --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Up4Stream.kt @@ -0,0 +1,60 @@ +package com.lagradost.cloudstream3.extractors + +import com.lagradost.api.Log +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.JsUnpacker +import com.lagradost.cloudstream3.utils.Qualities +import com.lagradost.cloudstream3.utils.fixUrl +import com.lagradost.cloudstream3.utils.newExtractorLink +import kotlinx.coroutines.delay + +class Up4FunTop : Up4Stream() { + override var mainUrl: String = "https://up4fun.top" +} + +open class Up4Stream : ExtractorApi() { + override var name = "Up4Stream" + override var mainUrl = "https://up4stream.com" + override val requiresReferer = true + + override suspend fun getUrl(url: String, referer: String?): List? { + val movieId = url.substringAfterLast("/").substringBefore(".html") + + // redirect from "wait 5 seconds" page to actual movie page + val redirectResponse = app.get(url, cookies = mapOf("id" to movieId)) + val redirectForm = redirectResponse.document.selectFirst("form[method=POST]") ?: return null + val redirectUrl = fixUrl(redirectForm.attr("action")) + val redirectParams = redirectForm.select("input[type=hidden]").associate { input -> + input.attr("name") to input.attr("value") + } + + // wait for 5 seconds, otherwise the below md5 hash is invalid + delay(5000) + val response = app.post(redirectUrl, data = redirectParams).document + + // starting here, this works similar to many other extractors like StreamWish + val extractedpack = + response.selectFirst("script:containsData(function(p,a,c,k,e,d))")?.data() + if (extractedpack == null) { + Log.e("up4stream", "file not ready: delay too short") + } + + JsUnpacker(extractedpack).unpack()?.let { unPacked -> + Regex("sources:\\[\\{file:\"(.*?)\"").find(unPacked)?.groupValues?.get(1)?.let { link -> + return listOf( + newExtractorLink( + this.name, + this.name, + link, + ) { + this.referer = referer.orEmpty() + this.quality = Qualities.Unknown.value + } + ) + } + } + return null + } +} \ No newline at end of file diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt index b9a147fc7..c0d5c5da7 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -230,6 +230,8 @@ import com.lagradost.cloudstream3.extractors.Techinmind import com.lagradost.cloudstream3.extractors.Tomatomatela import com.lagradost.cloudstream3.extractors.TomatomatelalClub import com.lagradost.cloudstream3.extractors.Tubeless +import com.lagradost.cloudstream3.extractors.Up4FunTop +import com.lagradost.cloudstream3.extractors.Up4Stream import com.lagradost.cloudstream3.extractors.Upstream import com.lagradost.cloudstream3.extractors.UpstreamExtractor import com.lagradost.cloudstream3.extractors.Uqload @@ -1223,6 +1225,8 @@ val extractorApis: MutableList = arrayListOf( VkExtractor(), Bysezejataos(), ByseSX(), + Up4Stream(), + Up4FunTop() ) From 5e54552338eca91ec7b792e54dd0e5da28d17bf5 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Sun, 4 Jan 2026 15:59:04 +0530 Subject: [PATCH 687/962] remove check icon in tvtype chips (#2363) --- app/src/main/res/values/styles.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index eb5a57c03..8cf61eaea 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -102,6 +102,7 @@ @font/google_sans @string/tv_no_focus_tag 0dp + false + + +