From e1932147595e5c141c88f12af18c3dcb4854dff0 Mon Sep 17 00:00:00 2001
From: Phisher98 <153359846+phisher98@users.noreply.github.com>
Date: Fri, 22 Aug 2025 19:59:57 +0530
Subject: [PATCH 001/639] 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 002/639] 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 003/639] 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 004/639] 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 007/639] 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 008/639] 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 009/639] [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 010/639] 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 011/639] 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 012/639] 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 013/639] 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 014/639] 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 015/639] 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