diff --git a/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt b/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt index b1d39b8..3d05973 100644 --- a/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt +++ b/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt @@ -171,7 +171,7 @@ data class ContinueWatchingItem( @SerialName("fully_watched") val fullyWatched: Boolean = false, ) -// EpisodePanel is used in ContinueWatchingItem +// EpisodePanel is used in ContinueWatchingItem and UpNextSeriesItem @Serializable data class EpisodePanel( @SerialName("id") val id: String, @@ -187,13 +187,16 @@ data class EpisodePanel( @Serializable data class EpisodeMetadata( @SerialName("duration_ms") val durationMs: Int, + @SerialName("episode_number") val episodeNumber: Int? = null, // default/nullable value since optional @SerialName("season_id") val seasonId: String, + @SerialName("season_number") val seasonNumber: Int, + @SerialName("season_title") val seasonTitle: String, @SerialName("series_id") val seriesId: String, @SerialName("series_title") val seriesTitle: String, ) val NoneItem = Item("", "", "", "", "", Images(emptyList(), emptyList())) -val NoneEpisodeMetadata = EpisodeMetadata(0, "", "", "") +val NoneEpisodeMetadata = EpisodeMetadata(0, 0, "", 0, "", "", "") val NoneEpisodePanel = EpisodePanel("", "", "", "", "", NoneEpisodeMetadata, Thumbnail(listOf()), "") val NoneCollection = Collection(0, emptyList()) diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt index 4fb412e..2b55e28 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt @@ -36,7 +36,9 @@ import com.bumptech.glide.Glide import kotlinx.coroutines.launch import org.mosad.teapod.R import org.mosad.teapod.databinding.FragmentHomeBinding +import org.mosad.teapod.ui.activity.main.MainActivity import org.mosad.teapod.ui.activity.main.viewmodel.HomeViewModel +import org.mosad.teapod.util.adapter.MediaEpisodeListAdapter import org.mosad.teapod.util.adapter.MediaItemListAdapter import org.mosad.teapod.util.decoration.MediaItemDecoration import org.mosad.teapod.util.setDrawableTop @@ -57,14 +59,17 @@ class HomeFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - binding.recyclerNewEpisodes.addItemDecoration(MediaItemDecoration(9)) + binding.recyclerUpNext.addItemDecoration(MediaItemDecoration(9)) binding.recyclerWatchlist.addItemDecoration(MediaItemDecoration(9)) binding.recyclerNewTitles.addItemDecoration(MediaItemDecoration(9)) binding.recyclerTopTen.addItemDecoration(MediaItemDecoration(9)) - binding.recyclerNewEpisodes.adapter = MediaItemListAdapter( - MediaItemListAdapter.OnClickListener { - activity?.showFragment(MediaFragment(it.id)) + binding.recyclerUpNext.adapter = MediaEpisodeListAdapter( + MediaEpisodeListAdapter.OnClickListener { + val activity = activity + if (activity is MainActivity) { + activity.startPlayer(it.panel.episodeMetadata.seasonId, it.panel.id) + } } ) @@ -118,8 +123,8 @@ class HomeFragment : Fragment() { } private fun bindUiStateNormal(uiState: HomeViewModel.UiState.Normal) { - val adapterUpNext = binding.recyclerNewEpisodes.adapter as MediaItemListAdapter - adapterUpNext.submitList(uiState.upNextItems.filter { !it.fullyWatched }.toItemMediaList()) + val adapterUpNext = binding.recyclerUpNext.adapter as MediaEpisodeListAdapter + adapterUpNext.submitList(uiState.upNextItems.filter { !it.fullyWatched }) val adapterWatchlist = binding.recyclerWatchlist.adapter as MediaItemListAdapter adapterWatchlist.submitList(uiState.watchlistItems.toItemMediaList()) diff --git a/app/src/main/java/org/mosad/teapod/util/adapter/MediaEpisodeListAdapter.kt b/app/src/main/java/org/mosad/teapod/util/adapter/MediaEpisodeListAdapter.kt new file mode 100644 index 0000000..c8a6570 --- /dev/null +++ b/app/src/main/java/org/mosad/teapod/util/adapter/MediaEpisodeListAdapter.kt @@ -0,0 +1,70 @@ +package org.mosad.teapod.util.adapter + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import org.mosad.teapod.R +import org.mosad.teapod.databinding.ItemMediaBinding +import org.mosad.teapod.parser.crunchyroll.ContinueWatchingItem + +class MediaEpisodeListAdapter(private val onClickListener: OnClickListener) : ListAdapter(DiffCallback) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder { + return MediaViewHolder( + ItemMediaBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + ) + } + + override fun onBindViewHolder(holder: MediaViewHolder, position: Int) { + val item = getItem(position) + holder.binding.root.setOnClickListener { + onClickListener.onClick(item) + } + holder.bind(item) + } + + inner class MediaViewHolder(val binding: ItemMediaBinding) : + RecyclerView.ViewHolder(binding.root) { + + fun bind(item: ContinueWatchingItem) { + val metadata = item.panel.episodeMetadata + + binding.textTitle.text = binding.root.context.getString(R.string.season_episode_title, + metadata.seasonNumber, metadata.episodeNumber, metadata.seriesTitle + ) + + Glide.with(binding.imagePoster) + .load(item.panel.images.thumbnail[0][0].source) + .into(binding.imagePoster) + + // add watched progress + val playheadProgress = ((item.playhead.toFloat() / (metadata.durationMs / 1000)) * 100) + .toInt() + binding.progressPlayhead.setProgressCompat(playheadProgress, false) + binding.progressPlayhead.visibility = if (playheadProgress <= 0) + View.GONE else View.VISIBLE + } + } + + companion object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: ContinueWatchingItem, newItem: ContinueWatchingItem): Boolean { + return oldItem.panel.id == newItem.panel.id + } + + override fun areContentsTheSame(oldItem: ContinueWatchingItem, newItem: ContinueWatchingItem): Boolean { + return oldItem == newItem + } + } + + class OnClickListener(val clickListener: (item: ContinueWatchingItem) -> Unit) { + fun onClick(item: ContinueWatchingItem) = clickListener(item) + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/util/adapter/MediaItemListAdapter.kt b/app/src/main/java/org/mosad/teapod/util/adapter/MediaItemListAdapter.kt index 0d1edee..d7763c1 100644 --- a/app/src/main/java/org/mosad/teapod/util/adapter/MediaItemListAdapter.kt +++ b/app/src/main/java/org/mosad/teapod/util/adapter/MediaItemListAdapter.kt @@ -2,6 +2,7 @@ package org.mosad.teapod.util.adapter import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.ListAdapter import androidx.recyclerview.widget.RecyclerView @@ -34,8 +35,13 @@ class MediaItemListAdapter(private val onClickListener: OnClickListener) : ListA fun bind(item: ItemMedia) { binding.textTitle.text = item.title - // can we use the view instead of context here? - Glide.with(binding.root.context).load(item.posterUrl).into(binding.imagePoster) + + Glide.with(binding.imagePoster) + .load(item.posterUrl) + .into(binding.imagePoster) + + binding.imageEpisodePlay.isVisible = false + binding.progressPlayhead.isVisible = false } } diff --git a/app/src/main/java/org/mosad/teapod/util/adapter/PlayerEpisodeItemAdapter.kt b/app/src/main/java/org/mosad/teapod/util/adapter/PlayerEpisodeItemAdapter.kt index e7f8d64..8d42439 100644 --- a/app/src/main/java/org/mosad/teapod/util/adapter/PlayerEpisodeItemAdapter.kt +++ b/app/src/main/java/org/mosad/teapod/util/adapter/PlayerEpisodeItemAdapter.kt @@ -10,7 +10,6 @@ import jp.wasabeef.glide.transformations.RoundedCornersTransformation import org.mosad.teapod.R import org.mosad.teapod.databinding.ItemEpisodePlayerBinding import org.mosad.teapod.parser.crunchyroll.Episodes -import org.mosad.teapod.util.tmdb.TMDBTVEpisode class PlayerEpisodeItemAdapter(private val episodes: Episodes) : RecyclerView.Adapter() { @@ -37,11 +36,7 @@ class PlayerEpisodeItemAdapter(private val episodes: Episodes) : RecyclerView.Ad } holder.binding.textEpisodeTitle2.text = titleText - holder.binding.textEpisodeDesc2.text = if (ep.description.isNotEmpty()) { - ep.description - } else { - "" - } + holder.binding.textEpisodeDesc2.text = ep.description.ifEmpty { "" } if (ep.images.thumbnail[0][0].source.isNotEmpty()) { Glide.with(context).load(ep.images.thumbnail[0][0].source) diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 0b55cd8..f20ab33 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -115,7 +115,7 @@ android:paddingBottom="7dp"> - + app:layout_constraintTop_toTopOf="parent"> + + + + + + + + app:layout_constraintTop_toBottomOf="@+id/frame_image_progress" /> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e4fdf0a..4c33130 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,6 +13,7 @@ New simulcasts New titles Top 10 + S%1$d E%2$d - %3$s Search for movies and series