rework MediaItemAdapter to use ItemMedia instead of Media

This allows us to get the media onClick directly from the AoDParser. Media inforamtion are now only stored in the parsers mediaList.
This commit is contained in:
Jannik 2020-10-16 19:56:08 +02:00
parent 2689c37af3
commit aeb74dcb29
Signed by: Seil0
GPG Key ID: E8459F3723C52C24
11 changed files with 110 additions and 90 deletions

View File

@ -11,7 +11,7 @@ android {
minSdkVersion 23 minSdkVersion 23
targetSdkVersion 30 targetSdkVersion 30
versionCode 1 versionCode 1
versionName "0.1-alpha2" versionName "0.1-alpha3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
resValue "string", "build_time", buildTime() resValue "string", "build_time", buildTime()

View File

@ -43,7 +43,6 @@ import org.mosad.teapod.ui.fragments.LibraryFragment
import org.mosad.teapod.ui.fragments.SearchFragment import org.mosad.teapod.ui.fragments.SearchFragment
import org.mosad.teapod.ui.fragments.LoadingFragment import org.mosad.teapod.ui.fragments.LoadingFragment
import org.mosad.teapod.util.StorageController import org.mosad.teapod.util.StorageController
import org.mosad.teapod.util.Media
import org.mosad.teapod.util.TMDBApiController import org.mosad.teapod.util.TMDBApiController
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -108,16 +107,18 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
} }
private fun load() { private fun load() {
// make sure credentials are set
EncryptedPreferences.readCredentials(this)
if (EncryptedPreferences.password.isEmpty()) showLoginDialog(true)
StorageController.load(this)
// running login and list in parallel does not bring any speed improvements // running login and list in parallel does not bring any speed improvements
val time = measureTimeMillis { val time = measureTimeMillis {
// try to login in, as most sites can only bee loaded once loged in // make sure credentials are set
if (!AoDParser().login()) showLoginDialog(false) EncryptedPreferences.readCredentials(this)
if (EncryptedPreferences.password.isEmpty()) {
showLoginDialog(true)
} else {
// try to login in, as most sites can only bee loaded once loged in
if (!AoDParser().login()) showLoginDialog(false)
}
StorageController.load(this)
// initially load all media // initially load all media
AoDParser().listAnimes() AoDParser().listAnimes()
@ -133,7 +134,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
* The loading and media fragment are not stored in activeBaseFragment, * The loading and media fragment are not stored in activeBaseFragment,
* as the don't replace a fragment but are added on top of one. * as the don't replace a fragment but are added on top of one.
*/ */
fun showMediaFragment(media: Media) = GlobalScope.launch { fun showMediaFragment(mediaId: Int) = GlobalScope.launch {
val loadingFragment = LoadingFragment() val loadingFragment = LoadingFragment()
supportFragmentManager.commit { supportFragmentManager.commit {
add(R.id.nav_host_fragment, loadingFragment, "MediaFragment") add(R.id.nav_host_fragment, loadingFragment, "MediaFragment")
@ -141,8 +142,8 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
} }
// load the streams for the selected media // load the streams for the selected media
media.episodes = AoDParser().loadStreams(media) val media = AoDParser().getMediaById(mediaId)
val tmdb = TMDBApiController().search(media.title, media.type) val tmdb = TMDBApiController().search(media.info.title, media.type)
val mediaFragment = MediaFragment(media, tmdb) val mediaFragment = MediaFragment(media, tmdb)
supportFragmentManager.commit { supportFragmentManager.commit {

View File

@ -8,6 +8,7 @@ import org.jsoup.Jsoup
import org.mosad.teapod.preferences.EncryptedPreferences import org.mosad.teapod.preferences.EncryptedPreferences
import org.mosad.teapod.util.DataTypes.MediaType import org.mosad.teapod.util.DataTypes.MediaType
import org.mosad.teapod.util.Episode import org.mosad.teapod.util.Episode
import org.mosad.teapod.util.ItemMedia
import org.mosad.teapod.util.Media import org.mosad.teapod.util.Media
import java.io.IOException import java.io.IOException
import java.util.* import java.util.*
@ -29,6 +30,7 @@ class AoDParser {
private var loginSuccess = false private var loginSuccess = false
val mediaList = arrayListOf<Media>() val mediaList = arrayListOf<Media>()
val itemMediaList = arrayListOf<ItemMedia>()
} }
fun login(): Boolean = runBlocking { fun login(): Boolean = runBlocking {
@ -90,16 +92,18 @@ class AoDParser {
} else { } else {
MediaType.MOVIE MediaType.MOVIE
} }
val mediaTitle = it.select("h3.animebox-title").text()
val mediaLink = it.select("p.animebox-link").select("a").attr("href")
val mediaImage = it.select("p.animebox-image").select("img").attr("src")
val mediaShortText = it.select("p.animebox-shorttext").text()
val mediaId = mediaLink.substringAfterLast("/").toInt()
val media = Media( itemMediaList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
it.select("h3.animebox-title").text(), mediaList.add(Media(mediaId, mediaLink, type).apply {
it.select("p.animebox-link").select("a").attr("href"), info.title = mediaTitle
type info.posterUrl = mediaImage
) info.shortDesc = mediaShortText
media.info.posterLink = it.select("p.animebox-image").select("img").attr("src") })
media.info.shortDesc = it.select("p.animebox-shorttext").text()
mediaList.add(media)
} }
Log.i(javaClass.name, "Total library size is: ${mediaList.size}") Log.i(javaClass.name, "Total library size is: ${mediaList.size}")
@ -108,20 +112,26 @@ class AoDParser {
} }
} }
fun getMediaById(mediaId: Int): Media {
val media = mediaList.first { it.id == mediaId }
if (media.episodes.isEmpty()) {
loadStreams(media)
}
return media
}
/** /**
* load streams for the media path * load streams for the media path, movies have one episode
* @param media is used as call ba reference
*/ */
fun loadStreams(media: Media): List<Episode> = runBlocking { private fun loadStreams(media: Media) = runBlocking {
if (sessionCookies.isEmpty()) login() if (sessionCookies.isEmpty()) login()
if (!loginSuccess) { if (!loginSuccess) {
Log.w(javaClass.name, "Login, was not successful.") Log.w(javaClass.name, "Login, was not successful.")
return@runBlocking listOf() return@runBlocking
}
// if the episodes list is not empty it was loaded before
if (media.episodes.isNotEmpty()) {
return@runBlocking media.episodes
} }
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
@ -147,22 +157,24 @@ class AoDParser {
} }
// parse additional information for tv shows // parse additional information for tv shows
val episodes = if (media.type == MediaType.TVSHOW) { media.episodes = when (media.type) {
res.select("div.three-box-container > div.episodebox").map { episodebox -> MediaType.MOVIE -> listOf(Episode())
val episodeId = episodebox.select("div.flip-front").attr("id").substringAfter("-").toInt() MediaType.TVSHOW -> {
val episodeShortDesc = episodebox.select("p.episodebox-shorttext").text() res.select("div.three-box-container > div.episodebox").map { episodebox ->
val episodeWatched = episodebox.select("div.episodebox-icons > div").hasClass("status-icon-orange") val episodeId = episodebox.select("div.flip-front").attr("id").substringAfter("-").toInt()
val episodeWatchedCallback = episodebox.select("input.streamstarter_html5").eachAttr("data-playlist").first() val episodeShortDesc = episodebox.select("p.episodebox-shorttext").text()
val episodeWatched = episodebox.select("div.episodebox-icons > div").hasClass("status-icon-orange")
val episodeWatchedCallback = episodebox.select("input.streamstarter_html5").eachAttr("data-playlist").first()
Episode( Episode(
id = episodeId, id = episodeId,
shortDesc = episodeShortDesc, shortDesc = episodeShortDesc,
watched = episodeWatched, watched = episodeWatched,
watchedCallback = episodeWatchedCallback watchedCallback = episodeWatchedCallback
) )
}
} }
} else { MediaType.OTHER -> listOf()
listOf(Episode())
} }
if (csrfToken.isEmpty()) { if (csrfToken.isEmpty()) {
@ -173,19 +185,17 @@ class AoDParser {
// TODO has attr data-lag (ger or jap) // TODO has attr data-lag (ger or jap)
val playlists = res.select("input.streamstarter_html5").eachAttr("data-playlist") val playlists = res.select("input.streamstarter_html5").eachAttr("data-playlist")
return@withContext if (playlists.size > 0) { if (playlists.size > 0) {
loadStreamInfo(playlists.first(), csrfToken, media.type, episodes) loadPlaylist(playlists.first(), csrfToken, media.type, media.episodes)
} else {
listOf()
} }
} }
} }
/** /**
* load the playlist path and parse it, read the stream info from json * load the playlist path and parse it, read the stream info from json
* @param episodes is used as call ba reference, additionally it is passed a return value * @param episodes is used as call ba reference
*/ */
private fun loadStreamInfo(playlistPath: String, csrfToken: String, type: MediaType, episodes: List<Episode>): List<Episode> = runBlocking { private fun loadPlaylist(playlistPath: String, csrfToken: String, type: MediaType, episodes: List<Episode>) = runBlocking {
withContext(Dispatchers.Default) { withContext(Dispatchers.Default) {
val headers = mutableMapOf( val headers = mutableMapOf(
Pair("Accept", "application/json, text/javascript, */*; q=0.01"), Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
@ -232,7 +242,7 @@ class AoDParser {
episodes.first { it.id == episodeId.asInt }.apply { episodes.first { it.id == episodeId.asInt }.apply {
this.title = episodeTitle this.title = episodeTitle
this.posterLink = episodePoster this.posterUrl = episodePoster
this.streamUrl = episodeStream this.streamUrl = episodeStream
this.description = episodeDescription this.description = episodeDescription
this.number = episodeNumber this.number = episodeNumber
@ -245,8 +255,6 @@ class AoDParser {
Log.e(javaClass.name, "Wrong Type, please report this issue.") Log.e(javaClass.name, "Wrong Type, please report this issue.")
} }
} }
return@withContext episodes
} }
} }

View File

@ -50,15 +50,15 @@ class HomeFragment : Fragment() {
// TODO recreating the adapter on list change is not a good solution // TODO recreating the adapter on list change is not a good solution
fun updateMyListMedia() { fun updateMyListMedia() {
val myListMedia = StorageController.myList.map { listElement -> val myListMedia = StorageController.myList.map { elementId ->
AoDParser.mediaList.first { AoDParser.itemMediaList.first {
listElement == it.link elementId == it.id
} }
} }
adapter = MediaItemAdapter(myListMedia) adapter = MediaItemAdapter(myListMedia)
adapter.onItemClick = { media, _ -> adapter.onItemClick = { mediaId, _ ->
(activity as MainActivity).showMediaFragment(media) (activity as MainActivity).showMediaFragment(mediaId)
} }
recycler_my_list.adapter = adapter recycler_my_list.adapter = adapter

View File

@ -33,9 +33,9 @@ class LibraryFragment : Fragment() {
// create and set the adapter, needs context // create and set the adapter, needs context
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
context?.let { context?.let {
adapter = MediaItemAdapter(AoDParser.mediaList) adapter = MediaItemAdapter(AoDParser.itemMediaList)
adapter.onItemClick = { media, _ -> adapter.onItemClick = { mediaId, _ ->
(activity as MainActivity).showMediaFragment(media) (activity as MainActivity).showMediaFragment(mediaId)
} }
recycler_media_library.adapter = adapter recycler_media_library.adapter = adapter

View File

@ -45,8 +45,8 @@ class MediaFragment(private val media: Media, private val tmdb: TMDBResponse) :
*/ */
private fun initGUI() { private fun initGUI() {
// generic gui // generic gui
val backdropUrl = if (tmdb.backdropUrl.isNotEmpty()) tmdb.backdropUrl else media.info.posterLink val backdropUrl = if (tmdb.backdropUrl.isNotEmpty()) tmdb.backdropUrl else media.info.posterUrl
val posterUrl = if (tmdb.posterUrl.isNotEmpty()) tmdb.posterUrl else media.info.posterLink val posterUrl = if (tmdb.posterUrl.isNotEmpty()) tmdb.posterUrl else media.info.posterUrl
Glide.with(requireContext()).load(backdropUrl) Glide.with(requireContext()).load(backdropUrl)
.apply(RequestOptions.placeholderOf(ColorDrawable(Color.DKGRAY))) .apply(RequestOptions.placeholderOf(ColorDrawable(Color.DKGRAY)))
@ -56,11 +56,11 @@ class MediaFragment(private val media: Media, private val tmdb: TMDBResponse) :
Glide.with(requireContext()).load(posterUrl) Glide.with(requireContext()).load(posterUrl)
.into(image_poster) .into(image_poster)
text_title.text = media.title text_title.text = media.info.title
text_year.text = media.info.year.toString() text_year.text = media.info.year.toString()
text_age.text = media.info.age.toString() text_age.text = media.info.age.toString()
text_overview.text = media.info.shortDesc text_overview.text = media.info.shortDesc
if (StorageController.myList.contains(media.link)) { if (StorageController.myList.contains(media.id)) {
Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(image_my_list_action) Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(image_my_list_action)
} else { } else {
Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(image_my_list_action) Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(image_my_list_action)
@ -96,13 +96,14 @@ class MediaFragment(private val media: Media, private val tmdb: TMDBResponse) :
// add or remove media from myList // add or remove media from myList
linear_my_list_action.setOnClickListener { linear_my_list_action.setOnClickListener {
if (StorageController.myList.contains(media.link)) { if (StorageController.myList.contains(media.id)) {
StorageController.myList.remove(media.link) StorageController.myList.remove(media.id)
Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(image_my_list_action) Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(image_my_list_action)
} else { } else {
StorageController.myList.add(media.link) StorageController.myList.add(media.id)
Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(image_my_list_action) Glide.with(requireContext()).load(R.drawable.ic_baseline_check_24).into(image_my_list_action)
} }
StorageController.saveMyList(requireContext())
// notify home fragment on change // notify home fragment on change
parentFragmentManager.findFragmentByTag("HomeFragment")?.let { parentFragmentManager.findFragmentByTag("HomeFragment")?.let {

View File

@ -33,10 +33,10 @@ class SearchFragment : Fragment() {
// create and set the adapter, needs context // create and set the adapter, needs context
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
context?.let { context?.let {
adapter = MediaItemAdapter(AoDParser.mediaList) adapter = MediaItemAdapter(AoDParser.itemMediaList)
adapter!!.onItemClick = { media, _ -> adapter!!.onItemClick = { mediaId, _ ->
search_text.clearFocus() search_text.clearFocus()
(activity as MainActivity).showMediaFragment(media) (activity as MainActivity).showMediaFragment(mediaId)
} }
recycler_media_search.adapter = adapter recycler_media_search.adapter = adapter

View File

@ -8,21 +8,31 @@ class DataTypes {
} }
} }
// TODO the episodes workflow could use a clean up/rework /**
data class Media( * this class is used to represent the item media
* it is uses in the ItemMediaAdapter (RecyclerView)
*/
data class ItemMedia(
val id: Int,
val title: String, val title: String,
val posterUrl: String
)
/**
* TODO the episodes workflow could use a clean up/rework
*/
data class Media(
val id: Int,
val link: String, val link: String,
val type: DataTypes.MediaType, val type: DataTypes.MediaType,
val info: Info = Info(), val info: Info = Info(),
var episodes: List<Episode> = listOf() var episodes: List<Episode> = listOf()
) { )
override fun toString(): String {
return title
}
}
data class Info( data class Info(
var posterLink: String = "", var title: String = "",
var posterUrl: String = "",
var shortDesc: String = "", var shortDesc: String = "",
var description: String = "", var description: String = "",
var year: Int = 0, var year: Int = 0,
@ -34,7 +44,7 @@ data class Episode(
val id: Int = 0, val id: Int = 0,
var title: String = "", var title: String = "",
var streamUrl: String = "", var streamUrl: String = "",
var posterLink: String = "", var posterUrl: String = "",
var description: String = "", var description: String = "",
var shortDesc: String = "", var shortDesc: String = "",
var number: Int = 0, var number: Int = 0,

View File

@ -14,7 +14,7 @@ object StorageController {
private const val fileNameMyList = "my_list.json" private const val fileNameMyList = "my_list.json"
val myList = ArrayList<String>() // a list of saved links val myList = ArrayList<Int>() // a list of saved mediaIds
fun load(context: Context) { fun load(context: Context) {
val file = File(context.filesDir, fileNameMyList) val file = File(context.filesDir, fileNameMyList)
@ -23,7 +23,7 @@ object StorageController {
myList.clear() myList.clear()
myList.addAll( myList.addAll(
GsonBuilder().create().fromJson(file.readText(), ArrayList<String>().javaClass) GsonBuilder().create().fromJson(file.readText(), ArrayList<Int>().javaClass)
) )
} }

View File

@ -33,8 +33,8 @@ class EpisodeItemAdapter(private val episodes: List<Episode>) : RecyclerView.Ada
) )
holder.view.text_episode_desc.text = episodes[position].shortDesc holder.view.text_episode_desc.text = episodes[position].shortDesc
if (episodes[position].posterLink.isNotEmpty()) { if (episodes[position].posterUrl.isNotEmpty()) {
Glide.with(context).load(episodes[position].posterLink) Glide.with(context).load(episodes[position].posterUrl)
.apply(RequestOptions.bitmapTransform(RoundedCornersTransformation(10, 0))) .apply(RequestOptions.bitmapTransform(RoundedCornersTransformation(10, 0)))
.into(holder.view.image_episode) .into(holder.view.image_episode)
} }

View File

@ -9,12 +9,12 @@ import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.item_media.view.* import kotlinx.android.synthetic.main.item_media.view.*
import org.mosad.teapod.R import org.mosad.teapod.R
import org.mosad.teapod.util.Media import org.mosad.teapod.util.ItemMedia
import java.util.* import java.util.*
class MediaItemAdapter(private val media: List<Media>) : RecyclerView.Adapter<MediaItemAdapter.ViewHolder>(), Filterable { class MediaItemAdapter(private val media: List<ItemMedia>) : RecyclerView.Adapter<MediaItemAdapter.ViewHolder>(), Filterable {
var onItemClick: ((Media, 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 = media.map { it.copy() }
@ -27,7 +27,7 @@ class MediaItemAdapter(private val media: List<Media>) : RecyclerView.Adapter<Me
override fun onBindViewHolder(holder: MediaItemAdapter.ViewHolder, position: Int) { override fun onBindViewHolder(holder: MediaItemAdapter.ViewHolder, position: Int) {
holder.view.apply { holder.view.apply {
text_title.text = filteredMedia[position].title text_title.text = filteredMedia[position].title
Glide.with(context).load(filteredMedia[position].info.posterLink).into(image_poster) Glide.with(context).load(filteredMedia[position].posterUrl).into(image_poster)
} }
} }
@ -42,7 +42,7 @@ class MediaItemAdapter(private val media: List<Media>) : RecyclerView.Adapter<Me
inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) { inner class ViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
init { init {
view.setOnClickListener { view.setOnClickListener {
onItemClick?.invoke(filteredMedia[adapterPosition], adapterPosition) onItemClick?.invoke(filteredMedia[adapterPosition].id, adapterPosition)
} }
} }
} }
@ -71,7 +71,7 @@ class MediaItemAdapter(private val media: List<Media>) : RecyclerView.Adapter<Me
* suppressing unchecked cast is safe, since we only use Media * suppressing unchecked cast is safe, since we only use Media
*/ */
override fun publishResults(constraint: CharSequence?, results: FilterResults?) { override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
filteredMedia = results?.values as List<Media> filteredMedia = results?.values as List<ItemMedia>
notifyDataSetChanged() notifyDataSetChanged()
} }
} }