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:
parent
2689c37af3
commit
aeb74dcb29
|
@ -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()
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue