implement season selection in MediaFragment

This commit is contained in:
Jannik 2021-12-29 19:36:33 +01:00
parent ecbbc5db7b
commit f97d07c2b8
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
9 changed files with 109 additions and 28 deletions

View File

@ -14,7 +14,7 @@ android {
minSdkVersion 23
targetSdkVersion 30
versionCode 4200 //00.04.200
versionName "1.0.0-alpha1"
versionName "1.0.0-alpha2"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime()

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

View File

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M7,10l5,5 5,-5z"/>
</vector>

View File

@ -1,10 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
android:layout_height="match_parent"
android:orientation="vertical">
<Button
android:id="@+id/button_season_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="7dp"
android:layout_marginTop="6dp"
android:layout_marginEnd="7dp"
android:layout_marginBottom="6dp"
android:singleLine="true"
android:text="@string/text_title_ex"
app:icon="@drawable/ic_baseline_arrow_drop_down_24"
app:iconGravity="end" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_episodes"
@ -16,4 +30,4 @@
tools:layout_editor_absoluteY="298dp"
tools:listitem="@layout/item_episode" />
</FrameLayout>
</LinearLayout>

View File

@ -133,4 +133,5 @@
<string name="intent_media_id" translatable="false">intent_media_id</string>
<string name="intent_season_id" translatable="false">intent_season_id</string>
<string name="intent_episode_id" translatable="false">intent_episode_id</string>
</resources>

View File

@ -4,6 +4,7 @@
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
<item name="popupMenuStyle">@style/Widget.App.PopupMenu</item>
</style>
<style name="AppTheme.Light" parent="AppTheme">
@ -65,4 +66,9 @@
<item name="cornerSize">5dp</item>
</style>
<!-- popup menus -->
<style name="Widget.App.PopupMenu" parent="Widget.MaterialComponents.PopupMenu">
<item name="android:popupBackground">?themeSecondary</item>
</style>
</resources>