mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
Support showing content ratings for TmdbProvider (#705)
* Support showing content ratings for TmdbProvider
This commit is contained in:
parent
d0aed5e51a
commit
3c152e04d1
7 changed files with 213 additions and 5 deletions
|
@ -1193,6 +1193,7 @@ interface LoadResponse {
|
||||||
var syncData: MutableMap<String, String>
|
var syncData: MutableMap<String, String>
|
||||||
var posterHeaders: Map<String, String>?
|
var posterHeaders: Map<String, String>?
|
||||||
var backgroundPosterUrl: String?
|
var backgroundPosterUrl: String?
|
||||||
|
var contentRating: String?
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val malIdPrefix = malApi.idPrefix
|
private val malIdPrefix = malApi.idPrefix
|
||||||
|
@ -1511,7 +1512,37 @@ data class TorrentLoadResponse(
|
||||||
override var syncData: MutableMap<String, String> = mutableMapOf(),
|
override var syncData: MutableMap<String, String> = mutableMapOf(),
|
||||||
override var posterHeaders: Map<String, String>? = null,
|
override var posterHeaders: Map<String, String>? = null,
|
||||||
override var backgroundPosterUrl: String? = null,
|
override var backgroundPosterUrl: String? = null,
|
||||||
) : LoadResponse
|
override var contentRating: String? = null,
|
||||||
|
) : LoadResponse {
|
||||||
|
/**
|
||||||
|
* Secondary constructor for backwards compatibility without contentRating.
|
||||||
|
* Remove this constructor after there is a new stable release and extensions are updated to support contentRating.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
apiName: String,
|
||||||
|
magnet: String?,
|
||||||
|
torrent: String?,
|
||||||
|
plot: String?,
|
||||||
|
type: TvType = TvType.Torrent,
|
||||||
|
posterUrl: String? = null,
|
||||||
|
year: Int? = null,
|
||||||
|
rating: Int? = null,
|
||||||
|
tags: List<String>? = null,
|
||||||
|
duration: Int? = null,
|
||||||
|
trailers: MutableList<TrailerData> = mutableListOf(),
|
||||||
|
recommendations: List<SearchResponse>? = null,
|
||||||
|
actors: List<ActorData>? = null,
|
||||||
|
comingSoon: Boolean = false,
|
||||||
|
syncData: MutableMap<String, String> = mutableMapOf(),
|
||||||
|
posterHeaders: Map<String, String>? = null,
|
||||||
|
backgroundPosterUrl: String? = null,
|
||||||
|
) : this(
|
||||||
|
name, url, apiName, magnet, torrent, plot, type, posterUrl, year, rating, tags, duration, trailers,
|
||||||
|
recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
data class AnimeLoadResponse(
|
data class AnimeLoadResponse(
|
||||||
var engName: String? = null,
|
var engName: String? = null,
|
||||||
|
@ -1542,6 +1573,7 @@ data class AnimeLoadResponse(
|
||||||
override var nextAiring: NextAiring? = null,
|
override var nextAiring: NextAiring? = null,
|
||||||
override var seasonNames: List<SeasonData>? = null,
|
override var seasonNames: List<SeasonData>? = null,
|
||||||
override var backgroundPosterUrl: String? = null,
|
override var backgroundPosterUrl: String? = null,
|
||||||
|
override var contentRating: String? = null,
|
||||||
) : LoadResponse, EpisodeResponse {
|
) : LoadResponse, EpisodeResponse {
|
||||||
override fun getLatestEpisodes(): Map<DubStatus, Int?> {
|
override fun getLatestEpisodes(): Map<DubStatus, Int?> {
|
||||||
return episodes.map { (status, episodes) ->
|
return episodes.map { (status, episodes) ->
|
||||||
|
@ -1559,6 +1591,41 @@ data class AnimeLoadResponse(
|
||||||
episodes.count { ((it.season ?: Int.MIN_VALUE) < season) && it.season != 0 }
|
episodes.count { ((it.season ?: Int.MIN_VALUE) < season) && it.season != 0 }
|
||||||
} + episode
|
} + episode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secondary constructor for backwards compatibility without contentRating.
|
||||||
|
* Remove this constructor after there is a new stable release and extensions are updated to support contentRating.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
engName: String? = null,
|
||||||
|
japName: String? = null,
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
apiName: String,
|
||||||
|
type: TvType,
|
||||||
|
posterUrl: String? = null,
|
||||||
|
year: Int? = null,
|
||||||
|
episodes: MutableMap<DubStatus, List<Episode>> = mutableMapOf(),
|
||||||
|
showStatus: ShowStatus? = null,
|
||||||
|
plot: String? = null,
|
||||||
|
tags: List<String>? = null,
|
||||||
|
synonyms: List<String>? = null,
|
||||||
|
rating: Int? = null,
|
||||||
|
duration: Int? = null,
|
||||||
|
trailers: MutableList<TrailerData> = mutableListOf(),
|
||||||
|
recommendations: List<SearchResponse>? = null,
|
||||||
|
actors: List<ActorData>? = null,
|
||||||
|
comingSoon: Boolean = false,
|
||||||
|
syncData: MutableMap<String, String> = mutableMapOf(),
|
||||||
|
posterHeaders: Map<String, String>? = null,
|
||||||
|
nextAiring: NextAiring? = null,
|
||||||
|
seasonNames: List<SeasonData>? = null,
|
||||||
|
backgroundPosterUrl: String? = null,
|
||||||
|
) : this(
|
||||||
|
engName, japName, name, url, apiName, type, posterUrl, year, episodes, showStatus, plot, tags,
|
||||||
|
synonyms, rating, duration, trailers, recommendations, actors, comingSoon, syncData, posterHeaders,
|
||||||
|
nextAiring, seasonNames, backgroundPosterUrl, null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1610,7 +1677,36 @@ data class LiveStreamLoadResponse(
|
||||||
override var syncData: MutableMap<String, String> = mutableMapOf(),
|
override var syncData: MutableMap<String, String> = mutableMapOf(),
|
||||||
override var posterHeaders: Map<String, String>? = null,
|
override var posterHeaders: Map<String, String>? = null,
|
||||||
override var backgroundPosterUrl: String? = null,
|
override var backgroundPosterUrl: String? = null,
|
||||||
) : LoadResponse
|
override var contentRating: String? = null,
|
||||||
|
) : LoadResponse {
|
||||||
|
/**
|
||||||
|
* Secondary constructor for backwards compatibility without contentRating.
|
||||||
|
* Remove this constructor after there is a new stable release and extensions are updated to support contentRating.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
apiName: String,
|
||||||
|
dataUrl: String,
|
||||||
|
posterUrl: String? = null,
|
||||||
|
year: Int? = null,
|
||||||
|
plot: String? = null,
|
||||||
|
type: TvType = TvType.Live,
|
||||||
|
rating: Int? = null,
|
||||||
|
tags: List<String>? = null,
|
||||||
|
duration: Int? = null,
|
||||||
|
trailers: MutableList<TrailerData> = mutableListOf(),
|
||||||
|
recommendations: List<SearchResponse>? = null,
|
||||||
|
actors: List<ActorData>? = null,
|
||||||
|
comingSoon: Boolean = false,
|
||||||
|
syncData: MutableMap<String, String> = mutableMapOf(),
|
||||||
|
posterHeaders: Map<String, String>? = null,
|
||||||
|
backgroundPosterUrl: String? = null,
|
||||||
|
) : this(
|
||||||
|
name, url, apiName, dataUrl, posterUrl, year, plot, type, rating, tags, duration, trailers,
|
||||||
|
recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl, null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
data class MovieLoadResponse(
|
data class MovieLoadResponse(
|
||||||
override var name: String,
|
override var name: String,
|
||||||
|
@ -1633,7 +1729,36 @@ data class MovieLoadResponse(
|
||||||
override var syncData: MutableMap<String, String> = mutableMapOf(),
|
override var syncData: MutableMap<String, String> = mutableMapOf(),
|
||||||
override var posterHeaders: Map<String, String>? = null,
|
override var posterHeaders: Map<String, String>? = null,
|
||||||
override var backgroundPosterUrl: String? = null,
|
override var backgroundPosterUrl: String? = null,
|
||||||
) : LoadResponse
|
override var contentRating: String? = null,
|
||||||
|
) : LoadResponse {
|
||||||
|
/**
|
||||||
|
* Secondary constructor for backwards compatibility without contentRating.
|
||||||
|
* Remove this constructor after there is a new stable release and extensions are updated to support contentRating.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
apiName: String,
|
||||||
|
type: TvType,
|
||||||
|
dataUrl: String,
|
||||||
|
posterUrl: String? = null,
|
||||||
|
year: Int? = null,
|
||||||
|
plot: String? = null,
|
||||||
|
rating: Int? = null,
|
||||||
|
tags: List<String>? = null,
|
||||||
|
duration: Int? = null,
|
||||||
|
trailers: MutableList<TrailerData> = mutableListOf(),
|
||||||
|
recommendations: List<SearchResponse>? = null,
|
||||||
|
actors: List<ActorData>? = null,
|
||||||
|
comingSoon: Boolean = false,
|
||||||
|
syncData: MutableMap<String, String> = mutableMapOf(),
|
||||||
|
posterHeaders: Map<String, String>? = null,
|
||||||
|
backgroundPosterUrl: String? = null,
|
||||||
|
) : this(
|
||||||
|
name, url, apiName, type, dataUrl, posterUrl, year, plot, rating, tags, duration, trailers,
|
||||||
|
recommendations, actors, comingSoon, syncData, posterHeaders, backgroundPosterUrl,null
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun <T> MainAPI.newMovieLoadResponse(
|
suspend fun <T> MainAPI.newMovieLoadResponse(
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -1757,6 +1882,7 @@ data class TvSeriesLoadResponse(
|
||||||
override var nextAiring: NextAiring? = null,
|
override var nextAiring: NextAiring? = null,
|
||||||
override var seasonNames: List<SeasonData>? = null,
|
override var seasonNames: List<SeasonData>? = null,
|
||||||
override var backgroundPosterUrl: String? = null,
|
override var backgroundPosterUrl: String? = null,
|
||||||
|
override var contentRating: String? = null,
|
||||||
) : LoadResponse, EpisodeResponse {
|
) : LoadResponse, EpisodeResponse {
|
||||||
override fun getLatestEpisodes(): Map<DubStatus, Int?> {
|
override fun getLatestEpisodes(): Map<DubStatus, Int?> {
|
||||||
val maxSeason =
|
val maxSeason =
|
||||||
|
@ -1773,6 +1899,38 @@ data class TvSeriesLoadResponse(
|
||||||
(it.season ?: Int.MIN_VALUE) < season && it.season != 0
|
(it.season ?: Int.MIN_VALUE) < season && it.season != 0
|
||||||
} + episode
|
} + episode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Secondary constructor for backwards compatibility without contentRating.
|
||||||
|
* Remove this constructor after there is a new stable release and extensions are updated to support contentRating.
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
name: String,
|
||||||
|
url: String,
|
||||||
|
apiName: String,
|
||||||
|
type: TvType,
|
||||||
|
episodes: List<Episode>,
|
||||||
|
posterUrl: String? = null,
|
||||||
|
year: Int? = null,
|
||||||
|
plot: String? = null,
|
||||||
|
showStatus: ShowStatus? = null,
|
||||||
|
rating: Int? = null,
|
||||||
|
tags: List<String>? = null,
|
||||||
|
duration: Int? = null,
|
||||||
|
trailers: MutableList<TrailerData> = mutableListOf(),
|
||||||
|
recommendations: List<SearchResponse>? = null,
|
||||||
|
actors: List<ActorData>? = null,
|
||||||
|
comingSoon: Boolean = false,
|
||||||
|
syncData: MutableMap<String, String> = mutableMapOf(),
|
||||||
|
posterHeaders: Map<String, String>? = null,
|
||||||
|
nextAiring: NextAiring? = null,
|
||||||
|
seasonNames: List<SeasonData>? = null,
|
||||||
|
backgroundPosterUrl: String? = null,
|
||||||
|
) : this(
|
||||||
|
name, url, apiName, type, episodes, posterUrl, year, plot, showStatus, rating, tags, duration,
|
||||||
|
trailers, recommendations, actors, comingSoon, syncData, posterHeaders, nextAiring, seasonNames,
|
||||||
|
backgroundPosterUrl, null
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun MainAPI.newTvSeriesLoadResponse(
|
suspend fun MainAPI.newTvSeriesLoadResponse(
|
||||||
|
|
|
@ -151,6 +151,8 @@ open class TmdbProvider : MainAPI() {
|
||||||
recommendations = (this@toLoadResponse.recommendations
|
recommendations = (this@toLoadResponse.recommendations
|
||||||
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
|
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
|
||||||
addActors(credits?.cast?.toList().toActors())
|
addActors(credits?.cast?.toList().toActors())
|
||||||
|
|
||||||
|
contentRating = fetchContentRating(id, "US")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -193,6 +195,8 @@ open class TmdbProvider : MainAPI() {
|
||||||
recommendations = (this@toLoadResponse.recommendations
|
recommendations = (this@toLoadResponse.recommendations
|
||||||
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
|
?: this@toLoadResponse.similar)?.results?.map { it.toSearchResponse() }
|
||||||
addActors(credits?.cast?.toList().toActors())
|
addActors(credits?.cast?.toList().toActors())
|
||||||
|
|
||||||
|
contentRating = fetchContentRating(id, "US")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,6 +268,26 @@ open class TmdbProvider : MainAPI() {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
open suspend fun fetchContentRating(id: Int?, country: String): String? {
|
||||||
|
id ?: return null
|
||||||
|
|
||||||
|
val contentRatings = tmdb.tvService().content_ratings(id).awaitResponse().body()?.results
|
||||||
|
return if (!contentRatings.isNullOrEmpty()) {
|
||||||
|
contentRatings.firstOrNull { it: ContentRating ->
|
||||||
|
it.iso_3166_1 == country
|
||||||
|
}?.rating
|
||||||
|
} else {
|
||||||
|
val releaseDates = tmdb.moviesService().releaseDates(id).awaitResponse().body()?.results
|
||||||
|
val certification = releaseDates?.firstOrNull { it: ReleaseDatesResult ->
|
||||||
|
it.iso_3166_1 == country
|
||||||
|
}?.release_dates?.firstOrNull { it: ReleaseDate ->
|
||||||
|
!it.certification.isNullOrBlank()
|
||||||
|
}?.certification
|
||||||
|
|
||||||
|
certification
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Possible to add recommendations and such here.
|
// Possible to add recommendations and such here.
|
||||||
override suspend fun load(url: String): LoadResponse? {
|
override suspend fun load(url: String): LoadResponse? {
|
||||||
// https://www.themoviedb.org/movie/7445-brothers
|
// https://www.themoviedb.org/movie/7445-brothers
|
||||||
|
|
|
@ -677,6 +677,7 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
resultMetaYear.setText(d.yearText)
|
resultMetaYear.setText(d.yearText)
|
||||||
resultMetaDuration.setText(d.durationText)
|
resultMetaDuration.setText(d.durationText)
|
||||||
resultMetaRating.setText(d.ratingText)
|
resultMetaRating.setText(d.ratingText)
|
||||||
|
resultMetaContentRating.setText(d.contentRatingText)
|
||||||
resultCastText.setText(d.actorsText)
|
resultCastText.setText(d.actorsText)
|
||||||
resultNextAiring.setText(d.nextAiringEpisode)
|
resultNextAiring.setText(d.nextAiringEpisode)
|
||||||
resultNextAiringTime.setText(d.nextAiringDate)
|
resultNextAiringTime.setText(d.nextAiringDate)
|
||||||
|
@ -701,6 +702,11 @@ open class ResultFragmentPhone : FullScreenPlayer() {
|
||||||
resultCastItems.isGone = d.actors.isNullOrEmpty()
|
resultCastItems.isGone = d.actors.isNullOrEmpty()
|
||||||
(resultCastItems.adapter as? ActorAdaptor)?.updateList(d.actors ?: emptyList())
|
(resultCastItems.adapter as? ActorAdaptor)?.updateList(d.actors ?: emptyList())
|
||||||
|
|
||||||
|
if (d.contentRatingText == null) {
|
||||||
|
// If there is no rating to display, we don't want an empty gap
|
||||||
|
resultMetaContentRating.width = 0
|
||||||
|
}
|
||||||
|
|
||||||
if (syncModel.addSyncs(d.syncData)) {
|
if (syncModel.addSyncs(d.syncData)) {
|
||||||
syncModel.updateMetaAndUser()
|
syncModel.updateMetaAndUser()
|
||||||
syncModel.updateSynced()
|
syncModel.updateSynced()
|
||||||
|
|
|
@ -826,6 +826,7 @@ class ResultFragmentTv : Fragment() {
|
||||||
resultMetaYear.setText(d.yearText)
|
resultMetaYear.setText(d.yearText)
|
||||||
resultMetaDuration.setText(d.durationText)
|
resultMetaDuration.setText(d.durationText)
|
||||||
resultMetaRating.setText(d.ratingText)
|
resultMetaRating.setText(d.ratingText)
|
||||||
|
resultMetaContentRating.setText(d.contentRatingText)
|
||||||
resultCastText.setText(d.actorsText)
|
resultCastText.setText(d.actorsText)
|
||||||
resultNextAiring.setText(d.nextAiringEpisode)
|
resultNextAiring.setText(d.nextAiringEpisode)
|
||||||
resultNextAiringTime.setText(d.nextAiringDate)
|
resultNextAiringTime.setText(d.nextAiringDate)
|
||||||
|
@ -865,6 +866,11 @@ class ResultFragmentTv : Fragment() {
|
||||||
(resultCastItems.adapter as? ActorAdaptor)?.updateList(
|
(resultCastItems.adapter as? ActorAdaptor)?.updateList(
|
||||||
d.actors ?: emptyList()
|
d.actors ?: emptyList()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (d.contentRatingText == null) {
|
||||||
|
// If there is no rating to display, we don't want an empty gap
|
||||||
|
resultMetaContentRating.width = 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is Resource.Loading -> {
|
is Resource.Loading -> {
|
||||||
|
|
|
@ -118,6 +118,7 @@ data class ResultData(
|
||||||
val plotText: UiText,
|
val plotText: UiText,
|
||||||
val apiName: UiText,
|
val apiName: UiText,
|
||||||
val ratingText: UiText?,
|
val ratingText: UiText?,
|
||||||
|
val contentRatingText: UiText?,
|
||||||
val vpnText: UiText?,
|
val vpnText: UiText?,
|
||||||
val metaText: UiText?,
|
val metaText: UiText?,
|
||||||
val durationText: UiText?,
|
val durationText: UiText?,
|
||||||
|
@ -249,6 +250,7 @@ fun LoadResponse.toResultData(repo: APIRepository): ResultData {
|
||||||
apiName = txt(apiName),
|
apiName = txt(apiName),
|
||||||
ratingText = rating?.div(1000f)
|
ratingText = rating?.div(1000f)
|
||||||
?.let { if (it <= 0.1f) null else txt(R.string.rating_format, it) },
|
?.let { if (it <= 0.1f) null else txt(R.string.rating_format, it) },
|
||||||
|
contentRatingText = txt(contentRating),
|
||||||
vpnText = txt(
|
vpnText = txt(
|
||||||
when (repo.vpnStatus) {
|
when (repo.vpnStatus) {
|
||||||
VPNStatus.None -> null
|
VPNStatus.None -> null
|
||||||
|
|
|
@ -360,10 +360,16 @@
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/result_meta_site"
|
android:id="@+id/result_meta_site"
|
||||||
style="@style/SmallBlackButton"
|
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
|
style="@style/SmallBlackButton"
|
||||||
tools:text="Gogoanime" />
|
tools:text="Gogoanime" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/result_meta_content_rating"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
style="@style/SmallBlackButton"
|
||||||
|
tools:text="PG-13" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/result_meta_type"
|
android:id="@+id/result_meta_type"
|
||||||
style="@style/ResultInfoText"
|
style="@style/ResultInfoText"
|
||||||
|
|
|
@ -391,10 +391,16 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/result_meta_site"
|
android:id="@+id/result_meta_site"
|
||||||
style="@style/SmallWhiteButton"
|
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
|
style="@style/SmallWhiteButton"
|
||||||
tools:text="Gogoanime" />
|
tools:text="Gogoanime" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/result_meta_content_rating"
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
style="@style/SmallWhiteButton"
|
||||||
|
tools:text="PG-13" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/result_meta_type"
|
android:id="@+id/result_meta_type"
|
||||||
style="@style/ResultInfoText"
|
style="@style/ResultInfoText"
|
||||||
|
|
Loading…
Reference in a new issue