mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-22 11:40:54 -05:00
add tabs to top of movies and tv screens
This commit is contained in:
@@ -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<Boolean> = mutableStateOf(false)) {
|
||||
fun MyApp(
|
||||
appNavController: NavHostController = rememberNavController(),
|
||||
displayUnderStatusBar: MutableState<Boolean> = mutableStateOf(false)
|
||||
) {
|
||||
TVTimeTheme {
|
||||
val appNavController = rememberNavController()
|
||||
Box {
|
||||
MainNavigationRoutes(navController = appNavController, displayUnderStatusBar = displayUnderStatusBar)
|
||||
}
|
||||
|
||||
@@ -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<out HomePageResponse>
|
||||
|
||||
suspend fun getPopular(page: Int = 1): Response<out HomePageResponse>
|
||||
|
||||
suspend fun getTopRated(page: Int = 1): Response<out HomePageResponse>
|
||||
|
||||
suspend fun getUpcoming(page: Int = 1): Response<out HomePageResponse>
|
||||
|
||||
}
|
||||
@@ -9,7 +9,16 @@ import retrofit2.http.Query
|
||||
interface MoviesApi {
|
||||
|
||||
@GET("movie/popular")
|
||||
suspend fun getPopularMovies(@Query("page") page: Int = 1): Response<PopularMoviesResponse>
|
||||
suspend fun getPopularMovies(@Query("page") page: Int = 1): Response<HomePageMoviesResponse>
|
||||
|
||||
@GET("movie/now_playing")
|
||||
suspend fun getNowPlayingMovies(@Query("page") page: Int = 1): Response<HomePageMoviesResponse>
|
||||
|
||||
@GET("movie/top_rated")
|
||||
suspend fun getTopRatedMovies(@Query("page") page: Int = 1): Response<HomePageMoviesResponse>
|
||||
|
||||
@GET("movie/upcoming")
|
||||
suspend fun getUpcomingMovies(@Query("page") page: Int = 1): Response<HomePageMoviesResponse>
|
||||
|
||||
@GET("movie/{id}")
|
||||
suspend fun getMovieById(@Path("id") id: Int): Response<DetailedMovie>
|
||||
|
||||
@@ -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<PopularMoviesResponse> {
|
||||
override suspend fun getPopular(page: Int): Response<out HomePageResponse> {
|
||||
return service.getPopularMovies(page)
|
||||
}
|
||||
|
||||
override suspend fun getNowPlaying(page: Int): Response<out HomePageResponse> {
|
||||
return service.getNowPlayingMovies(page)
|
||||
}
|
||||
|
||||
override suspend fun getTopRated(page: Int): Response<out HomePageResponse> {
|
||||
return service.getTopRatedMovies(page)
|
||||
}
|
||||
|
||||
override suspend fun getUpcoming(page: Int): Response<out HomePageResponse> {
|
||||
return service.getUpcomingMovies(page)
|
||||
}
|
||||
|
||||
suspend fun getReleaseDates(id: Int): Response<MovieReleaseResults> {
|
||||
return service.getReleaseDates(id)
|
||||
}
|
||||
|
||||
@@ -9,7 +9,16 @@ import retrofit2.http.Query
|
||||
interface TvApi {
|
||||
|
||||
@GET("tv/popular")
|
||||
suspend fun getPoplarTv(@Query("page") page: Int = 1): Response<PopularTvResponse>
|
||||
suspend fun getPoplarTv(@Query("page") page: Int = 1): Response<HomePageTvResponse>
|
||||
|
||||
@GET("tv/top_rated")
|
||||
suspend fun getTopRatedTv(@Query("page") page: Int = 1): Response<HomePageTvResponse>
|
||||
|
||||
@GET("tv/airing_today")
|
||||
suspend fun getTvAiringToday(@Query("page") page: Int = 1): Response<HomePageTvResponse>
|
||||
|
||||
@GET("tv/on_the_air")
|
||||
suspend fun getTvOnTheAir(@Query("page") page: Int = 1): Response<HomePageTvResponse>
|
||||
|
||||
@GET("tv/{id}")
|
||||
suspend fun getTvShowById(@Path("id") id: Int): Response<out DetailedTv>
|
||||
|
||||
@@ -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<out HomePageResponse> {
|
||||
return service.getPoplarTv(page)
|
||||
}
|
||||
|
||||
override suspend fun getNowPlaying(page: Int): Response<out HomePageResponse> {
|
||||
return service.getTvAiringToday(page)
|
||||
}
|
||||
|
||||
override suspend fun getTopRated(page: Int): Response<out HomePageResponse> {
|
||||
return service.getTopRatedTv(page)
|
||||
}
|
||||
|
||||
override suspend fun getUpcoming(page: Int): Response<out HomePageResponse> {
|
||||
return service.getTvOnTheAir(page)
|
||||
}
|
||||
|
||||
override suspend fun getById(id: Int): Response<out DetailedItem> {
|
||||
return service.getTvShowById(id)
|
||||
|
||||
@@ -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)
|
||||
): TmdbItem(id, posterPath, title)
|
||||
@@ -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<Genre>,
|
||||
@SerializedName("overview") override val overview: String?,
|
||||
|
||||
@@ -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<Genre>,
|
||||
@SerializedName("overview") override val overview: String?,
|
||||
|
||||
@@ -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)
|
||||
@@ -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<HomePageMovie>
|
||||
): HomePageResponse(count, page, results)
|
||||
@@ -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<PopularTv>
|
||||
@Transient open val results: List<TmdbItem>
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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<HomePageTv>
|
||||
): HomePageResponse(count, page, results)
|
||||
@@ -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)
|
||||
@@ -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<PopularMovie>
|
||||
)
|
||||
@@ -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)
|
||||
@@ -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
|
||||
)
|
||||
@@ -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<out HomePageResponse>
|
||||
|
||||
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) } )
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
@@ -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<DetailedItem?>(null) }
|
||||
@@ -176,7 +182,7 @@ private fun ContentColumn(modifier: Modifier,
|
||||
itemId: Int?,
|
||||
mediaItem: MutableState<DetailedItem?>,
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.owenlejeune.tvtime.ui.screens
|
||||
|
||||
enum class MediaViewType {
|
||||
MOVIE,
|
||||
TV
|
||||
}
|
||||
@@ -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<PopularMovie> = 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}"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -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}"
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -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
|
||||
@@ -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<PopularMovie> = moviesList.collectAsLazyPagingItems()
|
||||
@@ -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
|
||||
@@ -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<TabNavItem>,
|
||||
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<TabNavItem>,
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user