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
targetSdkVersion 30
versionCode 1
versionName "0.1-alpha2"
versionName "0.1-alpha3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
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.LoadingFragment
import org.mosad.teapod.util.StorageController
import org.mosad.teapod.util.Media
import org.mosad.teapod.util.TMDBApiController
import kotlin.system.measureTimeMillis
@ -108,16 +107,18 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
}
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
val time = measureTimeMillis {
// try to login in, as most sites can only bee loaded once loged in
if (!AoDParser().login()) showLoginDialog(false)
// make sure credentials are set
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
AoDParser().listAnimes()
@ -133,7 +134,7 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
* The loading and media fragment are not stored in activeBaseFragment,
* 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()
supportFragmentManager.commit {
add(R.id.nav_host_fragment, loadingFragment, "MediaFragment")
@ -141,8 +142,8 @@ class MainActivity : AppCompatActivity(), BottomNavigationView.OnNavigationItemS
}
// load the streams for the selected media
media.episodes = AoDParser().loadStreams(media)
val tmdb = TMDBApiController().search(media.title, media.type)
val media = AoDParser().getMediaById(mediaId)
val tmdb = TMDBApiController().search(media.info.title, media.type)
val mediaFragment = MediaFragment(media, tmdb)
supportFragmentManager.commit {

View File

@ -8,6 +8,7 @@ import org.jsoup.Jsoup
import org.mosad.teapod.preferences.EncryptedPreferences
import org.mosad.teapod.util.DataTypes.MediaType
import org.mosad.teapod.util.Episode
import org.mosad.teapod.util.ItemMedia
import org.mosad.teapod.util.Media
import java.io.IOException
import java.util.*
@ -29,6 +30,7 @@ class AoDParser {
private var loginSuccess = false
val mediaList = arrayListOf<Media>()
val itemMediaList = arrayListOf<ItemMedia>()
}
fun login(): Boolean = runBlocking {
@ -90,16 +92,18 @@ class AoDParser {
} else {
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(
it.select("h3.animebox-title").text(),
it.select("p.animebox-link").select("a").attr("href"),
type
)
media.info.posterLink = it.select("p.animebox-image").select("img").attr("src")
media.info.shortDesc = it.select("p.animebox-shorttext").text()
mediaList.add(media)
itemMediaList.add(ItemMedia(mediaId, mediaTitle, mediaImage))
mediaList.add(Media(mediaId, mediaLink, type).apply {
info.title = mediaTitle
info.posterUrl = mediaImage
info.shortDesc = mediaShortText
})
}
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 (!loginSuccess) {
Log.w(javaClass.name, "Login, was not successful.")
return@runBlocking listOf()
}
// if the episodes list is not empty it was loaded before
if (media.episodes.isNotEmpty()) {
return@runBlocking media.episodes
return@runBlocking
}
withContext(Dispatchers.Default) {
@ -147,22 +157,24 @@ class AoDParser {
}
// parse additional information for tv shows
val episodes = if (media.type == MediaType.TVSHOW) {
res.select("div.three-box-container > div.episodebox").map { episodebox ->
val episodeId = episodebox.select("div.flip-front").attr("id").substringAfter("-").toInt()
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()
media.episodes = when (media.type) {
MediaType.MOVIE -> listOf(Episode())
MediaType.TVSHOW -> {
res.select("div.three-box-container > div.episodebox").map { episodebox ->
val episodeId = episodebox.select("div.flip-front").attr("id").substringAfter("-").toInt()
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(
id = episodeId,
shortDesc = episodeShortDesc,
watched = episodeWatched,
watchedCallback = episodeWatchedCallback
)
Episode(
id = episodeId,
shortDesc = episodeShortDesc,
watched = episodeWatched,
watchedCallback = episodeWatchedCallback
)
}
}
} else {
listOf(Episode())
MediaType.OTHER -> listOf()
}
if (csrfToken.isEmpty()) {
@ -173,19 +185,17 @@ class AoDParser {
// TODO has attr data-lag (ger or jap)
val playlists = res.select("input.streamstarter_html5").eachAttr("data-playlist")
return@withContext if (playlists.size > 0) {
loadStreamInfo(playlists.first(), csrfToken, media.type, episodes)
} else {
listOf()
if (playlists.size > 0) {
loadPlaylist(playlists.first(), csrfToken, media.type, media.episodes)
}
}
}
/**
* 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) {
val headers = mutableMapOf(
Pair("Accept", "application/json, text/javascript, */*; q=0.01"),
@ -232,7 +242,7 @@ class AoDParser {
episodes.first { it.id == episodeId.asInt }.apply {
this.title = episodeTitle
this.posterLink = episodePoster
this.posterUrl = episodePoster
this.streamUrl = episodeStream
this.description = episodeDescription
this.number = episodeNumber
@ -245,8 +255,6 @@ class AoDParser {
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
fun updateMyListMedia() {
val myListMedia = StorageController.myList.map { listElement ->
AoDParser.mediaList.first {
listElement == it.link
val myListMedia = StorageController.myList.map { elementId ->
AoDParser.itemMediaList.first {
elementId == it.id
}
}
adapter = MediaItemAdapter(myListMedia)
adapter.onItemClick = { media, _ ->
(activity as MainActivity).showMediaFragment(media)
adapter.onItemClick = { mediaId, _ ->
(activity as MainActivity).showMediaFragment(mediaId)
}
recycler_my_list.adapter = adapter

View File

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

View File

@ -45,8 +45,8 @@ class MediaFragment(private val media: Media, private val tmdb: TMDBResponse) :
*/
private fun initGUI() {
// generic gui
val backdropUrl = if (tmdb.backdropUrl.isNotEmpty()) tmdb.backdropUrl else media.info.posterLink
val posterUrl = if (tmdb.posterUrl.isNotEmpty()) tmdb.posterUrl 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.posterUrl
Glide.with(requireContext()).load(backdropUrl)
.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)
.into(image_poster)
text_title.text = media.title
text_title.text = media.info.title
text_year.text = media.info.year.toString()
text_age.text = media.info.age.toString()
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)
} else {
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
linear_my_list_action.setOnClickListener {
if (StorageController.myList.contains(media.link)) {
StorageController.myList.remove(media.link)
if (StorageController.myList.contains(media.id)) {
StorageController.myList.remove(media.id)
Glide.with(requireContext()).load(R.drawable.ic_baseline_add_24).into(image_my_list_action)
} 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)
}
StorageController.saveMyList(requireContext())
// notify home fragment on change
parentFragmentManager.findFragmentByTag("HomeFragment")?.let {

View File

@ -33,10 +33,10 @@ class SearchFragment : Fragment() {
// create and set the adapter, needs context
withContext(Dispatchers.Main) {
context?.let {
adapter = MediaItemAdapter(AoDParser.mediaList)
adapter!!.onItemClick = { media, _ ->
adapter = MediaItemAdapter(AoDParser.itemMediaList)
adapter!!.onItemClick = { mediaId, _ ->
search_text.clearFocus()
(activity as MainActivity).showMediaFragment(media)
(activity as MainActivity).showMediaFragment(mediaId)
}
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 posterUrl: String
)
/**
* TODO the episodes workflow could use a clean up/rework
*/
data class Media(
val id: Int,
val link: String,
val type: DataTypes.MediaType,
val info: Info = Info(),
var episodes: List<Episode> = listOf()
) {
override fun toString(): String {
return title
}
}
)
data class Info(
var posterLink: String = "",
var title: String = "",
var posterUrl: String = "",
var shortDesc: String = "",
var description: String = "",
var year: Int = 0,
@ -34,7 +44,7 @@ data class Episode(
val id: Int = 0,
var title: String = "",
var streamUrl: String = "",
var posterLink: String = "",
var posterUrl: String = "",
var description: String = "",
var shortDesc: String = "",
var number: Int = 0,

View File

@ -14,7 +14,7 @@ object StorageController {
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) {
val file = File(context.filesDir, fileNameMyList)
@ -23,7 +23,7 @@ object StorageController {
myList.clear()
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
if (episodes[position].posterLink.isNotEmpty()) {
Glide.with(context).load(episodes[position].posterLink)
if (episodes[position].posterUrl.isNotEmpty()) {
Glide.with(context).load(episodes[position].posterUrl)
.apply(RequestOptions.bitmapTransform(RoundedCornersTransformation(10, 0)))
.into(holder.view.image_episode)
}

View File

@ -9,12 +9,12 @@ import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import kotlinx.android.synthetic.main.item_media.view.*
import org.mosad.teapod.R
import org.mosad.teapod.util.Media
import org.mosad.teapod.util.ItemMedia
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 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) {
holder.view.apply {
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) {
init {
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
*/
override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
filteredMedia = results?.values as List<Media>
filteredMedia = results?.values as List<ItemMedia>
notifyDataSetChanged()
}
}