From f97d07c2b8e6e3f6146595b1c35a8717fcf87585 Mon Sep 17 00:00:00 2001 From: Jannik Date: Wed, 29 Dec 2021 19:36:33 +0100 Subject: [PATCH] implement season selection in MediaFragment --- app/build.gradle | 2 +- .../teapod/parser/crunchyroll/DataTypes.kt | 10 ++-- .../main/fragments/MediaFragmentEpisodes.kt | 53 ++++++++++++++++--- .../main/viewmodel/MediaFragmentViewModel.kt | 28 +++++++--- .../teapod/util/adapter/EpisodeItemAdapter.kt | 12 ++--- .../ic_baseline_arrow_drop_down_24.xml | 5 ++ .../res/layout/fragment_media_episodes.xml | 20 +++++-- app/src/main/res/values/strings.xml | 1 + app/src/main/res/values/styles.xml | 6 +++ 9 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml diff --git a/app/build.gradle b/app/build.gradle index 8a96898..0233747 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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() 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 4ea22dd..4d8720a 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 @@ -80,23 +80,23 @@ data class Seasons( val total: Int, val items: List ) { - 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 diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentEpisodes.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentEpisodes.kt index 0d47b65..301385e 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentEpisodes.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentEpisodes.kt @@ -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") diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt index b3a26fb..112a71e 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt @@ -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() 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 diff --git a/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt b/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt index 7576d71..f90347c 100644 --- a/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt +++ b/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt @@ -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?) : RecyclerView.Adapter() { +class EpisodeItemAdapter(private val episodes: List, private val tmdbEpisodes: List?) : RecyclerView.Adapter() { 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 ) } } diff --git a/app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml b/app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml new file mode 100644 index 0000000..3dbfedb --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_arrow_drop_down_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/fragment_media_episodes.xml b/app/src/main/res/layout/fragment_media_episodes.xml index eb4485d..67ca94e 100644 --- a/app/src/main/res/layout/fragment_media_episodes.xml +++ b/app/src/main/res/layout/fragment_media_episodes.xml @@ -1,10 +1,24 @@ - + android:layout_height="match_parent" + android:orientation="vertical"> + +