rework the player activity starting behaviour

* add callbacks on player finish to update episode watch head progress in gui
* directly start the player from the fragment and not from MainActivity
This commit is contained in:
Jannik 2022-11-26 17:46:25 +01:00
parent a95813e91e
commit f49b5a2730
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
8 changed files with 69 additions and 91 deletions

View File

@ -43,7 +43,6 @@ import org.mosad.teapod.ui.activity.main.fragments.HomeFragment
import org.mosad.teapod.ui.activity.main.fragments.LibraryFragment import org.mosad.teapod.ui.activity.main.fragments.LibraryFragment
import org.mosad.teapod.ui.activity.main.fragments.SearchFragment import org.mosad.teapod.ui.activity.main.fragments.SearchFragment
import org.mosad.teapod.ui.activity.onboarding.OnboardingActivity import org.mosad.teapod.ui.activity.onboarding.OnboardingActivity
import org.mosad.teapod.ui.activity.player.PlayerActivity
import org.mosad.teapod.util.DataTypes import org.mosad.teapod.util.DataTypes
import org.mosad.teapod.util.metadb.MetaDBController import org.mosad.teapod.util.metadb.MetaDBController
import java.util.* import java.util.*
@ -190,17 +189,6 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen
finish() finish()
} }
/**
* start the player as new activity
*/
fun startPlayer(seasonId: String, episodeId: String) {
val intent = Intent(this, PlayerActivity::class.java).apply {
putExtra(getString(R.string.intent_season_id), seasonId)
putExtra(getString(R.string.intent_episode_id), episodeId)
}
startActivity(intent)
}
/** /**
* use custom restart instead of recreate(), since it has animations * use custom restart instead of recreate(), since it has animations
*/ */

View File

