From 8b7fb3ac5f07a92b6355715b56e153d8f6b412a8 Mon Sep 17 00:00:00 2001 From: Jannik Date: Sun, 19 Feb 2023 14:21:46 +0100 Subject: [PATCH] fix crunchyroll parser to work with the latest api changes --- .../teapod/parser/crunchyroll/Crunchyroll.kt | 166 ++++++++++-------- .../teapod/parser/crunchyroll/DataTypes.kt | 89 +++++++--- .../mosad/teapod/preferences/Preferences.kt | 24 ++- .../teapod/ui/activity/main/MainActivity.kt | 7 +- .../main/fragments/AccountFragment.kt | 2 +- .../activity/main/fragments/MediaFragment.kt | 15 +- .../main/fragments/MyListsFragment.kt | 2 +- .../activity/main/viewmodel/HomeViewModel.kt | 11 +- .../main/viewmodel/MediaFragmentViewModel.kt | 6 +- .../ui/activity/player/PlayerViewModel.kt | 60 +++++-- .../LanguageSettingsDialogFragment.kt | 77 +++++--- .../main/java/org/mosad/teapod/util/Utils.kt | 14 -- .../util/adapter/MediaEpisodeListAdapter.kt | 16 +- .../teapod/util/tmdb/TMDBApiController.kt | 2 +- app/src/main/res/layout/player_controls.xml | 2 +- .../res/layout/player_language_settings.xml | 75 ++++++-- app/src/main/res/values-de-rDE/strings.xml | 1 + app/src/main/res/values/strings.xml | 2 + build.gradle | 2 +- 19 files changed, 375 insertions(+), 198 deletions(-) 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 10fc05e..8208466 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 @@ -31,6 +31,7 @@ import io.ktor.client.request.* import io.ktor.client.request.forms.* import io.ktor.client.statement.* import io.ktor.http.* +import io.ktor.serialization.* import io.ktor.serialization.kotlinx.json.* import kotlinx.coroutines.* import kotlinx.serialization.SerializationException @@ -245,7 +246,7 @@ object Crunchyroll { val account: Account = try { requestGet(indexEndpoint) - } catch (ex: SerializationException) { + } catch (ex: Exception) { Log.e(TAG, "SerializationException in account(). This is bad!", ex) NoneAccount } @@ -275,7 +276,7 @@ object Crunchyroll { ): BrowseResult { val browseEndpoint = "/content/v1/browse" val parameters = mutableListOf( - "locale" to Preferences.preferredLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag(), "sort_by" to sortBy.str, "start" to start, "n" to n @@ -298,7 +299,7 @@ object Crunchyroll { Log.d(TAG, "browse result not cached, fetching: $parameters") val browseResult: BrowseResult = try { requestGet(browseEndpoint, parameters) - }catch (ex: SerializationException) { + }catch (ex: Exception) { Log.e(TAG, "SerializationException in browse().", ex) NoneBrowseResult } @@ -328,7 +329,7 @@ object Crunchyroll { suspend fun search(query: String, n: Int = 10): SearchResult { val searchEndpoint = "/content/v1/search" val parameters = listOf( - "locale" to Preferences.preferredLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag(), "q" to query, "n" to n, "type" to "series" @@ -339,8 +340,8 @@ object Crunchyroll { return try { requestGet(searchEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in search(), with query = \"$query\".", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in search(), with query = \"$query\".", ex) NoneSearchResult } } @@ -355,7 +356,7 @@ object Crunchyroll { suspend fun objects(objects: List): Collection { val episodesEndpoint = "/cms/v2/DE/M3/crunchyroll/objects/${objects.joinToString(",")}" val parameters = listOf( - "locale" to Preferences.preferredLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag(), "Signature" to signature, "Policy" to policy, "Key-Pair-Id" to keyPairID @@ -363,8 +364,8 @@ object Crunchyroll { return try { requestGet(episodesEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in objects().", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in objects().", ex) NoneCollection } } @@ -375,12 +376,12 @@ object Crunchyroll { @Suppress("unused") suspend fun seasonList(): DiscSeasonList { val seasonListEndpoint = "/content/v1/season_list" - val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag()) + val parameters = listOf("locale" to Preferences.preferredSubtitleLocale.toLanguageTag()) return try { requestGet(seasonListEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in seasonList().", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in seasonList().", ex) NoneDiscSeasonList } } @@ -395,7 +396,7 @@ object Crunchyroll { suspend fun series(seriesId: String): Series { val seriesEndpoint = "/cms/v2/${token.country}/M3/crunchyroll/series/$seriesId" val parameters = listOf( - "locale" to Preferences.preferredLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag(), "Signature" to signature, "Policy" to policy, "Key-Pair-Id" to keyPairID @@ -403,8 +404,8 @@ object Crunchyroll { return try { requestGet(seriesEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in series().", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in series().", ex) NoneSeries } } @@ -415,18 +416,21 @@ object Crunchyroll { * @param seriesId The series id for which to call up next * @return A **[UpNextSeriesItem]** with a Panel representing the up next episode */ - suspend fun upNextSeries(seriesId: String): UpNextSeriesItem { - val upNextSeriesEndpoint = "/content/v1/up_next_series" + suspend fun upNextSeries(seriesId: String): UpNextSeriesList { + val upNextSeriesEndpoint = "/content/v2/discover/up_next/$seriesId" val parameters = listOf( - "series_id" to seriesId, - "locale" to Preferences.preferredLocale.toLanguageTag() + "preferred_audio_language" to Preferences.preferredAudioLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag() ) return try { requestGet(upNextSeriesEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in upNextSeries().", ex) - NoneUpNextSeriesItem + } catch (ex: JsonConvertException) { + Log.e(TAG, "JsonConvertException in upNextSeries() with seriesId=$seriesId", ex) + NoneUpNextSeriesList + } catch (ex: Exception) { + Log.e(TAG, "Exception in upNextSeries().", ex) + NoneUpNextSeriesList } } @@ -439,14 +443,14 @@ object Crunchyroll { suspend fun seasons(seriesId: String): Seasons { val seasonsEndpoint = "/content/v2/cms/series/$seriesId/seasons" val parameters = listOf( - "preferred_audio_language" to Preferences.preferredLocale.toLanguageTag(), - "locale" to Preferences.preferredLocale.toLanguageTag() + "preferred_audio_language" to Preferences.preferredSubtitleLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag() ) return try { requestGet(seasonsEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in seasons().", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in seasons().", ex) NoneSeasons } } @@ -460,14 +464,14 @@ object Crunchyroll { suspend fun episodes(seasonId: String): Episodes { val episodesEndpoint = "/content/v2/cms/seasons/$seasonId/episodes" val parameters = listOf( - "preferred_audio_language" to Preferences.preferredLocale.toLanguageTag(), - "locale" to Preferences.preferredLocale.toLanguageTag() + "preferred_audio_language" to Preferences.preferredSubtitleLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag() ) return try { requestGet(episodesEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in episodes().", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in episodes().", ex) NoneEpisodes } } @@ -480,18 +484,23 @@ object Crunchyroll { */ suspend fun streams(url: String): Streams { val parameters = listOf( - "preferred_audio_language" to Preferences.preferredLocale.toLanguageTag(), - "locale" to Preferences.preferredLocale.toLanguageTag() + "preferred_audio_language" to Preferences.preferredSubtitleLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag() ) return try { requestGet(url, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in streams().", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in streams().", ex) NoneStreams } } + suspend fun streamsFromMediaGUID(mediaGUID: String): Streams { + val streamsEndpoint = "/content/v2/cms/videos/$mediaGUID/streams" + return streams(streamsEndpoint) + } + /** * Additional media functions: watchlist (series), playhead, similar to */ @@ -504,13 +513,13 @@ object Crunchyroll { */ suspend fun isWatchlist(seriesId: String): Boolean { val watchlistSeriesEndpoint = "/content/v1/watchlist/$accountID/$seriesId" - val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag()) + val parameters = listOf("locale" to Preferences.preferredSubtitleLocale.toLanguageTag()) return try { (requestGet(watchlistSeriesEndpoint, parameters) as JsonObject) .containsKey(seriesId) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in isWatchlist() with seriesId = $seriesId", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in isWatchlist() with seriesId = $seriesId", ex) false } } @@ -522,13 +531,18 @@ object Crunchyroll { */ suspend fun postWatchlist(seriesId: String) { val watchlistPostEndpoint = "/content/v1/watchlist/$accountID" - val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag()) + val parameters = listOf("locale" to Preferences.preferredSubtitleLocale.toLanguageTag()) val json = buildJsonObject { put("content_id", seriesId) } - requestPost(watchlistPostEndpoint, parameters, json) + try { + requestPost(watchlistPostEndpoint, parameters, json) + } catch (ex: Exception) { + Log.e(TAG, "Exception in postWatchlist() with seriesId = $seriesId", ex) + } + } /** @@ -538,9 +552,14 @@ object Crunchyroll { */ suspend fun deleteWatchlist(seriesId: String) { val watchlistDeleteEndpoint = "/content/v1/watchlist/$accountID/$seriesId" - val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag()) + val parameters = listOf("locale" to Preferences.preferredSubtitleLocale.toLanguageTag()) + + try { + requestDelete(watchlistDeleteEndpoint, parameters) + } catch (ex: Exception) { + Log.e(TAG, "Exception in deleteWatchlist() with seriesId = $seriesId", ex) + } - requestDelete(watchlistDeleteEndpoint, parameters) } /** @@ -553,7 +572,7 @@ object Crunchyroll { */ suspend fun playheads(episodeIDs: List): PlayheadsMap { val playheadsEndpoint = "/content/v1/playheads/$accountID/${episodeIDs.joinToString(",")}" - val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag()) + val parameters = listOf("locale" to Preferences.preferredSubtitleLocale.toLanguageTag()) return try { requestGet(playheadsEndpoint, parameters) @@ -574,7 +593,7 @@ object Crunchyroll { */ suspend fun postPlayheads(episodeId: String, playhead: Int) { val playheadsEndpoint = "/content/v1/playheads/$accountID" - val parameters = listOf("locale" to Preferences.preferredLocale.toLanguageTag()) + val parameters = listOf("locale" to Preferences.preferredSubtitleLocale.toLanguageTag()) val json = buildJsonObject { put("content_id", episodeId) @@ -583,7 +602,7 @@ object Crunchyroll { try { requestPost(playheadsEndpoint, parameters, json) - } catch (ex: Throwable) { + } catch (ex: Exception) { Log.e(TAG, "Exception in postPlayheads()", ex.cause) } } @@ -603,8 +622,8 @@ object Crunchyroll { return try { val response: HttpResponse = requestGet(datalabIntroEndpoint, url = staticUrl) Json.decodeFromString(response.bodyAsText()) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in datalabIntro(). EpisodeId=$episodeId", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in datalabIntro(). EpisodeId=$episodeId", ex) NoneDatalabIntro } } @@ -620,14 +639,14 @@ object Crunchyroll { val similarToEndpoint = "/content/v1/$accountID/similar_to" val parameters = listOf( "guid" to seriesId, - "locale" to Preferences.preferredLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag(), "n" to n ) return try { requestGet(similarToEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in similarTo().", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in similarTo().", ex) NoneSimilarToResult } } @@ -640,23 +659,24 @@ object Crunchyroll { * List items present in the watchlist. * * @param n Number of items to return, defaults to 20. - * @return A **[Watchlist]** containing up to n **[Item]**. + * @return A **[Collection]** containing up to n **[Item]**. */ - suspend fun watchlist(n: Int = 20): Watchlist { - val watchlistEndpoint = "/content/v1/$accountID/watchlist" + suspend fun watchlist(n: Int = 20): Collection { + val watchlistEndpoint = "/content/v2/discover/$accountID/watchlist" val parameters = listOf( - "locale" to Preferences.preferredLocale.toLanguageTag(), - "n" to n + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag(), + "n" to n, + "preferred_audio_language" to Preferences.preferredAudioLocale.toLanguageTag() ) - val list: ContinueWatchingList = try { + val list: Watchlist = try { requestGet(watchlistEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in watchlist().", ex) - NoneContinueWatchingList + } catch (ex: Exception) { + Log.e(TAG, "Exception in watchlist().", ex) + NoneWatchlist } - val objects = list.items.map{ it.panel.episodeMetadata.seriesId } + val objects = list.data.map{ it.panel.episodeMetadata.seriesId } return objects(objects) } @@ -664,27 +684,27 @@ object Crunchyroll { * 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]**. + * @return A **[HistoryList]** containing up to n **[UpNextAccountItem]**. */ - suspend fun upNextAccount(n: Int = 20): ContinueWatchingList { - val watchlistEndpoint = "/content/v1/$accountID/up_next_account" + suspend fun upNextAccount(n: Int = 20): HistoryList { + val watchlistEndpoint = "/content/v2/discover/$accountID/history" val parameters = listOf( - "locale" to Preferences.preferredLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag(), "n" to n ) return try { requestGet(watchlistEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in upNextAccount().", ex) - NoneContinueWatchingList + } catch (ex: Exception) { + Log.e(TAG, "Exception in upNextAccount().", ex) + NoneHistoryList } } suspend fun recommendations(n: Int = 20, start: Int = 0): RecommendationsList { val recommendationsEndpoint = "/content/v1/$accountID/recommendations" val parameters = listOf( - "locale" to Preferences.preferredLocale.toLanguageTag(), + "locale" to Preferences.preferredSubtitleLocale.toLanguageTag(), "n" to n, "start" to start, "variant_id" to 0 @@ -692,8 +712,8 @@ object Crunchyroll { return try { requestGet(recommendationsEndpoint, parameters) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in recommendations().", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in recommendations().", ex) NoneRecommendationsList } } @@ -712,8 +732,8 @@ object Crunchyroll { return try { requestGet(profileEndpoint) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in profile().", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in profile().", ex) NoneProfile } } @@ -742,8 +762,8 @@ object Crunchyroll { return try { requestGet(profileEndpoint) - } catch (ex: SerializationException) { - Log.e(TAG, "SerializationException in benefits().", ex) + } catch (ex: Exception) { + Log.e(TAG, "Exception in benefits().", ex) NoneBenefits } } 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 cb3435d..44227c8 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 @@ -117,24 +117,23 @@ data class Collection( @SerialName("items") val items: List ) +@Serializable +data class Collection2( + @SerialName("total") val total: Int, + @SerialName("data") val data: List +) + typealias SearchResult = Collection typealias SearchCollection = Collection typealias BrowseResult = Collection typealias SimilarToResult = Collection typealias DiscSeasonList = Collection -typealias Watchlist = Collection -typealias ContinueWatchingList = Collection +typealias Watchlist = Collection2 +typealias HistoryList = Collection2 +typealias UpNextSeriesList = Collection2 typealias RecommendationsList = Collection typealias Benefits = Collection -@Serializable -data class UpNextSeriesItem( - @SerialName("playhead") val playhead: Int, - @SerialName("fully_watched") val fullyWatched: Boolean, - @SerialName("never_watched") val neverWatched: Boolean, - @SerialName("panel") val panel: EpisodePanel, -) - /** * panel data classes */ @@ -178,18 +177,32 @@ data class SeasonListLocalization( /** * continue_watching_item data classes */ + @Serializable -data class ContinueWatchingItem( +data class WatchlistItem( @SerialName("panel") val panel: EpisodePanel, @SerialName("new") val new: Boolean, - @SerialName("new_content") val newContent: Boolean, - // not present in up_next_account -> 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, - // not present in watchlist -> continue_watching_item @SerialName("fully_watched") val fullyWatched: Boolean = false, + @SerialName("never_watched") val neverWatched: Boolean = false, + @SerialName("is_favorite") val isFavorite: Boolean, +) + +@Serializable +data class UpNextAccountItem( + @SerialName("panel") val panel: EpisodePanel, + @SerialName("new") val new: Boolean, + @SerialName("playhead") val playhead: Int, + @SerialName("fully_watched") val fullyWatched: Boolean = false, +) + +@Serializable +data class UpNextSeriesItem( + @SerialName("panel") val panel: EpisodePanel, + @SerialName("playhead") val playhead: Int, + @SerialName("fully_watched") val fullyWatched: Boolean, + @SerialName("never_watched") val neverWatched: Boolean, + ) // EpisodePanel is used in ContinueWatchingItem and UpNextSeriesItem @@ -202,7 +215,7 @@ data class EpisodePanel( @SerialName("description") val description: String, @SerialName("episode_metadata") val episodeMetadata: EpisodeMetadata, @SerialName("images") val images: Thumbnail, - @SerialName("playback") val playback: String, +// @SerialName("streams_link") val streamsLink: String, ) @Serializable @@ -218,24 +231,19 @@ data class EpisodeMetadata( val NoneItem = Item("", "", "", "", "", Images(emptyList(), emptyList())) val NoneEpisodeMetadata = EpisodeMetadata(0, 0, "", 0, "", "", "") -val NoneEpisodePanel = EpisodePanel("", "", "", "", "", NoneEpisodeMetadata, Thumbnail(listOf()), "") +val NoneEpisodePanel = EpisodePanel("", "", "", "", "", NoneEpisodeMetadata, Thumbnail(listOf())) //, "") val NoneCollection = Collection(0, emptyList()) val NoneSearchResult = SearchResult(0, emptyList()) val NoneBrowseResult = BrowseResult(0, emptyList()) val NoneSimilarToResult = SimilarToResult(0, emptyList()) val NoneDiscSeasonList = DiscSeasonList(0, emptyList()) -val NoneContinueWatchingList = ContinueWatchingList(0, emptyList()) +val NoneWatchlist = Watchlist(0, emptyList()) +val NoneHistoryList = HistoryList(0, emptyList()) +val NoneUpNextSeriesList = UpNextSeriesList(0, emptyList()) val NoneRecommendationsList = RecommendationsList(0, emptyList()) val NoneBenefits = Benefits(0, emptyList()) -val NoneUpNextSeriesItem = UpNextSeriesItem( - playhead = 0, - fullyWatched = false, - neverWatched = false, - panel = NoneEpisodePanel -) - /** * series data class */ @@ -309,7 +317,7 @@ data class Episode( @SerialName("is_dubbed") val isDubbed: Boolean, @SerialName("images") val images: Thumbnail, @SerialName("duration_ms") val durationMs: Int, - @SerialName("playback") val playback: String, + @SerialName("versions") val versions: List, @SerialName("streams_link") val streamsLink: String, ) @@ -318,6 +326,17 @@ data class Thumbnail( @SerialName("thumbnail") val thumbnail: List> ) +@Serializable +data class Version( + @SerialName("audio_locale") val audioLocale: String, + @SerialName("guid") val guid: String, + @SerialName("is_premium_only") val isPremiumOnly: Boolean, + @SerialName("media_guid") val mediaGUID: String, + @SerialName("original") val original: Boolean, + @SerialName("season_guid") val seasonGUID: String, + @SerialName("variant") val variant: String, +) + val NoneEpisodes = Episodes(0, listOf()) val NoneEpisode = Episode( id = "", @@ -335,10 +354,20 @@ val NoneEpisode = Episode( isDubbed = false, images = Thumbnail(listOf()), durationMs = 0, - playback = "", + versions = emptyList(), streamsLink = "" ) +val NoneVersion = Version( + audioLocale = "", + guid = "", + isPremiumOnly = false, + mediaGUID = "", + original = true, + seasonGUID = "", + variant = "" +) + typealias PlayheadsMap = Map @Serializable @@ -412,6 +441,7 @@ data class Profile( @SerialName("avatar") val avatar: String, @SerialName("email") val email: String, @SerialName("maturity_rating") val maturityRating: String, + @SerialName("preferred_content_audio_language") val preferredContentAudioLanguage: String, @SerialName("preferred_content_subtitle_language") val preferredContentSubtitleLanguage: String, @SerialName("username") val username: String, ) @@ -419,6 +449,7 @@ val NoneProfile = Profile( avatar = "", email = "", maturityRating = "", + preferredContentAudioLanguage = "", preferredContentSubtitleLanguage = "", username = "" ) diff --git a/app/src/main/java/org/mosad/teapod/preferences/Preferences.kt b/app/src/main/java/org/mosad/teapod/preferences/Preferences.kt index 92e7799..197e80f 100644 --- a/app/src/main/java/org/mosad/teapod/preferences/Preferences.kt +++ b/app/src/main/java/org/mosad/teapod/preferences/Preferences.kt @@ -8,7 +8,9 @@ import java.util.* object Preferences { - var preferredLocale: Locale = Locale.forLanguageTag("en-US") // TODO this should be saved (potential offline usage) but fetched on start + var preferredAudioLocale: Locale = Locale.forLanguageTag("en-US") + internal set + var preferredSubtitleLocale: Locale = Locale.forLanguageTag("en-US") internal set var preferSubbed = false internal set @@ -30,13 +32,22 @@ object Preferences { ) } - fun savePreferredLocal(context: Context, preferredLocale: Locale) { + fun savePreferredAudioLocal(context: Context, preferredLocale: Locale) { with(getSharedPref(context).edit()) { putString(context.getString(R.string.save_key_preferred_local), preferredLocale.toLanguageTag()) apply() } - this.preferredLocale = preferredLocale + this.preferredAudioLocale = preferredLocale + } + + fun savePreferredSubtitleLocal(context: Context, preferredLocale: Locale) { + with(getSharedPref(context).edit()) { + putString(context.getString(R.string.save_key_preferred_local), preferredLocale.toLanguageTag()) + apply() + } + + this.preferredSubtitleLocale = preferredLocale } fun savePreferSecondary(context: Context, preferSubbed: Boolean) { @@ -90,7 +101,12 @@ object Preferences { fun load(context: Context) { val sharedPref = getSharedPref(context) - preferredLocale = Locale.forLanguageTag( + preferredAudioLocale = Locale.forLanguageTag( + sharedPref.getString( + context.getString(R.string.save_key_preferred_audio_local), "en-US" + ) ?: "en-US" + ) + preferredSubtitleLocale = Locale.forLanguageTag( sharedPref.getString( context.getString(R.string.save_key_preferred_local), "en-US" ) ?: "en-US" diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt index 486e0a2..38ba950 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/MainActivity.kt @@ -169,9 +169,12 @@ class MainActivity : AppCompatActivity(), NavigationBarView.OnItemSelectedListen scope.launch { Crunchyroll.account() }, scope.launch { // update the local preferred content language, since it may have changed - val locale = Locale.forLanguageTag(Crunchyroll.profile().preferredContentSubtitleLanguage) - Preferences.savePreferredLocal(this@MainActivity, locale) + val profile = Crunchyroll.profile() + val audioLocale = Locale.forLanguageTag(profile.preferredContentAudioLanguage) + val subtitleLocale = Locale.forLanguageTag(profile.preferredContentSubtitleLanguage) + Preferences.savePreferredAudioLocal(this@MainActivity, audioLocale) + Preferences.savePreferredSubtitleLocal(this@MainActivity, subtitleLocale) } ) } diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AccountFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AccountFragment.kt index cbc5cd1..b679bf1 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AccountFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/AccountFragment.kt @@ -168,7 +168,7 @@ class AccountFragment : Fragment() { }.invokeOnCompletion { // update the local preferred content language - Preferences.savePreferredLocal(requireContext(), preferredLocale) + Preferences.savePreferredSubtitleLocal(requireContext(), preferredLocale) // update profile since the language selection might have changed profile = lifecycleScope.async { Crunchyroll.profile() } diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt index 1d803e3..91bd0dd 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MediaFragment.kt @@ -20,7 +20,7 @@ import jp.wasabeef.glide.transformations.BlurTransformation import kotlinx.coroutines.launch import org.mosad.teapod.R import org.mosad.teapod.databinding.FragmentMediaBinding -import org.mosad.teapod.parser.crunchyroll.NoneUpNextSeriesItem +import org.mosad.teapod.parser.crunchyroll.NoneUpNextSeriesList import org.mosad.teapod.ui.activity.main.viewmodel.MediaFragmentViewModel import org.mosad.teapod.util.playerIntent import org.mosad.teapod.util.tmdb.TMDBApiController @@ -104,8 +104,8 @@ class MediaFragment(private val mediaIdStr: String) : Fragment() { } binding.textAge.text = seriesCrunchy.maturityRatings.firstOrNull() - binding.textTitle.text = if (upNextSeries != NoneUpNextSeriesItem) { - upNextSeries.panel.title + binding.textTitle.text = if (upNextSeries != NoneUpNextSeriesList) { + upNextSeries.data.first().panel.title } else seriesCrunchy.title binding.textOverview.text = seriesCrunchy.description @@ -174,8 +174,9 @@ class MediaFragment(private val mediaIdStr: String) : Fragment() { private fun initActions() = with(model) { binding.buttonPlay.setOnClickListener { - if (upNextSeries != NoneUpNextSeriesItem) { - playEpisode(upNextSeries.panel.episodeMetadata.seasonId, upNextSeries.panel.id) + if (upNextSeries != NoneUpNextSeriesList) { + val panel = upNextSeries.data.first().panel + playEpisode(panel.episodeMetadata.seasonId, panel.id) } } @@ -199,8 +200,8 @@ class MediaFragment(private val mediaIdStr: String) : Fragment() { private fun playerFinishedCallback() = lifecycleScope.launch { model.updateOnResume() - if (model.upNextSeries != NoneUpNextSeriesItem) { - binding.textTitle.text = model.upNextSeries.panel.title + if (model.upNextSeries != NoneUpNextSeriesList) { + binding.textTitle.text = model.upNextSeries.data.first().panel.title } // needs to be called after model.updateOnResume() diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MyListsFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MyListsFragment.kt index 4cff382..4d52918 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MyListsFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/fragments/MyListsFragment.kt @@ -44,7 +44,7 @@ class MyListsFragment : Fragment() { }.attach() lifecycleScope.launch { - val items = Crunchyroll.watchlist(50).items + val items = Crunchyroll.watchlist(50) MediaFragmentSimilar(items.toItemMediaList()).also { fragments.add(it) diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/HomeViewModel.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/HomeViewModel.kt index 72ccf1a..4c073b4 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/HomeViewModel.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/HomeViewModel.kt @@ -26,7 +26,8 @@ import androidx.lifecycle.LifecycleCoroutineScope import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import kotlinx.coroutines.async -import kotlinx.coroutines.flow.* +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.mosad.teapod.parser.crunchyroll.* import kotlin.random.Random @@ -40,7 +41,7 @@ class HomeViewModel : ViewModel() { sealed class UiState { object Loading : UiState() data class Normal( - val upNextItems: List, + val upNextItems: List, val watchlistItems: List, val recommendationsItems: List, val recentlyAddedItems: List, @@ -65,7 +66,7 @@ class HomeViewModel : ViewModel() { uiState.emit(UiState.Loading) try { // run the loading in parallel to speed up the process - val upNextJob = viewModelScope.async { Crunchyroll.upNextAccount().items } + val upNextJob = viewModelScope.async { Crunchyroll.upNextAccount().data } val watchlistJob = viewModelScope.async { Crunchyroll.watchlist(WATCHLIST_LENGTH).items } val recommendationsJob = viewModelScope.async { Crunchyroll.recommendations(20).items @@ -81,7 +82,7 @@ class HomeViewModel : ViewModel() { // FIXME crashes on newTitles.items.size == 0 val highlightItem = recentlyAddedItems[Random.nextInt(recentlyAddedItems.size)] val highlightItemUpNextJob = viewModelScope.async { - Crunchyroll.upNextSeries(highlightItem.id) + Crunchyroll.upNextSeries(highlightItem.id).data.first() } val highlightItemIsWatchlistJob = viewModelScope.async { Crunchyroll.isWatchlist(highlightItem.id) @@ -132,7 +133,7 @@ class HomeViewModel : ViewModel() { viewModelScope.launch { uiState.update { currentUiState -> if (currentUiState is UiState.Normal) { - val upNextItems = Crunchyroll.upNextAccount().items + val upNextItems = Crunchyroll.upNextAccount().data currentUiState.copy(upNextItems = upNextItems) } else { currentUiState diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt index d69554e..3638528 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/main/viewmodel/MediaFragmentViewModel.kt @@ -30,7 +30,7 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic val currentPlayheads: MutableMap = mutableMapOf() var isWatchlist = false internal set - var upNextSeries = NoneUpNextSeriesItem + var upNextSeries = NoneUpNextSeriesList internal set var similarTo = NoneSimilarToResult internal set @@ -60,8 +60,8 @@ class MediaFragmentViewModel(application: Application) : AndroidViewModel(applic // load the preferred season: // next episode > preferred language (language per season, not per stream) currentSeasonCrunchy = seasonsCrunchy.data.firstOrNull{ season -> - season.id == upNextSeries.panel.episodeMetadata.seasonId - } ?: seasonsCrunchy.getPreferredSeasonByLocal(Preferences.preferredLocale) + season.id == upNextSeries.data.first().panel.episodeMetadata.seasonId + } ?: seasonsCrunchy.getPreferredSeasonByLocal(Preferences.preferredSubtitleLocale) // Note: if we need to query metaDB, do it now // load episodes and metaDB in parallel (tmdb needs mediaType, which is set via episodes) diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt b/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt index 584d20e..9c79574 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/player/PlayerViewModel.kt @@ -75,10 +75,15 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) internal set var currentEpisode = NoneEpisode internal set + var currentVersion = NoneVersion + internal set var currentStreams = NoneStreams + internal set // current playback settings - var currentLanguage: Locale = Preferences.preferredLocale + var currentAudioLocale: Locale = Preferences.preferredAudioLocale + internal set + var currentSubtitleLocale: Locale = Preferences.preferredSubtitleLocale internal set init { @@ -146,9 +151,31 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) playCurrentMedia(currentPlayhead) } - fun setLanguage(language: Locale) { - currentLanguage = language - playCurrentMedia(player.currentPosition) + fun setLanguage(newAudioLocale: Locale, newSubtitleLocale: Locale) { + // TODO if the audio locale has changes update the streams, if only the subtitle locale has changed load the new stream + if (newAudioLocale != currentAudioLocale) { + currentAudioLocale = newAudioLocale + + currentVersion = currentEpisode.versions.firstOrNull { + it.audioLocale == currentAudioLocale.toLanguageTag() + } ?: currentEpisode.versions.first() + + viewModelScope.launch { + currentStreams = Crunchyroll.streamsFromMediaGUID(currentVersion.mediaGUID) + Log.d(classTag, currentVersion.toString()) + + playCurrentMedia(player.currentPosition) + } + } else if (newSubtitleLocale != currentSubtitleLocale) { + currentSubtitleLocale = newSubtitleLocale + playCurrentMedia(player.currentPosition) + } + + println(newSubtitleLocale != currentSubtitleLocale) + println("currentSubtitleLocale: $currentSubtitleLocale") + println("newSubtitleLocale: $newSubtitleLocale") + + // else nothing has changed so no need do do anything } // player actions @@ -198,8 +225,17 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) // needs to be blocking, currentPlayback must be present when calling playCurrentMedia() joinAll( viewModelScope.launch(Dispatchers.IO) { - currentStreams = Crunchyroll.streams(currentEpisode.streamsLink) - println("stream: $Streams") + currentVersion = if (Preferences.preferSubbed) { + currentEpisode.versions.first { it.original } + } else { + currentEpisode.versions + .firstOrNull { it.audioLocale == currentAudioLocale.toLanguageTag() } + ?: currentEpisode.versions.first() + } + + currentStreams = Crunchyroll.streamsFromMediaGUID(currentVersion.mediaGUID) + Log.d(classTag, currentVersion.toString()) + println("stream: $currentStreams") }, viewModelScope.launch(Dispatchers.IO) { Crunchyroll.playheads(listOf(currentEpisode.id))[currentEpisode.id]?.let { @@ -215,7 +251,7 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) currentIntroMetadata = NoneDatalabIntro //Crunchyroll.datalabIntro(currentEpisode.id) } ) - Log.d(classTag, "playback: ${currentEpisode.streamsLink}") + Log.d(classTag, "streams: ${currentEpisode.streamsLink}") if (startPlayback) { playCurrentMedia() @@ -223,25 +259,25 @@ class PlayerViewModel(application: Application) : AndroidViewModel(application) } /** - * Play the current media from currentPlaybackCr. + * Play the current media from currentStreams. * - * @param seekPosition The seek position for the episode (default = 0). + * @param seekPosition The seek position for the media (default = 0). */ fun playCurrentMedia(seekPosition: Long = 0) { // get preferred stream url, set current language if it differs from the preferred one - val preferredLocale = currentLanguage + val preferredLocale = currentSubtitleLocale val fallbackLocal = Locale.US val url = when { currentStreams.data[0].adaptive_hls.containsKey(preferredLocale.toLanguageTag()) -> { currentStreams.data[0].adaptive_hls[preferredLocale.toLanguageTag()]?.url } currentStreams.data[0].adaptive_hls.containsKey(fallbackLocal.toLanguageTag()) -> { - currentLanguage = fallbackLocal + currentSubtitleLocale = fallbackLocal currentStreams.data[0].adaptive_hls[fallbackLocal.toLanguageTag()]?.url } else -> { // if no language tag is present use the first entry - currentLanguage = Locale.ROOT + currentSubtitleLocale = Locale.ROOT currentStreams.data[0].adaptive_hls.entries.first().value.url } } diff --git a/app/src/main/java/org/mosad/teapod/ui/activity/player/fragment/LanguageSettingsDialogFragment.kt b/app/src/main/java/org/mosad/teapod/ui/activity/player/fragment/LanguageSettingsDialogFragment.kt index f3b16f1..73e0bef 100644 --- a/app/src/main/java/org/mosad/teapod/ui/activity/player/fragment/LanguageSettingsDialogFragment.kt +++ b/app/src/main/java/org/mosad/teapod/ui/activity/player/fragment/LanguageSettingsDialogFragment.kt @@ -9,6 +9,7 @@ import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.LinearLayout import android.widget.TextView import androidx.core.view.children import androidx.fragment.app.DialogFragment @@ -24,8 +25,8 @@ class LanguageSettingsDialogFragment : DialogFragment() { private lateinit var model: PlayerViewModel private lateinit var binding: PlayerLanguageSettingsBinding - private var selectedLocale = Locale.ROOT - private var selectedView: View? = null + private var selectedSubtitleLocale = Locale.ROOT + private var selectedAudioLocale = Locale.ROOT companion object { const val TAG = "LanguageSettingsDialogFragment" @@ -35,7 +36,7 @@ class LanguageSettingsDialogFragment : DialogFragment() { super.onCreate(savedInstanceState) setStyle(STYLE_NO_TITLE, R.style.FullScreenDialogStyle) model = ViewModelProvider(requireActivity())[PlayerViewModel::class.java] - selectedLocale = model.currentLanguage + selectedSubtitleLocale = model.currentSubtitleLocale } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @@ -46,18 +47,41 @@ class LanguageSettingsDialogFragment : DialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + var selectedSubtitleView: TextView? = null model.currentStreams.data[0].adaptive_hls.keys.forEach { languageTag -> val locale = Locale.forLanguageTag(languageTag) - addLanguage(locale, locale == model.currentLanguage) { v -> - selectedLocale = locale - updateSelectedLanguage(v as TextView) + val subtitleView = addLanguage(binding.linearSubtitleLanguages, locale) { v -> + selectedSubtitleLocale = locale + updateSelectedLanguage(binding.linearSubtitleLanguages, v as TextView) + } + + // if the view is the currently selected one, highlight it + if (locale == model.currentSubtitleLocale) { + selectedSubtitleView = subtitleView + updateSelectedLanguage(binding.linearSubtitleLanguages, subtitleView) + } + } + + val currentAudioLocal = Locale.forLanguageTag(model.currentVersion.audioLocale) + var selectedAudioView: TextView? = null + model.currentEpisode.versions.forEach { version -> + val locale = Locale.forLanguageTag(version.audioLocale) + val audioView = addLanguage(binding.linearAudioLanguages, locale) { v -> + selectedAudioLocale = locale + updateSelectedLanguage(binding.linearAudioLanguages, v as TextView) + } + + // if the view is the currently selected one, highlight it + if (locale == currentAudioLocal) { + selectedAudioView = audioView + updateSelectedLanguage(binding.linearAudioLanguages, audioView) } } binding.buttonCloseLanguageSettings.setOnClickListener { dismiss() } binding.buttonCancel.setOnClickListener { dismiss() } binding.buttonSelect.setOnClickListener { - model.setLanguage(selectedLocale) + model.setLanguage(selectedAudioLocale, selectedSubtitleLocale) dismiss() } @@ -65,8 +89,12 @@ class LanguageSettingsDialogFragment : DialogFragment() { hideBars(requireDialog().window, binding.root) // scroll to the position of the view, if it's the selected language - binding.scrollLanguages.post { - binding.scrollLanguages.scrollTo(0, selectedView?.top ?: 0) + binding.scrollSubtitleLanguages.post { + binding.scrollSubtitleLanguages.scrollTo(0, selectedSubtitleView?.top ?: 0) + } + + binding.scrollAudioLanguages.post { + binding.scrollSubtitleLanguages.scrollTo(0, selectedAudioView?.top ?: 0) } } @@ -75,35 +103,32 @@ class LanguageSettingsDialogFragment : DialogFragment() { model.player.play() } - private fun addLanguage(locale: Locale, isSelected: Boolean, onClick: View.OnClickListener) { + private fun addLanguage(linear: LinearLayout, locale: Locale, onClick: View.OnClickListener): TextView { val text = TextView(context).apply { height = 96 gravity = Gravity.CENTER_VERTICAL text = if (locale == Locale.ROOT) context.getString(R.string.no_subtitles) else locale.displayLanguage setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f) - - if (isSelected) { - setTextColor(context.resources.getColor(R.color.textPrimaryDark, context.theme)) - setTypeface(null, Typeface.BOLD) - setCompoundDrawablesRelativeWithIntrinsicBounds(R.drawable.ic_baseline_check_24, 0, 0, 0) - compoundDrawablesRelative.getOrNull(0)?.setTint(Color.WHITE) - compoundDrawablePadding = 12 - - selectedView = this - } else { - setTextColor(context.resources.getColor(R.color.textSecondaryDark, context.theme)) - setPadding(75, 0, 0, 0) - } + setTextColor(context.resources.getColor(R.color.textSecondaryDark, context.theme)) + setPadding(75, 0, 0, 0) setOnClickListener(onClick) } - binding.linearLanguages.addView(text) + linear.addView(text) + + return text } - private fun updateSelectedLanguage(selected: TextView) { + /** + * Highlights the selected audio/subtitle language + * + * @param languageLayout The audio/subtitle Layout to update + * @param selected The newly selected language TextView + */ + private fun updateSelectedLanguage(languageLayout: LinearLayout, selected: TextView) { // rest all tf to not selected style - binding.linearLanguages.children.forEach { child -> + languageLayout.children.forEach { child -> if (child is TextView) { child.apply { setTextColor(context.resources.getColor(R.color.textPrimaryDark, context.theme)) 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 b8f2953..6495176 100644 --- a/app/src/main/java/org/mosad/teapod/util/Utils.kt +++ b/app/src/main/java/org/mosad/teapod/util/Utils.kt @@ -10,7 +10,6 @@ import androidx.core.view.WindowInsetsControllerCompat import androidx.fragment.app.Fragment import org.mosad.teapod.R import org.mosad.teapod.parser.crunchyroll.Collection -import org.mosad.teapod.parser.crunchyroll.ContinueWatchingItem import org.mosad.teapod.parser.crunchyroll.Item import org.mosad.teapod.ui.activity.player.PlayerActivity import java.util.* @@ -48,19 +47,6 @@ fun List.toItemMediaList(): List { } } -@JvmName("toItemMediaListContinueWatchingItem") -fun Collection.toItemMediaList(): List { - return items.map { - ItemMedia(it.panel.episodeMetadata.seriesId, it.panel.title, it.panel.images.thumbnail[0][0].source) - } -} - -fun List.toItemMediaList(): List { - return this.map { - ItemMedia(it.panel.episodeMetadata.seriesId, it.panel.title, it.panel.images.thumbnail[0][0].source) - } -} - fun Locale.toDisplayString(fallback: String): String { return if (this.displayLanguage.isNotEmpty() && this.displayCountry.isNotEmpty()) { "${this.displayLanguage} (${this.displayCountry})" diff --git a/app/src/main/java/org/mosad/teapod/util/adapter/MediaEpisodeListAdapter.kt b/app/src/main/java/org/mosad/teapod/util/adapter/MediaEpisodeListAdapter.kt index 4806167..cb92329 100644 --- a/app/src/main/java/org/mosad/teapod/util/adapter/MediaEpisodeListAdapter.kt +++ b/app/src/main/java/org/mosad/teapod/util/adapter/MediaEpisodeListAdapter.kt @@ -9,9 +9,9 @@ import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import org.mosad.teapod.R import org.mosad.teapod.databinding.ItemMediaBinding -import org.mosad.teapod.parser.crunchyroll.ContinueWatchingItem +import org.mosad.teapod.parser.crunchyroll.UpNextAccountItem -class MediaEpisodeListAdapter(private val onClickListener: OnClickListener, private val itemOffset: Int = 0) : ListAdapter(DiffCallback) { +class MediaEpisodeListAdapter(private val onClickListener: OnClickListener, private val itemOffset: Int = 0) : ListAdapter(DiffCallback) { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaViewHolder { val binding = ItemMediaBinding.inflate( @@ -37,7 +37,7 @@ class MediaEpisodeListAdapter(private val onClickListener: OnClickListener, priv inner class MediaViewHolder(val binding: ItemMediaBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(item: ContinueWatchingItem) { + fun bind(item: UpNextAccountItem) { val metadata = item.panel.episodeMetadata binding.textTitle.text = binding.root.context.getString(R.string.season_episode_title, @@ -57,17 +57,17 @@ class MediaEpisodeListAdapter(private val onClickListener: OnClickListener, priv } } - companion object DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame(oldItem: ContinueWatchingItem, newItem: ContinueWatchingItem): Boolean { + companion object DiffCallback : DiffUtil.ItemCallback() { + override fun areItemsTheSame(oldItem: UpNextAccountItem, newItem: UpNextAccountItem): Boolean { return oldItem.panel.id == newItem.panel.id } - override fun areContentsTheSame(oldItem: ContinueWatchingItem, newItem: ContinueWatchingItem): Boolean { + override fun areContentsTheSame(oldItem: UpNextAccountItem, newItem: UpNextAccountItem): Boolean { return oldItem == newItem } } - class OnClickListener(val clickListener: (item: ContinueWatchingItem) -> Unit) { - fun onClick(item: ContinueWatchingItem) = clickListener(item) + class OnClickListener(val clickListener: (item: UpNextAccountItem) -> Unit) { + fun onClick(item: UpNextAccountItem) = clickListener(item) } } \ No newline at end of file diff --git a/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt b/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt index 0c96901..98380dc 100644 --- a/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt +++ b/app/src/main/java/org/mosad/teapod/util/tmdb/TMDBApiController.kt @@ -67,7 +67,7 @@ class TMDBApiController { ): T = coroutineScope { val path = "$apiUrl$endpoint" val params = concatenate( - listOf("api_key" to apiKey, "language" to Preferences.preferredLocale.language), + listOf("api_key" to apiKey, "language" to Preferences.preferredSubtitleLocale.language), parameters ) diff --git a/app/src/main/res/layout/player_controls.xml b/app/src/main/res/layout/player_controls.xml index a27023d..c7f9d7c 100644 --- a/app/src/main/res/layout/player_controls.xml +++ b/app/src/main/res/layout/player_controls.xml @@ -131,7 +131,7 @@ android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginEnd="7dp" - android:text="@string/subtitles" + android:text="@string/language" android:textAllCaps="false" app:icon="@drawable/ic_baseline_subtitles_24" app:layout_constraintBottom_toBottomOf="parent" diff --git a/app/src/main/res/layout/player_language_settings.xml b/app/src/main/res/layout/player_language_settings.xml index 3aa30de..fda6fbd 100644 --- a/app/src/main/res/layout/player_language_settings.xml +++ b/app/src/main/res/layout/player_language_settings.xml @@ -36,7 +36,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginEnd="44dp" - android:text="@string/subtitles" android:textAlignment="center" android:textColor="@color/player_white" android:textSize="18sp" @@ -44,24 +43,80 @@ - - + android:layout_height="match_parent" + android:layout_weight="1" + android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + Nächste Folge Intro überspringen Sprache + Audio Untertitel Folgen Folge diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 77d8dd4..15a55d9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -112,6 +112,7 @@ %1$02d:%2$02d %1$d:%2$02d:%3$02d Language + Audio Subtitles Episodes Episode @@ -150,6 +151,7 @@ org.mosad.teapod.user_password org.mosad.teapod.prefer_secondary + org.mosad.teapod.preferred_audio_local org.mosad.teapod.preferred_local org.mosad.teapod.autoplay org.mosad.teapod.dev.settings diff --git a/build.gradle b/build.gradle index 519d2b2..ee0fd65 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.4.0' + classpath 'com.android.tools.build:gradle:7.4.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong