implement season selection in MediaFragment

This commit is contained in:
2021-12-29 19:36:33 +01:00
parent ecbbc5db7b
commit f97d07c2b8
9 changed files with 109 additions and 28 deletions

View File

@ -80,23 +80,23 @@ data class Seasons(
val total: Int,
val items: List<Season>
) {
fun getPreferredSeasonId(local: Locale): String {
fun getPreferredSeason(local: Locale): Season {
// try to get the the first seasons which matches the preferred local
items.forEach { season ->
if (season.title.startsWith("(${local.language})", true)) {
return season.id
return season
}
}
// if there is no season with the preferred local, try to find a subbed season
items.forEach { season ->
if (season.isSubbed) {
return season.id
return season
}
}
// if there is no preferred language season and no sub, use the first season
return items.first().id
return items.first()
}
}
@ -111,6 +111,8 @@ data class Season(
)
val NoneSeasons = Seasons(0, listOf())
val NoneSeason = Season("", "", "", 0, false, false)
/**
* Episodes data type

View File

@ -1,15 +1,19 @@
package org.mosad.teapod.ui.activity.main.fragments
import android.annotation.SuppressLint
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.PopupMenu
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch
import org.mosad.teapod.databinding.FragmentMediaEpisodesBinding
import org.mosad.teapod.ui.activity.main.MainActivity
import org.mosad.teapod.ui.activity.main.viewmodel.MediaFragmentViewModel
import org.mosad.teapod.databinding.FragmentMediaEpisodesBinding
import org.mosad.teapod.util.adapter.EpisodeItemAdapter
class MediaFragmentEpisodes : Fragment() {
@ -27,15 +31,17 @@ class MediaFragmentEpisodes : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapterRecEpisodes = EpisodeItemAdapter(model.episodesCrunchy, model.tmdbTVSeason?.episodes)
adapterRecEpisodes = EpisodeItemAdapter(model.currentEpisodesCrunchy, model.tmdbTVSeason?.episodes)
binding.recyclerEpisodes.adapter = adapterRecEpisodes
// set onItemClick only in adapter is initialized
if (this::adapterRecEpisodes.isInitialized) {
adapterRecEpisodes.onImageClick = { seasonId, episodeId ->
println("TODO playback episode $episodeId (season: $seasonId)")
playEpisode(seasonId, episodeId)
}
// set onItemClick, adapter is initialized
adapterRecEpisodes.onImageClick = { seasonId, episodeId ->
playEpisode(seasonId, episodeId)
}
binding.buttonSeasonSelection.text = model.currentSeasonCrunchy.title
binding.buttonSeasonSelection.setOnClickListener { v ->
showSeasonSelection(v)
}
}
@ -52,6 +58,37 @@ class MediaFragmentEpisodes : Fragment() {
}
}
private fun showSeasonSelection(v: View) {
// TODO replace with Exposed dropdown menu: https://material.io/components/menus/android#exposed-dropdown-menus
val popup = PopupMenu(requireContext(), v)
model.seasonsCrunchy.items.forEach { season ->
popup.menu.add(season.title).also {
it.setOnMenuItemClickListener {
onSeasonSelected(season.id)
false
}
}
}
popup.show()
}
/**
* Call model to load a new season.
* Once loaded update buttonSeasonSelection text and adapterRecEpisodes.
*
* Suppress waring since invalid.
*/
@SuppressLint("NotifyDataSetChanged")
private fun onSeasonSelected(seasonId: String) {
// load the new season
lifecycleScope.launch {
model.setCurrentSeason(seasonId)
binding.buttonSeasonSelection.text = model.currentSeasonCrunchy.title
adapterRecEpisodes.notifyDataSetChanged()
}
}
private fun playEpisode(seasonId: String, episodeId: String) {
(activity as MainActivity).startPlayer(seasonId, episodeId)
Log.d(javaClass.name, "Started Player with episodeId: $episodeId")

View File

@ -6,10 +6,7 @@ import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.joinAll
import kotlinx.coroutines.launch
import org.mosad.teapod.parser.crunchyroll.Crunchyroll
import org.mosad.teapod.parser.crunchyroll.NoneEpisodes
import org.mosad.teapod.parser.crunchyroll.NoneSeasons
import org.mosad.teapod.parser.crunchyroll.NoneSeries
import org.mosad.teapod.parser.crunchyroll.*
import org.mosad.teapod.preferences.Preferences
import org.mosad.teapod.util.DataTypes.MediaType
import org.mosad.teapod.util.Meta
@ -29,8 +26,11 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
internal set
var seasonsCrunchy = NoneSeasons
internal set
var currentSeasonCrunchy = NoneSeason
internal set
var episodesCrunchy = NoneEpisodes
internal set
val currentEpisodesCrunchy = arrayListOf<Episode>()
var tmdbResult: TMDBResult? = null // TODO rename
internal set
@ -55,8 +55,9 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
println("seasons: $seasonsCrunchy")
// load the preferred season (preferred language, language per season, not per stream)
val preferredSeasonId = seasonsCrunchy.getPreferredSeasonId(Preferences.preferredLocal)
episodesCrunchy = Crunchyroll.episodes(preferredSeasonId)
currentSeasonCrunchy = seasonsCrunchy.getPreferredSeason(Preferences.preferredLocal)
episodesCrunchy = Crunchyroll.episodes(currentSeasonCrunchy.id)
currentEpisodesCrunchy.addAll(episodesCrunchy.items)
println("episodes: $episodesCrunchy")
// TODO check if metaDB knows the title
@ -72,6 +73,21 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic
}
}
suspend fun setCurrentSeason(seasonId: String) {
// return if the id hasn't changed (performance)
if (currentSeasonCrunchy.id == seasonId) return
// set currentSeasonCrunchy to the new season with id == seasonId, if the id isn't found,
// don't change the current season (this should/can never happen)
currentSeasonCrunchy = seasonsCrunchy.items.firstOrNull {
it.id == seasonId
} ?: currentSeasonCrunchy
episodesCrunchy = Crunchyroll.episodes(currentSeasonCrunchy.id)
currentEpisodesCrunchy.clear()
currentEpisodesCrunchy.addAll(episodesCrunchy.items)
}
/**
* set media, tmdb and nextEpisode
* TODO run aod and tmdb load parallel

View File

@ -10,10 +10,10 @@ import com.bumptech.glide.request.RequestOptions
import jp.wasabeef.glide.transformations.RoundedCornersTransformation
import org.mosad.teapod.R
import org.mosad.teapod.databinding.ItemEpisodeBinding
import org.mosad.teapod.parser.crunchyroll.Episodes
import org.mosad.teapod.parser.crunchyroll.Episode
import org.mosad.teapod.util.tmdb.TMDBTVEpisode
class EpisodeItemAdapter(private val episodes: Episodes, private val tmdbEpisodes: List<TMDBTVEpisode>?) : RecyclerView.Adapter<EpisodeItemAdapter.EpisodeViewHolder>() {
class EpisodeItemAdapter(private val episodes: List<Episode>, private val tmdbEpisodes: List<TMDBTVEpisode>?) : RecyclerView.Adapter<EpisodeItemAdapter.EpisodeViewHolder>() {
var onImageClick: ((seasonId: String, episodeId: String) -> Unit)? = null
@ -23,7 +23,7 @@ class EpisodeItemAdapter(private val episodes: Episodes, private val tmdbEpisode
override fun onBindViewHolder(holder: EpisodeViewHolder, position: Int) {
val context = holder.binding.root.context
val ep = episodes.items[position]
val ep = episodes[position]
val titleText = if (ep.isDubbed) {
context.getString(R.string.component_episode_title, ep.episode, ep.title)
@ -61,7 +61,7 @@ class EpisodeItemAdapter(private val episodes: Episodes, private val tmdbEpisode
}
override fun getItemCount(): Int {
return episodes.items.size
return episodes.size
}
fun updateWatchedState(watched: Boolean, position: Int) {
@ -77,8 +77,8 @@ class EpisodeItemAdapter(private val episodes: Episodes, private val tmdbEpisode
// on image click return the episode id and index (within the adapter)
binding.imageEpisode.setOnClickListener {
onImageClick?.invoke(
episodes.items[bindingAdapterPosition].seasonId,
episodes.items[bindingAdapterPosition].id
episodes[bindingAdapterPosition].seasonId,
episodes[bindingAdapterPosition].id
)
}
}