mirror of
https://github.com/owenlejeune/TVTime.git
synced 2025-11-23 04:00:53 -05:00
show poster on details screen
This commit is contained in:
@@ -65,6 +65,7 @@ dependencies {
|
||||
implementation "com.google.accompanist:accompanist-systemuicontroller:${Versions.compose_accompanist}"
|
||||
implementation "androidx.navigation:navigation-compose:${Versions.compose_navigation}"
|
||||
implementation "androidx.paging:paging-compose:${Versions.compose_paging}"
|
||||
implementation "androidx.constraintlayout:constraintlayout-compose:${Versions.compose_constraint_layout}"
|
||||
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:${Versions.lifecycle_runtime}"
|
||||
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb
|
||||
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
||||
import retrofit2.Response
|
||||
|
||||
interface DetailService {
|
||||
|
||||
suspend fun getById(id: Int): Response<DetailedItem>
|
||||
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb
|
||||
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedMovie
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.PopularMovie
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.PopularMoviesResponse
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface MoviesApi {
|
||||
@@ -10,4 +13,7 @@ interface MoviesApi {
|
||||
@GET("movie/popular")
|
||||
suspend fun getPopularMovies(@Query("page") page: Int = 1): Response<PopularMoviesResponse>
|
||||
|
||||
@GET("movie/{id}")
|
||||
suspend fun getMovieById(@Path("id") id: Int): Response<DetailedMovie>
|
||||
|
||||
}
|
||||
@@ -1,11 +1,15 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb
|
||||
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
||||
import org.koin.core.component.KoinComponent
|
||||
import retrofit2.Response
|
||||
|
||||
class MoviesService: KoinComponent {
|
||||
class MoviesService: KoinComponent, DetailService {
|
||||
|
||||
private val service by lazy { TmdbClient().createMovieService() }
|
||||
|
||||
suspend fun getPopularMovies(page: Int = 1) = service.getPopularMovies(page)
|
||||
|
||||
override suspend fun getById(id: Int): Response<DetailedItem> = service.getMovieById(id) as Response<DetailedItem>
|
||||
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb
|
||||
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.MediaItem
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.TmdbItem
|
||||
|
||||
object TmdbUtils {
|
||||
|
||||
@@ -8,8 +8,8 @@ object TmdbUtils {
|
||||
return "https://image.tmdb.org/t/p/original${posterPath}"
|
||||
}
|
||||
|
||||
fun getFullPosterPath(mediaItem: MediaItem): String {
|
||||
return getFullPosterPath(mediaItem.posterPath)
|
||||
fun getFullPosterPath(tmdbItem: TmdbItem): String {
|
||||
return getFullPosterPath(tmdbItem.posterPath)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb
|
||||
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.PopularTvResponse
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedTv
|
||||
import retrofit2.Response
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface TvApi {
|
||||
@@ -10,4 +12,7 @@ interface TvApi {
|
||||
@GET("tv/popular")
|
||||
suspend fun getPoplarTv(@Query("page") page: Int = 1): Response<PopularTvResponse>
|
||||
|
||||
@GET("tv/{id}")
|
||||
suspend fun getTvShowById(@Path("id") id: Int): Response<DetailedTv>
|
||||
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb
|
||||
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
||||
import org.koin.core.component.KoinComponent
|
||||
import retrofit2.Response
|
||||
|
||||
class TvService: KoinComponent {
|
||||
class TvService: KoinComponent, DetailService {
|
||||
|
||||
private val service by lazy { TmdbClient().createTvService() }
|
||||
|
||||
suspend fun getPopularTv(page: Int = 1) = service.getPoplarTv(page)
|
||||
|
||||
override suspend fun getById(id: Int): Response<DetailedItem> {
|
||||
return service.getTvShowById(id) as Response<DetailedItem>
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
abstract class DetailedItem(id: Int, title: String, posterPath: String?): TmdbItem(id, title, posterPath)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
class DetailedMovie(
|
||||
@SerializedName("id") override val id: Int,
|
||||
@SerializedName("origina_title") override val title: String,
|
||||
@SerializedName("poster_path") override val posterPath: String?
|
||||
): DetailedItem(id, title, posterPath)
|
||||
@@ -0,0 +1,9 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
||||
|
||||
class DetailedTv(
|
||||
id: Int,
|
||||
title: String,
|
||||
posterPath: String?
|
||||
): DetailedItem(id, title, posterPath)
|
||||
@@ -1,7 +0,0 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
abstract class MediaItem(
|
||||
open val posterPath: String?,
|
||||
@Transient open val title: String,
|
||||
@Transient open val id: Int
|
||||
)
|
||||
@@ -3,7 +3,7 @@ package com.owenlejeune.tvtime.api.tmdb.model
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PopularMovie(
|
||||
@SerializedName("poster_path") override val posterPath: String?,
|
||||
@SerializedName("id") override val id: Int,
|
||||
@SerializedName("title") override val title: String,
|
||||
@SerializedName("id") override val id: Int
|
||||
): MediaItem(posterPath, title, id)
|
||||
@SerializedName("poster_path") override val posterPath: String?
|
||||
): TmdbItem(id, title, posterPath)
|
||||
@@ -3,7 +3,7 @@ package com.owenlejeune.tvtime.api.tmdb.model
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class PopularTv(
|
||||
@SerializedName("poster_path") override val posterPath: String?,
|
||||
@SerializedName("name") val name: String,
|
||||
@SerializedName("id") override val id: Int
|
||||
): MediaItem(posterPath, name, id)
|
||||
@SerializedName("id") override val id: Int,
|
||||
@SerializedName("name") override val title: String,
|
||||
@SerializedName("poster_path") override val posterPath: String?
|
||||
): TmdbItem(id, title, posterPath)
|
||||
@@ -0,0 +1,7 @@
|
||||
package com.owenlejeune.tvtime.api.tmdb.model
|
||||
|
||||
abstract class TmdbItem(
|
||||
@Transient open val id: Int,
|
||||
@Transient open val title: String,
|
||||
@Transient open val posterPath: String?
|
||||
)
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.owenlejeune.tvtime.ui.components
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.ExperimentalFoundationApi
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
@@ -15,25 +14,29 @@ import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.navigation.NavController
|
||||
import coil.compose.rememberImagePainter
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.api.tmdb.TmdbUtils
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.MediaItem
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.TmdbItem
|
||||
import com.owenlejeune.tvtime.extensions.dpToPx
|
||||
import com.owenlejeune.tvtime.extensions.listItems
|
||||
import com.owenlejeune.tvtime.ui.navigation.MainNavItem
|
||||
import com.owenlejeune.tvtime.ui.screens.DetailViewType
|
||||
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun PosterGrid(
|
||||
appNavController: NavController,
|
||||
fetchMedia: (MutableState<List<MediaItem>>) -> Unit
|
||||
type: DetailViewType,
|
||||
fetchMedia: (MutableState<List<TmdbItem>>) -> Unit
|
||||
) {
|
||||
val mediaList = remember { mutableStateOf(emptyList<MediaItem>()) }
|
||||
val mediaList = remember { mutableStateOf(emptyList<TmdbItem>()) }
|
||||
fetchMedia(mediaList)
|
||||
|
||||
LazyVerticalGrid(
|
||||
@@ -41,36 +44,50 @@ fun PosterGrid(
|
||||
contentPadding = PaddingValues(8.dp)
|
||||
) {
|
||||
listItems(mediaList.value) { item ->
|
||||
PosterItem(appNavController = appNavController, mediaItem = item)
|
||||
PosterItem(
|
||||
appNavController = appNavController,
|
||||
mediaItem = item,
|
||||
type = type
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun PosterItem(
|
||||
modifier: Modifier = Modifier,
|
||||
appNavController: NavController,
|
||||
mediaItem: MediaItem
|
||||
mediaItem: TmdbItem?,
|
||||
type: DetailViewType? = null,
|
||||
width: Dp = 127.dp,
|
||||
height: Dp = 190.dp
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val poster = TmdbUtils.getFullPosterPath(mediaItem)
|
||||
val poster = mediaItem?.let { TmdbUtils.getFullPosterPath(mediaItem) }
|
||||
Image(
|
||||
painter = rememberImagePainter(
|
||||
data = poster,
|
||||
builder = {
|
||||
transformations(RoundedCornersTransformation(5f.dpToPx(context)))
|
||||
placeholder(R.drawable.placeholder)
|
||||
}
|
||||
),
|
||||
contentDescription = mediaItem.title,
|
||||
modifier = Modifier
|
||||
.size(190.dp)
|
||||
painter = if (mediaItem != null) {
|
||||
rememberImagePainter(
|
||||
data = poster,
|
||||
builder = {
|
||||
transformations(RoundedCornersTransformation(5f.dpToPx(context)))
|
||||
placeholder(R.drawable.placeholder)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
rememberImagePainter(ContextCompat.getDrawable(context, R.drawable.placeholder))
|
||||
},
|
||||
contentDescription = mediaItem?.title,
|
||||
modifier = modifier
|
||||
.size(width = width, height = height)
|
||||
.padding(5.dp)
|
||||
.clickable {
|
||||
appNavController.navigate("${MainNavItem.DetailView.route}/${mediaItem.id}")
|
||||
// appNavController.n
|
||||
// Toast
|
||||
// .makeText(context, "${mediaItem.title} clicked", Toast.LENGTH_SHORT)
|
||||
// .show()
|
||||
type?.let {
|
||||
mediaItem?.let {
|
||||
appNavController.navigate(
|
||||
"${MainNavItem.DetailView.route}/${type}/${mediaItem.id}"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -6,9 +6,6 @@ import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.*
|
||||
import androidx.compose.foundation.gestures.detectTapGestures
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.foundation.lazy.GridCells
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyVerticalGrid
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.icons.Icons
|
||||
@@ -26,20 +23,12 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.geometry.CornerRadius
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.input.pointer.pointerInput
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.rememberImagePainter
|
||||
import coil.transform.RoundedCornersTransformation
|
||||
import com.owenlejeune.tvtime.R
|
||||
import com.owenlejeune.tvtime.api.tmdb.TmdbUtils
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.MediaItem
|
||||
import com.owenlejeune.tvtime.extensions.dpToPx
|
||||
import com.owenlejeune.tvtime.extensions.listItems
|
||||
|
||||
@Composable
|
||||
fun TopLevelSwitch(
|
||||
|
||||
@@ -7,7 +7,9 @@ import androidx.navigation.NavType
|
||||
import androidx.navigation.compose.NavHost
|
||||
import androidx.navigation.compose.composable
|
||||
import androidx.navigation.navArgument
|
||||
import com.owenlejeune.tvtime.ui.screens.*
|
||||
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
|
||||
@@ -20,13 +22,17 @@ fun MainNavigationRoutes(navController: NavHostController) {
|
||||
MainAppView(appNavController = navController)
|
||||
}
|
||||
composable(
|
||||
MainNavItem.DetailView.route.plus("/{ID_KEY}"),
|
||||
arguments = listOf(navArgument("ID_KEY") { type = NavType.IntType })
|
||||
MainNavItem.DetailView.route.plus("/{TYPE_KEY}/{ID_KEY}"),
|
||||
arguments = listOf(
|
||||
navArgument("ID_KEY") { type = NavType.IntType },
|
||||
navArgument("TYPE_KEY") { type = NavType.EnumType(DetailViewType::class.java) }
|
||||
)
|
||||
) { navBackStackEntry ->
|
||||
val args = navBackStackEntry.arguments
|
||||
DetailView(
|
||||
appNavController = navController,
|
||||
itemId = navBackStackEntry.arguments?.getInt("ID_KEY")
|
||||
itemId = args?.getInt("ID_KEY"),
|
||||
type = args?.getSerializable("TYPE_KEY") as DetailViewType
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,28 +1,71 @@
|
||||
package com.owenlejeune.tvtime.ui.screens
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.constraintlayout.compose.ConstraintLayout
|
||||
import androidx.navigation.NavController
|
||||
import com.owenlejeune.tvtime.api.tmdb.DetailService
|
||||
import com.owenlejeune.tvtime.api.tmdb.MoviesService
|
||||
import com.owenlejeune.tvtime.api.tmdb.TvService
|
||||
import com.owenlejeune.tvtime.api.tmdb.model.DetailedItem
|
||||
import com.owenlejeune.tvtime.ui.components.PosterItem
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
@Composable
|
||||
fun DetailView(
|
||||
appNavController: NavController,
|
||||
itemId: Int?
|
||||
itemId: Int?,
|
||||
type: DetailViewType
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.wrapContentSize(Alignment.Center)
|
||||
val mediaItem = remember { mutableStateOf<DetailedItem?>(null) }
|
||||
val service = when(type) {
|
||||
DetailViewType.MOVIE -> MoviesService()
|
||||
DetailViewType.TV -> TvService()
|
||||
}
|
||||
itemId?.let {
|
||||
fetchMediaItem(itemId, service, mediaItem)
|
||||
}
|
||||
|
||||
ConstraintLayout(
|
||||
modifier = Modifier.fillMaxSize()
|
||||
) {
|
||||
Text(
|
||||
text = itemId.toString(),
|
||||
color = MaterialTheme.colorScheme.onBackground
|
||||
val (
|
||||
posterImage,
|
||||
title
|
||||
) = createRefs()
|
||||
|
||||
PosterItem(
|
||||
appNavController = appNavController,
|
||||
mediaItem = mediaItem.value,
|
||||
modifier = Modifier
|
||||
.constrainAs(posterImage) {
|
||||
top.linkTo(parent.top, margin = 16.dp)
|
||||
start.linkTo(parent.start, margin = 16.dp)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun fetchMediaItem(id: Int, service: DetailService, mediaItem: MutableState<DetailedItem?>) {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val response = service.getById(id)
|
||||
if (response.isSuccessful) {
|
||||
withContext(Dispatchers.Main) {
|
||||
mediaItem.value = response.body()!!
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum class DetailViewType {
|
||||
MOVIE,
|
||||
TV
|
||||
}
|
||||
@@ -5,6 +5,7 @@ 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.screens.DetailViewType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -16,7 +17,7 @@ fun MoviesTab(appNavController: NavController) {
|
||||
// val moviesViewModel = viewModel(PopularMovieViewModel::class.java)
|
||||
// val moviesList = moviesViewModel.moviePage
|
||||
// val movieListItems: LazyPagingItems<PopularMovie> = moviesList.collectAsLazyPagingItems()
|
||||
PosterGrid(appNavController = appNavController) { moviesList ->
|
||||
PosterGrid(appNavController = appNavController, type = DetailViewType.MOVIE) { moviesList ->
|
||||
val service = MoviesService()
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val response = service.getPopularMovies()
|
||||
|
||||
@@ -5,6 +5,7 @@ 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.screens.DetailViewType
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -13,7 +14,7 @@ import kotlinx.coroutines.withContext
|
||||
@OptIn(ExperimentalFoundationApi::class)
|
||||
@Composable
|
||||
fun TvTab(appNavController: NavController) {
|
||||
PosterGrid(appNavController = appNavController) { tvList ->
|
||||
PosterGrid(appNavController = appNavController, type = DetailViewType.TV) { tvList ->
|
||||
val service = TvService()
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val response = service.getPopularTv()
|
||||
|
||||
@@ -7,6 +7,7 @@ object Versions {
|
||||
const val compose_accompanist = "0.22.1-rc"
|
||||
const val compose_navigation = "2.4.0"
|
||||
const val compose_paging = "1.0.0-alpha14"
|
||||
const val compose_constraint_layout = "1.0.0"
|
||||
const val gradle = "7.1.0"
|
||||
const val junit = "4.13.2"
|
||||
const val androidx_junit = "1.1.3"
|
||||
|
||||
Reference in New Issue
Block a user