* Refactor playback code * Fix back state when playing media and rotating device Problem was playerItems were re-emitted on fragment creation after config change. LiveData by design emit on every subscribe (observe) so to avoid that there are several possibilities. 1) easiest, observe playerItems not in onCreate but in playButton.clickListener. Stupid, since then we need to remember to only observe in this special place. 2) SingleLiveData - kind of hacky since LiveData were designed to behave this way so we don't want to go against their design. 3) Use Kotlin flow instead. I chose the flow approach since it's Kotlin native and modern way to do things and behaves much more Rx-like. Since now we need to call collect instead of observe and launch in coroutine, I added utility method to make this easier. Also, in the future we might want to improve this further, either by coming up with new way entirely or by at least moving this to parent fragment from which all fragments that want to play media will inherit and thus making it easy to use and maintain. Co-authored-by: Jarne Demeulemeester <32322857+jarnedemeulemeester@users.noreply.github.com>
145 lines
No EOL
5.1 KiB
Kotlin
145 lines
No EOL
5.1 KiB
Kotlin
package dev.jdtech.jellyfin.fragments
|
|
|
|
import android.os.Bundle
|
|
import android.util.TypedValue
|
|
import android.view.LayoutInflater
|
|
import android.view.View
|
|
import android.view.ViewGroup
|
|
import androidx.core.content.ContextCompat
|
|
import androidx.core.view.isVisible
|
|
import androidx.fragment.app.viewModels
|
|
import androidx.lifecycle.lifecycleScope
|
|
import androidx.navigation.fragment.findNavController
|
|
import androidx.navigation.fragment.navArgs
|
|
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
|
import dagger.hilt.android.AndroidEntryPoint
|
|
import dev.jdtech.jellyfin.R
|
|
import dev.jdtech.jellyfin.databinding.EpisodeBottomSheetBinding
|
|
import dev.jdtech.jellyfin.dialogs.ErrorDialogFragment
|
|
import dev.jdtech.jellyfin.models.PlayerItem
|
|
import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel
|
|
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
|
|
import timber.log.Timber
|
|
|
|
@AndroidEntryPoint
|
|
class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
|
|
private val args: EpisodeBottomSheetFragmentArgs by navArgs()
|
|
|
|
private lateinit var binding: EpisodeBottomSheetBinding
|
|
private val viewModel: EpisodeBottomSheetViewModel by viewModels()
|
|
private val playerViewModel: PlayerViewModel by viewModels()
|
|
|
|
override fun onCreateView(
|
|
inflater: LayoutInflater,
|
|
container: ViewGroup?,
|
|
savedInstanceState: Bundle?
|
|
): View {
|
|
binding = EpisodeBottomSheetBinding.inflate(inflater, container, false)
|
|
|
|
binding.lifecycleOwner = viewLifecycleOwner
|
|
binding.viewModel = viewModel
|
|
|
|
binding.playButton.setOnClickListener {
|
|
binding.playButton.setImageResource(android.R.color.transparent)
|
|
binding.progressCircular.visibility = View.VISIBLE
|
|
viewModel.item.value?.let {
|
|
playerViewModel.loadPlayerItems(it)
|
|
}
|
|
}
|
|
|
|
playerViewModel.onPlaybackRequested(lifecycleScope) { playerItems ->
|
|
when (playerItems) {
|
|
is PlayerViewModel.PlayerItemError -> bindPlayerItemsError(playerItems)
|
|
is PlayerViewModel.PlayerItems -> bindPlayerItems(playerItems)
|
|
}
|
|
}
|
|
|
|
binding.checkButton.setOnClickListener {
|
|
when (viewModel.played.value) {
|
|
true -> viewModel.markAsUnplayed(args.episodeId)
|
|
false -> viewModel.markAsPlayed(args.episodeId)
|
|
}
|
|
}
|
|
|
|
binding.favoriteButton.setOnClickListener {
|
|
when (viewModel.favorite.value) {
|
|
true -> viewModel.unmarkAsFavorite(args.episodeId)
|
|
false -> viewModel.markAsFavorite(args.episodeId)
|
|
}
|
|
}
|
|
|
|
viewModel.item.observe(viewLifecycleOwner, { episode ->
|
|
if (episode.userData?.playedPercentage != null) {
|
|
binding.progressBar.layoutParams.width = TypedValue.applyDimension(
|
|
TypedValue.COMPLEX_UNIT_DIP,
|
|
(episode.userData?.playedPercentage?.times(1.26))!!.toFloat(),
|
|
context?.resources?.displayMetrics
|
|
).toInt()
|
|
binding.progressBar.visibility = View.VISIBLE
|
|
}
|
|
binding.communityRating.visibility = when (episode.communityRating != null) {
|
|
false -> View.GONE
|
|
true -> View.VISIBLE
|
|
}
|
|
})
|
|
|
|
viewModel.played.observe(viewLifecycleOwner, {
|
|
val drawable = when (it) {
|
|
true -> R.drawable.ic_check_filled
|
|
false -> R.drawable.ic_check
|
|
}
|
|
|
|
binding.checkButton.setImageResource(drawable)
|
|
})
|
|
|
|
viewModel.favorite.observe(viewLifecycleOwner, {
|
|
val drawable = when (it) {
|
|
true -> R.drawable.ic_heart_filled
|
|
false -> R.drawable.ic_heart
|
|
}
|
|
|
|
binding.favoriteButton.setImageResource(drawable)
|
|
})
|
|
|
|
viewModel.loadEpisode(args.episodeId)
|
|
|
|
return binding.root
|
|
}
|
|
|
|
private fun bindPlayerItems(items: PlayerViewModel.PlayerItems) {
|
|
navigateToPlayerActivity(items.items.toTypedArray())
|
|
binding.playButton.setImageDrawable(
|
|
ContextCompat.getDrawable(
|
|
requireActivity(),
|
|
R.drawable.ic_play
|
|
)
|
|
)
|
|
binding.progressCircular.visibility = View.INVISIBLE
|
|
}
|
|
|
|
private fun bindPlayerItemsError(error: PlayerViewModel.PlayerItemError) {
|
|
Timber.e(error.message)
|
|
|
|
binding.playerItemsError.isVisible = true
|
|
binding.playButton.setImageDrawable(
|
|
ContextCompat.getDrawable(
|
|
requireActivity(),
|
|
R.drawable.ic_play
|
|
)
|
|
)
|
|
binding.progressCircular.visibility = View.INVISIBLE
|
|
binding.playerItemsErrorDetails.setOnClickListener {
|
|
ErrorDialogFragment(error.message).show(parentFragmentManager, "errordialog")
|
|
}
|
|
}
|
|
|
|
private fun navigateToPlayerActivity(
|
|
playerItems: Array<PlayerItem>,
|
|
) {
|
|
findNavController().navigate(
|
|
EpisodeBottomSheetFragmentDirections.actionEpisodeBottomSheetFragmentToPlayerActivity(
|
|
playerItems,
|
|
)
|
|
)
|
|
}
|
|
} |