cloudstream/app/src/main/java/com/lagradost/cloudstream3/ui/result/EpisodeAdapter.kt

427 lines
16 KiB
Kotlin
Raw Normal View History

2021-05-18 13:43:32 +00:00
package com.lagradost.cloudstream3.ui.result
2021-06-26 19:32:50 +00:00
import android.annotation.SuppressLint
import android.content.Context
2021-05-18 13:43:32 +00:00
import android.view.LayoutInflater
import android.view.ViewGroup
2022-06-18 00:30:39 +00:00
import androidx.core.view.isGone
2022-01-07 19:27:25 +00:00
import androidx.core.view.isVisible
import androidx.preference.PreferenceManager
2022-08-02 00:43:42 +00:00
import androidx.recyclerview.widget.DiffUtil
2021-05-18 13:43:32 +00:00
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.APIHolder.unixTimeMS
2021-07-23 23:44:54 +00:00
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.ResultEpisodeBinding
import com.lagradost.cloudstream3.databinding.ResultEpisodeLargeBinding
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.secondsToReadable
2021-07-24 20:50:57 +00:00
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
2023-07-19 15:58:40 +00:00
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_LONG_CLICK
2021-07-24 20:50:57 +00:00
import com.lagradost.cloudstream3.ui.download.DownloadClickEvent
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.PHONE
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
2022-06-29 01:20:23 +00:00
import com.lagradost.cloudstream3.utils.AppUtils.html
2021-08-19 20:05:18 +00:00
import com.lagradost.cloudstream3.utils.UIHelper.setImage
2023-07-27 19:47:42 +00:00
import com.lagradost.cloudstream3.utils.UIHelper.toPx
2021-07-24 20:50:57 +00:00
import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import java.text.DateFormat
import java.text.SimpleDateFormat
2021-07-28 19:14:45 +00:00
import java.util.*
2021-05-18 13:43:32 +00:00
2021-06-29 23:14:48 +00:00
const val ACTION_PLAY_EPISODE_IN_PLAYER = 1
2021-07-17 21:36:50 +00:00
const val ACTION_PLAY_EPISODE_IN_VLC_PLAYER = 2
2021-07-17 14:14:25 +00:00
const val ACTION_PLAY_EPISODE_IN_BROWSER = 3
const val ACTION_CHROME_CAST_EPISODE = 4
const val ACTION_CHROME_CAST_MIRROR = 5
const val ACTION_DOWNLOAD_EPISODE = 6
const val ACTION_DOWNLOAD_MIRROR = 7
const val ACTION_RELOAD_EPISODE = 8
const val ACTION_COPY_LINK = 9
const val ACTION_SHOW_OPTIONS = 10
2021-05-20 20:56:21 +00:00
2021-07-23 23:44:54 +00:00
const val ACTION_CLICK_DEFAULT = 11
const val ACTION_SHOW_TOAST = 12
2022-06-18 00:30:39 +00:00
const val ACTION_SHOW_DESCRIPTION = 15
2021-07-23 23:44:54 +00:00
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE = 13
const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14
2022-10-08 15:48:46 +00:00
const val ACTION_PLAY_EPISODE_IN_WEB_VIDEO = 16
2022-10-08 20:29:17 +00:00
const val ACTION_PLAY_EPISODE_IN_MPV = 17
2022-10-08 15:48:46 +00:00
const val ACTION_MARK_AS_WATCHED = 18
2023-07-27 19:47:42 +00:00
const val TV_EP_SIZE_LARGE = 400
const val TV_EP_SIZE_SMALL = 300
2021-05-20 20:56:21 +00:00
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
2021-05-18 13:43:32 +00:00
class EpisodeAdapter(
2021-07-24 20:50:57 +00:00
private val hasDownloadSupport: Boolean,
2021-06-10 15:15:14 +00:00
private val clickCallback: (EpisodeClickEvent) -> Unit,
2021-07-24 20:50:57 +00:00
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
/**
* @return ACTION_PLAY_EPISODE_IN_PLAYER, ACTION_PLAY_EPISODE_IN_BROWSER or ACTION_PLAY_EPISODE_IN_VLC_PLAYER depending on player settings.
* See array.xml/player_pref_values
**/
fun getPlayerAction(context: Context): Int {
2022-10-08 20:29:17 +00:00
val settingsManager = PreferenceManager.getDefaultSharedPreferences(context)
return when (settingsManager.getInt(context.getString(R.string.player_pref_key), 1)) {
1 -> ACTION_PLAY_EPISODE_IN_PLAYER
2 -> ACTION_PLAY_EPISODE_IN_VLC_PLAYER
3 -> ACTION_PLAY_EPISODE_IN_BROWSER
2022-10-08 15:48:46 +00:00
4 -> ACTION_PLAY_EPISODE_IN_WEB_VIDEO
2022-10-08 20:29:17 +00:00
5 -> ACTION_PLAY_EPISODE_IN_MPV
else -> ACTION_PLAY_EPISODE_IN_PLAYER
}
}
}
var cardList: MutableList<ResultEpisode> = mutableListOf()
2021-07-28 19:14:45 +00:00
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
if (holder.itemView.hasFocus()) {
2022-08-06 18:36:45 +00:00
holder.itemView.clearFocus()
}
2021-07-28 19:14:45 +00:00
}
2022-08-02 00:43:42 +00:00
fun updateList(newList: List<ResultEpisode>) {
val diffResult = DiffUtil.calculateDiff(
ResultDiffCallback(this.cardList, newList)
)
cardList.clear()
cardList.addAll(newList)
diffResult.dispatchUpdatesTo(this)
}
2023-07-19 15:58:40 +00:00
private fun getItem(position: Int): ResultEpisode {
return cardList[position]
}
override fun getItemViewType(position: Int): Int {
val item = getItem(position)
return if (item.poster.isNullOrBlank() && item.description.isNullOrBlank()) 0 else 1
}
2023-07-19 15:58:40 +00:00
// private val layout = R.layout.result_episode_both
2022-08-05 23:41:35 +00:00
2021-05-18 13:43:32 +00:00
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
2021-06-26 19:32:50 +00:00
/*val layout = if (cardList.filter { it.poster != null }.size >= cardList.size / 2)
R.layout.result_episode_large
else R.layout.result_episode*/
2023-07-19 15:58:40 +00:00
return when (viewType) {
0 -> {
EpisodeCardViewHolderSmall(
2023-07-19 15:58:40 +00:00
ResultEpisodeBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
hasDownloadSupport,
clickCallback,
downloadClickCallback
)
}
2023-07-19 15:58:40 +00:00
1 -> {
EpisodeCardViewHolderLarge(
2023-07-19 15:58:40 +00:00
ResultEpisodeLargeBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
),
hasDownloadSupport,
clickCallback,
downloadClickCallback
)
}
2023-07-19 15:58:40 +00:00
else -> throw NotImplementedError()
}
2021-05-18 13:43:32 +00:00
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is EpisodeCardViewHolderLarge -> {
holder.bind(getItem(position))
}
2023-07-19 15:58:40 +00:00
is EpisodeCardViewHolderSmall -> {
holder.bind(getItem(position))
2021-05-18 13:43:32 +00:00
}
}
}
override fun getItemCount(): Int {
return cardList.size
}
class EpisodeCardViewHolderLarge
2021-05-20 20:56:21 +00:00
constructor(
2023-07-19 15:58:40 +00:00
val binding: ResultEpisodeLargeBinding,
2021-07-30 23:41:54 +00:00
private val hasDownloadSupport: Boolean,
2021-06-06 18:06:01 +00:00
private val clickCallback: (EpisodeClickEvent) -> Unit,
2021-07-24 20:50:57 +00:00
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
2023-07-15 21:43:09 +00:00
) : RecyclerView.ViewHolder(binding.root) {
2021-07-28 19:14:45 +00:00
var localCard: ResultEpisode? = null
2021-06-26 19:32:50 +00:00
@SuppressLint("SetTextI18n")
2021-05-18 13:43:32 +00:00
fun bind(card: ResultEpisode) {
2021-07-28 19:14:45 +00:00
localCard = card
2023-07-19 15:58:40 +00:00
val setWidth =
if (isLayout(TV or EMULATOR)) TV_EP_SIZE_LARGE.toPx else ViewGroup.LayoutParams.MATCH_PARENT
2023-07-18 20:35:17 +00:00
binding.episodeLinHolder.layoutParams.width = setWidth
binding.episodeHolderLarge.layoutParams.width = setWidth
binding.episodeHolder.layoutParams.width = setWidth
2021-06-26 19:32:50 +00:00
binding.apply {
2023-07-15 21:43:09 +00:00
downloadButton.isVisible = hasDownloadSupport
2023-07-19 15:58:40 +00:00
downloadButton.setDefaultClickListener(
VideoDownloadHelper.DownloadEpisodeCached(
card.name,
card.poster,
card.episode,
card.season,
card.id,
card.parentId,
card.rating,
card.description,
System.currentTimeMillis(),
), null
) {
when (it.action) {
DOWNLOAD_ACTION_DOWNLOAD -> {
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
}
DOWNLOAD_ACTION_LONG_CLICK -> {
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_MIRROR, card))
}
else -> {
downloadClickCallback.invoke(it)
}
2023-07-15 21:43:09 +00:00
}
}
val name =
if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
episodeFiller.isVisible = card.isFiller == true
episodeText.text =
name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
episodeText.isSelected = true // is needed for text repeating
if (card.videoWatchState == VideoWatchState.Watched) {
// This cannot be done in getDisplayPosition() as when you have not watched something
// the duration and position is 0
episodeProgress.max = 1
episodeProgress.progress = 1
episodeProgress.isVisible = true
} else {
val displayPos = card.getDisplayPosition()
episodeProgress.max = (card.duration / 1000).toInt()
episodeProgress.progress = (displayPos / 1000).toInt()
episodeProgress.isVisible = displayPos > 0L
}
2021-06-26 19:32:50 +00:00
episodePoster.isVisible = episodePoster.setImage(card.poster) == true
2022-06-18 00:30:39 +00:00
if (card.rating != null) {
episodeRating.text = episodeRating.context?.getString(R.string.rated_format)
?.format(card.rating.toFloat() / 10f)
} else {
episodeRating.text = ""
2022-06-18 00:30:39 +00:00
}
2021-05-18 13:43:32 +00:00
episodeRating.isGone = episodeRating.text.isNullOrBlank()
episodeDescript.apply {
text = card.description.html()
isGone = text.isNullOrBlank()
var isExpanded = false
setOnClickListener {
if (isLayout(TV)) {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_DESCRIPTION, card))
} else {
isExpanded = !isExpanded
maxLines = if (isExpanded) {
Integer.MAX_VALUE
} else 4
}
}
2022-08-05 23:41:35 +00:00
}
2021-08-04 01:50:24 +00:00
if (card.airDate != null) {
val isUpcoming = unixTimeMS < card.airDate
if (isUpcoming) {
episodePlayIcon.isVisible = false
episodeUpcomingIcon.isVisible = !episodePoster.isVisible
episodeDate.setText(
txt(
R.string.episode_upcoming_format,
secondsToReadable(card.airDate.minus(unixTimeMS).div(1000).toInt(), "")
)
)
} else {
episodeUpcomingIcon.isVisible = false
val formattedAirDate = SimpleDateFormat.getDateInstance(
DateFormat.LONG,
Locale.getDefault()
).apply {
}.format(Date(card.airDate))
episodeDate.setText(txt(formattedAirDate))
}
} else {
episodeDate.isVisible = false
}
if (isLayout(EMULATOR or PHONE)) {
episodePoster.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
episodePoster.setOnLongClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_TOAST, card))
return@setOnLongClickListener true
}
2022-08-05 23:41:35 +00:00
}
2021-08-04 01:50:24 +00:00
}
itemView.setOnClickListener {
2021-07-23 23:44:54 +00:00
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
2021-07-17 14:14:25 +00:00
}
if (isLayout(TV)) {
itemView.isFocusable = true
itemView.isFocusableInTouchMode = true
//itemView.touchscreenBlocksFocus = false
2021-11-25 17:26:14 +00:00
}
itemView.setOnLongClickListener {
2021-07-17 14:14:25 +00:00
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
return@setOnLongClickListener true
2021-07-15 16:45:25 +00:00
}
2023-07-15 21:43:09 +00:00
//binding.resultEpisodeDownload.isVisible = hasDownloadSupport
//binding.resultEpisodeProgressDownloaded.isVisible = hasDownloadSupport
}
}
class EpisodeCardViewHolderSmall
constructor(
2023-07-19 15:58:40 +00:00
val binding: ResultEpisodeBinding,
private val hasDownloadSupport: Boolean,
private val clickCallback: (EpisodeClickEvent) -> Unit,
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
2023-07-15 21:43:09 +00:00
) : RecyclerView.ViewHolder(binding.root) {
@SuppressLint("SetTextI18n")
fun bind(card: ResultEpisode) {
binding.episodeHolder.layoutParams.apply {
2023-07-19 15:58:40 +00:00
width =
if (isLayout(TV or EMULATOR)) TV_EP_SIZE_SMALL.toPx else ViewGroup.LayoutParams.MATCH_PARENT
}
binding.apply {
2023-07-15 21:43:09 +00:00
downloadButton.isVisible = hasDownloadSupport
2023-07-19 15:58:40 +00:00
downloadButton.setDefaultClickListener(
VideoDownloadHelper.DownloadEpisodeCached(
card.name,
card.poster,
card.episode,
card.season,
card.id,
card.parentId,
card.rating,
card.description,
System.currentTimeMillis(),
), null
) {
when (it.action) {
DOWNLOAD_ACTION_DOWNLOAD -> {
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, card))
}
DOWNLOAD_ACTION_LONG_CLICK -> {
clickCallback.invoke(EpisodeClickEvent(ACTION_DOWNLOAD_MIRROR, card))
}
else -> {
downloadClickCallback.invoke(it)
}
2023-07-15 21:43:09 +00:00
}
}
val name =
if (card.name == null) "${episodeText.context.getString(R.string.episode)} ${card.episode}" else "${card.episode}. ${card.name}"
episodeFiller.isVisible = card.isFiller == true
episodeText.text =
name//if(card.isFiller == true) episodeText.context.getString(R.string.filler).format(name) else name
episodeText.isSelected = true // is needed for text repeating
if (card.videoWatchState == VideoWatchState.Watched) {
// This cannot be done in getDisplayPosition() as when you have not watched something
// the duration and position is 0
episodeProgress.max = 1
episodeProgress.progress = 1
episodeProgress.isVisible = true
} else {
val displayPos = card.getDisplayPosition()
episodeProgress.max = (card.duration / 1000).toInt()
episodeProgress.progress = (displayPos / 1000).toInt()
episodeProgress.isVisible = displayPos > 0L
}
itemView.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
if (isLayout(TV)) {
itemView.isFocusable = true
itemView.isFocusableInTouchMode = true
//itemView.touchscreenBlocksFocus = false
}
itemView.setOnLongClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_OPTIONS, card))
return@setOnLongClickListener true
}
2023-07-15 21:43:09 +00:00
//binding.resultEpisodeDownload.isVisible = hasDownloadSupport
//binding.resultEpisodeProgressDownloaded.isVisible = hasDownloadSupport
2021-05-18 13:43:32 +00:00
}
}
}
}
2022-08-02 00:43:42 +00:00
class ResultDiffCallback(
private val oldList: List<ResultEpisode>,
private val newList: List<ResultEpisode>
) :
DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].id == newList[newItemPosition].id
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition] == newList[newItemPosition]
}