@ -27,6 +27,7 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
@ -40,13 +41,10 @@ import kotlinx.coroutines.launch
import org.mosad.teapod.R import org.mosad.teapod.R
import org.mosad.teapod.databinding.FragmentHomeBinding import org.mosad.teapod.databinding.FragmentHomeBinding
import org.mosad.teapod.ui.activity.main.viewmodel.HomeViewModel import org.mosad.teapod.ui.activity.main.viewmodel.HomeViewModel
import org.mosad.teapod.util.*
import org.mosad.teapod.util.adapter.MediaEpisodeListAdapter import org.mosad.teapod.util.adapter.MediaEpisodeListAdapter
import org.mosad.teapod.util.adapter.MediaItemListAdapter import org.mosad.teapod.util.adapter.MediaItemListAdapter
import org.mosad.teapod.util.decoration.MediaItemDecoration import org.mosad.teapod.util.decoration.MediaItemDecoration
import org.mosad.teapod.util.setDrawableTop
import org.mosad.teapod.util.showFragment
import org.mosad.teapod.util.startPlayer
import org.mosad.teapod.util.toItemMediaList
class HomeFragment : Fragment() { class HomeFragment : Fragment() {
@ -54,6 +52,10 @@ class HomeFragment : Fragment() {
private val model: HomeViewModel by viewModels() private val model: HomeViewModel by viewModels()
private lateinit var binding: FragmentHomeBinding private lateinit var binding: FragmentHomeBinding
private val playerResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
model.updateUpNextItems()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentHomeBinding.inflate(inflater, container, false) binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root return binding.root
@ -70,7 +72,7 @@ class HomeFragment : Fragment() {
binding.recyclerUpNext.adapter = MediaEpisodeListAdapter( binding.recyclerUpNext.adapter = MediaEpisodeListAdapter(
MediaEpisodeListAdapter.OnClickListener { MediaEpisodeListAdapter.OnClickListener {
activity?.startPlayer(it.panel.episodeMetadata.seasonId, it.panel.id) playerResult.launch(playerIntent(it.panel.episodeMetadata.seasonId, it.panel.id))
} }
) )
@ -154,7 +156,7 @@ class HomeFragment : Fragment() {
binding.buttonPlayHighlight.setOnClickListener { binding.buttonPlayHighlight.setOnClickListener {
val panel = uiState.highlightItemUpNext.panel val panel = uiState.highlightItemUpNext.panel
activity?.startPlayer(panel.episodeMetadata.seasonId, panel.id) playerResult.launch(playerIntent(panel.episodeMetadata.seasonId, panel.id))
} }
// disable the shimmer effect // disable the shimmer effect

View File

@ -7,6 +7,7 @@ import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
@ -20,8 +21,8 @@ import kotlinx.coroutines.launch
import org.mosad.teapod.R import org.mosad.teapod.R
import org.mosad.teapod.databinding.FragmentMediaBinding import org.mosad.teapod.databinding.FragmentMediaBinding
import org.mosad.teapod.parser.crunchyroll.NoneUpNextSeriesItem import org.mosad.teapod.parser.crunchyroll.NoneUpNextSeriesItem
import org.mosad.teapod.ui.activity.main.MainActivity
import org.mosad.teapod.ui.activity.main.viewmodel.MediaFragmentViewModel import org.mosad.teapod.ui.activity.main.viewmodel.MediaFragmentViewModel
import org.mosad.teapod.util.playerIntent
import org.mosad.teapod.util.tmdb.TMDBApiController import org.mosad.teapod.util.tmdb.TMDBApiController
import org.mosad.teapod.util.tmdb.TMDBMovie import org.mosad.teapod.util.tmdb.TMDBMovie
import org.mosad.teapod.util.tmdb.TMDBTVShow import org.mosad.teapod.util.tmdb.TMDBTVShow
@ -40,7 +41,10 @@ class MediaFragment(private val mediaIdStr: String) : Fragment() {
private val fragments = arrayListOf<Fragment>() private val fragments = arrayListOf<Fragment>()
private var watchlistJobRunning = false private var watchlistJobRunning = false
private var runOnResume = false
private val playerResult = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
playerFinishedCallback()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@ -74,33 +78,6 @@ class MediaFragment(private val mediaIdStr: String) : Fragment() {
} }
} }
override fun onResume() {
super.onResume()
if (runOnResume) {
/**
* FIXME
* this is currently also run on back press when multiple MediaFragments have
* been open and closed via similar tab
*/
lifecycleScope.launch {
model.updateOnResume()
if (model.upNextSeries != NoneUpNextSeriesItem) {
binding.textTitle.text = model.upNextSeries.panel.title
}
// needs to be called after model.updateOnResume()
if (fragments.elementAtOrNull(0) is MediaFragmentEpisodes) {
(fragments[0] as MediaFragmentEpisodes).updateWatchedState()
}
}
} else {
runOnResume = true
}
}
/** /**
* if tmdb data is present, use it, else use the aod data * if tmdb data is present, use it, else use the aod data
*/ */
@ -218,15 +195,25 @@ class MediaFragment(private val mediaIdStr: String) : Fragment() {
} }
} }
/** private fun playerFinishedCallback() = lifecycleScope.launch {
* play the current episode model.updateOnResume()
* TODO this is also used in MediaFragmentEpisode, we should only have on implementation
*/
private fun playEpisode(seasonId: String, episodeId: String) {
(activity as MainActivity).startPlayer(seasonId, episodeId)
Log.d(javaClass.name, "Started Player with episodeId: $episodeId")
//model.updateNextEpisode(episodeId) // set the correct next episode if (model.upNextSeries != NoneUpNextSeriesItem) {
binding.textTitle.text = model.upNextSeries.panel.title
}
// needs to be called after model.updateOnResume()
(fragments.elementAtOrNull(0) as? MediaFragmentEpisodes)?.updateWatchedState()
Log.d(javaClass.name, "Updated model and gui after player closed")
}
/**
* play a episode, also runs callback on player result return
*/
fun playEpisode(seasonId: String, episodeId: String) {
playerResult.launch(playerIntent(seasonId, episodeId))
Log.d(javaClass.name, "Started Player with episodeId: $episodeId")
} }
/** /**

View File

@ -2,7 +2,6 @@ package org.mosad.teapod.ui.activity.main.fragments
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -13,7 +12,6 @@ import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.mosad.teapod.R import org.mosad.teapod.R
import org.mosad.teapod.databinding.FragmentMediaEpisodesBinding 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.ui.activity.main.viewmodel.MediaFragmentViewModel
import org.mosad.teapod.util.adapter.EpisodeItemAdapter import org.mosad.teapod.util.adapter.EpisodeItemAdapter
@ -37,7 +35,7 @@ class MediaFragmentEpisodes : Fragment() {
model.tmdbTVSeason.episodes, model.tmdbTVSeason.episodes,
model.currentPlayheads, model.currentPlayheads,
EpisodeItemAdapter.OnClickListener { episode -> EpisodeItemAdapter.OnClickListener { episode ->
playEpisode(episode.seasonId, episode.id) (requireParentFragment() as? MediaFragment)?.playEpisode(episode.seasonId, episode.id)
}, },
EpisodeItemAdapter.ViewType.MEDIA_FRAGMENT EpisodeItemAdapter.ViewType.MEDIA_FRAGMENT
) )
@ -106,11 +104,4 @@ class MediaFragmentEpisodes : Fragment() {
} }
} }
private fun playEpisode(seasonId: String, episodeId: String) {
(activity as MainActivity).startPlayer(seasonId, episodeId)
Log.d(javaClass.name, "Started Player with episodeId: $episodeId")
//model.updateNextEpisode(episodeId) // set the correct next episode
}
} }

View File

@ -33,6 +33,8 @@ import kotlin.random.Random
class HomeViewModel : ViewModel() { class HomeViewModel : ViewModel() {
private val WATCHLIST_LENGTH = 50
private val uiState = MutableStateFlow<UiState>(UiState.Loading) private val uiState = MutableStateFlow<UiState>(UiState.Loading)
sealed class UiState { sealed class UiState {
@ -64,7 +66,7 @@ class HomeViewModel : ViewModel() {
try { try {
// run the loading in parallel to speed up the process // run the loading in parallel to speed up the process
val upNextJob = viewModelScope.async { Crunchyroll.upNextAccount().items } val upNextJob = viewModelScope.async { Crunchyroll.upNextAccount().items }
val watchlistJob = viewModelScope.async { Crunchyroll.watchlist(50).items } val watchlistJob = viewModelScope.async { Crunchyroll.watchlist(WATCHLIST_LENGTH).items }
val recommendationsJob = viewModelScope.async { val recommendationsJob = viewModelScope.async {
Crunchyroll.recommendations(20).items Crunchyroll.recommendations(20).items
} }
@ -111,7 +113,7 @@ class HomeViewModel : ViewModel() {
} }
// update the watchlist after a item has been added/removed // update the watchlist after a item has been added/removed
val watchlistItems = Crunchyroll.watchlist(50).items val watchlistItems = Crunchyroll.watchlist(WATCHLIST_LENGTH).items
currentUiState.copy( currentUiState.copy(
watchlistItems = watchlistItems, watchlistItems = watchlistItems,
@ -123,4 +125,20 @@ class HomeViewModel : ViewModel() {
} }
} }
} /**
* Update the up next list. To be used on player result callbacks.
*/
fun updateUpNextItems() {
viewModelScope.launch {
uiState.update { currentUiState ->
if (currentUiState is UiState.Normal) {
val upNextItems = Crunchyroll.upNextAccount().items
currentUiState.copy(upNextItems = upNextItems)
} else {
currentUiState
}
}
}
}
}

View File

@ -142,14 +142,6 @@ class PlayerActivity : AppCompatActivity() {
if (isInPiPMode()) { finishAndRemoveTask() } if (isInPiPMode()) { finishAndRemoveTask() }
} }
// override fun onDestroy() {
// super.onDestroy()
//
// if (isFinishing) {
// // TODO notify about player finishing
// }
// }
/** /**
* used, when the player is in pip and the user selects a new media * used, when the player is in pip and the user selects a new media
*/ */

View File

@ -9,7 +9,6 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.commit import androidx.fragment.app.commit
import org.mosad.teapod.R import org.mosad.teapod.R
import org.mosad.teapod.ui.activity.player.PlayerActivity
import kotlin.system.exitProcess import kotlin.system.exitProcess
/** /**
@ -25,20 +24,6 @@ fun FragmentActivity.showFragment(fragment: Fragment) {
} }
} }
/**
* Start the player as new activity.
*
* @param seasonId The ID of the season the episode to be played is in
* @param episodeId The ID of the episode to play
*/
fun Activity.startPlayer(seasonId: String, episodeId: String) {
val intent = Intent(this, PlayerActivity::class.java).apply {
putExtra(getString(R.string.intent_season_id), seasonId)
putExtra(getString(R.string.intent_episode_id), episodeId)
}
startActivity(intent)
}
/** /**
* hide the status and navigation bar * hide the status and navigation bar
*/ */

View File

@ -1,16 +1,31 @@
package org.mosad.teapod.util package org.mosad.teapod.util
import android.content.Intent
import android.view.View import android.view.View
import android.view.Window import android.view.Window
import android.widget.TextView import android.widget.TextView
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
import androidx.fragment.app.Fragment
import org.mosad.teapod.R
import org.mosad.teapod.parser.crunchyroll.Collection import org.mosad.teapod.parser.crunchyroll.Collection
import org.mosad.teapod.parser.crunchyroll.ContinueWatchingItem import org.mosad.teapod.parser.crunchyroll.ContinueWatchingItem
import org.mosad.teapod.parser.crunchyroll.Item import org.mosad.teapod.parser.crunchyroll.Item
import org.mosad.teapod.ui.activity.player.PlayerActivity
import java.util.* import java.util.*
/**
* Create a Intent for PlayerActivity with season and episode id.
*
* @param seasonId The ID of the season the episode to be played is in
* @param episodeId The ID of the episode to play
*/
fun Fragment.playerIntent(seasonId: String, episodeId: String) = Intent(context, PlayerActivity::class.java).apply {
putExtra(getString(R.string.intent_season_id), seasonId)
putExtra(getString(R.string.intent_episode_id), episodeId)
}
fun TextView.setDrawableTop(drawable: Int) { fun TextView.setDrawableTop(drawable: Int) {
this.setCompoundDrawablesWithIntrinsicBounds(0, drawable, 0, 0) this.setCompoundDrawablesWithIntrinsicBounds(0, drawable, 0, 0)
} }