diff --git a/app/src/main/java/org/mosad/teapod/parser/crunchyroll/Crunchyroll.kt b/app/src/main/java/org/mosad/teapod/parser/crunchyroll/Crunchyroll.kt index be456da..2867969 100644 --- a/app/src/main/java/org/mosad/teapod/parser/crunchyroll/Crunchyroll.kt +++ b/app/src/main/java/org/mosad/teapod/parser/crunchyroll/Crunchyroll.kt @@ -74,7 +74,10 @@ object Crunchyroll { return@runBlocking false } - // TODO get/post difference + /** + * Requests: get, post, delete + */ + private suspend fun request( endpoint: String, params: Parameters = listOf(), @@ -83,7 +86,6 @@ object Crunchyroll { val path = if (url.isEmpty()) "$baseUrl$endpoint" else url // TODO before sending a request, make sure the accessToken is not expired - return@coroutineScope (Dispatchers.IO) { val (request, response, result) = Fuel.get(path, params) .header("Authorization", "$tokenType $accessToken") @@ -168,7 +170,7 @@ object Crunchyroll { } /** - * Main media functions: browse, search, series, season, episodes, playback + * General element/media functions: browse, search, objects, season_list */ // TODO locale de-DE, categories @@ -190,7 +192,7 @@ object Crunchyroll { 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()) { + val parameters = if (seasonTag.isNotEmpty()) { concatenate(noneOptParams, listOf("season_tag" to seasonTag)) } else { noneOptParams @@ -231,7 +233,7 @@ object Crunchyroll { * @param objects The object IDs as list of Strings * @return A **[Collection]** of Panels */ - suspend fun objects(objects: List): Collection { + suspend fun objects(objects: List): Collection { val episodesEndpoint = "/cms/v2/DE/M3/crunchyroll/objects/${objects.joinToString(",")}" val parameters = listOf( "locale" to locale, @@ -247,6 +249,25 @@ object Crunchyroll { } ?: NoneCollection } + /** + * List all available seasons as **[SeasonListItem]**. + */ + @Suppress("unused") + suspend fun seasonList(): DiscSeasonList { + val seasonListEndpoint = "/content/v1/season_list" + val parameters = listOf("locale" to locale) + + val result = request(seasonListEndpoint, parameters) + + return result.component1()?.obj()?.let { + json.decodeFromString(it.toString()) + } ?: NoneDiscSeasonList + } + + /** + * Main media functions: series, season, episodes, playback + */ + /** * series id == crunchyroll id? */ diff --git a/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt b/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt index 0c08afe..8ef866e 100644 --- a/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt +++ b/app/src/main/java/org/mosad/teapod/parser/crunchyroll/DataTypes.kt @@ -15,44 +15,25 @@ enum class SortBy(val str: String) { } /** - * search, browse, watchlist data types (all collections) + * search, browse, DiscSeasonList, Watchlist, ContinueWatchingList data types all use Collection */ -// TODO make generic @Serializable -data class Collection( +data class Collection( @SerialName("total") val total: Int, - @SerialName("items") val items: List + @SerialName("items") val items: List ) -// TODO don't use aliases -typealias SearchCollection = Collection -typealias BrowseResult = Collection -typealias Watchlist = Collection +typealias SearchResult = Collection +typealias SearchCollection = Collection +typealias BrowseResult = Collection +typealias DiscSeasonList = Collection +typealias Watchlist = Collection +typealias ContinueWatchingList = Collection -@Serializable -data class SearchResult( - @SerialName("total") val total: Int, - @SerialName("items") val items: List -) - -@Serializable -data class ContinueWatchingList( - @SerialName("total") val total: Int, - @SerialName("items") val items: List -) - -@Serializable -data class ContinueWatchingItem( - @SerialName("panel") val panel: EpisodePanel, - @SerialName("new") val new: Boolean, - @SerialName("new_content") val newContent: Boolean, - // not present in up_next_account's continue_watching_item -// @SerialName("is_favorite") val isFavorite: Boolean, -// @SerialName("never_watched") val neverWatched: Boolean, -// @SerialName("completion_status") val completionStatus: Boolean, - @SerialName("playhead") val playhead: Int, -) +/** + * panel data classes + */ // the data class Item is used in browse and search // TODO rename to MediaPanel @@ -64,7 +45,45 @@ data class Item( val channel_id: String, val description: String, val images: Images - // TODO metadata etc. + // TODO series_metadata etc. +) + +@Serializable +data class Images(val poster_tall: List>, val poster_wide: List>) +// crunchyroll why? + +@Serializable +data class Poster(val height: Int, val width: Int, val source: String, val type: String) + +/** + * season list data classes + */ +@Serializable +data class SeasonListItem( + @SerialName("id") val id: String, + @SerialName("localization") val localization: SeasonListLocalization +) + +@Serializable +data class SeasonListLocalization( + @SerialName("title") val title: String, + @SerialName("description") val description: String, +) + +/** + * continue_watching_item data classes + */ + +@Serializable +data class ContinueWatchingItem( + @SerialName("panel") val panel: EpisodePanel, + @SerialName("new") val new: Boolean, + @SerialName("new_content") val newContent: Boolean, + // not present in up_next_account's continue_watching_item +// @SerialName("is_favorite") val isFavorite: Boolean, +// @SerialName("never_watched") val neverWatched: Boolean, +// @SerialName("completion_status") val completionStatus: Boolean, + @SerialName("playhead") val playhead: Int, ) // EpisodePanel is used in ContinueWatchingItem @@ -79,13 +98,6 @@ data class EpisodePanel( @SerialName("episode_metadata") val episodeMetadata: EpisodeMetadata, ) -@Serializable -data class Images(val poster_tall: List>, val poster_wide: List>) -// crunchyroll why? - -@Serializable -data class Poster(val height: Int, val width: Int, val source: String, val type: String) - @Serializable data class EpisodeMetadata( @SerialName("series_id") val seriesId: String, @@ -93,9 +105,11 @@ data class EpisodeMetadata( ) val NoneItem = Item("", "", "", "", "", Images(emptyList(), emptyList())) -val NoneCollection = Collection(0, emptyList()) +val NoneCollection = Collection(0, emptyList()) val NoneSearchResult = SearchResult(0, emptyList()) val NoneBrowseResult = BrowseResult(0, emptyList()) +val NoneDiscSeasonList = DiscSeasonList(0, emptyList()) +val NoneWatchlist = Watchlist(0, emptyList()) val NoneContinueWatchingList = ContinueWatchingList(0, emptyList()) /** diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt index 01a86b9..154dd5c 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/HomeFragment.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.joinAll import kotlinx.coroutines.launch import org.mosad.teapod.databinding.FragmentHomeBinding import org.mosad.teapod.parser.crunchyroll.Crunchyroll +import org.mosad.teapod.parser.crunchyroll.SortBy import org.mosad.teapod.util.ItemMedia import org.mosad.teapod.util.adapter.MediaItemAdapter import org.mosad.teapod.util.decoration.MediaItemDecoration @@ -87,9 +88,17 @@ class HomeFragment : Fragment() { } asyncJobList.add(watchlistJob) - // new titles TODO -// adapterNewTitles = MediaItemAdapter(AoDParser.newTitlesList) -// binding.recyclerNewTitles.adapter = adapterNewTitles + // new simulcasts TODO replace with new titles? browse(sortBy = SortBy.NEWLY_ADDED, n = 50) + val simulcastsJob = lifecycleScope.launch { +// val latestSeasonTag = Crunchyroll.seasonList().items.first().id +// val newSimulcasts = Crunchyroll.browse(seasonTag = latestSeasonTag, n = 50) + + val newSimulcasts = Crunchyroll.browse(sortBy = SortBy.NEWLY_ADDED, n = 50) + + adapterNewTitles = MediaItemAdapter(newSimulcasts.toItemMediaList()) + binding.recyclerNewTitles.adapter = adapterNewTitles + } + asyncJobList.add(simulcastsJob) // top ten TODO // adapterTopTen = MediaItemAdapter(AoDParser.topTenList) @@ -135,10 +144,10 @@ class HomeFragment : Fragment() { activity?.showFragment(MediaFragment(id)) } -// adapterNewTitles.onItemClick = { id, _ -> -// activity?.showFragment(MediaFragment("")) //(mediaId)) -// } -// + adapterNewTitles.onItemClick = { id, _ -> + activity?.showFragment(MediaFragment(id)) + } + // adapterTopTen.onItemClick = { id, _ -> // activity?.showFragment(MediaFragment("")) //(mediaId)) // } diff --git a/app/src/main/java/org/mosad/teapod/util/Utils.kt b/app/src/main/java/org/mosad/teapod/util/Utils.kt index 368dbc1..28b6f5a 100644 --- a/app/src/main/java/org/mosad/teapod/util/Utils.kt +++ b/app/src/main/java/org/mosad/teapod/util/Utils.kt @@ -2,7 +2,9 @@ package org.mosad.teapod.util import android.widget.TextView import org.mosad.teapod.parser.crunchyroll.Collection +import org.mosad.teapod.parser.crunchyroll.ContinueWatchingItem import org.mosad.teapod.parser.crunchyroll.ContinueWatchingList +import org.mosad.teapod.parser.crunchyroll.Item fun TextView.setDrawableTop(drawable: Int) { this.setCompoundDrawablesWithIntrinsicBounds(0, drawable, 0, 0) @@ -13,16 +15,15 @@ fun concatenate(vararg lists: List): List { } // TODO move to correct location -fun Collection.toItemMediaList(): List { +fun Collection.toItemMediaList(): List { return this.items.map { ItemMedia(it.id, it.title, it.images.poster_wide[0][0].source) } } -fun ContinueWatchingList.toItemMediaList(): List { +@JvmName("toItemMediaListContinueWatchingItem") +fun Collection.toItemMediaList(): List { return this.items.map { - // TODO add season and episode to title ItemMedia(it.panel.episodeMetadata.seriesId, it.panel.title, it.panel.images.thumbnail[0][0].source) - } }