Browse Source

up next rework

* start playback, when up next episode is clicked
* add playhead progress indicator to up next episodes
develop
Jannik 3 months ago
parent
commit
dd6ca8b90e
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
  1. 7
      app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt
  2. 17
      app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt
  3. 70
      app/src/main/java/org/mosad/teapod/util/adapter/MediaEpisodeListAdapter.kt
  4. 10
      app/src/main/java/org/mosad/teapod/util/adapter/MediaItemListAdapter.kt
  5. 7
      app/src/main/java/org/mosad/teapod/util/adapter/PlayerEpisodeItemAdapter.kt
  6. 4
      app/src/main/res/layout/fragment_home.xml
  7. 39
      app/src/main/res/layout/item_media.xml
  8. 1
      app/src/main/res/values/strings.xml

7
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<Item>(0, emptyList())

17
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())

70
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<ContinueWatchingItem, MediaEpisodeListAdapter.MediaViewHolder>(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<ContinueWatchingItem>() {
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)
}
}

10
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
}
}

7
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<PlayerEpisodeItemAdapter.EpisodeViewHolder>() {
@ -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)

4
app/src/main/res/layout/fragment_home.xml

@ -115,7 +115,7 @@
android:paddingBottom="7dp">
<TextView
android:id="@+id/text_new_episodes"
android:id="@+id/text_up_next"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingStart="10dp"
@ -127,7 +127,7 @@
android:textStyle="bold" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_new_episodes"
android:id="@+id/recycler_up_next"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"

39
app/src/main/res/layout/item_media.xml

@ -13,18 +13,43 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/image_poster"
<FrameLayout
android:id="@+id/frame_image_progress"
android:layout_width="0dp"
android:layout_height="0dp"
android:contentDescription="@string/media_poster_desc"
android:scaleType="centerCrop"
app:layout_constraintBottom_toTopOf="@+id/text_title"
app:layout_constraintDimensionRatio="H,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@color/md_disabled_text_dark_theme" />
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/image_poster"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/media_poster_desc"
android:scaleType="centerCrop"
tools:srcCompat="@color/imagePlaceholder" />
<ImageView
android:id="@+id/image_episode_play"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@drawable/bg_circle__black_transparent_24dp"
android:contentDescription="@string/button_play"
app:srcCompat="@drawable/ic_baseline_play_arrow_24"
app:tint="#FFFFFF" />
<com.google.android.material.progressindicator.LinearProgressIndicator
android:id="@+id/progress_playhead"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:max="100"
app:trackColor="#00FFFFFF"
app:trackThickness="2dp" />
</FrameLayout>
<TextView
android:id="@+id/text_title"
@ -37,7 +62,7 @@
android:text="@string/text_title_ex"
android:textAlignment="center"
android:textSize="15sp"
app:layout_constraintTop_toBottomOf="@+id/image_poster" />
app:layout_constraintTop_toBottomOf="@+id/frame_image_progress" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView>

1
app/src/main/res/values/strings.xml

@ -13,6 +13,7 @@
<string name="new_simulcasts">New simulcasts</string>
<string name="new_titles">New titles</string>
<string name="top_ten">Top 10</string>
<string name="season_episode_title" translatable="false">S%1$d E%2$d - %3$s</string>
<!-- search fragment -->
<string name="search_hint">Search for movies and series</string>

Loading…
Cancel
Save

Du besuchst diese Seite mit einem veralteten IPv4-Internetzugang. Möglicherweise treten in Zukunft Probleme mit der Erreichbarkeit und Performance auf. Bitte frage deinen Internetanbieter oder Netzwerkadministrator nach IPv6-Unterstützung.
You are visiting this site with an outdated IPv4 internet access. You may experience problems with accessibility and performance in the future. Please ask your ISP or network administrator for IPv6 support.
Weitere Infos | More Information
Klicke zum schließen | Click to close