From 693f69f25f32faf5555fd84e20d54746502cc7a1 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 21 Aug 2025 03:51:52 +0200 Subject: [PATCH 001/640] Feat: Expandable navigation rail on focus --- .../lagradost/cloudstream3/MainActivity.kt | 29 +++++++++++++++++++ app/src/main/res/layout/activity_main_tv.xml | 8 +++-- gradle/libs.versions.toml | 2 +- 3 files changed, 36 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index ed3db1493..4bf26ceed 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -1709,6 +1709,35 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa } } + val rail = binding?.navRailView + if (rail != null && isLayout(TV)) { + val focus = mutableSetOf() + for (id in arrayOf( + R.id.navigation_home, + R.id.navigation_library, + R.id.navigation_search, + R.id.navigation_downloads, + R.id.navigation_settings + )) { + rail.findViewById(id)?.onFocusChangeListener = + View.OnFocusChangeListener { v, hasFocus -> + if (hasFocus) { + focus += id + binding?.navRailView?.labelVisibilityMode = NavigationRailView.LABEL_VISIBILITY_LABELED + binding?.navRailView?.expand() + } else { + focus -= id + v.post { + if(focus.isEmpty()) { + binding?.navRailView?.labelVisibilityMode = NavigationRailView.LABEL_VISIBILITY_UNLABELED + binding?.navRailView?.collapse() + } + } + } + } + } + } + // Navigation button long click functionality to scroll to top for (view in listOf(binding?.navView, binding?.navRailView)) { view?.findViewById(R.id.navigation_home)?.setOnLongClickListener { diff --git a/app/src/main/res/layout/activity_main_tv.xml b/app/src/main/res/layout/activity_main_tv.xml index 0003b2618..e36aacbcd 100644 --- a/app/src/main/res/layout/activity_main_tv.xml +++ b/app/src/main/res/layout/activity_main_tv.xml @@ -25,14 +25,18 @@ Date: Fri, 22 Aug 2025 19:59:57 +0530 Subject: [PATCH 002/640] Minor Fix for Filemoon (#1858) --- .../lagradost/cloudstream3/extractors/Filemoon.kt | 12 +++++++++--- .../com/lagradost/cloudstream3/extractors/Filesim.kt | 5 ----- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filemoon.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filemoon.kt index 6d01c31ab..6c10a92d9 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filemoon.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filemoon.kt @@ -15,6 +15,11 @@ class FileMoon : FilemoonV2() { override var name = "FileMoon" } +class FileMoonIn : FilemoonV2() { + override var mainUrl = "https://filemoon.in" + override var name = "FileMoon" +} + class FileMoonSx : FilemoonV2() { override var mainUrl = "https://filemoon.sx" override var name = "FileMoonSx" @@ -47,8 +52,8 @@ open class FilemoonV2 : ExtractorApi() { val fallbackScriptData = initialResponse.document .selectFirst("script:containsData(function(p,a,c,k,e,d))") ?.data().orEmpty() - val unpackedScript = JsUnpacker(fallbackScriptData).unpack() + val videoUrl = unpackedScript?.let { Regex("""sources:\[\{file:"(.*?)"""").find(it)?.groupValues?.get(1) } @@ -75,6 +80,7 @@ open class FilemoonV2 : ExtractorApi() { ?.data().orEmpty() val unpackedScript = JsUnpacker(iframeScriptData).unpack() + val videoUrl = unpackedScript?.let { Regex("""sources:\[\{file:"(.*?)"""").find(it)?.groupValues?.get(1) } @@ -85,7 +91,7 @@ open class FilemoonV2 : ExtractorApi() { videoUrl, mainUrl, headers = defaultHeaders - ) + ).forEach(callback) } else { // Last-resort fallback using WebView interception val resolver = WebViewResolver( @@ -107,7 +113,7 @@ open class FilemoonV2 : ExtractorApi() { interceptedUrl, mainUrl, headers = defaultHeaders - ) + ).forEach(callback) } else { Log.d("FilemoonV2", "No video URL intercepted in WebView fallback.") } diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filesim.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filesim.kt index e4ee282a1..4c5352dd9 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filesim.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Filesim.kt @@ -23,11 +23,6 @@ class Moviesm4u : Filesim() { override val name = "Moviesm4u" } -class FileMoonIn : Filesim() { - override val mainUrl = "https://filemoon.in" - override val name = "FileMoon" -} - class StreamhideTo : Filesim() { override val mainUrl = "https://streamhide.to" override val name = "Streamhide" From e60453ba2bbd506aa8e01ca32f64c7f94c1e1b30 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Sun, 24 Aug 2025 19:08:25 +0530 Subject: [PATCH 003/640] feat:mark as watched up to this episode (#1828) --- .../cloudstream3/ui/result/EpisodeAdapter.kt | 1 + .../ui/result/ResultViewModel2.kt | 59 +++++++++++++++++-- app/src/main/res/values/strings.xml | 2 + 3 files changed, 58 insertions(+), 4 deletions(-) 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 565c4240d..c1deb9125 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 @@ -59,6 +59,7 @@ const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14 const val ACTION_MARK_AS_WATCHED = 18 const val TV_EP_SIZE = 400 +const val ACTION_MARK_WATCHED_UP_TO_THIS_EPISODE = 19 data class EpisodeClickEvent(val action: Int, val data: ResultEpisode) 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 c445c49a1..03f083e80 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 @@ -15,6 +15,7 @@ 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.AcraApplication.Companion.context import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.CommonActivity.getCastSession @@ -55,7 +56,10 @@ 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.editor +import com.lagradost.cloudstream3.utils.DataStore.getFolderName import com.lagradost.cloudstream3.utils.DataStore.setKey +import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount import com.lagradost.cloudstream3.utils.DataStoreHelper.deleteBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllBookmarkedData import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllFavorites @@ -1424,6 +1428,33 @@ class ResultViewModel2 : ViewModel() { _episodeSynopsis.postValue(null) } + 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) + } + } + } + + private fun getEpisodesIdsBySeason(season: Int): HashMap> { + val result = currentEpisodes.entries + .asSequence() + .filter { it.key.season <= season && it.key.dubStatus == preferDubStatus } + .flatMap { entry -> + entry.value.asSequence().map { entry.key.season to it.id.toString() } + } + .groupBy({ it.first }, { it.second }) + .mapValues { (_, ids) -> ids.toTypedArray() } + .toMap(HashMap()) + + if(season != 0){ + result.remove(0) + } + return result + } + + private suspend fun handleEpisodeClickEvent(click: EpisodeClickEvent) { when (click.action) { ACTION_SHOW_OPTIONS -> { @@ -1461,9 +1492,13 @@ class ResultViewModel2 : ViewModel() { val watchedText = if (isWatched) R.string.action_remove_from_watched else R.string.action_mark_as_watched - options.add(txt(watchedText) to 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 + options.add(txt(watchedText) to ACTION_MARK_AS_WATCHED) + + options.add(txt(markUpToText) to ACTION_MARK_WATCHED_UP_TO_THIS_EPISODE) + } postPopup( txt( activity?.getNameFull( @@ -1640,17 +1675,33 @@ class ResultViewModel2 : ViewModel() { ACTION_MARK_AS_WATCHED -> { val isWatched = getVideoWatchState(click.data.id) == VideoWatchState.Watched - if (isWatched) { setVideoWatchState(click.data.id, VideoWatchState.None) } else { setVideoWatchState(click.data.id, VideoWatchState.Watched) } - // Kinda dirty to reload all episodes :( reloadEpisodes() } + 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) + + seasons.keys.forEach {currentSeason -> + var episodeIds = seasons[currentSeason] ?: emptyArray() + if(currentSeason == clickSeason) episodeIds = episodeIds.sliceArray(0 until clickEpisode) + markEpisodes(editor,episodeIds,watchState) + } + editor.apply() + reloadEpisodes() + } + } + else -> { val action = VideoClickActionHolder.getActionById(click.action) ?: return diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f32dc659c..c3a5eb773 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -889,4 +889,6 @@ No URL Found Invalid URL or Image Successfully Image Updated + Mark as watched up to this episode + Remove watched up to this episode From e8b7829d9038fdf1c06809bf7f508b8c1c33c6f5 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Wed, 27 Aug 2025 23:57:56 +0200 Subject: [PATCH 004/640] Revert "Hide PIP settings option on TV (#1829)" (#1869) This reverts commit b2ce0f81f2963c1651a82320e13818188e01e7c3. --- .../com/lagradost/cloudstream3/ui/settings/SettingsPlayer.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) 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 0f7a24d15..5c6acdd9b 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 @@ -43,8 +43,7 @@ class SettingsPlayer : PreferenceFragmentCompat() { R.string.pref_category_gestures_key, R.string.rotate_video_key, R.string.auto_rotate_video_key, - R.string.speedup_key, - R.string.pip_enabled_key + R.string.speedup_key ), TV or EMULATOR ) From 2c4894cc14e3b1935b411557f670d38c680f8476 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Fri, 29 Aug 2025 19:54:05 +0530 Subject: [PATCH 005/640] feat:head profile image on phone (#1868) --- .../ui/home/HomeParentItemAdapterPreview.kt | 22 ++-- .../res/drawable/rounded_select_ripple.xml | 14 +++ .../main/res/layout/fragment_home_head.xml | 100 +++++++++++++----- .../main/res/layout/fragment_home_head_tv.xml | 2 +- 4 files changed, 102 insertions(+), 36 deletions(-) create mode 100644 app/src/main/res/drawable/rounded_select_ripple.xml 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 0ce7ca8f2..5f7a0d64c 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 @@ -5,6 +5,7 @@ import android.os.Parcelable import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SearchView import androidx.core.content.ContextCompat @@ -49,6 +50,7 @@ import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLayout import com.lagradost.cloudstream3.utils.AppContextUtils.setDefaultFocus import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarMargin @@ -293,9 +295,11 @@ class HomeParentItemAdapterPreview( private val bookmarkRecyclerView: RecyclerView = itemView.findViewById(R.id.home_bookmarked_child_recyclerview) - private val homeAccount: View? = itemView.findViewById(R.id.home_preview_switch_account) - private val alternativeHomeAccount: View? = - itemView.findViewById(R.id.alternative_switch_account) + 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 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) @@ -469,13 +473,19 @@ class HomeParentItemAdapterPreview( } } - homeAccount?.isGone = isLayout(TV or EMULATOR) + headProfilePicCard?.isGone = isLayout(TV or EMULATOR) + alternateHeadProfilePicCard?.isGone = isLayout(TV or EMULATOR) - homeAccount?.setOnClickListener { + viewModel.currentAccount.observe(fragment.viewLifecycleOwner) { currentAccount -> + headProfilePic?.loadImage(currentAccount?.image) + alternateHeadProfilePic?.loadImage(currentAccount?.image) + } + + headProfilePicCard?.setOnClickListener { activity?.showAccountSelectLinear() } - alternativeHomeAccount?.setOnClickListener { + alternateHeadProfilePicCard?.setOnClickListener { activity?.showAccountSelectLinear() } diff --git a/app/src/main/res/drawable/rounded_select_ripple.xml b/app/src/main/res/drawable/rounded_select_ripple.xml new file mode 100644 index 000000000..5dd7559b3 --- /dev/null +++ b/app/src/main/res/drawable/rounded_select_ripple.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_home_head.xml b/app/src/main/res/layout/fragment_home_head.xml index 1014c20fa..e57990dc4 100644 --- a/app/src/main/res/layout/fragment_home_head.xml +++ b/app/src/main/res/layout/fragment_home_head.xml @@ -30,20 +30,21 @@ android:id="@+id/home_padding" android:layout_width="match_parent" android:layout_height="50dp" - android:gravity="center" - android:orientation="horizontal"> + android:orientation="horizontal" + android:gravity="center_vertical" + android:paddingHorizontal="0dp"> + - + android:layout_height="50dp" + android:gravity="center" + android:orientation="horizontal" + android:clickable="true" + android:focusable="true" + android:foreground="@drawable/rounded_select_ripple" + android:nextFocusLeft="@id/home_search"> + + + + + + diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index ed3db1493..59986fd24 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -98,6 +98,7 @@ import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STR import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_REPO import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_RESUME_WATCHING import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_SEARCH +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.APP_STRING_SHARE import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.localListApi import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.ui.APIRepository @@ -184,6 +185,7 @@ import java.nio.charset.Charset import kotlin.math.abs import kotlin.math.absoluteValue import kotlin.system.exitProcess +import androidx.core.net.toUri class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCallback { @@ -358,7 +360,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa START_ACTION_RESUME_LATEST ) } - } else if (!isWebview) { + } else if(str.startsWith(APP_STRING_SHARE)){ + try{ + val data = str.substringAfter("$APP_STRING_SHARE:") + val parts = data.split("?",limit=2) + loadResult(String(base64DecodeArray(parts[1]), Charsets.UTF_8),String(base64DecodeArray(parts[0]), Charsets.UTF_8),"") + return true + }catch (e: Exception) { + showToast("Invalid Uri",Toast.LENGTH_SHORT) + return false + } + }else if (!isWebview) { if (str.startsWith(DOWNLOAD_NAVIGATE_TO)) { this.navigate(R.id.navigation_downloads) return true 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 20a0b6446..afcf1c33f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AccountManager.kt @@ -135,6 +135,8 @@ abstract class AccountManager { // Instantly resume watching a show const val APP_STRING_RESUME_WATCHING = "cloudstreamcontinuewatching" + const val APP_STRING_SHARE = "csshare" + fun secondsToReadable(seconds: Int, completedValue: String): String { var secondsLong = seconds.toLong() val days = TimeUnit.SECONDS 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 9c39767a2..d14ce1378 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 @@ -39,6 +39,7 @@ import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.Score import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.base64Encode import com.lagradost.cloudstream3.databinding.FragmentResultBinding import com.lagradost.cloudstream3.databinding.FragmentResultSwipeBinding import com.lagradost.cloudstream3.databinding.ResultRecommendationsBinding @@ -49,6 +50,7 @@ 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.syncproviders.AccountManager.Companion.APP_STRING_SHARE import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK @@ -83,6 +85,9 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.getImageFromDrawable import com.lagradost.cloudstream3.utils.setText import com.lagradost.cloudstream3.utils.setTextHtml +import java.net.URLEncoder +import java.nio.charset.Charset +import kotlin.io.encoding.Base64 import kotlin.math.roundToInt open class ResultFragmentPhone : FullScreenPlayer() { @@ -810,15 +815,18 @@ open class ResultFragmentPhone : FullScreenPlayer() { resultShare.setOnClickListener { try { val i = Intent(Intent.ACTION_SEND) + val nameBase64 = base64Encode(d.apiName.toString().toByteArray(Charsets.UTF_8)) + val urlBase64 = base64Encode(d.url.toByteArray(Charsets.UTF_8)) + val encodedUri = URLEncoder.encode("$APP_STRING_SHARE:$nameBase64?$urlBase64","UTF-8") + val redirectUrl = "https://recloudstream.github.io/csredirect?redirectto=$encodedUri" i.type = "text/plain" i.putExtra(Intent.EXTRA_SUBJECT, d.title) - i.putExtra(Intent.EXTRA_TEXT, d.url) + i.putExtra(Intent.EXTRA_TEXT, redirectUrl) startActivity(Intent.createChooser(i, d.title)) } catch (e: Exception) { logError(e) } } - setUrl(d.url) resultBookmarkFab.apply { isVisible = true @@ -1261,4 +1269,4 @@ open class ResultFragmentPhone : FullScreenPlayer() { } } } -} \ No newline at end of file +} From 0ffe3822c46bc6162ce7f380654ebe26feb0ed1a Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sat, 30 Aug 2025 17:10:48 +0200 Subject: [PATCH 008/640] Change versionName to 4.5.5 --- 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 5203a28cb..93b349d50 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -62,7 +62,7 @@ android { minSdk = libs.versions.minSdk.get().toInt() targetSdk = libs.versions.targetSdk.get().toInt() versionCode = 66 - versionName = "4.5.4" + versionName = "4.5.5" resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}") resValue("string", "commit_hash", getGitCommitHash()) From 27a125705c64c1c953d5a894d81a631a6daad3ba Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sun, 31 Aug 2025 19:51:02 +0200 Subject: [PATCH 009/640] Fix(TV): FCast crashing, Closes #1872 --- .../actions/temp/fcast/FcastManager.kt | 98 +++++++++++-------- 1 file changed, 57 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastManager.kt b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastManager.kt index 282ef834e..e2cf4f002 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/actions/temp/fcast/FcastManager.kt @@ -7,6 +7,7 @@ import android.net.nsd.NsdServiceInfo import android.os.Build import android.os.ext.SdkExtensions import android.util.Log +import com.lagradost.cloudstream3.mvvm.safe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe class FcastManager { @@ -72,52 +73,66 @@ class FcastManager { } override fun onServiceFound(serviceInfo: NsdServiceInfo?) { - if (serviceInfo == null) return + // Safe here as, java.lang.NoClassDefFoundError: Failed resolution of: Landroid/net/nsd/NsdManager$ServiceInfoCallback + safe { + if (serviceInfo == null) return@safe - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion( - Build.VERSION_CODES.TIRAMISU) >= 7) { - nsdManager?.registerServiceInfoCallback(serviceInfo, - Runnable::run, - object : NsdManager.ServiceInfoCallback { - override fun onServiceInfoCallbackRegistrationFailed(errorCode: Int) { - Log.e(tag, "Service registration failed: $errorCode") + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion( + Build.VERSION_CODES.TIRAMISU + ) >= 7 + ) { + nsdManager?.registerServiceInfoCallback( + serviceInfo, + Runnable::run, + object : NsdManager.ServiceInfoCallback { + override fun onServiceInfoCallbackRegistrationFailed(errorCode: Int) { + Log.e(tag, "Service registration failed: $errorCode") + } + + override fun onServiceUpdated(serviceInfo: NsdServiceInfo) { + Log.d( + tag, + "Service updated: ${serviceInfo.serviceName}," + + "Net: ${serviceInfo.hostAddresses.firstOrNull()?.hostAddress}" + ) + synchronized(_currentDevices) { + _currentDevices.removeIf { it.rawName == serviceInfo.serviceName } + _currentDevices.add(PublicDeviceInfo(serviceInfo)) + } + } + + override fun onServiceLost() { + Log.d(tag, "Service lost: ${serviceInfo.serviceName},") + synchronized(_currentDevices) { + _currentDevices.removeIf { it.rawName == serviceInfo.serviceName } + } + } + + override fun onServiceInfoCallbackUnregistered() {} + }) + } else { + @Suppress("DEPRECATION") + nsdManager?.resolveService(serviceInfo, object : ResolveListener { + override fun onResolveFailed( + serviceInfo: NsdServiceInfo?, + errorCode: Int + ) { } - override fun onServiceUpdated(serviceInfo: NsdServiceInfo) { - Log.d(tag, - "Service updated: ${serviceInfo.serviceName}," + - "Net: ${serviceInfo.hostAddresses.firstOrNull()?.hostAddress}" - ) + + override fun onServiceResolved(serviceInfo: NsdServiceInfo?) { + if (serviceInfo == null) return + synchronized(_currentDevices) { - _currentDevices.removeIf { it.rawName == serviceInfo.serviceName } _currentDevices.add(PublicDeviceInfo(serviceInfo)) } + + Log.d( + tag, + "Service found: ${serviceInfo.serviceName}, Net: ${serviceInfo.host.hostAddress}" + ) } - override fun onServiceLost() { - Log.d(tag, "Service lost: ${serviceInfo.serviceName},") - synchronized(_currentDevices) { - _currentDevices.removeIf { it.rawName == serviceInfo.serviceName } - } - } - override fun onServiceInfoCallbackUnregistered() {} }) - } else { - @Suppress("DEPRECATION") - nsdManager?.resolveService(serviceInfo, object : ResolveListener { - override fun onResolveFailed(serviceInfo: NsdServiceInfo?, errorCode: Int) {} - - override fun onServiceResolved(serviceInfo: NsdServiceInfo?) { - if (serviceInfo == null) return - - synchronized(_currentDevices) { - _currentDevices.add(PublicDeviceInfo(serviceInfo)) - } - - Log.d( - tag, - "Service found: ${serviceInfo.serviceName}, Net: ${serviceInfo.host.hostAddress}" - ) - } - }) + } } } @@ -168,8 +183,9 @@ class PublicDeviceInfo(serviceInfo: NsdServiceInfo) { val host: String? = if ( Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && SdkExtensions.getExtensionVersion( - Build.VERSION_CODES.TIRAMISU) >= 7 - ) { + Build.VERSION_CODES.TIRAMISU + ) >= 7 + ) { serviceInfo.hostAddresses.firstOrNull()?.hostAddress } else { @Suppress("DEPRECATION") From 8215aa0b197196271f55ec88d8ff8b388f09348d Mon Sep 17 00:00:00 2001 From: Jace <54625750+Jacekun@users.noreply.github.com> Date: Mon, 1 Sep 2025 01:52:37 +0800 Subject: [PATCH 010/640] [skip ci] feat: Created discoverium.yml file to be discoverable by Discoverium. (#1876) --- discoverium.yml | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 discoverium.yml diff --git a/discoverium.yml b/discoverium.yml new file mode 100644 index 000000000..7d3fae8c5 --- /dev/null +++ b/discoverium.yml @@ -0,0 +1,8 @@ +app: + name: CloudStream + authors: recloudstream + category: entertainment + description: Android app for streaming and downloading media. + icon: https://raw.githubusercontent.com/recloudstream/cloudstream/refs/heads/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png + releases: + url: https://github.com/recloudstream/cloudstream/releases \ No newline at end of file From 2cce3fe6ad8883767e2f3a4f36d6fb6cf450afa1 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Sun, 31 Aug 2025 23:31:31 +0530 Subject: [PATCH 011/640] feat:reload provider btn on emulator and tv (#1877) --- .../ui/home/HomeParentItemAdapterPreview.kt | 9 ++++++++- app/src/main/res/drawable/ic_refresh.xml | 9 +++++++++ .../main/res/layout/fragment_home_head_tv.xml | 17 +++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 4 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/drawable/ic_refresh.xml 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 5f7a0d64c..ba17e02b9 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 @@ -6,6 +6,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.ImageView +import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.SearchView import androidx.core.content.ContextCompat @@ -33,6 +34,7 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.debugException import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountSelectLinear @@ -495,7 +497,11 @@ class HomeParentItemAdapterPreview( viewModel.loadAndCancel(api, 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 + } homePreviewSearchButton.setOnClickListener { _ -> // Open blank screen. viewModel.queryTextSubmit("") @@ -665,6 +671,7 @@ class HomeParentItemAdapterPreview( if (binding is FragmentHomeHeadTvBinding) { observe(viewModel.apiName) { name -> binding.homePreviewChangeApi.text = name + binding.homePreviewReloadProvider.isGone = (name == noneApi.name) } } observe(viewModel.resumeWatching) { diff --git a/app/src/main/res/drawable/ic_refresh.xml b/app/src/main/res/drawable/ic_refresh.xml new file mode 100644 index 000000000..e61dcf1ce --- /dev/null +++ b/app/src/main/res/drawable/ic_refresh.xml @@ -0,0 +1,9 @@ + + + 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 e0afd23b9..88e7c4c0a 100644 --- a/app/src/main/res/layout/fragment_home_head_tv.xml +++ b/app/src/main/res/layout/fragment_home_head_tv.xml @@ -50,6 +50,23 @@ + + Mark as watched up to this episode Remove watched up to this episode Reloaded + Reload Provider From c5d1b5f87e779d4977a72650b754d8cc89bd3d64 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Sun, 31 Aug 2025 23:40:53 +0200 Subject: [PATCH 012/640] Fix(TV): Focus for reload --- app/src/main/res/layout/fragment_home_head_tv.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 88e7c4c0a..06112f33c 100644 --- a/app/src/main/res/layout/fragment_home_head_tv.xml +++ b/app/src/main/res/layout/fragment_home_head_tv.xml @@ -45,7 +45,7 @@ android:background="@drawable/player_button_tv_attr_no_bg" android:gravity="center_vertical" android:nextFocusLeft="@id/home_preview_play_btt" - android:nextFocusRight="@id/home_preview_search_button" + android:nextFocusRight="@id/home_preview_reload_provider" android:nextFocusDown="@id/home_preview_play_btt" > @@ -75,7 +75,7 @@ android:background="@drawable/player_button_tv_attr_no_bg" android:contentDescription="@string/search" android:focusable="true" - android:nextFocusLeft="@id/home_preview_change_api" + android:nextFocusLeft="@id/home_preview_reload_provider" android:nextFocusRight="@id/home_preview_switch_account" android:nextFocusDown="@id/home_preview_info_btt" android:padding="10dp" From 94c560071a3305407fb4bf02e18adc7f16ab7b9f Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Thu, 4 Sep 2025 02:35:54 +0530 Subject: [PATCH 013/640] ui:improve 2x overlay btn (#1883) * ui:improve 2x overlay btn * no foreground * tint in drawable --- app/src/main/res/drawable/speedup.xml | 14 ++++++-------- app/src/main/res/layout/player_custom_layout.xml | 11 +++++++---- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/drawable/speedup.xml b/app/src/main/res/drawable/speedup.xml index 79ef428ac..879ef852c 100644 --- a/app/src/main/res/drawable/speedup.xml +++ b/app/src/main/res/drawable/speedup.xml @@ -1,12 +1,10 @@ - - - \ No newline at end of file + + diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index f671b42bc..1e9b16441 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -982,15 +982,18 @@ android:id="@+id/player_speedup_button" style="@style/Widget.MaterialComponents.Button.OutlinedButton" android:layout_width="45dp" - android:layout_height="44dp" + android:layout_height="45dp" android:layout_gravity="center_horizontal" - android:layout_marginTop="70dp" - app:iconGravity="top" + android:layout_marginTop="60dp" + app:iconPadding="0dp" + app:iconGravity="textStart" android:clickable="false" + android:focusable="false" android:textAllCaps="false" android:visibility="gone" app:icon="@drawable/speedup" app:iconTint="?attr/textColor" - app:rippleColor="?attr/colorPrimary" + android:foreground="@null" + android:backgroundTint="@color/skipOpTransparent" tools:visibility="visible" /> \ No newline at end of file From 91fe001103c15d1977315881efba0ecccd2cff53 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Wed, 3 Sep 2025 23:10:53 +0200 Subject: [PATCH 014/640] Fix: Change color of 2x to always be white, also Fixes #1882 --- app/src/main/res/layout/player_custom_layout.xml | 2 +- app/src/main/res/layout/player_custom_layout_tv.xml | 4 ++-- app/src/main/res/layout/trailer_custom_layout.xml | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index 1e9b16441..f94c30d9e 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -992,7 +992,7 @@ android:textAllCaps="false" android:visibility="gone" app:icon="@drawable/speedup" - app:iconTint="?attr/textColor" + app:iconTint="@color/textColor" android:foreground="@null" android:backgroundTint="@color/skipOpTransparent" tools:visibility="visible" /> diff --git a/app/src/main/res/layout/player_custom_layout_tv.xml b/app/src/main/res/layout/player_custom_layout_tv.xml index 0b4ac71d0..2afe63306 100644 --- a/app/src/main/res/layout/player_custom_layout_tv.xml +++ b/app/src/main/res/layout/player_custom_layout_tv.xml @@ -944,7 +944,7 @@ android:id="@+id/player_speedup_button" style="@style/Widget.MaterialComponents.Button.OutlinedButton" android:layout_width="45dp" - android:layout_height="44dp" + android:layout_height="45dp" android:layout_gravity="center_horizontal" android:layout_marginTop="70dp" app:iconGravity="top" @@ -952,7 +952,7 @@ android:textAllCaps="false" android:visibility="gone" app:icon="@drawable/speedup" - app:iconTint="?attr/textColor" + app:iconTint="@color/textColor" app:rippleColor="?attr/colorPrimary" tools:visibility="visible" /> \ No newline at end of file diff --git a/app/src/main/res/layout/trailer_custom_layout.xml b/app/src/main/res/layout/trailer_custom_layout.xml index 0da7d170e..f8c2882ce 100644 --- a/app/src/main/res/layout/trailer_custom_layout.xml +++ b/app/src/main/res/layout/trailer_custom_layout.xml @@ -915,7 +915,7 @@ android:id="@+id/player_speedup_button" style="@style/Widget.MaterialComponents.Button.OutlinedButton" android:layout_width="45dp" - android:layout_height="44dp" + android:layout_height="45dp" android:layout_gravity="center_horizontal" android:layout_marginTop="70dp" app:iconGravity="top" @@ -923,7 +923,7 @@ android:textAllCaps="false" android:visibility="gone" app:icon="@drawable/speedup" - app:iconTint="?attr/textColor" + app:iconTint="@color/textColor" app:rippleColor="?attr/colorPrimary" tools:visibility="visible" /> \ No newline at end of file From 4a36048b3ee92cb4cb12c7aa1aa126892f6476cc Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Thu, 4 Sep 2025 00:10:19 +0200 Subject: [PATCH 015/640] Fix: Made plugin languages filter only show existing languages. --- .../ui/settings/extensions/PluginsFragment.kt | 17 +++++++++----- .../settings/extensions/PluginsViewModel.kt | 23 ++++++++++++++++++- 2 files changed, 33 insertions(+), 7 deletions(-) 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 4878049b4..b0888dd83 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 @@ -89,13 +89,18 @@ class PluginsFragment : Fragment() { } R.id.lang_filter -> { - val tempLangs = appLanguages.toMutableList() - val languageCodes = - mutableListOf("none") + tempLangs.map { (_, _, iso) -> iso } + val languageCodes = pluginViewModel.pluginLanguages + val languageNames = - mutableListOf(getString(R.string.no_data)) + tempLangs.map { (emoji, name, iso) -> - val flag = - emoji.ifBlank { SubtitleHelper.getFlagFromIso(iso) ?: "ERROR" } + languageCodes.map { iso -> + val (flag, name) = when (iso) { + AllLanguagesName -> "" to getString(R.string.all_languages_preference) + "none" -> "" to getString(R.string.no_data) + else -> (SubtitleHelper.getFlagFromIso(iso) + ?: "") to (SubtitleHelper.fromTwoLettersToLanguage( + iso, + ) ?: iso) + } "$flag $name" } val selectedList = 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 a6f914898..6d20cb348 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 @@ -36,6 +36,22 @@ class PluginsViewModel : ViewModel() { /** plugins is an unaltered list of plugins */ private var plugins: List = emptyList() + set(value) { + // Also set all the plugin languages for easier filtering + val languages = + value.map { it -> + val language = it.plugin.second.language + if (language.isNullOrBlank()) "none" else language + }.distinct().sorted().toMutableList() + // Move "none" to the front as it is a special case + if (languages.remove("none")) { + languages.add(0, "none") + } + pluginLanguages = languages + + field = value + } + var pluginLanguages: List = emptyList() /** filteredPlugins is a subset of plugins following the current search query and tv type selection */ private var _filteredPlugins = MutableLiveData() @@ -227,7 +243,12 @@ class PluginsViewModel : ViewModel() { // Return list to base state if no query this.sortedBy { it.plugin.second.name } } else { - this.sortedBy { -FuzzySearch.partialRatio(it.plugin.second.name.lowercase(), query.lowercase()) } + this.sortedBy { + -FuzzySearch.partialRatio( + it.plugin.second.name.lowercase(), + query.lowercase() + ) + } } } From 909cdbfffc216b62c650276f1c7f50d4d643be31 Mon Sep 17 00:00:00 2001 From: BlipBlob <82711292+BlipBlob@users.noreply.github.com> Date: Wed, 3 Sep 2025 23:01:57 +0000 Subject: [PATCH 016/640] Paginated search (#1731) --- .../cloudstream3/ui/APIRepository.kt | 27 +++--- .../ui/quicksearch/QuickSearchFragment.kt | 61 ++++++++---- .../cloudstream3/ui/search/SearchFragment.kt | 29 ++++-- .../cloudstream3/ui/search/SearchViewModel.kt | 93 +++++++++++++++---- library/build.gradle.kts | 2 +- .../com/lagradost/cloudstream3/MainAPI.kt | 39 ++++++++ 6 files changed, 196 insertions(+), 55 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt index 492efacec..93a79689e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/APIRepository.kt @@ -9,13 +9,14 @@ import com.lagradost.cloudstream3.LoadResponse import com.lagradost.cloudstream3.MainAPI import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.MainPageRequest -import com.lagradost.cloudstream3.SearchResponse +import com.lagradost.cloudstream3.SearchResponseList import com.lagradost.cloudstream3.SubtitleFile import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.fixUrl import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.safeApiCall +import com.lagradost.cloudstream3.newSearchResponseList import com.lagradost.cloudstream3.utils.Coroutines.threadSafeListOf import com.lagradost.cloudstream3.utils.ExtractorLink import kotlinx.coroutines.CoroutineScope @@ -28,7 +29,7 @@ class APIRepository(val api: MainAPI) { // 2 minute timeout to prevent bad extensions/extractors from hogging the resources // No real provider should take longer, so we hard kill them. private const val DEFAULT_TIMEOUT = 120_000L - private const val MAX_TIMEOUT = 4*DEFAULT_TIMEOUT + private const val MAX_TIMEOUT = 4 * DEFAULT_TIMEOUT private const val MIN_TIMEOUT = 5_000L var dubStatusActive = HashSet() @@ -58,8 +59,8 @@ class APIRepository(val api: MainAPI) { private var cacheIndex: Int = 0 const val CACHE_SIZE = 20 - fun getTimeout(desired : Long?) : Long { - return (desired ?: DEFAULT_TIMEOUT).coerceIn(MIN_TIMEOUT, MAX_TIMEOUT) + fun getTimeout(desired: Long?): Long { + return (desired ?: DEFAULT_TIMEOUT).coerceIn(MIN_TIMEOUT, MAX_TIMEOUT) } } @@ -117,27 +118,29 @@ class APIRepository(val api: MainAPI) { } } - suspend fun search(query: String): Resource> { + suspend fun search(query: String, page: Int): Resource { if (query.isEmpty()) - return Resource.Success(emptyList()) + return Resource.Success(newSearchResponseList(emptyList())) return safeApiCall { withTimeout(getTimeout(api.searchTimeoutMs)) { - (api.search(query) + (api.search(query, page) ?: throw ErrorLoadingException()) - // .filter { typesActive.contains(it.type) } - .toList() + // .filter { typesActive.contains(it.type) } } } } - suspend fun quickSearch(query: String): Resource> { + suspend fun quickSearch(query: String): Resource { if (query.isEmpty()) - return Resource.Success(emptyList()) + return Resource.Success(newSearchResponseList(emptyList())) return safeApiCall { withTimeout(getTimeout(api.quickSearchTimeoutMs)) { - api.quickSearch(query) ?: throw ErrorLoadingException() + newSearchResponseList( + api.quickSearch(query) ?: throw ErrorLoadingException(), + false + ) } } } 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 12adc0400..d2e308a3c 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 @@ -27,6 +27,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.home.HomeFragment import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList +import com.lagradost.cloudstream3.ui.home.HomeViewModel import com.lagradost.cloudstream3.ui.home.ParentItemAdapter import com.lagradost.cloudstream3.ui.search.SearchAdapter import com.lagradost.cloudstream3.ui.search.SearchClickCallback @@ -39,6 +40,7 @@ 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.ownShow +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount @@ -176,19 +178,28 @@ class QuickSearchFragment : Fragment() { } } else { binding?.quickSearchMasterRecycler?.adapter = - ParentItemAdapter(fragment = this, id = "quickSearchMasterRecycler".hashCode(), { callback -> - SearchHelper.handleSearchClickCallback(callback) - //when (callback.action) { - //SEARCH_ACTION_LOAD -> { - // clickCallback?.invoke(callback) - //} - // else -> SearchHelper.handleSearchClickCallback(activity, callback) - //} - }, { item -> - bottomSheetDialog = activity?.loadHomepageList(item, dismissCallback = { - bottomSheetDialog = null + ParentItemAdapter( + fragment = this, + id = "quickSearchMasterRecycler".hashCode(), + { callback -> + SearchHelper.handleSearchClickCallback(callback) + //when (callback.action) { + //SEARCH_ACTION_LOAD -> { + // clickCallback?.invoke(callback) + //} + // else -> SearchHelper.handleSearchClickCallback(activity, callback) + //} + }, + { item -> + bottomSheetDialog = activity?.loadHomepageList(item, dismissCallback = { + bottomSheetDialog = null + }, expandCallback = { searchViewModel.expandAndReturn(it) }) + }, + expandCallback = { name -> + ioSafe { + searchViewModel.expandAndReturn(name) + } }) - }) binding?.quickSearchMasterRecycler?.layoutManager = GridLayoutManager(context, 1) } binding?.quickSearchAutofitResults?.isVisible = isSingleProvider @@ -200,13 +211,27 @@ class QuickSearchFragment : Fragment() { // https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist listLock.lock() (binding?.quickSearchMasterRecycler?.adapter as ParentItemAdapter?)?.apply { - updateList(list.map { ongoing -> - val ongoingList = HomePageList( - ongoing.apiName, - if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList() + val newItems = list.map { ongoing -> + val dataList = ongoing.value.list + val dataListFiltered = + context?.filterSearchResultByFilmQuality(dataList) ?: dataList + + val homePageList = HomePageList( + ongoing.key, + dataListFiltered ) - ongoingList - }) + + val expandableList = HomeViewModel.ExpandableHomepageList( + homePageList, + ongoing.value.currentPage, + ongoing.value.hasNext + ) + + expandableList + } + + submitList(newItems) + //notifyDataSetChanged() } } catch (e: Exception) { logError(e) 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 1922e4fae..2c5f80090 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 @@ -54,6 +54,7 @@ import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.currentSpan import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.updateChips +import com.lagradost.cloudstream3.ui.home.HomeViewModel import com.lagradost.cloudstream3.ui.home.ParentItemAdapter import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.setLinearListLayout @@ -67,6 +68,7 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.getApiSettings import com.lagradost.cloudstream3.utils.AppContextUtils.ownHide 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.Coroutines.main import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount @@ -516,18 +518,25 @@ class SearchFragment : Fragment() { listLock.lock() (binding?.searchMasterRecycler?.adapter as ParentItemAdapter?)?.apply { val newItems = list.map { ongoing -> - val dataList = - if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList() + val dataList = ongoing.value.list val dataListFiltered = context?.filterSearchResultByFilmQuality(dataList) ?: dataList - val ongoingList = HomePageList( - ongoing.apiName, + + val homePageList = HomePageList( + ongoing.key, dataListFiltered ) - ongoingList - } - updateList(newItems) + val expandableList = HomeViewModel.ExpandableHomepageList( + homePageList, + ongoing.value.currentPage, + ongoing.value.hasNext + ) + + expandableList + } + + submitList(newItems) //notifyDataSetChanged() } } catch (e: Exception) { @@ -552,7 +561,11 @@ class SearchFragment : Fragment() { }, { item -> bottomSheetDialog = activity?.loadHomepageList(item, dismissCallback = { bottomSheetDialog = null - }) + }, expandCallback = { name -> searchViewModel.expandAndReturn(name) }) + }, expandCallback = { name -> + ioSafe { + searchViewModel.expandAndReturn(name) + } }) val historyAdapter = SearchHistoryAdaptor(mutableListOf()) { click -> 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 839b9d3f8..a0d533545 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 @@ -8,11 +8,15 @@ 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.HomePageList import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.mvvm.Resource +import com.lagradost.cloudstream3.mvvm.debugAssert +import com.lagradost.cloudstream3.mvvm.debugWarning import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.ui.APIRepository +import com.lagradost.cloudstream3.ui.home.HomeViewModel import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount import kotlinx.coroutines.Dispatchers @@ -20,9 +24,9 @@ import kotlinx.coroutines.Job import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -data class OnGoingSearch( - val apiName: String, - val data: Resource> + +data class ExpandableSearchList( + var list: List, var currentPage: Int, var hasNext: Boolean, ) const val SEARCH_HISTORY_KEY = "search_history" @@ -32,8 +36,9 @@ class SearchViewModel : ViewModel() { MutableLiveData() val searchResponse: LiveData>> get() = _searchResponse - private val _currentSearch: MutableLiveData> = MutableLiveData() - val currentSearch: LiveData> get() = _currentSearch + private val _currentSearch: MutableLiveData> = + MutableLiveData() + val currentSearch: LiveData> get() = _currentSearch private val _currentHistory: MutableLiveData> = MutableLiveData() val currentHistory: LiveData> get() = _currentHistory @@ -42,9 +47,17 @@ class SearchViewModel : ViewModel() { fun clearSearch() { _searchResponse.postValue(Resource.Success(ArrayList())) - _currentSearch.postValue(emptyList()) + _currentSearch.postValue(emptyMap()) + expandableSearches.clear() } + var lastQuery: String? = null + + /** Save which providers can searched again and which search result page they are on. + * Maps provider name to search list. + * @see [HomeViewModel.expandable] */ + private val expandableSearches: MutableMap = mutableMapOf() + private var currentSearchIndex = 0 private var onGoingSearch: Job? = null @@ -72,6 +85,52 @@ class SearchViewModel : ViewModel() { } } + private val lock: MutableSet = mutableSetOf() + + // ExpandableHomepageList because the home adapter is reused in the search fragment + suspend fun expandAndReturn(name: String): HomeViewModel.ExpandableHomepageList? { + if (lock.contains(name)) return null + val query = lastQuery ?: return null + val repo = repos.find { it.name == name } ?: return null + + lock += name + + expandableSearches[name]?.let { current -> + debugAssert({ !current.hasNext }) { + "Expand called when not needed" + } + + val nextPage = current.currentPage + 1 + val next = repo.search(query, nextPage) + if (next is Resource.Success) { + val nextValue = next.value + expandableSearches[name]?.apply { + hasNext = nextValue.hasNext + 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 + } ?: debugWarning { + "Expanded an item not in search load named $name, current list is ${expandableSearches.keys}" + } + } else { + current.hasNext = false + } + + _currentSearch.postValue(expandableSearches) + } + + + lock -= name + + val item = expandableSearches[name] ?: return null + return HomeViewModel.ExpandableHomepageList(HomePageList(name, item.list), item.currentPage, item.hasNext) + } + private fun search( query: String, providersActive: Set, @@ -100,29 +159,31 @@ class SearchViewModel : ViewModel() { } _searchResponse.postValue(Resource.Loading()) + _currentSearch.postValue(emptyMap()) + expandableSearches.clear() - - _currentSearch.postValue(ArrayList()) + lastQuery = query withContext(Dispatchers.IO) { // This interrupts UI otherwise - val currentList = ArrayList() - repos.filter { a -> (ignoreSettings || (providersActive.isEmpty() || providersActive.contains(a.name))) && (!isQuickSearch || a.hasQuickSearch) }.amap { a -> // Parallel - val search = if (isQuickSearch) a.quickSearch(query) else a.search(query) + val search = if (isQuickSearch) a.quickSearch(query) else a.search(query, 1) if (currentSearchIndex != currentIndex) return@amap - currentList.add(OnGoingSearch(a.name, search)) - _currentSearch.postValue(currentList) + if (search is Resource.Success) { + val searchValue = search.value + expandableSearches[a.name] = ExpandableSearchList(searchValue.items, 1, searchValue.hasNext) + } + + _currentSearch.postValue(expandableSearches) } if (currentSearchIndex != currentIndex) return@withContext // this should prevent rewrite of existing data bug - _currentSearch.postValue(currentList) + _currentSearch.postValue(expandableSearches) val list = ArrayList() val nestedList = - currentList.map { it.data } - .filterIsInstance>>().map { it.value } + expandableSearches.map { it.value.list } // I do it this way to move the relevant search results to the top var index = 0 diff --git a/library/build.gradle.kts b/library/build.gradle.kts index b79d3ac9f..daae9a490 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -16,7 +16,7 @@ plugins { val javaTarget = JvmTarget.fromTarget(libs.versions.jvmTarget.get()) kotlin { - version = "1.0.0" + version = "1.0.1" androidTarget() jvm() diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt index c41189b07..a49d8585f 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/MainAPI.kt @@ -422,6 +422,22 @@ fun newHomePageResponse(list: List, hasNext: Boolean? = null): Hom return HomePageResponse(list, hasNext = hasNext ?: list.any { it.list.isNotEmpty() }) } +@Prerelease +fun newSearchResponseList( + list: List, + hasNext: Boolean? = null, +): SearchResponseList { + return SearchResponseList( + list, + hasNext = hasNext ?: list.isNotEmpty() + ) +} + +@Prerelease +fun List.toNewSearchResponseList(hasNext: Boolean? = null) : SearchResponseList { + return newSearchResponseList(this, hasNext) +} + /**Every provider will **not** have try catch built in, so handle exceptions when calling these functions*/ abstract class MainAPI { companion object { @@ -559,6 +575,17 @@ abstract class MainAPI { throw NotImplementedError() } + @Prerelease + /** Paginated search, starts with page: 1 */ + open suspend fun search(query: String, page: Int): SearchResponseList? { + val searchResults = search(query) ?: return null + + return newSearchResponseList( + searchResults, + false + ) + } + // @WorkerThread open suspend fun search(query: String): List? { throw NotImplementedError() @@ -1107,6 +1134,18 @@ data class HomePageList( val isHorizontalImages: Boolean = false ) +/** Data class for the Search results. + * @property items list of [SearchResponse] items that will be added to the search row. + * @property hasNext if there is a next page or not. + * */ +@Prerelease +data class SearchResponseList +@Deprecated("Use newSearchResponseList method", level = DeprecationLevel.WARNING) +constructor( + val items: List, + val hasNext: Boolean = false +) + /** enum class holds search quality. * * [Movie release types](https://en.wikipedia.org/wiki/Pirated_movie_release_types)**/ From b740afd648cc527c710f2084d25cf5c14263f184 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Fri, 5 Sep 2025 20:45:33 +0300 Subject: [PATCH 017/640] feat(TV UI): Netflix-like Horizontal scrolling behavior (#1888) --- .../ui/result/LinearListLayout.kt | 64 +++++++++++++++++-- 1 file changed, 58 insertions(+), 6 deletions(-) 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 b4e3062b4..7d0061cb6 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 @@ -8,6 +8,8 @@ import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.CommonActivity.activity import com.lagradost.cloudstream3.FocusDirection import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.settings.Globals.TV +import com.lagradost.cloudstream3.ui.settings.Globals.isLayout const val FOCUS_SELF = View.NO_ID - 1 const val FOCUS_INHERIT = FOCUS_SELF - 1 @@ -106,10 +108,16 @@ open class LinearListLayout(context: Context?) : override fun onInterceptFocusSearch(focused: View, direction: Int): View? { val dir = if (orientation == HORIZONTAL) { - if (direction == View.FOCUS_DOWN) getNextDirection(focused, FocusDirection.Down)?.let { newFocus -> + if (direction == View.FOCUS_DOWN) getNextDirection( + focused, + FocusDirection.Down + )?.let { newFocus -> return newFocus } - if (direction == View.FOCUS_UP) getNextDirection(focused, FocusDirection.Up)?.let { newFocus -> + if (direction == View.FOCUS_UP) getNextDirection( + focused, + FocusDirection.Up + )?.let { newFocus -> return newFocus } @@ -129,10 +137,16 @@ open class LinearListLayout(context: Context?) : } ret } else { - if (direction == View.FOCUS_RIGHT) getNextDirection(focused, FocusDirection.End)?.let { newFocus -> + if (direction == View.FOCUS_RIGHT) getNextDirection( + focused, + FocusDirection.End + )?.let { newFocus -> return newFocus } - if (direction == View.FOCUS_LEFT) getNextDirection(focused, FocusDirection.Start)?.let { newFocus -> + if (direction == View.FOCUS_LEFT) getNextDirection( + focused, + FocusDirection.Start + )?.let { newFocus -> return newFocus } @@ -151,9 +165,15 @@ open class LinearListLayout(context: Context?) : // if out of bounds then refocus as specified return if (lookFor >= itemCount) { - getNextDirection(focused, if(orientation == HORIZONTAL) FocusDirection.End else FocusDirection.Down) + getNextDirection( + focused, + if (orientation == HORIZONTAL) FocusDirection.End else FocusDirection.Down + ) } else if (lookFor < 0) { - getNextDirection(focused, if(orientation == HORIZONTAL) FocusDirection.Start else FocusDirection.Up) + getNextDirection( + focused, + if (orientation == HORIZONTAL) FocusDirection.Start else FocusDirection.Up + ) } else { getViewFromPos(lookFor) ?: run { scrollToPosition(lookFor) @@ -166,6 +186,38 @@ open class LinearListLayout(context: Context?) : } } + override fun requestChildRectangleOnScreen( + parent: RecyclerView, + child: View, + rect: android.graphics.Rect, + immediate: Boolean, + focusedChildVisible: Boolean + ): Boolean { + if (isLayout(TV) && orientation == HORIZONTAL) { + val dx = when { + isLayoutRTL -> getDecoratedRight(child) - (parent.width - parent.paddingRight) + else -> getDecoratedLeft(child) - parent.paddingLeft + } + return if (dx != 0) { + when { + immediate -> parent.scrollBy(dx, 0) + else -> parent.smoothScrollBy(dx, 0) + } + true + } else { + false + } + } else { + return super.requestChildRectangleOnScreen( + parent, + child, + rect, + immediate, + focusedChildVisible + ) + } + } + /*override fun onRequestChildFocus( parent: RecyclerView, state: RecyclerView.State, From d2472f0d2a8a98c85cadc9f7c202d97c13ab12cf Mon Sep 17 00:00:00 2001 From: BlipBlob <82711292+BlipBlob@users.noreply.github.com> Date: Fri, 5 Sep 2025 17:49:44 +0000 Subject: [PATCH 018/640] Fix broken resolveUsingWebView (#1887) * webview * webview 2 --- .../network/WebViewResolver.android.kt | 51 +++++++++++-------- .../cloudstream3/network/WebViewResolver.kt | 34 +++++++++++++ .../network/WebViewResolver.jvm.kt | 39 ++++++++++++++ 3 files changed, 102 insertions(+), 22 deletions(-) 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 fc11c4721..a99d0a16a 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 @@ -6,6 +6,7 @@ import android.net.http.SslError import android.os.Handler import android.os.Looper import android.webkit.* +import com.lagradost.api.Log import com.lagradost.api.getContext import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.debugException @@ -44,8 +45,9 @@ actual class WebViewResolver actual constructor( Interceptor { actual companion object { - var webViewUserAgent: String? = null + actual var webViewUserAgent: String? = null actual val DEFAULT_TIMEOUT = 60_000L + private const val TAG = "WebViewResolver" @JvmName("getWebViewUserAgent1") fun getWebViewUserAgent(): String? { @@ -69,15 +71,24 @@ actual class WebViewResolver actual constructor( } } - suspend fun resolveUsingWebView( + actual suspend fun resolveUsingWebView( url: String, - referer: String? = null, - method: String = "GET", - requestCallBack: (Request) -> Boolean = { false }, + referer: String?, + method: String, + requestCallBack: (Request) -> Boolean, + ): Pair> = + resolveUsingWebView(url, referer, emptyMap(), method, requestCallBack) + + actual suspend fun resolveUsingWebView( + url: String, + referer: String?, + headers: Map, + method: String, + requestCallBack: (Request) -> Boolean, ): Pair> { return try { resolveUsingWebView( - requestCreator(method, url, referer = referer), requestCallBack + requestCreator(method, url, referer = referer, headers = headers), requestCallBack ) } catch (e: java.lang.IllegalArgumentException) { logError(e) @@ -86,18 +97,14 @@ actual class WebViewResolver actual constructor( } } - /** - * @param requestCallBack asynchronously return matched requests by either interceptUrl or additionalUrls. If true, destroy WebView. - * @return the final request (by interceptUrl) and all the collected urls (by additionalUrls). - * */ @SuppressLint("SetJavaScriptEnabled") - suspend fun resolveUsingWebView( + actual suspend fun resolveUsingWebView( request: Request, - requestCallBack: (Request) -> Boolean = { false } + requestCallBack: (Request) -> Boolean ): Pair> { val url = request.url.toString() val headers = request.headers - println("Initial web-view request: $url") + Log.i(TAG, "Initial web-view request: $url") var webView: WebView? = null // Extra assurance it exits as it should. var shouldExit = false @@ -108,7 +115,7 @@ actual class WebViewResolver actual constructor( webView?.destroy() webView = null shouldExit = true - println("Destroyed webview") + Log.i(TAG, "Destroyed webview") } } @@ -142,12 +149,12 @@ actual class WebViewResolver actual constructor( request: WebResourceRequest ): WebResourceResponse? = runBlocking { val webViewUrl = request.url.toString() - println("Loading WebView URL: $webViewUrl") + Log.i(TAG, "Loading WebView URL: $webViewUrl") if (script != null) { val handler = Handler(Looper.getMainLooper()) handler.post { - view.evaluateJavascript("$script") + view.evaluateJavascript(script) { scriptCallback?.invoke(it) } } } @@ -156,7 +163,7 @@ actual class WebViewResolver actual constructor( fixedRequest = request.toRequest()?.also { requestCallBack(it) } - println("Web-view request finished: $webViewUrl") + Log.i(TAG, "Web-view request finished: $webViewUrl") destroyWebView() return@runBlocking null } @@ -205,8 +212,7 @@ actual class WebViewResolver actual constructor( * They don't contain all the headers the browser actually gives. * Overriding with okhttp might fuck up otherwise working requests, * e.g the recaptcha request. - * **/ - + * */ return@runBlocking try { when { blacklistedFiles.any { URI(webViewUrl).path.contains(it) } || webViewUrl.endsWith( @@ -216,6 +222,7 @@ actual class WebViewResolver actual constructor( null, null ) + webViewUrl.contains("recaptcha") || webViewUrl.contains("/cdn-cgi/") -> super.shouldInterceptRequest( view, request @@ -236,11 +243,12 @@ actual class WebViewResolver actual constructor( request ) } - } catch (e: Exception) { + } catch (_: Exception) { null } } + @SuppressLint("WebViewClientOnReceivedSslError") override fun onReceivedSslError( view: WebView?, handler: SslErrorHandler?, @@ -268,11 +276,10 @@ actual class WebViewResolver actual constructor( loop += 1 } - println("Web-view timeout after ${totalTime / 1000}s") + Log.i(TAG, "Web-view timeout after ${totalTime / 1000}s") destroyWebView() return fixedRequest to extraRequestList } - } fun WebResourceRequest.toRequest(): Request? { diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.kt index 8baf2f31d..c0a2d9525 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.network import com.lagradost.cloudstream3.USER_AGENT import okhttp3.Interceptor +import okhttp3.Request /** * When used as Interceptor additionalUrls cannot be returned, use WebViewResolver(...).resolveUsingWebView(...) @@ -24,5 +25,38 @@ expect class WebViewResolver( ) : Interceptor { companion object { val DEFAULT_TIMEOUT: Long + var webViewUserAgent: String? } + + /** + * @param requestCallBack asynchronously return matched requests by either interceptUrl or additionalUrls. If true, destroy WebView. + * @return the final request (by interceptUrl) and all the collected urls (by additionalUrls). + * */ + suspend fun resolveUsingWebView( + url: String, + referer: String? = null, + method: String = "GET", + requestCallBack: (Request) -> Boolean = { false }, + ) : Pair> + + /** + * @param requestCallBack asynchronously return matched requests by either interceptUrl or additionalUrls. If true, destroy WebView. + * @return the final request (by interceptUrl) and all the collected urls (by additionalUrls). + * */ + suspend fun resolveUsingWebView( + url: String, + referer: String? = null, + headers: Map = emptyMap(), + method: String = "GET", + requestCallBack: (Request) -> Boolean = { false }, + ) : Pair> + + /** + * @param requestCallBack asynchronously return matched requests by either interceptUrl or additionalUrls. If true, destroy WebView. + * @return the final request (by interceptUrl) and all the collected urls (by additionalUrls). + * */ + suspend fun resolveUsingWebView( + request: Request, + requestCallBack: (Request) -> Boolean = { false } + ): Pair> } diff --git a/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.jvm.kt b/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.jvm.kt index 6b99ef3b5..39e0d51f9 100644 --- a/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.jvm.kt +++ b/library/src/jvmMain/kotlin/com/lagradost/cloudstream3/network/WebViewResolver.jvm.kt @@ -1,6 +1,10 @@ package com.lagradost.cloudstream3.network +import com.lagradost.cloudstream3.mvvm.debugException +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.nicehttp.requestCreator import okhttp3.Interceptor +import okhttp3.Request import okhttp3.Response /** @@ -31,5 +35,40 @@ actual class WebViewResolver actual constructor( actual companion object { actual val DEFAULT_TIMEOUT = 60_000L + actual var webViewUserAgent: String? = null + } + + actual suspend fun resolveUsingWebView( + url: String, + referer: String?, + method: String, + requestCallBack: (Request) -> Boolean, + ): Pair> = + resolveUsingWebView(url, referer, emptyMap(), method, requestCallBack) + + actual suspend fun resolveUsingWebView( + url: String, + referer: String?, + headers: Map, + method: String, + requestCallBack: (Request) -> Boolean + ): Pair> { + return try { + resolveUsingWebView( + requestCreator(method, url, referer = referer, headers = headers), requestCallBack + ) + } catch (e: java.lang.IllegalArgumentException) { + logError(e) + debugException { "ILLEGAL URL IN resolveUsingWebView!" } + return null to emptyList() + } + } + + actual suspend fun resolveUsingWebView( + request: Request, + requestCallBack: (Request) -> Boolean + ): Pair> { + TODO("Not yet implemented") } } + From bf630923c73afe982cfbb43da6d133969ee19d62 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Fri, 5 Sep 2025 23:22:56 +0530 Subject: [PATCH 019/640] ui:2x alignment and animation (#1885) --- .../ui/player/FullScreenPlayer.kt | 19 +++++++++++++++++-- .../main/res/layout/player_custom_layout.xml | 2 +- .../main/res/layout/trailer_custom_layout.xml | 9 ++++++--- 3 files changed, 24 insertions(+), 6 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 22cd22d3c..0e2090b2d 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 @@ -1036,11 +1036,26 @@ open class FullScreenPlayer : AbstractPlayerFragment() { val holdhandler = Handler(Looper.getMainLooper()) var hasTriggeredSpeedUp = false val holdRunnable = Runnable { + if (isShowing) { + onClickChange() + } player.setPlaybackSpeed(2.0f) - playerBinding?.playerSpeedupButton?.isGone = false + showOrHideSpeedUp(true) hasTriggeredSpeedUp = true } + private fun showOrHideSpeedUp(show:Boolean){ + playerBinding?.playerSpeedupButton?.let { button -> + button.clearAnimation() + button.alpha = if(show) 0f else 1f + button.isVisible = show + button.animate() + .alpha(if(show) 1f else 0f) + .setDuration(200L) + .start() + } + } + @SuppressLint("SetTextI18n") private fun handleMotionEvent(view: View?, event: MotionEvent?): Boolean { if (event == null || view == null) return false @@ -1084,7 +1099,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { holdhandler.removeCallbacks(holdRunnable) if (hasTriggeredSpeedUp) { player.setPlaybackSpeed(DataStoreHelper.playBackSpeed) - playerSpeedupButton?.isGone = true + showOrHideSpeedUp(false) } if (isCurrentTouchValid && !isLocked && isFullScreenPlayer) { // seek time diff --git a/app/src/main/res/layout/player_custom_layout.xml b/app/src/main/res/layout/player_custom_layout.xml index f94c30d9e..adea09379 100644 --- a/app/src/main/res/layout/player_custom_layout.xml +++ b/app/src/main/res/layout/player_custom_layout.xml @@ -984,7 +984,7 @@ android:layout_width="45dp" android:layout_height="45dp" android:layout_gravity="center_horizontal" - android:layout_marginTop="60dp" + android:layout_marginTop="30dp" app:iconPadding="0dp" app:iconGravity="textStart" android:clickable="false" diff --git a/app/src/main/res/layout/trailer_custom_layout.xml b/app/src/main/res/layout/trailer_custom_layout.xml index f8c2882ce..0e1f80d83 100644 --- a/app/src/main/res/layout/trailer_custom_layout.xml +++ b/app/src/main/res/layout/trailer_custom_layout.xml @@ -917,13 +917,16 @@ android:layout_width="45dp" android:layout_height="45dp" android:layout_gravity="center_horizontal" - android:layout_marginTop="70dp" - app:iconGravity="top" + android:layout_marginTop="30dp" + app:iconPadding="0dp" + app:iconGravity="textStart" android:clickable="false" + android:focusable="false" android:textAllCaps="false" android:visibility="gone" app:icon="@drawable/speedup" app:iconTint="@color/textColor" - app:rippleColor="?attr/colorPrimary" + android:foreground="@null" + android:backgroundTint="@color/skipOpTransparent" tools:visibility="visible" /> \ No newline at end of file From 569f593793e6afff18db6375c030337b5149110e Mon Sep 17 00:00:00 2001 From: Diogo <24511782+diogob003@users.noreply.github.com> Date: Fri, 5 Sep 2025 15:02:05 -0300 Subject: [PATCH 020/640] SubtitleHelper: add IETF BCP 47 and OpenSubtitles (#1863) --- .../providers/OpenSubtitlesApi.kt | 35 +- .../cloudstream3/ui/player/GeneratorPlayer.kt | 2 +- .../ui/subtitles/SubtitlesFragment.kt | 2 +- .../cloudstream3/utils/SubtitleHelper.kt | 1198 ++++++++++------- 4 files changed, 691 insertions(+), 546 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 02f828a22..4b17fdb29 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 @@ -2,10 +2,9 @@ package com.lagradost.cloudstream3.syncproviders.providers import android.util.Log import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.ErrorLoadingException import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.TvType -import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.subtitles.AbstractSubtitleEntities import com.lagradost.cloudstream3.syncproviders.AuthData import com.lagradost.cloudstream3.syncproviders.AuthLoginRequirement @@ -13,9 +12,12 @@ import com.lagradost.cloudstream3.syncproviders.AuthLoginResponse import com.lagradost.cloudstream3.syncproviders.AuthToken import com.lagradost.cloudstream3.syncproviders.AuthUser import com.lagradost.cloudstream3.syncproviders.SubtitleAPI +import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.utils.AppUtils import com.lagradost.cloudstream3.utils.AppUtils.parseJson import com.lagradost.cloudstream3.utils.AppUtils.toJson +import com.lagradost.cloudstream3.utils.SubtitleHelper.fromCodeToLangTagIETF +import com.lagradost.cloudstream3.utils.SubtitleHelper.fromCodeToOpenSubtitlesTag class OpenSubtitlesApi : SubtitleAPI() { override val name = "OpenSubtitles" @@ -92,24 +94,6 @@ class OpenSubtitlesApi : SubtitleAPI() { ) } - /** - * Some languages do not use the normal country codes on OpenSubtitles - * */ - private val languageExceptions = mapOf( -// "pt" to "pt-PT", -// "pt" to "pt-BR" - ) - - private fun fixLanguage(language: String?): String? { - return languageExceptions[language] ?: language - } - - // O(n) but good enough, BiMap did not want to work properly - private fun fixLanguageReverse(language: String?): String? { - return languageExceptions.entries.firstOrNull { it.value == language }?.key ?: language - } - - /** * Fetch subtitles using token authenticated on previous method (see authorize). * Returns list of Subtitles which user can select to download (see load). @@ -119,7 +103,7 @@ class OpenSubtitlesApi : SubtitleAPI() { query: AbstractSubtitleEntities.SubtitleSearch ): List? { throwIfCantDoRequest() - val fixedLang = fixLanguage(query.lang) + val langOpenSubTag = fromCodeToOpenSubtitlesTag(query.lang) ?: query.lang ?: "" val imdbId = query.imdbId?.replace("tt", "")?.toInt() ?: 0 val queryText = query.query @@ -132,8 +116,8 @@ class OpenSubtitlesApi : SubtitleAPI() { val searchQueryUrl = when (imdbId > 0) { //Use imdb_id to search if its valid - true -> "$HOST/subtitles?imdb_id=$imdbId&languages=${fixedLang}$yearQuery$epQuery$seasonQuery" - false -> "$HOST/subtitles?query=${queryText}&languages=${fixedLang}$yearQuery$epQuery$seasonQuery" + true -> "$HOST/subtitles?imdb_id=$imdbId&languages=${langOpenSubTag}$yearQuery$epQuery$seasonQuery" + false -> "$HOST/subtitles?query=${queryText}&languages=${langOpenSubTag}$yearQuery$epQuery$seasonQuery" } val req = app.get( @@ -142,6 +126,7 @@ class OpenSubtitlesApi : SubtitleAPI() { Pair("Content-Type", "application/json") ) + headers, ) + Log.i(TAG, "searchQueryUrl => ${searchQueryUrl}") Log.i(TAG, "Search Req => ${req.text}") if (!req.isSuccessful) { if (req.code == 429) @@ -162,7 +147,7 @@ class OpenSubtitlesApi : SubtitleAPI() { //Use any valid name/title in hierarchy val name = filename ?: featureDetails?.movieName ?: featureDetails?.title ?: featureDetails?.parentTitle ?: attr.release ?: query.query - val lang = fixLanguageReverse(attr.language) ?: "" + val langTagIETF = fromCodeToLangTagIETF(attr.language) ?: "" val resEpNum = featureDetails?.episodeNumber ?: query.epNumber val resSeasonNum = featureDetails?.seasonNumber ?: query.seasonNumber val year = featureDetails?.year ?: query.year @@ -176,7 +161,7 @@ class OpenSubtitlesApi : SubtitleAPI() { AbstractSubtitleEntities.SubtitleEntity( idPrefix = this.idPrefix, name = name, - lang = lang, + lang = langTagIETF, data = resultData, type = type, source = this.name, 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 be9d01bbe..d79e4b8a3 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 @@ -1681,7 +1681,7 @@ class GeneratorPlayer : FullScreenPlayer() { sortSubs(subtitles).firstOrNull { sub -> val t = sub.name.replace(Regex("[^A-Za-z]"), " ").trim() - (settings) && t == lang || t.startsWith(lang) || t == langCode + settings && t == lang || t.startsWith(lang) || t == langCode }?.let { sub -> return sub } 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 e5671fa80..ceb859790 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 @@ -672,7 +672,7 @@ class SubtitlesFragment : DialogFragment() { subsAutoSelectLanguage.setFocusableInTv() subsAutoSelectLanguage.setOnClickListener { textView -> val langMap = arrayListOf( - SubtitleHelper.Language639( + SubtitleHelper.LanguageMetadata( textView.context.getString(R.string.none), textView.context.getString(R.string.none), "", diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt index c65b2b14b..e3b7803a9 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt @@ -1,519 +1,679 @@ -package com.lagradost.cloudstream3.utils - -import com.lagradost.cloudstream3.mvvm.logError -import java.util.Locale - -object SubtitleHelper { - data class Language639( - val languageName: String, - val nativeName: String, - val ISO_639_1: String, - val ISO_639_2_T: String, - val ISO_639_2_B: String, - val ISO_639_3: String, - val ISO_639_6: String, - ) - - /*fun createISO() { - val url = "https://infogalactic.com/info/List_of_ISO_639-1_codes" - val response = get(url).text - val document = Jsoup.parse(response) - val headers = document.select("table.wikitable > tbody > tr") - - var text = "listOf(\n" - for (head in headers) { - val tds = head.select("td") - if (tds.size < 8) continue - val name = tds[2].selectFirst("> a").text() - val native = tds[3].text() - val ISO_639_1 = tds[4].ownText().replace("+", "").replace(" ", "") - val ISO_639_2_T = tds[5].ownText().replace("+", "").replace(" ", "") - val ISO_639_2_B = tds[6].ownText().replace("+", "").replace(" ", "") - val ISO_639_3 = tds[7].ownText().replace("+", "").replace(" ", "") - val ISO_639_6 = tds[8].ownText().replace("+", "").replace(" ", "") - - val txtAdd = - "Language(\"$name\", \"$native\", \"$ISO_639_1\", \"$ISO_639_2_T\", \"$ISO_639_2_B\", \"$ISO_639_3\", \"$ISO_639_6\"),\n" - text += txtAdd - } - text += ")" - println("ISO CREATED:\n$text") - }*/ - - /** lang -> ISO_639_1 - * @param looseCheck will use .contains in addition to .equals - * */ - fun fromLanguageToTwoLetters(input: String, looseCheck: Boolean): String? { - languages.forEach { - if (it.languageName.equals(input, ignoreCase = true) - || it.nativeName.equals(input, ignoreCase = true) - ) return it.ISO_639_1 - } - - // Runs as a separate loop as to prioritize fully matching languages. - if (looseCheck) - languages.forEach { - if (input.contains(it.languageName, ignoreCase = true) - || input.contains(it.nativeName, ignoreCase = true) - ) return it.ISO_639_1 - } - - return null - } - - private var ISO_639_1Map: HashMap = hashMapOf() - private fun initISO6391Map() { - for (lang in languages) { - ISO_639_1Map[lang.ISO_639_1] = lang.languageName - } - } - - /** ISO_639_1 -> lang*/ - fun fromTwoLettersToLanguage(input: String): String? { - // pr-BR - if (input.substringBefore("-").length != 2) return null - if (ISO_639_1Map.isEmpty()) { - initISO6391Map() - } - val comparison = input.lowercase(Locale.ROOT) - - return ISO_639_1Map[comparison] - } - - /**ISO_639_2_B or ISO_639_2_T or ISO_639_3-> lang*/ - fun fromThreeLettersToLanguage(input: String): String? { - if (input.length != 3) return null - val comparison = input.lowercase(Locale.ROOT) - for (lang in languages) { - if (lang.ISO_639_2_B == comparison) { - return lang.languageName - } - } - for (lang in languages) { - if (lang.ISO_639_2_T == comparison) { - return lang.languageName - } - } - for (lang in languages) { - if (lang.ISO_639_3 == comparison) { - return lang.languageName - } - } - return null - } - - /** lang -> ISO_639_2_T*/ - fun fromLanguageToThreeLetters(input: String): String? { - for (lang in languages) { - if (lang.languageName == input || lang.nativeName == input) { - return lang.ISO_639_2_T - } - } - return null - } - - private const val flagOffset = 0x1F1E6 - private const val asciiOffset = 0x41 - private const val offset = flagOffset - asciiOffset - - private val flagRegex = Regex("[\uD83C\uDDE6-\uD83C\uDDFF]{2}") - - fun getFlagFromIso(inp: String?): String? { - if (inp.isNullOrBlank() || inp.length < 2) return null - - try { - val ret = getFlagFromIsoShort(flags[inp]) - ?: getFlagFromIsoShort(inp.uppercase()) ?: return null - - return if (flagRegex.matches(ret)) { - ret - } else { - null - } - } catch (e: Exception) { - logError(e) - return null - } - } - - private fun getFlagFromIsoShort(flagAscii: String?): String? { - if (flagAscii.isNullOrBlank() || flagAscii.length < 2) return null - return try { - val firstChar: Int = Character.codePointAt(flagAscii, 0) + offset - val secondChar: Int = Character.codePointAt(flagAscii, 1) + offset - - (String(Character.toChars(firstChar)) + String(Character.toChars(secondChar))) - } catch (e: Exception) { - logError(e) - null - } - } - - private val flags = mapOf( - "af" to "ZA", - "agq" to "CM", - "ajp" to "SY", - "ak" to "GH", - "am" to "ET", - "ar" to "AE", - "ars" to "SA", - "as" to "IN", - "asa" to "TZ", - "az" to "AZ", - "bas" to "CM", - "be" to "BY", - "bem" to "ZM", - "bez" to "IT", - "bg" to "BG", - "bm" to "ML", - "bn" to "BD", - "bo" to "CN", - "br" to "FR", - "brx" to "IN", - "bs" to "BA", - "ca" to "ES", - "cgg" to "UG", - "chr" to "US", - "cs" to "CZ", - "cy" to "GB", - "da" to "DK", - "dav" to "KE", - "de" to "DE", - "dje" to "NE", - "dua" to "CM", - "dyo" to "SN", - "ebu" to "KE", - "ee" to "GH", - "en" to "GB", - "el" to "GR", - "es" to "ES", - "et" to "EE", - "eu" to "ES", - "ewo" to "CM", - "fa" to "IR", - "fil" to "PH", - "fr" to "FR", - "ga" to "IE", - "gl" to "ES", - "gsw" to "CH", - "gu" to "IN", - "guz" to "KE", - "gv" to "GB", - "ha" to "NG", - "haw" to "US", - "he" to "IL", - "hi" to "IN", - "ff" to "CN", - "fi" to "FI", - "fo" to "FO", - "hr" to "HR", - "hu" to "HU", - "hy" to "AM", - "id" to "ID", - "ig" to "NG", - "ii" to "CN", - "is" to "IS", - "it" to "IT", - "ita" to "IT", - "ja" to "JP", - "jmc" to "TZ", - "ka" to "GE", - "kab" to "DZ", - "ki" to "KE", - "kam" to "KE", - "mer" to "KE", - "kde" to "TZ", - "kea" to "CV", - "khq" to "ML", - "kk" to "KZ", - "kl" to "GL", - "kln" to "KE", - "km" to "KH", - "kn" to "IN", - "ko" to "KR", - "kok" to "IN", - "ksb" to "TZ", - "ksf" to "CM", - "kw" to "GB", - "lag" to "TZ", - "lg" to "UG", - "ln" to "CG", - "lt" to "LT", - "lu" to "CD", - "lv" to "LV", - "lat" to "LV", - "luo" to "KE", - "luy" to "KE", - "mas" to "TZ", - "mfe" to "MU", - "mg" to "MG", - "mgh" to "MZ", - "ml" to "IN", - "mk" to "MK", - "mr" to "IN", - "ms" to "MY", - "mt" to "MT", - "mua" to "CM", - "my" to "MM", - "naq" to "NA", - "nb" to "NO", - "no" to "NO", - "nn" to "NO", - "nd" to "ZW", - "ne" to "NP", - "nl" to "NL", - "nmg" to "CM", - "nus" to "SD", - "nyn" to "UG", - "om" to "ET", - "or" to "IN", - "pa" to "PK", - "pl" to "PL", - "ps" to "AF", - "pt" to "PT", - "pt-pt" to "PT", - "pt-br" to "BR", - "rm" to "CH", - "rn" to "BI", - "ro" to "RO", - "ru" to "RU", - "rw" to "RW", - "rof" to "TZ", - "rwk" to "TZ", - "saq" to "KE", - "sbp" to "TZ", - "seh" to "MZ", - "ses" to "ML", - "sg" to "CF", - "shi" to "MA", - "si" to "LK", - "sk" to "SK", - "sl" to "SI", - "sn" to "ZW", - "so" to "SO", - "sq" to "AL", - "sr" to "RS", - "sv" to "SE", - "sw" to "TZ", - "swc" to "CD", - "ta" to "IN", - "te" to "IN", - "teo" to "UG", - "th" to "TH", - "ti" to "ET", - "to" to "TO", - "tr" to "TR", - "twq" to "NE", - "tzm" to "MA", - "uk" to "UA", - "ur" to "PK", - "uz" to "UZ", - "vai" to "LR", - "vi" to "VN", - "vun" to "TZ", - "xog" to "UG", - "yav" to "CM", - "yo" to "NG", - "zh" to "CN", - "zu" to "ZA", - "tl" to "PH", - ) - - val languages = listOf( - Language639("Abkhaz", "аҧсуа бызшәа, аҧсшәа", "ab", "abk", "abk", "abk", "abks"), - Language639("Afar", "Afaraf", "aa", "aar", "aar", "aar", "aars"), - Language639("Afrikaans", "Afrikaans", "af", "afr", "afr", "afr", "afrs"), - Language639("Akan", "Akan", "ak", "aka", "aka", "aka", ""), - Language639("Albanian", "Shqip", "sq", "sqi", "", "sqi", ""), - Language639("Amharic", "አማርኛ", "am", "amh", "amh", "amh", ""), - Language639("Arabic", "العربية", "ar", "ara", "ara", "ara", ""), - Language639("Aragonese", "aragonés", "an", "arg", "arg", "arg", ""), - Language639("Armenian", "Հայերեն", "hy", "hye", "", "hye", ""), - Language639("Assamese", "অসমীয়া", "as", "asm", "asm", "asm", ""), - Language639("Avaric", "авар мацӀ, магӀарул мацӀ", "av", "ava", "ava", "ava", ""), - Language639("Avestan", "avesta", "ae", "ave", "ave", "ave", ""), - Language639("Aymara", "aymar aru", "ay", "aym", "aym", "aym", ""), - Language639("Azerbaijani", "azərbaycan dili", "az", "aze", "aze", "aze", ""), - Language639("Bambara", "bamanankan", "bm", "bam", "bam", "bam", ""), - Language639("Bashkir", "башҡорт теле", "ba", "bak", "bak", "bak", ""), - Language639("Basque", "euskara, euskera", "eu", "eus", "", "eus", ""), - Language639("Belarusian", "беларуская мова", "be", "bel", "bel", "bel", ""), - Language639("Bengali", "বাংলা", "bn", "ben", "ben", "ben", ""), - Language639("Bihari", "भोजपुरी", "bh", "bih", "bih", "", ""), - Language639("Bislama", "Bislama", "bi", "bis", "bis", "bis", ""), - Language639("Bosnian", "bosanski jezik", "bs", "bos", "bos", "bos", "boss"), - Language639("Breton", "brezhoneg", "br", "bre", "bre", "bre", ""), - Language639("Bulgarian", "български език", "bg", "bul", "bul", "bul", "buls"), - Language639("Burmese", "ဗမာစာ", "my", "mya", "", "mya", ""), - Language639("Catalan", "català", "ca", "cat", "cat", "cat", ""), - Language639("Chamorro", "Chamoru", "ch", "cha", "cha", "cha", ""), - Language639("Chechen", "нохчийн мотт", "ce", "che", "che", "che", ""), - Language639("Chichewa", "chiCheŵa, chinyanja", "ny", "nya", "nya", "nya", ""), - Language639("Chinese", "中文 (Zhōngwén), 汉语, 漢語", "zh", "zho", "", "zho", ""), - Language639("Chuvash", "чӑваш чӗлхи", "cv", "chv", "chv", "chv", ""), - Language639("Cornish", "Kernewek", "kw", "cor", "cor", "cor", ""), - Language639("Corsican", "corsu, lingua corsa", "co", "cos", "cos", "cos", ""), - Language639("Cree", "ᓀᐦᐃᔭᐍᐏᐣ", "cr", "cre", "cre", "cre", ""), - Language639("Croatian", "hrvatski jezik", "hr", "hrv", "hrv", "hrv", ""), - Language639("Czech", "čeština, český jazyk", "cs", "ces", "", "ces", ""), - Language639("Danish", "dansk", "da", "dan", "dan", "dan", ""), - Language639("Divehi", "ދިވެހި", "dv", "div", "div", "div", ""), - Language639("Dutch", "Nederlands, Vlaams", "nl", "nld", "", "nld", ""), - Language639("Dzongkha", "རྫོང་ཁ", "dz", "dzo", "dzo", "dzo", ""), - Language639("English", "English", "en", "eng", "eng", "eng", "engs"), - Language639("Esperanto", "Esperanto", "eo", "epo", "epo", "epo", ""), - Language639("Estonian", "eesti, eesti keel", "et", "est", "est", "est", ""), - Language639("Ewe", "Eʋegbe", "ee", "ewe", "ewe", "ewe", ""), - Language639("Faroese", "føroyskt", "fo", "fao", "fao", "fao", ""), - Language639("Fijian", "vosa Vakaviti", "fj", "fij", "fij", "fij", ""), - Language639("Finnish", "suomi, suomen kieli", "fi", "fin", "fin", "fin", ""), - Language639("French", "français, langue française", "fr", "fra", "", "fra", "fras"), - Language639("Fula", "Fulfulde, Pulaar, Pular", "ff", "ful", "ful", "ful", ""), - Language639("Galician", "galego", "gl", "glg", "glg", "glg", ""), - Language639("Georgian", "ქართული", "ka", "kat", "", "kat", ""), - Language639("German", "Deutsch", "de", "deu", "", "deu", "deus"), - Language639("Greek", "ελληνικά", "el", "ell", "", "ell", "ells"), - Language639("Guaraní", "Avañe'ẽ", "gn", "grn", "grn", "grn", ""), - Language639("Gujarati", "ગુજરાતી", "gu", "guj", "guj", "guj", ""), - Language639("Haitian", "Kreyòl ayisyen", "ht", "hat", "hat", "hat", ""), - Language639("Hausa", "(Hausa) هَوُسَ", "ha", "hau", "hau", "hau", ""), - Language639("Hebrew", "עברית", "he", "heb", "heb", "heb", ""), - Language639("Herero", "Otjiherero", "hz", "her", "her", "her", ""), - Language639("Hindi", "हिन्दी, हिंदी", "hi", "hin", "hin", "hin", "hins"), - Language639("Hiri Motu", "Hiri Motu", "ho", "hmo", "hmo", "hmo", ""), - Language639("Hungarian", "magyar", "hu", "hun", "hun", "hun", ""), - Language639("Interlingua", "Interlingua", "ia", "ina", "ina", "ina", ""), - Language639("Indonesian", "Bahasa Indonesia", "id", "ind", "ind", "ind", ""), - Language639( - "Interlingue", - "Originally called Occidental; then Interlingue after WWII", - "ie", - "ile", - "ile", - "ile", - "" - ), - Language639("Irish", "Gaeilge", "ga", "gle", "gle", "gle", ""), - Language639("Igbo", "Asụsụ Igbo", "ig", "ibo", "ibo", "ibo", ""), - Language639("Inupiaq", "Iñupiaq, Iñupiatun", "ik", "ipk", "ipk", "ipk", ""), - Language639("Ido", "Ido", "io", "ido", "ido", "ido", "idos"), - Language639("Icelandic", "Íslenska", "is", "isl", "", "isl", ""), - Language639("Italian", "italiano", "it", "ita", "ita", "ita", "itas"), - Language639("Inuktitut", "ᐃᓄᒃᑎᑐᑦ", "iu", "iku", "iku", "iku", ""), - Language639("Japanese", "日本語 (にほんご)", "ja", "jpn", "jpn", "jpn", ""), - Language639("Javanese", "ꦧꦱꦗꦮ", "jv", "jav", "jav", "jav", ""), - Language639("Kalaallisut", "kalaallisut, kalaallit oqaasii", "kl", "kal", "kal", "kal", ""), - Language639("Kannada", "ಕನ್ನಡ", "kn", "kan", "kan", "kan", ""), - Language639("Kanuri", "Kanuri", "kr", "kau", "kau", "kau", ""), - Language639("Kashmiri", "कश्मीरी, كشميري‎", "ks", "kas", "kas", "kas", ""), - Language639("Kazakh", "қазақ тілі", "kk", "kaz", "kaz", "kaz", ""), - Language639("Khmer", "ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ", "km", "khm", "khm", "khm", ""), - Language639("Kikuyu", "Gĩkũyũ", "ki", "kik", "kik", "kik", ""), - Language639("Kinyarwanda", "Ikinyarwanda", "rw", "kin", "kin", "kin", ""), - Language639("Kyrgyz", "Кыргызча, Кыргыз тили", "ky", "kir", "kir", "kir", ""), - Language639("Komi", "коми кыв", "kv", "kom", "kom", "kom", ""), - Language639("Kongo", "Kikongo", "kg", "kon", "kon", "kon", ""), - Language639("Korean", "한국어, 조선어", "ko", "kor", "kor", "kor", ""), - Language639("Kurdish", "Kurdî, كوردی‎", "ku", "kur", "kur", "kur", ""), - Language639("Kwanyama", "Kuanyama", "kj", "kua", "kua", "kua", ""), - Language639("Latin", "latine, lingua latina", "la", "lat", "lat", "lat", "lats"), - Language639("Luxembourgish", "Lëtzebuergesch", "lb", "ltz", "ltz", "ltz", ""), - Language639("Ganda", "Luganda", "lg", "lug", "lug", "lug", ""), - Language639("Limburgish", "Limburgs", "li", "lim", "lim", "lim", ""), - Language639("Lingala", "Lingála", "ln", "lin", "lin", "lin", ""), - Language639("Lao", "ພາສາລາວ", "lo", "lao", "lao", "lao", ""), - Language639("Lithuanian", "lietuvių kalba", "lt", "lit", "lit", "lit", ""), - Language639("Luba-Katanga", "Tshiluba", "lu", "lub", "lub", "lub", ""), - Language639("Latvian", "latviešu valoda", "lv", "lav", "lav", "lav", ""), - Language639("Manx", "Gaelg, Gailck", "gv", "glv", "glv", "glv", ""), - Language639("Macedonian", "македонски јазик", "mk", "mkd", "", "mkd", ""), - Language639("Malagasy", "fiteny malagasy", "mg", "mlg", "mlg", "mlg", ""), - Language639("Malay", "bahasa Melayu, بهاس ملايو‎", "ms", "msa", "", "msa", ""), - Language639("Malayalam", "മലയാളം", "ml", "mal", "mal", "mal", ""), - Language639("Maltese", "Malti", "mt", "mlt", "mlt", "mlt", ""), - Language639("Māori", "te reo Māori", "mi", "mri", "", "mri", ""), - Language639("Marathi", "मराठी", "mr", "mar", "mar", "mar", ""), - Language639("Marshallese", "Kajin M̧ajeļ", "mh", "mah", "mah", "mah", ""), - Language639("Mongolian", "Монгол хэл", "mn", "mon", "mon", "mon", ""), - Language639("Nauruan", "Dorerin Naoero", "na", "nau", "nau", "nau", ""), - Language639("Navajo", "Diné bizaad", "nv", "nav", "nav", "nav", ""), - Language639("Northern Ndebele", "isiNdebele", "nd", "nde", "nde", "nde", ""), - Language639("Nepali", "नेपाली", "ne", "nep", "nep", "nep", ""), - Language639("Ndonga", "Owambo", "ng", "ndo", "ndo", "ndo", ""), - Language639("Norwegian Bokmål", "Norsk bokmål", "nb", "nob", "nob", "nob", ""), - Language639("Norwegian Nynorsk", "Norsk nynorsk", "nn", "nno", "nno", "nno", ""), - Language639("Norwegian", "Norsk", "no", "nor", "nor", "nor", ""), - Language639("Nuosu", "ꆈꌠ꒿ Nuosuhxop", "ii", "iii", "iii", "iii", ""), - Language639("Southern Ndebele", "isiNdebele", "nr", "nbl", "nbl", "nbl", ""), - Language639("Occitan", "occitan, lenga d'òc", "oc", "oci", "oci", "oci", ""), - Language639("Ojibwe", "ᐊᓂᔑᓈᐯᒧᐎᓐ", "oj", "oji", "oji", "oji", ""), - Language639("Old Church Slavonic", "ѩзыкъ словѣньскъ", "cu", "chu", "chu", "chu", ""), - Language639("Oromo", "Afaan Oromoo", "om", "orm", "orm", "orm", ""), - Language639("Oriya", "ଓଡ଼ିଆ", "or", "ori", "ori", "ori", ""), - Language639("Ossetian", "ирон æвзаг", "os", "oss", "oss", "oss", ""), - Language639("Panjabi", "ਪੰਜਾਬੀ, پنجابی‎", "pa", "pan", "pan", "pan", ""), - Language639("Pāli", "पाऴि", "pi", "pli", "pli", "pli", ""), - Language639("Persian", "فارسی", "fa", "fas", "", "fas", ""), - Language639("Polish", "język polski, polszczyzna", "pl", "pol", "pol", "pol", "pols"), - Language639("Pashto", "پښتو", "ps", "pus", "pus", "pus", ""), - Language639("Portuguese", "português", "pt-pt", "por", "por", "por", ""), - // Addition to support Brazilian Portuguese properly, might break other things - Language639("Portuguese (Brazilian)", "português", "pt-br", "por", "por", "por", ""), - Language639("Quechua", "Runa Simi, Kichwa", "qu", "que", "que", "que", ""), - Language639("Romansh", "rumantsch grischun", "rm", "roh", "roh", "roh", ""), - Language639("Kirundi", "Ikirundi", "rn", "run", "run", "run", ""), - Language639("Reunion Creole", "Kréol Rénioné", "rc", "rcf", "rcf", "rcf", ""), - Language639("Romanian", "limba română", "ro", "ron", "", "ron", ""), - Language639("Russian", "Русский", "ru", "rus", "rus", "rus", ""), - Language639("Sanskrit", "संस्कृतम्", "sa", "san", "san", "san", ""), - Language639("Sardinian", "sardu", "sc", "srd", "srd", "srd", ""), - Language639("Sindhi", "सिन्धी, سنڌي، سندھی‎", "sd", "snd", "snd", "snd", ""), - Language639("Northern Sami", "Davvisámegiella", "se", "sme", "sme", "sme", ""), - Language639("Samoan", "gagana fa'a Samoa", "sm", "smo", "smo", "smo", ""), - Language639("Sango", "yângâ tî sängö", "sg", "sag", "sag", "sag", ""), - Language639("Serbian", "српски језик", "sr", "srp", "srp", "srp", ""), - Language639("Scottish Gaelic", "Gàidhlig", "gd", "gla", "gla", "gla", ""), - Language639("Shona", "chiShona", "sn", "sna", "sna", "sna", ""), - Language639("Sinhalese", "සිංහල", "si", "sin", "sin", "sin", ""), - Language639("Slovak", "slovenčina, slovenský jazyk", "sk", "slk", "", "slk", ""), - Language639("Slovene", "slovenski jezik, slovenščina", "sl", "slv", "slv", "slv", ""), - Language639("Somali", "Soomaaliga, af Soomaali", "so", "som", "som", "som", ""), - Language639("Southern Sotho", "Sesotho", "st", "sot", "sot", "sot", ""), - Language639("Spanish", "español", "es", "spa", "spa", "spa", ""), - Language639("Sundanese", "Basa Sunda", "su", "sun", "sun", "sun", ""), - Language639("Swahili", "Kiswahili", "sw", "swa", "swa", "swa", ""), - Language639("Swati", "SiSwati", "ss", "ssw", "ssw", "ssw", ""), - Language639("Swedish", "svenska", "sv", "swe", "swe", "swe", ""), - Language639("Tamil", "தமிழ்", "ta", "tam", "tam", "tam", ""), - Language639("Telugu", "తెలుగు", "te", "tel", "tel", "tel", ""), - Language639("Tajik", "тоҷикӣ, toçikī, تاجیکی‎", "tg", "tgk", "tgk", "tgk", ""), - Language639("Thai", "ไทย", "th", "tha", "tha", "tha", ""), - Language639("Tigrinya", "ትግርኛ", "ti", "tir", "tir", "tir", ""), - Language639("Tibetan Standard", "བོད་ཡིག", "bo", "bod", "", "bod", ""), - Language639("Turkmen", "Türkmen, Түркмен", "tk", "tuk", "tuk", "tuk", ""), - Language639("Tagalog", "Wikang Tagalog", "tl", "tgl", "tgl", "tgl", ""), - Language639("Tswana", "Setswana", "tn", "tsn", "tsn", "tsn", ""), - Language639("Tonga", "faka Tonga", "to", "ton", "ton", "ton", ""), - Language639("Turkish", "Türkçe", "tr", "tur", "tur", "tur", ""), - Language639("Tsonga", "Xitsonga", "ts", "tso", "tso", "tso", ""), - Language639("Tatar", "татар теле, tatar tele", "tt", "tat", "tat", "tat", ""), - Language639("Twi", "Twi", "tw", "twi", "twi", "twi", ""), - Language639("Tahitian", "Reo Tahiti", "ty", "tah", "tah", "tah", ""), - Language639("Uyghur", "ئۇيغۇرچە‎, Uyghurche", "ug", "uig", "uig", "uig", ""), - Language639("Ukrainian", "Українська", "uk", "ukr", "ukr", "ukr", ""), - Language639("Urdu", "اردو", "ur", "urd", "urd", "urd", ""), - Language639("Uzbek", "Oʻzbek, Ўзбек, أۇزبېك‎", "uz", "uzb", "uzb", "uzb", ""), - Language639("Venda", "Tshivenḓa", "ve", "ven", "ven", "ven", ""), - Language639("Vietnamese", "Tiếng Việt", "vi", "vie", "vie", "vie", ""), - Language639("Volapük", "Volapük", "vo", "vol", "vol", "vol", ""), - Language639("Walloon", "walon", "wa", "wln", "wln", "wln", ""), - Language639("Welsh", "Cymraeg", "cy", "cym", "", "cym", ""), - Language639("Wolof", "Wollof", "wo", "wol", "wol", "wol", ""), - Language639("Western Frisian", "Frysk", "fy", "fry", "fry", "fry", ""), - Language639("Xhosa", "isiXhosa", "xh", "xho", "xho", "xho", ""), - Language639("Yiddish", "ייִדיש", "yi", "yid", "yid", "yid", ""), - Language639("Yoruba", "Yorùbá", "yo", "yor", "yor", "yor", ""), - Language639("Zhuang", "Saɯ cueŋƅ, Saw cuengh", "za", "zha", "zha", "zha", ""), - Language639("Zulu", "isiZulu", "zu", "zul", "zul", "zul", ""), - ) -} +package com.lagradost.cloudstream3.utils + +import com.lagradost.cloudstream3.Prerelease +import java.util.Locale +import kotlin.text.RegexOption.IGNORE_CASE + +// If you find a way to use SettingsGeneral getCurrentLocale() +// instead of this function do it. +fun getCurrentLocale(): String { + return Locale.getDefault().toLanguageTag() +} + +object SubtitleHelper { + @Deprecated( + "Default language code changed to IETF BCP 47 tag", + ReplaceWith("LanguageMetadata(languageName, nativeName, ISO_639_1.ifBlank { ISO_639_2_B }), ISO_639_1, ISO_639_2_B, ISO_639_3, ISO_639_1")) + data class Language639( + val languageName: String, + val nativeName: String, + val ISO_639_1: String, + val ISO_639_2_T: String, + val ISO_639_2_B: String, + val ISO_639_3: String, + val ISO_639_6: String, + ) + + /** + * Represents one language with english name, native name and codes. + * IETF BCP 47 conformant tag shall be used as the first choice for language code! + * + * See locales on: + * https://github.com/unicode-org/cldr-json/blob/main/cldr-json/cldr-core/availableLocales.json + * https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry + * https://android.googlesource.com/platform/frameworks/base/+/android-16.0.0_r2/core/res/res/values/locale_config.xml + * https://iso639-3.sil.org/code_tables/639/data/all + */ + data class LanguageMetadata( + val languageName: String, + val nativeName: String, + val IETF_tag: String, // Combine ISO 639-1, 639-3 and ISO 3166-1 (or numeric UN M49 / CLDR) for country / region + val ISO_639_1: String, + val ISO_639_2_B: String, // ISO 639-2/T missing as it's a subset of ISO 639-3 + val ISO_639_3: String, // ISO 639-6 missing as it's intended to differentiate specific dialects and variants + val openSubtitles: String, // inconsistent codes that do not conform ISO 639 + ) { + internal fun localizedName(localizedTo: String? = null): String { + // Use system locale to localize language name + val localeOfLangCode = Locale.forLanguageTag(this.IETF_tag) + val localeOfLocalizeTo = Locale.forLanguageTag(localizedTo ?: getCurrentLocale()) + val sysLocalizedName = localeOfLangCode.getDisplayName(localeOfLocalizeTo) + + val langCodeWithCountry = "${localeOfLangCode.language} (${localeOfLangCode.country})" + val failedToLocalize = + sysLocalizedName.contains(this.IETF_tag, ignoreCase = true) || + sysLocalizedName.contains(langCodeWithCountry, ignoreCase = true) + + return if (failedToLocalize) + // fallback to native language name + this.nativeName + else + sysLocalizedName + } + + internal fun nameNextToFlagEmoji(localizedTo: String? = null): String { + // fallback to [A][A] -> [?] question mak flag + val flag = getFlagFromIso(this.IETF_tag) ?: "\ud83c\udde6\ud83c\udde6" + + return "$flag ${localizedName(localizedTo)}" + } + } + + /** + * Language name (english or native) -> [LanguageMetadata] + * @param languageName language name + * @param halfMatch match with `contains()` instead of `equals()` + */ + private fun getLanguageDataFromName(languageName: String?, halfMatch: Boolean? = false): LanguageMetadata? { + if (languageName.isNullOrBlank() || languageName.length < 2) return null + + val lowLangName = languageName.lowercase() + val index = + indexMapLanguageName[lowLangName] ?: + indexMapNativeName[lowLangName] ?: -1 + val langMetadata = languages.getOrNull(index) + + if (halfMatch == true && langMetadata == null) { + // Workaround to avoid junk like "English (original audio)" or "Spanish 123" + // or "اَلْعَرَبِيَّةُ (Original Audio) 1" or "English (hindi sub)"… + // Subtitle downloads and auto selection should rely on proper language codes + // instead of language names! And remove remove junk beforehand. + val arabicDiacritics = Regex("[\\u064B-\\u065B]") + val withoutDiacritics = lowLangName.replace(arabicDiacritics, "") + val nameWithoutJunk = Regex("^([^()\\s\\d]+)").find(withoutDiacritics)?.value ?: withoutDiacritics + + for (lang in languages) + if (lang.languageName.contains(nameWithoutJunk, ignoreCase = true) || + lang.nativeName.contains(nameWithoutJunk, ignoreCase = true)) + return lang + } + return langMetadata + } + + // @Deprecated( + // "Default language code changed to IETF BCP 47 tag", + // ReplaceWith("fromLanguageToTagIETF(input, looseCheck)")) + /** + * Language name (english or native) -> ISO_639_1 + * @param languageName language name + * @param looseCheck match with `contains()` instead of `equals()` + */ + fun fromLanguageToTwoLetters(input: String, looseCheck: Boolean): String? { + return getLanguageDataFromName(input, looseCheck)?.ISO_639_1 + } + + + // @Deprecated( + // "Default language code changed to IETF BCP 47 tag", + // ReplaceWith("fromLanguageToTagIETF(input)")) + /** + * Language name (english or native) -> ISO_639_3 + */ + fun fromLanguageToThreeLetters(input: String): String? { + return getLanguageDataFromName(input)?.ISO_639_3 + } + + /** + * Language name -> IETF_tag + * @param languageName language name + * @param halfMatch match with `contains()` instead of `equals()` + */ + fun fromLanguageToTagIETF(languageName: String?, halfMatch: Boolean? = false): String? { + return getLanguageDataFromName(languageName, halfMatch)?.IETF_tag + } + + /** + * Language code -> [LanguageMetadata] + * @param languageCode IETF BCP 47, ISO 639-1, ISO 639-2B/T, ISO 639-3, OpenSubtitles + * @return [LanguageMetadata] or `null` + */ + private fun getLanguageDataFromCode(languageCode: String?): LanguageMetadata? { + if (languageCode.isNullOrBlank() || languageCode.length < 2) return null + + val lowLangCode = languageCode.lowercase() + val index = + indexMapIETF_tag[lowLangCode] ?: + indexMapISO_639_1[lowLangCode] ?: + indexMapISO_639_3[lowLangCode] ?: + indexMapISO_639_2_B[lowLangCode] ?: + indexMapOpenSubtitles[lowLangCode] ?: -1 + + return languages.getOrNull(index) + } + + // @Deprecated( + // "Default language code changed to IETF BCP 47 tag", + // ReplaceWith("fromTagToLanguageName(input)")) + /** + * Language code -> language english name + */ + fun fromTwoLettersToLanguage(input: String): String? { + return getLanguageDataFromCode(input)?.languageName + } + + // @Deprecated( + // "Default language code changed to IETF BCP 47 tag", + // ReplaceWith("fromTagToLanguageName(input)")) + /** + * Language code -> language english name + */ + fun fromThreeLettersToLanguage(input: String): String? { + return getLanguageDataFromCode(input)?.languageName + } + + /** + * Language code -> language name (if not found, fallback to native language name) + * @param languageCode IETF BCP 47, ISO 639-1, ISO 639-2B/T, ISO 639-3, OpenSubtitles + * @param localizedTo IETF BCP 47 tag to localize the language name to. Default: app current language + */ + @Prerelease + fun fromTagToLanguageName(languageCode: String?, localizedTo: String? = null): String? { + return getLanguageDataFromCode(languageCode)?.localizedName(localizedTo) + } + + /** + * Language code -> openSubtitles inconsistent language tag + * @param languageCode IETF BCP 47, ISO 639-1, ISO 639-2B/T, ISO 639-3, OpenSubtitles + */ + @Prerelease + fun fromCodeToOpenSubtitlesTag(languageCode: String?): String? { + return getLanguageDataFromCode(languageCode)?.openSubtitles + } + + /** openSubtitles -> IETF_tag */ + @Prerelease + fun fromCodeToLangTagIETF(languageCode: String?): String? { + return getLanguageDataFromCode(languageCode)?.IETF_tag + } + + /** + * Check for a well formed IETF BCP 47 conformant language tag + * + * See locales on: + * https://github.com/unicode-org/cldr-json/blob/main/cldr-json/cldr-core/availableLocales.json + * https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry + * https://android.googlesource.com/platform/frameworks/base/+/android-16.0.0_r2/core/res/res/values/locale_config.xml + */ + @Prerelease + fun isWellFormedTagIETF(langTagIETF: String?): Boolean { + if (langTagIETF.isNullOrBlank() || langTagIETF.length < 2) return false + + // Written by Addison Phillips, + // https://www.langtag.net/philips-regexp.html + val langTagRegex = """ + +(^[xX](\x2d\p{Alnum}{1,8})*$)" + +|(((^\p{Alpha}{2,8}(?=\x2d|$)){1}" + +((\x2d\p{Alpha}{3})(?=\x2d|$)){0,3}" + +(\x2d\p{Alpha}{4}(?=\x2d|$))?" + +(\x2d(\p{Alpha}{2}|\d{3})(?=\x2d|$))?" + +(\x2d(\d\p{Alnum}{3}|\p{Alnum}{5,8})(?=\x2d|$))*)" + +((\x2d([a-wyzA-WYZ](?=\x2d))(\x2d(\p{Alnum}{2,8})+)*))*" + +(\x2d[xX](\x2d\p{Alnum}{1,8})*)?)$ + """.trimMargin("+").toRegex() + return langTagIETF.matches(langTagRegex) + } + + /** + * Try to get a flag emoji form a language code + * or two letters country code (ISO 3166-1-alfa-2) + */ + fun getFlagFromIso(inp: String?): String? { + if (inp.isNullOrBlank() || inp.length < 2) return null + + // 2 times a symbol between regional indicator "[A]" and "[Z]" + val unicodeFlagRegex = Regex("[\uD83C\uDDE6-\uD83C\uDDFF]{2}") + // language tags (en-US, es-419, pt-BR, zh-hant-TW) that includes country + val countryRegex = Regex("(?<=[-_])(?\\p{Alnum}{2,3})$", IGNORE_CASE) + + val country = countryRegex.find(inp)?.value + + val flagEmoji = + getFlagFromCountry2Letters(lang2country[inp.lowercase()]) ?: + getFlagFromCountry2Letters(country?.uppercase()) ?: + getFlagFromCountry2Letters(inp.uppercase()) ?: + getFlagFromCountry2Letters(lang2country[country?.lowercase()]) ?: "" + + if (inp.equals("qt", ignoreCase = true)) + return "\uD83E\uDD8D" // "mmmm... monke" -> gorilla [🦍] + if (flagEmoji.matches(unicodeFlagRegex)) + return flagEmoji + return null + } + + /** + * Get a flag emoji next to the language name + * @param languageCode IETF BCP 47, ISO 639-1, ISO 639-2B/T, ISO 639-3, OpenSubtitles + * @param localizedTo IETF BCP 47 tag to localize the language name to. Default: app current language + */ + @Prerelease + fun getNameNextToFlagEmoji(languageCode: String?, localizedTo: String? = null): String? { + return getLanguageDataFromCode(languageCode)?.nameNextToFlagEmoji(localizedTo) + } + + // 2 letters country code (ISO 3166-1-alfa-2) -> flag emoji + private fun getFlagFromCountry2Letters(countryLetters: String?): String? { + if (countryLetters.isNullOrBlank() || countryLetters.length != 2) return null + + val asciiOffset = 0x41 // uppercase "A" + val flagOffset = 0x1F1E6 // regional indicator "[A]" + val offset = flagOffset - asciiOffset + + val firstChar: Int = Character.codePointAt(countryLetters, 0) + offset + val secondChar: Int = Character.codePointAt(countryLetters, 1) + offset + + return String(Character.toChars(firstChar)) + String(Character.toChars(secondChar)) + } + + // when (langTag = country) or (langTag contains country) + // as in: + // "es" to "ES" + // "pt" to "PT" + // "fr" to "FR" + // "en-US" to "US" + // "pt-BR" to "BR" + // "zh-hant-TW" to "TW" + // add to this list is useless as getFlagFromIso() already + // handles it. + private val lang2country = mapOf( + "419" to "ES", // (?_?) Latin America Spanish -> ES or a L.A. country? + "aa" to "ET", + "af" to "ZA", + "agq" to "CM", + "ajp" to "SY", + "ak" to "GH", + "am" to "ET", + "an" to "ES", + "apc" to "SY", + "ar" to "AE", + "arg" to "ES", + "ars" to "SA", + "as" to "IN", + "asa" to "TZ", + "av" to "RU", // (?_?) Avaric -> RU + "ay" to "BO", // (?_?) Aymara -> BO + "azb" to "AZ", + "bas" to "CM", + "be" to "BY", + "bem" to "ZM", + "bez" to "IT", + "bm" to "ML", + "bn" to "BD", + "bo" to "CN", + "br" to "FR", + "brx" to "IN", + "bs" to "BA", + "ca" to "ES", + "cgg" to "UG", + "chr" to "US", + "cnr" to "ME", + "cs" to "CZ", + "cy" to "GB", + "da" to "DK", + "dav" to "KE", + "dje" to "NE", + "dua" to "CM", + "dyo" to "SN", + "dz" to "BT", + "ea" to "ES", + "ebu" to "KE", + "ee" to "GH", + "el" to "GR", + "en" to "GB", + "et" to "EE", + "eu" to "ES", + "ewo" to "CM", + "ex" to "ES", + "ext" to "ES", + "fa" to "IR", + "ff" to "CN", + "fil" to "PH", + "fj" to "FJ", + "ga" to "IE", + "gd" to "GB", + "gl" to "ES", + "gn" to "PY", // (?_?) Guarani -> PY + "gsw" to "CH", + "gu" to "IN", + "guz" to "KE", + "gv" to "GB", + "ha" to "NG", + "haw" to "US", + "he" to "IL", + "hi" to "IN", + "hy" to "AM", + "ig" to "NG", + "ii" to "CN", + "in" to "ID", // "in" deprecate, use "id" instead. Keeping for exiting subtitles + "ita" to "IT", + "iw" to "IL", // "iw" deprecate, use "he" instead. Keeping for exiting subtitles + "ja" to "JP", + "jmc" to "TZ", + "jv" to "ID", + "jvn" to "ID", + "ka" to "GE", + "kab" to "DZ", + "kam" to "KE", + "kde" to "TZ", + "kea" to "CV", + "kg" to "CD", // (?_?) Kongo -> CD or CG or AO + "khq" to "ML", + "ki" to "KE", + "kk" to "KZ", + "kl" to "GL", + "kln" to "KE", + "km" to "KH", + "kn" to "IN", + "ko" to "KR", + "kok" to "IN", + "kr" to "TD", // (?_?) Kanuri -> TD or NE + "ks" to "IN", // (?_?) Kashmiri -> IN or PK + "ksb" to "TZ", + "ksf" to "CM", + "ku" to "IQ", + "kw" to "GB", + "ky" to "KG", + "la" to "IT", + "lag" to "TZ", + "lat" to "IT", + "lat" to "LV", + "lb" to "LU", + "lg" to "UG", + "ln" to "CG", + "lo" to "LA", + "ltz" to "LU", + "lu" to "CD", + "luo" to "KE", + "luy" to "KE", + "ma" to "IN", + "mas" to "TZ", + "mer" to "KE", + "mfe" to "MU", + "mgh" to "MZ", + "ml" to "IN", + "mni" to "IN", + "mr" to "IN", + "ms" to "MY", + "mua" to "CM", + "my" to "MM", + "naq" to "NA", + "nb" to "NO", + "nd" to "ZW", + "ne" to "NP", + "nmg" to "CM", + "nn" to "NO", + "nr" to "ZA", // (?_?) Southern Ndebele -> ZA or ZW + "nus" to "SD", + "nv" to "US", + "ny" to "MW", // (?_?) Nyanja -> MW + "nyn" to "UG", + "oc" to "ES", + "oci" to "ES", + "om" to "ET", + "or" to "IN", + "pa" to "IN", + "pa" to "PK", + "pan" to "IN", + "pb" to "BR", + "pm" to "MZ", + "por" to "PT", + "pr" to "AF", + "prs" to "AF", + "ps" to "AF", + "qu" to "PE", // (?_?) Quechua -> PE or EC or BO or CO or CL or AR + "que" to "PE", // (?_?) Quechua -> PE or EC or BO or CO or CL or AR + "rm" to "CH", + "rn" to "BI", + "rof" to "TZ", + "rwk" to "TZ", + "sa" to "IN", + "san" to "IN", + "saq" to "KE", + "sat" to "IN", + "sbp" to "TZ", + "sd" to "PK", // (?_?) Sindhi -> PK or IN + "sdn" to "PK", // (?_?) Sindhi -> PK or IN + "se" to "NO", + "seh" to "MZ", + "ses" to "ML", + "sg" to "CF", + "shi" to "MA", + "si" to "LK", + "sl" to "SI", + "sm" to "WS", + "smo" to "WS", + "sn" to "ZW", + "sp" to "ES", + "sq" to "AL", + "sr" to "RS", + "st" to "LS", // (?_?) Southern Sotho -> LS or ZA + "su" to "ID", + "sv" to "SE", + "sw" to "TZ", + "swc" to "CD", + "ta" to "IN", + "tat" to "RU", + "tdt" to "TL", + "te" to "IN", + "teo" to "UG", + "tg" to "TJ", + "tgk" to "TJ", + "ti" to "ET", + "tk" to "TM", + "tl" to "PH", + "tm-td" to "TL", // (?_?) Tetun -> TL + "tn" to "ZA", // (?_?) Tswana -> ZA or BW + "ts" to "ZA", + "tso" to "ZA", + "tt" to "RU", + "tuk" to "TM", + "twq" to "NE", + "tzm" to "MA", + "uk" to "UA", + "ur" to "PK", + "vai" to "LR", + "vi" to "VN", + "vun" to "TZ", + "wo" to "SN", + "xh" to "ZA", + "xho" to "ZA", + "xog" to "UG", + "yav" to "CM", + "yo" to "NG", + "yue" to "CN", + "za" to "CN", + "zh-hans" to "CN", // (?_?) Chinese (simplified) -> CN ? + "zh-hant" to "TW", // (?_?) Chinese (traditional) -> CN or TW or other ? + "zh" to "CN", + "zha" to "CN", + "zu" to "ZA", + ) + + val languages = listOf( + // languageName, nativeName, IETF_tag, ISO_639_1, ISO_639_2_B, ISO_639_3, openSubtitles + LanguageMetadata("Afar","Afaraf","aa","aa","aar","aar",""), + LanguageMetadata("Afrikaans","Afrikaans","af","af","afr","afr","af"), + LanguageMetadata("Akan","Akan","ak","ak","aka","aka",""), + LanguageMetadata("Albanian","Shqip","sq","sq","","sqi","sq"), + LanguageMetadata("Amharic","አማርኛ","am","am","amh","amh","am"), + LanguageMetadata("Arabic","العربية","ar","ar","ara","ara","ar"), + LanguageMetadata("Arabic (Levantine)","عربي شامي","apc","","","apc","ar"), + LanguageMetadata("Arabic (Najdi)","عربي شامي","ars","","","ars","ar"), + LanguageMetadata("Aragonese","aragonés","an","an","arg","arg","an"), + LanguageMetadata("Armenian","Հայերեն","hy","hy","","hye","hy"), + LanguageMetadata("Assamese","অসমীয়া","as","as","asm","asm","as"), + LanguageMetadata("Avaric","авар мацӀ, магӀарул мацӀ","av","av","ava","ava",""), + LanguageMetadata("Aymara","aymar aru","ay","ay","aym","aym",""), + LanguageMetadata("Azerbaijani","Azərbaycan","az","az","aze","aze","az-az"), + LanguageMetadata("Azerbaijani (South)","Azərbaycan (Cənubi)","azb","","","azb","az-zb"), + LanguageMetadata("Bambara","bamanankan","bm","bm","bam","bam",""), + LanguageMetadata("Basque","euskara, euskera","eu","eu","","eus","eu"), + LanguageMetadata("Belarusian","беларуская мова","be","be","bel","bel","be"), + LanguageMetadata("Bengali","বাংলা","bn","bn","ben","ben","bn"), + LanguageMetadata("Bosnian","bosanski jezik","bs","bs","bos","bos","bs"), + LanguageMetadata("Breton","brezhoneg","br","br","bre","bre","br"), + LanguageMetadata("Bulgarian","български език","bg","bg","bul","bul","bg"), + LanguageMetadata("Burmese","ဗမာစာ","my","my","","mya","my"), + LanguageMetadata("Catalan","català","ca","ca","cat","cat","ca"), + LanguageMetadata("Chichewa","chiCheŵa, chinyanja","ny","ny","nya","nya",""), + LanguageMetadata("Chinese","中文, 汉语, 漢語","zh","zh","chi","zho","ze"), + LanguageMetadata("Chinese (Cantonese)","廣東話, 广东话","yue","","","yue","zh-ca"), + LanguageMetadata("Chinese (simplified)","汉语","zh-hans","","","","zh-cn"), + LanguageMetadata("Chinese (Taiwan)","正體中文(臺灣)","zh-hant-tw","","","","zh-tw"), + LanguageMetadata("Chinese (traditional)","漢語","zh-hant","","","","zh-tw"), + LanguageMetadata("Croatian","hrvatski jezik","hr","hr","hrv","hrv","hr"), + LanguageMetadata("Czech","čeština, český jazyk","cs","cs","","ces","cs"), + LanguageMetadata("Danish","dansk","da","da","dan","dan","da"), + LanguageMetadata("Dari","دری","prs","","","prs","pr"), + LanguageMetadata("Dutch","Nederlands, Vlaams","nl","nl","","nld","nl"), + LanguageMetadata("Dzongkha","རྫོང་ཁ","dz","dz","dzo","dzo",""), + LanguageMetadata("English","English","en","en","eng","eng","en"), + LanguageMetadata("Esperanto","Esperanto","eo","eo","epo","epo","eo"), + LanguageMetadata("Estonian","eesti, eesti keel","et","et","est","est","et"), + LanguageMetadata("Ewe","Eʋegbe","ee","ee","ewe","ewe",""), + LanguageMetadata("Extremaduran","Estremeñu","ext","","","ext","ex"), + LanguageMetadata("Faroese","føroyskt","fo","fo","fao","fao",""), + LanguageMetadata("Fijian","vosa Vakaviti","fj","fj","fij","fij",""), + LanguageMetadata("Filipino","Wikang Filipino","fil","","fil","fil",""), + LanguageMetadata("Finnish","suomi, suomen kieli","fi","fi","fin","fin","fi"), + LanguageMetadata("French","Français","fr","fr","","fra","fr"), + LanguageMetadata("Fula","Fulfulde, Pulaar, Pular","ff","ff","ful","ful",""), + LanguageMetadata("Galician","Galego","gl","gl","glg","glg","gl"), + LanguageMetadata("Ganda","Luganda","lg","lg","lug","lug",""), + LanguageMetadata("Georgian","ქართული","ka","ka","","kat","ka"), + LanguageMetadata("German","Deutsch","de","de","","deu","de"), + LanguageMetadata("Greek","ελληνικά","el","el","","ell","el"), + LanguageMetadata("Guarani","Avañe'ẽ","gn","gn","grn","gug",""), + LanguageMetadata("Gujarati","ગુજરાતી","gu","gu","guj","guj",""), + LanguageMetadata("Haitian","Kreyòl ayisyen","ht","ht","hat","hat",""), + LanguageMetadata("Hausa","(Hausa) هَوُسَ","ha","ha","hau","hau",""), + LanguageMetadata("Hebrew","עברית","he","he","heb","heb","he"), + LanguageMetadata("Hindi","हिन्दी, हिंदी","hi","hi","hin","hin","hi"), + LanguageMetadata("Hungarian","Magyar","hu","hu","hun","hun","hu"), + LanguageMetadata("Icelandic","Íslenska","is","is","","isl","is"), + LanguageMetadata("Ido","Ido","io","io","ido","ido",""), + LanguageMetadata("Igbo","Asụsụ Igbo","ig","ig","ibo","ibo","ig"), + LanguageMetadata("Indonesian","Bahasa Indonesia","id","id","ind","ind","id"), + LanguageMetadata("Interlingua","Interlingua","ia","ia","ina","ina","ia"), + LanguageMetadata("Interlingue","Interlingue (originally Occidental)","ie","ie","ile","ile",""), + LanguageMetadata("Irish","Gaeilge","ga","ga","gle","gle","ga"), + LanguageMetadata("Italian","italiano","it","it","ita","ita","it"), + LanguageMetadata("Japanese","日本語 (にほんご)","ja","ja","jpn","jpn","ja"), + LanguageMetadata("Javanese","ꦧꦱꦗꦮ","jv","jv","jav","jvn",""), + LanguageMetadata("Kalaallisut","kalaallisut, kalaallit oqaasii","kl","kl","kal","kal",""), + LanguageMetadata("Kannada","ಕನ್ನಡ","kn","kn","kan","kan","kn"), + LanguageMetadata("Kanuri","Kanuri","kr","kr","kau","kau",""), + LanguageMetadata("Kashmiri","कश्मीरी, كشميري‎","ks","ks","kas","kas",""), + LanguageMetadata("Kazakh","қазақ тілі","kk","kk","kaz","kaz","kk"), + LanguageMetadata("Khmer","ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ","km","km","khm","khm","km"), + LanguageMetadata("Kikuyu","Gĩkũyũ","ki","ki","kik","kik",""), + LanguageMetadata("Kinyarwanda","Ikinyarwanda","rw","rw","kin","kin",""), + LanguageMetadata("Kirundi","Ikirundi","rn","rn","run","run",""), + LanguageMetadata("Kongo","Kikongo","kg","kg","kon","kon",""), + LanguageMetadata("Korean","한국어, 조선어","ko","ko","kor","kor","ko"), + LanguageMetadata("Kurdish","Kurdî, كوردی‎","ku","ku","kur","kur","ku"), + LanguageMetadata("Kyrgyz","Кыргызча, Кыргыз тили","ky","ky","kir","kir",""), + LanguageMetadata("Lao","ພາສາລາວ","lo","lo","lao","lao",""), + LanguageMetadata("Latin","Latine","la","la","lat","lat",""), + LanguageMetadata("Latvian","latviešu valoda","lv","lv","lav","lav","lv"), + LanguageMetadata("Lingala","Lingála","ln","ln","lin","lin",""), + LanguageMetadata("Lithuanian","lietuvių kalba","lt","lt","lit","lit","lt"), + LanguageMetadata("Luba-Katanga","Tshiluba","lu","lu","lub","lub",""), + LanguageMetadata("Luxembourgish","Lëtzebuergesch","lb","lb","ltz","ltz","lb"), + LanguageMetadata("Macedonian","македонски","mk","mk","","mkd","mk"), + LanguageMetadata("Malagasy","fiteny malagasy","mg","mg","mlg","mlg",""), + LanguageMetadata("Malay","Bahasa Melayu, بهاس ملايو‎","ms","ms","","msa","ms"), + LanguageMetadata("Malayalam","മലയാളം","ml","ml","mal","mal","ml"), + LanguageMetadata("Maltese","Malti","mt","mt","mlt","mlt",""), + LanguageMetadata("Manx","Gaelg, Gailck","gv","gv","glv","glv",""), + LanguageMetadata("Marathi","मराठी","mr","mr","mar","mar","mr"), + LanguageMetadata("Marshallese","Kajin M̧ajeļ","mh","mh","mah","mah",""), + LanguageMetadata("Meitei","ꯃꯅꯤꯄꯨꯔꯤ, মণিপুরী","mni","","mni","mni","ma"), + LanguageMetadata("Mongolian","Монгол хэл","mn","mn","mon","mon","mn"), + LanguageMetadata("Montenegrin","crnogorski, црногорски","cnr","","cnr","cnr","me"), + LanguageMetadata("Navajo","Diné bizaad","nv","nv","nav","nav","nv"), + LanguageMetadata("Nepali","नेपाली","ne","ne","nep","nep","ne"), + LanguageMetadata("Northern Ndebele","isiNdebele","nd","nd","nde","nde",""), + LanguageMetadata("Northern Sami","Davvisámegiella","se","se","sme","sme","se"), + LanguageMetadata("Norwegian","Norsk","no","no","nor","nor","no"), + LanguageMetadata("Norwegian Bokmål","Norsk bokmål","nb","nb","nob","nob",""), + LanguageMetadata("Norwegian Nynorsk","Norsk nynorsk","nn","nn","nno","nno",""), + LanguageMetadata("Nuosu","ꆈꌠ꒿ Nuosuhxop","ii","ii","iii","iii",""), + LanguageMetadata("Occitan","occitan, lenga d'òc","oc","oc","oci","oci","oc"), + LanguageMetadata("Oriya","ଓଡ଼ିଆ","or","or","ori","ori","or"), + LanguageMetadata("Oromo","Afaan Oromoo","om","om","orm","orm",""), + LanguageMetadata("Panjabi","ਪੰਜਾਬੀ, پنجابی‎","pa","pa","pan","pan",""), + LanguageMetadata("Pashto","پښتو","ps","ps","pus","pus","ps"), + LanguageMetadata("Persian (Farsi)","فارسی","fa","fa","","fas","fa"), + LanguageMetadata("Polish","Polski, polszczyzna","pl","pl","pol","pol","pl"), + LanguageMetadata("Portuguese","Português","pt","pt","por","por","pt-pt"), + LanguageMetadata("Portuguese (Brazil)","Português (Brasil)","pt-br","","","","pt-br"), + LanguageMetadata("Portuguese (Mozambique)","Português (Moçambique)","pt-mz","","","","pm"), + LanguageMetadata("Quechua","Runa Simi, Kichwa","qu","qu","que","que",""), + LanguageMetadata("Romanian","Română","ro","ro","","ron","ro"), + LanguageMetadata("Romansh","rumantsch grischun","rm","rm","roh","roh",""), + LanguageMetadata("Russian","Русский","ru","ru","rus","rus","ru"), + LanguageMetadata("Samoan","gagana fa'a Samoa","sm","sm","smo","smo",""), + LanguageMetadata("Sango","yângâ tî sängö","sg","sg","sag","sag",""), + LanguageMetadata("Sanskrit","संस्कृतम्","sa","sa","san","san",""), + LanguageMetadata("Santali","ᱥᱟᱱᱛᱟᱲᱤ","sat","","","sat","sx"), + LanguageMetadata("Scottish Gaelic","Gàidhlig","gd","gd","gla","gla","gd"), + LanguageMetadata("Serbian","српски језик","sr","sr","srp","srp","sr"), + LanguageMetadata("Shona","chiShona","sn","sn","sna","sna",""), + LanguageMetadata("Sindhi","सिन्धी, سنڌي، سندھی‎","sd","sd","snd","snd","sd"), + LanguageMetadata("Sinhala","සිංහල","si","si","sin","sin","si"), + LanguageMetadata("Slovak","slovenčina, slovenský jazyk","sk","sk","","slk","sk"), + LanguageMetadata("Slovenian","slovenski jezik, slovenščina","sl","sl","slv","slv","sl"), + LanguageMetadata("Somali","Soomaaliga, af Soomaali","so","so","som","som","so"), + LanguageMetadata("Sotho","Sesotho","st","st","sot","sot",""), + LanguageMetadata("Southern Ndebele","isiNdebele","nr","nr","nbl","nbl",""), + LanguageMetadata("Spanish","Español","es","es","spa","spa","es"), + LanguageMetadata("Spanish (Europe)","Español (Europa)","es-es","","","","sp"), + LanguageMetadata("Spanish (Latin America)","Español (Latinoamérica)","es-419","","","","ea"), + LanguageMetadata("Sundanese","Basa Sunda","su","su","sun","sun",""), + LanguageMetadata("Swahili","Kiswahili","sw","sw","swa","swa","sw"), + LanguageMetadata("Swedish","Svenska","sv","sv","swe","swe","sv"), + LanguageMetadata("Tagalog","Wikang Tagalog, ᜆᜄᜎᜓᜄ᜔","tl","tl","","tlg","tl"), + LanguageMetadata("Tajik","тоҷикӣ, toçikī, تاجیکی‎","tg","tg","tgk","tgk",""), + LanguageMetadata("Tamil","தமிழ்","ta","ta","tam","tam","ta"), + LanguageMetadata("Tatar","татар теле, tatar tele","tt","tt","tat","tat","tt"), + LanguageMetadata("Telugu","తెలుగు","te","te","tel","tel","te"), + LanguageMetadata("Tetum","Tetun","tdt","","","tdt","tm-td"), + LanguageMetadata("Thai","ไทย","th","th","tha","tha","th"), + LanguageMetadata("Tibetan Standard","བོད་ཡིག","bo","bo","","bod",""), + LanguageMetadata("Tigrinya","ትግርኛ","ti","ti","tir","tir",""), + LanguageMetadata("Toki Pona","toki pona","tok","","","tok","tp"), + LanguageMetadata("Tonga","faka Tonga","to","to","ton","ton",""), + LanguageMetadata("Tsonga","Xitsonga","ts","ts","tso","tso",""), + LanguageMetadata("Tswana","Setswana","tn","tn","tsn","tsn",""), + LanguageMetadata("Turkish","Türkçe","tr","tr","tur","tur","tr"), + LanguageMetadata("Turkmen","Türkmen, Түркмен","tk","tk","tuk","tuk","tk"), + LanguageMetadata("Ukrainian","Українська","uk","uk","ukr","ukr","uk"), + LanguageMetadata("Urdu","اردو","ur","ur","urd","urd","ur"), + LanguageMetadata("Uzbek","Oʻzbek, Ўзбек, أۇزبېك‎","uz","uz","uzb","uzb","uz"), + LanguageMetadata("Vietnamese","Tiếng Việt","vi","vi","vie","vie","vi"), + LanguageMetadata("Welsh","Cymraeg","cy","cy","","cym","cy"), + LanguageMetadata("Wolof","Wollof","wo","wo","wol","wol",""), + LanguageMetadata("Xhosa","isiXhosa","xh","xh","xho","xho",""), + LanguageMetadata("Yoruba","Yorùbá","yo","yo","yor","yor",""), + LanguageMetadata("Zhuang","Saɯ cueŋƅ, Saw cuengh","za","za","zha","zha",""), + LanguageMetadata("Zulu","isiZulu","zu","zu","zul","zul",""), + ) + + val indexMapLanguageName = languages.withIndex().associate { (i, lang) -> lang.languageName.lowercase() to i} + val indexMapNativeName = languages.withIndex().associate { (i, lang) -> lang.nativeName.lowercase() to i} + val indexMapIETF_tag = languages.withIndex().associate { (i, lang) -> lang.IETF_tag.lowercase() to i} + val indexMapISO_639_1 = languages.withIndex().associate { (i, lang) -> lang.ISO_639_1.lowercase() to i} + val indexMapISO_639_2_B = languages.withIndex().associate { (i, lang) -> lang.ISO_639_2_B.lowercase() to i} + val indexMapISO_639_3 = languages.withIndex().associate { (i, lang) -> lang.ISO_639_3.lowercase() to i} + val indexMapOpenSubtitles = languages.withIndex().associate { (i, lang) -> lang.openSubtitles.lowercase() to i} +} From 9b50b11bfa85647ce8cce46aabccc09cfc34bb27 Mon Sep 17 00:00:00 2001 From: firelight <147925818+fire-light42@users.noreply.github.com> Date: Fri, 5 Sep 2025 18:13:11 +0000 Subject: [PATCH 021/640] Feat: Added "Mexican Spanish" as a language in subtitlehelper --- .../kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt index e3b7803a9..6fcbc2bcc 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt @@ -598,6 +598,7 @@ object SubtitleHelper { LanguageMetadata("Marathi","मराठी","mr","mr","mar","mar","mr"), LanguageMetadata("Marshallese","Kajin M̧ajeļ","mh","mh","mah","mah",""), LanguageMetadata("Meitei","ꯃꯅꯤꯄꯨꯔꯤ, মণিপুরী","mni","","mni","mni","ma"), + LanguageMetadata("Mexican Spanish", "Español mexicano", "es-MX", "mx","","",""), // iso639_1 is not mx but, some extension use it as such LanguageMetadata("Mongolian","Монгол хэл","mn","mn","mon","mon","mn"), LanguageMetadata("Montenegrin","crnogorski, црногорски","cnr","","cnr","cnr","me"), LanguageMetadata("Navajo","Diné bizaad","nv","nv","nav","nav","nv"), From 994211d32ff3eba9ee09c33f2f15b69ab3696cf6 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Wed, 10 Sep 2025 02:19:17 +0300 Subject: [PATCH 022/640] Fix: crash on API version < 25 (#1900) --- .../kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt index 6fcbc2bcc..815690872 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt @@ -233,9 +233,9 @@ object SubtitleHelper { // 2 times a symbol between regional indicator "[A]" and "[Z]" val unicodeFlagRegex = Regex("[\uD83C\uDDE6-\uD83C\uDDFF]{2}") // language tags (en-US, es-419, pt-BR, zh-hant-TW) that includes country - val countryRegex = Regex("(?<=[-_])(?\\p{Alnum}{2,3})$", IGNORE_CASE) + val countryRegex = Regex("[-_](\\p{Alnum}{2,3})$", RegexOption.IGNORE_CASE) - val country = countryRegex.find(inp)?.value + val country = countryRegex.find(inp)?.groupValues?.get(1) val flagEmoji = getFlagFromCountry2Letters(lang2country[inp.lowercase()]) ?: From 2d617da99a47cad8d841c7861b38ad3490644c54 Mon Sep 17 00:00:00 2001 From: KingLucius Date: Wed, 10 Sep 2025 15:43:35 +0300 Subject: [PATCH 023/640] feat(Emulator UI): Click anywhere to hide Episodes (#1899) --- .../lagradost/cloudstream3/ui/result/ResultFragmentTv.kt | 7 +++++++ 1 file changed, 7 insertions(+) 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 e5ca2e4e1..0c2a44ae3 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 @@ -466,9 +466,16 @@ class ResultFragmentTv : Fragment() { ).firstOrNull { it?.isVisible == true } + resultCastItems.adapter = ActorAdaptor(aboveCast?.id) { toggleEpisodes(false) } + + if (isLayout(EMULATOR)) { + episodesShadow.setOnClickListener { + toggleEpisodes(false) + } + } } observeNullable(viewModel.resumeWatching) { resume -> From 7af581ba8f528aaa98a916de46a86d2822772b33 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Sat, 13 Sep 2025 20:02:04 +0200 Subject: [PATCH 024/640] Translated using Weblate (Filipino) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 21.4% (175 of 815 strings) Translated using Weblate (Malay) Currently translated at 72.1% (588 of 815 strings) Translated using Weblate (Malay) Currently translated at 72.1% (588 of 815 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (815 of 815 strings) Translated using Weblate (Korean) Currently translated at 90.6% (739 of 815 strings) Translated using Weblate (French) Currently translated at 100.0% (815 of 815 strings) Translated using Weblate (Kurdish (Central)) Currently translated at 24.9% (203 of 815 strings) Translated using Weblate (Arabic) Currently translated at 99.2% (809 of 815 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Kurdish (Central)) Currently translated at 100.0% (4 of 4 strings) Translated using Weblate (Macedonian) Currently translated at 100.0% (815 of 815 strings) Added translation using Weblate (Kurdish (Central)) 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 (Indonesian) Currently translated at 97.9% (798 of 815 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' Translated using Weblate (Portuguese) Currently translated at 100.0% (815 of 815 strings) Translated using Weblate (Spanish) Currently translated at 99.2% (809 of 815 strings) Translated using Weblate (Russian) Currently translated at 100.0% (815 of 815 strings) Translated using Weblate (Italian) Currently translated at 100.0% (815 of 815 strings) Translated using Weblate (Croatian) Currently translated at 100.0% (4 of 4 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (815 of 815 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (815 of 815 strings) Translated using Weblate (Croatian) Currently translated at 100.0% (815 of 815 strings) Translated using Weblate (Czech) Currently translated at 100.0% (815 of 815 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Polish) Currently translated at 100.0% (815 of 815 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (German) Currently translated at 100.0% (814 of 814 strings) Translated using Weblate (Russian) Currently translated at 100.0% (814 of 814 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (814 of 814 strings) Translated using Weblate (Italian) Currently translated at 100.0% (814 of 814 strings) Translated using Weblate (Czech) Currently translated at 100.0% (814 of 814 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (814 of 814 strings) Translated using Weblate (Polish) Currently translated at 100.0% (814 of 814 strings) Translated using Weblate (French) Currently translated at 100.0% (814 of 814 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Russian) Currently translated at 100.0% (813 of 813 strings) Translated using Weblate (French) Currently translated at 100.0% (813 of 813 strings) Translated using Weblate (French) Currently translated at 100.0% (813 of 813 strings) Translated using Weblate (Italian) Currently translated at 100.0% (813 of 813 strings) Translated using Weblate (Czech) Currently translated at 100.0% (813 of 813 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (813 of 813 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (813 of 813 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (813 of 813 strings) Translated using Weblate (Polish) Currently translated at 100.0% (813 of 813 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' Translated using Weblate (Odia) Currently translated at 34.0% (276 of 811 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (811 of 811 strings) Translated using Weblate (German) Currently translated at 100.0% (811 of 811 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (811 of 811 strings) Translated using Weblate (French) Currently translated at 99.5% (807 of 811 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (811 of 811 strings) Translated using Weblate (Czech) Currently translated at 100.0% (811 of 811 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (811 of 811 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (811 of 811 strings) Translated using Weblate (Polish) Currently translated at 100.0% (811 of 811 strings) Translated using Weblate (Italian) Currently translated at 100.0% (811 of 811 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (806 of 806 strings) Translated using Weblate (Russian) Currently translated at 100.0% (806 of 806 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Spanish (Argentina)) Currently translated at 100.0% (4 of 4 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' Translated using Weblate (French) Currently translated at 100.0% (806 of 806 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (806 of 806 strings) Translated using Weblate (Italian) Currently translated at 100.0% (806 of 806 strings) Translated using Weblate (Czech) Currently translated at 100.0% (806 of 806 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (806 of 806 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (806 of 806 strings) Translated using Weblate (Polish) Currently translated at 100.0% (806 of 806 strings) Translated using Weblate (German) Currently translated at 100.0% (805 of 805 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Assamese) Currently translated at 100.0% (4 of 4 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Vietnamese) Currently translated at 100.0% (805 of 805 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Ukrainian) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Vietnamese) Currently translated at 99.8% (804 of 805 strings) Translated using Weblate (Romanian) Currently translated at 91.0% (733 of 805 strings) Translated using Weblate (Romanian) Currently translated at 91.0% (733 of 805 strings) Translated using Weblate (French) Currently translated at 99.7% (803 of 805 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (805 of 805 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Russian) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Spanish) Currently translated at 99.5% (801 of 805 strings) Translated using Weblate (Vietnamese) Currently translated at 99.8% (804 of 805 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Hindi) Currently translated at 55.2% (445 of 805 strings) Translated using Weblate (Italian) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Arabic (Levantine)) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (French) Currently translated at 99.8% (804 of 805 strings) Translated using Weblate (Czech) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Polish) Currently translated at 100.0% (805 of 805 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (803 of 803 strings) Translated using Weblate (Polish) Currently translated at 100.0% (803 of 803 strings) Translated using Weblate (Italian) Currently translated at 100.0% (803 of 803 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Turkish) Currently translated at 100.0% (800 of 800 strings) Added translation using Weblate (Javanese) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Indonesian) Currently translated at 99.6% (797 of 800 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (4 of 4 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (800 of 800 strings) Translated using Weblate (Macedonian) Currently translated at 100.0% (800 of 800 strings) Translated using Weblate (Bulgarian) Currently translated at 99.8% (799 of 800 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (800 of 800 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (800 of 800 strings) Update translation files Updated by "Remove blank strings" hook in Weblate. Translated using Weblate (Turkish) Currently translated at 99.6% (797 of 800 strings) Translated using Weblate (Turkish) Currently translated at 99.6% (797 of 800 strings) Translated using Weblate (Arabic (Levantine)) Currently translated at 100.0% (800 of 800 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Spanish) Currently translated at 99.2% (794 of 800 strings) Translated using Weblate (Spanish) Currently translated at 99.2% (794 of 800 strings) Translated using Weblate (German) Currently translated at 100.0% (800 of 800 strings) Translated using Weblate (Russian) Currently translated at 100.0% (800 of 800 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (800 of 800 strings) Translated using Weblate (Czech) Currently translated at 100.0% (800 of 800 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Italian) Currently translated at 100.0% (800 of 800 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (800 of 800 strings) Translated using Weblate (Polish) Currently translated at 100.0% (800 of 800 strings) Translated using Weblate (Bulgarian) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Bulgarian) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Bulgarian) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Bulgarian) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Macedonian) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Macedonian) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Tamil) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Tamil) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Russian) Currently translated at 100.0% (796 of 796 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Arabic (Levantine)) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Italian) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Italian) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Urdu) Currently translated at 90.5% (721 of 796 strings) Translated using Weblate (Urdu) Currently translated at 90.5% (721 of 796 strings) Translated using Weblate (Urdu) Currently translated at 90.5% (721 of 796 strings) Translated using Weblate (Urdu) Currently translated at 90.5% (721 of 796 strings) Translated using Weblate (Urdu) Currently translated at 90.5% (721 of 796 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Italian) Currently translated at 99.4% (792 of 796 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Persian) Currently translated at 58.0% (462 of 796 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Spanish) Currently translated at 98.7% (786 of 796 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Czech) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Polish) Currently translated at 100.0% (796 of 796 strings) Translated using Weblate (Swedish) Currently translated at 97.2% (771 of 793 strings) Translated using Weblate (Russian) Currently translated at 100.0% (793 of 793 strings) Update translation files Updated by "Remove blank strings" hook in Weblate. Translated using Weblate (Afrikaans) Currently translated at 29.7% (236 of 793 strings) Translated using Weblate (Arabic (Levantine)) Currently translated at 98.9% (785 of 793 strings) Translated using Weblate (qt (generated) (qt)) Currently translated at 94.1% (747 of 793 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (793 of 793 strings) Translated using Weblate (Hungarian) Currently translated at 83.1% (659 of 793 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (793 of 793 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (793 of 793 strings) Translated using Weblate (Polish) Currently translated at 100.0% (793 of 793 strings) Translated using Weblate (Dutch) Currently translated at 85.4% (678 of 793 strings) Translated using Weblate (Polish) Currently translated at 100.0% (790 of 790 strings) Translated using Weblate (Polish) Currently translated at 100.0% (788 of 788 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (782 of 782 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Dutch) Currently translated at 50.0% (2 of 4 strings) Translated using Weblate (Dutch) Currently translated at 86.3% (675 of 782 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Portuguese) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Korean) Currently translated at 93.3% (730 of 782 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' Translated using Weblate (Japanese) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Swedish) Currently translated at 98.0% (767 of 782 strings) Translated using Weblate (Dutch) Currently translated at 84.3% (660 of 782 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Croatian) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Ukrainian) Currently translated at 99.7% (780 of 782 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Macedonian) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Ukrainian) Currently translated at 99.7% (780 of 782 strings) Translated using Weblate (German) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Macedonian) Currently translated at 99.8% (781 of 782 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (German) Currently translated at 99.8% (781 of 782 strings) Translated using Weblate (Hindi) Currently translated at 54.3% (425 of 782 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Arabic (Levantine)) Currently translated at 100.0% (782 of 782 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' Translated using Weblate (Tamil) Currently translated at 95.5% (747 of 782 strings) Translated using Weblate (Vietnamese) Currently translated at 99.8% (781 of 782 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Greek) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Arabic) Currently translated at 99.4% (778 of 782 strings) Translated using Weblate (Russian) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Bulgarian) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Bulgarian) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (French) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (French) Currently translated at 99.8% (781 of 782 strings) Translated using Weblate (Spanish) Currently translated at 99.8% (781 of 782 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Czech) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Polish) Currently translated at 100.0% (782 of 782 strings) Translated using Weblate (Italian) Currently translated at 100.0% (782 of 782 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Ukrainian) Currently translated at 100.0% (780 of 780 strings) Translated using Weblate (Spanish) Currently translated at 100.0% (780 of 780 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (780 of 780 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (780 of 780 strings) Translated using Weblate (Spanish) Currently translated at 99.8% (779 of 780 strings) Translated using Weblate (Russian) Currently translated at 100.0% (780 of 780 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (780 of 780 strings) Translated using Weblate (Polish) Currently translated at 100.0% (780 of 780 strings) Translated using Weblate (Italian) Currently translated at 100.0% (780 of 780 strings) Translated using Weblate (Czech) Currently translated at 100.0% (780 of 780 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Russian) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Russian) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Russian) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Russian) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Russian) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Russian) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Russian) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Russian) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (777 of 777 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Greek) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Czech) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Ukrainian) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Polish) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Italian) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Greek) Currently translated at 99.7% (775 of 777 strings) Translated using Weblate (Arabic) Currently translated at 100.0% (777 of 777 strings) Merge remote-tracking branch 'origin/master' Translated using Weblate (Assamese) Currently translated at 97.2% (756 of 777 strings) Translated using Weblate (Ukrainian) Currently translated at 99.8% (776 of 777 strings) Translated using Weblate (German) Currently translated at 99.8% (776 of 777 strings) Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 99.8% (776 of 777 strings) Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 96.7% (752 of 777 strings) Translated using Weblate (Vietnamese) Currently translated at 99.4% (773 of 777 strings) Translated using Weblate (Swedish) Currently translated at 97.4% (757 of 777 strings) Translated using Weblate (Polish) Currently translated at 99.8% (776 of 777 strings) Translated using Weblate (Macedonian) Currently translated at 99.0% (770 of 777 strings) Translated using Weblate (Italian) Currently translated at 99.6% (774 of 777 strings) Translated using Weblate (Croatian) Currently translated at 99.3% (772 of 777 strings) Translated using Weblate (Czech) Currently translated at 99.8% (776 of 777 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 99.8% (776 of 777 strings) Translated using Weblate (Arabic) Currently translated at 99.8% (776 of 777 strings) Translated using Weblate (Japanese) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Portuguese) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (777 of 777 strings) Merge remote-tracking branch 'origin/master' Merge remote-tracking branch 'origin/master' Translated using Weblate (Spanish) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (French) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Indonesian) Currently translated at 100.0% (777 of 777 strings) Translated using Weblate (Arabic (Levantine)) Currently translated at 100.0% (777 of 777 strings) Co-authored-by: Adolfo Jayme Barrientos Co-authored-by: Aerin Park Co-authored-by: Ahmed Abd El-Fattah Co-authored-by: Anarchydr Co-authored-by: Angel Ivanov Co-authored-by: Angga Wangza Co-authored-by: Anonymous Co-authored-by: Arina Co-authored-by: Bernardo Gomes Co-authored-by: Boqirz Co-authored-by: Chris V Co-authored-by: Christopher Allen Co-authored-by: Cloudburst <18114966+C10udburst@users.noreply.github.com> Co-authored-by: Cris P Co-authored-by: Dan Co-authored-by: Darias Co-authored-by: Deleted User Co-authored-by: Dinh Nguyen Co-authored-by: Do you know my name? Co-authored-by: Empty Context Co-authored-by: Esspel Co-authored-by: Fjuro Co-authored-by: Fjuro Co-authored-by: Francisco Serrador Co-authored-by: Fritz Blümke Co-authored-by: Geovani Amaral Co-authored-by: GiannosOB Co-authored-by: Harshit Sethi Co-authored-by: Hosted Weblate Co-authored-by: Itsmechinmoy Co-authored-by: Ivan Kostov Co-authored-by: Jagattananda Satapathy Co-authored-by: Jagrit Rajoriya Co-authored-by: Jimmy Dahlberg Co-authored-by: John Kennedy Peña Co-authored-by: Juan Rubin Co-authored-by: Juzé Co-authored-by: Kerim Demirkaynak Co-authored-by: Kingsis Trader Co-authored-by: Lacey Anaya Co-authored-by: Lourenço Martins Co-authored-by: MM Co-authored-by: Maksim_220 Кабанов Co-authored-by: Massimo Pissarello Co-authored-by: Matthaiks Co-authored-by: Mickael Marques da mota Co-authored-by: Milo Ivir Co-authored-by: MrSliv69 Co-authored-by: Nataniel Dika Kurniawan Co-authored-by: Nguyễn Tiến Đạt Co-authored-by: Night Sky Co-authored-by: Niko Co-authored-by: Pizza Party Co-authored-by: Plamen Ivanov Co-authored-by: Py- Droid Co-authored-by: Quentin Co-authored-by: Ramazan S Co-authored-by: Rex_sa Co-authored-by: Reza Almanda Co-authored-by: SBS1313 Co-authored-by: Saúl Palacios Co-authored-by: Serdar Sağlam Co-authored-by: Sergei Bumazhnov Co-authored-by: Sergey Ponomarev Co-authored-by: Simeon Tthhuueett Co-authored-by: Sofia Co-authored-by: Somme4096 Co-authored-by: Splow95 Co-authored-by: Uzair Syed Co-authored-by: Vdrsl Co-authored-by: Vitalii Panyk Co-authored-by: alb bla Co-authored-by: bbstacks Co-authored-by: dabao1955 Co-authored-by: dedakir923 Co-authored-by: ene-sword-group Co-authored-by: hidden Co-authored-by: icosahedr.online Co-authored-by: khalidbelk Co-authored-by: maxim Co-authored-by: michte Co-authored-by: oguzugur28 Co-authored-by: ssantos Co-authored-by: stojkovskistefan Co-authored-by: vanapro1 Co-authored-by: william piti Co-authored-by: yigit bilgin Co-authored-by: zmni Co-authored-by: λpex_herbivore Co-authored-by: Максим Горпиніч Co-authored-by: Максим Горпиніч Co-authored-by: Максим Горпиніч Co-authored-by: Максим Горпиніч Co-authored-by: தமிழ்நேரம் Co-authored-by: 大王叫我来巡山 Co-authored-by: 송의찬 Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/af/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/apc/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ar/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/as/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/bg/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ckb/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/cs/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/de/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/el/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/es/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/fa/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/fil/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/fr/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/hi/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/hr/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/hu/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/id/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/it/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ja/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ko/ 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/nl/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/or/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/pl/ 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/qt/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ro/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ru/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/sv/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/ta/ 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/ur/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/vi/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/zh_Hans/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/app/zh_Hant/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/as/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/ckb/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/es_AR/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/hr/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/ja/ Translate-URL: https://hosted.weblate.org/projects/cloudstream/fastlane/nl/ Translation: Cloudstream/App Translation: Cloudstream/Fastlane --- app/src/main/res/values-af/strings.xml | 3 +- app/src/main/res/values-ajp/strings.xml | 21 ++++- app/src/main/res/values-ar/strings.xml | 33 ++++++- app/src/main/res/values-bg/strings.xml | 15 +++- app/src/main/res/values-bp/strings.xml | 20 ++++- app/src/main/res/values-ckb/strings.xml | 87 +++++++++++++++++++ app/src/main/res/values-cs/strings.xml | 29 ++++++- app/src/main/res/values-de/strings.xml | 38 ++++++-- app/src/main/res/values-es/strings.xml | 60 ++++++++----- app/src/main/res/values-fa/strings.xml | 77 +++++++++++++--- app/src/main/res/values-fil/strings.xml | 56 +++++++++++- app/src/main/res/values-fr/strings.xml | 34 +++++++- app/src/main/res/values-hi/strings.xml | 18 +++- app/src/main/res/values-hr/strings.xml | 31 ++++++- app/src/main/res/values-hu/strings.xml | 4 +- app/src/main/res/values-in/strings.xml | 31 ++++++- app/src/main/res/values-it/strings.xml | 29 ++++++- app/src/main/res/values-ja/strings.xml | 15 +++- app/src/main/res/values-jv/strings.xml | 3 + app/src/main/res/values-ko/strings.xml | 11 ++- app/src/main/res/values-mk/strings.xml | 31 ++++++- app/src/main/res/values-ms/strings.xml | 50 +++++++++-- app/src/main/res/values-nl/strings.xml | 21 +++-- app/src/main/res/values-or/strings.xml | 5 +- app/src/main/res/values-pl/strings.xml | 31 ++++++- app/src/main/res/values-pt/strings.xml | 38 +++++++- app/src/main/res/values-qt/strings.xml | 2 +- app/src/main/res/values-ro/strings.xml | 18 +++- app/src/main/res/values-ru/strings.xml | 29 ++++++- app/src/main/res/values-sv/strings.xml | 3 +- app/src/main/res/values-ta/strings.xml | 48 +++++++++- app/src/main/res/values-tr/strings.xml | 25 +++++- app/src/main/res/values-uk/strings.xml | 37 ++++++-- app/src/main/res/values-ur/strings.xml | 20 ++++- app/src/main/res/values-vi/strings.xml | 29 ++++++- app/src/main/res/values-zh/strings.xml | 29 ++++++- .../metadata/android/as/full_description.txt | 12 +-- .../metadata/android/as/short_description.txt | 2 +- .../metadata/android/ckb/changelogs/2.txt | 1 + .../metadata/android/ckb/full_description.txt | 10 +++ .../android/ckb/short_description.txt | 1 + fastlane/metadata/android/ckb/title.txt | 1 + .../android/es-AR/full_description.txt | 13 ++- .../metadata/android/hr/full_description.txt | 2 +- .../metadata/android/hr/short_description.txt | 2 +- .../android/ja-JP/full_description.txt | 10 +++ .../android/ja-JP/short_description.txt | 1 + .../android/nl-NL/short_description.txt | 1 + fastlane/metadata/android/nl-NL/title.txt | 1 + 49 files changed, 969 insertions(+), 119 deletions(-) create mode 100644 app/src/main/res/values-ckb/strings.xml create mode 100644 app/src/main/res/values-jv/strings.xml create mode 100644 fastlane/metadata/android/ckb/changelogs/2.txt create mode 100644 fastlane/metadata/android/ckb/full_description.txt create mode 100644 fastlane/metadata/android/ckb/short_description.txt create mode 100644 fastlane/metadata/android/ckb/title.txt create mode 100644 fastlane/metadata/android/ja-JP/full_description.txt create mode 100644 fastlane/metadata/android/ja-JP/short_description.txt create mode 100644 fastlane/metadata/android/nl-NL/short_description.txt create mode 100644 fastlane/metadata/android/nl-NL/title.txt diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml index b66161820..e27fe0edb 100644 --- a/app/src/main/res/values-af/strings.xml +++ b/app/src/main/res/values-af/strings.xml @@ -107,7 +107,6 @@ Rolverdeling: %s Nuwe episode notifikasie hide_player_control_names_key - "Season 000 Episode 000 will be released in" Gratis Gebruik Wis Uit @@ -123,4 +122,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-ajp/strings.xml b/app/src/main/res/values-ajp/strings.xml index 3c2d18037..be8fee5ba 100644 --- a/app/src/main/res/values-ajp/strings.xml +++ b/app/src/main/res/values-ajp/strings.xml @@ -40,7 +40,7 @@ انضم لگروپ \"كلود ستريم\" ع الـ\"ديسكورد\" بعد خيارات تسيّڤة النسخة الإحتياطية - صرتو عاطين %d موزززة للمطورين! + صرتو عاطين %d موزززة للمطورين سحابو لتقدمو وترجعو إذن الوصول لذاكرة التخزين مفقود. پليز جرب بعد مرّة. عم إحضر @@ -704,4 +704,21 @@ عمول خط الترجمة بولد عمول خط الترجمة أيتاليك قوة برمة الخلفية - \ No newline at end of file + كم شغلة مختلفة فيها تتنزل ب نفس الوقت + تنزيل ب نفس الوقت + كم إتصال ب نفص الوقت + كم إتصال ب نفس الوقت فيه يستعمل كل تنزيل + فتاح التنزيلات + مافي إنترنت.\n\nپليز جرب مرّة تانية لمّا يكون عندك إنترنت، أو حضار الإشيا اللي منزلها إنت و أفلاين. + بت غير حدود الشاشة + أوڤر سكان + بت غيّر حجم الپوستر + حجم الپوستر + دايمًا سئالوني + خليك كابس حتى تسرع + خليك كابس كرمال صرعة الـ2x + %1$dساعة %2$dد %3$dث + %1$dد %2$dث + %1$dث + لايبل الرايتينگ + diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index a20132c89..158739078 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -727,4 +727,35 @@ اونلاين داخلياً لقد تجاوز مستوى الصوت 100٪ - \ No newline at end of file + جعل جميع العناوين الفرعية عريضة + اسحب لأعلى مرة أخرى لتتجاوز 100% + جعل كل العناوين الفرعية مائلة + نصف قطر الخلفية + %1$dh %2$dm %3$d + %1$dm %2$d + %1$dث + ملصق التصنيف + أسال دائما + عدد الأصناف المختلفة التي يمكن تنزيلها بالتوازي + تحميلات موازية + الاتصالات الجارية + كم عدد الاتصالات المتزامنة التي يمكن أن يستخدمها كل تحميل + الذهاب إلى تحميل + لا يوجد اتصال بالإنترنت.\n\nيُرجى الاتصال بالإنترنت والمحاولة مرة أخرى، أو مشاهدة التنزيلات أثناء عدم الاتصال بالإنترنت. + تغير حدود الشاشة + مسح زائد + تغيير حجم الملصقات + حجم الملصقات + تبديل سرعة الضغط لفترة طويلة + اضغط مع الاستمرار للحصول على سرعة 2x + لا يوجد حساب + تعديل صورة الملف الشخصي + ادخل رابط صورة الملف الشخصي + لم يتم العثور على الرابط + رابط او صورة غير صالحة للاستخدام + تم رفع الصورة + ضع علامة تمت المشاهدة لهذه الحلقة + ازالة علامة تمت المشاهدة لهذه الحلقة + تم تحديث الصفحة + تحديث المزود + diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index a7f7f9d87..0444970b1 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -697,4 +697,17 @@ За да гарантираме непрекъснати изтегляния и известия за абонирани телевизионни шоута, CloudStream се нуждае от разрешение да работи в фонов режим. Като натиснете ДОБРЕ, ще видите диалогов прозорец с искане. Моля, натиснете \"Позволи\".\n\nМоля, имайте предвид, че това разрешение не означава, че CS3 ще изтощава батерията ви. То ще работи във фонов режим само когато е необходимо, като например при получаване на известия или изтегляне на видеоклипове от официални разширения. Изображение на QR код Премахни от любими - \ No newline at end of file + Колко различни елемента могат да бъдат изтеглени паралелно + Паралелни изтегляния + Едновременни връзки + Колко едновременни връзки може да използва всяко изтегляне по време на самото изтегляне + Сваляния + Няма интернет връзка.\n\nМоля, свържете се с интернет и опитайте отново, или гледайте вашите изтегляния, докато сте офлайн. + Променя границите на екрана + Overscan + Променя размера на плакатите + Размер на плаката + Винаги изпращай запитване + Задръжте, за да удвоите скоростта + Дълго задържане за смяна на скоростта + diff --git a/app/src/main/res/values-bp/strings.xml b/app/src/main/res/values-bp/strings.xml index cd719ef69..54b9f32df 100644 --- a/app/src/main/res/values-bp/strings.xml +++ b/app/src/main/res/values-bp/strings.xml @@ -721,4 +721,22 @@ Tornar todas as legendas em itálico O volume ultrapassou 100% Deslize para cima novamente para ir além de 100% - \ No newline at end of file + %1$dh %2$dm %3$ds + %1$dm %2$ds + %1$d s + Rótulo de Classificação + Perguntar sempre + Quantos itens diferentes podem ser baixados em paralelo + Downloads em paralelo + Conexões simultâneas + Quantas conexões simultâneas cada download pode usar durante o processo de download + Ir para Downloads + Sem conexão com a internet.\n\nConecte-se à internet e tente novamente, ou acompanhe seus downloads enquanto estiver offline. + Altera os limites da tela + Varredura excessiva + Altera o tamanho dos pôsteres + Tamanhos dos pôsteres + Alternar Velocidade do Pressionamento Longo + Segure para duplicar a velocidade + Sem conta + diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml new file mode 100644 index 000000000..607c879ea --- /dev/null +++ b/app/src/main/res/values-ckb/strings.xml @@ -0,0 +1,87 @@ + + + %1$sئەڵقەى%2$d + تیم:%s + ئەڵقەی %d لە + وەرزی %1$d ئەڵقەی %2$d لە + %1$dd %2$dh %3$dm + %1$dh %2$dm + %dm + %1$dh %2$dm %3$ds + %1$dm %2$ds + %1$ds + پۆستەر + پۆستەری ئەڵقە + پۆستەری سەرەکی + داهاتوو بە هەڕەمەکی + گەڕانەوە + لە سەرەتاوە دەست پێ بکە + گۆڕینی دابینکەر + پێشبینی پاشبنەما + خێرای(x%.2f) + هەڵسەنگاندن:%.1f + نوێکاری نوێ دۆزرایەوە!\n%1$s->%2$s + فیلەر + min%d + کلاود ستریم + سەرەکی + گەڕان + داونڵۆد + ڕێکخستن + گەڕان… + بگەڕێ بۆ%s… + ناسینەوەی قسەکردن بەردەست نییە + دەست بکە بە قسەکردن… + هیچ داتایەک نییە + هەڵبژاردنی زیاتر + ئەڵقەی داهاتوو + ژانرەکان + شەیر + بکەرەوە لە براوزەر + براوزەر + تەماشاکردن + تەواو بووە + دابەزیووە + پلان بۆ سەیرکردن + دووبارە سەیرکردنەوە + تۆرێنت ستریم بکە + ئەم ڤیدیۆیە تۆرێنتە، ئەمەش مانای ئەوەیە کە دەتوانرێت شوێنپێی چالاکیی ڤیدیۆکەت بگرێت.\nپێش ئەوەی بەردەوام بیت دڵنیابە لە تۆرێنتینگ تێدەگەیت. + سەرچاوەکان + ژێرنووسەکان + دووبارە هەوڵبدە پەیوەندی… + گەڕانەوە بۆ دواوە + پۆستەر + داونلۆد + دابەزێنراوە + دادەبەزێندرێت + دابەزاندن وەستاوە + داگرتن دەستی پێکرد + داونلۆدکردن شکستی هێنا + داونلۆد هەڵوەشایەوە + داونلۆد تەواو بووە + ئەو شتانە هەڵبژێرە کە دەتەوێت بیسڕیتەوە + لە ئێستادا هیچ داونلۆدێک نییە. + بەردەستە بۆ سەیرکردنی ئۆفلاین + هەموو هەڵبژێرە + هەمووی لابدە + نوێکردنەوە دەستی پێکرد + ڤیدیۆی ناوخۆیی بکەرەوە + کۆگای ناوخۆیی + دۆبلاژ + ژێرنووس + سڕینەوەی فایل + دووبارە دەستپێکردنەوەی دابەزاندن + وەستاندنی دابەزاندن + ڕاپۆرتکردنی هەڵە بە شێوەیەکی ئۆتۆماتیکی لەکاربخە + زانیاری زیاتر + شاردنەوە + زانیاری + فلتەرکردنی ئاماژەکان + ئاماژەکان + لابردن + دۆخی تەماشاکردن دابنێ + جێبەجێکردن + کۆپی + داخستن + سەیڤ + diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index ffab5531f..6980fe029 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -723,4 +723,31 @@ Poloměr pozadí Přejeďte opět nahoru pro pokračování nad 100 % Hlasitost překročila 100 % - \ No newline at end of file + Počet různých položek, které lze stahovat v jednu chvíli + Změní velikost plakátů + Žádné připojení k internetu. \n\nPřipojte se prosím k internetu a zkuste to znovu, nebo sledujte stažené, zatímco jste offline. + Velikost plakátů + Změní hranice obrazovky + Oříznutí + Paralelní stahování + Souběžná připojení + Kolik souběžných připojení může každé stahování použít + Přejít na stažené + Vždy se zeptat + Přepínač rychlosti při podržení + Podržte pro 2x rychlost + %1$dh %2$dmin %3$ds + %1$dmin %2$ds + %1$ds + Štítek hodnocení + Žádný účet + Upravit profilový obrázek + Zadejte adresu profilového obrázku + Adresa nenalezena + Neplatná adresa nebo obrázek + Obrázek úspěšně upraven + Označit jako zhlédnuté po tuto epizodu + Odebrat zhlédnutí po tuto epizodu + Znovu načteno + Znovu načíst poskytovatele + diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 752ffeb7d..1566e000b 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -386,7 +386,7 @@ Einrichtung überspringen Aussehen der App passend zu dem des Geräts ändern Absturzmeldung - Was möchtest du sehen? + Was möchten Sie sehen Fertig Erweiterungen Repository hinzufügen @@ -414,7 +414,7 @@ Community-Repositories anzeigen Öffentliche Liste Alle Untertitel in Großbuchstaben - Alle Plugins aus diesem Repository herunterladen? + Warnung: CloudStream 3 übernimmt keine Verantwortung für das nutzen von Dritt-Anbieter-Erweiterungen und leistet für sie keine Unterstützung! %s (Deaktiviert) Spuren Audiospuren @@ -630,7 +630,7 @@ Warnung Code läuft ab in %1$dm %2$ds CloudStream-Wiki - Um einen unterbrechungsfreien Download zu gewährleisten und Benachrichtigungen für abonnierte TV-Shows anzuzeigen, benötigt Cloudstream die Berechtigung, im Hintergrund ausgeführt zu werden. Beim drücken auf 𝙊𝙆 wird ein Dialog angezeigt. Bitte drücke ‚Erlauben‘.\n Bitte beachte, dass dies nicht dazu führt, dass CS3 den Akku dauerhaft entlädt. Es läuft nur im Hintergrund, wenn es nötig ist, beispielsweise zum Empfang von Benachrichtigungen oder dem Download von Videos aus offiziellen Erweiterungen. + Um einen unterbrechungsfreien Download zu gewährleisten und Benachrichtigungen für abonnierte TV-Shows anzuzeigen, benötigt Cloudstream die Berechtigung, im Hintergrund ausgeführt zu werden. Beim drücken auf 𝙊𝙆 wird ein Dialog angezeigt. Bitte drücke ‚Erlauben‘.\n\nBitte beachte, dass dies nicht dazu führt, dass CS3 den Akku dauerhaft entlädt. Es läuft nur im Hintergrund, wenn es nötig ist, beispielsweise zum Empfang von Benachrichtigungen oder dem Download von Videos aus offiziellen Erweiterungen. Dateien löschen Löschen (%1$d | %2$s) Alle auswählen @@ -692,9 +692,35 @@ Spracherkennung ist nicht verfügbar Beginne zu sprechen… Player-Benachrichtigung - Alle Untertitel fettgedruckt anzeigen - Alle Untertitel kursiv anzeigen + Alle Untertitel fett machen + Alle Untertitel kursiv machen Hintergrundsradius Lautstärke hat 100% überschritten Noch einmal nach oben wischen, um 100% zu überschreiten - \ No newline at end of file + Immer fragen + Wie viele verschiedene Elemente können parallel heruntergeladen werden + Parallele Downloads + Gleichzeitige Verbindungen + Wie viele gleichzeitige Verbindungen jeder Download während des Downloads verwenden kann + Zu den Downloads + Keine Internetverbindung.\n\nBitte verbinden Sie sich mit dem Internet und versuchen Sie es erneut, oder schauen Sie sich Ihre Downloads an, während Sie offline sind. + Ändert die Grenzen des Bildschirms + Überscannen + Ändert die Größe von Postern + Größe des Posters + LongPress Geschwindigkeitsschalter + Halten Sie gedrückt, um die 2x Geschwindigkeit zu erhalten + %1$dstd%2$dm%3$ds + %1$dm%2$ds + %1$ds + Bewerungslabel + Kein Konto + Profilbild ändern + Profilbild URL eingeben + Keine URL gefunden + Ungültige URL oder ungültiges Bild + Profilbild erfolgreich geändert + Označi kao gledano do ove epizode + Ukloni epizodu koju sam gledao do sada + Ponovo učitano + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 23480d804..c1f1771dc 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -7,9 +7,9 @@ Descargado %1$d %2$s Borrar repositorio El episodio %d se lanzará en - %1$dh %2$dm - %dm - Poster + %1$d h %2$d m + %d m + Póster Extensiones Archivo descargado Todas las extensiones se desactivaron debido a un fallo para ayudarlo a encontrar la que está causando problemas. @@ -68,7 +68,7 @@ Resumen Créditos Final mixto - Poster del Episodio + Póster del episodio Siguiente episodio Más opciones Actualizar progreso de lo visto @@ -85,28 +85,27 @@ Está seguro que quiere salir? Continuar Descarga Código de idioma (es_LA) - Poster Principal + Póster principal Idioma de la aplicación Ver videos en estos idiomas - Poster + Póster Siguiente al azar Todos los Idiomas Regresar Cambiar proveedor - Vista previa del fondo + Previsualizar fondo Nota:%.1f - ¡Nueva actualización encontrada! -\n%1$s -> %2$s + Se encontró una actualización nueva:\n%1$s → %2$s Descargar Pausar Descarga Formato de fuente Color de Fondo Tamaño de Fuente - Velocidad (%.2fx) + Velocidad (%.2f×) Omitir carga %1$s Ep. %2$d - %1$dd %2$dh %3$dm - Elenco %s + %1$d d %2$d h %3$d m + Elenco: %s Relleno %d min CloudStream @@ -120,7 +119,7 @@ Sin datos Géneros Compartir - Abrir en Navegador + Abrir en navegador Cargando… Ocultar Aplicar @@ -134,7 +133,7 @@ Planeando ver Volviendo a mirar Reproducir película - Reproducir Trailer + Reproducir tráiler Reproducir transmisión en vivo (livestream) Transmitir Torrent Fuentes @@ -563,9 +562,7 @@ Suscríbase Eliminar de favoritos Seleccione una cuenta - Parece que ya existe un elemento potencialmente duplicado en su biblioteca: \'%s.\' -\n -\n¿Desea añadir este elemento de todos modos, sustituir el existente o cancelar la acción? + Parece que ya existe un elemento potencialmente duplicado en su colección: «%s». \n \n¿Quiere añadir este elemento de todos modos, sustituir el existente o cancelar la acción? Introducir el PIN PIN Introduzca el PIN actual @@ -674,7 +671,7 @@ Reiniciar app y aceptar aparición de Stream Torrent para proceder. Software de decodificado El software de decodificado permite que el reproductor reproduzca archivos de video no soportados por su teléfono, lo cual podría causar una reproducción lenta o inestable en alta resolución - La notificación del jugador para controlar el juego desde el segundo plano + La notificación del reproductor para controlar la reproducción en segundo plano Puntuación (Más alta) Episodio (Descendente) Puntuación (Más baja) @@ -686,7 +683,7 @@ Fecha aérea (más antigua) Puntuar %s %d complemento(s) actualizados correctamente. - Ningún complemento fue actualizado. + No se actualizó ningún complemento. Episodio (Ascendente) Reconocimiento de habla no disponible Empiece a hablar… @@ -699,4 +696,27 @@ Radio del fondo El volumen ha excedido 100% Deslice de nuevo hacia arriba para ir más allá del 100% - \ No newline at end of file + Cuántos elementos diferentes pueden descargarse en paralelo + Preguntar siempre + Descargas en paralelo + Conexiones concurrentes + Cuántas conexiones concurrentes para cada descarga se pueden usar + 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 + Overscan + Cambios en el tamaño de los pósteres + Tamaño del póster + %1$d h %2$d m %3$d s + %1$d m %2$d s + %1$d s + Etiqueta de valoración + Mantenga presionado para duplicar la velocidad + Sin cuenta + Ninguna URL Encontrada + URL o imagen no válida + Imagen Actualizada Correctamente + Marcar como vigilado en este episodio + Retirar vigilado para este episodio + Recargado + diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 3bb1de645..f4df505a6 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -10,7 +10,7 @@ دانلود ناموفق بود فیلتر کردن نشانک ها جست‌وجو - اندازه‌کردن + اندازه کردن با صفحه نمایش پر کردن بزرگ‌نمایی همه @@ -130,7 +130,7 @@ برنامه‌ریزی برای تماشا برای بازنشانی به پیشفرض نگه‌دارید کتابخانه - درادامه + در ادامه این فرآیند بطور کامل %s را حذف می‌کند \nآیا از این کار اطمینان دارید؟ نام مخزن و نشانی @@ -140,7 +140,7 @@ اندازه قلم برای تغییر تنظیمات بکشید برای تغییر میزان روشنایی یا صدا در گوشه چپ و راست به بالا یا پایین بکشید - بروزرسانی خودکار افزونه + بروزرسانی خودکار پلاگین آغاز زبان برنامه پخش قسمت @@ -151,8 +151,8 @@ رنگ حاشیه متن دکمه تغییر‌اندازه پخش‌کننده گزینه سرعت در پخش‌کننده را اضافه می‌کند - بروزرسانی‌ و پشتیبانی - نمایش پوستر از طریق Kitsu + بروزرسانی و پشتیبانی + نمیش پوستر از kitsu جستجوی پیشرفته فصل قسمت @@ -166,12 +166,12 @@ حساب‌ها و امنیت نمایش Logcat 🐈 پیوند در حافظه موقت کپی شد - هیچ پیوندی یافت‌نشد + هیچ پیوندی یافت‌ نشد درام آسیایی - بارگیری خودکار افزونه‌ها + بارگیری خودکار افزونه‌ها (پلاگین ها) مستند سرعت پخش - هیچ قسمتی یافت‌نشد + هیچ قسمتی یافت‌ نشد امتیاز: %.1f قلم‌ها را با گذاشتن در %s وارد کنید ادامه @@ -179,12 +179,12 @@ −۳۰ +۳۰ حذف پرونده - نمایش پیش‌پرده‌ها + نمایش تریلر ها قسمت‌ها %dد \nباقی‌مانده گیتهاب - نهان کردن کیفیت ویدئو انتخابی در نتایج جستجو + پنهان کردن ویدیو مشخص شده از نتایج جستجو لغو %s \nباقی‌مانده @@ -260,8 +260,8 @@ زبان برنامه پیدا نشد خطا - قابلیت‌های - ارائه‌دهنده‌ها + قابلیت‌ها + ارائه‌دهنده ها خطای دانلود، دسترسی به حافظه را چک کنید کیفیت کیفیت و عنوان @@ -306,4 +306,55 @@ برای مکث دو بار در وسط ضربه بزنید Use system brightness in the app player instead of a dark overlay به‌روزرسانی پیشرفت ساعت - \ No newline at end of file + شروع کنید به صحبت کردن… + جواب سرچ های شما با تامین کننده + فقط درصورت کرش اطلاعات فرستاده می شوند + هیچ اطلاعاتی (دیتایی) فرستاده نمی شود + نشان دادن آپدیت های برنامه + دوباره پروسه راه اندازی را انجام بده + نسخه های پیشین را آپدیت کن + نصب کننده APK + برنامه سبک رمان از توسعه دهندگان یکسان + برنامه انیمه از توسعه دهندگان یکسان + این ارائه دهنده هیچ پشتیبانی از کروم کست ندارد + متأسفیم، برنامه کرش کرد. یک گزارش از این باگ بطور ناشناس به توسعه دهندگان فرستاده می‌شود + با شکست مواجه شد + اخطار + امتیاز + استریم های زنده + مثبت ۱۸ سال + انیمه خانگی + پخش زنده + مثبت ۱۸ سال + موسیقی + کتاب صوتی + صدا + پادکست + پخش کردن در برنامه + دانلود اتوماتیک + هیچ آپدیتی پیدا نشد + قفل + تغییر اندازه + منبع + دیگر نمایش نده + این آپدیت را اسکیپ کن + آپدیت + اگر در دستگاه های دارای حافظه کم در تنظیمات بالا گذاشته شود باعث کرش می‌شود، مانند تلویزیون های اندروید + از بین بردن سایت + راه دانلود + نمایش دادن انیمه های دوبله شده یا دارای زیرنویس + هشدار + لینک ها + آپدیت های برنامه + افزونه ها + تلویزیون اندروید + محافظت + اکانت ها + زیرنویس ها + حالت عادی + کلی + دکمه رندوم + ­همه افزونه ها را تست کنید + خودکار + رنگ اصلی + diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml index 1ad7cbc8a..6e75dea5a 100644 --- a/app/src/main/res/values-fil/strings.xml +++ b/app/src/main/res/values-fil/strings.xml @@ -2,4 +2,58 @@ hide_player_control_names_key Maling PIN. Pakisubukang muli. - \ No newline at end of file + 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. + Ang abiso ng player para sa pagkontrol sa pag-playback mula sa background + Matagumpay na na-update ang %d plugin! + Binabago ang mga hangganan ng screen + Gawing bold ( ≖‿ ≖ ) ang lahat ng subtitle + Gawing italic ang lahat ng subtitle + Lakas ng tunog ay lumampas sa 100% + Muling mag-slide pataas upang lumampas sa 100% + Ipakita ang dialog bago lumabas sa app + Paganahin ang preview thumbnail sa seekbar + Wala pang na-load na mga subtitle + Kumpirmahin bago lumabas + I-restart ang app at tanggapin ang Stream Torrent pop-up upang magpatuloy. + Binibigyang-daan ng software decoding ang player na mag-play ng mga video file na hindi sinusuportahan ng iyong telepono, ngunit maaaring magdulot ng laggy o hindi maayos na pag-playback sa mataas na resolution + Lokasyon ng backup na folder + Itago ang mga pangalan ng mga kontrol ng player + Na-back up na ngayon ang iyong data sa CloudStream. Bagama\'t napakababa ng posibilidad nito, lahat ng device ay maaaring kumilos nang iba. Sa pambihirang kaso, na ma-lock out ka mula sa pag-access sa app, ganap na i-clear ang data ng app at i-restore mula sa isang backup. Lubos kaming humihingi ng paumanhin para sa anumang abala na dulot nito. + Laktawan ang pagpili ng account sa pagsisimula + May nakitang potensyal na mga nag-doble na mga items sa iyong library: \n\n%s \n\nGusto mo pa rin bang idagdag ang item na ito, palitan ang mga nauna na, o kanselahin ito? + Mga paborito <3 + Dito maaari mong baguhin kung paano inayos ang mga pinagmulan. Kung ang isang video ay may mas mataas na priyoridad, ito ay lalabas na mas mataas sa pagpili ng pinagmulan. Ang kabuuan ng priyoridad ng source at priyoridad sa quality ay ang priyoridad ng video. \n\nSource A: 3 \nQuality B: 7 \nMagkakaroon ng pinagsamang priyoridad ng video na 10. \n\nTANDAAN: Kung ang kabuuan ay 10 o higit pa ang player ay awtomatikong lalaktawan ang paglo-load kapag na-load ang link na iyon! + Hindi magawa nang tama ang UI, isa itong MAJOR BUG at dapat na iulat kaagad %s + Idagdag sa mga paborito <3 + Alisin sa mga paborito </3 + May Natagpuan na Nag-doble + Lumalabas na mayroong potensyal na nag-doble na item sa iyong library: \'%s.\' \n\nGusto mo pa rin bang idagdag ang item na ito, palitan ang nauna, o kanselahin ito? + Para matiyak ang mga walang patid na pag-download at notification para sa mga naka-subscribe na palabas sa TV, kailangan ng CloudStream ng pahintulot na tumakbo sa background. Sa pamamagitan ng pagpindot sa OK, ipapakita sa iyo ang isang dialog. Mangyaring pindutin ang \'Allow\'.\n\nPakitandaan, ang pahintulot na ito ay hindi nangangahulugan na maubos ng CS3 ang iyong baterya. Ito ay gagana lamang sa background kung kinakailangan, tulad ng kapag tumatanggap ng mga notification o nagda-download ng mga video mula sa mga opisyal na extension. + Ang CloudStream ay walang mga site na naka-install sa umpisa. Kailangan mong i-install ang mga site mula sa mga repositoryo. \n \nSumali sa aming Discord o maghanap online. + Babala: Walang pananagutan ang CloudStream 3 sa paggamit ng mga extension ng third-party at hindi kami nagbibigay ng anumang suporta para sa kanila! + Hindi available ang speech recognition + Buksan sa Browser + Ang video na ito ay isang Torrent, nangangahulugan ito na ang iyong aktibidad sa video ay maaaring masubaybayan.\nTiyaking naiintindihan mo ang Torrenting bago magpatuloy. + Piliin ang Mga Item na Tatanggalin + Kasalukuyang walang mga dina-download. + 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. + Ipinagpapatuloy ang pag-playback sa isang miniature na player sa ibabaw ng iba pang mga app + Nagdaragdag ng opsyon sa bilis sa player + Mag-swipe mula sa gilid patungo sa kabilang gilid upang kontrolin ang iyong posisyon sa isang video + Mag-slide pataas o pababa sa kaliwa o kanang bahagi upang baguhin ang liwanag o volume + 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-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 853eff4ce..687bb98f6 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -595,8 +595,7 @@ Impossible d\'ouvrir les informations de l\'application CloudStream. Déverrouiller CloudStream Musique - %s -\nrestants + %s \nrestants Erreur d\'accès au presse-papiers, veuillez réessayer. OK L\'authentification biométrique n\'est pas prise en charge sur cet appareil @@ -688,6 +687,33 @@ Le volume a dépassé 100 % Glisser à nouveau vers le haut pour dépasser 100 % Mettre tous les sous-titres en gras - Rayon de l\'arrière-plan + Rayon d\'arrière-plan Mettre tous les sous-titres en italique - \ No newline at end of file + %1$dh %2$dm %3$ds + %1$dm %2$ds + %1$ds + Étiquette de notation + Demandez toujours + Combien d\'objets différents peuvent être téléchargés en parallèle + Téléchargements parallèles + Connections simultanées + Combien de connexions simultanées chaque téléchargement peut utiliser pendant le téléchargement + Aller aux téléchargements + Pas de connexion Internet.\n\nConnectez-vous à Internet et essayez à nouveau, ou regardez vos téléchargements pendant que vous êtes hors ligne. + Change les limites de l\'écran + Surbalayage + Change la taille des affiches + Taille des affiches + Basculement de vitesse par appui long + Maintenir pour vitesse 2x + Pas de compte + Modifier le profil Image + Saisir l\'URL de la photo de profil + Aucune URL trouvée + Image ou URL Invalide + Image mise à jour avec succès + Marquer comme vu jusqu’à cet épisode + Supprimer l\'historique de visionnage jusqu\'à cet épisode + Rechargé + Recharger le fournisseur + diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index c1f495a81..945f06555 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -319,7 +319,7 @@ खोज परिणामों में चयनित वीडियो गुणवत्ता छुपाएं प्रकरण - + %1$d-%2$d @string/home_play @@ -327,4 +327,18 @@ जोड़े गए रिपॉजिटरीज से अभी तक इंस्टॉल नहीं किए गए सभी प्लगइन्स को स्वचालित रूप से इंस्टॉल करें। वाक् पहचान उपलब्ध नहीं है बोलना शुरू करें… - \ No newline at end of file + "%1$dघं %2$d मि %3$dसे" + %1$d मि %2$d से + %1$d सेकंड + प्लगइन डाउनलोड को फ़िल्टर करने के लिए मोड चुने + ऐप अपडेट दिखाएं + सेटअप प्रक्रिया दोबारा करें + प्री-रिलीज़ पर अपडेट करें + APK इंस्टॉलर + कुछ फ़ोन नए पैकेज इंस्टॉलर का समर्थन नहीं करते हैं। यदि अपडेट इंस्टॉल नहीं हो रहे हैं, तो पुराने विकल्प का उपयोग करें। + डिफ़ॉल्ट मान पर रीसेट करें + ऑडियो + पॉडकास्ट + रेंडरर त्रुटि + एन्कोडिंग त्रुटि + diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 1c0c5b577..c05ba0dc3 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -637,7 +637,7 @@ Pogreška pri kopiranju. Kopirajte zapisnik i kontaktirajte podršku aplikacije. Da biste osigurali neprekinuta preuzimanja i obavijesti za pretplaćene TV emisije, CloudStream treba dopuštenje za rad u pozadini. Pritiskom na \"U redu\" prikazat će vam se dijaloški okvir s zahtjevom. Molimo vas da pritisnete \"Dopusti\". \n\nNapominjemo da ovo dopuštenje ne znači da će CS3 trošiti vašu bateriju. Aplikacija će raditi u pozadini samo kada je to potrebno, primjerice prilikom primanja obavijesti ili preuzimanja videozapisa iz službenih proširenja. 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 + Sezona %1$d epizoda %2$d izlazi za Cast mirror Odaberi uređaj za emitiranje CloudStream Wiki @@ -723,4 +723,31 @@ Online Prepoznavanje govora nije dostupno Počnite govoriti… - \ No newline at end of file + %1$dh %2$dmin %3$ds + %1$dmin %2$ds + %1$ds + Oznaka za ocjenjivanje + Uvijek pitaj + Nema računa + Koliko se različitih stavki može istovremeno preuzimati + Istovremena preuzimanja + Istovremene veze + Koliko istovremenih veza svako preuzimanje može koristiti tijekom preuzimanja + Idi na preuzimanja + Nema internetske veze.\n\nPovežite se s internetom i pokušajte ponovo ili gledajte svoje preuzete sadržaje kada niste povezani s internetom. + Mijenja granice ekrana + Slika veća od ekrana + Mijenja veličinu postera + Veličina postera + Prebacivanje brzine dugim pritiskom + Držite pritisnuto za dvostruku brzinu + Uredi sliku profila + Unesite URL slike profila + Nijedan URL nije pronađen + Nevaljan URL ili slika + Slika uspješno aktualizirana + Označi kao gledano do ove epizode + Ukloni gledano do ove epizode + Ponovo učitano + Usluga ponovnog učitavanja + diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 3350f3887..5311a6c3f 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -265,7 +265,7 @@ Forrás Bevezető intro átugrása Ne mutasd újra - A(z) %d epizód ekkor jelenik meg: + A(z) %d epizód ekkor jelenik meg Szüneteltetve Elvetve Minőségi jelzés @@ -599,4 +599,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-in/strings.xml b/app/src/main/res/values-in/strings.xml index f0bbe1ec3..f8bae875b 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -649,7 +649,7 @@ Kunjungi %s di ponsel pintar atau komputer Anda dan masukkan kode di atas Pratinjau bilah pencarian Aktifkan pratinjau gambar mini di bilah pencarian - Sembunyikan nama kontrol pemain + Sembunyikan Nama Kontrol Pemain Buka arsip Mainkan dari Awal Tidak ada unduhan. @@ -719,4 +719,31 @@ Radius Latar Belakang Volume telah melebihi 100% Geser ke atas lagi untuk melampaui 100% - \ No newline at end of file + Selalu tanya + Berapa banyak item yang bisa diunduh secara paralel + Pengunduhan paralel + Ubah ukuran poster + Ukuran poster + Tahan untuk kecepatan 2x + Sambungan internet tidak tersedia.\n\nSilakan sambung ke internet dan coba lagi, atau tonton unduhan Anda saat sedang luring. + Ubah batas layar + Kunjungi Unduhan + Jumlah sambungan yang bisa digunakan secara bersamaan saat mengunduh + %1$dh %2$dm %3$ds + %1$dm %2$ds + %1$dd + Label Penilaian + Tidak ada akun + Koneksi bersamaan + Pemotongan Layar Berlebih + Alih Kecepatan (tekan lama) + Sunting Gambar Profil + Masukkan URL Gambar Profil + URL Tidak Ditemukan + URL atau Gambar Tidak Valid + Gambar Profil Berhasil Diperbarui + Tandai sebagai telah ditonton hingga episode ini + Hapus penandaan telah ditonton hingga episode ini + Dimuat Ulang + Muat Ulang Penyedia + diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index ada40b57c..7b8d8dcfa 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -719,4 +719,31 @@ Raggio di sfondo Il volume ha superato il 100% Scorri di nuovo verso l\'alto per andare oltre il 100% - \ No newline at end of file + Quanti oggetti possono essere scaricati contemporaneamente + Download paralleli + Connessioni concorrenti + Quante connessioni simultanee può utilizzare ogni download durante il download + Vai ai Downloads + Nessuna connessione a internet. \n\nConnettiti a Internet e riprova oppure guarda i tuoi download mentre sei offline. + Modifica limiti dello schermo + Overscan + Modifica dimensioni dei poster + Dimensione poster + Chiedi sempre + Attiva/disattiva velocità LongPress + Tieni premuto per velocità 2x + %1$dh %2$dm %3$ds + %1$dm %2$ds + %1$ds + Etichetta di valutazione + Nessun account + Modifica immagine del profilo + Inserisci l\'URL dell\'immagine del profilo + Nessun URL trovato + URL o immagine non validi + Immagine aggiornata con successo + Rimuovi gli episodi visti fino a questo + Contrassegna come visto fino a questo episodio + Ricaricato + Ricarica provider + diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index b2c39da7d..66dcdf242 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -675,4 +675,17 @@ 再度上へスライドして100%以上に調整できます 字幕を斜体にする 背景の半径 - \ No newline at end of file + 常に尋ねること + 並行してダウンロードできるアイテム + 並行ダウンロード + 並行接続 + ダウンロード中に使用できる同時接続数 + ダウンロードへ + インターネットに接続していません。\n\nインターネットに接続して再度お試しいただくか、オフラインの状態でダウンロードをご覧ください。 + 画面の境界を変更する + オーバースキャン + ポスターの大きさを変更 + ポスター + 長押し速度トグル + ホールドで2倍速に + diff --git a/app/src/main/res/values-jv/strings.xml b/app/src/main/res/values-jv/strings.xml new file mode 100644 index 000000000..55344e519 --- /dev/null +++ b/app/src/main/res/values-jv/strings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 36da4ddad..441c7137b 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -484,7 +484,7 @@ 이 목록이 비어 있습니다. 다른 목록으로 전환해 보세요. 필러 라이브 스트리밍 재생 - 스트림 + 네트워크 스트림 유형을 사용하여 검색 개발자에게 %d 바나나 줌 바나나를 주지 않음 @@ -659,4 +659,11 @@ 이 동영상은 토렌트이므로 동영상 활동이 추적될 수 있습니다.\n계속하기 전에 토렌트에 대해 충분히 이해했는지 확인하세요. 오디오 팟캐스트 - \ No newline at end of file + 음성 시작… + 인코딩 오류 + 지원되지 않는 오류 + 음성 인식 사용 불가 + %1$d시간 %2$d분 %3$d초 + %1$d분 %2$d초 + %1$d초 + diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index a6f095484..6cb298198 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -648,7 +648,7 @@ Вчитај прво достапно Рестартирај ја апликацијата и прифати го Stream Torrent искачувачкиот прозорец за да продолжиш. Овозможи torrent во Поставки/Провајдери/Претпочитани медиуми - Софтверското декодирање му овозможува на плеерот да репродуцира видео фајлови што не се поддржани од вашиот телефон, но може да предизвика задоцнување или нестабилна репродукција при висока резолуција. + Софтверското декодирање овозможува плеерот да воспоставува видео фајлови кои не се поддржани од вашиот телефон, но може да предизвика застој или нестабилно репродуцирање при висока резолуција Софтверско декодирање Започнување на процесот на ажурирање на приклучоци! Ажурирај приклучоци @@ -675,4 +675,31 @@ Препознавањето на говор не е достапно Започни со зборување… Радиус на позадина - \ No newline at end of file + Колку различни содржини можат да се симнуваат паралелно + Паралелни симнувања + Истовремени конекции + Колку истовремени конекции може да користи секое симнување додека се симнува + Оди во Симнувања + Нема интернет конекција.\n\nПоврзете се на интернет и обидете се повторно, или гледајте ги симнатите содржини додека сте офлајн. + Ги менува границите на екранот + Оверскен + Менување на големината на постерите + Големина на постер + Секогаш прашувај + Префрлувач на брзина со долго притискање + Држи за да добиеш 2x брзина + %1$dч %2$dм %3$dс + %1$dм %2$dс + %1$dс + Ознака за рејтинг + Нема корисничка сметка + Измени профилна слика + Внеси линк на профилна слика + Линкот не е пронајден + Неважечки линк или слика + Успешно прикачена слика + Означи изгледано до оваа епизода + Избриши изгледано до оваа епизода + Повторно вчитано + Повторно вчитај провајдер + diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index e865d58db..77261cb6c 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -69,7 +69,7 @@ Lumpuhkan pelaporan pepijat automatik Buang Mulakan episod seterusnya apabila episod semasa tamat - Pasang video lokal + Buka video tempatan Tetapan Sari Kata Pemberitahuan episod baru Carian dalam sambungan lain @@ -94,7 +94,7 @@ Dirancang untuk ditonton Tonton semula Sumber - Sari kata + Sarikata Kembali Memuat turun Muat turun dibatalkan @@ -382,8 +382,7 @@ Github tidak dapat dicapai. Menghidupkan proksi jsDelivr… Papar butang rawak di laman utama dan perpustakaan %s (Dilumpuhkan) - Perpustakaan anda kosong :( -\nLog masuk dalam akaun perpustakaan atau tambah cerita di perpustakaan lokal + Perpustakaan anda kosong :(\nLog masuk dalam akaun perpustakaan atau tambah cerita di perpustakaan lokal. Kelewatan sari kata Tarikh Keluar (Tua ke Baru) Tarikh Keluar (Baru ke Tua) @@ -421,8 +420,8 @@ Tamat Tambahan Tambah repositori - Nama repositori - URL repositori + Nama repositori (Pilihan) + Arkibkan URL atau Kod Pendek Dimuat turun %1$d%2$s Semua %s telah dimuat turun Muat turun senarai laman web yang anda ingin gunakan @@ -436,7 +435,7 @@ Versi Pengarang Tetapkan tambahan dahulu - Buka aplikasi dengan Cap Jari, Pengesahan Muka, PIN, Corak dan Password + Buka aplikasi dengan Cap Jari, Pengesahan Muka, PIN, Corak dan Password. Zum Umum Tambahan @@ -489,4 +488,39 @@ Selaraskan kemajuan episod secara automatik Memuatkan fail sandaran Kemaskini dan sandaran - \ No newline at end of file + Ketik dua kali untuk mencari + Gunakan kecerahan sistem dalam pemain apl dan bukannya tindanan gelap + %1$dh %2$dm %3$ds + %1$dm %2$ds + %1$ds + Pengecaman pertuturan tidak tersedia + Mula Bercakap… + Video ini adalah torrent, ini bermakna aktiviti video anda dapat dikesan.\nPastikan anda memahami torrenting sebelum meneruskan. + \@string/home_play + Pemain Mencari Amaun (Seconds) + Secara automatik memasang semua plugin yang belum dipasang dari repositori yang ditambah. + Tunjukkan kemas kini aplikasi + Secara automatik cari kemas kini baru selepas memulakan aplikasi. + Redo Proses Persediaan + Kemas kini kepada Prereleases + Cari kemas kini prerelease bukan sahaja siaran penuh + Pemasang APK + Sesetengah telefon tidak menyokong pemasang pakej baru. Cuba pilihan Legacy jika kemas kini tidak dipasang. + Berikan benen kepada devs + Diberi pisang + Bahasa App + Penyedia ini tidak mempunyai sokongan Chromecast + Tiada pautan yang dijumpai + 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 + Pulihkan data dari sandaran + Sandarkan data + Gagal pulihkan data dari fail %s + Ralat sandaran %s + Hanya hantar data apabila mengalami kegagalan + diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index e4a331a8f..002f1b8b5 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -62,7 +62,7 @@ Download Mislukt Download Geannuleerd Download Gereed - Stream + Netwerkstream Fout bij laden links Interne opslag Dub @@ -123,7 +123,7 @@ Speler Ondertiteling instellingen Chromecast Ondertitels Chromecast ondertitels instellingen - Eigengravy Modus + Afspeelsnelheid Swipe to seek Veeg naar links of rechts om de tijd in de videospeler te regelen Veeg om instellingen te wijzigen @@ -481,7 +481,6 @@ Uitbreidingen Intro Publieke lijst - Alle plugins uit deze repository downloaden? Beoordeling: %s Alle extensies zijn uitgeschakeld door een crash om u te helpen degene te vinden die problemen veroorzaakt. Bekijk de crash info @@ -608,5 +607,17 @@ Verbergen Tonen Seizoen %1$d Aflevering %2$d zal worden uitgebracht in - Kijken vanaf het begin - \ No newline at end of file + Speel van het begin + Repositorynaam en URL + Aanbevelingen tonen + Selecteer items om te verwijderen + Deze video is een Torrent, dit betekent dat je videoactiviteit kan worden gevolgd.\nZorg ervoor dat je Torrenting begrijpt voordat je doorgaat. + Voegt een snelheidsoptie toe in de speler + Open lokale video + Spraakherkenning is niet beschikbaar + Begin met praten… + Beschikbaar om offline te kijken + Selecteer alles + Deselecteer alles + Bericht voor nieuwe afleveringen + diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index 1a7d9c72f..807a3bcc6 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -156,4 +156,7 @@ %1$s ଅ %2$d ଆଦ୍ୟ ବାଦ୍ ଦିଅ hide_player_control_names_key - \ No newline at end of file + ଏପିସୋଡ୍ %d ମୁକ୍ତିଲାଭ କରିବ + ସିଜିନ୍ %1$d ଏପିସୋଡ୍ %2$d ମୁକ୍ତିଲାଭ କରିବ + %1$dଘଣ୍ଟା %2$dମିନିଟ୍ %3$dସେକେଣ୍ଡ + diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 4fb2a33ed..234e23937 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -260,7 +260,7 @@ Ustawienie zbyt wysokiej wartości może powodować problemy na urządzeniach z małą ilością pamięci RAM, takich jak urządzenia Android TV lub stare telefony. Zbyt wysokie ustawienie może powodować problemy na urządzeniach z małą ilością dostęponej pamięci, takich jak urządzenia Android TV. DNS over HTTPS - Przydatne w pomijaniu blokad dostawców internetu + Przydatne w pomijaniu blokad dostawców Internetu Sklonuj stronę Usuń stronę Dodaj klona istniejącej strony z innym adresem URL @@ -700,4 +700,31 @@ Zmień wszystkie napisy na pogrubione Przesuń ponownie w górę, aby przekroczyć 100% Głośność przekroczyła 100% - \ No newline at end of file + Ile równoczesnych połączeń może wykorzystać każde pobieranie + Ile różnych elementów można pobrać równolegle + Pobieranie równoległe + Połączenia równoczesne + Przejdź do pobranych + Brak połączenia z Internetem.\n\nPołącz się z Internetem i spróbuj ponownie lub obejrzyj pobrane pliki, będąc offline. + Nadmiarowość obrazu + Zmienia granice ekranu + Rozmiar plakatu + Zmienia rozmiar plakatów + Zawsze pytaj + Przełączanie prędkości długiego naciśnięcia + Przytrzymaj, aby uzyskać 2x większą prędkość + %1$dh %2$dm %3$ds + %1$dm %2$ds + %1$ds + Etykieta oceny + Bez konta + Edytuj obraz profilowy + Wpisz adres URL obrazu profilowego + Nie znaleziono adresu URL + Nieprawidłowy adres URL lub obraz + Pomyślnie zaktualizowano obraz + Usuń obejrzane do tego odcinka + Oznacz jako obejrzane do tego odcinka + Przeładowano + Przeładuj dostawcę + diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 2a0473b9d..ee293274c 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -610,7 +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á mostrado um diálogo. Prima \"Permitir\".\n\nTenha 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. Reiniciar - Episódio %1$d Episódio %2$d vai ser lançado em + Temporada %1$d Episódio %2$d será lançado em Escolha o dispositivo Transmitir hide_player_control_names_key @@ -669,7 +669,7 @@ Erro de codificação Erro não suportado Ativar torrent nas Configurações/Provedores/Mídia preferida - Torrent não aceito + Reinicie a aplicação e aceite o pop-up do Stream Torrent para continuar. Descodificação por software Descodificação por software permite que o leitor reproduza ficheiros não suportados pelo seu dispositivo, mas pode resultar numa reprodução desfasada ou instável em altas resoluções Incorporada @@ -692,4 +692,36 @@ Data %s Notificação do player para controlar a reprodução em segundo plano Notificações de reprodução - \ No newline at end of file + Colocar todas as legendas em itálico + Raio do fundo + O volume excedeu 100% + Colocar todas as legendas em negrito + Deslize para cima novamente para ir além de 100% + Quantos itens diferentes podem ser baixados em paralelo + Downloads paralelos + Conexões simultâneas + Quantas conexões simultâneas cada download pode usar durante o download + Ir para downloads + Sem conexão com a internet.\n\nConecte-se à internet e tente novamente ou assista aos seus downloads enquanto estiver offline. + Altera os limites da tela + Overscan + Altera o tamanho dos pôsteres + Tamanho do pôster + Sempre perguntar + Pressionamento para acelerar + Pressione para acelerar + %1$dh:%2$dm:%3$ds + %1$dm:%2$ds + %1$ds + Etiqueta de classificação + Nehuma conta + Editar Imagem do Perfil + Inserir URL da Imagem de Perfil + Nenhum URL Encontrado + URL ou Imagem Inválido + Imagem Atualizada com Sucesso + Marcar como assistido o episódio + Removar marcação de assistido até esse episódio + Recarregado + Provedor de Recarregamento + diff --git a/app/src/main/res/values-qt/strings.xml b/app/src/main/res/values-qt/strings.xml index 2ca5a5881..02fc86ce2 100644 --- a/app/src/main/res/values-qt/strings.xml +++ b/app/src/main/res/values-qt/strings.xml @@ -661,4 +661,4 @@ Boo Oo-ahh oo-chit ar-ar Boooooo - \ No newline at end of file + diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index ed516d5cd..6e0719c58 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -642,4 +642,20 @@ Deselectionati totul Acest video este un Torrent, ceea ce înseamnă că activitatea dumneavoastră video poate fi urmărită.\n Asigurați-vă că înțelegeți Torrentul înainte să continuați. Disponibil pentru vizionare offline - \ No newline at end of file + Recogniție de voce nu este disponibilă + Începeți să vorbiți… + Deschideți video local + Avertisment + Sunteți siguri că doriți să ștergeți definitiv următoarele fișiere?\n\n%s + Veți mai și șterge definitiv toate episoadele în seria următoare:\n\n%s + Sunteți siguri ca doriți sa ștergeți definitiv toate episoadele în seria următoare?\n\n%s + Audio + Podcast + Eroare de codificare + Eroare Nesuportată + Etichetă de clasificare + Ștergeți fișiere + Șterge (%1$d | %2$s) + Sunteți sigur că vreți să ștergeți permanent următoarele epsioade în %1$s?\n\n%2$s + Securitate + diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 520229347..2d9d97c0f 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -685,4 +685,31 @@ Сделать все субтитры курсивом Громкость превысила 100% Повысьте снова чтобы перевысить 100% - \ No newline at end of file + Перейти к загрузкам + Нет подключения к Интернету.\n\nПожалуйста, подключитесь к Интернету и повторите попытку, или смотрите свои загрузки, находясь в автономном режиме. + Сколько различных элементов можно загружать параллельно + Параллельные загрузки + Одновременные соединения + Сколько одновременных соединений может использовать каждая загрузка во время скачивания + Изменение границ экрана + Пересканирование + Изменение размера плакатов + Размер плаката + Спрашивать всегда + Переключатель скорости LongPress + Удерживайте, чтобы увеличить скорость в 2 раза + %1$dч %2$dм %3$dс + %1$dм %2$dс + %1$dс + Рейтинговая этикетка + Не существует учетной записи + Изменить изображение профиля + Введите URL изображения профиля + URL не найден + Неверный URL или изображение + Успешное обновление изображения + Отметить как просмотренное до этой серии + Удалите просмотренное до этой серии + Перезагружен + Перезагрузить провайдера + diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 594fef481..0c4da9205 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -686,4 +686,5 @@ Ep %s Betyg%s Uppdatera Plugins - \ No newline at end of file + Gå till Hämtade filer + diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index 7604ec6f8..e66fd80ff 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -563,7 +563,7 @@ விடுபதிகை விரலிடைத் தோல் சில தொலைபேசிகள் புதிய தொகுப்பு நிறுவியை ஆதரிக்கவில்லை. புதுப்பிப்புகள் நிறுவப்படாவிட்டால் மரபு விருப்பத்தை முயற்சிக்கவும். - சந்தா தொலைக்காட்சி நிகழ்ச்சிகளுக்கான தடையற்ற பதிவிறக்கங்கள் மற்றும் அறிவிப்புகளை உறுதிப்படுத்த, கிளவுட்ச்ட்ரீம் பின்னணியில் இயங்க இசைவு தேவை. சரி என்பதை அழுத்துவதன் மூலம், நீங்கள் பயன்பாட்டுத் தகவலுக்கு அனுப்பப்படுவீர்கள். அங்கு, 𝘼𝙥𝙥 𝘼𝙥𝙥 பெறுநர் க்கு உருட்டி, பேட்டரி பயன்பாட்டை 𝙐𝙣𝙧𝙚𝙨𝙩𝙧𝙞𝙘𝙩𝙚𝙙 என அமைக்கவும். தயவுசெய்து கவனிக்கவும், இந்த இசைவு CS3 உங்கள் பேட்டரியை வெளியேற்றும் என்று அர்த்தமல்ல. அறிவிப்புகளைப் பெறும்போது அல்லது உத்தியோகபூர்வ நீட்டிப்புகளிலிருந்து வீடியோக்களைப் பதிவிறக்குவது போன்ற பின்னணியில் மட்டுமே இது செயல்படும். நீங்கள் ரத்து செய்ய தேர்வுசெய்தால், இந்த அமைப்பை பின்னர் 𝙂𝙚𝙣𝙚𝙧𝙖𝙡 in இல் சரிசெய்யலாம். + சந்தா தொலைக்காட்சி நிகழ்ச்சிகளுக்கான தடையற்ற பதிவிறக்கங்கள் மற்றும் அறிவிப்புகளை உறுதிப்படுத்த, கிளவுட்ச்ட்ரீம் பின்னணியில் இயங்க இசைவு தேவை. சரி என்பதை அழுத்துவதன் மூலம், உங்களுக்கு கோரிக்கை உரையாடல் காண்பிக்கப்படும். தயவுசெய்து \'இசைவு\' என்பதை அழுத்தவும். \n\nதயவுசெய்து கவனிக்கவும், இந்த இசைவு CS3 உங்கள் பேட்டரியை வெளியேற்றும் என்று அர்த்தமல்ல. அறிவிப்புகளைப் பெறும்போது அல்லது உத்தியோகபூர்வ நீட்டிப்புகளிலிருந்து வீடியோக்களைப் பதிவிறக்குவது போன்ற பின்னணியில் மட்டுமே இது செயல்படும். பயன்பாட்டு பேட்டரி பயன்பாடு ஏற்கனவே கட்டுப்பாடற்றதாக அமைக்கப்பட்டுள்ளது பயன்பாட்டு புதுப்பிப்பை நிறுவுகிறது… பயன்பாட்டின் புதிய பதிப்பை நிறுவ முடியவில்லை @@ -637,4 +637,48 @@ நீக்கு ( %1$d | %2$s) பின்வரும் அத்தியாயங்களை %1$s இல் நிரந்தரமாக நீக்க விரும்புகிறீர்களா?\n\n %2$s பின்வரும் தொடரில் உள்ள அனைத்து அத்தியாயங்களையும் நீங்கள் நிரந்தரமாக நீக்குவீர்கள்:\n\n %s - \ No newline at end of file + பேச்சு ஏற்பு கிடைக்கவில்லை + பேசத் தொடங்குங்கள்… + ஆடியோ + போட்காச்ட் + குறியீட்டு பிழை + ஆதரிக்கப்படாத பிழை + முதலில் கிடைக்கிறது + அத்தியாயம் (ஏறுதல்) + அத்தியாயம் (இறங்கு) + மதிப்பீடு (மிக உயர்ந்த) + மதிப்பீடு (மிகக் குறைந்த) + காற்று தேதி (புதியது) + காற்று தேதி (பழமையானது) + பாகம் %s + மதிப்பீடு %s + தேதி %s + அமைப்புகள்/வழங்குநர்கள்/விருப்பமான ஊடகங்களில் டொரெண்டை இயக்கவும் + பயன்பாட்டை மறுதொடக்கம் செய்து, தொடர ச்ட்ரீம் டொரண்ட் பாப்-அப் ஏற்றுக்கொள்ளுங்கள். + மென்பொருள் டிகோடிங் + மென்பொருள் டிகோடிங் உங்கள் தொலைபேசியால் ஆதரிக்கப்படாத வீடியோ கோப்புகளை இயக்க பிளேயருக்கு உதவுகிறது, ஆனால் உயர் தெளிவுத்திறனில் பின்னடைவு அல்லது நிலையற்ற பின்னணியை ஏற்படுத்தக்கூடும் + தொகுதி 100% ஐ தாண்டியுள்ளது + 100% க்கு அப்பால் செல்ல மீண்டும் சறுக்கவும் + செருகுநிரல்களைப் புதுப்பிக்கவும் + செருகுநிரல்களை கைமுறையாக புதுப்பிக்கவும் + சொருகி புதுப்பிப்பு செயல்முறையைத் தொடங்குகிறது! + வெற்றிகரமாக புதுப்பிக்கப்பட்டது %d சொருகி (கள்)! + செருகுநிரல்கள் எதுவும் புதுப்பிக்கப்படவில்லை. + பிளேயர் அறிவிப்புகள் + பின்னணியில் இருந்து பின்னணியைக் கட்டுப்படுத்துவதற்கான பிளேயர் அறிவிப்பு + உட்பொதிக்கப்பட்டது + ஆன்லைனில் + அனைத்து வசனங்களையும் தைரியமாக ஆக்குங்கள் + அனைத்து வசன வரிகளையும் சாய்வு செய்யுங்கள் + பின்னணி ஆரம் + எத்தனை வெவ்வேறு உருப்படிகளை இணையாக பதிவிறக்கம் செய்யலாம் + இணை பதிவிறக்கங்கள் + ஒரே நேரத்தில் இணைப்புகள் + பதிவிறக்கும் போது ஒவ்வொரு பதிவிறக்கமும் எத்தனை ஒரே நேரத்தில் இணைப்புகளைப் பயன்படுத்தலாம் + பதிவிறக்கங்களுக்குச் செல்லுங்கள் + இணைய இணைப்பு இல்லை. \n\nதயவுசெய்து இணையத்துடன் இணைத்து மீண்டும் முயற்சிக்கவும், அல்லது நீங்கள் ஆஃப்லைனில் இருக்கும்போது உங்கள் பதிவிறக்கங்களைப் பாருங்கள். + திரையின் எல்லைகளை மாற்றுகிறது + ஓவர்ச்கான் + சுவரொட்டிகளின் அளவை மாற்றுகிறது + சுவரொட்டி அளவு + diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 3a820d2d7..93d2a4928 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -743,4 +743,27 @@ Tüm altyazıları kalın yap Tüm altyazıları italik yap Arka Plan Yarıçapı - \ No newline at end of file + Ekran sığdırma + Eşzamanlı indirmeler + Eşzamanlı bağlantılar + İnternet bağlantısı yok.\n\nLütfen internete bağlanıp tekrar deneyin ya da çevrimdışıyken indirdiğiniz içerikleri izleyin. + İndirilenlere Git + Aynı anda kaç farklı öğe indirilebilir + Ekranın sınırlarını değiştirir + Her indirme sırasında kaç eşzamanlı bağlantı kullanılabilir + Afiş boyutu + Afişlerin boyutlarını değiştir + Her zaman sor + Uzun basma hızını değiştir + 2x hız için basılı tut + %1$dsa %2$ddk %3$dsn + %1$ddk %2$dsn + %1$dsn + Reyting etiketi + Hesap yok + Profil Resmini düzenle + Profil Resmi Bağlantısını Girin + URL bulunamadı + Geçersiz Bağlantı veya Görsel + Görsel Başarıyla Güncellendi + diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 30e1e314b..585cf247f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -93,10 +93,10 @@ Заповнювач Відтворити в CloudStream Мережева трансляція - Перегляд + Переглядаю Поділитися Відкладено - Повторний перегляд + Повторно переглядаю Завантажити Переглянути трансляцію Джерела @@ -106,7 +106,7 @@ Завантаження розпочато Не вдалося завантажити Оновлення розпочато - Помилка завантаження покликань + Помилка завантаження посилань Призупинити завантаження Переглянути файл Докладніше @@ -160,7 +160,7 @@ Дати банан розробникам Мова застосунку Цей постачальник не має підтримування Chromecast - Покликань не знайдено + Посилань не знайдено Переглянути епізод Скинути до типових значень Немає сезона @@ -671,4 +671,31 @@ Зробити всі субтитри курсивом Гучність перевищила 100% Ще раз проведіть угору, щоби перевищити 100% - \ No newline at end of file + Одночасних з’єднань + Змінює межі екрана + Обрізання зображення + Перейти до завантажень + Немає підключення до Інтернету.\n\nБудь ласка, підключіться до Інтернету та спробуйте ще раз або перегляньте завантажені відео офлайн. + Скільки різних елементів можна завантажити паралельно + Паралельних завантажень + Скільки одночасних з’єднань може використовувати кожне завантаження + Зміна розміру плакатів + Розмір постера + Завжди запитуйте + Змінювати швидкість при утриманні + Утримуйте, щоб отримати 2-кратну швидкість + %1$dгод %2$dхв %3$dс + %1$dхв %2$dс + %1$dс + Мітка рейтингу + Немає облікового запису + Редагувати зображення профілю + Введіть URL-адресу зображення профілю + URL-адресу не знайдено + Недійсна URL-адреса або зображення + Зображення успішно оновлено + Позначити як переглянуте до цього епізоду + Вилучити переглянуті до цього епізоду + Перезавантажено + Постачальник послуг поповнення рахунку + diff --git a/app/src/main/res/values-ur/strings.xml b/app/src/main/res/values-ur/strings.xml index 06508bc9e..2c80820a9 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -478,7 +478,7 @@ تاریخ کے ساتھ کھولیں غلط ID - ذخیرہ کا نام + ذخیرہ کا نام (اختیاری) ختم ہونے والا کھل رہا ہے کیا آپ کو یقین ہے کہ آپ ہیاں سے نکلنا چاہتے ہیں؟ @@ -612,4 +612,20 @@ سفارشات دکھائیں آپ کے CloudStream ڈیٹا کا اب بیک اپ لیا گیا ہے۔ اگرچہ اس کا امکان بہت کم ہے، لیکن مختلف ڈیوائس مختلف طریقے سے کام کر سکتے ہیں۔ اگر آپ ایپ تک رسائی حاصل کرنے سے قاصر ہیں تو، ایپ کا ڈیٹا مکمل طور پر صاف کریں اور بیک اپ سے بحال کریں۔ اس سے ہونے والی کسی بھی تکلیف کے لیے ہم بہت معذرت خواہ ہیں۔ hide_player_control_names_key - \ No newline at end of file + سیزن %1$d کی قسط %2$d جاری ہوگی + شروع سےپلے کریں + کلام شناسی دستیاب نہیں + بولنا شروع کریں… + یہ ویڈیو ایک ٹورنٹ ہے،یعنی آپ کی ویڈیو سرگرمی کا سراغ آپ تک لگایا جاسکتا ہے۔\nآگے بڑھنے سے پہلے یہ بات یقینی بنائیں کہ آپ ٹورنٹنگ کو صحیح سے جانتے ہیں + ڈیلیٹ کرنے کے لئے چیزیں چنیں + فی الحال کوئی ڈاونلوڈ نہیں۔ + آف لائن دیکھنے کے لئے دستیاب ہے + سب چُنیں + سب غیر منتخب کریں + لوکل ویڈیو کھولیں + فائلز حذف کریں + انتباہ + آواز + تاریخ %s + اپنے اسمارٹ فون یا کمپیوٹر پر یہ %s وزٹ کریں اور مندرجہ بالا کوڈ ڈالیں + diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 999181db5..89043fd48 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -550,7 +550,7 @@ \nSẽ có mức độ ưu tiên video kết hợp là 10. \n \nLƯU Ý: Nếu tổng là 10 hoặc nhiều hơn, trình phát sẽ tự động bỏ tải khi liên kết đó được tải! - Các phẩm chất + Các chất lượng Bạn đã bình chọn Vô hiệu hoá Không tìm thấy tiện ích, hãy kiểm tra URL và thử lại với VPN @@ -681,7 +681,7 @@ Lỗi mã hóa Cho phép trình phát các tệp video không được điện thoại của bạn hỗ trợ, nhưng có thể bị lag ở độ phân giải cao Bộ giải mã ứng dụng - Khởi động lại ứng dụng và chấp nhận cửa sổ bật lên của Stream Torrent để tiếp tục + Khởi động lại ứng dụng và chấp nhận cửa sổ Stream Torrent để tiếp tục Kích hoạt torrent trong Cài đặt/Nguồn phim/Thể loại ưu tiên Tải phụ đề đầu tiên có sẵn Âm thanh @@ -712,4 +712,27 @@ Được nhúng Làm tất cả phụ đề in đậm Làm tất cả phụ đề in nghiêng - \ No newline at end of file + Luôn hỏi + Nhãn đánh giá + Số lượng mục khác nhau có thể tải xuống cùng lúc + Tải xuống song song + Kết nối đồng thời + Số lượng kết nối đồng thời có thể sử dụng cho mỗi lượt tải + Đến mục tải xuống + Không có kết nối Internet. \n\nVui lòng kết nối Internet rồi thử lại, hoặc xem các nội dung đã tải xuống khi đang ngoại tuyến. + Thay đổi khung hiển thị màn hình + Vượt khung + Thay đổi kích thước của hình poster + Kích thước poster + Tăng tốc độ phát khi nhấn giữ + Nhấn giữ để tăng tốc độ phát 2x + %1$dh %2$dm %3$ds + %1$dm %2$ds + %1$ds + Không có tài khoản + Đổi hình đại điện + Nhập url hình đại diện + 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 + diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 3ed225bb4..a71390afa 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -743,4 +743,31 @@ 加粗所有字幕 音量已经超过 100% 再次上滑让音量超过 100% - \ No newline at end of file + 并行下载 + 并发连接 + 更改屏幕边界 + 过扫描 + 转到下载 + 可同时下载多少项目 + 每个下载项目下载期间可同时使用多少个连接 + 无互联网连接。\n\n请连接到互联网并重试,或者在离线时观看已下载项目。 + 更改海报尺寸 + 海报尺寸 + 始终询问 + 长按速度开关 + 按住获得 2 倍速 + %1$d小时 %2$d分钟 %3$d秒 + %1$d分钟 %2$d秒 + %1$d秒 + 评分标签 + 无账户 + 编辑个人资料图片 + 输入个人资料图片 URL + 未找到 URL + 无效 URL 或图片 + 成功更新了图片 + 将这一集和之前的标为已观看 + 去除这一集和之前集数的已观看状态 + 已重新加载 + 重新加载视频源 + diff --git a/fastlane/metadata/android/as/full_description.txt b/fastlane/metadata/android/as/full_description.txt index 84bfe12bc..4f8a72fdc 100644 --- a/fastlane/metadata/android/as/full_description.txt +++ b/fastlane/metadata/android/as/full_description.txt @@ -1,10 +1,10 @@ -ক্লাউডষ্ট্ৰীম-3-য়ে আপোনাক চলচ্চিত্ৰ, টিভি-চিৰিজ আৰু এনিমে ষ্ট্ৰীম আৰু ডাউনলোড কৰিবলৈ দিয়ে। +ক্লাউডষ্ট্ৰিম-৩ৰ সৈতে চলচ্চিত্ৰ, টিভি শৃঙ্খলা আৰু এনিমে ষ্ট্ৰিম আৰু ডাউনলোড কৰক। -এপ্পটো কোনো বিজ্ঞাপন আৰু বিশ্লেষণ অবিহনে আহে আৰু -একাধিক ট্ৰেইলাৰ আৰু চলচ্চিত্ৰ ছাইট সমৰ্থন কৰে, আৰু অধিক, উদাহৰণ স্বৰূপে। +এই এপত কোনো বিজ্ঞাপন বা বিশ্লেষণ নাই আৰু +একাধিক ট্ৰেইলাৰ আৰু চলচ্চিত্ৰ ছাইট সমৰ্থন কৰে, আৰু বহুতো, যেনে: -বুকমাৰ্কসমূহ +বুকমাৰ্ক -উপশীৰ্ষক ডাউনলোডসমূহ +উপশিৰোনামা ডাউনলোড -ক্ৰোমকাষ্ট সমৰ্থন +ক্ৰ’মকাষ্ট সমৰ্থন diff --git a/fastlane/metadata/android/as/short_description.txt b/fastlane/metadata/android/as/short_description.txt index a37c6826a..84d0f2883 100644 --- a/fastlane/metadata/android/as/short_description.txt +++ b/fastlane/metadata/android/as/short_description.txt @@ -1 +1 @@ -চলচ্চিত্ৰ, টিভি শৃংখলা , এনিম ষ্ট্ৰীম আৰু ডাউনলোড কৰক। +চলচ্চিত্ৰ, টিভি শৃঙ্খলা আৰু এনিমে ষ্ট্ৰিম আৰু ডাউনলোড কৰক। diff --git a/fastlane/metadata/android/ckb/changelogs/2.txt b/fastlane/metadata/android/ckb/changelogs/2.txt new file mode 100644 index 000000000..07f3a2d59 --- /dev/null +++ b/fastlane/metadata/android/ckb/changelogs/2.txt @@ -0,0 +1 @@ +- گۆڕانکاريەکان زيادکرا! diff --git a/fastlane/metadata/android/ckb/full_description.txt b/fastlane/metadata/android/ckb/full_description.txt new file mode 100644 index 000000000..f8934e41c --- /dev/null +++ b/fastlane/metadata/android/ckb/full_description.txt @@ -0,0 +1,10 @@ +CloudStream-3 ڕێگەت پێدەدات فیلم و زنجیرەی تیڤی و ئەنیمێ ستریم و دابەزێنى. + +ئەپەکە بەبێ هیچ ڕیکلام و شیکارییەک دێت هەروەها +پشتگیری لە چەندین سایتی ترەیلەر و فیلم دەکات، و زیاتر، بۆ نموونە. + +ئاماژەکان + +داونڵۆدکردنى ‌ژێرنووس + +پشتگيرى chromecast دەکات diff --git a/fastlane/metadata/android/ckb/short_description.txt b/fastlane/metadata/android/ckb/short_description.txt new file mode 100644 index 000000000..65edb4b0d --- /dev/null +++ b/fastlane/metadata/android/ckb/short_description.txt @@ -0,0 +1 @@ +ستریم و دابەزاندنی فیلم، و زنجیرە تەلەفزیۆنی و ئەنیمێ. diff --git a/fastlane/metadata/android/ckb/title.txt b/fastlane/metadata/android/ckb/title.txt new file mode 100644 index 000000000..ffb1ecb7d --- /dev/null +++ b/fastlane/metadata/android/ckb/title.txt @@ -0,0 +1 @@ +کلاود ستريم diff --git a/fastlane/metadata/android/es-AR/full_description.txt b/fastlane/metadata/android/es-AR/full_description.txt index 32582b59b..745456a15 100644 --- a/fastlane/metadata/android/es-AR/full_description.txt +++ b/fastlane/metadata/android/es-AR/full_description.txt @@ -1,11 +1,10 @@ -CloudStream-3 te permite ver y descargar Películas, Series de TV y Anime. La app viene sin anuncios ni análisis. Soporta varios sitios de películas, trailers... Y -Más. Características incluídas: +CloudStream-3 le permite transmitir y descargar películas, series de TV y Anime. +La aplicación viene sin anuncios y análisis y +soporta múltiples sitios de película de trailer y más, por ejemplo. -Marcadores +Marcas -Descargar y mirar streams de películas, shows de TV y anime +Subtítulos descargas -Descargar subtítulos - -Soporte para Chromecast +Apoyo Chromecast diff --git a/fastlane/metadata/android/hr/full_description.txt b/fastlane/metadata/android/hr/full_description.txt index 9272be260..65adcdae5 100644 --- a/fastlane/metadata/android/hr/full_description.txt +++ b/fastlane/metadata/android/hr/full_description.txt @@ -1,4 +1,4 @@ -CloudStream-3 omogućuje emitiranje i preuzimanje filmova, TV serija i animea. +CloudStream-3 omogućuje gledanje i preuzimanje filmova, TV serija i animea. Aplikacija ne koristi oglase i analitike te podržava stranice s trailerima, filmovima i više, npr. diff --git a/fastlane/metadata/android/hr/short_description.txt b/fastlane/metadata/android/hr/short_description.txt index 09b5d4c50..f43f8e381 100644 --- a/fastlane/metadata/android/hr/short_description.txt +++ b/fastlane/metadata/android/hr/short_description.txt @@ -1 +1 @@ -Emitirajte i preuzmite filmove, TV serije i anime. +Gledajte i preuzmite filmove, TV serije i anime. diff --git a/fastlane/metadata/android/ja-JP/full_description.txt b/fastlane/metadata/android/ja-JP/full_description.txt new file mode 100644 index 000000000..fea7edd17 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/full_description.txt @@ -0,0 +1,10 @@ +CloudStream-3は映画、TVシリーズ、アニメのストリーミングとダウンロードを可能にします。 + +このアプリは広告やアナリティクスが一切なく、 +、複数の予告編や映画サイトなどをサポートしています。 + +ブックマーク + +字幕ダウンロード + +Chromecastサポート diff --git a/fastlane/metadata/android/ja-JP/short_description.txt b/fastlane/metadata/android/ja-JP/short_description.txt new file mode 100644 index 000000000..6384c1200 --- /dev/null +++ b/fastlane/metadata/android/ja-JP/short_description.txt @@ -0,0 +1 @@ +映画、TVシリーズ、アニメのストリーミングとダウンロード。 diff --git a/fastlane/metadata/android/nl-NL/short_description.txt b/fastlane/metadata/android/nl-NL/short_description.txt new file mode 100644 index 000000000..15ec550c5 --- /dev/null +++ b/fastlane/metadata/android/nl-NL/short_description.txt @@ -0,0 +1 @@ +Stream en download films, tv-shows en anime. diff --git a/fastlane/metadata/android/nl-NL/title.txt b/fastlane/metadata/android/nl-NL/title.txt new file mode 100644 index 000000000..dde89d58f --- /dev/null +++ b/fastlane/metadata/android/nl-NL/title.txt @@ -0,0 +1 @@ +CloudStream From 56e5eb2d63a9041bb795a278ee48bf89eaabb966 Mon Sep 17 00:00:00 2001 From: rockhero1234 <149141736+rockhero1234@users.noreply.github.com> Date: Mon, 15 Sep 2025 23:22:02 +0530 Subject: [PATCH 025/640] fix:removed volume overlay on trailers (#1904) --- .../com/lagradost/cloudstream3/ui/player/FullScreenPlayer.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 0e2090b2d..81330b248 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 @@ -1547,7 +1547,7 @@ open class FullScreenPlayer : AbstractPlayerFragment() { // alpha fade playerBinding?.playerProgressbarLeftHolder?.apply { - if (!isVisible || alpha < 1f) { + if (isFullScreenPlayer && (!isVisible || alpha < 1f)) { alpha = 1f isVisible = true } From 4d5b74c65b8bc22dab6405c2fafc2a63b2f47f85 Mon Sep 17 00:00:00 2001 From: Diogo <24511782+diogob003@users.noreply.github.com> Date: Mon, 15 Sep 2025 15:16:50 -0300 Subject: [PATCH 026/640] Settings: translate languages and use IETF BCP 47 tag (#1894) --- .github/locales.py | 16 +- .../lagradost/cloudstream3/CommonActivity.kt | 33 ++-- .../cloudstream3/plugins/RepositoryManager.kt | 1 + .../ui/settings/SettingsGeneral.kt | 172 +++++++++--------- .../ui/settings/SettingsProviders.kt | 34 ++-- .../ui/settings/extensions/PluginAdapter.kt | 26 ++- .../extensions/PluginDetailsFragment.kt | 13 +- .../ui/settings/extensions/PluginsFragment.kt | 52 +++--- .../settings/extensions/PluginsViewModel.kt | 29 ++- .../ui/setup/SetupFragmentLanguage.kt | 25 +-- .../ui/setup/SetupFragmentProviderLanguage.kt | 76 ++++---- .../{values-ajp => values-apc}/strings.xml | 0 .../{values-bp => values-pt-rBR}/strings.xml | 0 .../cloudstream3/utils/SubtitleHelper.kt | 20 +- 14 files changed, 240 insertions(+), 257 deletions(-) rename app/src/main/res/{values-ajp => values-apc}/strings.xml (100%) rename app/src/main/res/{values-bp => values-pt-rBR}/strings.xml (100%) diff --git a/.github/locales.py b/.github/locales.py index a74d72588..53f2a4c9f 100644 --- a/.github/locales.py +++ b/.github/locales.py @@ -21,9 +21,9 @@ rest, after_src = rest.split(END_MARKER) # Load already added langs languages = {} -for lang in re.finditer(r'Triple\("(.*)", "(.*)", "(.*)"\)', rest): - flag, name, iso = lang.groups() - languages[iso] = (flag, name) +for lang in re.finditer(r'Pair\("(.*)", "(.*)"\)', rest): + name, iso = lang.groups() + languages[iso] = name # Add not yet added langs for folder in glob.glob(f"{XML_NAME}*"): @@ -32,18 +32,18 @@ for folder in glob.glob(f"{XML_NAME}*"): entry = iso_map.get(iso.lower(),{'nativeName':iso}) languages[iso] = ("", entry['nativeName'].split(',')[0]) -# Create triples -triples = [] +# Create pairs +pairs = [] for iso in sorted(languages.keys()): - flag, name = languages[iso] - triples.append(f'{INDENT}Triple("{flag}", "{name}", "{iso}"),') + name = languages[iso] + pairs.append(f'{INDENT}Pair("{name}", "{iso}"),') # Update settings file open(SETTINGS_PATH, "w+",encoding='utf-8').write( before_src + START_MARKER + "\n" + - "\n".join(triples) + + "\n".join(pairs) + "\n" + END_MARKER + after_src diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 34698fee0..53ab0ce24 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -1,12 +1,12 @@ package com.lagradost.cloudstream3 -import android.Manifest import android.app.Activity import android.app.PictureInPictureParams import android.content.Context import android.content.pm.PackageManager import android.content.res.Configuration import android.content.res.Resources +import android.Manifest import android.os.Build import android.util.DisplayMetrics import android.util.Log @@ -37,9 +37,8 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager 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.TV import com.lagradost.cloudstream3.ui.settings.Globals.updateTv import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl import com.lagradost.cloudstream3.utils.Coroutines.ioSafe @@ -48,11 +47,12 @@ import com.lagradost.cloudstream3.utils.UIHelper import com.lagradost.cloudstream3.utils.UIHelper.hasPIPPermission import com.lagradost.cloudstream3.utils.UIHelper.shouldShowPIPMode import com.lagradost.cloudstream3.utils.UIHelper.toPx -import org.schabi.newpipe.extractor.NewPipe +import com.lagradost.cloudstream3.utils.UiText import java.lang.ref.WeakReference import java.util.Locale import kotlin.math.max import kotlin.math.min +import org.schabi.newpipe.extractor.NewPipe enum class FocusDirection { Start, @@ -188,17 +188,19 @@ object CommonActivity { } /** - * Not all languages can be fetched from locale with a code. - * This map allows sidestepping the default Locale(languageCode) - * when setting the app language. - **/ - val appLanguageExceptions = hashMapOf( - "zh-rTW" to Locale.TRADITIONAL_CHINESE - ) - - fun setLocale(context: Context?, languageCode: String?) { - if (context == null || languageCode == null) return - val locale = appLanguageExceptions[languageCode] ?: Locale(languageCode) + * Set locale + * @param languageTag shall a IETF BCP 47 conformant tag. + * Check [com.lagradost.cloudstream3.utils.SubtitleHelper]. + * + * See locales on: + * https://github.com/unicode-org/cldr-json/blob/main/cldr-json/cldr-core/availableLocales.json + * https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry + * https://android.googlesource.com/platform/frameworks/base/+/android-16.0.0_r2/core/res/res/values/locale_config.xml + * https://iso639-3.sil.org/code_tables/639/data/all + */ + fun setLocale(context: Context?, languageTag: String?) { + if (context == null || languageTag == null) return + val locale = Locale.forLanguageTag(languageTag) val resources: Resources = context.resources val config = resources.configuration Locale.setDefault(locale) @@ -206,6 +208,7 @@ object CommonActivity { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) context.createConfigurationContext(config) + @Suppress("DEPRECATION") resources.updateConfiguration( config, 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 82537ccbc..f8f0ccd7f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -62,6 +62,7 @@ data class SitePlugin( @JsonProperty("repositoryUrl") val repositoryUrl: String?, // These types are yet to be mapped and used, ignore for now @JsonProperty("tvTypes") val tvTypes: List?, + // Most often a language tag like "en" or "zh-TW" @JsonProperty("language") val language: String?, @JsonProperty("iconUrl") val iconUrl: String?, // Automatically generated by the gradle plugin 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 e82481ffa..be6aa8d6b 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 @@ -2,11 +2,11 @@ package com.lagradost.cloudstream3.ui.settings import android.content.Context import android.net.Uri -import android.os.Build import android.os.Bundle import android.view.View import android.widget.Toast import androidx.appcompat.app.AlertDialog +import androidx.core.os.ConfigurationCompat import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import com.fasterxml.jackson.annotation.JsonProperty @@ -45,86 +45,94 @@ import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.USER_PROVIDER_API import com.lagradost.cloudstream3.utils.VideoDownloadManager import com.lagradost.cloudstream3.utils.VideoDownloadManager.getBasePath +import java.util.Locale // Change local language settings in the app. fun getCurrentLocale(context: Context): String { - val res = context.resources - val conf = res.configuration + val conf = context.resources.configuration - return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { - conf?.locales?.get(0)?.toString() ?: "en" - } else { - @Suppress("DEPRECATION") - conf?.locale?.toString() ?: "en" - } + return ConfigurationCompat.getLocales(conf)?.get(0)?.toLanguageTag() ?: "en" } -// idk, if you find a way of automating this it would be great -// https://www.iemoji.com/view/emoji/1794/flags/antarctica -// Emoji Character Encoding Data --> C/C++/Java Src -// https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes leave blank for auto +/** + * List of app supported languages. + * Language code shall be a IETF BCP 47 conformant tag + * + * See locales on: + * https://github.com/unicode-org/cldr-json/blob/main/cldr-json/cldr-core/availableLocales.json + * https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry + * https://android.googlesource.com/platform/frameworks/base/+/android-16.0.0_r2/core/res/res/values/locale_config.xml + * https://iso639-3.sil.org/code_tables/639/data/all +*/ val appLanguages = arrayListOf( /* begin language list */ - Triple("", "Afrikaans", "af"), - Triple("", "عربي شامي", "ajp"), - Triple("", "አማርኛ", "am"), - Triple("", "العربية", "ar"), - Triple("", "اللهجة النجدية", "ars"), - Triple("", "অসমীয়া", "as"), - Triple("", "azərbaycan dili", "az"), - Triple("", "български", "bg"), - Triple("", "বাংলা", "bn"), - Triple("\uD83C\uDDE7\uD83C\uDDF7", "português brasileiro", "bp"), - Triple("", "čeština", "cs"), - Triple("", "Deutsch", "de"), - Triple("", "Ελληνικά", "el"), - Triple("", "English", "en"), - Triple("", "Esperanto", "eo"), - Triple("", "español", "es"), - Triple("", "فارسی", "fa"), - Triple("", "fil", "fil"), - Triple("", "français", "fr"), - Triple("", "galego", "gl"), - Triple("", "हिन्दी", "hi"), - Triple("", "hrvatski", "hr"), - Triple("", "magyar", "hu"), - Triple("\uD83C\uDDEE\uD83C\uDDE9", "Bahasa Indonesia", "in"), - Triple("", "italiano", "it"), - Triple("\uD83C\uDDEE\uD83C\uDDF1", "עברית", "iw"), - Triple("", "日本語 (にほんご)", "ja"), - Triple("", "ಕನ್ನಡ", "kn"), - Triple("", "한국어", "ko"), - Triple("", "lietuvių kalba", "lt"), - Triple("", "latviešu valoda", "lv"), - Triple("", "македонски", "mk"), - Triple("", "മലയാളം", "ml"), - Triple("", "bahasa Melayu", "ms"), - Triple("", "Malti", "mt"), - Triple("", "ဗမာစာ", "my"), - Triple("", "नेपाली", "ne"), - Triple("", "Nederlands", "nl"), - Triple("", "norsk nynorsk", "nn"), - Triple("", "norsk bokmål", "no"), - Triple("", "ଓଡ଼ିଆ", "or"), - Triple("", "polski", "pl"), - Triple("\uD83C\uDDF5\uD83C\uDDF9", "português", "pt"), - Triple("\uD83E\uDD8D", "mmmm... monke", "qt"), - Triple("", "română", "ro"), - Triple("", "русский", "ru"), - Triple("", "slovenčina", "sk"), - Triple("", "Soomaaliga", "so"), - Triple("", "svenska", "sv"), - Triple("", "தமிழ்", "ta"), - Triple("", "ትግርኛ", "ti"), - Triple("", "Tagalog", "tl"), - Triple("", "Türkçe", "tr"), - Triple("", "українська", "uk"), - Triple("", "اردو", "ur"), - Triple("", "Tiếng Việt", "vi"), - Triple("", "中文", "zh"), - Triple("\uD83C\uDDF9\uD83C\uDDFC", "正體中文(臺灣)", "zh-rTW"), + Pair("Afrikaans", "af"), + Pair("azərbaycan dili", "az"), + Pair("Bahasa Indonesia", "id"), // "in" is deprecated, "id" <-> "in" + Pair("bahasa Melayu", "ms"), + Pair("čeština", "cs"), + Pair("Deutsch", "de"), + Pair("English", "en"), + Pair("Español", "es"), + Pair("Esperanto", "eo"), + Pair("Français", "fr"), + Pair("Galego", "gl"), + Pair("hrvatski", "hr"), + Pair("Italiano", "it"), + Pair("latviešu valoda", "lv"), + Pair("lietuvių kalba", "lt"), + Pair("magyar", "hu"), + Pair("Malti", "mt"), + Pair("mmmm... monke", "qt"), + Pair("Nederlands", "nl"), + Pair("norsk bokmål", "no"), + Pair("norsk nynorsk", "nn"), + Pair("polski", "pl"), + Pair("Português (Brasil)", "pt-BR"), + Pair("Português", "pt"), + Pair("română", "ro"), + Pair("slovenčina", "sk"), + Pair("Soomaaliga", "so"), + Pair("svenska", "sv"), + Pair("Tagalog", "tl"), + Pair("Tiếng Việt", "vi"), + Pair("Türkçe", "tr"), + Pair("Wikang Filipino", "fil"), + Pair("Ελληνικά", "el"), + Pair("български", "bg"), + Pair("македонски", "mk"), + Pair("русский", "ru"), + Pair("українська", "uk"), + Pair("עברית", "he"), // "iw" is deprecated, "he" <-> "iw" + Pair("اردو", "ur"), + Pair("العربية", "ar"), + Pair("اللهجة النجدية", "ars"), // Arabic (Najdi) + Pair("عربي شامي", "apc"), // "ajp" is deprecated, changed to "apc" Arabic (Shami / Levantine) + Pair("فارسی", "fa"), + Pair("ትግርኛ", "ti"), + Pair("አማርኛ", "am"), + Pair("नेपाली", "ne"), + Pair("हिन्दी", "hi"), + Pair("অসমীয়া", "as"), + Pair("বাংলা", "bn"), + Pair("ଓଡ଼ିଆ", "or"), + Pair("தமிழ்", "ta"), + Pair("ಕನ್ನಡ", "kn"), + Pair("മലയാളം", "ml"), + Pair("ဗမာစာ", "my"), + Pair("한국어", "ko"), + Pair("中文", "zh"), + Pair("日本語 (にほんご)", "ja"), + Pair("正體中文(臺灣)", "zh-TW"), /* end language list */ -).sortedBy { it.second.lowercase() } //ye, we go alphabetical, so ppl don't put their lang on top +).sortedBy { it.first.lowercase(Locale.ROOT) } // ye, we go alphabetical, so ppl don't put their lang on top + +fun Pair.nameNextToFlagEmoji(): String { + // fallback to [A][A] -> [?] question mak flag + val flag = SubtitleHelper.getFlagFromIso(this.second) ?: "\ud83c\udde6\ud83c\udde6" + + return "$flag ${this.first}" +} class SettingsGeneral : PreferenceFragmentCompat() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -166,22 +174,18 @@ class SettingsGeneral : PreferenceFragmentCompat() { } getPref(R.string.locale_key)?.setOnPreferenceClickListener { pref -> - val tempLangs = appLanguages.toMutableList() val current = getCurrentLocale(pref.context) - val languageCodes = tempLangs.map { (_, _, iso) -> iso } - val languageNames = tempLangs.map { (emoji, name, iso) -> - val flag = emoji.ifBlank { SubtitleHelper.getFlagFromIso(iso) ?: "ERROR" } - "$flag $name" - } - val index = languageCodes.indexOf(current) + val languageTagsIETF = appLanguages.map { it.second } + val languageNames = appLanguages.map { it.nameNextToFlagEmoji() } + val currentIndex = languageTagsIETF.indexOf(current) activity?.showDialog( - languageNames, index, getString(R.string.app_language), true, { } - ) { languageIndex -> + languageNames, currentIndex, getString(R.string.app_language), true, { } + ) { selectedLangIndex -> try { - val code = languageCodes[languageIndex] - CommonActivity.setLocale(activity, code) - settingsManager.edit().putString(getString(R.string.locale_key), code).apply() + val langTagIETF = languageTagsIETF[selectedLangIndex] + CommonActivity.setLocale(activity, langTagIETF) + settingsManager.edit().putString(getString(R.string.locale_key), langTagIETF).apply() activity?.recreate() } catch (e: Exception) { logError(e) 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 cb7d25fd7..3222992e0 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,8 +2,8 @@ package com.lagradost.cloudstream3.ui.settings import android.os.Bundle import android.view.View -import androidx.navigation.NavOptions import androidx.navigation.fragment.findNavController +import androidx.navigation.NavOptions import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.* @@ -16,7 +16,7 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.getApiDubstatusSettings import com.lagradost.cloudstream3.utils.AppContextUtils.getApiProviderLangSettings import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog -import com.lagradost.cloudstream3.utils.SubtitleHelper +import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard class SettingsProviders : PreferenceFragmentCompat() { @@ -104,35 +104,25 @@ class SettingsProviders : PreferenceFragmentCompat() { } getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener { - activity?.getApiProviderLangSettings()?.let { current -> - val languages = synchronized(APIHolder.apis) { - APIHolder.apis.map { it.lang }.toSet() - .sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName + activity?.getApiProviderLangSettings()?.let { currentLangTags -> + val languagesTagName = synchronized(APIHolder.apis) { + listOf( Pair(AllLanguagesName, getString(R.string.all_languages_preference)) ) + + APIHolder.apis.map { Pair(it.lang, getNameNextToFlagEmoji(it.lang) ?: it.lang) } + .toSet().sortedBy { it.second.substringAfter(" ") } // name ignoring flag emoji } - val currentList = current.map { - languages.indexOf(it) - } - - val names = languages.map { - if (it == AllLanguagesName) { - Pair(it, getString(R.string.all_languages_preference)) - } else { - val emoji = SubtitleHelper.getFlagFromIso(it) - val name = SubtitleHelper.fromTwoLettersToLanguage(it) - val fullName = "$emoji $name" - Pair(it, fullName) - } + val currentIndexList = currentLangTags.map { langTag -> + languagesTagName.indexOfFirst { lang -> lang.first == langTag } } activity?.showMultiDialog( - names.map { it.second }, - currentList, + languagesTagName.map { it.second }, + currentIndexList, getString(R.string.provider_lang_settings), {}) { selectedList -> settingsManager.edit().putStringSet( this.getString(R.string.provider_lang_key), - selectedList.map { names[it].first }.toMutableSet() + selectedList.map { languagesTagName[it].first }.toSet() ).apply() //APIRepository.providersActive = it.context.getApiSettings() } 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 15228b260..58c88a434 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 @@ -11,30 +11,29 @@ import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity -import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN -import com.lagradost.cloudstream3.R -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.utils.setText -import com.lagradost.cloudstream3.utils.txt -import com.lagradost.cloudstream3.ui.settings.Globals.TV +import com.lagradost.cloudstream3.PROVIDER_STATUS_DOWN +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.ui.settings.Globals.isLayout +import com.lagradost.cloudstream3.ui.settings.Globals.TV 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.toPx import com.lagradost.cloudstream3.utils.getImageFromDrawable -import org.junit.Assert -import org.junit.Test +import com.lagradost.cloudstream3.utils.ImageLoader.loadImage +import com.lagradost.cloudstream3.utils.setText +import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji +import com.lagradost.cloudstream3.utils.txt +import com.lagradost.cloudstream3.utils.UIHelper.toPx import java.text.DecimalFormat import kotlin.math.floor import kotlin.math.log10 import kotlin.math.pow +import org.junit.Assert +import org.junit.Test data class PluginViewData( val plugin: Plugin, @@ -216,8 +215,7 @@ class PluginAdapter( binding.langIcon.isVisible = false } else { binding.langIcon.isVisible = true - binding.langIcon.text = - "${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}" + binding.langIcon.text = getNameNextToFlagEmoji(metadata.language) ?: metadata.language } binding.extVotes.isVisible = false 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 80be3cf4b..3aa97dad1 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 @@ -10,21 +10,20 @@ import android.view.ViewGroup import androidx.core.view.isVisible import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.lagradost.cloudstream3.AcraApplication.Companion.openBrowser -import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.FragmentPluginDetailsBinding import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.VotingApi.canVote import com.lagradost.cloudstream3.plugins.VotingApi.getVotes import com.lagradost.cloudstream3.plugins.VotingApi.hasVoted import com.lagradost.cloudstream3.plugins.VotingApi.vote +import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main +import com.lagradost.cloudstream3.utils.getImageFromDrawable 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.SubtitleHelper.getNameNextToFlagEmoji import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute import com.lagradost.cloudstream3.utils.UIHelper.toPx -import com.lagradost.cloudstream3.utils.getImageFromDrawable class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragment() { @@ -85,9 +84,9 @@ class PluginDetailsFragment(val data: PluginViewData) : BottomSheetDialogFragmen ", " ) pluginLang.text = if (metadata.language == null) - getString(R.string.no_data) - else - "${getFlagFromIso(metadata.language)} ${fromTwoLettersToLanguage(metadata.language)}" + getString(R.string.no_data) + else + getNameNextToFlagEmoji(metadata.language) ?: metadata.language githubBtn.setOnClickListener { if (metadata.repositoryUrl != null) { 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 b0888dd83..a1526d4d8 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 @@ -6,26 +6,25 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.widget.SearchView import androidx.core.view.isVisible -import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels +import androidx.fragment.app.Fragment import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.BuildConfig -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.databinding.FragmentPluginsBinding import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.setLinearListLayout 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.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar -import com.lagradost.cloudstream3.ui.settings.appLanguages import com.lagradost.cloudstream3.utils.AppContextUtils.getApiProviderLangSettings import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog -import com.lagradost.cloudstream3.utils.SubtitleHelper +import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji import com.lagradost.cloudstream3.utils.UIHelper.toPx const val PLUGINS_BUNDLE_NAME = "name" @@ -56,15 +55,14 @@ class PluginsFragment : Fragment() { // Since the ViewModel is getting reused the tvTypes must be cleared between uses pluginViewModel.tvTypes.clear() - pluginViewModel.languages = listOf() + pluginViewModel.selectedLanguages = listOf() pluginViewModel.search(null) // Filter by language set on preferred media activity?.let { val providerLangs = it.getApiProviderLangSettings().toList() if (!providerLangs.contains(AllLanguagesName)) { - pluginViewModel.languages = mutableListOf("none") + providerLangs - //Log.i("DevDebug", "providerLang => ${pluginViewModel.languages.toJson()}") + pluginViewModel.selectedLanguages = mutableListOf("none") + providerLangs } } @@ -89,29 +87,27 @@ class PluginsFragment : Fragment() { } R.id.lang_filter -> { - val languageCodes = pluginViewModel.pluginLanguages + var languagesTagName = pluginViewModel.pluginLanguages + .map { langTag -> Pair(langTag, getNameNextToFlagEmoji(langTag) ?: langTag) } + .sortedBy { it.second.substringAfter(" ") } // name ignoring flag emoji + .toMutableList() - val languageNames = - languageCodes.map { iso -> - val (flag, name) = when (iso) { - AllLanguagesName -> "" to getString(R.string.all_languages_preference) - "none" -> "" to getString(R.string.no_data) - else -> (SubtitleHelper.getFlagFromIso(iso) - ?: "") to (SubtitleHelper.fromTwoLettersToLanguage( - iso, - ) ?: iso) - } - "$flag $name" - } - val selectedList = - pluginViewModel.languages.map { languageCodes.indexOf(it) } + // Move "none" to 1st position as it's special code to indicate unknown/missing language + if (languagesTagName.remove(Pair("none", "none"))) { + languagesTagName.add(0, Pair("none", getString(R.string.no_data))) + } + + val currentIndexList = pluginViewModel.selectedLanguages.map { langTag -> + languagesTagName.indexOfFirst { lang -> lang.first == langTag } + } activity?.showMultiDialog( - languageNames, - selectedList, + languagesTagName.map { it.second }, + currentIndexList, getString(R.string.provider_lang_settings), - {}) { newList -> - pluginViewModel.languages = newList.map { languageCodes[it] } + {} + ) { selectedList -> + pluginViewModel.selectedLanguages = selectedList.map { languagesTagName[it].first } pluginViewModel.updateFilteredPlugins() } } 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 6d20cb348..bf8c28a60 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 @@ -26,6 +26,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread import me.xdrop.fuzzywuzzy.FuzzySearch import java.io.File +// String => repository url typealias Plugin = Pair /** * The boolean signifies if the plugin list should be scrolled to the top, used for searching. @@ -38,27 +39,25 @@ class PluginsViewModel : ViewModel() { private var plugins: List = emptyList() set(value) { // Also set all the plugin languages for easier filtering - val languages = - value.map { it -> - val language = it.plugin.second.language - if (language.isNullOrBlank()) "none" else language - }.distinct().sorted().toMutableList() - // Move "none" to the front as it is a special case - if (languages.remove("none")) { - languages.add(0, "none") + value.map{ pluginViewData -> + val language = pluginViewData.plugin.second.language?.lowercase() + pluginLanguages.add( + when { + language.isNullOrBlank() -> "none" + else -> language.lowercase() + }) + // not sorting as most likely this is a language tag instead of name } - pluginLanguages = languages - field = value } - var pluginLanguages: List = emptyList() + var pluginLanguages = mutableSetOf() // set to avoid duplicates /** filteredPlugins is a subset of plugins following the current search query and tv type selection */ private var _filteredPlugins = MutableLiveData() var filteredPlugins: LiveData = _filteredPlugins val tvTypes = mutableListOf() - var languages = listOf() + var selectedLanguages = listOf() private var currentQuery: String? = null companion object { @@ -229,12 +228,12 @@ class PluginsViewModel : ViewModel() { } private fun List.filterLang(): List { - if (languages.isEmpty()) return this + if (selectedLanguages.isEmpty()) return this // do not filter return this.filter { if (it.plugin.second.language == null) { - return@filter languages.contains("none") + return@filter selectedLanguages.contains("none") } - languages.contains(it.plugin.second.language) + selectedLanguages.contains(it.plugin.second.language?.lowercase()) } } 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 a908db55a..b3429d049 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 @@ -13,13 +13,13 @@ import androidx.preference.PreferenceManager import com.lagradost.cloudstream3.AcraApplication.Companion.setKey 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.safe import com.lagradost.cloudstream3.plugins.PluginManager +import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.ui.settings.appLanguages import com.lagradost.cloudstream3.ui.settings.getCurrentLocale -import com.lagradost.cloudstream3.utils.SubtitleHelper +import com.lagradost.cloudstream3.ui.settings.nameNextToFlagEmoji import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar const val HAS_DONE_SETUP_KEY = "HAS_DONE_SETUP" @@ -68,24 +68,20 @@ class SetupFragmentLanguage : Fragment() { } val current = getCurrentLocale(ctx) - val languageCodes = appLanguages.map { it.third } - val languageNames = appLanguages.map { (emoji, name, iso) -> - val flag = emoji.ifBlank { SubtitleHelper.getFlagFromIso(iso) ?: "ERROR" } - "$flag $name" - } - val index = languageCodes.indexOf(current) + val languageTagsIETF = appLanguages.map { it.second } + val languageNames = appLanguages.map { it.nameNextToFlagEmoji() } + val currentIndex = languageTagsIETF.indexOf(current) arrayAdapter.addAll(languageNames) listview1.adapter = arrayAdapter listview1.choiceMode = AbsListView.CHOICE_MODE_SINGLE - listview1.setItemChecked(index, true) + listview1.setItemChecked(currentIndex, true) - listview1.setOnItemClickListener { _, _, position, _ -> - val code = languageCodes[position] - CommonActivity.setLocale(activity, code) - settingsManager.edit().putString(getString(R.string.locale_key), code) + listview1.setOnItemClickListener { _, _, selectedLangIndex, _ -> + val langTagIETF = languageTagsIETF[selectedLangIndex] + CommonActivity.setLocale(activity, langTagIETF) + settingsManager.edit().putString(getString(R.string.locale_key), langTagIETF) .apply() - activity?.recreate() } nextBtt.setOnClickListener { @@ -108,7 +104,6 @@ class SetupFragmentLanguage : Fragment() { findNavController().navigate(R.id.navigation_home) } } - } } } \ No newline at end of file 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 353e735e9..023f0b80a 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 @@ -10,13 +10,13 @@ import androidx.core.util.forEach import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import androidx.preference.PreferenceManager -import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.AllLanguagesName -import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.APIHolder import com.lagradost.cloudstream3.databinding.FragmentSetupProviderLanguagesBinding import com.lagradost.cloudstream3.mvvm.safe +import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.utils.AppContextUtils.getApiProviderLangSettings -import com.lagradost.cloudstream3.utils.SubtitleHelper +import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar class SetupFragmentProviderLanguage : Fragment() { @@ -50,51 +50,45 @@ class SetupFragmentProviderLanguage : Fragment() { val arrayAdapter = ArrayAdapter(ctx, R.layout.sort_bottom_single_choice) - val current = ctx.getApiProviderLangSettings() - val langs = synchronized(APIHolder.apis) { APIHolder.apis.map { it.lang }.toSet() - .sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName} + val currentLangTags = ctx.getApiProviderLangSettings() - val currentList = - current.map { langs.indexOf(it) }.filter { it != -1 } // TODO LOOK INTO - - val languageNames = langs.map { - if (it == AllLanguagesName) { - getString(R.string.all_languages_preference) - } else { - val emoji = SubtitleHelper.getFlagFromIso(it) - val name = SubtitleHelper.fromTwoLettersToLanguage(it) - "$emoji $name" - } + val languagesTagName = synchronized(APIHolder.apis) { + listOf( Pair(AllLanguagesName, getString(R.string.all_languages_preference)) ) + + APIHolder.apis.map { Pair(it.lang, getNameNextToFlagEmoji(it.lang) ?: it.lang) } + .toSet().sortedBy { it.second.substringAfter(" ") } // name ignoring flag emoji } - arrayAdapter.addAll(languageNames) + val currentIndexList = currentLangTags.map { langTag -> + languagesTagName.indexOfFirst { lang -> lang.first == langTag } + }.filter { it > -1 } + + arrayAdapter.addAll(languagesTagName.map { it.second }) binding?.apply { - listview1.adapter = arrayAdapter - listview1.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE - currentList.forEach { - listview1.setItemChecked(it, true) - } - - listview1.setOnItemClickListener { _, _, _, _ -> - val currentLanguages = mutableListOf() - listview1.checkedItemPositions?.forEach { key, value -> - if (value) currentLanguages.add(langs[key]) + listview1.adapter = arrayAdapter + listview1.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE + currentIndexList.forEach { + listview1.setItemChecked(it, true) } - settingsManager.edit().putStringSet( - ctx.getString(R.string.provider_lang_key), - currentLanguages.toSet() - ).apply() - } - nextBtt.setOnClickListener { - findNavController().navigate(R.id.navigation_setup_provider_languages_to_navigation_setup_media) - } + listview1.setOnItemClickListener { _, _, _, _ -> + val selectedLanguages = mutableSetOf() + listview1.checkedItemPositions?.forEach { key, value -> + if (value) selectedLanguages.add(languagesTagName[key].first) + } + settingsManager.edit().putStringSet( + ctx.getString(R.string.provider_lang_key), + selectedLanguages.toSet() + ).apply() + } - prevBtt.setOnClickListener { - findNavController().popBackStack() - } } + nextBtt.setOnClickListener { + findNavController().navigate(R.id.navigation_setup_provider_languages_to_navigation_setup_media) + } + + prevBtt.setOnClickListener { + findNavController().popBackStack() + } + } } } - - } \ No newline at end of file diff --git a/app/src/main/res/values-ajp/strings.xml b/app/src/main/res/values-apc/strings.xml similarity index 100% rename from app/src/main/res/values-ajp/strings.xml rename to app/src/main/res/values-apc/strings.xml diff --git a/app/src/main/res/values-bp/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml similarity index 100% rename from app/src/main/res/values-bp/strings.xml rename to app/src/main/res/values-pt-rBR/strings.xml diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt index 815690872..fe2818a91 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/SubtitleHelper.kt @@ -2,7 +2,6 @@ package com.lagradost.cloudstream3.utils import com.lagradost.cloudstream3.Prerelease import java.util.Locale -import kotlin.text.RegexOption.IGNORE_CASE // If you find a way to use SettingsGeneral getCurrentLocale() // instead of this function do it. @@ -49,9 +48,9 @@ object SubtitleHelper { val localeOfLocalizeTo = Locale.forLanguageTag(localizedTo ?: getCurrentLocale()) val sysLocalizedName = localeOfLangCode.getDisplayName(localeOfLocalizeTo) - val langCodeWithCountry = "${localeOfLangCode.language} (${localeOfLangCode.country})" + val langCodeWithCountry = "${localeOfLangCode.language} (" // ${localeOfLangCode.country})" val failedToLocalize = - sysLocalizedName.contains(this.IETF_tag, ignoreCase = true) || + sysLocalizedName.equals(this.IETF_tag, ignoreCase = true) || sysLocalizedName.contains(langCodeWithCountry, ignoreCase = true) return if (failedToLocalize) @@ -87,7 +86,7 @@ object SubtitleHelper { // Workaround to avoid junk like "English (original audio)" or "Spanish 123" // or "اَلْعَرَبِيَّةُ (Original Audio) 1" or "English (hindi sub)"… // Subtitle downloads and auto selection should rely on proper language codes - // instead of language names! And remove remove junk beforehand. + // instead of language names! And remove junk beforehand. val arabicDiacritics = Regex("[\\u064B-\\u065B]") val withoutDiacritics = lowLangName.replace(arabicDiacritics, "") val nameWithoutJunk = Regex("^([^()\\s\\d]+)").find(withoutDiacritics)?.value ?: withoutDiacritics @@ -105,7 +104,7 @@ object SubtitleHelper { // ReplaceWith("fromLanguageToTagIETF(input, looseCheck)")) /** * Language name (english or native) -> ISO_639_1 - * @param languageName language name + * @param input language name * @param looseCheck match with `contains()` instead of `equals()` */ fun fromLanguageToTwoLetters(input: String, looseCheck: Boolean): String? { @@ -284,6 +283,11 @@ object SubtitleHelper { // "zh-hant-TW" to "TW" // add to this list is useless as getFlagFromIso() already // handles it. + // Adding here is still an option to overwrite a flag like in: + // "am" to "ET" => Ethiopia flag for Amharic instead of Armenia flag + // For country / region see + // https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 + // https://en.wikipedia.org/wiki/UN_M49 private val lang2country = mapOf( "419" to "ES", // (?_?) Latin America Spanish -> ES or a L.A. country? "aa" to "ET", @@ -506,7 +510,7 @@ object SubtitleHelper { LanguageMetadata("Albanian","Shqip","sq","sq","","sqi","sq"), LanguageMetadata("Amharic","አማርኛ","am","am","amh","amh","am"), LanguageMetadata("Arabic","العربية","ar","ar","ara","ara","ar"), - LanguageMetadata("Arabic (Levantine)","عربي شامي","apc","","","apc","ar"), + LanguageMetadata("Arabic (Levantine)","عربي شامي","apc","","ajp","apc","ar"), // "ajp" is deprecated, keeping for compatibility LanguageMetadata("Arabic (Najdi)","عربي شامي","ars","","","ars","ar"), LanguageMetadata("Aragonese","aragonés","an","an","arg","arg","an"), LanguageMetadata("Armenian","Հայերեն","hy","hy","","hye","hy"), @@ -556,13 +560,13 @@ object SubtitleHelper { LanguageMetadata("Gujarati","ગુજરાતી","gu","gu","guj","guj",""), LanguageMetadata("Haitian","Kreyòl ayisyen","ht","ht","hat","hat",""), LanguageMetadata("Hausa","(Hausa) هَوُسَ","ha","ha","hau","hau",""), - LanguageMetadata("Hebrew","עברית","he","he","heb","heb","he"), + LanguageMetadata("Hebrew","עברית","he","iw","heb","heb","he"), // "iw" is deprecated, keeping for compatibility LanguageMetadata("Hindi","हिन्दी, हिंदी","hi","hi","hin","hin","hi"), LanguageMetadata("Hungarian","Magyar","hu","hu","hun","hun","hu"), LanguageMetadata("Icelandic","Íslenska","is","is","","isl","is"), LanguageMetadata("Ido","Ido","io","io","ido","ido",""), LanguageMetadata("Igbo","Asụsụ Igbo","ig","ig","ibo","ibo","ig"), - LanguageMetadata("Indonesian","Bahasa Indonesia","id","id","ind","ind","id"), + LanguageMetadata("Indonesian","Bahasa Indonesia","id","in","ind","ind","id"), // "in" is deprecated, keeping for compatibility LanguageMetadata("Interlingua","Interlingua","ia","ia","ina","ina","ia"), LanguageMetadata("Interlingue","Interlingue (originally Occidental)","ie","ie","ile","ile",""), LanguageMetadata("Irish","Gaeilge","ga","ga","gle","gle","ga"), From b270989cf38d8d96eb8b512b4437d445d5a22e0a Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Mon, 15 Sep 2025 18:17:06 +0000 Subject: [PATCH 027/640] chore(locales): fix locale issues --- .../ui/settings/SettingsGeneral.kt | 76 ++++++++++--------- 1 file changed, 40 insertions(+), 36 deletions(-) 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 be6aa8d6b..03ac2a4ed 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 @@ -67,63 +67,67 @@ fun getCurrentLocale(context: Context): String { val appLanguages = arrayListOf( /* begin language list */ Pair("Afrikaans", "af"), + Pair("አማርኛ", "am"), + Pair("عربي شامي", "apc"), + Pair("العربية", "ar"), + Pair("اللهجة النجدية", "ars"), + Pair("অসমীয়া", "as"), Pair("azərbaycan dili", "az"), - Pair("Bahasa Indonesia", "id"), // "in" is deprecated, "id" <-> "in" - Pair("bahasa Melayu", "ms"), + Pair("български", "bg"), + Pair("বাংলা", "bn"), Pair("čeština", "cs"), Pair("Deutsch", "de"), + Pair("Ελληνικά", "el"), Pair("English", "en"), - Pair("Español", "es"), Pair("Esperanto", "eo"), + Pair("Español", "es"), + Pair("فارسی", "fa"), + Pair("Wikang Filipino", "fil"), Pair("Français", "fr"), Pair("Galego", "gl"), + Pair("עברית", "he"), + Pair("हिन्दी", "hi"), Pair("hrvatski", "hr"), - Pair("Italiano", "it"), - Pair("latviešu valoda", "lv"), - Pair("lietuvių kalba", "lt"), Pair("magyar", "hu"), + Pair("Bahasa Indonesia", "id"), + Pair("('', 'in')", "in"), + Pair("Italiano", "it"), + Pair("('', 'iw')", "iw"), + Pair("日本語 (にほんご)", "ja"), + Pair("ಕನ್ನಡ", "kn"), + Pair("한국어", "ko"), + Pair("lietuvių kalba", "lt"), + Pair("latviešu valoda", "lv"), + Pair("македонски", "mk"), + Pair("മലയാളം", "ml"), + Pair("bahasa Melayu", "ms"), Pair("Malti", "mt"), - Pair("mmmm... monke", "qt"), + Pair("ဗမာစာ", "my"), + Pair("नेपाली", "ne"), Pair("Nederlands", "nl"), - Pair("norsk bokmål", "no"), Pair("norsk nynorsk", "nn"), + Pair("norsk bokmål", "no"), + Pair("ଓଡ଼ିଆ", "or"), Pair("polski", "pl"), - Pair("Português (Brasil)", "pt-BR"), Pair("Português", "pt"), + Pair("Português (Brasil)", "pt-BR"), + Pair("('', 'pt-rBR')", "pt-rBR"), + Pair("mmmm... monke", "qt"), Pair("română", "ro"), + Pair("русский", "ru"), Pair("slovenčina", "sk"), Pair("Soomaaliga", "so"), Pair("svenska", "sv"), - Pair("Tagalog", "tl"), - Pair("Tiếng Việt", "vi"), - Pair("Türkçe", "tr"), - Pair("Wikang Filipino", "fil"), - Pair("Ελληνικά", "el"), - Pair("български", "bg"), - Pair("македонски", "mk"), - Pair("русский", "ru"), - Pair("українська", "uk"), - Pair("עברית", "he"), // "iw" is deprecated, "he" <-> "iw" - Pair("اردو", "ur"), - Pair("العربية", "ar"), - Pair("اللهجة النجدية", "ars"), // Arabic (Najdi) - Pair("عربي شامي", "apc"), // "ajp" is deprecated, changed to "apc" Arabic (Shami / Levantine) - Pair("فارسی", "fa"), - Pair("ትግርኛ", "ti"), - Pair("አማርኛ", "am"), - Pair("नेपाली", "ne"), - Pair("हिन्दी", "hi"), - Pair("অসমীয়া", "as"), - Pair("বাংলা", "bn"), - Pair("ଓଡ଼ିଆ", "or"), Pair("தமிழ்", "ta"), - Pair("ಕನ್ನಡ", "kn"), - Pair("മലയാളം", "ml"), - Pair("ဗမာစာ", "my"), - Pair("한국어", "ko"), + Pair("ትግርኛ", "ti"), + Pair("Tagalog", "tl"), + Pair("Türkçe", "tr"), + Pair("українська", "uk"), + Pair("اردو", "ur"), + Pair("Tiếng Việt", "vi"), Pair("中文", "zh"), - Pair("日本語 (にほんご)", "ja"), Pair("正體中文(臺灣)", "zh-TW"), + Pair("('', 'zh-rTW')", "zh-rTW"), /* end language list */ ).sortedBy { it.first.lowercase(Locale.ROOT) } // ye, we go alphabetical, so ppl don't put their lang on top From feeae8907dbec3dc9e31cd2f89bcc5d827bdecc8 Mon Sep 17 00:00:00 2001 From: "recloudstream[bot]" <111277985+recloudstream[bot]@users.noreply.github.com> Date: Tue, 16 Sep 2025 22:33:04 +0000 Subject: [PATCH 028/640] chore(locales): fix locale issues --- .../com/lagradost/cloudstream3/ui/settings/SettingsGeneral.kt | 2 ++ app/src/main/res/values-af/strings.xml | 2 +- app/src/main/res/values-apc/strings.xml | 2 +- app/src/main/res/values-ar/strings.xml | 2 +- app/src/main/res/values-bg/strings.xml | 2 +- app/src/main/res/values-ckb/strings.xml | 2 +- app/src/main/res/values-cs/strings.xml | 2 +- app/src/main/res/values-de/strings.xml | 2 +- app/src/main/res/values-es/strings.xml | 2 +- app/src/main/res/values-fa/strings.xml | 2 +- app/src/main/res/values-fil/strings.xml | 2 +- app/src/main/res/values-fr/strings.xml | 2 +- app/src/main/res/values-hi/strings.xml | 4 ++-- app/src/main/res/values-hr/strings.xml | 2 +- app/src/main/res/values-hu/strings.xml | 2 +- app/src/main/res/values-in/strings.xml | 2 +- app/src/main/res/values-it/strings.xml | 2 +- app/src/main/res/values-ja/strings.xml | 2 +- app/src/main/res/values-ko/strings.xml | 2 +- app/src/main/res/values-mk/strings.xml | 2 +- app/src/main/res/values-ms/strings.xml | 4 ++-- app/src/main/res/values-nl/strings.xml | 2 +- app/src/main/res/values-or/strings.xml | 2 +- app/src/main/res/values-pl/strings.xml | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 +- app/src/main/res/values-pt/strings.xml | 2 +- app/src/main/res/values-qt/strings.xml | 2 +- app/src/main/res/values-ro/strings.xml | 2 +- app/src/main/res/values-ru/strings.xml | 2 +- app/src/main/res/values-sv/strings.xml | 2 +- app/src/main/res/values-ta/strings.xml | 2 +- app/src/main/res/values-tr/strings.xml | 2 +- app/src/main/res/values-uk/strings.xml | 2 +- app/src/main/res/values-ur/strings.xml | 2 +- app/src/main/res/values-vi/strings.xml | 2 +- app/src/main/res/values-zh/strings.xml | 2 +- 36 files changed, 39 insertions(+), 37 deletions(-) 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 03ac2a4ed..6ae1ed912 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 @@ -75,6 +75,7 @@ val appLanguages = arrayListOf( Pair("azərbaycan dili", "az"), Pair("български", "bg"), Pair("বাংলা", "bn"), + Pair("('', 'ckb')", "ckb"), Pair("čeština", "cs"), Pair("Deutsch", "de"), Pair("Ελληνικά", "el"), @@ -94,6 +95,7 @@ val appLanguages = arrayListOf( Pair("Italiano", "it"), Pair("('', 'iw')", "iw"), Pair("日本語 (にほんご)", "ja"), + Pair("('', 'ꦧꦱꦗꦮ')", "jv"), Pair("ಕನ್ನಡ", "kn"), Pair("한국어", "ko"), Pair("lietuvių kalba", "lt"), diff --git a/app/src/main/res/values-af/strings.xml b/app/src/main/res/values-af/strings.xml index e27fe0edb..8b08a9875 100644 --- a/app/src/main/res/values-af/strings.xml +++ b/app/src/main/res/values-af/strings.xml @@ -122,4 +122,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-apc/strings.xml b/app/src/main/res/values-apc/strings.xml index be8fee5ba..c6a429fb7 100644 --- a/app/src/main/res/values-apc/strings.xml +++ b/app/src/main/res/values-apc/strings.xml @@ -721,4 +721,4 @@ %1$dد %2$dث %1$dث لايبل الرايتينگ - + \ 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 158739078..ea7f54556 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -758,4 +758,4 @@ ازالة علامة تمت المشاهدة لهذه الحلقة تم تحديث الصفحة تحديث المزود - + \ No newline at end of file diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml index 0444970b1..ce9d11a2b 100644 --- a/app/src/main/res/values-bg/strings.xml +++ b/app/src/main/res/values-bg/strings.xml @@ -710,4 +710,4 @@ Винаги изпращай запитване Задръжте, за да удвоите скоростта Дълго задържане за смяна на скоростта - + \ No newline at end of file diff --git a/app/src/main/res/values-ckb/strings.xml b/app/src/main/res/values-ckb/strings.xml index 607c879ea..79941c8e3 100644 --- a/app/src/main/res/values-ckb/strings.xml +++ b/app/src/main/res/values-ckb/strings.xml @@ -84,4 +84,4 @@ کۆپی داخستن سەیڤ - + \ 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 6980fe029..f58b65859 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -750,4 +750,4 @@ Odebrat zhlédnutí po tuto epizodu Znovu načteno Znovu načíst poskytovatele - + \ 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 1566e000b..e482d20f5 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -723,4 +723,4 @@ Označi kao gledano do ove epizode Ukloni epizodu koju sam gledao do sada Ponovo učitano - + \ 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 c1f1771dc..98adc1bec 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -719,4 +719,4 @@ Marcar como vigilado en este episodio Retirar vigilado para este episodio Recargado - + \ No newline at end of file diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index f4df505a6..2e348017c 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -357,4 +357,4 @@ ­همه افزونه ها را تست کنید خودکار رنگ اصلی - + \ No newline at end of file diff --git a/app/src/main/res/values-fil/strings.xml b/app/src/main/res/values-fil/strings.xml index 6e75dea5a..dbea6c62f 100644 --- a/app/src/main/res/values-fil/strings.xml +++ b/app/src/main/res/values-fil/strings.xml @@ -56,4 +56,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-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 687bb98f6..1d246f1ef 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -716,4 +716,4 @@ Supprimer l\'historique de visionnage jusqu\'à cet épisode Rechargé Recharger le fournisseur - + \ No newline at end of file diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 945f06555..49eaa7352 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -319,7 +319,7 @@ खोज परिणामों में चयनित वीडियो गुणवत्ता छुपाएं प्रकरण - + %1$d-%2$d @string/home_play @@ -341,4 +341,4 @@ पॉडकास्ट रेंडरर त्रुटि एन्कोडिंग त्रुटि - + \ No newline at end of file diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index c05ba0dc3..f65765e7e 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -750,4 +750,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-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 5311a6c3f..02a07a3f6 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -599,4 +599,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-in/strings.xml b/app/src/main/res/values-in/strings.xml index f8bae875b..75e544b30 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -746,4 +746,4 @@ Hapus penandaan telah ditonton hingga episode ini Dimuat Ulang Muat Ulang Penyedia - + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 7b8d8dcfa..af77a6bfd 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -746,4 +746,4 @@ Contrassegna come visto fino a questo episodio Ricaricato Ricarica provider - + \ No newline at end of file diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 66dcdf242..7196cdb22 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -688,4 +688,4 @@ ポスター 長押し速度トグル ホールドで2倍速に - + \ No newline at end of file diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 441c7137b..9ac7e1185 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -666,4 +666,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-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index 6cb298198..9c1cddd0f 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -702,4 +702,4 @@ Избриши изгледано до оваа епизода Повторно вчитано Повторно вчитај провајдер - + \ No newline at end of file diff --git a/app/src/main/res/values-ms/strings.xml b/app/src/main/res/values-ms/strings.xml index 77261cb6c..bd4462e21 100644 --- a/app/src/main/res/values-ms/strings.xml +++ b/app/src/main/res/values-ms/strings.xml @@ -496,7 +496,7 @@ Pengecaman pertuturan tidak tersedia Mula Bercakap… Video ini adalah torrent, ini bermakna aktiviti video anda dapat dikesan.\nPastikan anda memahami torrenting sebelum meneruskan. - \@string/home_play + @string/home_play Pemain Mencari Amaun (Seconds) Secara automatik memasang semua plugin yang belum dipasang dari repositori yang ditambah. Tunjukkan kemas kini aplikasi @@ -523,4 +523,4 @@ Gagal pulihkan data dari fail %s Ralat sandaran %s Hanya hantar data apabila mengalami kegagalan - + \ No newline at end of file diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 002f1b8b5..c034c20fd 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -620,4 +620,4 @@ Selecteer alles Deselecteer alles Bericht voor nieuwe afleveringen - + \ No newline at end of file diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index 807a3bcc6..8d0f604fb 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-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-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 234e23937..2326d9d8f 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -727,4 +727,4 @@ Oznacz jako obejrzane do tego odcinka Przeładowano Przeładuj dostawcę - + \ No newline at end of file diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 54b9f32df..688dbfcad 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -739,4 +739,4 @@ Alternar Velocidade do Pressionamento Longo Segure para duplicar a velocidade Sem conta - + \ 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 ee293274c..bd278b0c3 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -724,4 +724,4 @@ Removar marcação de assistido até esse episódio Recarregado Provedor de Recarregamento - + \ 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 02fc86ce2..2ca5a5881 100644 --- a/app/src/main/res/values-qt/strings.xml +++ b/app/src/main/res/values-qt/strings.xml @@ -661,4 +661,4 @@ Boo Oo-ahh oo-chit ar-ar Boooooo - + \ No newline at end of file diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 6e0719c58..a49bcb6f9 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -658,4 +658,4 @@ Șterge (%1$d | %2$s) Sunteți sigur că vreți să ștergeți permanent următoarele epsioade în %1$s?\n\n%2$s Securitate - + \ 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 2d9d97c0f..8ddd1d199 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -712,4 +712,4 @@ Удалите просмотренное до этой серии Перезагружен Перезагрузить провайдера - + \ 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 0c4da9205..2b6de30af 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -687,4 +687,4 @@ Betyg%s Uppdatera Plugins Gå till Hämtade filer - + \ No newline at end of file diff --git a/app/src/main/res/values-ta/strings.xml b/app/src/main/res/values-ta/strings.xml index e66fd80ff..a71e4df2b 100644 --- a/app/src/main/res/values-ta/strings.xml +++ b/app/src/main/res/values-ta/strings.xml @@ -681,4 +681,4 @@ ஓவர்ச்கான் சுவரொட்டிகளின் அளவை மாற்றுகிறது சுவரொட்டி அளவு - + \ No newline at end of file diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 93d2a4928..b2ff41831 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -766,4 +766,4 @@ URL bulunamadı Geçersiz Bağlantı veya Görsel Görsel Başarıyla Güncellendi - + \ 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 585cf247f..222c60e5f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -698,4 +698,4 @@ Вилучити переглянуті до цього епізоду Перезавантажено Постачальник послуг поповнення рахунку - + \ 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 2c80820a9..a6afb22de 100644 --- a/app/src/main/res/values-ur/strings.xml +++ b/app/src/main/res/values-ur/strings.xml @@ -628,4 +628,4 @@ آواز تاریخ %s اپنے اسمارٹ فون یا کمپیوٹر پر یہ %s وزٹ کریں اور مندرجہ بالا کوڈ ڈالیں - + \ 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 89043fd48..0bc141d24 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -735,4 +735,4 @@ 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 diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index a71390afa..1f1c3645f 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -770,4 +770,4 @@ 去除这一集和之前集数的已观看状态 已重新加载 重新加载视频源 - + \ No newline at end of file From 237690069daf1e53be327ccc5bbe53b8b1369dea Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 20 Sep 2025 13:34:27 +0200 Subject: [PATCH 029/640] feat: add streamup extractor api (#1916) --- .../cloudstream3/extractors/Streamup.kt | 44 +++++++++++++++++++ .../cloudstream3/utils/ExtractorApi.kt | 2 + 2 files changed, 46 insertions(+) create mode 100644 library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamup.kt diff --git a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamup.kt b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamup.kt new file mode 100644 index 000000000..e9898c48e --- /dev/null +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/extractors/Streamup.kt @@ -0,0 +1,44 @@ +package com.lagradost.cloudstream3.extractors + +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.SubtitleFile +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.utils.ExtractorApi +import com.lagradost.cloudstream3.utils.ExtractorLink +import com.lagradost.cloudstream3.utils.ExtractorLinkType +import com.lagradost.cloudstream3.utils.newExtractorLink + +open class Streamup() : ExtractorApi() { + override val name: String = "Streamup" + override val mainUrl: String = "https://strmup.to" + override val requiresReferer: Boolean = false + + override suspend fun getUrl( + url: String, + referer: String?, + subtitleCallback: (SubtitleFile) -> Unit, + callback: (ExtractorLink) -> Unit + ) { + val fileCode = url.substringAfterLast("/") + val fileInfo = app.get("$mainUrl/ajax/stream?filecode=$fileCode") + .parsed() + + callback.invoke( + newExtractorLink( + source = name, + name = name, + url = fileInfo.streamingUrl, + type = ExtractorLinkType.M3U8 + ) + ) + } + + private data class StreamUpFileInfo( + val title: String, + val thumbnail: String, + @JsonProperty("streaming_url") + val streamingUrl: String, + // subtitles seems to always be empty + // val subtitles: List + ) +} \ 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 c0125ebf4..20214d47f 100644 --- a/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt +++ b/library/src/commonMain/kotlin/com/lagradost/cloudstream3/utils/ExtractorApi.kt @@ -218,6 +218,7 @@ import com.lagradost.cloudstream3.extractors.Streamlare import com.lagradost.cloudstream3.extractors.StreamoUpload import com.lagradost.cloudstream3.extractors.Streamplay import com.lagradost.cloudstream3.extractors.Streamsss +import com.lagradost.cloudstream3.extractors.Streamup import com.lagradost.cloudstream3.extractors.Streamwish2 import com.lagradost.cloudstream3.extractors.Strwish import com.lagradost.cloudstream3.extractors.Strwish2 @@ -1107,6 +1108,7 @@ val extractorApis: MutableList = arrayListOf( MoviehabNet(), Jeniusplay(), StreamoUpload(), + Streamup(), GamoVideo(), Gdriveplayerapi(), From 5994a0bad13dab9436eac35e72d3856e431387e0 Mon Sep 17 00:00:00 2001 From: Bnyro Date: Sat, 20 Sep 2025 13:35:44 +0200 Subject: [PATCH 030/640] feat: properly insert copied repo data as new repository (#1917) --- .../ui/settings/extensions/ExtensionsFragment.kt | 11 +++++++++-- .../ui/settings/extensions/RepoAdapter.kt | 6 +++++- 2 files changed, 14 insertions(+), 3 deletions(-) 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 9c5229212..78a43c6b1 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 @@ -232,8 +232,15 @@ class ExtensionsFragment : Fragment() { dialog.show() (activity?.getSystemService(Context.CLIPBOARD_SERVICE) as? ClipboardManager?)?.primaryClip?.getItemAt( 0 - )?.text?.toString()?.let { copy -> - binding.repoUrlInput.setText(copy) + )?.text?.toString()?.let { copiedText -> + if (copiedText.contains(RepoAdapter.SHAREABLE_REPO_SEPARATOR)) { + // text is of format : + val (name, url) = copiedText.split(RepoAdapter.SHAREABLE_REPO_SEPARATOR, limit = 2) + binding.repoUrlInput.setText(url.trim()) + binding.repoNameInput.setText(name.trim()) + } else { + binding.repoUrlInput.setText(copiedText) + } } // dialog.list_repositories?.setOnClickListener { 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 42550091a..f0b188ed4 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 @@ -127,7 +127,7 @@ class RepoAdapter( } repositoryItemRoot.setOnLongClickListener { - val shareableRepoData = "${repositoryData.name} : \n ${repositoryData.url}" + val shareableRepoData = "${repositoryData.name}$SHAREABLE_REPO_SEPARATOR\n ${repositoryData.url}" clipboardHelper(txt(R.string.repo_copy_label), shareableRepoData) true } @@ -144,6 +144,10 @@ class RepoAdapter( } } } + + companion object { + const val SHAREABLE_REPO_SEPARATOR = " : " + } } class RepoDiffCallback( From f5bd9f3121f40ce4a70858c967188eea912c3ee7 Mon Sep 17 00:00:00 2001 From: Kraptor123 <89366989+Kraptor123@users.noreply.github.com> Date: Sat, 20 Sep 2025 14:41:39 +0300 Subject: [PATCH 031/640] 2 New Themes Dracula Theme And Lavender Dreams (#1908) * New 2 Themes Dracula And Lavender Dreams * dracula theme new color changes --- .../lagradost/cloudstream3/CommonActivity.kt | 2 ++ app/src/main/res/values-es/array.xml | 4 ++++ app/src/main/res/values-pl/array.xml | 4 ++++ app/src/main/res/values-tr/array.xml | 4 ++++ app/src/main/res/values-vi/array.xml | 4 ++++ app/src/main/res/values/array.xml | 4 ++++ app/src/main/res/values/colors.xml | 18 ++++++++++++++ app/src/main/res/values/styles.xml | 24 +++++++++++++++++++ 8 files changed, 64 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt index 53ab0ce24..4a75ef147 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt @@ -336,6 +336,8 @@ object CommonActivity { "AmoledLight" -> R.style.AmoledModeLight "Monet" -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) R.style.MonetMode else R.style.AppTheme + "Dracula" -> R.style.DraculaMode + "Lavender" -> R.style.LavenderMode else -> R.style.AppTheme } diff --git a/app/src/main/res/values-es/array.xml b/app/src/main/res/values-es/array.xml index 803fcede1..9c85a5404 100644 --- a/app/src/main/res/values-es/array.xml +++ b/app/src/main/res/values-es/array.xml @@ -225,6 +225,8 @@ Destello Sistema Material You + Dracula + Sueños De Lavanda AmoledLight @@ -233,6 +235,8 @@ Light System Monet + Dracula + Lavender diff --git a/app/src/main/res/values-pl/array.xml b/app/src/main/res/values-pl/array.xml index ec7039a38..708bab010 100644 --- a/app/src/main/res/values-pl/array.xml +++ b/app/src/main/res/values-pl/array.xml @@ -234,6 +234,8 @@ Flashbang System Material You + Dracula + Lawendowe Marzenia AmoledLight @@ -242,6 +244,8 @@ Light System Monet + Dracula + Lavender diff --git a/app/src/main/res/values-tr/array.xml b/app/src/main/res/values-tr/array.xml index dc35585d6..ef99bd389 100644 --- a/app/src/main/res/values-tr/array.xml +++ b/app/src/main/res/values-tr/array.xml @@ -247,6 +247,8 @@ Flaş Bombası Sistem Material You + Dracula + Lavanta Rüyası AmoledLight @@ -255,6 +257,8 @@ Light System Monet + Dracula + Lavender diff --git a/app/src/main/res/values-vi/array.xml b/app/src/main/res/values-vi/array.xml index f75fa1267..f34042839 100644 --- a/app/src/main/res/values-vi/array.xml +++ b/app/src/main/res/values-vi/array.xml @@ -226,6 +226,8 @@ Sáng Hệ thống Material You + Dracula + Giấc Mơ Oải Hương AmoledLight @@ -234,6 +236,8 @@ Light System Monet + Dracula + Lavender diff --git a/app/src/main/res/values/array.xml b/app/src/main/res/values/array.xml index 54ba24c5c..c436ab3b1 100644 --- a/app/src/main/res/values/array.xml +++ b/app/src/main/res/values/array.xml @@ -307,6 +307,8 @@ Flashbang System Material You + Dracula + Lavender Dreams AmoledLight @@ -315,6 +317,8 @@ Light System Monet + Dracula + Lavender diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 885250ec0..66cf8c5be 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -66,6 +66,24 @@ #5f6267 #5f6267 + + #414450 + #282a36 + #44475A + #373844 + #F8F8F2 + #6272A4 + #6272A4 + + + #f7eefc + #fdf0fb + #b794f6 + #f8f5ff + #2d1b47 + #9ab3ff + #7c3aed + #5664B7 #D50000 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 30bd59ce7..a1e87aac7 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -181,6 +181,30 @@ @color/blackText + + + + + ?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 056/640] 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 057/640] 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 058/640] 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 059/640] 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 060/640] 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 061/640] 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 062/640] 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 063/640] 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 064/640] 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 065/640] 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 066/640] 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 067/640] 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 068/640] 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 069/640] 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 070/640] 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 071/640] 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 072/640] 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 073/640] 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 074/640] 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 075/640] 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 076/640] 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 077/640] 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 078/640] 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 079/640] 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 080/640] 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 081/640] 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 281/640] 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 282/640] 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 283/640] 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 284/640] 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 285/640] 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 286/640] 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 287/640] 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 288/640] 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 289/640] 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 290/640] 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 291/640] 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 292/640] 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 293/640] 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 294/640] 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 295/640] 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 296/640] 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 297/640] 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 298/640] 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 299/640] 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 300/640] 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 301/640] 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 302/640] 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 303/640] 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 304/640] 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 305/640] 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 306/640] 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 307/640] 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 308/640] 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 309/640] 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 310/640] 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 311/640] 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 312/640] 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 313/640] 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 314/640] 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 315/640] 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 316/640] 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 317/640] 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 318/640] 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 319/640] 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 320/640] 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 321/640] 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 322/640] 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 354/640] 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 355/640] 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 356/640] 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 357/640] 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 358/640] 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 359/640] 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 360/640] 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 361/640] 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 362/640] 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 363/640] 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 364/640] 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 365/640] 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 + + +