Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature rating sites #112

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.filmtime.core.ui.common.componnents

import androidx.annotation.DrawableRes
import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp

@Composable
fun RatingCard(
@DrawableRes icon: Int,
rate: String,
votes: String,
onClick: (() -> Unit)?,
) {
Row(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
modifier = Modifier
.clip(RoundedCornerShape(4.dp))
.then(
if (onClick != null) Modifier.clickable { onClick.invoke() } else Modifier,
)
.padding(vertical = 2.dp, horizontal = 4.dp),
) {
Image(
modifier = Modifier.size(28.dp),
painter = painterResource(icon),
contentDescription = null,
)
Column {
Text(
text = rate,
fontWeight = FontWeight.Bold,
)
Text(
text = votes,
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
)
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
package io.filmtime.core.ui.common.componnents

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import io.filmtime.core.designsystem.theme.PreviewFilmTimeTheme
import io.filmtime.core.designsystem.theme.ThemePreviews
Expand All @@ -27,6 +21,7 @@ import io.filmtime.data.model.Ratings
fun RatingsInfo(
modifier: Modifier = Modifier,
ratings: Ratings,
onRatingClick: (String) -> Unit,
) {
CompositionLocalProvider(
LocalContentColor provides MaterialTheme.colorScheme.onSurface,
Expand All @@ -36,87 +31,47 @@ fun RatingsInfo(
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally),
) {
ratings.imdb?.let {
Image(
modifier = Modifier.size(28.dp),
painter = painterResource(R.drawable.ic_imdb),
contentDescription = null,
ratings.imdb?.let { rate ->
RatingCard(
icon = R.drawable.ic_imdb,
rate = "${rate.rating}",
votes = ratings.imdb?.votes ?: "N/A",
onClick = rate.link?.let { { onRatingClick(rate.link!!) } },
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Why do not we handle the link click and hoist this url to parent composable while we can sort it out here?
  2. Please try to avoid using !! as much as possible. Although in this case rate.link!! will never be null, it takes time for someone reading the code to understand why. They might need to verify whether a null case is possible and if an exception could be thrown. Using a safer approach can make the code more readable and maintainable.
Suggested change
onClick = rate.link?.let { { onRatingClick(rate.link!!) } },
onClick = rate.link?.let { { onRatingClick(it) } },

)
Column {
Text(
text = "${ratings.imdb?.rating}",
fontWeight = FontWeight.Bold,
)
Text(
text = ratings.imdb?.votes ?: "N/A",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
)
}
Spacer(modifier = Modifier.width(8.dp))
}

ratings.rottenTomatoes?.let {
Image(
modifier = Modifier.size(24.dp),
painter = if (it.info == "Fresh") {
painterResource(R.drawable.ic_rt_fresh)
ratings.rottenTomatoes?.let { rate ->
RatingCard(
icon = if (rate.info == "Fresh") {
R.drawable.ic_rt_fresh
} else {
painterResource(R.drawable.ic_rt_rotten)
R.drawable.ic_rt_rotten
},
contentDescription = null,
rate = "${ratings.rottenTomatoes?.rating}",
votes = ratings.rottenTomatoes?.info.orEmpty(),
onClick = rate.link?.let { { onRatingClick(rate.link!!) } },
)
Column {
Text(
text = "${ratings.rottenTomatoes?.rating}",
fontWeight = FontWeight.Bold,
)
Text(
text = ratings.rottenTomatoes?.info.orEmpty(),
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
)
}
Spacer(modifier = Modifier.width(8.dp))
}

ratings.tmdb?.let {
Image(
modifier = Modifier.size(28.dp),
painter = painterResource(R.drawable.ic_tmdb_square),
contentDescription = null,
ratings.tmdb?.let { rate ->
RatingCard(
icon = R.drawable.ic_tmdb_square,
rate = "${ratings.tmdb?.rating}",
votes = "${ratings.tmdb?.votes}",
onClick = rate.link?.let { { onRatingClick(rate.link!!) } },
)
Column {
Text(
text = "${ratings.tmdb?.rating}",
fontWeight = FontWeight.Bold,
)
Text(
text = "${ratings.tmdb?.votes}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
)
}
Spacer(modifier = Modifier.width(8.dp))
}

ratings.trakt?.let {
Image(
modifier = Modifier.size(36.dp),
painter = painterResource(R.drawable.ic_trakt),
contentDescription = null,
ratings.trakt?.let { rate ->
RatingCard(
icon = R.drawable.ic_trakt,
rate = "${rate.rating}",
votes = "${rate.votes}",
onClick = rate.link?.let { { onRatingClick(rate.link!!) } },
)
Column {
Text(
text = "${ratings.trakt?.rating}",
fontWeight = FontWeight.Bold,
)
Text(
text = "${ratings.trakt?.votes}",
style = MaterialTheme.typography.bodySmall,
color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
)
}
}
}
}
Expand All @@ -128,6 +83,7 @@ private fun RatingsInfoPreview() {
PreviewFilmTimeTheme {
RatingsInfo(
ratings = Ratings.Preview,
onRatingClick = {},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ fun VideoThumbnailInfo(
onAddBookmark: () -> Unit,
onRemoveBookmark: () -> Unit,
primaryButton: @Composable () -> Unit,
onRatingClick: (String) -> Unit,
traktHistoryButton: @Composable RowScope.() -> Unit,
) {
Column(
Expand Down Expand Up @@ -93,6 +94,7 @@ fun VideoThumbnailInfo(
RatingsInfo(
modifier = Modifier.fillMaxWidth(),
ratings = ratings,
onRatingClick = onRatingClick,
)
}
}
Expand Down Expand Up @@ -126,6 +128,7 @@ private fun MovieDetailScreenPreview() {
Text("Add to history")
}
},
onRatingClick = {},
)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.filmtime.data.api.trakt

import io.filmtime.data.model.ExternalID
import io.filmtime.data.model.GeneralError
import io.filmtime.data.model.Result

Expand All @@ -11,4 +12,6 @@ enum class TraktMediaType(val queryName: String) {
interface TraktSearchRemoteSource {

suspend fun getByTmdbId(id: Int, type: TraktMediaType): Result<Int, GeneralError>

suspend fun getOtherWebsiteIds(id: Int, type: TraktMediaType): Result<ExternalID, GeneralError>
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.filmtime.data.api.trakt

import io.filmtime.data.model.ExternalID
import io.filmtime.data.model.GeneralError
import io.filmtime.data.model.Result
import io.filmtime.data.network.adapter.NetworkResponse
Expand Down Expand Up @@ -39,4 +40,48 @@ internal class TraktSearchRemoteSourceImpl @Inject constructor(
is NetworkResponse.UnknownError -> Result.Failure(GeneralError.UnknownError(result.error))
}
}

override suspend fun getOtherWebsiteIds(id: Int, type: TraktMediaType): Result<ExternalID, GeneralError> {
return when (val result = traktIDLookupService.movieIDLookup(idType = "tmdb", id = id, type = type.queryName)) {
is NetworkResponse.ApiError -> {
val errorResponse = result.body
Result.Failure(GeneralError.ApiError(errorResponse.error, result.code))
}

is NetworkResponse.NetworkError -> Result.Failure(GeneralError.NetworkError)
is NetworkResponse.Success -> {
val body = result.body ?: emptyList()

val mediaItem = body.find { response ->
if (type == TraktMediaType.Movie) {
response.movie?.ids?.tmdb == id
} else {
response.show?.ids?.tmdb == id
}
}

val externalID = if (type == TraktMediaType.Movie) {
val ids = mediaItem?.movie?.ids

ExternalID(
traktId = ids?.trakt.toString(),
imdbId = ids?.imdb,
tmdbId = ids?.tmdb.toString(),
slug = ids?.slug,
)
} else {
val ids = mediaItem?.show?.ids
ExternalID(
traktId = ids?.trakt.toString(),
imdbId = ids?.imdb,
tmdbId = ids?.tmdb.toString(),
slug = ids?.slug,
)
}
Result.Success(externalID)
}

is NetworkResponse.UnknownError -> Result.Failure(GeneralError.UnknownError(result.error))
}
}
}
8 changes: 8 additions & 0 deletions data/model/src/main/java/io/filmtime/data/model/ExternalID.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package io.filmtime.data.model

data class ExternalID(
val traktId: String?,
val tmdbId: String?,
val imdbId: String?,
val slug: String?,
)
1 change: 1 addition & 0 deletions data/model/src/main/java/io/filmtime/data/model/Ratings.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ data class RatingInfo(
val rating: String? = null,
val votes: String? = null,
val info: String? = null,
val link: String? = null,
)

val Ratings.Companion.Preview: Ratings
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import io.filmtime.data.model.GeneralError
import io.filmtime.data.model.Ratings
import io.filmtime.data.model.Result
import io.filmtime.data.model.VideoType
import io.filmtime.data.model.VideoType.Movie
import io.filmtime.data.model.VideoType.Show
import io.filmtime.data.trakt.model.toTraktMediaType
import javax.inject.Inject

Expand All @@ -14,13 +16,42 @@ internal class TraktRepositoryImpl @Inject constructor(
private val traktSearchRemoteSource: TraktSearchRemoteSource,
) : TraktRepository {

companion object {
const val TRAKT_MOVIE_BASE_URL = "https://trakt.tv/movies/"
const val TRAKT_SHOWS_BASE_URL = "https://trakt.tv/shows/"
const val IMDB_MOVIE_BASE_URL = "https://imdb.com/title/"
const val TMDB_MOVIE_BASE_URL = "https://themoviedb.org/movie/"
const val TMDB_SHOW_BASE_URL = "https://themoviedb.org/tv/"
}

override suspend fun ratings(type: VideoType, tmdbId: Int): Result<Ratings, GeneralError> =
when (val traktIdResult = traktSearchRemoteSource.getByTmdbId(id = tmdbId, type = type.toTraktMediaType())) {
is Result.Success -> traktRemoteSource.ratings(
type = "${type.toTraktMediaType().queryName}s",
traktId = traktIdResult.data,
)
.mapSuccess { ratings -> ratings }
when (val traktIdResult = traktSearchRemoteSource.getOtherWebsiteIds(id = tmdbId, type = type.toTraktMediaType())) {
is Result.Success -> {
if (traktIdResult.data.traktId == null) Result.Failure("traktId is null")
traktRemoteSource.ratings(
type = "${type.toTraktMediaType().queryName}s",
traktId = traktIdResult.data.traktId!!.toInt(),
)
.mapSuccess { ratings ->
ratings.copy(
trakt = ratings.trakt?.copy(
link = when (type) {
Movie -> TRAKT_MOVIE_BASE_URL
Show -> TRAKT_SHOWS_BASE_URL
} + traktIdResult.data.traktId,
),
tmdb = ratings.tmdb?.copy(
link = when (type) {
Movie -> TMDB_MOVIE_BASE_URL
Show -> TMDB_SHOW_BASE_URL
} + traktIdResult.data.tmdbId,
),
imdb = ratings.imdb?.copy(
link = IMDB_MOVIE_BASE_URL + traktIdResult.data.imdbId,
),
)
}
}

is Result.Failure -> traktIdResult
}
Expand Down
1 change: 1 addition & 0 deletions feature/movie-detail/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ android {
}

dependencies {
implementation(project(":core:browser"))

implementation(project(":data:model"))

Expand Down
Loading