Browse Source

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>
pull/34/head
Jannik 1 year ago
parent
commit
5bb51c9054
  1. 4
      app/build.gradle
  2. 10
      app/src/main/AndroidManifest.xml
  3. 20
      app/src/main/java/org/mosad/teapod/parser/AoDParser.kt
  4. 4
      app/src/main/java/org/mosad/teapod/ui/activity/SplashActivity.kt
  5. 20
      app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt
  6. 2
      app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AboutFragment.kt
  7. 4
      app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AccountFragment.kt
  8. 4
      app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt
  9. 3
      app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/LibraryFragment.kt
  10. 143
      app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt
  11. 61
      app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentEpisodes.kt
  12. 41
      app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragmentSimilar.kt
  13. 3
      app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/SearchFragment.kt
  14. 48
      app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt
  15. 2
      app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnLoginFragment.kt
  16. 2
      app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnWelcomeFragment.kt
  17. 7
      app/src/main/java/org/mosad/teapod/ui/activity/onboarding/OnboardingActivity.kt
  18. 2
      app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerActivity.kt
  19. 2
      app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt
  20. 2
      app/src/main/java/org/mosad/teapod/ui/components/EpisodesListPlayer.kt
  21. 2
      app/src/main/java/org/mosad/teapod/ui/components/LanguageSettingsPlayer.kt
  22. 5
      app/src/main/java/org/mosad/teapod/util/DataTypes.kt
  23. 2
      app/src/main/res/layout/activity_player.xml
  24. 2
      app/src/main/res/layout/fragment_about.xml
  25. 2
      app/src/main/res/layout/fragment_account.xml
  26. 2
      app/src/main/res/layout/fragment_home.xml
  27. 2
      app/src/main/res/layout/fragment_library.xml
  28. 23
      app/src/main/res/layout/fragment_media.xml
  29. 20
      app/src/main/res/layout/fragment_media_episodes.xml
  30. 22
      app/src/main/res/layout/fragment_media_similar.xml
  31. 2
      app/src/main/res/layout/fragment_search.xml
  32. 8
      app/src/main/res/navigation/mobile_navigation.xml
  33. 1
      app/src/main/res/values-de-rDE/strings.xml
  34. 1
      app/src/main/res/values/strings.xml

4
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'

10
app/src/main/AndroidManifest.xml

@ -13,7 +13,7 @@
android:supportsRtl="true"
android:theme="@style/AppTheme.Dark">
<activity
android:name=".activity.SplashActivity"
android:name="org.mosad.teapod.ui.activity.SplashActivity"
android:label="@string/app_name"
android:theme="@style/SplashTheme"
android:screenOrientation="portrait">
@ -23,24 +23,24 @@
</intent-filter>
</activity>
<activity
android:name=".activity.onboarding.OnboardingActivity"
android:name="org.mosad.teapod.ui.activity.onboarding.OnboardingActivity"
android:label="@string/app_name"
android:screenOrientation="portrait"
android:launchMode="singleTop"
android:windowSoftInputMode="adjustPan">
</activity>
<activity
android:name=".activity.main.MainActivity"
android:name="org.mosad.teapod.ui.activity.main.MainActivity"
android:label="@string/app_name"
android:screenOrientation="portrait">
</activity>
<activity
android:name="org.mosad.teapod.activity.player.PlayerActivity"
android:name="org.mosad.teapod.ui.activity.player.PlayerActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|layoutDirection"
android:excludeFromRecents="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:parentActivityName=".activity.main.MainActivity"
android:parentActivityName="org.mosad.teapod.ui.activity.main.MainActivity"
android:supportsPictureInPicture="true"
android:taskAffinity=".player.PlayerActivity"
android:theme="@style/PlayerTheme"

20
app/src/main/java/org/mosad/teapod/parser/AoDParser.kt

@ -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

4
app/src/main/java/org/mosad/teapod/activity/SplashActivity.kt → 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() {

20
app/src/main/java/org/mosad/teapod/activity/main/MainActivity.kt → 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?) {

2
app/src/main/java/org/mosad/teapod/activity/main/fragments/AboutFragment.kt → 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

4
app/src/main/java/org/mosad/teapod/activity/main/fragments/AccountFragment.kt → 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

4
app/src/main/java/org/mosad/teapod/activity/main/fragments/HomeFragment.kt → 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

3
app/src/main/java/org/mosad/teapod/activity/main/fragments/LibraryFragment.kt → 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

143
app/src/main/java/org/mosad/teapod/activity/main/fragments/MediaFragment.kt → 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<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]
}
}

61
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
}
}

41
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))
}
}
}
}

3
app/src/main/java/org/mosad/teapod/activity/main/fragments/SearchFragment.kt → 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

48
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()
}
}

2
app/src/main/java/org/mosad/teapod/activity/onboarding/OnLoginFragment.kt → 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

2
app/src/main/java/org/mosad/teapod/activity/onboarding/OnWelcomeFragment.kt → 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

7
app/src/main/java/org/mosad/teapod/activity/onboarding/OnboardingActivity.kt → 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

2
app/src/main/java/org/mosad/teapod/activity/player/PlayerActivity.kt → 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

2
app/src/main/java/org/mosad/teapod/activity/player/PlayerViewModel.kt → 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

2
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(

2
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(

5
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<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,

2
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">
<com.google.android.exoplayer2.ui.StyledPlayerView
android:id="@+id/video_view"

2
app/src/main/res/layout/fragment_about.xml

@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?themePrimary"
tools:context=".activity.main.fragments.AboutFragment">
tools:context=".ui.activity.main.fragments.AboutFragment">
<LinearLayout
android:layout_width="match_parent"

2
app/src/main/res/layout/fragment_account.xml

@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?themePrimary"
tools:context=".activity.main.fragments.AccountFragment">
tools:context=".ui.activity.main.fragments.AccountFragment">
<ScrollView
android:layout_width="match_parent"

2
app/src/main/res/layout/fragment_home.xml

@ -6,7 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?themePrimary"
tools:context=".activity.main.fragments.HomeFragment">
tools:context=".ui.activity.main.fragments.HomeFragment">
<ScrollView
android:layout_width="match_parent"

2
app/src/main/res/layout/fragment_library.xml

@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?themePrimary"
tools:context=".activity.main.fragments.LibraryFragment">
tools:context=".ui.activity.main.fragments.LibraryFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_media_library"

23
app/src/main/res/layout/fragment_media.xml

@ -5,7 +5,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?themePrimary"
tools:context=".activity.main.fragments.MediaFragment">
tools:context=".ui.activity.main.fragments.MediaFragment">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
@ -148,17 +148,24 @@
</LinearLayout>
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_episodes"
<com.google.android.material.tabs.TabLayout
android:id="@+id/tab_episodes_similar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:layout_marginStart="7dp"
android:layout_marginTop="17dp"
android:layout_marginTop="12dp"
android:layout_marginEnd="7dp"
android:nestedScrollingEnabled="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:layout_editor_absoluteY="298dp"
tools:listitem="@layout/item_episode" />
app:tabMode="scrollable"
app:tabGravity="start"
app:tabSelectedTextColor="?textPrimary"
app:tabTextColor="?textSecondary" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/pager_episodes_similar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>

20
app/src/main/res/layout/fragment_media_episodes.xml

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_episodes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="false"
android:paddingStart="7dp"
android:paddingEnd="7dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:layout_editor_absoluteY="298dp"
tools:listitem="@layout/item_episode" />
</FrameLayout>

22
app/src/main/res/layout/fragment_media_similar.xml

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_media_similar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingStart="3dp"
android:paddingTop="6dp"
android:paddingEnd="3dp"
android:paddingBottom="3dp"
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
app:spanCount="2"
tools:listitem="@layout/item_media" />
</FrameLayout>

2
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">
<SearchView
android:id="@+id/search_text"

8
app/src/main/res/navigation/mobile_navigation.xml

@ -7,25 +7,25 @@
<fragment
android:id="@+id/navigation_home"
android:name="org.mosad.teapod.activity.main.fragments.HomeFragment"
android:name="org.mosad.teapod.ui.activity.main.fragments.HomeFragment"
android:label="@string/title_home"
tools:layout="@layout/fragment_home" />
<fragment
android:id="@+id/navigation_library"
android:name="org.mosad.teapod.activity.main.fragments.LibraryFragment"
android:name="org.mosad.teapod.ui.activity.main.fragments.LibraryFragment"
android:label="@string/title_library"
tools:layout="@layout/fragment_library" />
<fragment
android:id="@+id/navigation_search"
android:name="org.mosad.teapod.activity.main.fragments.SearchFragment"
android:name="org.mosad.teapod.ui.activity.main.fragments.SearchFragment"
android:label="@string/title_search"
tools:layout="@layout/fragment_search" />
<fragment
android:id="@+id/navigation_account"
android:name="org.mosad.teapod.activity.main.fragments.AccountFragment"
android:name="org.mosad.teapod.ui.activity.main.fragments.AccountFragment"
android:label="@string/title_account"
tools:layout="@layout/fragment_account" />

1
app/src/main/res/values-de-rDE/strings.xml

@ -27,6 +27,7 @@
<item quantity="one">%d Minute</item>
<item quantity="other">%d Minuten</item>
</plurals>
<string name="similar_titles">Ähnliche Titel</string>
<string name="component_episode_title">Flg. %1$d %2$s</string>
<string name="component_episode_title_sub">Flg. %1$d %2$s (OmU)</string>

1
app/src/main/res/values/strings.xml

@ -34,6 +34,7 @@
<item quantity="one">%d Minute</item>
<item quantity="other">%d Minutes</item>
</plurals>
<string name="similar_titles">Similar titles</string>
<string name="component_episode_title">Ep. %1$d %2$s</string>
<string name="component_episode_title_sub">Ep. %1$d %2$s (Sub)</string>
<string name="component_poster_desc" translatable="false">episode poster</string>

Loading…
Cancel
Save

Du besuchst diese Seite mit einem veralteten IPv4-Internetzugang. Möglicherweise treten in Zukunft Probleme mit der Erreichbarkeit und Performance auf. Bitte frage deinen Internetanbieter oder Netzwerkadministrator nach IPv6-Unterstützung.
You are visiting this site with an outdated IPv4 internet access. You may experience problems with accessibility and performance in the future. Please ask your ISP or network administrator for IPv6 support.
Weitere Infos | More Information
Klicke zum schließen | Click to close