From 5bb51c9054eb05f0b20931ed008c3161632eec0d Mon Sep 17 00:00:00 2001 From: Jannik Date: Sat, 6 Feb 2021 19:02:12 +0100 Subject: [PATCH] Add similar titles to media fragment (#28) * update androidx navigation libraries * add similar media to MediaFragment * parse similar media in AoDParser Reviewed-on: https://git.mosad.xyz/Seil0/teapod/pulls/28 Co-Authored-By: Jannik Co-Committed-By: Jannik --- app/build.gradle | 4 +- app/src/main/AndroidManifest.xml | 10 +- .../java/org/mosad/teapod/parser/AoDParser.kt | 20 ++- .../{ => ui}/activity/SplashActivity.kt | 4 +- .../{ => ui}/activity/main/MainActivity.kt | 20 ++- .../activity/main/fragments/AboutFragment.kt | 2 +- .../main/fragments/AccountFragment.kt | 4 +- .../activity/main/fragments/HomeFragment.kt | 4 +- .../main/fragments/LibraryFragment.kt | 3 +- .../activity/main/fragments/MediaFragment.kt | 143 ++++++++++-------- .../main/fragments/MediaFragmentEpisodes.kt | 61 ++++++++ .../main/fragments/MediaFragmentSimilar.kt | 41 +++++ .../activity/main/fragments/SearchFragment.kt | 3 +- .../main/viewmodel/MediaFragmentViewModel.kt | 48 ++++++ .../activity/onboarding/OnLoginFragment.kt | 2 +- .../activity/onboarding/OnWelcomeFragment.kt | 2 +- .../activity/onboarding/OnboardingActivity.kt | 7 +- .../activity/player/PlayerActivity.kt | 2 +- .../activity/player/PlayerViewModel.kt | 2 +- .../ui/components/EpisodesListPlayer.kt | 2 +- .../ui/components/LanguageSettingsPlayer.kt | 2 +- .../java/org/mosad/teapod/util/DataTypes.kt | 5 +- app/src/main/res/layout/activity_player.xml | 2 +- app/src/main/res/layout/fragment_about.xml | 2 +- app/src/main/res/layout/fragment_account.xml | 2 +- app/src/main/res/layout/fragment_home.xml | 2 +- app/src/main/res/layout/fragment_library.xml | 2 +- app/src/main/res/layout/fragment_media.xml | 23 ++- .../res/layout/fragment_media_episodes.xml | 20 +++ .../res/layout/fragment_media_similar.xml | 22 +++ app/src/main/res/layout/fragment_search.xml | 2 +- .../main/res/navigation/mobile_navigation.xml | 8 +- app/src/main/res/values-de-rDE/strings.xml | 1 + app/src/main/res/values/strings.xml | 1 + 34 files changed, 357 insertions(+), 121 deletions(-) rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/SplashActivity.kt (80%) rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/main/MainActivity.kt (93%) rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/main/fragments/AboutFragment.kt (98%) rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/main/fragments/AccountFragment.kt (97%) rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/main/fragments/HomeFragment.kt (98%) rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/main/fragments/LibraryFragment.kt (94%) rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/main/fragments/MediaFragment.kt (54%) create mode 100644 app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentEpisodes.kt create mode 100644 app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/main/fragments/SearchFragment.kt (95%) create mode 100644 app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/onboarding/OnLoginFragment.kt (97%) rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/onboarding/OnWelcomeFragment.kt (95%) rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/onboarding/OnboardingActivity.kt (92%) rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/player/PlayerActivity.kt (99%) rename app/src/main/java/org/mosad/teapod/{ => ui}/activity/player/PlayerViewModel.kt (99%) create mode 100644 app/src/main/res/layout/fragment_media_episodes.xml create mode 100644 app/src/main/res/layout/fragment_media_similar.xml diff --git a/app/build.gradle b/app/build.gradle index 218155c..cae0d62 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -46,8 +46,8 @@ dependencies { implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.4' - implementation 'androidx.navigation:navigation-fragment-ktx:2.3.2' - implementation 'androidx.navigation:navigation-ui-ktx:2.3.2' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.3' + implementation 'androidx.navigation:navigation-ui-ktx:2.3.3' implementation 'androidx.security:security-crypto:1.1.0-alpha03' implementation 'androidx.legacy:legacy-support-v4:1.0.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 11b032f..794a418 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -13,7 +13,7 @@ android:supportsRtl="true" android:theme="@style/AppTheme.Dark"> @@ -23,24 +23,24 @@ () 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 diff --git a/app/src/main/java/org/mosad/teapod/activity/SplashActivity.kt b/app/src/main/java/org/mosad/teapod/ui/activity/SplashActivity.kt similarity index 80% rename from app/src/main/java/org/mosad/teapod/activity/SplashActivity.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/SplashActivity.kt index 39b6744..c7dfa49 100644 --- a/app/src/main/java/org/mosad/teapod/activity/SplashActivity.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/SplashActivity.kt @@ -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() { diff --git a/app/src/main/java/org/mosad/teapod/activity/main/MainActivity.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt similarity index 93% rename from app/src/main/java/org/mosad/teapod/activity/main/MainActivity.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt index c22a68b..19dd297 100644 --- a/app/src/main/java/org/mosad/teapod/activity/main/MainActivity.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt @@ -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?) { diff --git a/app/src/main/java/org/mosad/teapod/activity/main/fragments/AboutFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AboutFragment.kt similarity index 98% rename from app/src/main/java/org/mosad/teapod/activity/main/fragments/AboutFragment.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AboutFragment.kt index ddba535..a30d129 100644 --- a/app/src/main/java/org/mosad/teapod/activity/main/fragments/AboutFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AboutFragment.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/activity/main/fragments/AccountFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AccountFragment.kt similarity index 97% rename from app/src/main/java/org/mosad/teapod/activity/main/fragments/AccountFragment.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AccountFragment.kt index 1561b1c..61ca637 100644 --- a/app/src/main/java/org/mosad/teapod/activity/main/fragments/AccountFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AccountFragment.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/activity/main/fragments/HomeFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt similarity index 98% rename from app/src/main/java/org/mosad/teapod/activity/main/fragments/HomeFragment.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt index cf0fc0f..6e19d11 100644 --- a/app/src/main/java/org/mosad/teapod/activity/main/fragments/HomeFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/activity/main/fragments/LibraryFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/LibraryFragment.kt similarity index 94% rename from app/src/main/java/org/mosad/teapod/activity/main/fragments/LibraryFragment.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/LibraryFragment.kt index 50ba7b9..01ab191 100644 --- a/app/src/main/java/org/mosad/teapod/activity/main/fragments/LibraryFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/LibraryFragment.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/activity/main/fragments/MediaFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt similarity index 54% rename from app/src/main/java/org/mosad/teapod/activity/main/fragments/MediaFragment.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt index ed3f42f..47d9067 100644 --- a/app/src/main/java/org/mosad/teapod/activity/main/fragments/MediaFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt @@ -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() + + 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] } } \ No newline at end of file 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 new file mode 100644 index 0000000..78f480f --- /dev/null +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentEpisodes.kt @@ -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 + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt new file mode 100644 index 0000000..db6d519 --- /dev/null +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt @@ -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)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/activity/main/fragments/SearchFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/SearchFragment.kt similarity index 95% rename from app/src/main/java/org/mosad/teapod/activity/main/fragments/SearchFragment.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/SearchFragment.kt index 69ea9c4..57c43b1 100644 --- a/app/src/main/java/org/mosad/teapod/activity/main/fragments/SearchFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/SearchFragment.kt @@ -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 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 new file mode 100644 index 0000000..c2ba21d --- /dev/null +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt @@ -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() + } + +} \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/activity/onboarding/OnLoginFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnLoginFragment.kt similarity index 97% rename from app/src/main/java/org/mosad/teapod/activity/onboarding/OnLoginFragment.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnLoginFragment.kt index b60646e..f65ab30 100644 --- a/app/src/main/java/org/mosad/teapod/activity/onboarding/OnLoginFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnLoginFragment.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/activity/onboarding/OnWelcomeFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnWelcomeFragment.kt similarity index 95% rename from app/src/main/java/org/mosad/teapod/activity/onboarding/OnWelcomeFragment.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnWelcomeFragment.kt index 28e221c..b51e6ed 100644 --- a/app/src/main/java/org/mosad/teapod/activity/onboarding/OnWelcomeFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnWelcomeFragment.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/activity/onboarding/OnboardingActivity.kt b/app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnboardingActivity.kt similarity index 92% rename from app/src/main/java/org/mosad/teapod/activity/onboarding/OnboardingActivity.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnboardingActivity.kt index 4493b82..8087e98 100644 --- a/app/src/main/java/org/mosad/teapod/activity/onboarding/OnboardingActivity.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnboardingActivity.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/activity/player/PlayerActivity.kt b/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerActivity.kt similarity index 99% rename from app/src/main/java/org/mosad/teapod/activity/player/PlayerActivity.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerActivity.kt index d3e66ec..0999106 100644 --- a/app/src/main/java/org/mosad/teapod/activity/player/PlayerActivity.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerActivity.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/activity/player/PlayerViewModel.kt b/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt similarity index 99% rename from app/src/main/java/org/mosad/teapod/activity/player/PlayerViewModel.kt rename to app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt index 8a1eb89..4efb7c4 100644 --- a/app/src/main/java/org/mosad/teapod/activity/player/PlayerViewModel.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt b/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt index 259305f..cb51deb 100644 --- a/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt +++ b/app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt @@ -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( diff --git a/app/src/main/java/org/mosad/teapod/ui/components/LanguageSettingsPlayer.kt b/app/src/main/java/org/mosad/teapod/ui/components/LanguageSettingsPlayer.kt index a1b1d92..404ba7e 100644 --- a/app/src/main/java/org/mosad/teapod/ui/components/LanguageSettingsPlayer.kt +++ b/app/src/main/java/org/mosad/teapod/ui/components/LanguageSettingsPlayer.kt @@ -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( diff --git a/app/src/main/java/org/mosad/teapod/util/DataTypes.kt b/app/src/main/java/org/mosad/teapod/util/DataTypes.kt index bc27719..cf936bd 100644 --- a/app/src/main/java/org/mosad/teapod/util/DataTypes.kt +++ b/app/src/main/java/org/mosad/teapod/util/DataTypes.kt @@ -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 = 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, diff --git a/app/src/main/res/layout/activity_player.xml b/app/src/main/res/layout/activity_player.xml index 050d442..5a8f6bd 100644 --- a/app/src/main/res/layout/activity_player.xml +++ b/app/src/main/res/layout/activity_player.xml @@ -7,7 +7,7 @@ android:layout_height="match_parent" android:background="#000000" android:keepScreenOn="true" - tools:context=".activity.player.PlayerActivity"> + tools:context=".ui.activity.player.PlayerActivity"> + tools:context=".ui.activity.main.fragments.AboutFragment"> + tools:context=".ui.activity.main.fragments.AccountFragment"> + tools:context=".ui.activity.main.fragments.HomeFragment"> + tools:context=".ui.activity.main.fragments.LibraryFragment"> + tools:context=".ui.activity.main.fragments.MediaFragment"> - + app:tabMode="scrollable" + app:tabGravity="start" + app:tabSelectedTextColor="?textPrimary" + app:tabTextColor="?textSecondary" /> + + + diff --git a/app/src/main/res/layout/fragment_media_episodes.xml b/app/src/main/res/layout/fragment_media_episodes.xml new file mode 100644 index 0000000..89b3e68 --- /dev/null +++ b/app/src/main/res/layout/fragment_media_episodes.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_media_similar.xml b/app/src/main/res/layout/fragment_media_similar.xml new file mode 100644 index 0000000..67399e4 --- /dev/null +++ b/app/src/main/res/layout/fragment_media_similar.xml @@ -0,0 +1,22 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 02fd428..6427560 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -5,7 +5,7 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:background="?themePrimary" - tools:context=".activity.main.fragments.SearchFragment"> + tools:context=".ui.activity.main.fragments.SearchFragment"> diff --git a/app/src/main/res/values-de-rDE/strings.xml b/app/src/main/res/values-de-rDE/strings.xml index d710961..c958865 100644 --- a/app/src/main/res/values-de-rDE/strings.xml +++ b/app/src/main/res/values-de-rDE/strings.xml @@ -27,6 +27,7 @@ %d Minute %d Minuten + Ähnliche Titel Flg. %1$d %2$s Flg. %1$d %2$s (OmU) diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 1c6411c..fe2da5e 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -34,6 +34,7 @@ %d Minute %d Minutes + Similar titles Ep. %1$d %2$s Ep. %1$d %2$s (Sub) episode poster