add playheads to crunchyroll parser
* show watched icon, if episode has been fully watched * add seasonTag to browse()
This commit is contained in:
		| @ -14,6 +14,7 @@ import kotlinx.serialization.json.Json | ||||
| import kotlinx.serialization.json.buildJsonObject | ||||
| import kotlinx.serialization.json.put | ||||
| import org.mosad.teapod.preferences.Preferences | ||||
| import org.mosad.teapod.util.concatenate | ||||
| import java.util.* | ||||
|  | ||||
| private val json = Json { ignoreUnknownKeys = true } | ||||
| @ -179,9 +180,21 @@ object Crunchyroll { | ||||
|      * | ||||
|      * @return A **[BrowseResult]** object is returned. | ||||
|      */ | ||||
|     suspend fun browse(sortBy: SortBy = SortBy.ALPHABETICAL, start: Int = 0, n: Int = 10): BrowseResult { | ||||
|     suspend fun browse( | ||||
|         sortBy: SortBy = SortBy.ALPHABETICAL, | ||||
|         seasonTag: String = "", | ||||
|         start: Int = 0, | ||||
|         n: Int = 10 | ||||
|     ): BrowseResult { | ||||
|         val browseEndpoint = "/content/v1/browse" | ||||
|         val parameters = listOf("sort_by" to sortBy.str, "start" to start, "n" to n) | ||||
|         val noneOptParams = listOf("sort_by" to sortBy.str, "start" to start, "n" to n) | ||||
|  | ||||
|         // if a season tag is present add it to the parameters | ||||
|         val parameters = if (seasonTag.isEmpty()) { | ||||
|             concatenate(noneOptParams, listOf("season_tag" to seasonTag)) | ||||
|         } else { | ||||
|             noneOptParams | ||||
|         } | ||||
|  | ||||
|         val result = request(browseEndpoint, parameters) | ||||
|         val browseResult = result.component1()?.obj()?.let { | ||||
| @ -216,7 +229,7 @@ object Crunchyroll { | ||||
|      * Note: episode objects are currently not supported | ||||
|      * | ||||
|      * @param objects The object IDs as list of Strings | ||||
|      * @return A Collection of Panels | ||||
|      * @return A **[Collection]** of Panels | ||||
|      */ | ||||
|     suspend fun objects(objects: List<String>): Collection { | ||||
|         val episodesEndpoint = "/cms/v2/DE/M3/crunchyroll/objects/${objects.joinToString(",")}" | ||||
| @ -228,7 +241,6 @@ object Crunchyroll { | ||||
|         ) | ||||
|  | ||||
|         val result = request(episodesEndpoint, parameters) | ||||
|         println(result.component1()?.obj()?.toString()) | ||||
|  | ||||
|         return result.component1()?.obj()?.let { | ||||
|             json.decodeFromString(it.toString()) | ||||
| @ -304,7 +316,7 @@ object Crunchyroll { | ||||
|      * Check if a media is in the user's watchlist. | ||||
|      * | ||||
|      * @param seriesId The crunchyroll series id of the media to check | ||||
|      * @return Boolean: ture if it was found, else false | ||||
|      * @return **[Boolean]**: ture if it was found, else false | ||||
|      */ | ||||
|     suspend fun isWatchlist(seriesId: String): Boolean { | ||||
|         val watchlistSeriesEndpoint = "/content/v1/watchlist/$accountID/$seriesId" | ||||
| @ -345,16 +357,34 @@ object Crunchyroll { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * TODO | ||||
|      * Get playhead information for all episodes in episodeIDs. | ||||
|      * The Information returned contains the playhead position, watched state | ||||
|      * and last modified date. | ||||
|      * | ||||
|      * @param episodeIDs A **[List]** of episodes IDs as strings. | ||||
|      * @return A **[Map]**<String, **[PlayheadObject]**> containing playback info. | ||||
|      */ | ||||
|     suspend fun playhead() { | ||||
|         // implement | ||||
|     suspend fun playheads(episodeIDs: List<String>): PlayheadsMap { | ||||
|         val playheadsEndpoint = "/content/v1/playheads/$accountID/${episodeIDs.joinToString(",")}" | ||||
|         val parameters = listOf("locale" to locale) | ||||
|  | ||||
|         val result = request(playheadsEndpoint, parameters) | ||||
|  | ||||
|         return result.component1()?.obj()?.let { | ||||
|             json.decodeFromString(it.toString()) | ||||
|         } ?: emptyMap() | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * Listing functions: watchlist (list), up_next_account | ||||
|      */ | ||||
|  | ||||
|     /** | ||||
|      * List items present in the watchlist. | ||||
|      * | ||||
|      * @param n Number of items to return, defaults to 20. | ||||
|      * @return A **[Watchlist]** containing up to n **[Item]**. | ||||
|      */ | ||||
|     suspend fun watchlist(n: Int = 20): Watchlist { | ||||
|         val watchlistEndpoint = "/content/v1/$accountID/watchlist" | ||||
|         val parameters = listOf("locale" to locale, "n" to n) | ||||
| @ -369,20 +399,19 @@ object Crunchyroll { | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * TODO | ||||
|      * List the next up episodes for the logged in account. | ||||
|      * | ||||
|      * @param n Number of items to return, defaults to 20. | ||||
|      * @return A **[ContinueWatchingList]** containing up to n **[ContinueWatchingItem]**. | ||||
|      */ | ||||
|     suspend fun upNextAccount(n: Int = 20): ContinueWatchingList { | ||||
|         val watchlistEndpoint = "/content/v1/$accountID/up_next_account" | ||||
|         val parameters = listOf("locale" to locale, "n" to n) | ||||
|  | ||||
|         val resultUpNextAccount = request(watchlistEndpoint, parameters) | ||||
|         val list: ContinueWatchingList = resultUpNextAccount.component1()?.obj()?.let { | ||||
|         return resultUpNextAccount.component1()?.obj()?.let { | ||||
|             json.decodeFromString(it.toString()) | ||||
|         } ?: NoneContinueWatchingList | ||||
|  | ||||
| //        val objects = list.items.map{ it.panel.episodeMetadata.seriesId } | ||||
| //        return objects(objects) | ||||
|         return list | ||||
|     } | ||||
|  | ||||
| } | ||||
|  | ||||
| @ -29,7 +29,6 @@ data class Collection( | ||||
| typealias SearchCollection = Collection | ||||
| typealias BrowseResult = Collection | ||||
| typealias Watchlist = Collection | ||||
| typealias UpNextAccount = Collection | ||||
|  | ||||
| @Serializable | ||||
| data class SearchResult( | ||||
| @ -112,7 +111,6 @@ data class Series( | ||||
| ) | ||||
| val NoneSeries = Series("", "", "", Images(emptyList(), emptyList()), emptyList()) | ||||
|  | ||||
|  | ||||
| /** | ||||
|  * Seasons data type | ||||
|  */ | ||||
| @ -209,6 +207,16 @@ val NoneEpisode = Episode( | ||||
|     playback = "" | ||||
| ) | ||||
|  | ||||
| typealias PlayheadsMap = Map<String, PlayheadObject> | ||||
|  | ||||
| @Serializable | ||||
| data class PlayheadObject( | ||||
|     @SerialName("playhead") val playhead: Int, | ||||
|     @SerialName("content_id") val contentId: String, | ||||
|     @SerialName("fully_watched") val fullyWatched: Boolean, | ||||
|     @SerialName("last_modified") val lastModified: String, | ||||
| ) | ||||
|  | ||||
| /** | ||||
|  * Playback/stream data type | ||||
|  */ | ||||
|  | ||||
| @ -31,7 +31,11 @@ class MediaFragmentEpisodes : Fragment() { | ||||
|     override fun onViewCreated(view: View, savedInstanceState: Bundle?) { | ||||
|         super.onViewCreated(view, savedInstanceState) | ||||
|  | ||||
|         adapterRecEpisodes = EpisodeItemAdapter(model.currentEpisodesCrunchy, model.tmdbTVSeason.episodes) | ||||
|         adapterRecEpisodes = EpisodeItemAdapter( | ||||
|             model.currentEpisodesCrunchy, | ||||
|             model.tmdbTVSeason.episodes, | ||||
|             model.currentPlayheads | ||||
|         ) | ||||
|         binding.recyclerEpisodes.adapter = adapterRecEpisodes | ||||
|  | ||||
|         // set onItemClick, adapter is initialized | ||||
|  | ||||
| @ -29,6 +29,7 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic | ||||
|     var episodesCrunchy = NoneEpisodes | ||||
|         internal set | ||||
|     val currentEpisodesCrunchy = arrayListOf<Episode>() // used for EpisodeItemAdapter (easier updates) | ||||
|     var currentPlayheads: PlayheadsMap = emptyMap() | ||||
|     var isWatchlist = false | ||||
|         internal set | ||||
|  | ||||
| @ -66,6 +67,10 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic | ||||
|         currentEpisodesCrunchy.addAll(episodesCrunchy.items) | ||||
|         println("episodes: $episodesCrunchy") | ||||
|  | ||||
|         // get playheads (including fully watched state) | ||||
|         val episodeIDs = episodesCrunchy.items.map { it.id } | ||||
|         currentPlayheads = Crunchyroll.playheads(episodeIDs) | ||||
|  | ||||
|         // set media type | ||||
|         mediaType = episodesCrunchy.items.firstOrNull()?.let { | ||||
|             if (it.episodeNumber != null) MediaType.TVSHOW else MediaType.MOVIE | ||||
|  | ||||
| @ -2,8 +2,10 @@ package org.mosad.teapod.util.adapter | ||||
|  | ||||
| import android.graphics.Color | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.graphics.drawable.Drawable | ||||
| import android.view.LayoutInflater | ||||
| import android.view.ViewGroup | ||||
| import androidx.core.content.ContextCompat | ||||
| import androidx.recyclerview.widget.RecyclerView | ||||
| import com.bumptech.glide.Glide | ||||
| import com.bumptech.glide.request.RequestOptions | ||||
| @ -11,9 +13,14 @@ import jp.wasabeef.glide.transformations.RoundedCornersTransformation | ||||
| import org.mosad.teapod.R | ||||
| import org.mosad.teapod.databinding.ItemEpisodeBinding | ||||
| import org.mosad.teapod.parser.crunchyroll.Episode | ||||
| import org.mosad.teapod.parser.crunchyroll.PlayheadsMap | ||||
| import org.mosad.teapod.util.tmdb.TMDBTVEpisode | ||||
|  | ||||
| class EpisodeItemAdapter(private val episodes: List<Episode>, private val tmdbEpisodes: List<TMDBTVEpisode>?) : RecyclerView.Adapter<EpisodeItemAdapter.EpisodeViewHolder>() { | ||||
| class EpisodeItemAdapter( | ||||
|     private val episodes: List<Episode>, | ||||
|     private val tmdbEpisodes: List<TMDBTVEpisode>?, | ||||
|     private val playheads: PlayheadsMap | ||||
| ) : RecyclerView.Adapter<EpisodeItemAdapter.EpisodeViewHolder>() { | ||||
|  | ||||
|     var onImageClick: ((seasonId: String, episodeId: String) -> Unit)? = null | ||||
|  | ||||
| @ -53,16 +60,13 @@ class EpisodeItemAdapter(private val episodes: List<Episode>, private val tmdbEp | ||||
|                 .into(holder.binding.imageEpisode) | ||||
|         } | ||||
|  | ||||
|         // TODO | ||||
| //        if (ep.watched) { | ||||
| //            holder.binding.imageWatched.setImageDrawable( | ||||
| //                ContextCompat.getDrawable(context, R.drawable.ic_baseline_check_circle_24) | ||||
| //            ) | ||||
| //        } else { | ||||
| //            holder.binding.imageWatched.setImageDrawable(null) | ||||
| //        } | ||||
|         // disable watched icon until implemented | ||||
|         holder.binding.imageWatched.setImageDrawable(null) | ||||
|         // add watched icon to episode, if the episode id is present in playheads and fullyWatched | ||||
|         val watchedImage: Drawable? = if (playheads[ep.id]?.fullyWatched == true) { | ||||
|             ContextCompat.getDrawable(context, R.drawable.ic_baseline_check_circle_24) | ||||
|         } else { | ||||
|             null | ||||
|         } | ||||
|         holder.binding.imageWatched.setImageDrawable(watchedImage) | ||||
|     } | ||||
|  | ||||
|     override fun getItemCount(): Int { | ||||
|  | ||||
		Reference in New Issue
	
	Block a user