From c90c699a27391ff76fa883eb241724e98c2a3e42 Mon Sep 17 00:00:00 2001 From: Owen LeJeune Date: Thu, 17 Feb 2022 13:18:20 -0500 Subject: [PATCH] add tabs to top of movies and tv screens --- .../com/owenlejeune/tvtime/MainActivity.kt | 19 +-- .../tvtime/api/tmdb/HomePageService.kt | 16 +++ .../owenlejeune/tvtime/api/tmdb/MoviesApi.kt | 11 +- .../tvtime/api/tmdb/MoviesService.kt | 16 ++- .../com/owenlejeune/tvtime/api/tmdb/TvApi.kt | 11 +- .../owenlejeune/tvtime/api/tmdb/TvService.kt | 23 +++- .../tvtime/api/tmdb/model/DetailedItem.kt | 2 +- .../tvtime/api/tmdb/model/DetailedMovie.kt | 4 +- .../tvtime/api/tmdb/model/DetailedTv.kt | 4 +- .../tvtime/api/tmdb/model/HomePageMovie.kt | 9 ++ .../api/tmdb/model/HomePageMoviesResponse.kt | 9 ++ ...pularTvResponse.kt => HomePageResponse.kt} | 4 +- .../tvtime/api/tmdb/model/HomePageTv.kt | 9 ++ .../api/tmdb/model/HomePageTvResponse.kt | 9 ++ .../tvtime/api/tmdb/model/PopularMovie.kt | 9 -- .../api/tmdb/model/PopularMoviesResponse.kt | 9 -- .../tvtime/api/tmdb/model/PopularTv.kt | 9 -- .../tvtime/api/tmdb/model/TmdbItem.kt | 8 +- .../tvtime/ui/navigation/MainTabNavItem.kt | 51 ++++++++ .../tvtime/ui/navigation/Routes.kt | 20 ++- .../tvtime/ui/screens/DetailView.kt | 27 ++-- .../owenlejeune/tvtime/ui/screens/MainView.kt | 13 +- .../tvtime/ui/screens/MediaViewType.kt | 6 + .../tvtime/ui/screens/tabs/MoviesTab.kt | 39 ------ .../tvtime/ui/screens/tabs/TvTab.kt | 36 ------ .../tabs/{ => bottom}/FavouritesTab.kt | 2 +- .../tvtime/ui/screens/tabs/bottom/MediaTab.kt | 69 ++++++++++ .../screens/tabs/{ => bottom}/SettingsTab.kt | 2 +- .../tvtime/ui/screens/tabs/top/TabsCommon.kt | 122 ++++++++++++++++++ app/src/main/res/values/strings.xml | 6 + 30 files changed, 415 insertions(+), 159 deletions(-) create mode 100644 app/src/main/java/com/owenlejeune/tvtime/api/tmdb/HomePageService.kt create mode 100644 app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageMovie.kt create mode 100644 app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageMoviesResponse.kt rename app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/{PopularTvResponse.kt => HomePageResponse.kt} (69%) create mode 100644 app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageTv.kt create mode 100644 app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageTvResponse.kt delete mode 100644 app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularMovie.kt delete mode 100644 app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularMoviesResponse.kt delete mode 100644 app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularTv.kt create mode 100644 app/src/main/java/com/owenlejeune/tvtime/ui/navigation/MainTabNavItem.kt create mode 100644 app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaViewType.kt delete mode 100644 app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/MoviesTab.kt delete mode 100644 app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/TvTab.kt rename app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/{ => bottom}/FavouritesTab.kt (92%) create mode 100644 app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/bottom/MediaTab.kt rename app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/{ => bottom}/SettingsTab.kt (97%) create mode 100644 app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/top/TabsCommon.kt diff --git a/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt b/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt index 1501bc9..04b887f 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/MainActivity.kt @@ -3,22 +3,20 @@ package com.owenlejeune.tvtime import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent -import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.layout.Box -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview -import androidx.core.view.WindowCompat +import androidx.navigation.NavHostController import androidx.navigation.compose.rememberNavController -import com.google.accompanist.systemuicontroller.rememberSystemUiController import com.owenlejeune.tvtime.ui.navigation.MainNavigationRoutes import com.owenlejeune.tvtime.ui.theme.TVTimeTheme class MainActivity : ComponentActivity() { +// private val appNavControllerProvider: (@Composable () -> NavHostController) by inject(named(NavControllers.APP)) + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { @@ -31,15 +29,20 @@ class MainActivity : ComponentActivity() { // } // val systemUiController = rememberSystemUiController() // systemUiController.setStatusBarColor(statusBarColor, !isSystemInDarkTheme()) - MyApp(displayUnderStatusBar = displayUnderStatusBar) + MyApp( + appNavController = rememberNavController(), + displayUnderStatusBar = displayUnderStatusBar + ) } } } @Composable -fun MyApp(displayUnderStatusBar: MutableState = mutableStateOf(false)) { +fun MyApp( + appNavController: NavHostController = rememberNavController(), + displayUnderStatusBar: MutableState = mutableStateOf(false) +) { TVTimeTheme { - val appNavController = rememberNavController() Box { MainNavigationRoutes(navController = appNavController, displayUnderStatusBar = displayUnderStatusBar) } diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/HomePageService.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/HomePageService.kt new file mode 100644 index 0000000..cb11eec --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/HomePageService.kt @@ -0,0 +1,16 @@ +package com.owenlejeune.tvtime.api.tmdb + +import com.owenlejeune.tvtime.api.tmdb.model.HomePageResponse +import retrofit2.Response + +interface HomePageService { + + suspend fun getNowPlaying(page: Int = 1): Response + + suspend fun getPopular(page: Int = 1): Response + + suspend fun getTopRated(page: Int = 1): Response + + suspend fun getUpcoming(page: Int = 1): Response + +} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/MoviesApi.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/MoviesApi.kt index 00f13f7..d9a7327 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/MoviesApi.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/MoviesApi.kt @@ -9,7 +9,16 @@ import retrofit2.http.Query interface MoviesApi { @GET("movie/popular") - suspend fun getPopularMovies(@Query("page") page: Int = 1): Response + suspend fun getPopularMovies(@Query("page") page: Int = 1): Response + + @GET("movie/now_playing") + suspend fun getNowPlayingMovies(@Query("page") page: Int = 1): Response + + @GET("movie/top_rated") + suspend fun getTopRatedMovies(@Query("page") page: Int = 1): Response + + @GET("movie/upcoming") + suspend fun getUpcomingMovies(@Query("page") page: Int = 1): Response @GET("movie/{id}") suspend fun getMovieById(@Path("id") id: Int): Response diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/MoviesService.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/MoviesService.kt index 39524ef..4e27309 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/MoviesService.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/MoviesService.kt @@ -4,14 +4,26 @@ import com.owenlejeune.tvtime.api.tmdb.model.* import org.koin.core.component.KoinComponent import retrofit2.Response -class MoviesService: KoinComponent, DetailService { +class MoviesService: KoinComponent, DetailService, HomePageService { private val service by lazy { TmdbClient().createMovieService() } - suspend fun getPopularMovies(page: Int = 1): Response { + override suspend fun getPopular(page: Int): Response { return service.getPopularMovies(page) } + override suspend fun getNowPlaying(page: Int): Response { + return service.getNowPlayingMovies(page) + } + + override suspend fun getTopRated(page: Int): Response { + return service.getTopRatedMovies(page) + } + + override suspend fun getUpcoming(page: Int): Response { + return service.getUpcomingMovies(page) + } + suspend fun getReleaseDates(id: Int): Response { return service.getReleaseDates(id) } diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TvApi.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TvApi.kt index 18785d5..11847ff 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TvApi.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TvApi.kt @@ -9,7 +9,16 @@ import retrofit2.http.Query interface TvApi { @GET("tv/popular") - suspend fun getPoplarTv(@Query("page") page: Int = 1): Response + suspend fun getPoplarTv(@Query("page") page: Int = 1): Response + + @GET("tv/top_rated") + suspend fun getTopRatedTv(@Query("page") page: Int = 1): Response + + @GET("tv/airing_today") + suspend fun getTvAiringToday(@Query("page") page: Int = 1): Response + + @GET("tv/on_the_air") + suspend fun getTvOnTheAir(@Query("page") page: Int = 1): Response @GET("tv/{id}") suspend fun getTvShowById(@Path("id") id: Int): Response diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TvService.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TvService.kt index dc58812..9c9f950 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TvService.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/TvService.kt @@ -1,17 +1,28 @@ package com.owenlejeune.tvtime.api.tmdb -import com.owenlejeune.tvtime.api.tmdb.model.CastAndCrew -import com.owenlejeune.tvtime.api.tmdb.model.ImageCollection -import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem -import com.owenlejeune.tvtime.api.tmdb.model.TvContentRatings +import com.owenlejeune.tvtime.api.tmdb.model.* import org.koin.core.component.KoinComponent import retrofit2.Response -class TvService: KoinComponent, DetailService { +class TvService: KoinComponent, DetailService, HomePageService { private val service by lazy { TmdbClient().createTvService() } - suspend fun getPopularTv(page: Int = 1) = service.getPoplarTv(page) + override suspend fun getPopular(page: Int): Response { + return service.getPoplarTv(page) + } + + override suspend fun getNowPlaying(page: Int): Response { + return service.getTvAiringToday(page) + } + + override suspend fun getTopRated(page: Int): Response { + return service.getTopRatedTv(page) + } + + override suspend fun getUpcoming(page: Int): Response { + return service.getTvOnTheAir(page) + } override suspend fun getById(id: Int): Response { return service.getTvShowById(id) diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedItem.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedItem.kt index 4bcc6e1..9dbe9ca 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedItem.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedItem.kt @@ -11,4 +11,4 @@ abstract class DetailedItem( @Transient open val status: String, @Transient open val tagline: String?, @Transient open val voteAverage: Float -): TmdbItem(id, title, posterPath) \ No newline at end of file +): TmdbItem(id, posterPath, title) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedMovie.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedMovie.kt index ed86bcd..af553b2 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedMovie.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedMovie.kt @@ -3,9 +3,9 @@ package com.owenlejeune.tvtime.api.tmdb.model import com.google.gson.annotations.SerializedName class DetailedMovie( - @SerializedName("id") override val id: Int, + id: Int, + posterPath: String?, @SerializedName("original_title") override val title: String, - @SerializedName("poster_path") override val posterPath: String?, @SerializedName("backdrop_path") override val backdropPath: String?, @SerializedName("genres") override val genres: List, @SerializedName("overview") override val overview: String?, diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedTv.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedTv.kt index fa90dfb..afd63ef 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedTv.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/DetailedTv.kt @@ -3,9 +3,9 @@ package com.owenlejeune.tvtime.api.tmdb.model import com.google.gson.annotations.SerializedName class DetailedTv( - @SerializedName("id") override val id: Int, + id: Int, + posterPath: String?, @SerializedName("name") override val title: String, - @SerializedName("poster_path") override val posterPath: String?, @SerializedName("backdrop_path") override val backdropPath: String?, @SerializedName("genres") override val genres: List, @SerializedName("overview") override val overview: String?, diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageMovie.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageMovie.kt new file mode 100644 index 0000000..8fb19fb --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageMovie.kt @@ -0,0 +1,9 @@ +package com.owenlejeune.tvtime.api.tmdb.model + +import com.google.gson.annotations.SerializedName + +class HomePageMovie( + id: Int, + posterPath: String?, + @SerializedName("title") override val title: String +): TmdbItem(id, posterPath, title) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageMoviesResponse.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageMoviesResponse.kt new file mode 100644 index 0000000..21bc8ec --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageMoviesResponse.kt @@ -0,0 +1,9 @@ +package com.owenlejeune.tvtime.api.tmdb.model + +import com.google.gson.annotations.SerializedName + +class HomePageMoviesResponse( + count: Int, + page: Int, + @SerializedName("results") override val results: List +): HomePageResponse(count, page, results) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularTvResponse.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageResponse.kt similarity index 69% rename from app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularTvResponse.kt rename to app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageResponse.kt index f734bce..ea3358d 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularTvResponse.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageResponse.kt @@ -2,8 +2,8 @@ package com.owenlejeune.tvtime.api.tmdb.model import com.google.gson.annotations.SerializedName -data class PopularTvResponse( +abstract class HomePageResponse( @SerializedName("total_results") val count: Int, @SerializedName("page") val page: Int, - @SerializedName("results") val tv: List + @Transient open val results: List ) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageTv.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageTv.kt new file mode 100644 index 0000000..ea4813e --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageTv.kt @@ -0,0 +1,9 @@ +package com.owenlejeune.tvtime.api.tmdb.model + +import com.google.gson.annotations.SerializedName + +class HomePageTv( + id: Int, + posterPath: String?, + @SerializedName("name") override val title: String, +): TmdbItem(id, posterPath, title) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageTvResponse.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageTvResponse.kt new file mode 100644 index 0000000..4b71474 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/HomePageTvResponse.kt @@ -0,0 +1,9 @@ +package com.owenlejeune.tvtime.api.tmdb.model + +import com.google.gson.annotations.SerializedName + +class HomePageTvResponse( + count: Int, + page: Int, + @SerializedName("results") override val results: List +): HomePageResponse(count, page, results) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularMovie.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularMovie.kt deleted file mode 100644 index 45d3c0d..0000000 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularMovie.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.owenlejeune.tvtime.api.tmdb.model - -import com.google.gson.annotations.SerializedName - -data class PopularMovie( - @SerializedName("id") override val id: Int, - @SerializedName("title") override val title: String, - @SerializedName("poster_path") override val posterPath: String? -): TmdbItem(id, title, posterPath) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularMoviesResponse.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularMoviesResponse.kt deleted file mode 100644 index 5c6a1c2..0000000 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularMoviesResponse.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.owenlejeune.tvtime.api.tmdb.model - -import com.google.gson.annotations.SerializedName - -data class PopularMoviesResponse( - @SerializedName("total_results") val count: Int, - @SerializedName("page") val page: Int, - @SerializedName("results") val movies: List -) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularTv.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularTv.kt deleted file mode 100644 index 95fe8f9..0000000 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/PopularTv.kt +++ /dev/null @@ -1,9 +0,0 @@ -package com.owenlejeune.tvtime.api.tmdb.model - -import com.google.gson.annotations.SerializedName - -data class PopularTv( - @SerializedName("id") override val id: Int, - @SerializedName("name") override val title: String, - @SerializedName("poster_path") override val posterPath: String? -): TmdbItem(id, title, posterPath) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/TmdbItem.kt b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/TmdbItem.kt index ac309ba..0ac1b96 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/TmdbItem.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/api/tmdb/model/TmdbItem.kt @@ -1,7 +1,9 @@ package com.owenlejeune.tvtime.api.tmdb.model +import com.google.gson.annotations.SerializedName + abstract class TmdbItem( - @Transient open val id: Int, - @Transient open val title: String, - @Transient open val posterPath: String? + @SerializedName("id") val id: Int, + @SerializedName("poster_path") val posterPath: String?, + @Transient open val title: String ) \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/MainTabNavItem.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/MainTabNavItem.kt new file mode 100644 index 0000000..0897c28 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/MainTabNavItem.kt @@ -0,0 +1,51 @@ +package com.owenlejeune.tvtime.ui.navigation + +import androidx.compose.runtime.Composable +import androidx.navigation.NavHostController +import com.owenlejeune.tvtime.R +import com.owenlejeune.tvtime.api.tmdb.HomePageService +import com.owenlejeune.tvtime.api.tmdb.model.HomePageResponse +import com.owenlejeune.tvtime.ui.screens.MediaViewType +import com.owenlejeune.tvtime.ui.screens.tabs.bottom.MediaTabContent +import com.owenlejeune.tvtime.utils.ResourceUtils +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import retrofit2.Response + +typealias NavComposableFun = @Composable (NavHostController, MediaViewType, MediaFetchFun) -> Unit + +private val screenContent: NavComposableFun = { appNavController, mediaViewType, mediaFetchFun -> + MediaTabContent(appNavController = appNavController, mediaType = mediaViewType, mediaFetchFun = mediaFetchFun) +} + +typealias MediaFetchFun = suspend (service: HomePageService, page: Int) -> Response + +abstract class TabNavItem(val route: String, val screen: NavComposableFun, val mediaFetchFun: MediaFetchFun): KoinComponent { + abstract val name: String +} + +sealed class MainTabNavItem(stringRes: Int, route: String, screen: NavComposableFun, mediaFetchFun: MediaFetchFun) + : TabNavItem(route, screen, mediaFetchFun) +{ + private val resourceUtils: ResourceUtils by inject() + + override val name = resourceUtils.getString(stringRes) + + companion object { + val MovieItems = listOf(Popular, TopRated, NowPlaying, Upcoming) + val TvItems = listOf(Popular, TopRated, AiringToday, OnTheAir) + + private val Items = listOf(NowPlaying, Popular, TopRated, Upcoming, AiringToday, OnTheAir) + + fun getByRoute(route: String?): MainTabNavItem? { + return Items.firstOrNull { it.route == route } + } + } + + object Popular: MainTabNavItem(R.string.nav_popular_title, "popular_route", screenContent, { s, p -> s.getPopular(p) } ) + object TopRated: MainTabNavItem(R.string.nav_top_rated_title, "top_rated_route", screenContent, { s, p -> s.getTopRated(p) } ) + object NowPlaying: MainTabNavItem(R.string.nav_now_playing_title, "now_playing_route", screenContent, { s, p -> s.getNowPlaying(p) } ) + object Upcoming: MainTabNavItem(R.string.nav_upcoming_title, "upcoming_route", screenContent, { s, p -> s.getUpcoming(p) } ) + object AiringToday: MainTabNavItem(R.string.nav_tv_airing_today_title, "airing_today_route", screenContent, { s, p -> s.getNowPlaying(p) } ) + object OnTheAir: MainTabNavItem(R.string.nav_tv_on_the_air, "on_the_air_route", screenContent, { s, p -> s.getUpcoming(p) } ) +} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt index fdb81d0..0b86f5a 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/navigation/Routes.kt @@ -3,19 +3,17 @@ package com.owenlejeune.tvtime.ui.navigation import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf -import androidx.navigation.NavController import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.navArgument import com.owenlejeune.tvtime.ui.screens.DetailView -import com.owenlejeune.tvtime.ui.screens.DetailViewType import com.owenlejeune.tvtime.ui.screens.MainAppView -import com.owenlejeune.tvtime.ui.screens.tabs.FavouritesTab -import com.owenlejeune.tvtime.ui.screens.tabs.MoviesTab -import com.owenlejeune.tvtime.ui.screens.tabs.SettingsTab -import com.owenlejeune.tvtime.ui.screens.tabs.TvTab +import com.owenlejeune.tvtime.ui.screens.MediaViewType +import com.owenlejeune.tvtime.ui.screens.tabs.bottom.FavouritesTab +import com.owenlejeune.tvtime.ui.screens.tabs.bottom.MediaTab +import com.owenlejeune.tvtime.ui.screens.tabs.bottom.SettingsTab object NavConstants { const val ID_KEY = "id_key" @@ -33,7 +31,7 @@ fun MainNavigationRoutes(navController: NavHostController, displayUnderStatusBar MainNavItem.DetailView.route.plus("/{${NavConstants.TYPE_KEY}}/{${NavConstants.ID_KEY}}"), arguments = listOf( navArgument(NavConstants.ID_KEY) { type = NavType.IntType }, - navArgument(NavConstants.TYPE_KEY) { type = NavType.EnumType(DetailViewType::class.java) } + navArgument(NavConstants.TYPE_KEY) { type = NavType.EnumType(MediaViewType::class.java) } ) ) { navBackStackEntry -> displayUnderStatusBar.value = true @@ -41,7 +39,7 @@ fun MainNavigationRoutes(navController: NavHostController, displayUnderStatusBar DetailView( appNavController = navController, itemId = args?.getInt(NavConstants.ID_KEY), - type = args?.getSerializable(NavConstants.TYPE_KEY) as DetailViewType + type = args?.getSerializable(NavConstants.TYPE_KEY) as MediaViewType ) } } @@ -49,15 +47,15 @@ fun MainNavigationRoutes(navController: NavHostController, displayUnderStatusBar @Composable fun BottomNavigationRoutes( - appNavController: NavController, + appNavController: NavHostController, navController: NavHostController ) { NavHost(navController = navController, startDestination = BottomNavItem.Movies.route) { composable(BottomNavItem.Movies.route) { - MoviesTab(appNavController = appNavController) + MediaTab(appNavController = appNavController, mediaType = MediaViewType.MOVIE) } composable(BottomNavItem.TV.route) { - TvTab(appNavController = appNavController) + MediaTab(appNavController = appNavController, mediaType = MediaViewType.TV) } composable(BottomNavItem.Favourites.route) { FavouritesTab() diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/DetailView.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/DetailView.kt index 5f9963c..5a4035b 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/DetailView.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/DetailView.kt @@ -14,7 +14,10 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text -import androidx.compose.runtime.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color @@ -29,11 +32,14 @@ import coil.transform.RoundedCornersTransformation import com.owenlejeune.tvtime.R import com.owenlejeune.tvtime.api.tmdb.DetailService import com.owenlejeune.tvtime.api.tmdb.MoviesService -import com.owenlejeune.tvtime.utils.TmdbUtils import com.owenlejeune.tvtime.api.tmdb.TvService import com.owenlejeune.tvtime.api.tmdb.model.* import com.owenlejeune.tvtime.extensions.dpToPx -import com.owenlejeune.tvtime.ui.components.* +import com.owenlejeune.tvtime.ui.components.BackdropImage +import com.owenlejeune.tvtime.ui.components.ChipGroup +import com.owenlejeune.tvtime.ui.components.MinLinesText +import com.owenlejeune.tvtime.ui.components.PosterItem +import com.owenlejeune.tvtime.utils.TmdbUtils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -43,11 +49,11 @@ import kotlinx.coroutines.withContext fun DetailView( appNavController: NavController, itemId: Int?, - type: DetailViewType + type: MediaViewType ) { val service = when(type) { - DetailViewType.MOVIE -> MoviesService() - DetailViewType.TV -> TvService() + MediaViewType.MOVIE -> MoviesService() + MediaViewType.TV -> TvService() } val mediaItem = remember { mutableStateOf(null) } @@ -176,7 +182,7 @@ private fun ContentColumn(modifier: Modifier, itemId: Int?, mediaItem: MutableState, service: DetailService, - mediaType: DetailViewType + mediaType: MediaViewType ) { Column( modifier = modifier @@ -185,7 +191,7 @@ private fun ContentColumn(modifier: Modifier, .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) ) { - if (mediaType == DetailViewType.MOVIE) { + if (mediaType == MediaViewType.MOVIE) { MiscMovieDetails(mediaItem = mediaItem, service as MoviesService) } else { MiscTvDetails(mediaItem = mediaItem, service as TvService) @@ -438,9 +444,4 @@ private fun fetchTvContentRating(id: Int, service: TvService, contentRating: Mut } } } -} - -enum class DetailViewType { - MOVIE, - TV } \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MainView.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MainView.kt index 25c2afe..2ce5dd9 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MainView.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MainView.kt @@ -10,15 +10,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.res.painterResource import androidx.navigation.NavController +import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import com.google.accompanist.pager.ExperimentalPagerApi import com.owenlejeune.tvtime.ui.components.SearchFab import com.owenlejeune.tvtime.ui.navigation.BottomNavItem import com.owenlejeune.tvtime.ui.navigation.BottomNavigationRoutes -@OptIn(ExperimentalMaterial3Api::class) +@OptIn(ExperimentalMaterial3Api::class, ExperimentalPagerApi::class) @Composable -fun MainAppView(appNavController: NavController) { +fun MainAppView(appNavController: NavHostController) { val navController = rememberNavController() val navBackStackEntry by navController.currentBackStackEntryAsState() val currentRoute = navBackStackEntry?.destination?.route @@ -83,7 +85,12 @@ private fun BottomNavBar(navController: NavController, appBarTitle: MutableState appBarTitle = appBarTitle, item = item ) - } + }, + colors = NavigationBarItemDefaults + .colors( + selectedIconColor = MaterialTheme.colorScheme.onPrimary, + indicatorColor = MaterialTheme.colorScheme.primary + ) ) } } diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaViewType.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaViewType.kt new file mode 100644 index 0000000..6020758 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/MediaViewType.kt @@ -0,0 +1,6 @@ +package com.owenlejeune.tvtime.ui.screens + +enum class MediaViewType { + MOVIE, + TV +} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/MoviesTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/MoviesTab.kt deleted file mode 100644 index 3f05d7f..0000000 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/MoviesTab.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.owenlejeune.tvtime.ui.screens.tabs - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.runtime.Composable -import androidx.navigation.NavController -import com.owenlejeune.tvtime.api.tmdb.MoviesService -import com.owenlejeune.tvtime.ui.components.PosterGrid -import com.owenlejeune.tvtime.ui.navigation.MainNavItem -import com.owenlejeune.tvtime.ui.screens.DetailViewType -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun MoviesTab(appNavController: NavController) { -// val moviesViewModel = viewModel(PopularMovieViewModel::class.java) -// val moviesList = moviesViewModel.moviePage -// val movieListItems: LazyPagingItems = moviesList.collectAsLazyPagingItems() - PosterGrid( - fetchMedia = { moviesList -> - val service = MoviesService() - CoroutineScope(Dispatchers.IO).launch { - val response = service.getPopularMovies() - if (response.isSuccessful) { - withContext(Dispatchers.Main) { - moviesList.value = response.body()!!.movies - } - } - } - }, - onClick = { id -> - appNavController.navigate( - "${MainNavItem.DetailView.route}/${DetailViewType.MOVIE}/${id}" - ) - } - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/TvTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/TvTab.kt deleted file mode 100644 index 24de917..0000000 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/TvTab.kt +++ /dev/null @@ -1,36 +0,0 @@ -package com.owenlejeune.tvtime.ui.screens.tabs - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.runtime.Composable -import androidx.navigation.NavController -import com.owenlejeune.tvtime.api.tmdb.TvService -import com.owenlejeune.tvtime.ui.components.PosterGrid -import com.owenlejeune.tvtime.ui.navigation.MainNavItem -import com.owenlejeune.tvtime.ui.screens.DetailViewType -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext - -@OptIn(ExperimentalFoundationApi::class) -@Composable -fun TvTab(appNavController: NavController) { - PosterGrid( - fetchMedia = { tvList -> - val service = TvService() - CoroutineScope(Dispatchers.IO).launch { - val response = service.getPopularTv() - if (response.isSuccessful) { - withContext(Dispatchers.Main) { - tvList.value = response.body()!!.tv - } - } - } - }, - onClick = { id -> - appNavController.navigate( - "${MainNavItem.DetailView.route}/${DetailViewType.TV}/${id}" - ) - } - ) -} \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/FavouritesTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/bottom/FavouritesTab.kt similarity index 92% rename from app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/FavouritesTab.kt rename to app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/bottom/FavouritesTab.kt index db78953..0f771c7 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/FavouritesTab.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/bottom/FavouritesTab.kt @@ -1,4 +1,4 @@ -package com.owenlejeune.tvtime.ui.screens.tabs +package com.owenlejeune.tvtime.ui.screens.tabs.bottom import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/bottom/MediaTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/bottom/MediaTab.kt new file mode 100644 index 0000000..60c3133 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/bottom/MediaTab.kt @@ -0,0 +1,69 @@ +package com.owenlejeune.tvtime.ui.screens.tabs.bottom + +import androidx.compose.foundation.layout.Column +import androidx.compose.runtime.Composable +import androidx.navigation.NavHostController +import com.google.accompanist.pager.ExperimentalPagerApi +import com.google.accompanist.pager.rememberPagerState +import com.owenlejeune.tvtime.api.tmdb.HomePageService +import com.owenlejeune.tvtime.api.tmdb.MoviesService +import com.owenlejeune.tvtime.api.tmdb.TvService +import com.owenlejeune.tvtime.ui.components.PosterGrid +import com.owenlejeune.tvtime.ui.navigation.MainNavItem +import com.owenlejeune.tvtime.ui.navigation.MainTabNavItem +import com.owenlejeune.tvtime.ui.navigation.MediaFetchFun +import com.owenlejeune.tvtime.ui.screens.MediaViewType +import com.owenlejeune.tvtime.ui.screens.tabs.top.Tabs +import com.owenlejeune.tvtime.ui.screens.tabs.top.TabsContent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +@OptIn(ExperimentalPagerApi::class) +@Composable +fun MediaTab(appNavController: NavHostController, mediaType: MediaViewType) { + Column { + val tabs = when (mediaType) { + MediaViewType.MOVIE -> MainTabNavItem.MovieItems + MediaViewType.TV -> MainTabNavItem.TvItems + } + val pagerState = rememberPagerState() + Tabs(tabs = tabs, pagerState = pagerState) + TabsContent( + tabs = tabs, + pagerState = pagerState, + appNavController = appNavController, + mediaViewType = mediaType + ) + } +} + +@Composable +fun MediaTabContent(appNavController: NavHostController, mediaType: MediaViewType, mediaFetchFun: MediaFetchFun) { + val service: HomePageService = when(mediaType) { + MediaViewType.MOVIE -> MoviesService() + MediaViewType.TV -> TvService() + } + PosterGrid( + fetchMedia = { mediaList -> + CoroutineScope(Dispatchers.IO).launch { + val response = mediaFetchFun.invoke(service, 1) + if (response.isSuccessful) { + withContext(Dispatchers.Main) { + mediaList.value = response.body()?.results ?: emptyList() + } + } + } + }, + onClick = { id -> + appNavController.navigate( + "${MainNavItem.DetailView.route}/${mediaType}/${id}" + ) + } + ) +} + +// val moviesViewModel = viewModel(PopularMovieViewModel::class.java) +// val moviesList = moviesViewModel.moviePage +// val movieListItems: LazyPagingItems = moviesList.collectAsLazyPagingItems() \ No newline at end of file diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/SettingsTab.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/bottom/SettingsTab.kt similarity index 97% rename from app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/SettingsTab.kt rename to app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/bottom/SettingsTab.kt index 56a19b0..291910e 100644 --- a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/SettingsTab.kt +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/bottom/SettingsTab.kt @@ -1,4 +1,4 @@ -package com.owenlejeune.tvtime.ui.screens.tabs +package com.owenlejeune.tvtime.ui.screens.tabs.bottom import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column diff --git a/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/top/TabsCommon.kt b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/top/TabsCommon.kt new file mode 100644 index 0000000..a44bec6 --- /dev/null +++ b/app/src/main/java/com/owenlejeune/tvtime/ui/screens/tabs/top/TabsCommon.kt @@ -0,0 +1,122 @@ +package com.owenlejeune.tvtime.ui.screens.tabs.top + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.ScrollableTabRow +import androidx.compose.material.Tab +import androidx.compose.material.TabRowDefaults.tabIndicatorOffset +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.compose.rememberNavController +import com.google.accompanist.pager.ExperimentalPagerApi +import com.google.accompanist.pager.HorizontalPager +import com.google.accompanist.pager.PagerState +import com.google.accompanist.pager.rememberPagerState +import com.owenlejeune.tvtime.ui.navigation.MainTabNavItem +import com.owenlejeune.tvtime.ui.navigation.TabNavItem +import com.owenlejeune.tvtime.ui.screens.MediaViewType +import kotlinx.coroutines.launch + +@OptIn(ExperimentalPagerApi::class) +@Composable +fun Tabs( + tabs: List, + pagerState: PagerState, + modifier: Modifier = Modifier, + backgroundColor: Color = MaterialTheme.colorScheme.background, + contentColor: Color = MaterialTheme.colorScheme.primary, + selectedTabTextColor: Color = MaterialTheme.colorScheme.primary, + unselectedTabTextColor: Color = MaterialTheme.colorScheme.onBackground, + tabTextStyle: TextStyle = MaterialTheme.typography.bodySmall, + tabIndicatorColor: Color = MaterialTheme.colorScheme.primary +) { + val scope = rememberCoroutineScope() + + ScrollableTabRow( + modifier = modifier, + selectedTabIndex = pagerState.currentPage, + backgroundColor = backgroundColor, + contentColor = contentColor, + edgePadding = 8.dp, + indicator = { tabPositions -> + SmallTabIndicator( + modifier = Modifier.tabIndicatorOffset(tabPositions[pagerState.currentPage]), + color = tabIndicatorColor + ) + } + ) { + tabs.forEachIndexed { index, tab -> + Tab( + text = { + Text( + text = tab.name, + style = tabTextStyle, + color = if (pagerState.currentPage == index) selectedTabTextColor else unselectedTabTextColor + ) + }, + selected = pagerState.currentPage == index, + onClick = { + scope.launch { + pagerState.animateScrollToPage(index) + } + } + ) + } + } +} + +@Composable +private fun SmallTabIndicator( + modifier: Modifier = Modifier, + color: Color = MaterialTheme.colorScheme.primary +) { + Spacer( + modifier + .padding(horizontal = 28.dp) + .height(2.dp) + .background(color, RoundedCornerShape(topStartPercent = 100, topEndPercent = 100)) + ) +} + +@OptIn(ExperimentalPagerApi::class) +@Preview(showBackground = true) +@Composable +fun TabsPreview() { + val tabs = MainTabNavItem.MovieItems + val pagerState = rememberPagerState() + Tabs(tabs = tabs, pagerState = pagerState) +} + +@OptIn(ExperimentalPagerApi::class) +@Composable +fun TabsContent( + tabs: List, + pagerState: PagerState, + mediaViewType: MediaViewType, + appNavController: NavHostController = rememberNavController() +) { + HorizontalPager(count = tabs.size, state = pagerState) { page -> + tabs[page].screen(appNavController, mediaViewType, tabs[page].mediaFetchFun) + } +} + +@OptIn(ExperimentalPagerApi::class) +@Preview(showBackground = true) +@Composable +fun TabsContentPreview() { + val tabs = MainTabNavItem.MovieItems + val pagerState = rememberPagerState() + TabsContent(tabs = tabs, pagerState = pagerState, MediaViewType.MOVIE) +} + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d8e2c74..e6ab375 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,4 +5,10 @@ Favourites Settings Cast + Now Playing + Popular + Top Rated + Upcoming + Airing Today + On The Air \ No newline at end of file