show AoD highlights on home fragment

This commit is contained in:
Jannik 2020-12-06 15:18:15 +01:00
parent a390bc9686
commit 6775a4da2e
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
7 changed files with 237 additions and 47 deletions

View File

@ -47,6 +47,7 @@ object AoDParser {
private val mediaList = arrayListOf<Media>() private val mediaList = arrayListOf<Media>()
val itemMediaList = arrayListOf<ItemMedia>() val itemMediaList = arrayListOf<ItemMedia>()
val highlightsList = arrayListOf<ItemMedia>()
val newEpisodesList = arrayListOf<ItemMedia>() val newEpisodesList = arrayListOf<ItemMedia>()
fun login(): Boolean = runBlocking { fun login(): Boolean = runBlocking {
@ -95,7 +96,7 @@ object AoDParser {
*/ */
fun initialLoading() = runBlocking { fun initialLoading() = runBlocking {
val newEPJob = GlobalScope.async { val newEPJob = GlobalScope.async {
listNewEpisodes() loadHome()
} }
val listJob = GlobalScope.async { val listJob = GlobalScope.async {
@ -182,9 +183,9 @@ object AoDParser {
} }
/** /**
* load all new episodes from AoD into newEpisodesList * load new episodes and highlights
*/ */
private fun listNewEpisodes() = runBlocking { private fun loadHome() = runBlocking {
if (sessionCookies.isEmpty()) login() if (sessionCookies.isEmpty()) login()
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
@ -192,6 +193,7 @@ object AoDParser {
.cookies(sessionCookies) .cookies(sessionCookies)
.get() .get()
// get all new episodes from AoD
newEpisodesList.clear() newEpisodesList.clear()
resHome.select("div.jcarousel-container-new").select("li").forEach { resHome.select("div.jcarousel-container-new").select("li").forEach {
if (it.select("span").hasClass("neweps")) { if (it.select("span").hasClass("neweps")) {
@ -204,6 +206,18 @@ object AoDParser {
} }
} }
// get highlights from AoD
highlightsList.clear()
resHome.select("#aod-highlights").select("div.news-item").forEach {
val mediaId = it.select("div.news-item-text").select("a.serienlink")
.attr("href").substringAfterLast("/").toInt()
val mediaTitle = it.select("div.news-title").select("h2").text()
val mediaImage = it.select("img").attr("src")
highlightsList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
}
} }
} }

View File

@ -1,17 +1,24 @@
package org.mosad.teapod.ui.fragments package org.mosad.teapod.ui.fragments
import android.graphics.drawable.Drawable
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.mosad.teapod.MainActivity import org.mosad.teapod.MainActivity
import org.mosad.teapod.R
import org.mosad.teapod.databinding.FragmentHomeBinding import org.mosad.teapod.databinding.FragmentHomeBinding
import org.mosad.teapod.parser.AoDParser import org.mosad.teapod.parser.AoDParser
import org.mosad.teapod.util.ItemMedia
import org.mosad.teapod.util.StorageController import org.mosad.teapod.util.StorageController
import org.mosad.teapod.util.adapter.MediaItemAdapter import org.mosad.teapod.util.adapter.MediaItemAdapter
import org.mosad.teapod.util.decoration.MediaItemDecoration import org.mosad.teapod.util.decoration.MediaItemDecoration
@ -22,6 +29,8 @@ class HomeFragment : Fragment() {
private lateinit var adapterMyList: MediaItemAdapter private lateinit var adapterMyList: MediaItemAdapter
private lateinit var adapterNewEpisodes: MediaItemAdapter private lateinit var adapterNewEpisodes: MediaItemAdapter
private lateinit var highlightMedia: ItemMedia
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentHomeBinding.inflate(inflater, container, false) binding = FragmentHomeBinding.inflate(inflater, container, false)
return binding.root return binding.root
@ -30,24 +39,89 @@ class HomeFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
GlobalScope.launch { GlobalScope.launch(Dispatchers.Main) {
withContext(Dispatchers.Main) { context?.let {
context?.let { initHighlight()
binding.recyclerMyList.addItemDecoration(MediaItemDecoration(9)) initRecyclerViews()
initActions()
updateMyListMedia()
adapterNewEpisodes = MediaItemAdapter(AoDParser.newEpisodesList)
binding.recyclerNewEpisodes.adapter = adapterNewEpisodes
binding.recyclerNewEpisodes.addItemDecoration(MediaItemDecoration(9))
initActions()
}
} }
} }
} }
// TODO recreating the adapter on list change is not a good solution private fun initHighlight() {
highlightMedia = AoDParser.highlightsList[0]
binding.textHighlightTitle.text = highlightMedia.title
Glide.with(requireContext()).load(highlightMedia.posterUrl)
.into(binding.imageHighlight)
if (StorageController.myList.contains(highlightMedia.id)) {
loadIntoCompoundDrawable(R.drawable.ic_baseline_check_24, binding.textHighlightMyList)
} else {
loadIntoCompoundDrawable(R.drawable.ic_baseline_add_24, binding.textHighlightMyList)
}
}
private fun initRecyclerViews() {
binding.recyclerMyList.addItemDecoration(MediaItemDecoration(9))
binding.recyclerNewEpisodes.addItemDecoration(MediaItemDecoration(9))
// my list
val myListMedia = StorageController.myList.map { elementId ->
AoDParser.itemMediaList.first {
elementId == it.id
}
}
adapterMyList = MediaItemAdapter(myListMedia)
adapterMyList.onItemClick = { mediaId, _ ->
(activity as MainActivity).showFragment(MediaFragment(mediaId))
}
binding.recyclerMyList.adapter = adapterMyList
// new episodes
adapterNewEpisodes = MediaItemAdapter(AoDParser.newEpisodesList)
binding.recyclerNewEpisodes.adapter = adapterNewEpisodes
}
private fun initActions() {
binding.buttonPlayHighlight.setOnClickListener {
// TODO get next episode
GlobalScope.launch {
val media = AoDParser.getMediaById(highlightMedia.id)
Log.d(javaClass.name, "Starting Player with mediaId: ${media.id}")
(activity as MainActivity).startPlayer(media.id, media.episodes.first().id)
}
}
binding.textHighlightMyList.setOnClickListener {
if (StorageController.myList.contains(highlightMedia.id)) {
StorageController.myList.remove(highlightMedia.id)
loadIntoCompoundDrawable(R.drawable.ic_baseline_add_24, binding.textHighlightMyList)
} else {
StorageController.myList.add(highlightMedia.id)
loadIntoCompoundDrawable(R.drawable.ic_baseline_check_24, binding.textHighlightMyList)
}
StorageController.saveMyList(requireContext())
updateMyListMedia() // update my list, since it has changed
}
binding.textHighlightInfo.setOnClickListener {
(activity as MainActivity).showFragment(MediaFragment(highlightMedia.id))
}
adapterNewEpisodes.onItemClick = { mediaId, _ ->
(activity as MainActivity).showFragment(MediaFragment(mediaId))
}
}
/**
* update my media list
* TODO
* * auto call when StorageController.myList is changed
* * only update actual change and not all data (performance)
*/
fun updateMyListMedia() { fun updateMyListMedia() {
val myListMedia = StorageController.myList.map { elementId -> val myListMedia = StorageController.myList.map { elementId ->
AoDParser.itemMediaList.first { AoDParser.itemMediaList.first {
@ -55,17 +129,22 @@ class HomeFragment : Fragment() {
} }
} }
adapterMyList = MediaItemAdapter(myListMedia) adapterMyList.updateMediaList(myListMedia)
adapterMyList.onItemClick = { mediaId, _ -> adapterMyList.notifyDataSetChanged()
(activity as MainActivity).showFragment(MediaFragment(mediaId))
}
binding.recyclerMyList.adapter = adapterMyList
} }
private fun initActions() { private fun loadIntoCompoundDrawable(drawable: Int, textView: TextView) {
adapterNewEpisodes.onItemClick = { mediaId, _ -> Glide.with(requireContext())
(activity as MainActivity).showFragment(MediaFragment(mediaId)) .load(drawable)
} .into(object : CustomTarget<Drawable>(48, 48) {
override fun onLoadCleared(drawable: Drawable?) {
textView.setCompoundDrawablesWithIntrinsicBounds(null, drawable, null, null)
}
override fun onResourceReady(res: Drawable, transition: Transition<in Drawable>?) {
textView.setCompoundDrawablesWithIntrinsicBounds(null, res, null, null)
}
})
} }
} }

View File

@ -10,11 +10,11 @@ import org.mosad.teapod.databinding.ItemMediaBinding
import org.mosad.teapod.util.ItemMedia import org.mosad.teapod.util.ItemMedia
import java.util.* import java.util.*
class MediaItemAdapter(private val media: List<ItemMedia>) : RecyclerView.Adapter<MediaItemAdapter.MediaViewHolder>(), Filterable { class MediaItemAdapter(private val initMedia: List<ItemMedia>) : RecyclerView.Adapter<MediaItemAdapter.MediaViewHolder>(), Filterable {
var onItemClick: ((Int, Int) -> Unit)? = null var onItemClick: ((Int, Int) -> Unit)? = null
private val filter = MediaFilter() private val filter = MediaFilter()
private var filteredMedia = media.map { it.copy() } private var filteredMedia = initMedia.map { it.copy() }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaItemAdapter.MediaViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaItemAdapter.MediaViewHolder {
return MediaViewHolder(ItemMediaBinding.inflate(LayoutInflater.from(parent.context), parent, false)) return MediaViewHolder(ItemMediaBinding.inflate(LayoutInflater.from(parent.context), parent, false))
@ -35,6 +35,10 @@ class MediaItemAdapter(private val media: List<ItemMedia>) : RecyclerView.Adapte
return filter return filter
} }
fun updateMediaList(mediaList: List<ItemMedia>) {
filteredMedia = mediaList
}
inner class MediaViewHolder(val binding: ItemMediaBinding) : RecyclerView.ViewHolder(binding.root) { inner class MediaViewHolder(val binding: ItemMediaBinding) : RecyclerView.ViewHolder(binding.root) {
init { init {
binding.root.setOnClickListener { binding.root.setOnClickListener {
@ -49,9 +53,9 @@ class MediaItemAdapter(private val media: List<ItemMedia>) : RecyclerView.Adapte
val results = FilterResults() val results = FilterResults()
val filteredList = if (filterTerm.isEmpty()) { val filteredList = if (filterTerm.isEmpty()) {
media initMedia
} else { } else {
media.filter { initMedia.filter {
it.title.toLowerCase(Locale.ROOT).contains(filterTerm) it.title.toLowerCase(Locale.ROOT).contains(filterTerm)
} }
} }

View File

@ -55,7 +55,7 @@
android:layout_marginStart="5dp" android:layout_marginStart="5dp"
android:layout_marginTop="7dp" android:layout_marginTop="7dp"
android:layout_marginEnd="5dp" android:layout_marginEnd="5dp"
android:text="@string/info_about_dialog" android:text="@string/about_info"
android:textAlignment="center" /> android:textAlignment="center" />
<TextView <TextView

View File

@ -18,6 +18,97 @@
android:orientation="vertical"> android:orientation="vertical">
<LinearLayout <LinearLayout
android:id="@+id/linear_highlight"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="7dp">
<ImageView
android:id="@+id/image_highlight"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:contentDescription="@string/highlight_media"
app:layout_constraintDimensionRatio="H,16:9"
tools:src="@drawable/ic_launcher_background" />
<TextView
android:id="@+id/text_highlight_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="7dp"
android:text="@string/text_title_ex"
android:textAlignment="center"
android:textSize="16sp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="7dp"
android:gravity="center"
android:orientation="horizontal">
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<TextView
android:id="@+id/text_highlight_my_list"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/my_list"
android:textColor="?textSecondary"
android:textSize="12sp"
app:drawableTint="?buttonBackground"
app:drawableTopCompat="@drawable/ic_baseline_add_24" />
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<com.google.android.material.button.MaterialButton
android:id="@+id/button_play_highlight"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/button_play"
android:textAllCaps="false"
android:textColor="?themePrimary"
android:textSize="16sp"
app:backgroundTint="?buttonBackground"
app:icon="@drawable/ic_baseline_play_arrow_24"
app:iconGravity="textStart"
app:iconTint="?themePrimary" />
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
<TextView
android:id="@+id/text_highlight_info"
android:layout_width="64dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/info"
android:textColor="?textSecondary"
android:textSize="12sp"
app:drawableTint="?buttonBackground"
app:drawableTopCompat="@drawable/ic_baseline_info_24" />
<Space
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_weight="1" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/linear_my_list"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"
@ -45,6 +136,7 @@
</LinearLayout> </LinearLayout>
<LinearLayout <LinearLayout
android:id="@+id/linear_new_episodes"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical" android:orientation="vertical"

View File

@ -6,6 +6,7 @@
<string name="title_account">Account</string> <string name="title_account">Account</string>
<!-- home fragment --> <!-- home fragment -->
<string name="highlight_media">Highlight</string>
<string name="my_list">Meine Liste</string> <string name="my_list">Meine Liste</string>
<string name="new_episodes">Neue Episoden</string> <string name="new_episodes">Neue Episoden</string>
@ -24,13 +25,6 @@
<string name="account_login_desc">Zum bearbeiten tippen</string> <string name="account_login_desc">Zum bearbeiten tippen</string>
<string name="info">Info</string> <string name="info">Info</string>
<string name="info_about_desc">Version %1$s (%2$s)</string> <string name="info_about_desc">Version %1$s (%2$s)</string>
<string name="info_about_dialog">
Teapod ist eine inoffizielle App für Anime on Demand.
Sie wird unter den Bedingungen der GNU GPL 3 oder höher zur Verfügung gestellt.
\n\n
© 2020 seil0@mosad.xyz
</string>
<string name="licenses">Lizenzen</string>
<string name="settings">Einstellungen</string> <string name="settings">Einstellungen</string>
<string name="settings_secondary">Bevorzuge alternativen Stream</string> <string name="settings_secondary">Bevorzuge alternativen Stream</string>
<string name="settings_secondary_desc">Untertitle-Stream verwenden, sofern vorhanden</string> <string name="settings_secondary_desc">Untertitle-Stream verwenden, sofern vorhanden</string>
@ -41,6 +35,12 @@
<string name="theme_dark">Dunkel</string> <string name="theme_dark">Dunkel</string>
<!-- about fragment --> <!-- about fragment -->
<string name="about_info">
Teapod ist eine inoffizielle App für Anime on Demand.
Sie wird unter den Bedingungen der GNU GPL 3 oder höher zur Verfügung gestellt.
\n\n
© 2020 seil0@mosad.xyz
</string>
<string name="third_party_heading">Lizenzen von Drittanbietern</string> <string name="third_party_heading">Lizenzen von Drittanbietern</string>
<string name="third_party_component_desc">© %1$s %2$s unter %3$s</string> <string name="third_party_component_desc">© %1$s %2$s unter %3$s</string>

View File

@ -6,6 +6,7 @@
<string name="title_account">Account</string> <string name="title_account">Account</string>
<!-- home fragment --> <!-- home fragment -->
<string name="highlight_media">Highlight</string>
<string name="my_list">My list</string> <string name="my_list">My list</string>
<string name="new_episodes">New episodes</string> <string name="new_episodes">New episodes</string>
@ -33,12 +34,6 @@
<string name="info">Info</string> <string name="info">Info</string>
<string name="info_about" translatable="false">Teapod by @Seil0</string> <string name="info_about" translatable="false">Teapod by @Seil0</string>
<string name="info_about_desc">Version %1$s (%2$s)</string> <string name="info_about_desc">Version %1$s (%2$s)</string>
<string name="info_about_dialog">
Teapod is an unofficial app for anime on demand.
It is published under the terms and conditions of the GNU GPL 3 or later.
\n\n
© 2020 seil0@mosad.xyz</string>
<string name="licenses">Licenses</string>
<string name="settings">Settings</string> <string name="settings">Settings</string>
<string name="settings_secondary">Prefer secondary (sub) stream</string> <string name="settings_secondary">Prefer secondary (sub) stream</string>
<string name="settings_secondary_desc">Use the subtitles stream if present</string> <string name="settings_secondary_desc">Use the subtitles stream if present</string>
@ -49,8 +44,14 @@
<string name="theme_dark">Dark</string> <string name="theme_dark">Dark</string>
<!-- about fragment --> <!-- about fragment -->
<string name="teapod_repo" translatable="false">git.mosad.xyz/Seil0/teapod</string> <string name="about_info">
Teapod is an unofficial app for anime on demand.
It is published under the terms and conditions of the GNU GPL 3 or later.
\n\n
© 2020 seil0@mosad.xyz
</string>
<string name="tmdb_notice" translatable="false">This product uses the TMDb API but is not endorsed or certified by TMDb.</string> <string name="tmdb_notice" translatable="false">This product uses the TMDb API but is not endorsed or certified by TMDb.</string>
<string name="teapod_repo" translatable="false">git.mosad.xyz/Seil0/teapod</string>
<string name="third_party_heading">Third Party Licenses</string> <string name="third_party_heading">Third Party Licenses</string>
<string name="third_party_component_desc">© %1$s %2$s under %3$s</string> <string name="third_party_component_desc">© %1$s %2$s under %3$s</string>