Add similar titles to media fragment (#28)
* update androidx navigation libraries * add similar media to MediaFragment * parse similar media in AoDParser Reviewed-on: #28 Co-Authored-By: Jannik <seil0@mosad.xyz> Co-Committed-By: Jannik <seil0@mosad.xyz>
This commit is contained in:
		@ -41,7 +41,7 @@ object AoDParser {
 | 
			
		||||
    private const val loginPath = "/users/sign_in"
 | 
			
		||||
    private const val libraryPath = "/animes"
 | 
			
		||||
 | 
			
		||||
    private const val userAgent = "Mozilla/5.0 (X11; Linux x86_64; rv:80.0) Gecko/20100101 Firefox/80.0"
 | 
			
		||||
    private const val userAgent = "Mozilla/5.0 (X11; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0"
 | 
			
		||||
 | 
			
		||||
    private var sessionCookies = mutableMapOf<String, String>()
 | 
			
		||||
    private var csrfToken: String = ""
 | 
			
		||||
@ -323,7 +323,7 @@ object AoDParser {
 | 
			
		||||
        }
 | 
			
		||||
        Log.i(javaClass.name, "Loaded playlists successfully")
 | 
			
		||||
 | 
			
		||||
        // parse additional info from the media page
 | 
			
		||||
        // additional info from the media page
 | 
			
		||||
        res.select("table.vertical-table").select("tr").forEach { row ->
 | 
			
		||||
            when (row.select("th").text().toLowerCase(Locale.ROOT)) {
 | 
			
		||||
                "produktionsjahr" -> media.info.year = row.select("td").text().toInt()
 | 
			
		||||
@ -337,7 +337,21 @@ object AoDParser {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // parse additional information for tv shows the episode title (description) is loaded from the "api"
 | 
			
		||||
        // similar titles from media page
 | 
			
		||||
        media.info.similar = res.select("h2:contains(Ähnliche Animes)").next().select("li").mapNotNull {
 | 
			
		||||
            val mediaId = it.select("a.thumbs").attr("href")
 | 
			
		||||
                .substringAfterLast("/").toIntOrNull()
 | 
			
		||||
            val mediaImage = it.select("a.thumbs > img").attr("src")
 | 
			
		||||
            val mediaTitle = it.select("a").text()
 | 
			
		||||
 | 
			
		||||
            if (mediaId != null) {
 | 
			
		||||
                ItemMedia(mediaId, mediaTitle, mediaImage)
 | 
			
		||||
            } else {
 | 
			
		||||
                null
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // additional information for tv shows the episode title (description) is loaded from the "api"
 | 
			
		||||
        if (media.type == MediaType.TVSHOW) {
 | 
			
		||||
            res.select("div.three-box-container > div.episodebox").forEach { episodebox ->
 | 
			
		||||
                // make sure the episode has a streaming link
 | 
			
		||||
 | 
			
		||||
@ -1,9 +1,9 @@
 | 
			
		||||
package org.mosad.teapod.activity
 | 
			
		||||
package org.mosad.teapod.ui.activity
 | 
			
		||||
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import androidx.appcompat.app.AppCompatActivity
 | 
			
		||||
import org.mosad.teapod.activity.main.MainActivity
 | 
			
		||||
import org.mosad.teapod.ui.activity.main.MainActivity
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class SplashActivity : AppCompatActivity() {
 | 
			
		||||
@ -20,7 +20,7 @@
 | 
			
		||||
 *
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
package org.mosad.teapod.activity.main
 | 
			
		||||
package org.mosad.teapod.ui.activity.main
 | 
			
		||||
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
@ -37,20 +37,19 @@ import kotlinx.coroutines.runBlocking
 | 
			
		||||
import org.mosad.teapod.R
 | 
			
		||||
import org.mosad.teapod.databinding.ActivityMainBinding
 | 
			
		||||
import org.mosad.teapod.parser.AoDParser
 | 
			
		||||
import org.mosad.teapod.activity.player.PlayerActivity
 | 
			
		||||
import org.mosad.teapod.ui.activity.player.PlayerActivity
 | 
			
		||||
import org.mosad.teapod.preferences.EncryptedPreferences
 | 
			
		||||
import org.mosad.teapod.preferences.Preferences
 | 
			
		||||
import org.mosad.teapod.ui.components.LoginDialog
 | 
			
		||||
import org.mosad.teapod.activity.main.fragments.AccountFragment
 | 
			
		||||
import org.mosad.teapod.activity.main.fragments.HomeFragment
 | 
			
		||||
import org.mosad.teapod.activity.main.fragments.LibraryFragment
 | 
			
		||||
import org.mosad.teapod.activity.main.fragments.SearchFragment
 | 
			
		||||
import org.mosad.teapod.activity.onboarding.OnboardingActivity
 | 
			
		||||
import org.mosad.teapod.ui.activity.main.fragments.AccountFragment
 | 
			
		||||
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.SearchFragment
 | 
			
		||||
import org.mosad.teapod.ui.activity.onboarding.OnboardingActivity
 | 
			
		||||
import org.mosad.teapod.util.DataTypes
 | 
			
		||||
import org.mosad.teapod.util.StorageController
 | 
			
		||||
import org.mosad.teapod.util.exitAndRemoveTask
 | 
			
		||||
import java.net.SocketTimeoutException
 | 
			
		||||
import kotlin.system.exitProcess
 | 
			
		||||
import kotlin.system.measureTimeMillis
 | 
			
		||||
 | 
			
		||||
class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemSelectedListener {
 | 
			
		||||
@ -60,6 +59,11 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
 | 
			
		||||
 | 
			
		||||
    companion object {
 | 
			
		||||
        var wasInitialized = false
 | 
			
		||||
        lateinit var instance: MainActivity
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        instance = this
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package org.mosad.teapod.activity.main.fragments
 | 
			
		||||
package org.mosad.teapod.ui.activity.main.fragments
 | 
			
		||||
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package org.mosad.teapod.activity.main.fragments
 | 
			
		||||
package org.mosad.teapod.ui.activity.main.fragments
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.util.Log
 | 
			
		||||
@ -9,7 +9,7 @@ import androidx.fragment.app.Fragment
 | 
			
		||||
import com.afollestad.materialdialogs.MaterialDialog
 | 
			
		||||
import com.afollestad.materialdialogs.list.listItemsSingleChoice
 | 
			
		||||
import org.mosad.teapod.BuildConfig
 | 
			
		||||
import org.mosad.teapod.activity.main.MainActivity
 | 
			
		||||
import org.mosad.teapod.ui.activity.main.MainActivity
 | 
			
		||||
import org.mosad.teapod.R
 | 
			
		||||
import org.mosad.teapod.databinding.FragmentAccountBinding
 | 
			
		||||
import org.mosad.teapod.parser.AoDParser
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package org.mosad.teapod.activity.main.fragments
 | 
			
		||||
package org.mosad.teapod.ui.activity.main.fragments
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.util.Log
 | 
			
		||||
@ -11,7 +11,7 @@ import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.GlobalScope
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import org.mosad.teapod.R
 | 
			
		||||
import org.mosad.teapod.activity.main.MainActivity
 | 
			
		||||
import org.mosad.teapod.ui.activity.main.MainActivity
 | 
			
		||||
import org.mosad.teapod.databinding.FragmentHomeBinding
 | 
			
		||||
import org.mosad.teapod.parser.AoDParser
 | 
			
		||||
import org.mosad.teapod.util.ItemMedia
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package org.mosad.teapod.activity.main.fragments
 | 
			
		||||
package org.mosad.teapod.ui.activity.main.fragments
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
@ -9,7 +9,6 @@ import kotlinx.coroutines.Dispatchers
 | 
			
		||||
import kotlinx.coroutines.GlobalScope
 | 
			
		||||
import kotlinx.coroutines.launch
 | 
			
		||||
import kotlinx.coroutines.withContext
 | 
			
		||||
import org.mosad.teapod.activity.main.MainActivity
 | 
			
		||||
import org.mosad.teapod.databinding.FragmentLibraryBinding
 | 
			
		||||
import org.mosad.teapod.parser.AoDParser
 | 
			
		||||
import org.mosad.teapod.util.adapter.MediaItemAdapter
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package org.mosad.teapod.activity.main.fragments
 | 
			
		||||
package org.mosad.teapod.ui.activity.main.fragments
 | 
			
		||||
 | 
			
		||||
import android.graphics.Color
 | 
			
		||||
import android.graphics.drawable.ColorDrawable
 | 
			
		||||
@ -8,26 +8,34 @@ import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.fragment.app.Fragment
 | 
			
		||||
import androidx.fragment.app.FragmentActivity
 | 
			
		||||
import androidx.fragment.app.activityViewModels
 | 
			
		||||
import androidx.viewpager2.adapter.FragmentStateAdapter
 | 
			
		||||
import com.bumptech.glide.Glide
 | 
			
		||||
import com.bumptech.glide.request.RequestOptions
 | 
			
		||||
import com.google.android.material.tabs.TabLayoutMediator
 | 
			
		||||
import jp.wasabeef.glide.transformations.BlurTransformation
 | 
			
		||||
import kotlinx.coroutines.*
 | 
			
		||||
import org.mosad.teapod.R
 | 
			
		||||
import org.mosad.teapod.activity.main.MainActivity
 | 
			
		||||
import org.mosad.teapod.ui.activity.main.MainActivity
 | 
			
		||||
import org.mosad.teapod.ui.activity.main.viewmodel.MediaFragmentViewModel
 | 
			
		||||
import org.mosad.teapod.databinding.FragmentMediaBinding
 | 
			
		||||
import org.mosad.teapod.parser.AoDParser
 | 
			
		||||
import org.mosad.teapod.util.*
 | 
			
		||||
import org.mosad.teapod.util.DataTypes.MediaType
 | 
			
		||||
import org.mosad.teapod.util.adapter.EpisodeItemAdapter
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * The media detail fragment.
 | 
			
		||||
 * Note: the fragment is created only once, when selecting a similar title etc.
 | 
			
		||||
 * therefore fragments may be not empty and model may be the old one
 | 
			
		||||
 */
 | 
			
		||||
class MediaFragment(private val mediaId: Int) : Fragment() {
 | 
			
		||||
 | 
			
		||||
    private lateinit var binding: FragmentMediaBinding
 | 
			
		||||
    private lateinit var adapterRecEpisodes: EpisodeItemAdapter
 | 
			
		||||
    private lateinit var pagerAdapter: FragmentStateAdapter
 | 
			
		||||
 | 
			
		||||
    private lateinit var media: Media
 | 
			
		||||
    private lateinit var tmdb: TMDBResponse
 | 
			
		||||
    private lateinit var nextEpisode: Episode
 | 
			
		||||
    private val fragments = arrayListOf<Fragment>()
 | 
			
		||||
 | 
			
		||||
    private val model: MediaFragmentViewModel by activityViewModels()
 | 
			
		||||
 | 
			
		||||
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
 | 
			
		||||
        binding = FragmentMediaBinding.inflate(inflater, container, false)
 | 
			
		||||
@ -38,10 +46,19 @@ class MediaFragment(private val mediaId: Int) : Fragment() {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
        binding.frameLoading.visibility = View.VISIBLE
 | 
			
		||||
 | 
			
		||||
        // tab layout and pager TODO
 | 
			
		||||
        pagerAdapter = ScreenSlidePagerAdapter(requireActivity())
 | 
			
		||||
        binding.pagerEpisodesSimilar.adapter = pagerAdapter
 | 
			
		||||
        TabLayoutMediator(binding.tabEpisodesSimilar, binding.pagerEpisodesSimilar) { tab, position ->
 | 
			
		||||
            tab.text = if (model.media.type == MediaType.TVSHOW && position == 0) {
 | 
			
		||||
                getString(R.string.episodes)
 | 
			
		||||
            } else {
 | 
			
		||||
                getString(R.string.similar_titles)
 | 
			
		||||
            }
 | 
			
		||||
        }.attach()
 | 
			
		||||
 | 
			
		||||
        GlobalScope.launch(Dispatchers.Main) {
 | 
			
		||||
            // load the streams for the selected media
 | 
			
		||||
            media = AoDParser.getMediaById(mediaId)
 | 
			
		||||
            tmdb = TMDBApiController().search(media.info.title, media.type)
 | 
			
		||||
            model.load(mediaId) // load the streams and tmdb for the selected media
 | 
			
		||||
 | 
			
		||||
            if (this@MediaFragment.isAdded) {
 | 
			
		||||
                updateGUI()
 | 
			
		||||
@ -53,20 +70,14 @@ class MediaFragment(private val mediaId: Int) : Fragment() {
 | 
			
		||||
    override fun onResume() {
 | 
			
		||||
        super.onResume()
 | 
			
		||||
 | 
			
		||||
        // only notify adapter, if initialized
 | 
			
		||||
        if (this::adapterRecEpisodes.isInitialized) {
 | 
			
		||||
            // TODO find a better solution for this
 | 
			
		||||
            media.episodes.forEachIndexed { index, episode ->
 | 
			
		||||
                adapterRecEpisodes.updateWatchedState(episode.watched, index)
 | 
			
		||||
            }
 | 
			
		||||
            adapterRecEpisodes.notifyDataSetChanged()
 | 
			
		||||
        }
 | 
			
		||||
        // update the next ep text, since it may have changed
 | 
			
		||||
        binding.textTitle.text = model.nextEpisode.title
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * if tmdb data is present, use it, else use the aod data
 | 
			
		||||
     */
 | 
			
		||||
    private fun updateGUI() = with(binding) {
 | 
			
		||||
    private fun updateGUI() = with(model) {
 | 
			
		||||
        // generic gui
 | 
			
		||||
        val backdropUrl = if (tmdb.backdropUrl.isNotEmpty()) tmdb.backdropUrl else media.info.posterUrl
 | 
			
		||||
        val posterUrl = if (tmdb.posterUrl.isNotEmpty()) tmdb.posterUrl else media.info.posterUrl
 | 
			
		||||
@ -74,33 +85,27 @@ class MediaFragment(private val mediaId: Int) : Fragment() {
 | 
			
		||||
        Glide.with(requireContext()).load(backdropUrl)
 | 
			
		||||
            .apply(RequestOptions.placeholderOf(ColorDrawable(Color.DKGRAY)))
 | 
			
		||||
            .apply(RequestOptions.bitmapTransform(BlurTransformation(20, 3)))
 | 
			
		||||
            .into(imageBackdrop)
 | 
			
		||||
            .into(binding.imageBackdrop)
 | 
			
		||||
 | 
			
		||||
        Glide.with(requireContext()).load(posterUrl)
 | 
			
		||||
            .into(imagePoster)
 | 
			
		||||
            .into(binding.imagePoster)
 | 
			
		||||
 | 
			
		||||
        textTitle.text = media.info.title
 | 
			
		||||
        textYear.text = media.info.year.toString()
 | 
			
		||||
        textAge.text = media.info.age.toString()
 | 
			
		||||
        textOverview.text = media.info.shortDesc
 | 
			
		||||
        binding.textTitle.text = media.info.title
 | 
			
		||||
        binding.textYear.text = media.info.year.toString()
 | 
			
		||||
        binding.textAge.text = media.info.age.toString()
 | 
			
		||||
        binding.textOverview.text = media.info.shortDesc
 | 
			
		||||
        if (StorageController.myList.contains(media.id)) {
 | 
			
		||||
            Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(imageMyListAction)
 | 
			
		||||
            Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(binding.imageMyListAction)
 | 
			
		||||
        } else {
 | 
			
		||||
            Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(imageMyListAction)
 | 
			
		||||
            Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(binding.imageMyListAction)
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // clear fragments, since it lives in onCreate scope (don't do this in onPause/onStop -> FragmentManager transaction)
 | 
			
		||||
        fragments.clear()
 | 
			
		||||
        pagerAdapter.notifyDataSetChanged()
 | 
			
		||||
 | 
			
		||||
        // specific gui
 | 
			
		||||
        if (media.type == MediaType.TVSHOW) {
 | 
			
		||||
            adapterRecEpisodes = EpisodeItemAdapter(media.episodes)
 | 
			
		||||
            recyclerEpisodes.adapter = adapterRecEpisodes
 | 
			
		||||
 | 
			
		||||
            // episodes count
 | 
			
		||||
            binding.textEpisodesOrRuntime.text = resources.getQuantityString(
 | 
			
		||||
                R.plurals.text_episodes_count,
 | 
			
		||||
                media.info.episodesCount,
 | 
			
		||||
                media.info.episodesCount
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            // get next episode
 | 
			
		||||
            nextEpisode = if (media.episodes.firstOrNull{ !it.watched } != null) {
 | 
			
		||||
                media.episodes.first{ !it.watched }
 | 
			
		||||
@ -109,28 +114,44 @@ class MediaFragment(private val mediaId: Int) : Fragment() {
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            // title is the next episodes title
 | 
			
		||||
            textTitle.text = nextEpisode.title
 | 
			
		||||
            binding.textTitle.text = nextEpisode.title
 | 
			
		||||
 | 
			
		||||
            // episodes count
 | 
			
		||||
            binding.textEpisodesOrRuntime.text = resources.getQuantityString(
 | 
			
		||||
                R.plurals.text_episodes_count,
 | 
			
		||||
                media.info.episodesCount,
 | 
			
		||||
                media.info.episodesCount
 | 
			
		||||
            )
 | 
			
		||||
 | 
			
		||||
            // episodes
 | 
			
		||||
            fragments.add(MediaFragmentEpisodes())
 | 
			
		||||
            pagerAdapter.notifyDataSetChanged()
 | 
			
		||||
        } else if (media.type == MediaType.MOVIE) {
 | 
			
		||||
            recyclerEpisodes.visibility = View.GONE
 | 
			
		||||
 | 
			
		||||
            if (tmdb.runtime > 0) {
 | 
			
		||||
                textEpisodesOrRuntime.text = resources.getQuantityString(
 | 
			
		||||
                binding.textEpisodesOrRuntime.text = resources.getQuantityString(
 | 
			
		||||
                    R.plurals.text_runtime,
 | 
			
		||||
                    tmdb.runtime,
 | 
			
		||||
                    tmdb.runtime
 | 
			
		||||
                )
 | 
			
		||||
            } else {
 | 
			
		||||
                textEpisodesOrRuntime.visibility = View.GONE
 | 
			
		||||
                binding.textEpisodesOrRuntime.visibility = View.GONE
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        frameLoading.visibility = View.GONE // hide loading indicator
 | 
			
		||||
        // if has similar titles
 | 
			
		||||
        if (media.info.similar.isNotEmpty()) {
 | 
			
		||||
            fragments.add(MediaFragmentSimilar())
 | 
			
		||||
            pagerAdapter.notifyDataSetChanged()
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        binding.frameLoading.visibility = View.GONE // hide loading indicator
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun initActions() {
 | 
			
		||||
    private fun initActions() = with(model) {
 | 
			
		||||
        binding.buttonPlay.setOnClickListener {
 | 
			
		||||
            when (media.type) {
 | 
			
		||||
                MediaType.MOVIE -> playStream(media.episodes.first())
 | 
			
		||||
                MediaType.MOVIE -> playEpisode(media.episodes.first())
 | 
			
		||||
                MediaType.TVSHOW -> playEpisode(nextEpisode)
 | 
			
		||||
                else -> Log.e(javaClass.name, "Wrong Type: $media.type")
 | 
			
		||||
            }
 | 
			
		||||
@ -152,30 +173,26 @@ class MediaFragment(private val mediaId: Int) : Fragment() {
 | 
			
		||||
                (it as HomeFragment).updateMyListMedia()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // set onItemClick only in adapter is initialized
 | 
			
		||||
        if (this::adapterRecEpisodes.isInitialized) {
 | 
			
		||||
            adapterRecEpisodes.onImageClick = { _, position ->
 | 
			
		||||
                playEpisode(media.episodes[position])
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * play the current episode
 | 
			
		||||
     * TODO this is also used in MediaFragmentEpisode, we should only have on implementation
 | 
			
		||||
     */
 | 
			
		||||
    private fun playEpisode(ep: Episode) {
 | 
			
		||||
        playStream(ep)
 | 
			
		||||
        (activity as MainActivity).startPlayer(model.media.id, ep.id)
 | 
			
		||||
        Log.d(javaClass.name, "Started Player with  episodeId: ${ep.id}")
 | 
			
		||||
 | 
			
		||||
        // update nextEpisode
 | 
			
		||||
        nextEpisode = if (media.episodes.firstOrNull{ !it.watched } != null) {
 | 
			
		||||
            media.episodes.first{ !it.watched }
 | 
			
		||||
        } else {
 | 
			
		||||
            media.episodes.first()
 | 
			
		||||
        }
 | 
			
		||||
        binding.textTitle.text = nextEpisode.title
 | 
			
		||||
        model.updateNextEpisode(ep) // set the correct next episode
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun playStream(ep: Episode) {
 | 
			
		||||
        Log.d(javaClass.name, "Starting Player with  mediaId: ${media.id}")
 | 
			
		||||
        (activity as MainActivity).startPlayer(media.id, ep.id)
 | 
			
		||||
    /**
 | 
			
		||||
     * A simple pager adapter
 | 
			
		||||
     */
 | 
			
		||||
    private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
 | 
			
		||||
        override fun getItemCount(): Int = fragments.size
 | 
			
		||||
 | 
			
		||||
        override fun createFragment(position: Int): Fragment = fragments[position]
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,61 @@
 | 
			
		||||
package org.mosad.teapod.ui.activity.main.fragments
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.util.Log
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.fragment.app.Fragment
 | 
			
		||||
import androidx.fragment.app.activityViewModels
 | 
			
		||||
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.Episode
 | 
			
		||||
import org.mosad.teapod.util.adapter.EpisodeItemAdapter
 | 
			
		||||
 | 
			
		||||
class MediaFragmentEpisodes : Fragment() {
 | 
			
		||||
 | 
			
		||||
    private lateinit var binding: FragmentMediaEpisodesBinding
 | 
			
		||||
    private lateinit var adapterRecEpisodes: EpisodeItemAdapter
 | 
			
		||||
 | 
			
		||||
    private val model: MediaFragmentViewModel by activityViewModels()
 | 
			
		||||
 | 
			
		||||
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
 | 
			
		||||
        binding = FragmentMediaEpisodesBinding.inflate(inflater, container, false)
 | 
			
		||||
        return binding.root
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        adapterRecEpisodes = EpisodeItemAdapter(model.media.episodes)
 | 
			
		||||
        binding.recyclerEpisodes.adapter = adapterRecEpisodes
 | 
			
		||||
 | 
			
		||||
        // set onItemClick only in adapter is initialized
 | 
			
		||||
        if (this::adapterRecEpisodes.isInitialized) {
 | 
			
		||||
            adapterRecEpisodes.onImageClick = { _, position ->
 | 
			
		||||
                playEpisode(model.media.episodes[position])
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onResume() {
 | 
			
		||||
        super.onResume()
 | 
			
		||||
 | 
			
		||||
        // if adapterRecEpisodes is initialized, update the watched state for the episodes
 | 
			
		||||
        if (this::adapterRecEpisodes.isInitialized) {
 | 
			
		||||
            model.media.episodes.forEachIndexed { index, episode ->
 | 
			
		||||
                adapterRecEpisodes.updateWatchedState(episode.watched, index)
 | 
			
		||||
            }
 | 
			
		||||
            adapterRecEpisodes.notifyDataSetChanged()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private fun playEpisode(ep: Episode) {
 | 
			
		||||
        (activity as MainActivity).startPlayer(model.media.id, ep.id)
 | 
			
		||||
        Log.d(javaClass.name, "Started Player with  episodeId: ${ep.id}")
 | 
			
		||||
 | 
			
		||||
        model.updateNextEpisode(ep) // set the correct next episode
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -0,0 +1,41 @@
 | 
			
		||||
package org.mosad.teapod.ui.activity.main.fragments
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
import android.view.View
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import androidx.fragment.app.Fragment
 | 
			
		||||
import androidx.fragment.app.activityViewModels
 | 
			
		||||
import org.mosad.teapod.databinding.FragmentMediaSimilarBinding
 | 
			
		||||
import org.mosad.teapod.ui.activity.main.viewmodel.MediaFragmentViewModel
 | 
			
		||||
import org.mosad.teapod.util.adapter.MediaItemAdapter
 | 
			
		||||
import org.mosad.teapod.util.decoration.MediaItemDecoration
 | 
			
		||||
import org.mosad.teapod.util.showFragment
 | 
			
		||||
 | 
			
		||||
class MediaFragmentSimilar : Fragment()  {
 | 
			
		||||
 | 
			
		||||
    private lateinit var binding: FragmentMediaSimilarBinding
 | 
			
		||||
    private val model: MediaFragmentViewModel by activityViewModels()
 | 
			
		||||
 | 
			
		||||
    private lateinit var adapterSimilar: MediaItemAdapter
 | 
			
		||||
 | 
			
		||||
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
 | 
			
		||||
        binding = FragmentMediaSimilarBinding.inflate(inflater, container, false)
 | 
			
		||||
        return binding.root
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onViewCreated(view, savedInstanceState)
 | 
			
		||||
 | 
			
		||||
        adapterSimilar = MediaItemAdapter(model.media.info.similar)
 | 
			
		||||
        binding.recyclerMediaSimilar.adapter = adapterSimilar
 | 
			
		||||
        binding.recyclerMediaSimilar.addItemDecoration(MediaItemDecoration(9))
 | 
			
		||||
 | 
			
		||||
        // set onItemClick only in adapter is initialized
 | 
			
		||||
        if (this::adapterSimilar.isInitialized) {
 | 
			
		||||
            adapterSimilar.onItemClick = { mediaId, _ ->
 | 
			
		||||
                activity?.showFragment(MediaFragment(mediaId))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package org.mosad.teapod.activity.main.fragments
 | 
			
		||||
package org.mosad.teapod.ui.activity.main.fragments
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
@ -7,7 +7,6 @@ import android.view.ViewGroup
 | 
			
		||||
import android.widget.SearchView
 | 
			
		||||
import androidx.fragment.app.Fragment
 | 
			
		||||
import kotlinx.coroutines.*
 | 
			
		||||
import org.mosad.teapod.activity.main.MainActivity
 | 
			
		||||
import org.mosad.teapod.databinding.FragmentSearchBinding
 | 
			
		||||
import org.mosad.teapod.parser.AoDParser
 | 
			
		||||
import org.mosad.teapod.util.decoration.MediaItemDecoration
 | 
			
		||||
@ -0,0 +1,48 @@
 | 
			
		||||
package org.mosad.teapod.ui.activity.main.viewmodel
 | 
			
		||||
 | 
			
		||||
import android.app.Application
 | 
			
		||||
import androidx.lifecycle.AndroidViewModel
 | 
			
		||||
import org.mosad.teapod.parser.AoDParser
 | 
			
		||||
import org.mosad.teapod.util.*
 | 
			
		||||
import org.mosad.teapod.util.DataTypes.MediaType
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * handle media, next ep and tmdb
 | 
			
		||||
 */
 | 
			
		||||
class MediaFragmentViewModel(application: Application) : AndroidViewModel(application) {
 | 
			
		||||
 | 
			
		||||
    var media = Media(-1, "", MediaType.OTHER)
 | 
			
		||||
        internal set
 | 
			
		||||
    var nextEpisode = Episode()
 | 
			
		||||
        internal set
 | 
			
		||||
    var tmdb = TMDBResponse()
 | 
			
		||||
        internal set
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * set media, tmdb and nextEpisode
 | 
			
		||||
     */
 | 
			
		||||
    suspend fun load(mediaId: Int) {
 | 
			
		||||
        media = AoDParser.getMediaById(mediaId)
 | 
			
		||||
        tmdb = TMDBApiController().search(media.info.title, media.type)
 | 
			
		||||
 | 
			
		||||
        if (media.type == MediaType.TVSHOW) {
 | 
			
		||||
            nextEpisode = if (media.episodes.firstOrNull{ !it.watched } != null) {
 | 
			
		||||
                media.episodes.first{ !it.watched }
 | 
			
		||||
            } else {
 | 
			
		||||
                media.episodes.first()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * get the next episode based on episode number (the true next episode)
 | 
			
		||||
     * if no matching is found, use first episode
 | 
			
		||||
     */
 | 
			
		||||
    fun updateNextEpisode(currentEp: Episode) {
 | 
			
		||||
        if (media.type == MediaType.MOVIE) return // return if movie
 | 
			
		||||
 | 
			
		||||
        nextEpisode = media.episodes.firstOrNull{ it.number > currentEp.number }
 | 
			
		||||
            ?: media.episodes.first()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package org.mosad.teapod.activity.onboarding
 | 
			
		||||
package org.mosad.teapod.ui.activity.onboarding
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package org.mosad.teapod.activity.onboarding
 | 
			
		||||
package org.mosad.teapod.ui.activity.onboarding
 | 
			
		||||
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
import android.view.LayoutInflater
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package org.mosad.teapod.activity.onboarding
 | 
			
		||||
package org.mosad.teapod.ui.activity.onboarding
 | 
			
		||||
 | 
			
		||||
import android.content.Intent
 | 
			
		||||
import android.os.Bundle
 | 
			
		||||
@ -8,7 +8,7 @@ import androidx.fragment.app.Fragment
 | 
			
		||||
import androidx.fragment.app.FragmentActivity
 | 
			
		||||
import androidx.viewpager2.adapter.FragmentStateAdapter
 | 
			
		||||
import com.google.android.material.tabs.TabLayoutMediator
 | 
			
		||||
import org.mosad.teapod.activity.main.MainActivity
 | 
			
		||||
import org.mosad.teapod.ui.activity.main.MainActivity
 | 
			
		||||
import org.mosad.teapod.databinding.ActivityOnboardingBinding
 | 
			
		||||
 | 
			
		||||
class OnboardingActivity : AppCompatActivity() {
 | 
			
		||||
@ -67,8 +67,7 @@ class OnboardingActivity : AppCompatActivity() {
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * A simple pager adapter that represents 5 ScreenSlidePageFragment objects, in
 | 
			
		||||
     * sequence.
 | 
			
		||||
     * A simple pager adapter
 | 
			
		||||
     */
 | 
			
		||||
    private inner class ScreenSlidePagerAdapter(fa: FragmentActivity) : FragmentStateAdapter(fa) {
 | 
			
		||||
        override fun getItemCount(): Int = fragments.size
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package org.mosad.teapod.activity.player
 | 
			
		||||
package org.mosad.teapod.ui.activity.player
 | 
			
		||||
 | 
			
		||||
import android.animation.Animator
 | 
			
		||||
import android.animation.AnimatorListenerAdapter
 | 
			
		||||
@ -1,4 +1,4 @@
 | 
			
		||||
package org.mosad.teapod.activity.player
 | 
			
		||||
package org.mosad.teapod.ui.activity.player
 | 
			
		||||
 | 
			
		||||
import android.app.Application
 | 
			
		||||
import android.net.Uri
 | 
			
		||||
@ -6,7 +6,7 @@ import android.view.LayoutInflater
 | 
			
		||||
import android.view.ViewGroup
 | 
			
		||||
import android.widget.LinearLayout
 | 
			
		||||
import org.mosad.teapod.databinding.PlayerEpisodesListBinding
 | 
			
		||||
import org.mosad.teapod.activity.player.PlayerViewModel
 | 
			
		||||
import org.mosad.teapod.ui.activity.player.PlayerViewModel
 | 
			
		||||
import org.mosad.teapod.util.adapter.PlayerEpisodeItemAdapter
 | 
			
		||||
 | 
			
		||||
class EpisodesListPlayer @JvmOverloads constructor(
 | 
			
		||||
 | 
			
		||||
@ -13,7 +13,7 @@ import android.widget.TextView
 | 
			
		||||
import androidx.core.view.children
 | 
			
		||||
import org.mosad.teapod.R
 | 
			
		||||
import org.mosad.teapod.databinding.PlayerLanguageSettingsBinding
 | 
			
		||||
import org.mosad.teapod.activity.player.PlayerViewModel
 | 
			
		||||
import org.mosad.teapod.ui.activity.player.PlayerViewModel
 | 
			
		||||
import java.util.*
 | 
			
		||||
 | 
			
		||||
class LanguageSettingsPlayer @JvmOverloads constructor(
 | 
			
		||||
 | 
			
		||||
@ -55,6 +55,7 @@ data class Media(
 | 
			
		||||
    fun getEpisodeById(id: Int) = episodes.first { it.id == id }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO all val?
 | 
			
		||||
data class Info(
 | 
			
		||||
    var title: String = "",
 | 
			
		||||
    var posterUrl: String = "",
 | 
			
		||||
@ -62,7 +63,8 @@ data class Info(
 | 
			
		||||
    var description: String = "",
 | 
			
		||||
    var year: Int = 0,
 | 
			
		||||
    var age: Int = 0,
 | 
			
		||||
    var episodesCount: Int = 0
 | 
			
		||||
    var episodesCount: Int = 0,
 | 
			
		||||
    var similar: List<ItemMedia> = listOf()
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -96,6 +98,7 @@ data class Stream(
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * this class is used for tmdb responses
 | 
			
		||||
 * TODO why is runtime var?
 | 
			
		||||
 */
 | 
			
		||||
data class TMDBResponse(
 | 
			
		||||
    val id: Int = 0,
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user