From ce601a1f0cd395dbf17592301503ac166fe95c47 Mon Sep 17 00:00:00 2001 From: Phisher98 Date: Fri, 30 Jan 2026 20:49:24 +0530 Subject: [PATCH 1/5] Adding Download Metadata (Large Initial) --- .../ui/download/DownloadAdapter.kt | 161 ++++++++++++++++-- .../cloudstream3/ui/result/EpisodeAdapter.kt | 5 + .../ui/result/ResultFragmentPhone.kt | 2 + .../ui/result/ResultViewModel2.kt | 2 + .../cloudstream3/utils/VideoDownloadHelper.kt | 2 + .../download_child_episode_large.xml | 156 +++++++++++++++++ .../layout/download_child_episode_large.xml | 139 +++++++++++++++ 7 files changed, 457 insertions(+), 10 deletions(-) create mode 100644 app/src/main/res/layout-v23/download_child_episode_large.xml create mode 100644 app/src/main/res/layout/download_child_episode_large.xml 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 d0740f66a81..4c37e0edac9 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 @@ -9,10 +9,14 @@ import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.viewbinding.ViewBinding +import com.lagradost.api.Log +import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding +import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeLargeBinding import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable import com.lagradost.cloudstream3.ui.NoStateAdapter import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.download.button.DownloadStatusTell @@ -20,6 +24,13 @@ import com.lagradost.cloudstream3.utils.AppContextUtils.getNameFull import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.ImageLoader.loadImage import com.lagradost.cloudstream3.utils.VideoDownloadHelper +import com.lagradost.cloudstream3.utils.setText +import com.lagradost.cloudstream3.utils.txt +import java.text.DateFormat +import java.text.SimpleDateFormat +import java.util.Date +import java.util.Locale + const val DOWNLOAD_ACTION_PLAY_FILE = 0 const val DOWNLOAD_ACTION_DELETE_FILE = 1 @@ -76,6 +87,7 @@ class DownloadAdapter( companion object { private const val VIEW_TYPE_HEADER = 0 private const val VIEW_TYPE_CHILD = 1 + private const val VIEW_TYPE_CHILD_LARGE = 2 } @@ -366,11 +378,131 @@ class DownloadAdapter( } } + private fun bindChildLarge( + binding: DownloadChildEpisodeLargeBinding, + card: VisualDownloadCached.Child? + ) { + if (card == null) return + val data = card.data + + binding.apply { + episodePoster.loadImage(data.poster) + episodeText.text = root.context.getNameFull(data.name, data.episode, data.season) + + val ratingText = data.score?.toString() + + episodeRating.isVisible = !ratingText.isNullOrBlank() + episodeRating.text = ratingText?.let { "Rated: $it" } + + episodeRuntime.isVisible = data.runtime != null && data.runtime > 0 + episodeRuntime.text = data.runtime?.let { "$it min" } + + episodeDescript.isVisible = !data.description.isNullOrBlank() + episodeDescript.text = data.description.orEmpty() + + episodeDate.isVisible = data.airDate != null + + data.airDate?.let { airDate -> + val formattedAirDate = SimpleDateFormat.getDateInstance( + DateFormat.LONG, + Locale.getDefault() + ).format(Date(airDate)) + episodeDate.setText(txt(formattedAirDate)) + } + + Log.d("Phisher",data.airDate.toString()) + + episodeMetaRow?.isVisible = episodeDate.isVisible || episodeRating.isVisible || episodeRuntime.isVisible + + val posDur = getViewPos(data.id) + episodeProgress.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()) { + episodePlayIcon.setImageResource(R.drawable.ic_baseline_check_24) + episodeProgress.isVisible = false + } else { + episodePlayIcon.setImageResource(R.drawable.netflix_play) + episodeProgress.max = max + episodeProgress.progress = progress + episodeProgress.isVisible = true + } + } + + // Download button + val status = downloadButton.getStatus( + data.id, + card.currentBytes, + card.totalBytes + ) + + if (status == DownloadStatusTell.IsDone) { + downloadButton.setProgress(card.currentBytes, card.totalBytes) + downloadButton.applyMetaData(data.id, card.currentBytes, card.totalBytes) + downloadButton.doSetProgress = false + } else { + downloadButton.resetView() + } + + downloadButton.setDefaultClickListener( + data, + downloadSize, + onItemClickEvent + ) + + downloadButton.isVisible = !isMultiDeleteState + + // Selection / multi-delete parity + downloadChildEpisodeLargeHolder.apply { + when { + isMultiDeleteState -> { + setOnClickListener { + toggleIsChecked(deleteCheckbox, data.id) + } + setOnLongClickListener { + toggleIsChecked(deleteCheckbox, data.id) + true + } + } + else -> { + setOnClickListener { + onItemClickEvent.invoke( + DownloadClickEvent(DOWNLOAD_ACTION_PLAY_FILE, data) + ) + } + setOnLongClickListener { + onItemSelectionChanged.invoke(data.id, true) + true + } + } + } + } + + if (isMultiDeleteState) { + deleteCheckbox?.setOnCheckedChangeListener { _, isChecked -> + onItemSelectionChanged.invoke(data.id, isChecked) + } + } else { + deleteCheckbox?.setOnCheckedChangeListener(null) + } + + deleteCheckbox.apply { + this?.isVisible = isMultiDeleteState + this?.isChecked = card.isSelected + } + } + } + + 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) + VIEW_TYPE_CHILD_LARGE -> DownloadChildEpisodeLargeBinding.inflate(inflater, parent, false) else -> throw IllegalArgumentException("Invalid view type") } return ViewHolderState(binding) @@ -382,25 +514,32 @@ class DownloadAdapter( position: Int ) { when (val binding = holder.view) { - is DownloadHeaderEpisodeBinding -> bindHeader( - binding, - item as? VisualDownloadCached.Header - ) + is DownloadHeaderEpisodeBinding -> + bindHeader(binding, item as? VisualDownloadCached.Header) - is DownloadChildEpisodeBinding -> bindChild( - binding, - item as? VisualDownloadCached.Child - ) + is DownloadChildEpisodeBinding -> + bindChild(binding, item as? VisualDownloadCached.Child) + + is DownloadChildEpisodeLargeBinding -> + bindChildLarge(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 + is VisualDownloadCached.Child -> { + val poster = item.data.poster + if (poster.isNullOrBlank()) { + VIEW_TYPE_CHILD + } else { + VIEW_TYPE_CHILD_LARGE + } + } } } + @SuppressLint("NotifyDataSetChanged") fun setIsMultiDeleteState(value: Boolean) { if (isMultiDeleteState == value) return @@ -408,12 +547,14 @@ class DownloadAdapter( notifyDataSetChanged() // This is shit, but what can you do? } - private fun toggleIsChecked(checkbox: CheckBox, itemId: Int) { + private fun toggleIsChecked(checkbox: CheckBox?, itemId: Int) { + if (checkbox == null) return val isChecked = !checkbox.isChecked checkbox.isChecked = isChecked onItemSelectionChanged.invoke(itemId, isChecked) } + class DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame( oldItem: VisualDownloadCached, 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 818e79d7456..611474d6970 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt @@ -11,6 +11,7 @@ import androidx.preference.PreferenceManager import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import coil3.dispose +import com.lagradost.api.Log import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.R @@ -170,6 +171,8 @@ class EpisodeAdapter( score = item.score, description = item.description, cacheTime = System.currentTimeMillis(), + runtime = item.runTime, + airDate = item.airDate ), null ) { when (it.action) { @@ -386,6 +389,8 @@ class EpisodeAdapter( score = item.score, description = item.description, cacheTime = System.currentTimeMillis(), + runtime = item.runTime, + airDate = item.airDate ), null ) { when (it.action) { 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 4ff73fd84c2..60cdad61b6b 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 @@ -742,6 +742,8 @@ open class ResultFragmentPhone : FullScreenPlayer() { score = ep.score, description = ep.description, cacheTime = System.currentTimeMillis(), + runtime = ep.runTime, + airDate = ep.airDate ), null ) { click -> 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 4b4d0b5fadd..7ff06e96a21 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 @@ -773,6 +773,8 @@ class ResultViewModel2 : ViewModel() { score = episode.score, description = episode.description, cacheTime = System.currentTimeMillis(), + runtime = episode.runTime, + airDate = episode.airDate ) ) diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt index fcee1e45ac8..3e375518f09 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/VideoDownloadHelper.kt @@ -17,6 +17,8 @@ object VideoDownloadHelper { @JsonProperty("score") var score: Score? = null, @JsonProperty("description") val description: String?, @JsonProperty("cacheTime") val cacheTime: Long, + @JsonProperty("airDate") val airDate: Long? = null, + @JsonProperty("runtime") val runtime: Int? = null, override val id: Int, ): DownloadCached(id) { @JsonProperty("rating", access = JsonProperty.Access.WRITE_ONLY) diff --git a/app/src/main/res/layout-v23/download_child_episode_large.xml b/app/src/main/res/layout-v23/download_child_episode_large.xml new file mode 100644 index 00000000000..9daad3ce49b --- /dev/null +++ b/app/src/main/res/layout-v23/download_child_episode_large.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/download_child_episode_large.xml b/app/src/main/res/layout/download_child_episode_large.xml new file mode 100644 index 00000000000..450f95d03c8 --- /dev/null +++ b/app/src/main/res/layout/download_child_episode_large.xml @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 62cdb5070c6eae54826f626eedb42b3dba54ac89 Mon Sep 17 00:00:00 2001 From: Phisher98 Date: Fri, 30 Jan 2026 20:50:46 +0530 Subject: [PATCH 2/5] Adding Download Metadata (Large Initial) --- .../com/lagradost/cloudstream3/ui/download/DownloadAdapter.kt | 4 ---- 1 file changed, 4 deletions(-) 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 4c37e0edac9..20be4d69603 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 @@ -9,8 +9,6 @@ import androidx.core.content.ContextCompat import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.viewbinding.ViewBinding -import com.lagradost.api.Log -import com.lagradost.cloudstream3.APIHolder.unixTimeMS import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.DownloadChildEpisodeBinding import com.lagradost.cloudstream3.databinding.DownloadHeaderEpisodeBinding @@ -410,8 +408,6 @@ class DownloadAdapter( episodeDate.setText(txt(formattedAirDate)) } - Log.d("Phisher",data.airDate.toString()) - episodeMetaRow?.isVisible = episodeDate.isVisible || episodeRating.isVisible || episodeRuntime.isVisible val posDur = getViewPos(data.id) From c662c623370a577d7b07ace422efa804578955ef Mon Sep 17 00:00:00 2001 From: Phisher98 Date: Sun, 1 Feb 2026 18:10:14 +0530 Subject: [PATCH 3/5] Minor Changes --- .../cloudstream3/ui/download/DownloadAdapter.kt | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) 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 20be4d69603..b6163d701d4 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 @@ -392,8 +392,8 @@ class DownloadAdapter( episodeRating.isVisible = !ratingText.isNullOrBlank() episodeRating.text = ratingText?.let { "Rated: $it" } - episodeRuntime.isVisible = data.runtime != null && data.runtime > 0 - episodeRuntime.text = data.runtime?.let { "$it min" } + episodeRuntime.isVisible = (data.runtime ?: 0) > 0 + episodeRuntime.text = secondsToReadable(data.runtime ?: 0, "") episodeDescript.isVisible = !data.description.isNullOrBlank() episodeDescript.text = data.description.orEmpty() @@ -456,10 +456,14 @@ class DownloadAdapter( when { isMultiDeleteState -> { setOnClickListener { - toggleIsChecked(deleteCheckbox, data.id) + deleteCheckbox?.let { + toggleIsChecked(it, data.id) + } } setOnLongClickListener { - toggleIsChecked(deleteCheckbox, data.id) + deleteCheckbox?.let { + toggleIsChecked(it, data.id) + } true } } @@ -543,14 +547,12 @@ class DownloadAdapter( notifyDataSetChanged() // This is shit, but what can you do? } - private fun toggleIsChecked(checkbox: CheckBox?, itemId: Int) { - if (checkbox == null) return + private fun toggleIsChecked(checkbox: CheckBox, itemId: Int) { val isChecked = !checkbox.isChecked checkbox.isChecked = isChecked onItemSelectionChanged.invoke(itemId, isChecked) } - class DiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame( oldItem: VisualDownloadCached, From d420c8f66b91648eb6e2825702a08ae4418a5651 Mon Sep 17 00:00:00 2001 From: Phisher98 Date: Sun, 1 Feb 2026 20:14:21 +0530 Subject: [PATCH 4/5] Minor Changes --- .../download_child_episode_large.xml | 156 ------------------ 1 file changed, 156 deletions(-) delete mode 100644 app/src/main/res/layout-v23/download_child_episode_large.xml diff --git a/app/src/main/res/layout-v23/download_child_episode_large.xml b/app/src/main/res/layout-v23/download_child_episode_large.xml deleted file mode 100644 index 9daad3ce49b..00000000000 --- a/app/src/main/res/layout-v23/download_child_episode_large.xml +++ /dev/null @@ -1,156 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - From d8fecff8c3ac0a34a84479faaac50aa16aa47f87 Mon Sep 17 00:00:00 2001 From: Phisher98 Date: Sun, 1 Feb 2026 20:23:26 +0530 Subject: [PATCH 5/5] Fix --- app/src/main/res/layout/download_child_episode_large.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/layout/download_child_episode_large.xml b/app/src/main/res/layout/download_child_episode_large.xml index 450f95d03c8..31769bd5da6 100644 --- a/app/src/main/res/layout/download_child_episode_large.xml +++ b/app/src/main/res/layout/download_child_episode_large.xml @@ -103,6 +103,13 @@ android:textColor="?attr/grayTextColor" tools:text="15 Apr 2024" /> +