Skip to content

Commit

Permalink
#108 Browse Movies and Shows by genre
Browse files Browse the repository at this point in the history
  • Loading branch information
moallemi authored Aug 11, 2024
2 parents bd43cdd + 235a0d7 commit f0d10a2
Show file tree
Hide file tree
Showing 44 changed files with 607 additions and 67 deletions.
1 change: 1 addition & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ dependencies {
implementation(project(":feature:trakt-login"))
implementation(project(":feature:search"))
implementation(project(":feature:video-thumbnail-grid"))
implementation(project(":feature:video-thumbnail-grid-genre"))

implementation(project(":core:libs:logger"))
implementation(project(":core:libs:tracker"))
Expand Down
19 changes: 19 additions & 0 deletions app/src/main/java/io/filmtime/ui/navigation/FilmTimeNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import io.filmtime.feature.show.detail.showDetailScreen
import io.filmtime.feature.shows.showsGraph
import io.filmtime.feature.trakt.login.navigateToTraktLogin
import io.filmtime.feature.trakt.login.traktLoginScreen
import io.filmtime.feature.video.thumbnail.grid.genre.navigateVideoGridByGenre
import io.filmtime.feature.video.thumbnail.grid.genre.videoThumbnailGridGenreScreen
import io.filmtime.feature.video.thumbnail.grid.navigateToVideoThumbnailGridScreen
import io.filmtime.feature.video.thumbnail.grid.videoThumbnailGridScreen

Expand Down Expand Up @@ -57,6 +59,7 @@ fun FilmTimeNavHost(
movieDetailScreen(rootRoute, navController)
showDetailScreen(rootRoute, navController)
playerScreen(rootRoute)
genreGridScreen(rootRoute, navController)
},
)

Expand All @@ -72,6 +75,7 @@ fun FilmTimeNavHost(
nestedGraphs = { rootRoute ->
videoThumbnailGridScreen(rootRoute, navController)
movieDetailScreen(rootRoute, navController)
genreGridScreen(rootRoute, navController)
},
)

Expand All @@ -88,6 +92,7 @@ fun FilmTimeNavHost(
videoThumbnailGridScreen(rootRoute, navController)
showDetailScreen(rootRoute = rootRoute, navController)
playerScreen(rootRoute = rootRoute)
genreGridScreen(rootRoute, navController)
},
)

Expand Down Expand Up @@ -130,6 +135,7 @@ private fun NavGraphBuilder.movieDetailScreen(
onStreamReady = navController::navigateToPlayer,
onCastItemClick = { _, _ -> },
onMovieClick = navController::navigateToMovieDetail,
onGenreClick = navController::navigateVideoGridByGenre,
onBack = navController::popBackStack,
)
}
Expand All @@ -144,6 +150,7 @@ private fun NavGraphBuilder.showDetailScreen(
onCastItemClick = { _, _ -> },
onSimilarClick = navController::navigateToShowDetail,
onBack = navController::popBackStack,
onGenreClick = navController::navigateVideoGridByGenre,
)
}

Expand All @@ -156,3 +163,15 @@ private fun NavGraphBuilder.traktLoginScreen(
onBack = navController::popBackStack,
)
}

