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..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 @@ -12,7 +12,9 @@ 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.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 +22,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 +85,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 +376,133 @@ 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 ?: 0) > 0 + episodeRuntime.text = secondsToReadable(data.runtime ?: 0, "") + + 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)) + } + + 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 { + deleteCheckbox?.let { + toggleIsChecked(it, data.id) + } + } + setOnLongClickListener { + deleteCheckbox?.let { + toggleIsChecked(it, 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 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/download_child_episode_large.xml b/app/src/main/res/layout/download_child_episode_large.xml new file mode 100644 index 00000000000..31769bd5da6 --- /dev/null +++ b/app/src/main/res/layout/download_child_episode_large.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +