diff --git a/app/build.gradle b/app/build.gradle index e271d2a..97cd59e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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() diff --git a/app/src/main/java/org/mosad/teapod/MainActivity.kt b/app/src/main/java/org/mosad/teapod/MainActivity.kt index 7f69f8a..daa9f30 100644 --- a/app/src/main/java/org/mosad/teapod/MainActivity.kt +++ b/app/src/main/java/org/mosad/teapod/MainActivity.kt @@ -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 { diff --git a/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt b/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt index 1904398..5c5b493 100644 --- a/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt +++ b/app/src/main/java/org/mosad/teapod/parser/AoDParser.kt @@ -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() + val itemMediaList = arrayListOf() } 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 = 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): List = runBlocking { + private fun loadPlaylist(playlistPath: String, csrfToken: String, type: MediaType, episodes: List) = 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 } } diff --git a/app/src/main/java/org/mosad/teapod/ui/fragments/HomeFragment.kt b/app/src/main/java/org/mosad/teapod/ui/fragments/HomeFragment.kt index fe2050a..9371c48 100644 --- a/app/src/main/java/org/mosad/teapod/ui/fragments/HomeFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/fragments/HomeFragment.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/ui/fragments/LibraryFragment.kt b/app/src/main/java/org/mosad/teapod/ui/fragments/LibraryFragment.kt index a6fdffd..0caeeff 100644 --- a/app/src/main/java/org/mosad/teapod/ui/fragments/LibraryFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/fragments/LibraryFragment.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/ui/fragments/MediaFragment.kt b/app/src/main/java/org/mosad/teapod/ui/fragments/MediaFragment.kt index 4324c5f..b6622b3 100644 --- a/app/src/main/java/org/mosad/teapod/ui/fragments/MediaFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/fragments/MediaFragment.kt @@ -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 { diff --git a/app/src/main/java/org/mosad/teapod/ui/fragments/SearchFragment.kt b/app/src/main/java/org/mosad/teapod/ui/fragments/SearchFragment.kt index 0053d6c..b6f87aa 100644 --- a/app/src/main/java/org/mosad/teapod/ui/fragments/SearchFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/fragments/SearchFragment.kt @@ -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 diff --git a/app/src/main/java/org/mosad/teapod/util/DataTypes.kt b/app/src/main/java/org/mosad/teapod/util/DataTypes.kt index e6390ae..ff51e1f 100644 --- a/app/src/main/java/org/mosad/teapod/util/DataTypes.kt +++ b/app/src/main/java/org/mosad/teapod/util/DataTypes.kt @@ -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 = 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, diff --git a/app/src/main/java/org/mosad/teapod/util/StorageController.kt b/app/src/main/java/org/mosad/teapod/util/StorageController.kt index f11592d..b3d2836 100644 --- a/app/src/main/java/org/mosad/teapod/util/StorageController.kt +++ b/app/src/main/java/org/mosad/teapod/util/StorageController.kt @@ -14,7 +14,7 @@ object StorageController { private const val fileNameMyList = "my_list.json" - val myList = ArrayList() // a list of saved links + val myList = ArrayList() // 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().javaClass) + GsonBuilder().create().fromJson(file.readText(), ArrayList().javaClass) ) } diff --git a/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt b/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt index eedc150..d079bf8 100644 --- a/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt +++ b/app/src/main/java/org/mosad/teapod/util/adapter/EpisodeItemAdapter.kt @@ -33,8 +33,8 @@ class EpisodeItemAdapter(private val episodes: List) : 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) } diff --git a/app/src/main/java/org/mosad/teapod/util/adapter/MediaItemAdapter.kt b/app/src/main/java/org/mosad/teapod/util/adapter/MediaItemAdapter.kt index ccb65af..ccb5eee 100644 --- a/app/src/main/java/org/mosad/teapod/util/adapter/MediaItemAdapter.kt +++ b/app/src/main/java/org/mosad/teapod/util/adapter/MediaItemAdapter.kt @@ -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) : RecyclerView.Adapter(), Filterable { +class MediaItemAdapter(private val media: List) : RecyclerView.Adapter(), 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) : RecyclerView.Adapter) : RecyclerView.Adapter) : RecyclerView.Adapter + filteredMedia = results?.values as List notifyDataSetChanged() } }