private fun NavGraphBuilder.genreGridScreen(
rootRoute: DestinationRoute,
navController: NavHostController,
) {
videoThumbnailGridGenreScreen(
rootRoute = rootRoute,
onMovieClick = navController::navigateToMovieDetail,
onShowClick = navController::navigateToShowDetail,
onBack = navController::popBackStack,
)
}
1 change: 1 addition & 0 deletions core/ui/common/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,5 @@ dependencies {
implementation(project(":core:design-system"))

implementation(libs.lottie)
implementation(libs.paging.compose)
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,30 @@
package io.filmtime.core.ui.common.componnents

import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
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.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.unit.dp
import io.filmtime.core.designsystem.theme.PreviewFilmTimeTheme
import io.filmtime.core.designsystem.theme.ThemePreviews
import io.filmtime.data.model.PreviewMovie
import io.filmtime.data.model.VideoDetail
import io.filmtime.data.model.VideoGenre

@Composable
fun VideoInfo(
videoDetail: VideoDetail,
modifier: Modifier = Modifier,
onGenreClick: (VideoGenre) -> Unit,
) {
Column(
modifier = modifier
Expand All @@ -29,9 +36,10 @@ fun VideoInfo(
style = MaterialTheme.typography.titleMedium,
text = "Information",
)
InfoItem(
GenresInfoItem(
title = "Genres",
subTitle = videoDetail.genres.joinToString { it },
genres = videoDetail.genres,
onClick = onGenreClick,
)
InfoItem(
title = "Release Date",
Expand Down Expand Up @@ -75,12 +83,39 @@ private fun ColumnScope.InfoItem(
)
}

@Composable
private fun ColumnScope.GenresInfoItem(
title: String,
genres: List<VideoGenre>,
onClick: (VideoGenre) -> Unit,
) {
Text(
style = MaterialTheme.typography.bodyMedium,
text = title,
)
LazyRow {
items(items = genres) { genre ->
Text(
modifier = Modifier
.clip(RoundedCornerShape(4.dp))
.clickable { onClick(genre) }
.padding(horizontal = 8.dp, vertical = 4.dp),
style = MaterialTheme.typography.bodySmall.copy(
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.7f),
),
text = genre.name,
)
}
}
}

@ThemePreviews
@Composable
private fun VideoAboutInfoPreview() {
PreviewFilmTimeTheme {
VideoInfo(
videoDetail = VideoDetail.PreviewMovie,
onGenreClick = {},
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package io.filmtime.core.ui.common.componnents

import android.util.Log
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.paging.compose.LazyPagingItems
import io.filmtime.core.designsystem.plus
import io.filmtime.data.model.VideoThumbnail
import io.filmtime.data.model.VideoType

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun VideoThumbnailGrid(
contentPadding: PaddingValues,
pagedList: LazyPagingItems<VideoThumbnail>,
onMovieClick: (tmdbId: Int) -> Unit,
onShowClick: (tmdbId: Int) -> Unit,
) {
LazyVerticalGrid(
columns = GridCells.Adaptive(100.dp),
modifier = Modifier,
contentPadding = contentPadding + PaddingValues(16.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
items(
pagedList.itemCount,
key = { index -> pagedList[index]?.ids?.tmdbId ?: index },
) { index ->
val videoThumbnail = pagedList[index]
if (videoThumbnail != null) {
VideoThumbnailCard(
modifier = Modifier
.animateItemPlacement()
.fillMaxWidth()
.aspectRatio(2 / 3f),
videoThumbnail = videoThumbnail,
onClick = {
videoThumbnail.ids.tmdbId?.let {
when (videoThumbnail.type) {
VideoType.Movie -> onMovieClick(it)
VideoType.Show -> onShowClick(it)
}
} ?: run {
Log.e("VideoThumbnailGrid", "tmdbId is null")
}
},
)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ fun VideoThumbnailInfo(
color = MaterialTheme.colorScheme.onSurface,
),
) {
Text(text = videoDetail.genres.firstOrNull().orEmpty())
Text(text = videoDetail.genres.first().name)
Text(text = "\u2022")
Text(text = videoDetail.year.toString())
videoDetail.runtime?.let { runtime ->
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ interface TmdbMoviesRemoteSource {

suspend fun getCollection(collectionId: Int): Result<MovieCollection, GeneralError>

suspend fun getByGenres(page: Int, genresId: List<Long>): Result<List<VideoThumbnail>, GeneralError>

companion object {
const val PAGE_SIZE = 20 // TMDB API default page size
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.filmtime.data.model.Result
import io.filmtime.data.model.VideoDetail
import io.filmtime.data.model.VideoThumbnail
import io.filmtime.data.network.TmdbCollectionService
import io.filmtime.data.network.TmdbDiscoverService
import io.filmtime.data.network.TmdbErrorResponse
import io.filmtime.data.network.TmdbMoviesService
import io.filmtime.data.network.TmdbVideoListResponse
Expand All @@ -16,7 +17,7 @@ import javax.inject.Inject
internal class TmdbMoviesRemoteSourceImpl @Inject constructor(
private val tmdbMoviesService: TmdbMoviesService,
private val tmdbCollectionService: TmdbCollectionService,
private val tmdbSearchRemoteSource: TmdbSearchRemoteSource,
private val tmdbDiscoverService: TmdbDiscoverService,
) : TmdbMoviesRemoteSource {

override suspend fun trendingMovies(page: Int): Result<List<VideoThumbnail>, GeneralError> =
Expand Down Expand Up @@ -100,6 +101,11 @@ internal class TmdbMoviesRemoteSourceImpl @Inject constructor(
is NetworkResponse.UnknownError -> Result.Failure(GeneralError.UnknownError(result.error))
}

override suspend fun getByGenres(page: Int, genresId: List<Long>): Result<List<VideoThumbnail>, GeneralError> =
getMovieList {
tmdbDiscoverService.getMovies(page, genresId.map { it.toString() })
}

override suspend fun upcomingMovies(
page: Int,
): Result<List<VideoThumbnail>, GeneralError> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ interface TmdbShowsRemoteSource {

suspend fun episodesBySeason(showId: Int, seasonNumber: Int): Result<List<EpisodeThumbnail>, GeneralError>

suspend fun getByGenres(page: Int, genresId: List<Long>): Result<List<VideoThumbnail>, GeneralError>

companion object {
const val PAGE_SIZE = 20 // TMDB API default page size
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.filmtime.data.model.Person
import io.filmtime.data.model.Result
import io.filmtime.data.model.VideoDetail
import io.filmtime.data.model.VideoThumbnail
import io.filmtime.data.network.TmdbDiscoverService
import io.filmtime.data.network.TmdbErrorResponse
import io.filmtime.data.network.TmdbShowListResponse
import io.filmtime.data.network.TmdbShowsService
Expand All @@ -14,6 +15,7 @@ import javax.inject.Inject

class TmdbShowsRemoteSourceImpl @Inject constructor(
private val tmdbShowsService: TmdbShowsService,
private val tmdbDiscoverService: TmdbDiscoverService,
) : TmdbShowsRemoteSource {

override suspend fun showDetails(showId: Int): Result<VideoDetail, GeneralError> =
Expand Down Expand Up @@ -115,6 +117,11 @@ class TmdbShowsRemoteSourceImpl @Inject constructor(
is NetworkResponse.UnknownError -> Result.Failure(GeneralError.UnknownError(response.error))
}

override suspend fun getByGenres(page: Int, genresId: List<Long>): Result<List<VideoThumbnail>, GeneralError> =
getShowsList {
tmdbDiscoverService.getShows(page, genresId.map { it.toString() })
}

override suspend fun similar(showId: Int): Result<List<VideoThumbnail>, GeneralError> =
getShowsList { tmdbShowsService.getSimilar(showId) }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package io.filmtime.data.api.tmdb

import io.filmtime.data.model.SearchResult.Person
import io.filmtime.data.model.VideoDetail
import io.filmtime.data.model.VideoGenre
import io.filmtime.data.model.VideoId
import io.filmtime.data.model.VideoThumbnail
import io.filmtime.data.model.VideoType
Expand All @@ -25,7 +26,7 @@ fun TmdbMovieDetailsResponse.toVideoDetail() =
posterUrl = TMDB_BASE_IMAGE_URL.plus(posterPath),
coverUrl = TMDB_BASE_IMAGE_URL.plus(backdropPath),
year = releaseDate?.takeIf { it.isNotEmpty() }?.take(4)?.toInt() ?: 0,
genres = genres?.mapNotNull { it.name } ?: listOf<String>(),
genres = genres?.map { VideoGenre(it.id!!, it.name!!) } ?: listOf(),
originalLanguage = originalLanguage,
spokenLanguages = spokenLanguages?.map { it.englishName ?: "" }?.filter { it.isNotEmpty() }
?: listOf(),
Expand All @@ -51,7 +52,7 @@ fun TmdbShowDetailsResponse.toVideoDetail() =
posterUrl = TMDB_BASE_IMAGE_URL.plus(posterPath),
coverUrl = TMDB_BASE_IMAGE_URL.plus(backdropPath),
year = firstAirDate?.take(4)?.toInt() ?: 0,
genres = genres?.mapNotNull { it.name } ?: listOf(),
genres = genres?.map { VideoGenre(it.id!!, it.name!!) } ?: listOf(),
originalLanguage = originalLanguage,
spokenLanguages = spokenLanguages?.map { it.englishName ?: "" }?.filter { it.isNotEmpty() } ?: listOf(),
description = overview ?: "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ data class VideoDetail(
val posterUrl: String,
val coverUrl: String,
val year: Int,
val genres: List<String>,
val genres: List<VideoGenre>,
val originalLanguage: String?,
val spokenLanguages: List<String>,
val description: String,
Expand All @@ -31,7 +31,7 @@ val VideoDetail.Companion.PreviewMovie: VideoDetail
posterUrl = "",
coverUrl = "",
year = 2024,
genres = listOf("Action", "Adventure"),
genres = listOf(VideoGenre(1L, "Action"), VideoGenre(2, "Adventure")),
originalLanguage = "en",
spokenLanguages = listOf("en"),
description = "Snatched from the Green Place of Many Mothers, young Furiosa falls into the hands of a great biker" +
Expand All @@ -56,7 +56,7 @@ val VideoDetail.Companion.PreviewShow: VideoDetail
posterUrl = "",
coverUrl = "",
year = 2019,
genres = listOf("Action", "Adventure"),
genres = listOf(VideoGenre(1L, "Action"), VideoGenre(2, "Adventure")),
originalLanguage = "en",
spokenLanguages = listOf("en"),
description = "Geralt of Rivia, a mutated monster-hunter for hire, journeys toward his destiny in a turbulent" +
Expand Down
6 changes: 6 additions & 0 deletions data/model/src/main/java/io/filmtime/data/model/VideoGenre.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package io.filmtime.data.model

data class VideoGenre(
val id: Long,
val name: String,
)
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,11 @@ object NetworkModule {
return NetworkCallAdapterFactory()
}

@Provides
@Singleton
fun providesDiscoverService(retrofit: Retrofit): TmdbDiscoverService =
retrofit.create(TmdbDiscoverService::class.java)

@Provides
@Singleton
fun providesCollectionService(retrofit: Retrofit): TmdbCollectionService =
Expand Down
Loading

0 comments on commit f0d10a2

Please sign in to comment.